76 lines
2.1 KiB
TypeScript
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>
|
|
);
|
|
}
|