// ui.jsx — design system + presentational components for the redesigned
// dashboard (ported from the "Attribution Dashboard" mockup). Dumb components:
// they receive already-computed view models and render. Data wiring lives in
// app.jsx; the Supabase layer is untouched.

// ---- design tokens ---------------------------------------------------------
const DS = {
  // editorial cream + near-black palette ("Executive Analytics" look)
  bg: '#F4F2ED', card: '#fff', ink: '#1A1917', ink2: '#3B3A37',
  muted: '#57544E', dim: '#78746C', faint: '#A8A39A',
  border: '#E6E1D8', border2: '#EFEBE3', field: '#DDD7CC', chip: '#F0ECE3',
  // "blue" is the primary accent everywhere (buttons, charts) — now near-black
  blue: '#1A1917', blueTints: ['#1A1917', '#57544E', '#A8A39A', '#D8D3C8'],
  gray: '#D8D3C8', areaFill: 'rgba(26,25,23,.07)',
  pos: '#3F7D57', posBg: '#E8F0E9', neg: '#B8443A', negBg: '#F6E9E6',
  info: '#1A1917', link: '#1A1917', linkBg: '#F0ECE3',
  // subtle warm highlight for "primary" KPI cards
  hi: '#F3EFE6', hiBorder: '#E7E0D2',
  font: "Inter, system-ui, -apple-system, sans-serif",
  mono: "'JetBrains Mono', ui-monospace, monospace",
  logoPal: [['#EEEAE1', '#1A1917'], ['#E8F0E9', '#3F7D57'], ['#F0ECE3', '#57544E'],
            ['#F3EAE0', '#8A5A2B'], ['#EDEBE6', '#3B3A37']],
};

// deterministic-ish logo from a client name
function hashStrUI(s) { let h = 2166136261; for (let i = 0; i < s.length; i++) { h ^= s.charCodeAt(i); h = Math.imul(h, 16777619); } return h >>> 0; }
function logoFor(name) {
  const words = (name || '?').replace(/[&]/g, ' ').split(/\s+/).filter(Boolean);
  const initials = ((words[0] ? words[0][0] : '?') + (words[1] ? words[1][0] : (words[0] ? words[0][1] : ''))).toUpperCase();
  const pal = DS.logoPal[hashStrUI(name || '') % DS.logoPal.length];
  return { initials, bg: pal[0], fg: pal[1] };
}

