72 lines
2.0 KiB
TypeScript
72 lines
2.0 KiB
TypeScript
import React, { useEffect, useRef } from 'react';
|
|
import { COLORS } from '../constants';
|
|
|
|
export const Confetti: React.FC = () => {
|
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
|
|
useEffect(() => {
|
|
const canvas = canvasRef.current;
|
|
if (!canvas) return;
|
|
const ctx = canvas.getContext('2d');
|
|
if (!ctx) return;
|
|
|
|
// Set dimensions
|
|
canvas.width = window.innerWidth;
|
|
canvas.height = window.innerHeight;
|
|
|
|
const particles: any[] = [];
|
|
// Bright celebratory colors
|
|
const colors = [COLORS.grass, COLORS.bridge, COLORS.character, '#ef4444', '#3b82f6', '#8b5cf6', '#ec4899'];
|
|
|
|
// Create particles
|
|
for (let i = 0; i < 200; i++) {
|
|
particles.push({
|
|
x: canvas.width / 2,
|
|
y: canvas.height * 0.3,
|
|
vx: (Math.random() - 0.5) * 25,
|
|
vy: (Math.random() - 0.5) * 25 - 5,
|
|
size: Math.random() * 8 + 4,
|
|
color: colors[Math.floor(Math.random() * colors.length)],
|
|
rotation: Math.random() * 360,
|
|
rotationSpeed: (Math.random() - 0.5) * 15,
|
|
gravity: 0.6,
|
|
drag: 0.95
|
|
});
|
|
}
|
|
|
|
let animationId: number;
|
|
const animate = () => {
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
|
|
let activeParticles = 0;
|
|
particles.forEach((p) => {
|
|
if (p.y < canvas.height + 20) {
|
|
activeParticles++;
|
|
p.x += p.vx;
|
|
p.y += p.vy;
|
|
p.vy += p.gravity;
|
|
p.vx *= p.drag;
|
|
p.y *= 1.0;
|
|
p.rotation += p.rotationSpeed;
|
|
|
|
ctx.save();
|
|
ctx.translate(p.x, p.y);
|
|
ctx.rotate((p.rotation * Math.PI) / 180);
|
|
ctx.fillStyle = p.color;
|
|
ctx.fillRect(-p.size / 2, -p.size / 2, p.size, p.size);
|
|
ctx.restore();
|
|
}
|
|
});
|
|
|
|
if (activeParticles > 0) {
|
|
animationId = requestAnimationFrame(animate);
|
|
}
|
|
};
|
|
|
|
animate();
|
|
|
|
return () => cancelAnimationFrame(animationId);
|
|
}, []);
|
|
|
|
return <canvas ref={canvasRef} className="fixed inset-0 pointer-events-none z-50" />;
|
|
}; |