complete design overhaul
This commit is contained in:
parent
d033a08d87
commit
0a0bafa0ec
14 changed files with 505 additions and 160 deletions
103
apps/web/src/components/ui/ConfettiBurst.tsx
Normal file
103
apps/web/src/components/ui/ConfettiBurst.tsx
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
import { useEffect, useMemo, useState, useId } from "react";
|
||||
|
||||
type ConfettiBurstProps = {
|
||||
className?: string;
|
||||
colors?: string[];
|
||||
count?: number;
|
||||
};
|
||||
|
||||
type Piece = {
|
||||
id: number;
|
||||
style: React.CSSProperties & ConfettiVars;
|
||||
};
|
||||
|
||||
type ConfettiVars = {
|
||||
["--x0"]: string;
|
||||
["--y0"]: string;
|
||||
["--x1"]: string;
|
||||
["--y1"]: string;
|
||||
};
|
||||
|
||||
const hashStringToUint32 = (value: string) => {
|
||||
// FNV-1a 32-bit
|
||||
let hash = 2166136261;
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
hash ^= value.charCodeAt(i);
|
||||
hash = Math.imul(hash, 16777619);
|
||||
}
|
||||
return hash >>> 0;
|
||||
};
|
||||
|
||||
const mulberry32 = (seed: number) => {
|
||||
return () => {
|
||||
let t = (seed += 0x6d2b79f5);
|
||||
t = Math.imul(t ^ (t >>> 15), t | 1);
|
||||
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
||||
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
||||
};
|
||||
};
|
||||
|
||||
export const ConfettiBurst = ({
|
||||
className,
|
||||
colors = [
|
||||
"var(--color-primary)",
|
||||
"var(--color-accent)",
|
||||
"var(--color-primary-light)",
|
||||
"var(--color-accent-light)",
|
||||
],
|
||||
count = 18,
|
||||
}: ConfettiBurstProps) => {
|
||||
const [visible, setVisible] = useState(true);
|
||||
const instanceId = useId();
|
||||
|
||||
useEffect(() => {
|
||||
const t = window.setTimeout(() => setVisible(false), 1100);
|
||||
return () => window.clearTimeout(t);
|
||||
}, []);
|
||||
|
||||
const pieces = useMemo<Piece[]>(() => {
|
||||
const seed = hashStringToUint32(`${instanceId}:${count}:${colors.join(",")}`);
|
||||
const rand = mulberry32(seed);
|
||||
const rnd = (min: number, max: number) => min + rand() * (max - min);
|
||||
|
||||
return Array.from({ length: count }).map((_, i) => {
|
||||
const x0 = rnd(-6, 6);
|
||||
const y0 = rnd(-6, 6);
|
||||
const x1 = rnd(-160, 160);
|
||||
const y1 = rnd(60, 220);
|
||||
const delay = rnd(0, 120);
|
||||
const rotate = rnd(0, 360);
|
||||
const color = colors[i % colors.length];
|
||||
|
||||
return {
|
||||
id: i,
|
||||
style: {
|
||||
left: "50%",
|
||||
top: "0%",
|
||||
backgroundColor: color,
|
||||
transform: `translate(${x0}px, ${y0}px) rotate(${rotate}deg)`,
|
||||
animationDelay: `${delay}ms`,
|
||||
// consumed by keyframes
|
||||
["--x0"]: `${x0}px`,
|
||||
["--y0"]: `${y0}px`,
|
||||
["--x1"]: `${x1}px`,
|
||||
["--y1"]: `${y1}px`,
|
||||
},
|
||||
};
|
||||
});
|
||||
}, [colors, count, instanceId]);
|
||||
|
||||
if (!visible) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`pointer-events-none absolute inset-0 overflow-visible ${className ?? ""}`}
|
||||
aria-hidden
|
||||
>
|
||||
{pieces.map((p) => (
|
||||
<span key={p.id} className="lila-confetti-piece" style={p.style} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue