// ================= Shared UI primitives =================
const { useState, useEffect, useRef, useMemo, useCallback } = React;

// ---------- Icons (tiny inline SVG set) ----------
const Icon = ({ name, size = 14, ...props }) => {
  const s = size;
  const common = { width: s, height: s, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.8, strokeLinecap: "round", strokeLinejoin: "round", ...props };
  const paths = {
    home:       <><path d="M3 10.5 12 3l9 7.5"/><path d="M5 9v12h14V9"/></>,
    brand:      <><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></>,
    pacing:     <><path d="M3 3v18h18"/><path d="M7 14l4-4 4 4 5-6"/></>,
    nonmedia:   <><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 10h18"/><path d="M7 15h4"/></>,
    alert:      <><path d="M12 2 1 21h22L12 2z"/><path d="M12 10v5"/><circle cx="12" cy="18" r=".8" fill="currentColor"/></>,
    search:     <><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></>,
    bell:       <><path d="M6 8a6 6 0 1 1 12 0v4l2 3H4l2-3z"/><path d="M10 19a2 2 0 0 0 4 0"/></>,
    settings:   <><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"/></>,
    chev_r:     <path d="m9 6 6 6-6 6"/>,
    chev_d:     <path d="m6 9 6 6 6-6"/>,
    chev_u:     <path d="m6 15 6-6 6 6"/>,
    chev_l:     <path d="m15 6-6 6 6 6"/>,
    plus:       <><path d="M12 5v14"/><path d="M5 12h14"/></>,
    x:          <><path d="M18 6 6 18"/><path d="m6 6 12 12"/></>,
    sun:        <><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></>,
    moon:       <path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/>,
    refresh:    <><path d="M3 12a9 9 0 0 1 15.3-6.4L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-15.3 6.4L3 16"/><path d="M3 21v-5h5"/></>,
    export:     <><path d="M12 3v12"/><path d="m7 8 5-5 5 5"/><path d="M5 21h14"/></>,
    filter:     <path d="M3 5h18l-7 9v6l-4-2v-4L3 5z"/>,
    copy:       <><rect x="9" y="9" width="12" height="12" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></>,
    external:   <><path d="M14 3h7v7"/><path d="M10 14 21 3"/><path d="M21 14v5a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5"/></>,
    link:       <><path d="M10 13a5 5 0 0 0 7 0l3-3a5 5 0 0 0-7-7l-1 1"/><path d="M14 11a5 5 0 0 0-7 0l-3 3a5 5 0 0 0 7 7l1-1"/></>,
    dots:       <><circle cx="5" cy="12" r="1.3" fill="currentColor"/><circle cx="12" cy="12" r="1.3" fill="currentColor"/><circle cx="19" cy="12" r="1.3" fill="currentColor"/></>,
    check:      <path d="M5 13l4 4L19 7"/>,
    arrow_up:   <><path d="M12 19V5"/><path d="m5 12 7-7 7 7"/></>,
    arrow_dn:   <><path d="M12 5v14"/><path d="m19 12-7 7-7-7"/></>,
    user:       <><circle cx="12" cy="8" r="4"/><path d="M4 21c1.5-4 4.5-6 8-6s6.5 2 8 6"/></>,
    clock:      <><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></>,
    cmd:        <path d="M6 6h4v4H6a2 2 0 1 1 0-4zm8 0h4a2 2 0 1 1 0 4h-4V6zm0 8h4a2 2 0 1 1-4 0v4h-4v-4a2 2 0 1 1-4 0h4V10h4v4z"/>,
    bq:         <><rect x="3" y="4" width="18" height="6" rx="1"/><rect x="3" y="14" width="18" height="6" rx="1"/><circle cx="7" cy="7" r=".8" fill="currentColor"/><circle cx="7" cy="17" r=".8" fill="currentColor"/></>,
    eye:        <><path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7-11-7-11-7z"/><circle cx="12" cy="12" r="3"/></>,
    eye_off:    <><path d="m3 3 18 18"/><path d="M10.6 10.6a3 3 0 0 0 4.2 4.2"/><path d="M9 5.5A11 11 0 0 1 12 5c7 0 11 7 11 7a19 19 0 0 1-3.2 4.2"/><path d="M6.1 6.1A19 19 0 0 0 1 12s4 7 11 7a11 11 0 0 0 5-1.2"/></>,
    google:     <><path d="M21.8 12.2c0-.7-.1-1.4-.2-2H12v3.8h5.5a4.7 4.7 0 0 1-2 3v2.6h3.3c2-1.8 3-4.4 3-7.4z" fill="#4285f4" stroke="none"/><path d="M12 22c2.7 0 5-.9 6.7-2.4l-3.3-2.6c-.9.6-2 1-3.4 1-2.6 0-4.8-1.7-5.6-4H3v2.5A10 10 0 0 0 12 22z" fill="#34a853" stroke="none"/><path d="M6.4 14a6 6 0 0 1 0-4V7.5H3a10 10 0 0 0 0 9L6.4 14z" fill="#fbbc04" stroke="none"/><path d="M12 5.6c1.5 0 2.8.5 3.8 1.5l2.9-2.9A10 10 0 0 0 3 7.5L6.4 10c.8-2.3 3-4 5.6-4z" fill="#ea4335" stroke="none"/></>,
    menu:       <><path d="M3 6h18"/><path d="M3 12h18"/><path d="M3 18h18"/></>,
    dollar:     <><path d="M12 1v22"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></>,
    layers:     <><path d="m2 7 10-5 10 5-10 5L2 7z"/><path d="m2 17 10 5 10-5"/><path d="m2 12 10 5 10-5"/></>,
    trend_up:   <><path d="m3 17 6-6 4 4 8-8"/><path d="M14 7h7v7"/></>,
    trend_dn:   <><path d="m3 7 6 6 4-4 8 8"/><path d="M14 17h7v-7"/></>,
    split:      <><path d="M3 3v18"/><path d="M21 3v18"/><path d="M12 3v18"/></>,
    folder:     <path d="M3 6a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6z"/>,
    logo:       <><path d="M4 4 L12 4 L20 20 L12 20 Z" fill="currentColor" stroke="none"/><path d="M20 4 L12 20" stroke="currentColor" strokeWidth="2.5"/></>,
  };
  return <svg {...common}>{paths[name] || null}</svg>;
};

