import { useEffect, useRef } from 'react'; export default function ForgeBackground() { const canvasRef = useRef(null); useEffect(() => { const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); let animId; let W, H, gridOffset = 0; const resize = () => { W = canvas.width = window.innerWidth; H = canvas.height = window.innerHeight; }; resize(); window.addEventListener('resize', resize); // Particles const particles = Array.from({ length: 90 }, () => ({ x: Math.random() * window.innerWidth, y: Math.random() * window.innerHeight, r: Math.random() * 1.8 + 0.4, vx: (Math.random() - 0.5) * 0.3, vy: (Math.random() - 0.5) * 0.3, a: Math.random(), col: Math.random() > 0.85 ? '#ff6a00' : '#00efff', })); // Nodes const nodes = Array.from({ length: 28 }, () => ({ x: Math.random() * window.innerWidth, y: Math.random() * window.innerHeight, vx: (Math.random() - 0.5) * 0.4, vy: (Math.random() - 0.5) * 0.4, })); // Matrix drops let drops = []; const initDrops = () => { drops = Array.from({ length: Math.floor(W / 22) }, () => Math.random() * -80); }; initDrops(); window.addEventListener('resize', initDrops); const drawGrid = () => { const spacing = 48; gridOffset = (gridOffset + 0.3) % spacing; ctx.strokeStyle = '#0d2a3a'; ctx.lineWidth = 0.7; ctx.beginPath(); for (let x = gridOffset; x < W; x += spacing) { ctx.moveTo(x, 0); ctx.lineTo(x, H); } for (let y = gridOffset; y < H; y += spacing) { ctx.moveTo(0, y); ctx.lineTo(W, y); } ctx.stroke(); }; const drawNodes = () => { 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; }); for (let i = 0; i < nodes.length; i++) { for (let j = i + 1; j < nodes.length; j++) { const dx = nodes[i].x - nodes[j].x, dy = nodes[i].y - nodes[j].y; const d = Math.sqrt(dx*dx + dy*dy); if (d < 160) { ctx.beginPath(); ctx.strokeStyle = `rgba(0,200,255,${(1 - d/160) * 0.45})`; ctx.lineWidth = 0.8; ctx.moveTo(nodes[i].x, nodes[i].y); ctx.lineTo(nodes[j].x, nodes[j].y); ctx.stroke(); } } } nodes.forEach(n => { ctx.beginPath(); ctx.arc(n.x, n.y, 2.2, 0, Math.PI*2); ctx.fillStyle = '#00c8ff'; ctx.shadowBlur = 8; ctx.shadowColor = '#00efff'; ctx.fill(); ctx.shadowBlur = 0; }); }; const drawParticles = () => { particles.forEach(p => { p.x += p.vx; p.y += p.vy; p.a += 0.008; 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 alpha = (Math.sin(p.a) * 0.5 + 0.5) * 0.75 + 0.1; ctx.beginPath(); ctx.arc(p.x, p.y, p.r, 0, Math.PI*2); ctx.fillStyle = p.col === '#ff6a00' ? `rgba(255,106,0,${alpha * 0.8})` : `rgba(0,239,255,${alpha})`; ctx.shadowBlur = 6; ctx.shadowColor = p.col; ctx.fill(); ctx.shadowBlur = 0; }); }; const chars = '01アイウエオカキクケコQFFORGE<>{}[]|/\\'; const drawMatrix = () => { ctx.font = '13px "Courier New"'; for (let i = 0; i < drops.length; i++) { const ch = chars[Math.floor(Math.random() * chars.length)]; ctx.fillStyle = `rgba(0,239,255,0.75)`; ctx.fillText(ch, i * 22, drops[i] * 16); if (drops[i] * 16 > H && Math.random() > 0.975) drops[i] = 0; drops[i] += 0.4; } }; const frame = () => { ctx.fillStyle = 'rgba(6,11,16,0.18)'; ctx.fillRect(0, 0, W, H); drawGrid(); drawMatrix(); drawNodes(); drawParticles(); animId = requestAnimationFrame(frame); }; frame(); return () => { cancelAnimationFrame(animId); window.removeEventListener('resize', resize); window.removeEventListener('resize', initDrops); }; }, []); return ( ); }