cffy.in/src/apps/bridge-builder-math/components/Confetti.tsx

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" />;
};