Spaces:
Running
Running
| /* βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| ParticleBackground β Animated cyberpunk network visualization | |
| Canvas-based particle system with connecting lines | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| import { useEffect, useRef } from 'react'; | |
| const PARTICLE_COUNT = 50; | |
| const CONNECTION_DISTANCE = 140; | |
| const PARTICLE_SPEED = 0.25; | |
| export default function ParticleBackground() { | |
| const canvasRef = useRef(null); | |
| const animRef = useRef(null); | |
| const particlesRef = useRef([]); | |
| useEffect(() => { | |
| const canvas = canvasRef.current; | |
| const ctx = canvas.getContext('2d'); | |
| let width = window.innerWidth; | |
| let height = window.innerHeight; | |
| canvas.width = width; | |
| canvas.height = height; | |
| // Initialize particles | |
| particlesRef.current = Array.from({ length: PARTICLE_COUNT }, () => ({ | |
| x: Math.random() * width, | |
| y: Math.random() * height, | |
| vx: (Math.random() - 0.5) * PARTICLE_SPEED, | |
| vy: (Math.random() - 0.5) * PARTICLE_SPEED, | |
| radius: Math.random() * 1.5 + 0.5, | |
| opacity: Math.random() * 0.3 + 0.1, | |
| })); | |
| function animate() { | |
| ctx.clearRect(0, 0, width, height); | |
| const particles = particlesRef.current; | |
| // Update & draw particles | |
| particles.forEach((p) => { | |
| p.x += p.vx; | |
| p.y += p.vy; | |
| // Wrap around edges | |
| if (p.x < 0) p.x = width; | |
| if (p.x > width) p.x = 0; | |
| if (p.y < 0) p.y = height; | |
| if (p.y > height) p.y = 0; | |
| // Draw particle | |
| ctx.beginPath(); | |
| ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2); | |
| ctx.fillStyle = `rgba(0, 240, 255, ${p.opacity})`; | |
| ctx.fill(); | |
| }); | |
| // Draw connections | |
| for (let i = 0; i < particles.length; i++) { | |
| for (let j = i + 1; j < particles.length; j++) { | |
| const dx = particles[i].x - particles[j].x; | |
| const dy = particles[i].y - particles[j].y; | |
| const dist = Math.sqrt(dx * dx + dy * dy); | |
| if (dist < CONNECTION_DISTANCE) { | |
| const opacity = (1 - dist / CONNECTION_DISTANCE) * 0.15; | |
| ctx.beginPath(); | |
| ctx.moveTo(particles[i].x, particles[i].y); | |
| ctx.lineTo(particles[j].x, particles[j].y); | |
| ctx.strokeStyle = `rgba(0, 240, 255, ${opacity})`; | |
| ctx.lineWidth = 0.5; | |
| ctx.stroke(); | |
| } | |
| } | |
| } | |
| animRef.current = requestAnimationFrame(animate); | |
| } | |
| animate(); | |
| const handleResize = () => { | |
| width = window.innerWidth; | |
| height = window.innerHeight; | |
| canvas.width = width; | |
| canvas.height = height; | |
| }; | |
| window.addEventListener('resize', handleResize); | |
| return () => { | |
| cancelAnimationFrame(animRef.current); | |
| window.removeEventListener('resize', handleResize); | |
| }; | |
| }, []); | |
| return ( | |
| <canvas | |
| ref={canvasRef} | |
| style={{ | |
| position: 'fixed', | |
| top: 0, | |
| left: 0, | |
| width: '100%', | |
| height: '100%', | |
| zIndex: -1, | |
| pointerEvents: 'none', | |
| }} | |
| /> | |
| ); | |
| } | |