// ---------- Platform pill ----------
const PLAT_LOGOS = {
  meta: (
    <svg viewBox="0 0 36 36">
      <defs>
        <linearGradient id="mg" x1="0" y1="0" x2="1" y2="1">
          <stop offset="0%" stopColor="#0081fb"/>
          <stop offset="100%" stopColor="#0064e0"/>
        </linearGradient>
      </defs>
      <path fill="url(#mg)" d="M9.5 9c-3.5 0-6 3.1-6 8s2.5 9 6 9c2.3 0 4-1.5 6.3-5.4l2-3.5c.8-1.5 1.6-2.8 2.5-3.8 1.1-1.3 2.2-1.8 3.3-1.8 2 0 3.2 2 3.2 5.5s-1.2 5.5-3.2 5.5c-1 0-1.8-.4-2.7-1.3l-1.7 2.5c1.3 1.3 2.7 2 4.4 2 3.5 0 6-3.1 6-8s-2.5-9-6-9c-2.3 0-4 1.5-6.3 5.4l-2 3.5c-.8 1.5-1.6 2.8-2.5 3.8-1.1 1.3-2.2 1.8-3.3 1.8-2 0-3.2-2-3.2-5.5S7.5 11.5 9.5 11.5c1 0 1.8.4 2.7 1.3l1.7-2.5C12.6 9 11.2 8.3 9.5 8.3"/>
    </svg>
  ),
  google: (
    <svg viewBox="0 0 36 36">
      <path fill="#fbbc04" d="M12.6 6.5l6 10.4-4.6 8-6-10.4z"/>
      <path fill="#4285f4" d="M20.6 25.2l-4-7c-.7-1.2-.7-2.6 0-3.8l4-7 6.8 11.8c1 1.7-.2 3.9-2.2 3.9h-2.2c-.9 0-1.7.5-2.2 1.2z"/>
      <circle fill="#34a853" cx="9.2" cy="25.1" r="4.5"/>
    </svg>
  ),
  dv360: (
    <svg viewBox="0 0 36 36">
      <circle fill="#34a853" cx="18" cy="18" r="13"/>
      <path fill="#fff" d="M13 12h4.5c3.3 0 5.5 2.1 5.5 6s-2.2 6-5.5 6H13V12zm2.5 2.3v7.4H17c2 0 3.3-1.4 3.3-3.7 0-2.3-1.2-3.7-3.3-3.7h-1.5z"/>
    </svg>
  ),
  tiktok: (
    <svg viewBox="0 0 36 36">
      <rect width="36" height="36" rx="7" fill="#010101"/>
      <path fill="#25f4ee" d="M21.7 9.3h-3.1v13.9a2.7 2.7 0 1 1-2.7-2.7c.3 0 .5 0 .7.1v-3.2a5.8 5.8 0 1 0 5.1 5.7v-7.9c1.1.8 2.5 1.3 3.9 1.3v-3.1c-2.1 0-3.9-1.7-3.9-3z"/>
      <path fill="#fe2c55" d="M22.9 10.2h-3.1v13.9a2.7 2.7 0 1 1-2.7-2.7c.3 0 .5 0 .7.1v-3.2a5.8 5.8 0 1 0 5.1 5.7v-7.9c1.1.8 2.5 1.3 3.9 1.3v-3.1c-2.1 0-3.9-1.7-3.9-4.1z"/>
      <path fill="#fff" d="M22.3 9.7h-3.1v13.9a2.7 2.7 0 1 1-2.7-2.7c.3 0 .5 0 .7.1v-3.2a5.8 5.8 0 1 0 5.1 5.7v-7.9c1.1.8 2.5 1.3 3.9 1.3v-3.1c-2.1 0-3.9-1.7-3.9-4.1z"/>
    </svg>
  ),
  ttd: (
    <svg viewBox="0 0 36 36">
      <rect width="36" height="36" rx="7" fill="#0098eb"/>
      <path fill="#fff" d="M8 11h20v3H20v11h-4V14H8z"/>
    </svg>
  ),
  youtube: (
    <svg viewBox="0 0 36 36">
      <rect x="2" y="8" width="32" height="20" rx="5" fill="#ff0000"/>
      <path fill="#fff" d="M15 13l8 5-8 5z"/>
    </svg>
  ),
};

