/* Charts โ€” pure SVG, no libraries. Brand-tuned. */ const { useMemo: useMemo_c } = React; /* Sparkline */ function Spark({ values, w = 80, h = 20, color = 'var(--text-dim)', fill }) { if (!values || values.length === 0) return null; const min = Math.min(...values); const max = Math.max(...values); const range = max - min || 1; const step = w / (values.length - 1); const pts = values.map((v, i) => [i * step, h - ((v - min) / range) * h]); const d = pts.map((p, i) => (i === 0 ? `M${p[0]},${p[1]}` : `L${p[0]},${p[1]}`)).join(' '); const dArea = `${d} L${w},${h} L0,${h} Z`; return ( {fill && } ); } /* Position history mini โ€” line that goes down=better */ function PosSpark({ values, w = 100, h = 24 }) { if (!values || values.length === 0) return null; const max = Math.max(...values); const min = Math.min(...values); const range = max - min || 1; const step = w / (values.length - 1); // Lower position = higher on chart const pts = values.map((v, i) => [i * step, ((v - min) / range) * h]); const d = pts.map((p, i) => (i === 0 ? `M${p[0]},${p[1]}` : `L${p[0]},${p[1]}`)).join(' '); const last = values[values.length - 1]; const lastTop = last <= 3 ? 'var(--gold)' : last <= 10 ? 'var(--accent)' : 'var(--text-muted)'; return ( ); } /* Big trend chart for traffic */ function TrendChart({ current, previous, height = 260, format = (v) => v }) { const w = 1000; const h = height; const padL = 48; const padR = 16; const padT = 16; const padB = 32; const innerW = w - padL - padR; const innerH = h - padT - padB; const all = [...current, ...(previous || [])]; const max = Math.ceil(Math.max(...all) / 100) * 100; const min = 0; const stepX = innerW / (current.length - 1); const pathFor = (vals) => vals.map((v, i) => { const x = padL + i * stepX; const y = padT + innerH - ((v - min) / (max - min)) * innerH; return (i === 0 ? `M${x},${y}` : `L${x},${y}`); }).join(' '); const areaFor = (vals) => `${pathFor(vals)} L${padL + (vals.length - 1) * stepX},${padT + innerH} L${padL},${padT + innerH} Z`; const yTicks = 4; const ticks = Array.from({ length: yTicks + 1 }, (_, i) => Math.round((max / yTicks) * i)); const labels = ['Feb 1', 'Feb 15', 'Mar 1', 'Mar 15', 'Apr 1', 'Apr 15', 'Apr 28']; const labelIdx = labels.map((_, i) => Math.round((current.length - 1) * (i / (labels.length - 1)))); return ( {/* Grid + y labels */} {ticks.map((t, i) => { const y = padT + innerH - ((t - min) / (max - min)) * innerH; return ( {format(t)} ); })} {/* Previous (dashed) */} {previous && ( )} {/* Current */} {/* X labels */} {labels.map((l, i) => { const x = padL + labelIdx[i] * stepX; return {l}; })} ); } /* Donut for channel mix */ function Donut({ data, size = 200, total }) { const r = size / 2 - 14; const cx = size / 2; const cy = size / 2; const C = 2 * Math.PI * r; const colors = ['var(--gold)', 'var(--text)', 'var(--accent)', 'var(--gold-light)', 'var(--text-muted)', 'var(--text-dim)']; let off = 0; return ( {data.map((d, i) => { const len = (d.share / 100) * C; const dash = `${len} ${C - len}`; const el = ( ); off += len; return el; })} {(total / 1000).toFixed(1)}k Sessions ยท 30d ); } /* Horizontal bars stack โ€” for distributions like DR */ function HBar({ items, max, format = (v) => v }) { return (
{items.map((it, i) => (
{it.label} {format(it.value)}
))}
); } window.Spark = Spark; window.PosSpark = PosSpark; window.TrendChart = TrendChart; window.Donut = Donut; window.HBar = HBar;