diff --git a/app/page.tsx b/app/page.tsx
index 531ed1e..5d9147d 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,16 +1,19 @@
import * as React from 'react';
-import { Zap, Shield, MousePointer2, Sparkles, ArrowRight, Check, X, Layout, Database, Workflow } from 'lucide-react';
+import { Zap, Shield, ArrowRight, Check, X, Layout, Database, Workflow } from 'lucide-react';
import { Reveal } from '../src/components/Reveal';
import { Section } from '../src/components/Section';
-import { HeroItem, FeatureCard, ComparisonRow, ServiceCard, HeroLines, GridLines, FlowLines, CirclePattern, ServicesFlow, ComparisonLines, ConnectorStart, ConnectorBranch, ConnectorSplit, ConnectorEnd } from '../src/components/Landing';
+import { HeroItem, FeatureCard, ComparisonRow, ServiceCard, HeroLines, GridLines, FlowLines, CirclePattern, ServicesFlow, ComparisonLines, ConnectorStart, ConnectorBranch, ConnectorSplit, ConnectorEnd, FlowingPath } from '../src/components/Landing';
export default function LandingPage() {
return (
-
- {/* Global Background Pattern */}
-
+ {/* Flowing Path Animation - wraps around content */}
+
+
+ {/* Subtle Grid Pattern Overlay */}
+
{/* Hero Section - Split Layout */}
diff --git a/src/components/Landing/FlowingPath.tsx b/src/components/Landing/FlowingPath.tsx
new file mode 100644
index 0000000..8189ff1
--- /dev/null
+++ b/src/components/Landing/FlowingPath.tsx
@@ -0,0 +1,333 @@
+'use client';
+
+import * as React from 'react';
+import { useEffect, useRef, useState, useCallback } from 'react';
+import { motion, useScroll, useTransform, useSpring } from 'framer-motion';
+
+interface FlowingPathProps {
+ className?: string;
+}
+
+interface PathNode {
+ x: number;
+ y: number;
+ type: 'curve' | 'line';
+}
+
+export const FlowingPath: React.FC
= ({ className = '' }) => {
+ const containerRef = useRef(null);
+ const [pathData, setPathData] = useState('');
+ const [pathLength, setPathLength] = useState(0);
+ const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
+ const pathRef = useRef(null);
+
+ const { scrollYProgress } = useScroll();
+
+ // Smooth spring animation for the path drawing
+ const smoothProgress = useSpring(scrollYProgress, {
+ stiffness: 50,
+ damping: 20,
+ restDelta: 0.001,
+ });
+
+ // Transform scroll progress to path drawing
+ const pathDrawn = useTransform(smoothProgress, [0, 1], [0, 1]);
+
+ // Accent path transform
+ const accentPathDrawn = useTransform(smoothProgress, [0, 1], [0, 0.95]);
+
+ // Dot transforms
+ const dot1Distance = useTransform(smoothProgress, [0, 1], ['0%', '100%']);
+ const dot2Distance = useTransform(smoothProgress, [0, 1], ['0%', '95%']);
+
+ // Generate a flowing path that wraps around content sections
+ const generatePath = useCallback((width: number, height: number): string => {
+ const margin = 60;
+ const contentWidth = Math.min(width * 0.85, 900);
+ const leftEdge = (width - contentWidth) / 2 - margin;
+ const rightEdge = (width + contentWidth) / 2 + margin;
+ const centerX = width / 2;
+
+ // Define key vertical positions (approximate section boundaries)
+ const sectionHeight = height / 6;
+
+ const nodes: PathNode[] = [
+ // Start from top left, flowing down
+ { x: leftEdge, y: 0, type: 'line' },
+ { x: leftEdge, y: sectionHeight * 0.3, type: 'curve' },
+
+ // Curve around hero section (right side)
+ { x: rightEdge, y: sectionHeight * 0.6, type: 'curve' },
+ { x: rightEdge, y: sectionHeight * 1.2, type: 'curve' },
+
+ // Flow to left for section 02
+ { x: leftEdge, y: sectionHeight * 1.5, type: 'curve' },
+ { x: leftEdge, y: sectionHeight * 2, type: 'curve' },
+
+ // Cross to right for section 03
+ { x: rightEdge, y: sectionHeight * 2.3, type: 'curve' },
+ { x: rightEdge, y: sectionHeight * 2.8, type: 'curve' },
+
+ // Back to left for section 04
+ { x: leftEdge, y: sectionHeight * 3.2, type: 'curve' },
+ { x: leftEdge, y: sectionHeight * 3.8, type: 'curve' },
+
+ // Right side for section 05
+ { x: rightEdge, y: sectionHeight * 4.2, type: 'curve' },
+ { x: rightEdge, y: sectionHeight * 4.8, type: 'curve' },
+
+ // Final section - converge to center
+ { x: centerX, y: sectionHeight * 5.5, type: 'curve' },
+ { x: centerX, y: height, type: 'line' },
+ ];
+
+ // Build SVG path with smooth bezier curves
+ let path = `M ${nodes[0].x} ${nodes[0].y}`;
+
+ for (let i = 1; i < nodes.length; i++) {
+ const prev = nodes[i - 1];
+ const curr = nodes[i];
+
+ if (curr.type === 'curve') {
+ // Calculate control points for smooth S-curves
+ const midY = (prev.y + curr.y) / 2;
+ const controlX1 = prev.x;
+ const controlY1 = midY;
+ const controlX2 = curr.x;
+ const controlY2 = midY;
+
+ path += ` C ${controlX1} ${controlY1}, ${controlX2} ${controlY2}, ${curr.x} ${curr.y}`;
+ } else {
+ path += ` L ${curr.x} ${curr.y}`;
+ }
+ }
+
+ return path;
+ }, []);
+
+ // Generate secondary decorative paths
+ const generateSecondaryPath = useCallback((width: number, height: number, offset: number): string => {
+ const margin = 40 + offset;
+ const contentWidth = Math.min(width * 0.85, 900);
+ const leftEdge = (width - contentWidth) / 2 - margin;
+ const rightEdge = (width + contentWidth) / 2 + margin;
+
+ const sectionHeight = height / 6;
+
+ let path = `M ${leftEdge + offset * 2} ${-50}`;
+
+ // Simpler parallel path
+ const points = [
+ { x: leftEdge, y: sectionHeight * 0.5 },
+ { x: rightEdge, y: sectionHeight * 1.0 },
+ { x: leftEdge, y: sectionHeight * 1.8 },
+ { x: rightEdge, y: sectionHeight * 2.5 },
+ { x: leftEdge, y: sectionHeight * 3.5 },
+ { x: rightEdge, y: sectionHeight * 4.5 },
+ { x: width / 2, y: height + 50 },
+ ];
+
+ for (let i = 0; i < points.length; i++) {
+ const curr = points[i];
+ const prev = i > 0 ? points[i - 1] : { x: leftEdge + offset * 2, y: -50 };
+ const midY = (prev.y + curr.y) / 2;
+ path += ` C ${prev.x} ${midY}, ${curr.x} ${midY}, ${curr.x} ${curr.y}`;
+ }
+
+ return path;
+ }, []);
+
+ // Update dimensions and path on resize
+ useEffect(() => {
+ const updateDimensions = () => {
+ if (typeof window !== 'undefined') {
+ const width = window.innerWidth;
+ const height = document.documentElement.scrollHeight;
+ setDimensions({ width, height });
+ setPathData(generatePath(width, height));
+ }
+ };
+
+ updateDimensions();
+
+ // Debounced resize handler
+ let timeoutId: NodeJS.Timeout;
+ const handleResize = () => {
+ clearTimeout(timeoutId);
+ timeoutId = setTimeout(updateDimensions, 100);
+ };
+
+ window.addEventListener('resize', handleResize);
+
+ // Also update when content might have changed
+ const observer = new ResizeObserver(handleResize);
+ if (document.body) {
+ observer.observe(document.body);
+ }
+
+ return () => {
+ window.removeEventListener('resize', handleResize);
+ observer.disconnect();
+ clearTimeout(timeoutId);
+ };
+ }, [generatePath]);
+
+ // Get path length after path is rendered
+ useEffect(() => {
+ if (pathRef.current) {
+ setPathLength(pathRef.current.getTotalLength());
+ }
+ }, [pathData]);
+
+ if (dimensions.width === 0 || dimensions.height === 0) {
+ return null;
+ }
+
+ return (
+
+
+
+ );
+};
+
+interface NodeMarkerProps {
+ pos: number;
+ pathData: string;
+ progress: any;
+}
+
+const NodeMarker: React.FC = ({ pos, pathData, progress }) => {
+ const opacity = useTransform(
+ progress,
+ [pos - 0.1, pos, pos + 0.05],
+ [0, 1, 1]
+ );
+
+ const scale = useTransform(
+ progress,
+ [pos - 0.05, pos, pos + 0.1],
+ [0.5, 1.2, 1]
+ );
+
+ return (
+
+ );
+};
+
+export default FlowingPath;
diff --git a/src/components/Landing/ParticleNetwork.tsx b/src/components/Landing/ParticleNetwork.tsx
new file mode 100644
index 0000000..64c6e98
--- /dev/null
+++ b/src/components/Landing/ParticleNetwork.tsx
@@ -0,0 +1,259 @@
+'use client';
+
+import * as React from 'react';
+import { useEffect, useRef, useCallback } from 'react';
+
+interface Particle {
+ x: number;
+ y: number;
+ baseX: number; // The "home" x position (in the gutters)
+ baseY: number;
+ vx: number;
+ vy: number;
+ size: number;
+ alpha: number;
+ targetAlpha: number;
+}
+
+interface ParticleNetworkProps {
+ className?: string;
+}
+
+export const ParticleNetwork: React.FC = ({ className = '' }) => {
+ const canvasRef = useRef(null);
+ const containerRef = useRef(null);
+ const particlesRef = useRef([]);
+ const animationFrameRef = useRef(0);
+ const scrollRef = useRef(0);
+ const mouseRef = useRef<{ x: number; y: number }>({ x: -1000, y: -1000 });
+ const dimensionsRef = useRef<{ width: number; height: number }>({ width: 0, height: 0 });
+
+ // Configuration
+ const config = {
+ particleCount: 60, // Reduced count for cleaner look
+ connectionDistance: 180,
+ mouseRadius: 250,
+ baseSpeed: 0.2,
+ gutterWidth: 0.25, // 25% of width on each side
+ colors: {
+ particle: 'rgba(148, 163, 184, 0.8)', // slate-400
+ line: 'rgba(203, 213, 225, 0.4)', // slate-300
+ },
+ };
+
+ // Initialize particles
+ const initParticles = useCallback((width: number, height: number) => {
+ const particles: Particle[] = [];
+ const count = config.particleCount;
+
+ // Create particles primarily in the side gutters
+ for (let i = 0; i < count; i++) {
+ const isLeft = Math.random() > 0.5;
+ // Random position within the gutter (0-25% or 75-100%)
+ const gutterX = isLeft
+ ? Math.random() * (width * config.gutterWidth)
+ : width - (Math.random() * (width * config.gutterWidth));
+
+ // Add some occasional strays near the content but not IN it
+ const x = gutterX;
+ const y = Math.random() * height;
+
+ particles.push({
+ x,
+ y,
+ baseX: x,
+ baseY: y,
+ vx: (Math.random() - 0.5) * 0.1, // Very slight horizontal drift
+ vy: 0.2 + Math.random() * 0.3, // Consistent downward flow
+ size: Math.random() * 1.5 + 0.5,
+ alpha: 0,
+ targetAlpha: Math.random() * 0.6 + 0.2,
+ });
+ }
+
+ particlesRef.current = particles;
+ }, [config.particleCount, config.gutterWidth]);
+
+ // Animation loop
+ const animate = useCallback(() => {
+ const canvas = canvasRef.current;
+ const ctx = canvas?.getContext('2d');
+ if (!canvas || !ctx) return;
+
+ const { width, height } = dimensionsRef.current;
+ const scroll = scrollRef.current;
+ const mouse = mouseRef.current;
+ const particles = particlesRef.current;
+
+ // Clear canvas
+ ctx.clearRect(0, 0, width, height);
+
+ // Update and draw particles
+ particles.forEach((p, i) => {
+ // 1. Movement
+ // Apply downward flow
+ p.y += p.vy;
+ p.x += p.vx;
+
+ // Wrap around vertical
+ if (p.y > height) {
+ p.y = -10;
+ p.x = p.baseX; // Reset X to base to keep structure
+ }
+
+ // 2. Mouse Interaction (Repulsion)
+ const dx = p.x - mouse.x;
+ const dy = p.y - mouse.y;
+ const dist = Math.sqrt(dx * dx + dy * dy);
+
+ if (dist < config.mouseRadius) {
+ const force = (config.mouseRadius - dist) / config.mouseRadius;
+ const angle = Math.atan2(dy, dx);
+ const pushX = Math.cos(angle) * force * 2;
+ const pushY = Math.sin(angle) * force * 2;
+
+ p.x += pushX;
+ p.y += pushY;
+ }
+
+ // 3. Return to "Lane" (Elasticity)
+ // Gently pull x back towards baseX if not influenced by mouse
+ if (dist >= config.mouseRadius) {
+ p.x += (p.baseX - p.x) * 0.02;
+ }
+
+ // 4. Scroll Effect (Parallax/Speed)
+ // Scroll adds a temporary velocity boost or shift
+ // We use a simple factor here, but could be more complex
+ const scrollFactor = Math.max(0, Math.min(1, scroll * 0.001));
+ p.y += scrollFactor * 0.5; // Move faster when scrolled down?
+ // Actually, let's make them react to scroll speed if we tracked delta,
+ // but for now just position shift based on scroll is handled by the container being fixed.
+ // Let's add a subtle wave based on scroll position
+ p.x += Math.sin(scroll * 0.002 + p.y * 0.01) * 0.2;
+
+ // Fade in
+ if (p.alpha < p.targetAlpha) p.alpha += 0.01;
+
+ // Draw Particle
+ ctx.beginPath();
+ ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
+ ctx.fillStyle = `rgba(148, 163, 184, ${p.alpha})`;
+ ctx.fill();
+ });
+
+ // Draw Connections
+ // We only connect particles that are close enough
+ ctx.lineWidth = 0.5;
+
+ for (let i = 0; i < particles.length; i++) {
+ const p1 = particles[i];
+ // Optimization: only check particles after this one
+ for (let j = i + 1; j < particles.length; j++) {
+ const p2 = particles[j];
+
+ // Quick check for distance
+ const dx = p1.x - p2.x;
+ const dy = p1.y - p2.y;
+
+ // Optimization: skip sqrt if obviously too far
+ if (Math.abs(dx) > config.connectionDistance || Math.abs(dy) > config.connectionDistance) continue;
+
+ const dist = Math.sqrt(dx * dx + dy * dy);
+
+ if (dist < config.connectionDistance) {
+ // Calculate opacity based on distance
+ const opacity = (1 - dist / config.connectionDistance) * 0.3;
+
+ ctx.beginPath();
+ ctx.moveTo(p1.x, p1.y);
+ ctx.lineTo(p2.x, p2.y);
+ ctx.strokeStyle = `rgba(203, 213, 225, ${opacity})`;
+ ctx.stroke();
+ }
+ }
+ }
+
+ animationFrameRef.current = requestAnimationFrame(animate);
+ }, [config]);
+
+ // Handle resize
+ const handleResize = useCallback(() => {
+ const container = containerRef.current;
+ const canvas = canvasRef.current;
+ if (!container || !canvas) return;
+
+ const rect = container.getBoundingClientRect();
+ const dpr = window.devicePixelRatio || 1;
+
+ canvas.width = rect.width * dpr;
+ canvas.height = rect.height * dpr;
+ canvas.style.width = `${rect.width}px`;
+ canvas.style.height = `${rect.height}px`;
+
+ const ctx = canvas.getContext('2d');
+ if (ctx) {
+ ctx.scale(dpr, dpr);
+ }
+
+ dimensionsRef.current = { width: rect.width, height: rect.height };
+ initParticles(rect.width, rect.height);
+ }, [initParticles]);
+
+ // Handle scroll
+ const handleScroll = useCallback(() => {
+ scrollRef.current = window.scrollY;
+ }, []);
+
+ // Handle mouse move
+ const handleMouseMove = useCallback((e: MouseEvent) => {
+ const container = containerRef.current;
+ if (!container) return;
+
+ const rect = container.getBoundingClientRect();
+ mouseRef.current = {
+ x: e.clientX - rect.left,
+ y: e.clientY - rect.top, // Fixed position, so no scrollY needed for mouse relative to canvas
+ };
+ }, []);
+
+ // Handle mouse leave
+ const handleMouseLeave = useCallback(() => {
+ mouseRef.current = { x: -1000, y: -1000 };
+ }, []);
+
+ useEffect(() => {
+ handleResize();
+ handleScroll();
+
+ window.addEventListener('resize', handleResize);
+ window.addEventListener('scroll', handleScroll, { passive: true });
+ window.addEventListener('mousemove', handleMouseMove, { passive: true });
+ window.addEventListener('mouseleave', handleMouseLeave);
+
+ animationFrameRef.current = requestAnimationFrame(animate);
+
+ return () => {
+ window.removeEventListener('resize', handleResize);
+ window.removeEventListener('scroll', handleScroll);
+ window.removeEventListener('mousemove', handleMouseMove);
+ window.removeEventListener('mouseleave', handleMouseLeave);
+ cancelAnimationFrame(animationFrameRef.current);
+ };
+ }, [handleResize, handleScroll, handleMouseMove, handleMouseLeave, animate]);
+
+ return (
+
+
+
+ );
+};
+
+export default ParticleNetwork;
diff --git a/src/components/Landing/index.ts b/src/components/Landing/index.ts
index d4e5737..0161484 100644
--- a/src/components/Landing/index.ts
+++ b/src/components/Landing/index.ts
@@ -3,3 +3,5 @@ export * from './FeatureCard';
export * from './ComparisonRow';
export * from './ServiceCard';
export * from './AbstractLines';
+export * from './ParticleNetwork';
+export * from './FlowingPath';