const PlatPill = ({ platform, size = 20 }) => {
  const logo = PLAT_LOGOS[platform];
  if (logo) {
    return (
      <span title={platform} style={{
        display: "inline-flex", alignItems: "center", justifyContent: "center",
        width: size, height: size, borderRadius: Math.max(3, size * 0.22),
        overflow: "hidden", flex: "none",
      }}>
        {React.cloneElement(logo, { width: size, height: size })}
      </span>
    );
  }
  return <span className={`plat ${platform}`} style={{ width: size, height: size, fontSize: size * 0.5 }} title={platform}>?</span>;
};

// ---------- Status ----------
const StatusDot = ({ status }) => {
  const tone = status?.tone || "default";
  return (
    <span className="status-dot text-xs">
      <span className={`dot ${tone === "success" ? "success" : tone === "danger" ? "danger" : tone === "info" ? "info" : ""}`} />
      <span className="muted">{status?.label}</span>
    </span>
  );
};

// ---------- Sparkline ----------
const Sparkline = ({ data, width = 72, height = 22, color, showFill = true }) => {
  if (!data || data.length === 0) return null;
  const min = Math.min(...data);
  const max = Math.max(...data);
  const range = max - min || 1;
  const step = width / (data.length - 1);
  const pts = data.map((v, i) => [i * step, height - ((v - min) / range) * (height - 4) - 2]);
  const d = pts.map((p, i) => (i === 0 ? `M${p[0]},${p[1]}` : `L${p[0]},${p[1]}`)).join(" ");
  const fillD = `${d} L${width},${height} L0,${height} Z`;
  const stroke = color || "var(--accent)";
  return (
    <svg className="spark" width={width} height={height}>
      {showFill && <path d={fillD} fill={stroke} opacity="0.14"/>}
      <path d={d} fill="none" stroke={stroke} strokeWidth="1.3"/>
      <circle cx={pts[pts.length - 1][0]} cy={pts[pts.length - 1][1]} r="1.6" fill={stroke}/>
    </svg>
  );
};

// ---------- Pacing bar ----------
const Pacing = ({ pct, planPct = null, tone = "success", width = 80 }) => {
  const clamp = Math.max(0, Math.min(1.5, pct));
  const fillPct = Math.min(1, clamp);
  return (
    <div className={`pacing ${tone}`} style={{ width }}>
      <div className="fill" style={{ width: `${fillPct * 100}%` }}/>
      {planPct != null && <div className="marker" style={{ left: `${planPct * 100}%` }}/>}
    </div>
  );
};

// ---------- KPI card ----------
const KPI = ({ label, value, sub, trend, spark, sparkColor }) => (
  <div className="kpi">
    <div className="label">{label}</div>
    <div className="val">{value}</div>
    <div className="sub">
      {trend != null && <span className={`trend ${trend >= 0 ? "up" : "down"}`}>{trend >= 0 ? "▲" : "▼"} {Math.abs(trend * 100).toFixed(1)}%</span>}
      {sub && <span>{sub}</span>}
      {spark && <span style={{ marginLeft: "auto" }}><Sparkline data={spark} color={sparkColor} /></span>}
    </div>
  </div>
);

