diff --git a/src/components/ui/particle-background.tsx b/src/components/ui/particle-background.tsx new file mode 100644 index 0000000..9627e6e --- /dev/null +++ b/src/components/ui/particle-background.tsx @@ -0,0 +1,110 @@ +'use client'; + +import { useEffect, useRef } from 'react'; + +interface Particle { + x: number; + y: number; + vx: number; + vy: number; + size: number; + opacity: number; +} + +interface ParticleBackgroundProps { + particleCount?: number; + className?: string; +} + +export function ParticleBackground({ + particleCount = 50, + className = '' +}: ParticleBackgroundProps) { + const canvasRef = useRef(null); + + useEffect(() => { + const canvas = canvasRef.current; + if (!canvas) return; + + const ctx = canvas.getContext('2d'); + if (!ctx) return; + + let animationFrameId: number; + let particles: Particle[] = []; + + const resizeCanvas = () => { + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + }; + + const createParticles = () => { + particles = []; + for (let i = 0; i < particleCount; 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, + size: Math.random() * 2 + 1, + opacity: Math.random() * 0.5 + 0.2, + }); + } + }; + + const drawParticles = () => { + ctx.clearRect(0, 0, canvas.width, canvas.height); + + particles.forEach((particle, i) => { + particle.x += particle.vx; + particle.y += particle.vy; + + if (particle.x < 0 || particle.x > canvas.width) particle.vx *= -1; + if (particle.y < 0 || particle.y > canvas.height) particle.vy *= -1; + + ctx.beginPath(); + ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2); + ctx.fillStyle = `rgba(0, 217, 255, ${particle.opacity})`; + ctx.fill(); + + particles.forEach((otherParticle, j) => { + if (i === j) return; + const dx = particle.x - otherParticle.x; + const dy = particle.y - otherParticle.y; + const distance = Math.sqrt(dx * dx + dy * dy); + + if (distance < 150) { + ctx.beginPath(); + ctx.moveTo(particle.x, particle.y); + ctx.lineTo(otherParticle.x, otherParticle.y); + ctx.strokeStyle = `rgba(0, 217, 255, ${0.1 * (1 - distance / 150)})`; + ctx.stroke(); + } + }); + }); + + animationFrameId = requestAnimationFrame(drawParticles); + }; + + resizeCanvas(); + createParticles(); + drawParticles(); + + window.addEventListener('resize', () => { + resizeCanvas(); + createParticles(); + }); + + return () => { + cancelAnimationFrame(animationFrameId); + window.removeEventListener('resize', resizeCanvas); + }; + }, [particleCount]); + + return ( + + ); +}