'use client'; import { useEffect, useRef } from 'react'; interface Particle { x: number; y: number; vx: number; vy: number; alpha: number; color: string; decay: number; } interface Firework { x: number; y: number; targetY: number; vx: number; vy: number; color: string; particles: Particle[]; exploded: boolean; dead: boolean; } export function FireworksEffect() { const canvasRef = useRef(null); useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); if (!ctx) return; let animationFrameId: number; let fireworks: Firework[] = []; const colors = [ '#ff0000', '#ff7f00', '#ffff00', '#00ff00', '#0000ff', '#4b0082', '#8f00ff', '#ff1493', '#00bfff', '#ffd700', '#ff4500', '#32cd32', ]; const resizeCanvas = () => { canvas.width = window.innerWidth; canvas.height = window.innerHeight; }; const createFirework = () => { const x = Math.random() * canvas.width; const y = canvas.height; const targetY = canvas.height * 0.2 + Math.random() * (canvas.height * 0.5); const speed = 8 + Math.random() * 5; fireworks.push({ x, y, targetY, vx: (Math.random() - 0.5) * 3, vy: -speed, color: colors[Math.floor(Math.random() * colors.length)], particles: [], exploded: false, dead: false, }); }; const createParticles = (x: number, y: number, color: string) => { const particleCount = 50 + Math.random() * 50; const particles: Particle[] = []; for (let i = 0; i < particleCount; i++) { const angle = Math.random() * Math.PI * 2; const speed = Math.random() * 5 + 1; particles.push({ x, y, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed, alpha: 1, color: color, decay: Math.random() * 0.01 + 0.005, }); } return particles; }; const updateAndDraw = () => { if (!ctx || !canvas) return; ctx.globalCompositeOperation = 'destination-out'; ctx.fillStyle = 'rgba(0, 0, 0, 0.1)'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.globalCompositeOperation = 'lighter'; if (Math.random() < 0.04) { createFirework(); } fireworks.forEach((fw) => { if (fw.dead) return; if (!fw.exploded) { ctx.beginPath(); ctx.arc(fw.x, fw.y, 2, 0, Math.PI * 2); ctx.fillStyle = fw.color; ctx.fill(); fw.x += fw.vx; fw.y += fw.vy; fw.vy += 0.15; if (fw.vy >= 0 || fw.y <= fw.targetY) { fw.exploded = true; fw.particles = createParticles(fw.x, fw.y, fw.color); } } else { for (let i = fw.particles.length - 1; i >= 0; i--) { const p = fw.particles[i]; ctx.beginPath(); ctx.arc(p.x, p.y, 1.5, 0, Math.PI * 2); ctx.fillStyle = p.color; ctx.globalAlpha = p.alpha; ctx.fill(); ctx.globalAlpha = 1; p.x += p.vx; p.y += p.vy; p.vy += 0.1; p.alpha -= p.decay; if (p.alpha <= 0) { fw.particles.splice(i, 1); } } if (fw.particles.length === 0) { fw.dead = true; } } }); fireworks = fireworks.filter((fw) => !fw.dead); animationFrameId = requestAnimationFrame(updateAndDraw); }; resizeCanvas(); updateAndDraw(); window.addEventListener('resize', resizeCanvas); return () => { cancelAnimationFrame(animationFrameId); window.removeEventListener('resize', resizeCanvas); }; }, []); return ( ); }