/* Shared components and small visual primitives */
const Icon = ({ name, size = 16, stroke = 1.6 }) => {
const props = {
width: size, height: size, viewBox: "0 0 24 24",
fill: "none", stroke: "currentColor",
strokeWidth: stroke, strokeLinecap: "round", strokeLinejoin: "round",
};
switch (name) {
case "arrow-right":
return ;
case "chevron-right":
return ;
case "chevron-down":
return ;
case "chevron-left":
return ;
case "plus":
return ;
case "check":
return ;
case "x":
return ;
case "menu":
return ;
case "shield":
return ;
case "heart":
return ;
case "calendar":
return ;
case "file":
return ;
case "activity":
return ;
case "lock":
return ;
case "search":
return ;
case "bell":
return ;
case "star":
return ;
case "sun":
return ;
case "moon":
return ;
case "play":
return ;
case "users":
return ;
case "trending":
return ;
default: return null;
}
};
/* simple sparkline svg */
const Sparkline = ({ values, color = "currentColor", height = 36, fill = false }) => {
const max = Math.max(...values), min = Math.min(...values);
const w = 100, h = height;
const points = values.map((v, i) => {
const x = (i / (values.length - 1)) * w;
const y = h - ((v - min) / (max - min || 1)) * (h - 6) - 3;
return `${x},${y}`;
});
return (
);
};
/* count up hook */
const useCountUp = (target, duration = 1400, start = 0, enabled = true) => {
const [v, setV] = React.useState(start);
React.useEffect(() => {
if (!enabled) return;
let raf, t0;
const step = (t) => {
if (!t0) t0 = t;
const p = Math.min(1, (t - t0) / duration);
setV(Math.round(start + (target - start) * (1 - Math.pow(1 - p, 3))));
if (p < 1) raf = requestAnimationFrame(step);
};
raf = requestAnimationFrame(step);
return () => cancelAnimationFrame(raf);
}, [target, duration, start, enabled]);
return v;
};
const useInView = (ref, threshold = 0.2) => {
const [vis, setVis] = React.useState(false);
React.useEffect(() => {
if (!ref.current) return;
const obs = new IntersectionObserver(([e]) => {
if (e.isIntersecting) { setVis(true); obs.disconnect(); }
}, { threshold });
obs.observe(ref.current);
return () => obs.disconnect();
}, []);
return vis;
};
const StatNumber = ({ target, suffix = "" }) => {
const ref = React.useRef(null);
const inView = useInView(ref);
const v = useCountUp(target, 1600, 0, inView);
return {v}{suffix};
};
Object.assign(window, { Icon, Sparkline, useCountUp, useInView, StatNumber });