// ---- chart math ------------------------------------------------------------
function niceMax(m) { const p = Math.pow(10, Math.floor(Math.log10(Math.max(1, m)))); const f = m / p; let nf; if (f <= 1) nf = 1; else if (f <= 2) nf = 2; else if (f <= 5) nf = 5; else nf = 10; return nf * p; }
function smoothPath(pts, cmin, cmax) {
  if (!pts.length) return '';
  if (pts.length < 2) return 'M ' + pts[0][0] + ' ' + pts[0][1];
  // clamp control points to the drawable band so the spline never overshoots
  // below the baseline (which looked like negative values) or above the top.
  const clamp = (y) => (cmin != null ? Math.max(cmin, Math.min(cmax, y)) : y);
  let d = 'M ' + pts[0][0].toFixed(1) + ' ' + pts[0][1].toFixed(1);
  for (let i = 0; i < pts.length - 1; i++) {
    const p0 = pts[i - 1] || pts[i], p1 = pts[i], p2 = pts[i + 1], p3 = pts[i + 2] || p2;
    const c1x = p1[0] + (p2[0] - p0[0]) / 6, c1y = clamp(p1[1] + (p2[1] - p0[1]) / 6);
    const c2x = p2[0] - (p3[0] - p1[0]) / 6, c2y = clamp(p2[1] - (p3[1] - p1[1]) / 6);
    d += ' C ' + c1x.toFixed(1) + ' ' + c1y.toFixed(1) + ' ' + c2x.toFixed(1) + ' ' + c2y.toFixed(1) + ' ' + p2[0].toFixed(1) + ' ' + p2[1].toFixed(1);
  }
  return d;
}
function buildLine(vals, labels, allX) {
  const W = 520, H = 170, pL = 8, pR = 8, pT = 14, pB = 8, n = vals.length;
  const nm = niceMax(Math.max(1, ...vals));
  const X = (i) => n <= 1 ? pL : pL + (W - pL - pR) * (i / (n - 1));
  const Y = (v) => pT + (H - pT - pB) * (1 - v / nm);
  const pts = vals.map((v, i) => [X(i), Y(v)]);
  const line = smoothPath(pts, pT, H - pB);
  const baseY = (H - pB).toFixed(1);
  const area = n ? line + ' L ' + X(n - 1).toFixed(1) + ' ' + baseY + ' L ' + X(0).toFixed(1) + ' ' + baseY + ' Z' : '';
  const ticks = [0, nm / 2, nm].map((t) => ({ y: Y(t).toFixed(1) }));
  const idxs = !n ? [] : (allX ? vals.map((_, i) => i) : [0, Math.floor((n - 1) / 2), n - 1]);
  const xLabels = idxs.map((i) => ({ label: labels[i] }));
  const points = vals.map((v, i) => ({ x: X(i), y: Y(v), v, label: labels[i] }));
  return { line, area, ticks, xLabels, points, W, H, baseY: H - pB };
}
function buildDonut(segs, centerNum, centerLabel) {
  const r = 64, C = 2 * Math.PI * r;
  const total = segs.reduce((a, s) => a + s.value, 0) || 1;
  let acc = 0;
  // OVER extends each segment slightly past its end so the next segment (drawn
  // on top) covers the sub-pixel seam — otherwise thin white gaps show between
  // colors. gap = 2*C so the dash never repeats (only `draw` is painted).
  const OVER = 1.4;
  const arcs = segs.map((s) => {
    const len = s.value / total * C;
    const draw = len > 0.01 ? len + OVER : 0;
    const a = { dash: draw.toFixed(2) + ' ' + (C * 2).toFixed(2), offset: (-acc).toFixed(2), color: s.color };
    acc += len; return a;
  });
  return { arcs, centerNum, centerLabel, legend: segs.map((s) => ({ label: s.label, value: window.fmt(s.value), color: s.color })) };
}

// ---- trend -----------------------------------------------------------------
function Trend({ cur, prev, size = 13, invert = false }) {
  const t = window.trend(cur, prev || 0);
  if (t.dir === 0) return <span style={{ fontSize: size, fontWeight: 600, color: DS.faint, fontVariantNumeric: 'tabular-nums' }}>— 0%</span>;
  const favorable = invert ? t.dir < 0 : t.dir > 0;
  const color = favorable ? DS.pos : DS.neg;
  const arrow = t.dir > 0 ? '↑' : '↓';
  return <span style={{ fontSize: size, fontWeight: 600, color, fontVariantNumeric: 'tabular-nums', whiteSpace: 'nowrap' }}>{arrow}{t.pct.toFixed(1)}%</span>;
}

