import React, { useEffect, useRef } from 'react'; const COLORS = [ [59, 142, 255], [0, 229, 255], [155, 109, 255], [0, 255, 178], ]; export default function ParticleCanvas() { const canvasRef = useRef(null); const animRef = useRef(null); const particlesRef = useRef([]); useEffect(() => { const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); let W, H; function resize() { W = canvas.width = window.innerWidth; H = canvas.height = window.innerHeight; } function mkParticle() { return { x: Math.random() * W, y: Math.random() * H, vx: (Math.random() - 0.5) * 0.25, vy: (Math.random() - 0.5) * 0.25, r: Math.random() * 1.8 + 0.6, opacity: Math.random() * 0.55 + 0.2, pulse: Math.random() * Math.PI * 2, speed: Math.random() * 0.018 + 0.006, colorIdx: Math.floor(Math.random() * COLORS.length), }; } resize(); window.addEventListener('resize', resize); particlesRef.current = Array.from({ length: 100 }, mkParticle); function draw() { ctx.clearRect(0, 0, W, H); const pts = particlesRef.current; pts.forEach(p => { p.x += p.vx; p.y += p.vy; p.pulse += p.speed; const op = p.opacity * (0.55 + 0.45 * Math.sin(p.pulse)); if (p.x < 0) p.x = W; if (p.x > W) p.x = 0; if (p.y < 0) p.y = H; if (p.y > H) p.y = 0; const [r, g, b] = COLORS[p.colorIdx]; const halo = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, p.r * 5); halo.addColorStop(0, `rgba(${r},${g},${b},${op * 0.55})`); halo.addColorStop(1, `rgba(${r},${g},${b},0)`); ctx.beginPath(); ctx.arc(p.x, p.y, p.r * 5, 0, Math.PI * 2); ctx.fillStyle = halo; ctx.fill(); ctx.beginPath(); ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2); ctx.fillStyle = `rgba(${r},${g},${b},${Math.min(op * 1.6, 1)})`; ctx.fill(); }); for (let i = 0; i < pts.length; i++) { for (let j = i + 1; j < pts.length; j++) { const dx = pts[i].x - pts[j].x; const dy = pts[i].y - pts[j].y; const dist = Math.sqrt(dx * dx + dy * dy); if (dist < 140) { const t = 1 - dist / 140; const [r1, g1, b1] = COLORS[pts[i].colorIdx]; const [r2, g2, b2] = COLORS[pts[j].colorIdx]; const lg = ctx.createLinearGradient(pts[i].x, pts[i].y, pts[j].x, pts[j].y); lg.addColorStop(0, `rgba(${r1},${g1},${b1},${t * 0.22})`); lg.addColorStop(1, `rgba(${r2},${g2},${b2},${t * 0.22})`); ctx.beginPath(); ctx.moveTo(pts[i].x, pts[i].y); ctx.lineTo(pts[j].x, pts[j].y); ctx.strokeStyle = lg; ctx.lineWidth = t * 1.4; ctx.stroke(); } } } animRef.current = requestAnimationFrame(draw); } draw(); return () => { cancelAnimationFrame(animRef.current); window.removeEventListener('resize', resize); }; }, []); return ( ); }