Spaces:
Running
Running
| import React, { useEffect, useRef } from 'react'; | |
| import { useUserStore } from '@/store/userStore'; | |
| interface Particle { | |
| x: number; | |
| y: number; | |
| vx: number; | |
| vy: number; | |
| radius: number; | |
| } | |
| export const ParticleNetwork: React.FC = () => { | |
| const canvasRef = useRef<HTMLCanvasElement>(null); | |
| const { isDark } = useUserStore(); | |
| useEffect(() => { | |
| const canvas = canvasRef.current; | |
| if (!canvas) return; | |
| const ctx = canvas.getContext('2d'); | |
| if (!ctx) return; | |
| let particles: Particle[] = []; | |
| let animationFrameId: number; | |
| const resize = () => { | |
| canvas.width = window.innerWidth; | |
| canvas.height = window.innerHeight; | |
| initParticles(); | |
| }; | |
| const initParticles = () => { | |
| particles = []; | |
| const numParticles = Math.floor((canvas.width * canvas.height) / 15000); | |
| for (let i = 0; i < numParticles; i++) { | |
| particles.push({ | |
| x: Math.random() * canvas.width, | |
| y: Math.random() * canvas.height, | |
| vx: (Math.random() - 0.5) * 0.5, | |
| vy: (Math.random() - 0.5) * 0.5, | |
| radius: Math.random() * 1.5 + 0.5, | |
| }); | |
| } | |
| }; | |
| const draw = () => { | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| const color = isDark ? 'rgba(255, 255, 255,' : 'rgba(0, 0, 0,'; | |
| // Update positions | |
| particles.forEach((p) => { | |
| p.x += p.vx; | |
| p.y += p.vy; | |
| if (p.x < 0 || p.x > canvas.width) p.vx *= -1; | |
| if (p.y < 0 || p.y > canvas.height) p.vy *= -1; | |
| }); | |
| // 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 distance = Math.sqrt(dx * dx + dy * dy); | |
| if (distance < 120) { | |
| ctx.beginPath(); | |
| ctx.strokeStyle = `${color} ${1 - distance / 120})`; | |
| ctx.lineWidth = 0.5; | |
| ctx.moveTo(particles[i].x, particles[i].y); | |
| ctx.lineTo(particles[j].x, particles[j].y); | |
| ctx.stroke(); | |
| } | |
| } | |
| } | |
| // Draw particles | |
| particles.forEach((p) => { | |
| ctx.beginPath(); | |
| ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2); | |
| ctx.fillStyle = `${color} 0.5)`; | |
| ctx.fill(); | |
| }); | |
| animationFrameId = requestAnimationFrame(draw); | |
| }; | |
| window.addEventListener('resize', resize); | |
| resize(); | |
| draw(); | |
| return () => { | |
| window.removeEventListener('resize', resize); | |
| cancelAnimationFrame(animationFrameId); | |
| }; | |
| }, [isDark]); | |
| return ( | |
| <canvas | |
| ref={canvasRef} | |
| className="absolute inset-0 pointer-events-none" | |
| style={{ opacity: 0.4 }} | |
| /> | |
| ); | |
| }; | |