// ---- period pills (reuses window.RangeCalendar for custom) ------------------
function PeriodPills({ period, onPeriod, custom, onCustom }) {
  const [open, setOpen] = React.useState(false);
  const wrap = React.useRef(null);
  React.useEffect(() => {
    if (!open) return;
    const onDoc = (e) => { if (wrap.current && !wrap.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', onDoc);
    return () => document.removeEventListener('mousedown', onDoc);
  }, [open]);
  const pills = [['weekly', 'Weekly'], ['monthly', 'Monthly']];
  const customActive = period === 'custom';
  const customLabel = customActive && custom ? window.fmtRange(custom.from, custom.to) : 'Custom';
  const btn = (active) => ({
    border: 'none', cursor: 'pointer', fontSize: 13.5, fontWeight: active ? 600 : 500,
    padding: '6px 13px', borderRadius: 8, background: active ? '#fff' : 'transparent',
    color: active ? DS.ink : DS.dim, transition: 'all .12s',
    boxShadow: active ? '0 1px 2px rgba(16,18,28,.08)' : 'none', display: 'inline-flex', alignItems: 'center', gap: 5,
  });
  return (
    <div ref={wrap} style={{ position: 'relative' }}>
      <div style={{ display: 'inline-flex', background: '#EAE5DB', borderRadius: 10, padding: 3, gap: 2 }}>
        {pills.map(([k, l]) => (
          <button key={k} style={btn(period === k)} onClick={() => { onPeriod(k); setOpen(false); }}>{l}</button>
        ))}
        <button style={btn(customActive)} onClick={() => setOpen((o) => !o)}>
          {customLabel}
        </button>
      </div>
      {open && (
        <div style={{ position: 'absolute', right: 0, top: 'calc(100% + 8px)', zIndex: 60 }}>
          <window.RangeCalendar value={custom} onClose={() => setOpen(false)}
            onApply={(r) => { onCustom(r); onPeriod('custom'); setOpen(false); }} />
        </div>
      )}
    </div>
  );
}

// ---- back / ghost buttons --------------------------------------------------
function GhostBtn({ onClick, children, strong }) {
  return (
    <button onClick={onClick} style={{
      border: '1px solid ' + DS.field, background: '#fff', cursor: 'pointer', fontSize: 13.5,
      fontWeight: strong ? 600 : 500, color: strong ? DS.ink : DS.muted, padding: '7px 13px',
      borderRadius: 9, display: 'inline-flex', alignItems: 'center', gap: 6, transition: 'all .12s',
    }}>{children}</button>
  );
}

// ---- chart card + band -----------------------------------------------------
function ChartCard({ title, headline, span = 6, children }) {
  return (
    <div style={{ gridColumn: 'span ' + span, background: '#fff', borderRadius: 14, border: '1px solid ' + DS.border2, boxShadow: '0 1px 2px rgba(16,18,28,.04)', padding: '17px 19px 16px', minWidth: 0 }}>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 15 }}>
        <div style={{ fontSize: 13, fontWeight: 600, color: DS.ink2 }}>{title}</div>
        {headline != null && <div style={{ fontSize: 14, fontWeight: 700, fontVariantNumeric: 'tabular-nums', color: DS.ink }}>{headline}</div>}
      </div>
      {children}
    </div>
  );
}

function Donut({ segs, centerNum, centerLabel }) {
  const d = buildDonut(segs, centerNum, centerLabel);
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 18, padding: 6 }}>
      <div style={{ flex: 1, display: 'flex', justifyContent: 'center' }}>
        <svg viewBox="0 0 160 160" style={{ width: 200, height: 200, flexShrink: 0 }}>
          <circle cx="80" cy="80" r="64" fill="none" stroke={DS.chip} strokeWidth="22" />
          {d.arcs.map((a, i) => (
            <circle key={i} cx="80" cy="80" r="64" fill="none" stroke={a.color} strokeWidth="22"
              strokeDasharray={a.dash} strokeDashoffset={a.offset} transform="rotate(-90 80 80)" />
          ))}
          <text x="80" y="78" textAnchor="middle" style={{ font: '700 27px Inter', fill: DS.ink }}>{d.centerNum}</text>
          <text x="80" y="98" textAnchor="middle" style={{ font: '600 11px Inter', fill: DS.dim, letterSpacing: '.06em' }}>{d.centerLabel}</text>
        </svg>
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 9, flex: '0 0 auto', minWidth: 140 }}>
        {d.legend.map((l, i) => (
          <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12.5}}>
            <span style={{ width: 9, height: 9, borderRadius: 3, background: l.color, flexShrink: 0 }} />
            <span style={{ color: DS.muted, flex: 1, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{l.label}</span>
            <span style={{ fontWeight: 600, fontVariantNumeric: 'tabular-nums', color: DS.ink }}>{l.value}</span>
          </div>
        ))}
      </div>
    </div>
  );
}

function Bars({ items }) {
  const max = Math.max(1, ...items.map((i) => i.value));
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 13 }}>
      {items.slice(0, 6).map((b, i) => (
        <div key={i}>
          <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13.5, marginBottom: 5, gap: 10 }}>
            <span style={{ color: DS.ink2, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{b.label}</span>
            <span style={{ fontWeight: 600, fontVariantNumeric: 'tabular-nums', color: DS.ink, flexShrink: 0 }}>{window.fmt(b.value)}</span>
          </div>
          <div style={{ height: 7, background: DS.chip, borderRadius: 5, overflow: 'hidden' }}>
            <div style={{ height: '100%', width: Math.round(b.value / max * 100) + '%', background: DS.blue, borderRadius: 5, transition: 'width .35s' }} />
          </div>
        </div>
      ))}
      {items.length === 0 && <div style={{ fontSize: 13.5, color: DS.faint, padding: '8px 0' }}>No data in range.</div>}
    </div>
  );
}

