// Shared UI primitives + icon library const { useState, useEffect, useRef, useMemo, useCallback, createContext, useContext } = React; // --- Icons (lucide-ish, drawn at 16x16 viewBox 24) --- const Icon = ({ d, size = 16, stroke = 1.6, fill, children, ...rest }) => ( {d ? : children} ); const I = { home: (p) => , play: (p) => , bot: (p) => , key: (p) => , wallet: (p) => , list: (p) => , book: (p) => , users: (p) => , chart: (p) => , cube: (p) => , star: (p) => , wrench: (p) => , cog: (p) => , pulse: (p) => , plus: (p) => , search: (p) => , filter: (p) => , copy: (p) => , trash: (p) => , edit: (p) => , check: (p) => , x: (p) => , download: (p) => , send: (p) => , sun: (p) => , moon: (p) => , flask: (p) => , zap: (p) => , arrowUp: (p) => , arrowDown: (p) => , arrowRight: (p) => , eye: (p) => , eyeOff: (p) => , more: (p) => , refresh: (p) => , globe: (p) => , shield: (p) => , spark: (p) => , link: (p) => , file: (p) => , logout: (p) => , menu: (p) => , warn: (p) => , code: (p) => , arrowRight: (p) => , alert: (p) => , }; window.I = I; window.Icon = Icon; // --- Sparkline --- function Spark({ data, color = "var(--accent)", w = 80, h = 30 }) { if (!data || !data.length) return null; const min = Math.min(...data), max = Math.max(...data), range = max - min || 1; const step = w / (data.length - 1); const pts = data.map((v, i) => `${i*step},${h - ((v-min)/range)*h}`).join(" "); const area = `M0,${h} L${pts.replaceAll(",", " ").split(" ").map((v, i) => i % 2 ? v : v).join(",")} L${w},${h} Z`; return ( ); } window.Spark = Spark; // --- KPI card --- function KPI({ label, value, unit, delta, spark, sparkColor, icon }) { return (
{icon}{label}
{value}{unit ? {unit} : null}
{(delta != null) && (
= 0 ? "pos" : "neg")}> {delta >= 0 ? "+" : ""}{delta}% vs last 7d
)} {spark &&
}
); } window.KPI = KPI; // --- Modal --- function Modal({ open, onClose, title, sub, children, foot, size }) { useEffect(() => { if (!open) return; const h = (e) => e.key === "Escape" && onClose(); document.addEventListener("keydown", h); return () => document.removeEventListener("keydown", h); }, [open, onClose]); if (!open) return null; return (
e.stopPropagation()}>

{title}

{sub &&
{sub}
}
{children}
{foot &&
{foot}
}
); } window.Modal = Modal; // --- Toasts --- const ToastCtx = createContext(null); function ToastProvider({ children }) { const [items, setItems] = useState([]); const push = useCallback((msg, kind = "info") => { const id = Math.random(); setItems(s => [...s, { id, msg, kind }]); setTimeout(() => setItems(s => s.filter(x => x.id !== id)), 3000); }, []); return ( {children}
{items.map(t => (
{t.kind === "pos" ? : t.kind === "neg" ? : } {t.msg}
))}
); } function useToast() { return useContext(ToastCtx); } window.ToastProvider = ToastProvider; window.useToast = useToast; // --- Switch --- function Switch({ on, onChange }) { return
onChange(!on)} role="switch" aria-checked={on}/>; } window.Switch = Switch; // --- Provider chip --- function ProvChip({ slug }) { const p = window.HDR_DATA.providers[slug] || { name: slug, color: "#888" }; return {p.name}; } window.ProvChip = ProvChip; // --- Tabs --- function Tabs({ items, value, onChange }) { return (
{items.map(it => ( ))}
); } window.Tabs = Tabs; // --- Bar chart (simple SVG) --- function BarChart({ data, height = 200, fmt = v => v, color = "var(--accent)" }) { const max = Math.max(...data.map(d => d.v), 1); const w = 100 / data.length; return (
{[0.25, 0.5, 0.75].map(p => ( ))} {data.map((d, i) => { const h = (d.v / max) * (height - 16); return ( {d.label}: {fmt(d.v)} ); })}
{data.filter((_, i) => i % Math.ceil(data.length/6) === 0).map((d, i) => {d.label})}
); } window.BarChart = BarChart; // --- Line chart with area --- function LineChart({ series, height = 220, color = "var(--accent)", fmt = v => v }) { if (!series || !series.length) return null; const vals = series.map(s => s.v); const max = Math.max(...vals), min = Math.min(...vals, 0); const range = max - min || 1; const W = 100, H = height; const step = W / (series.length - 1); const pts = series.map((s, i) => [i*step, H - 20 - ((s.v - min)/range)*(H - 40)]); const path = pts.map((p,i) => (i ? "L" : "M") + p[0] + " " + p[1]).join(" "); const area = path + ` L${W} ${H-20} L0 ${H-20} Z`; return (
{[0.25,0.5,0.75].map(p => ( ))}
{series.filter((_, i) => i % Math.ceil(series.length/6) === 0).map((s, i) => {s.label})}
); } window.LineChart = LineChart; // --- Donut chart --- function Donut({ slices, size = 140, thickness = 18 }) { const total = slices.reduce((a, s) => a + s.v, 0) || 1; const r = (size - thickness) / 2; const c = 2 * Math.PI * r; let off = 0; return ( {slices.map((s, i) => { const len = (s.v / total) * c; const el = ( ); off += len; return el; })} ); } window.Donut = Donut; // Copy to clipboard helper window.copyText = (txt, toast) => { navigator.clipboard?.writeText(txt).then(() => toast && toast("Copied to clipboard", "pos")); };