puzzle-trainer/Confetti.tsx
2026-05-23 01:05:21 +00:00

76 lines
2.1 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
const COLORS = ["#f97316", "#10b981", "#3b82f6", "#f59e0b", "#ec4899", "#8b5cf6", "#14b8a6"];
interface Piece {
id: number;
color: string;
left: number;
delay: number;
duration: number;
size: number;
rotation: number;
}
function makePieces(n: number): Piece[] {
return Array.from({ length: n }, (_, i) => ({
id: i,
color: COLORS[i % COLORS.length],
left: Math.random() * 100,
delay: Math.random() * 1.2,
duration: 2.2 + Math.random() * 1.4,
size: 6 + Math.random() * 8,
rotation: Math.random() * 360,
}));
}
export default function Confetti() {
const [pieces] = useState(() => makePieces(48));
const [visible, setVisible] = useState(true);
useEffect(() => {
const t = setTimeout(() => setVisible(false), 4000);
return () => clearTimeout(t);
}, []);
if (!visible) return null;
return (
<div style={{
position: "fixed", inset: 0, pointerEvents: "none",
zIndex: 9999, overflow: "hidden",
}}>
<style>{`
@keyframes confetti-fall {
0% { transform: translateY(-20px) rotate(var(--rot)); opacity: 1; }
80% { opacity: 1; }
100% { transform: translateY(110vh) rotate(calc(var(--rot) + 720deg)); opacity: 0; }
}
@keyframes confetti-sway {
0%,100% { margin-left: 0; }
50% { margin-left: 30px; }
}
`}</style>
{pieces.map(p => (
<div
key={p.id}
style={{
position: "absolute",
top: 0,
left: `${p.left}%`,
width: p.size,
height: p.size * (Math.random() > 0.5 ? 1 : 0.4),
backgroundColor: p.color,
borderRadius: Math.random() > 0.5 ? "50%" : 2,
// @ts-expect-error css var
"--rot": `${p.rotation}deg`,
animation: `confetti-fall ${p.duration}s ease-in ${p.delay}s both, confetti-sway ${p.duration * 0.6}s ease-in-out ${p.delay}s infinite`,
willChange: "transform",
}}
/>
))}
</div>
);
}