function LineChart({ vals, labels, allLabels }) {
  const L = buildLine(vals, labels, allLabels);
  const SVG_H = 158;
  const [hi, setHi] = React.useState(null);
  const wrap = React.useRef(null);
  const n = L.points.length;
  const onMove = (e) => {
    if (!wrap.current || n === 0) return;
    const r = wrap.current.getBoundingClientRect();
    const frac = Math.min(1, Math.max(0, (e.clientX - r.left) / r.width));
    setHi(Math.round(frac * (n - 1)));
  };
  const pt = (hi != null && L.points[hi]) ? L.points[hi] : null;
  const leftPct = pt ? (pt.x / L.W) * 100 : 0;
  const topPx = pt ? (pt.y / L.H) * SVG_H : 0;
  return (
    <div ref={wrap} style={{ position: 'relative' }} onMouseMove={onMove} onMouseLeave={() => setHi(null)}>
      <svg viewBox="0 0 520 170" preserveAspectRatio="none" style={{ width: '100%', height: SVG_H, display: 'block' }}>
        {L.ticks.map((t, i) => (<line key={i} x1="8" y1={t.y} x2="512" y2={t.y} stroke="#F1F2F4" strokeWidth="1" vectorEffect="non-scaling-stroke" />))}
        <path d={L.area} fill={DS.areaFill} stroke="none" />
        <path d={L.line} fill="none" stroke={DS.blue} strokeWidth="2.5" strokeLinejoin="round" strokeLinecap="round" vectorEffect="non-scaling-stroke" />
        {pt && <line x1={pt.x} x2={pt.x} y1="6" y2={L.baseY} stroke={DS.blue} strokeWidth="1" strokeDasharray="3 3" vectorEffect="non-scaling-stroke" opacity="0.5" />}
      </svg>
      {pt && <div style={{ position: 'absolute', left: leftPct + '%', top: topPx, width: 9, height: 9, marginLeft: -4.5, marginTop: -4.5, borderRadius: '50%', background: '#fff', border: '2px solid ' + DS.blue, pointerEvents: 'none' }} />}
      {pt && (
        <div style={{ position: 'absolute', left: leftPct + '%', top: topPx - 12, transform: 'translate(-50%,-100%)', background: DS.ink, color: '#fff', borderRadius: 7, padding: '5px 9px', fontSize: 11.5, whiteSpace: 'nowrap', pointerEvents: 'none', boxShadow: '0 2px 8px rgba(16,18,28,.22)', zIndex: 5 }}>
          <span style={{ color: '#A6ABB2' }}>{pt.label}</span>&nbsp;&nbsp;<span style={{ fontWeight: 700, fontVariantNumeric: 'tabular-nums' }}>{window.fmt(pt.v)}</span>
        </div>
      )}
      <div style={{ display: 'flex', justifyContent: 'space-between', marginTop: 6, padding: '0 2px' }}>
        {L.xLabels.map((x, i) => (<span key={i} style={{ fontSize: 11.5, color: DS.faint, fontVariantNumeric: 'tabular-nums' }}>{x.label}</span>))}
      </div>
    </div>
  );
}

