diff --git a/components/sections/AiCopilotSection.tsx b/components/sections/AiCopilotSection.tsx index faf8cfa..974ce43 100644 --- a/components/sections/AiCopilotSection.tsx +++ b/components/sections/AiCopilotSection.tsx @@ -98,7 +98,7 @@ export function AiCopilotSection() { {/* Middle Luminous Orb (Figma: 437x437 centered at x=608, y=218.5) */}
- + {/* */}
diff --git a/components/sections/HeroDotCanvas.tsx b/components/sections/HeroDotCanvas.tsx index bc1d22c..83aaa93 100644 --- a/components/sections/HeroDotCanvas.tsx +++ b/components/sections/HeroDotCanvas.tsx @@ -328,7 +328,7 @@ export function HeroDotCanvas({ sectionRef }: HeroDotCanvasProps) { return ( ); } diff --git a/components/sections/HeroSection.tsx b/components/sections/HeroSection.tsx index 580d7a2..394b3c5 100644 --- a/components/sections/HeroSection.tsx +++ b/components/sections/HeroSection.tsx @@ -4,6 +4,7 @@ import Image from "next/image"; import Link from "next/link"; import { useRef } from "react"; import { HeroDotCanvas } from "./HeroDotCanvas"; +import { HeroWaves } from "./HeroWaves"; export function HeroSection() { const sectionRef = useRef(null); @@ -17,13 +18,15 @@ export function HeroSection() { {/* Background Graphic & Curve (back_4178_60221: x: 32, y: 230, w: 1856, h: 566) */}
+ {/* Interactive Gravitational Water Waves */} + {/* */} {/* Interactive Canvas Dots */}
diff --git a/components/sections/HeroWaves.tsx b/components/sections/HeroWaves.tsx new file mode 100644 index 0000000..9f1296c --- /dev/null +++ b/components/sections/HeroWaves.tsx @@ -0,0 +1,447 @@ +"use client"; + +import React, { useEffect, useRef } from "react"; +import { STARLIGHT_DOTS_MARKUP } from "./heroStarlightDots"; + +interface HeroWavesProps { + sectionRef?: React.RefObject; +} + +interface LayerConfig { + baseY: number; + maxPull: number; + sigma: number; + baseline: number; + freq1: number; + speed1: number; + amp1: number; + freq2: number; + speed2: number; + amp2: number; + phase: number; +} + +const LAYERS: LayerConfig[] = [ + // Layer 1: Deep Blue Base (#0029B2, blur 55px) + { + baseY: 255, + maxPull: 85, + sigma: 290, + baseline: 0.22, + freq1: 0.0028, + speed1: 0.85, + amp1: 28, + freq2: 0.0055, + speed2: -1.1, + amp2: 16, + phase: 0, + }, + // Layer 2: Mid-Ocean Blue (#3D6FE5, blur 30px, mask 2) + { + baseY: 295, + maxPull: 95, + sigma: 250, + baseline: 0.24, + freq1: 0.0033, + speed1: 1.15, + amp1: 32, + freq2: 0.0065, + speed2: -1.4, + amp2: 18, + phase: 1.4, + }, + // Layer 3: Sky Blue Swell (#4B80FF, blur 60px, mask 3) + { + baseY: 350, + maxPull: 90, + sigma: 270, + baseline: 0.23, + freq1: 0.0036, + speed1: 0.95, + amp1: 34, + freq2: 0.007, + speed2: -1.25, + amp2: 18, + phase: 2.7, + }, + // Layer 4: Luminous Atmosphere Indigo (#2D4D99, plus-lighter, blur 60px, mask 4) + { + baseY: 410, + maxPull: 75, + sigma: 280, + baseline: 0.2, + freq1: 0.003, + speed1: 0.75, + amp1: 30, + freq2: 0.0058, + speed2: -0.95, + amp2: 16, + phase: 3.9, + }, + // Layer 5: Pure White Core Light (#FFFFFF, blur 60px, mask 5) + { + baseY: 485, + maxPull: 55, + sigma: 230, + baseline: 0.22, + freq1: 0.0034, + speed1: 0.8, + amp1: 24, + freq2: 0.0062, + speed2: -1.05, + amp2: 12, + phase: 0.8, + }, +]; + +const NUM_POINTS = 64; +const X_MIN = -32; +const X_MAX = 1888; +const DX = (X_MAX - X_MIN) / (NUM_POINTS - 1); + +// Pre-calculate X coordinates +const X_COORDS = new Float64Array(NUM_POINTS); +for (let i = 0; i < NUM_POINTS; i++) { + X_COORDS[i] = X_MIN + i * DX; +} + +// Pre-allocated buffers for Y coordinates +const Y_BUFFERS = Array.from({ length: 5 }, () => new Float64Array(NUM_POINTS)); + +function buildSplinePath(xArr: Float64Array, yArr: Float64Array): string { + const n = NUM_POINTS; + let d = `M ${xArr[0].toFixed(1)} ${yArr[0].toFixed(1)}`; + + for (let i = 0; i < n - 1; i++) { + const i0 = i === 0 ? 0 : i - 1; + const i1 = i; + const i2 = i + 1; + const i3 = i + 2 >= n ? n - 1 : i + 2; + + const cp1x = xArr[i1] + (xArr[i2] - xArr[i0]) / 6; + const cp1y = yArr[i1] + (yArr[i2] - yArr[i0]) / 6; + const cp2x = xArr[i2] - (xArr[i3] - xArr[i1]) / 6; + const cp2y = yArr[i2] - (yArr[i3] - yArr[i1]) / 6; + + d += ` C ${cp1x.toFixed(1)} ${cp1y.toFixed(1)}, ${cp2x.toFixed(1)} ${cp2y.toFixed(1)}, ${xArr[i2].toFixed(1)} ${yArr[i2].toFixed(1)}`; + } + + d += " L 1888 855 L -32 855 Z"; + return d; +} + +export function HeroWaves({ sectionRef }: HeroWavesProps) { + const svgRef = useRef(null); + const pathRefs = [ + useRef(null), + useRef(null), + useRef(null), + useRef(null), + useRef(null), + ]; + + useEffect(() => { + const SVG_WIDTH = 1856; + const SVG_HEIGHT = 566; + + let width = 0; + let height = 0; + let scale = 1; + let offsetX = 0; + + const resize = () => { + const svg = svgRef.current; + if (!svg) return; + const rect = svg.getBoundingClientRect(); + if (rect.width === 0 || rect.height === 0) return; + width = rect.width; + height = rect.height; + scale = height / SVG_HEIGHT; + offsetX = (width - SVG_WIDTH * scale) / 2; + }; + + let targetX = -1000; + let targetY = -1000; + let currentX = -1000; + let currentY = -1000; + let targetStrength = 0; + let currentStrength = 0; + let isHovering = false; + let animFrameId: number | null = null; + + const LERP_LAG = 0.08; + const FADE_IN_LAG = 0.06; + const FADE_OUT_LAG = 0.04; + + const updateWaves = (t: number) => { + for (let layerIdx = 0; layerIdx < LAYERS.length; layerIdx++) { + const cfg = LAYERS[layerIdx]; + const yBuf = Y_BUFFERS[layerIdx]; + + // Gravitational attraction proximity: + // Strongest when cursor is near the layer's waterline, fading smoothly when far above + const distY = Math.abs(currentY - cfg.baseY); + const verticalProximity = Math.pow( + Math.max(0, Math.min(1, 1 - distY / 380)), + 1.3 + ); + const pull = cfg.maxPull * verticalProximity * currentStrength; + + for (let i = 0; i < NUM_POINTS; i++) { + const x = X_COORDS[i]; + const dx = x - currentX; + + // Gaussian tidal crest centered at cursor X + const bell = Math.exp(-(dx * dx) / (2 * cfg.sigma * cfg.sigma)); + // Volume conservation: water pulled towards cursor causes surrounding water to recede + const tidalFactor = bell - cfg.baseline; + const tidalDisplacement = tidalFactor * pull; + + // Harmonic ambient sea wave motion + const yAmbient = + cfg.baseY + + Math.sin(x * cfg.freq1 + t * cfg.speed1 + cfg.phase) * cfg.amp1 + + Math.cos(x * cfg.freq2 + t * cfg.speed2) * cfg.amp2; + + // Dynamic ripple emanating from the gravitational center + const ripple = + Math.sin(dx * 0.022 - t * 3.2) * + Math.exp(-(dx * dx) / (2 * 160 * 160)) * + 5 * + verticalProximity * + currentStrength; + + // Pulling upward reduces SVG Y coordinate + yBuf[i] = yAmbient - tidalDisplacement - ripple; + } + + // Directly mutate DOM path 'd' attribute for maximum 60-120 FPS performance + const pathEl = pathRefs[layerIdx].current; + if (pathEl) { + pathEl.setAttribute("d", buildSplinePath(X_COORDS, yBuf)); + } + } + }; + + const loop = () => { + if (isHovering) { + currentX += (targetX - currentX) * LERP_LAG; + currentY += (targetY - currentY) * LERP_LAG; + currentStrength += (targetStrength - currentStrength) * FADE_IN_LAG; + } else if (currentStrength > 0.001) { + currentStrength += (0 - currentStrength) * FADE_OUT_LAG; + } else { + currentStrength = 0; + } + + const t = performance.now() * 0.001; + updateWaves(t); + + animFrameId = requestAnimationFrame(loop); + }; + + const handleMouseMove = (e: MouseEvent) => { + const section = sectionRef?.current; + const svg = svgRef.current; + const targetArea = section || svg; + if (!targetArea) return; + + const sRect = targetArea.getBoundingClientRect(); + if ( + e.clientX >= sRect.left && + e.clientX <= sRect.right && + e.clientY >= sRect.top && + e.clientY <= sRect.bottom + ) { + targetX = (e.clientX - sRect.left - offsetX) / scale; + targetY = (e.clientY - sRect.top) / scale; + targetStrength = 1; + isHovering = true; + + if (currentX < -500) { + currentX = targetX; + currentY = targetY; + } + } else if (isHovering) { + targetStrength = 0; + isHovering = false; + } + }; + + const handleMouseLeave = () => { + targetStrength = 0; + isHovering = false; + }; + + resize(); + const ro = new ResizeObserver(() => resize()); + if (svgRef.current) ro.observe(svgRef.current); + + animFrameId = requestAnimationFrame(loop); + + window.addEventListener("mousemove", handleMouseMove, { passive: true }); + window.addEventListener("mouseleave", handleMouseLeave); + + return () => { + ro.disconnect(); + window.removeEventListener("mousemove", handleMouseMove); + window.removeEventListener("mouseleave", handleMouseLeave); + if (animFrameId) cancelAnimationFrame(animFrameId); + }; + }, [sectionRef]); + + return ( + + + {/* GPU Gaussian Blur Filters matching original Figma specifications */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* Top Fade Gradient Masks for depth blending */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* 5 Dynamic Wave Layers */} + {/* Layer 1: Deep Blue Base */} + + + + + {/* Layer 2: Mid-Ocean Blue */} + + + + + {/* Layer 3: Sky Blue Swell */} + + + + + {/* Layer 4: Atmospheric Luminous Indigo */} + + + + + {/* Layer 5: White Core Glow */} + + + + + {/* Floating Starlight Sparkle Dots */} + + + ); +} diff --git a/components/sections/heroStarlightDots.ts b/components/sections/heroStarlightDots.ts new file mode 100644 index 0000000..f037c14 --- /dev/null +++ b/components/sections/heroStarlightDots.ts @@ -0,0 +1 @@ +export const STARLIGHT_DOTS_MARKUP = "\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r"; diff --git a/next.config.ts b/next.config.ts index 68a6c64..963de36 100644 --- a/next.config.ts +++ b/next.config.ts @@ -2,6 +2,7 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { output: "standalone", + allowedDevOrigins: ["192.168.1.64"], }; export default nextConfig; diff --git a/public/assets/images/hero_back.svg b/public/assets/images/hero_back.svg index 1f9ab13..56ab682 100644 --- a/public/assets/images/hero_back.svg +++ b/public/assets/images/hero_back.svg @@ -1015,31 +1015,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - -