)}
);
}
function BranchMark() {
return (
);
}
// Arrow — flipped for RTL by default (points to the start of the line, like ←).
function Arrow({ size = 14, flip = true }) {
return (
);
}
function SectionId({ n, label }) {
return
{n} · {label}
;
}
function Sparkline({ seed = 1, w = 120, h = 32, up = true, color }) {
const pts = useMemo(() => {
const rng = (s) => { let x = s; return () => (x = (x * 9301 + 49297) % 233280) / 233280; };
const r = rng(seed);
const n = 24;
const arr = [];
let v = 0.5;
for (let i = 0; i < n; i++) {
v += (r() - 0.5) * 0.18 + (up ? 0.012 : -0.012);
v = Math.max(0.05, Math.min(0.95, v));
arr.push(v);
}
return arr;
}, [seed, up]);
const c = color || "var(--accent)";
const path = pts.map((v, i) => {
const x = (i / (pts.length - 1)) * w;
const y = h - v * h;
return `${i === 0 ? "M" : "L"} ${x.toFixed(1)} ${y.toFixed(1)}`;
}).join(" ");
const fill = `${path} L ${w} ${h} L 0 ${h} Z`;
return (
);
}
function CountUp({ to, suffix = "", duration = 1400, decimals = 0 }) {
const ref = useRef(null);
const [val, setVal] = useState(0);
useEffect(() => {
const el = ref.current; if (!el) return;
let started = false;
const io = new IntersectionObserver((es) => {
es.forEach((e) => {
if (e.isIntersecting && !started) {
started = true;
const t0 = performance.now();
const tick = (t) => {
const p = Math.min(1, (t - t0) / duration);
const eased = 1 - Math.pow(1 - p, 3);
setVal(to * eased);
if (p < 1) requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
}
});
}, { threshold: 0.4 });
io.observe(el);
return () => io.disconnect();
}, [to, duration]);
return (
{decimals === 0 ? Math.round(val).toLocaleString("en-US") : val.toFixed(decimals)}{suffix}
);
}
Object.assign(window, {
Reveal, Nav, BranchMark, Arrow, SectionId, Sparkline, CountUp,
});