// ---------- Tabs ----------
const Tabs = ({ tabs, active, onChange }) => (
  <div className="tabs">
    {tabs.map(t => (
      <div key={t.id} className={`tab ${active === t.id ? "active" : ""}`} onClick={() => onChange(t.id)}>
        {t.label} {t.count != null && <span className="count">{t.count}</span>}
      </div>
    ))}
  </div>
);

// ---------- Breadcrumbs ----------
const Crumbs = ({ items }) => (
  <div className="crumbs">
    {items.map((c, i) => (
      <React.Fragment key={i}>
        <span className={`crumb ${i === items.length - 1 ? "current" : ""}`} onClick={c.onClick}>{c.label}</span>
        {i < items.length - 1 && <span className="dim">/</span>}
      </React.Fragment>
    ))}
  </div>
);

// ---------- Table ----------
const Table = ({ columns, rows, onRowClick, onRowContext, rowKey = (r) => r.id, selected, style, striped = false, lined = false }) => {
  const [sort, setSort] = useState(null);
  const sorted = useMemo(() => {
    if (!sort) return rows;
    const col = columns.find(c => c.key === sort.key);
    if (!col?.sort) return rows;
    const dir = sort.dir === "asc" ? 1 : -1;
    return [...rows].sort((a, b) => col.sort(a, b) * dir);
  }, [rows, sort, columns]);
  const toggleSort = (k) => {
    setSort(prev => {
      if (!prev || prev.key !== k) return { key: k, dir: "desc" };
      if (prev.dir === "desc") return { key: k, dir: "asc" };
      return null;
    });
  };
  return (
    <div className="tbl-wrap" style={style}>
      <div className="tbl-scroll">
        <table className={`tbl ${striped ? "striped" : ""} ${lined ? "lined" : ""}`}>
          <thead>
            <tr>
              {columns.map(c => {
                const sorting = sort?.key === c.key;
                const cls = [c.sort && "sortable", c.right && "right", sorting && sort.dir].filter(Boolean).join(" ");
                return (
                  <th key={c.key} className={cls} style={{ width: c.width, minWidth: c.minWidth }} onClick={c.sort ? () => toggleSort(c.key) : null}>
                    {c.header}
                    {c.sort && <span className="sort-ind">{sorting ? (sort.dir === "asc" ? "▲" : "▼") : "◇"}</span>}
                  </th>
                );
              })}
            </tr>
          </thead>
          <tbody>
            {sorted.map(r => (
              <tr key={rowKey(r)}
                  className={`${onRowClick ? "clickable" : ""} ${selected === rowKey(r) ? "selected" : ""}`}
                  onClick={onRowClick ? (e) => onRowClick(r, e) : null}
                  onContextMenu={onRowContext ? (e) => { e.preventDefault(); onRowContext(r, e); } : null}>
                {columns.map(c => (
                  <td key={c.key} className={`${c.right ? "right" : ""} ${c.num ? "num" : ""}`} style={{ width: c.width }}>
                    {c.render ? c.render(r) : r[c.key]}
                  </td>
                ))}
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
};

// ---------- Context menu ----------
const CtxMenu = ({ items, pos, onClose }) => {
  const ref = useRef(null);
  useEffect(() => {
    const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) onClose(); };
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    document.addEventListener("mousedown", onDoc);
    document.addEventListener("keydown", onKey);
    return () => { document.removeEventListener("mousedown", onDoc); document.removeEventListener("keydown", onKey); };
  }, [onClose]);
  if (!pos) return null;
  return (
    <div className="ctx-menu" ref={ref} style={{ left: pos.x, top: pos.y }}>
      {items.map((it, i) => it.divider ? (
        <div key={i} className="ctx-divider"/>
      ) : (
        <div key={i} className="ctx-item" onClick={() => { it.onClick?.(); onClose(); }}>
          {it.icon && <Icon name={it.icon} size={13} />}
          <span>{it.label}</span>
          {it.shortcut && <span className="trail">{it.shortcut}</span>}
        </div>
      ))}
    </div>
  );
};

// ---------- Toast ----------
const useToast = () => {
  const [toasts, setToasts] = useState([]);
  const push = useCallback((msg, opts = {}) => {
    const id = Math.random().toString(36).slice(2);
    setToasts(t => [...t, { id, msg, icon: opts.icon || "check" }]);
    setTimeout(() => setToasts(t => t.filter(x => x.id !== id)), opts.duration || 2400);
  }, []);
  const node = (
    <div className="toast-wrap">
      {toasts.map(t => (
        <div key={t.id} className="toast">
          <Icon name={t.icon} size={13} style={{ color: "var(--accent)" }}/>
          <span>{t.msg}</span>
        </div>
      ))}
    </div>
  );
  return [node, push];
};

// ---------- Donut ----------
const Donut = ({ items, size = 140, thick = 18 }) => {
  const total = items.reduce((s, it) => s + it.value, 0) || 1;
  const r = (size - thick) / 2;
  const cx = size / 2, cy = size / 2;
  const circ = 2 * Math.PI * r;
  let offset = 0;
  return (
    <svg width={size} height={size}>
      <circle cx={cx} cy={cy} r={r} fill="none" stroke="var(--bg-3)" strokeWidth={thick}/>
      {items.map((it, i) => {
        const frac = it.value / total;
        const len = frac * circ;
        const gap = circ - len;
        const el = (
          <circle key={i} cx={cx} cy={cy} r={r} fill="none"
            stroke={it.color}
            strokeWidth={thick}
            strokeDasharray={`${len} ${gap}`}
            strokeDashoffset={-offset}
            transform={`rotate(-90 ${cx} ${cy})`}
          />
        );
        offset += len;
        return el;
      })}
    </svg>
  );
};

// ---------- Line chart (small) ----------
const LineChart = ({ series, width = 760, height = 220, padding = { t: 12, r: 16, b: 24, l: 44 } }) => {
  // series: [{ label, color, data: number[] }]
  const days = series[0]?.data.length || 0;
  const all = series.flatMap(s => s.data);
  const min = 0;
  const max = Math.max(...all, 1) * 1.1;
  const innerW = width - padding.l - padding.r;
  const innerH = height - padding.t - padding.b;
  const x = (i) => padding.l + (i / Math.max(1, days - 1)) * innerW;
  const y = (v) => padding.t + innerH - ((v - min) / (max - min)) * innerH;
  const yTicks = 4;
  return (
    <svg width={width} height={height}>
      {/* grid */}
      {Array.from({ length: yTicks + 1 }).map((_, i) => {
        const v = (max / yTicks) * i;
        const yy = y(v);
        return (
          <g key={i}>
            <line x1={padding.l} x2={width - padding.r} y1={yy} y2={yy} stroke="var(--grid)" strokeDasharray={i === 0 ? "0" : "3 3"}/>
            <text x={padding.l - 6} y={yy + 3} textAnchor="end" fontSize="10" fill="var(--text-3)" fontFamily="var(--font-mono)">{window.fmt.hkdK(v)}</text>
          </g>
        );
      })}
      {/* x ticks */}
      {Array.from({ length: Math.min(6, days) }).map((_, i) => {
        const idx = Math.round((days - 1) * (i / 5));
        const xx = x(idx);
        return <text key={i} x={xx} y={height - 6} textAnchor="middle" fontSize="10" fill="var(--text-3)" fontFamily="var(--font-mono)">d{idx + 1}</text>;
      })}
      {/* series */}
      {series.map((s, si) => {
        const d = s.data.map((v, i) => `${i === 0 ? "M" : "L"}${x(i)},${y(v)}`).join(" ");
        const fillD = `${d} L${x(s.data.length - 1)},${padding.t + innerH} L${x(0)},${padding.t + innerH} Z`;
        return (
          <g key={si}>
            {s.fill && <path d={fillD} fill={s.color} opacity="0.09"/>}
            <path d={d} fill="none" stroke={s.color} strokeWidth="1.6"/>
          </g>
        );
      })}
    </svg>
  );
};

// ---------- Stacked bar ----------
const StackedBar = ({ items, width = 200, height = 8 }) => {
  const total = items.reduce((s, it) => s + it.value, 0) || 1;
  let offset = 0;
  return (
    <svg width={width} height={height}>
      {items.map((it, i) => {
        const w = (it.value / total) * width;
        const rect = <rect key={i} x={offset} y={0} width={w - 1} height={height} fill={it.color} rx={2}/>;
        offset += w;
        return rect;
      })}
    </svg>
  );
};

Object.assign(window, {
  Icon, PlatPill, StatusDot, Sparkline, Pacing, KPI, Tabs, Crumbs, Table,
  CtxMenu, useToast, Donut, LineChart, StackedBar,
});