// vertical bar chart — last bar (current period) is inked, the rest are faint.
function VBars({ vals, labels }) {
  const max = Math.max(1, ...vals);
  const n = vals.length;
  const H = 220;
  const [hi, setHi] = React.useState(null);
  const barH = (v) => Math.max(2, Math.round(v / max * H));
  const showTip = hi != null && vals[hi] != null;
  const leftPct = hi != null ? ((hi + 0.5) / n) * 100 : 0;
  const topPx = hi != null ? (H - barH(vals[hi])) : 0;
  return (
    <div>
      <div style={{ position: 'relative', display: 'flex', alignItems: 'flex-end', gap: n > 45 ? 1 : n > 20 ? 2 : 4, height: H }}>
        {vals.map((v, i) => (
          <div key={i} onMouseEnter={() => setHi(i)} onMouseLeave={() => setHi(null)}
            style={{ flex: 1, minWidth: 2, height: barH(v) + 'px',
              background: (i === n - 1 || i === hi) ? DS.blue : DS.blueTints[3], borderRadius: 3, transition: 'background .12s' }} />
        ))}
        {showTip && (
          <div style={{ position: 'absolute', left: leftPct + '%', top: topPx, transform: 'translate(-50%,-100%)', marginTop: -8, background: DS.ink, color: '#fff', borderRadius: 7, padding: '5px 9px', fontSize: 11.5, whiteSpace: 'nowrap', pointerEvents: 'none', boxShadow: '0 3px 10px rgba(16,18,28,.24)', zIndex: 6 }}>
            <span style={{ opacity: 0.62 }}>{labels[hi]}</span>&nbsp;&nbsp;<span style={{ fontWeight: 700, fontVariantNumeric: 'tabular-nums' }}>{window.fmt(vals[hi])}</span>
          </div>
        )}
      </div>
      <div style={{ display: 'flex', justifyContent: 'space-between', marginTop: 8, padding: '0 2px' }}>
        <span style={{ fontSize: 11.5, color: DS.faint, fontVariantNumeric: 'tabular-nums' }}>{labels[0]}</span>
        {n > 2 && <span style={{ fontSize: 11.5, color: DS.faint, fontVariantNumeric: 'tabular-nums' }}>{labels[Math.floor((n - 1) / 2)]}</span>}
        <span style={{ fontSize: 11.5, color: DS.faint, fontVariantNumeric: 'tabular-nums' }}>{labels[n - 1]}</span>
      </div>
    </div>
  );
}

// over-time chart with a bar/line toggle. Remembers the choice (localStorage),
// so all time-charts default to whatever was last picked.
function TimeChart({ vals, labels, initial }) {
  const saved = (typeof localStorage !== 'undefined' && localStorage.getItem('dash_time_chart')) || null;
  const [mode, setMode] = React.useState(saved || (initial === 'line' ? 'line' : 'bars'));
  const pick = (m) => { setMode(m); try { localStorage.setItem('dash_time_chart', m); } catch (e) { /* ignore */ } };
  const barIcon = (<svg width="13" height="13" viewBox="0 0 14 14" style={{ display: 'block' }}><rect x="1" y="6" width="3" height="7" rx="1" fill="currentColor" /><rect x="5.5" y="2" width="3" height="11" rx="1" fill="currentColor" /><rect x="10" y="8" width="3" height="5" rx="1" fill="currentColor" /></svg>);
  const lineIcon = (<svg width="13" height="13" viewBox="0 0 14 14" style={{ display: 'block' }}><polyline points="1,10 5,5 8,8 13,2" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinejoin="round" strokeLinecap="round" /></svg>);
  return (
    <div>
      <div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: 8 }}>
        <div style={{ display: 'inline-flex', gap: 2, background: DS.chip, borderRadius: 8, padding: 2 }}>
          {[['bars', barIcon, 'Barras'], ['line', lineIcon, 'Línea']].map(([m, icon, label]) => (
            <button key={m} type="button" onClick={() => pick(m)} title={label} aria-label={label} aria-pressed={mode === m}
              style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 26, height: 21, border: 'none', borderRadius: 6, cursor: 'pointer', padding: 0,
                background: mode === m ? DS.card : 'transparent', color: mode === m ? DS.blue : DS.faint,
                boxShadow: mode === m ? '0 1px 2px rgba(16,18,28,.10)' : 'none', transition: 'color .12s, background .12s' }}>
              {icon}
            </button>
          ))}
        </div>
      </div>
      {mode === 'line' ? <LineChart vals={vals} labels={labels} /> : <VBars vals={vals} labels={labels} />}
    </div>
  );
}

// charts: array of { span, title, headline, kind, ...props }
function ChartsBand({ charts }) {
  return (
    <div style={{ display: 'grid', gridTemplateColumns: 'repeat(12,minmax(0,1fr))', gap: 16 }}>
      {charts.map((c, i) => (
        <ChartCard key={i} title={c.title} headline={c.headline} span={c.span}>
          {c.kind === 'donut' && <Donut segs={c.segs} centerNum={c.centerNum} centerLabel={c.centerLabel} />}
          {c.kind === 'bars' && <Bars items={c.items} />}
          {(c.kind === 'vbars' || c.kind === 'line') && <TimeChart vals={c.vals} labels={c.labels} initial={c.kind} />}
        </ChartCard>
      ))}
    </div>
  );
}

