feat: add webpush and sadhna tracker
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* Confetti — a lightweight, dependency-free confetti shower for the reward
|
||||
* celebration. Spawns a fixed set of colored pieces that fall with a little
|
||||
* horizontal drift and spin, then fade. Pointer-events-none so it never
|
||||
* blocks the dialog beneath it. Honors prefers-reduced-motion.
|
||||
*/
|
||||
|
||||
import { useMemo } from "react";
|
||||
|
||||
const COLORS = [
|
||||
"#f59e0b",
|
||||
"#fb7185",
|
||||
"#34d399",
|
||||
"#60a5fa",
|
||||
"#fbbf24",
|
||||
"#f472b6",
|
||||
"#a78bfa",
|
||||
"#facc15",
|
||||
];
|
||||
|
||||
// Deterministic pseudo-random in [0,1) from a seed. Math.sin is pure, so this
|
||||
// keeps the confetti layout stable across renders (no Math.random impurity).
|
||||
function rand(seed: number): number {
|
||||
const x = Math.sin(seed * 127.1) * 43758.5453;
|
||||
return x - Math.floor(x);
|
||||
}
|
||||
|
||||
export function Confetti({ pieces = 140 }: { pieces?: number }) {
|
||||
// Computed once per mount; varying by index keeps it deterministic-ish
|
||||
// without Date/Math.random concerns (this is a normal component).
|
||||
const items = useMemo(
|
||||
() =>
|
||||
Array.from({ length: pieces }, (_, i) => {
|
||||
const r = (k: number) => rand(i * 9.17 + k * 3.13);
|
||||
return {
|
||||
left: r(1) * 100,
|
||||
delay: r(2) * 0.5,
|
||||
dur: 2.4 + r(3) * 1.8,
|
||||
rot: r(4) * 360,
|
||||
drift: (r(5) * 2 - 1) * 70,
|
||||
size: 6 + r(6) * 7,
|
||||
color: COLORS[i % COLORS.length],
|
||||
round: r(7) < 0.3,
|
||||
};
|
||||
}),
|
||||
[pieces],
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="pointer-events-none absolute inset-0 overflow-hidden"
|
||||
aria-hidden
|
||||
>
|
||||
<style>{`
|
||||
@keyframes sadhna-confetti {
|
||||
0% { transform: translate(0,-10%) rotateZ(0deg); opacity: 1; }
|
||||
85% { opacity: 1; }
|
||||
100% { transform: translate(var(--drift), 115vh) rotateZ(720deg); opacity: 0; }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.sadhna-confetti-piece { display: none; }
|
||||
}
|
||||
`}</style>
|
||||
{items.map((p, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className="sadhna-confetti-piece absolute top-0 block"
|
||||
style={{
|
||||
left: `${p.left}%`,
|
||||
width: p.size,
|
||||
height: p.size * (p.round ? 1 : 0.5),
|
||||
background: p.color,
|
||||
borderRadius: p.round ? "9999px" : "1px",
|
||||
transform: `rotateZ(${p.rot}deg)`,
|
||||
// CSS var consumed by the keyframe's final transform.
|
||||
["--drift" as string]: `${p.drift}px`,
|
||||
animation: `sadhna-confetti ${p.dur}s cubic-bezier(0.2,0.6,0.4,1) ${p.delay}s forwards`,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user