"use client"; import { useRef, useEffect } from "react"; type Node = { x: number; y: number; vx: number; vy: number; r: number; pulse: number; type: "blue" | "green" | "red"; }; export default function HeroCanvas() { const ref = useRef(null); useEffect(() => { const canvas = ref.current; if (!canvas) return; const ctx = canvas.getContext("2d"); if (!ctx) return; let W = 0, H = 0; let nodes: Node[] = []; let animId = 0; function resize() { W = canvas!.width = canvas!.offsetWidth; H = canvas!.height = canvas!.offsetHeight; buildNodes(); } function buildNodes() { nodes = []; const count = Math.floor((W * H) / 18000); for (let i = 0; i < count; i++) { nodes.push({ x: Math.random() * W, y: Math.random() * H, vx: (Math.random() - 0.5) * 0.3, vy: (Math.random() - 0.5) * 0.3, r: Math.random() * 2 + 1, pulse: Math.random() * Math.PI * 2, type: Math.random() < 0.1 ? "red" : Math.random() < 0.15 ? "green" : "blue", }); } } function draw() { ctx!.clearRect(0, 0, W, H); // Grid ctx!.strokeStyle = "rgba(0,100,200,0.06)"; ctx!.lineWidth = 0.5; for (let x = 0; x < W; x += 60) { ctx!.beginPath(); ctx!.moveTo(x, 0); ctx!.lineTo(x, H); ctx!.stroke(); } for (let y = 0; y < H; y += 60) { ctx!.beginPath(); ctx!.moveTo(0, y); ctx!.lineTo(W, y); ctx!.stroke(); } // Update nodes.forEach(n => { n.x += n.vx; n.y += n.vy; if (n.x < 0 || n.x > W) n.vx *= -1; if (n.y < 0 || n.y > H) n.vy *= -1; n.pulse += 0.02; }); // Edges for (let i = 0; i < nodes.length; i++) { for (let j = i + 1; j < nodes.length; j++) { const a = nodes[i], b = nodes[j]; const dx = a.x - b.x, dy = a.y - b.y; const dist = Math.sqrt(dx * dx + dy * dy); if (dist < 140) { const alpha = (1 - dist / 140) * 0.25; let color: string; if (a.type === "red" || b.type === "red") color = `rgba(255,45,85,${alpha * 0.8})`; else if (a.type === "green" || b.type === "green") color = `rgba(0,255,136,${alpha * 0.6})`; else color = `rgba(0,200,255,${alpha})`; ctx!.strokeStyle = color; ctx!.lineWidth = 0.5; ctx!.beginPath(); ctx!.moveTo(a.x, a.y); ctx!.lineTo(b.x, b.y); ctx!.stroke(); } } } // Nodes nodes.forEach(n => { const pulse = 0.5 + 0.5 * Math.sin(n.pulse); let color: string, gcolor: string; if (n.type === "red") { color = `rgba(255,45,85,${0.4 + 0.5 * pulse})`; gcolor = "#FF2D55"; } else if (n.type === "green") { color = `rgba(0,255,136,${0.4 + 0.5 * pulse})`; gcolor = "#00FF88"; } else { color = `rgba(0,200,255,${0.35 + 0.4 * pulse})`; gcolor = "#00F5FF"; } const g = ctx!.createRadialGradient(n.x, n.y, 0, n.x, n.y, n.r * 3); g.addColorStop(0, gcolor); g.addColorStop(1, "transparent"); ctx!.fillStyle = g; ctx!.beginPath(); ctx!.arc(n.x, n.y, n.r * 3, 0, Math.PI * 2); ctx!.fill(); ctx!.fillStyle = color; ctx!.beginPath(); ctx!.arc(n.x, n.y, n.r, 0, Math.PI * 2); ctx!.fill(); }); animId = requestAnimationFrame(draw); } const onResize = () => { cancelAnimationFrame(animId); resize(); draw(); }; window.addEventListener("resize", onResize); resize(); draw(); return () => { cancelAnimationFrame(animId); window.removeEventListener("resize", onResize); }; }, []); return (
); }