// ---- mono icon chip --------------------------------------------------------
function IconChip({ children, color, bg }) {
  return <div style={{ fontFamily: DS.mono, fontSize: 11, fontWeight: 600, color: color || DS.muted, background: bg || DS.chip, borderRadius: 6, padding: '3px 5px', letterSpacing: '.02em' }}>{children}</div>;
}

// ---- selectable color palettes (professional) ------------------------------
// Each palette overrides the DS color tokens; font/logos stay constant. Applied
// by mutating the shared DS object + the HTML :root vars (calendar), then the
// app re-renders. Choice persists in localStorage.
// Shared NEUTRAL base (canvas stays constant); the palette only swaps the
// ACCENT used for details — charts, highlights, active states, links.
const PAL_BASE = {
  bg: '#F5F3EF', card: '#fff', ink: '#1B1A18', ink2: '#3A3936', muted: '#57544E', dim: '#78746C', faint: '#A8A39A',
  border: '#E7E2D9', border2: '#EFEBE3', field: '#DED8CD', chip: '#F0ECE3', gray: '#E4E0D8',
  pos: '#3F7D57', posBg: '#E8F0E9', neg: '#B8443A', negBg: '#F6E9E6',
};
function hexA(h, a) { const n = parseInt(h.slice(1), 16); return 'rgba(' + ((n >> 16) & 255) + ',' + ((n >> 8) & 255) + ',' + (n & 255) + ',' + a + ')'; }
function mkPal(name, tints, hi, hiBorder) {
  const accent = tints[0];
  return { name, tokens: { ...PAL_BASE, blue: accent, blueTints: tints, areaFill: hexA(accent, 0.10), info: accent, link: accent, linkBg: hi, hi, hiBorder } };
}
// Muted / deep professional accents (desaturated, mid-dark — not black).
// Navy is the chosen default (first in the list).
const PALETTES = [
  mkPal('Navy', ['#2B4066', '#556A8E', '#93A2BC', '#CBD3E0'], '#EBEFF5', '#D8E0EC'),
  mkPal('Graphite', ['#3E4650', '#697079', '#A2A8B0', '#D3D6DB'], '#ECEEF0', '#DEE1E5'),
  mkPal('Slate Blue', ['#3C5A78', '#647F99', '#9CB0C4', '#CFDAE4'], '#EBF0F5', '#D8E2EC'),
  mkPal('Pine', ['#33564A', '#5C7A6F', '#94AAA1', '#CCD8D2'], '#EAF0ED', '#D6E1DB'),
  mkPal('Aubergine', ['#4E3F63', '#75688A', '#A79FB7', '#D7D2DF'], '#EFECF4', '#E0DAEA'),
  mkPal('Clay', ['#79503C', '#9E7A6A', '#C1A99B', '#E0D3CB'], '#F4ECE7', '#E9DBD1'),
  mkPal('Ink', ['#1B1A18', '#57544E', '#A8A39A', '#DAD5CB'], '#F0ECE3', '#E3DCCE'),
];
function applyPalette(idx) {
  const i = Math.max(0, Math.min(PALETTES.length - 1, idx | 0));
  const t = PALETTES[i].tokens;
  Object.assign(DS, t);
  window.CURRENT_PALETTE = i;
  try { localStorage.setItem('dash_palette', String(i)); } catch (e) { /* ignore */ }
  const r = document.documentElement && document.documentElement.style;
  if (r) {
    r.setProperty('--accent', t.blue); r.setProperty('--bg', t.bg); r.setProperty('--surface', t.card);
    r.setProperty('--surface-2', t.chip); r.setProperty('--border', t.border); r.setProperty('--border-2', t.border2);
    r.setProperty('--text', t.ink); r.setProperty('--text-dim', t.muted); r.setProperty('--text-faint', t.faint);
  }
}

Object.assign(window, {
  DS, PALETTES, applyPalette, logoFor, niceMax, smoothPath, buildLine, buildDonut,
  Trend, PeriodPills, GhostBtn, ChartCard, Donut, Bars, VBars, LineChart, TimeChart, ChartsBand, IconChip,
});

// apply the saved palette before the app first renders
applyPalette(Number((typeof localStorage !== 'undefined' && localStorage.getItem('dash_palette')) || 0));
