"use client"; import React, { useEffect, useRef } from "react"; interface Props { className?: string; } const GravityWave: React.FC = ({ className }) => { const canvasRef = useRef(null); useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext("2d"); if (!ctx) return; // Initialize dimensions based on parent container let width = canvas.width = canvas.parentElement?.clientWidth || window.innerWidth; let height = canvas.height = canvas.parentElement?.clientHeight || window.innerHeight; let animationFrameId: number; let time = 0; // Configuration const gap = 50; const rows = Math.ceil(height / gap) + 4; const cols = Math.ceil(width / gap) + 4; // Mouse state const mouse = { x: -500, y: -500 }; const handleResize = () => { // Resize based on parent element if (canvas.parentElement) { width = canvas.width = canvas.parentElement.clientWidth; height = canvas.height = canvas.parentElement.clientHeight; } }; const handleMouseMove = (e: MouseEvent) => { const rect = canvas.getBoundingClientRect(); mouse.x = e.clientX - rect.left; mouse.y = e.clientY - rect.top; }; window.addEventListener("resize", handleResize); // Attach mouse move to the specific canvas/container, not window, if you want localized interaction // But keeping it on window usually feels smoother for "approaching" the section window.addEventListener("mousemove", handleMouseMove); const render = () => { ctx.clearRect(0, 0, width, height); time += 0.02; ctx.shadowBlur = 4; ctx.shadowColor = "rgba(34, 211, 238, 0.5)"; const gradient = ctx.createLinearGradient(0, 0, width, height); gradient.addColorStop(0, "rgba(34, 211, 238, 0.4)"); gradient.addColorStop(1, "rgba(52, 211, 153, 0.4)"); ctx.strokeStyle = gradient; ctx.lineWidth = 1.5; // Re-calculate rows/cols in case of resize const currentRows = Math.ceil(height / gap) + 4; const currentCols = Math.ceil(width / gap) + 4; for (let iy = 0; iy < currentRows; iy++) { const yBase = iy * gap; ctx.beginPath(); for (let ix = 0; ix < currentCols; ix++) { const xBase = ix * gap; const waveOffset = Math.sin(ix * 0.2 + time) * 8 + Math.cos(iy * 0.3 + time) * 8; const dx = xBase - mouse.x; const dy = yBase - mouse.y; const dist = Math.sqrt(dx * dx + dy * dy); const maxDist = 400; let gravityX = 0; let gravityY = 0; if (dist < maxDist) { const force = (maxDist - dist) / maxDist; const power = force * 60; const angle = Math.atan2(dy, dx); gravityX = Math.cos(angle) * power; gravityY = Math.sin(angle) * power; } const xFinal = xBase + gravityX; const yFinal = yBase + waveOffset + gravityY; if (ix === 0) ctx.moveTo(xFinal, yFinal); else ctx.lineTo(xFinal, yFinal); } ctx.stroke(); } animationFrameId = requestAnimationFrame(render); }; render(); return () => { window.removeEventListener("resize", handleResize); window.removeEventListener("mousemove", handleMouseMove); cancelAnimationFrame(animationFrameId); }; }, []); return ( ); }; export default GravityWave;