// Shared small components: icons, sparklines, score ring, confetti

const Icon = {
  home: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M3 11l9-7 9 7M5 10v10h14V10"/></svg>,
  results: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M4 20V10M10 20V4M16 20v-8M22 20H2"/></svg>,
  services: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg>,
  budget: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 3v9l6 4"/></svg>,
  campaigns: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M3 11l18-7v16l-18-7M7 10v7"/></svg>,
  listings: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M21 10c0 7-9 13-9 13S3 17 3 10a9 9 0 1118 0z"/><circle cx="12" cy="10" r="3"/></svg>,
  reviews: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M12 2l3 6.5 7 1-5 5 1 7-6-3-6 3 1-7-5-5 7-1z"/></svg>,
  chat: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15a3 3 0 01-3 3H8l-5 4V6a3 3 0 013-3h12a3 3 0 013 3z"/></svg>,
  bench: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M3 21l6-6 4 4 8-8M15 11h6v6"/></svg>,
  invoice: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M4 3h12l4 4v14H4zM14 3v5h5"/></svg>,
  settings: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 01-2.83 2.83l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09a1.65 1.65 0 00-1-1.51 1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09a1.65 1.65 0 001.51-1 1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 012.83-2.83l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 014 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z"/></svg>,
  bell: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M18 8a6 6 0 10-12 0c0 7-3 9-3 9h18s-3-2-3-9M13.7 21a2 2 0 01-3.4 0"/></svg>,
  search: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>,
  spark: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M12 2l1.5 4.5L18 8l-4.5 1.5L12 14l-1.5-4.5L6 8l4.5-1.5zM19 14l.8 2.3L22 17l-2.2.7L19 20l-.8-2.3L16 17l2.2-.7zM5 4l.6 1.6L7 6l-1.4.4L5 8l-.6-1.6L3 6l1.4-.4z"/></svg>,
  phone: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M22 16.9v3a2 2 0 01-2.2 2 19.8 19.8 0 01-8.6-3 19.5 19.5 0 01-6-6A20 20 0 012 4.2 2 2 0 014 2h3a2 2 0 012 1.7c.1.9.3 1.8.6 2.6a2 2 0 01-.4 2.1L7.9 9.7a16 16 0 006 6l1.3-1.3a2 2 0 012-.5c.9.3 1.7.5 2.6.6A2 2 0 0122 16.9z"/></svg>,
  star: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M12 2l3 6.5 7 1-5 5 1 7-6-3-6 3 1-7-5-5 7-1z"/></svg>,
  check: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M20 6L9 17l-5-5"/></svg>,
  dollar: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M12 2v20M17 5H9.5a3.5 3.5 0 000 7h5a3.5 3.5 0 010 7H6"/></svg>,
  sparkle: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M12 2l2 7 7 2-7 2-2 7-2-7-7-2 7-2z"/></svg>,
  trophy: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M8 21h8M12 17v4M7 4h10v5a5 5 0 01-10 0zM17 4h4v3a3 3 0 01-3 3M7 4H3v3a3 3 0 003 3"/></svg>,
  user: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0116 0"/></svg>,
  arrow: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg>,
  plus: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M12 5v14M5 12h14"/></svg>,
  send: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M22 2L11 13M22 2l-7 20-4-9-9-4z"/></svg>,
  chevron: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M6 9l6 6 6-6"/></svg>,
  zap: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M13 2L3 14h9l-1 8 10-12h-9z"/></svg>,
  leads: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M16 21v-2a4 4 0 00-4-4H6a4 4 0 00-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 11l-3 3-2-2"/></svg>,
  users: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 00-3-3.87M16 3.13a4 4 0 010 7.75"/></svg>,
  mirror: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7z"/><circle cx="12" cy="12" r="3"/></svg>,
};
window.Icon = Icon;

// ----- Sparkline -----
function Sparkline({ data, color = "var(--terra)", w = 80, h = 24, fill = true }) {
  const max = Math.max(...data), min = Math.min(...data);
  const range = max - min || 1;
  const pts = data.map((v, i) => {
    const x = (i / (data.length - 1)) * w;
    const y = h - ((v - min) / range) * h;
    return [x, y];
  });
  const d = pts.map((p, i) => (i === 0 ? `M${p[0]},${p[1]}` : `L${p[0]},${p[1]}`)).join(' ');
  const fillD = d + ` L${w},${h} L0,${h} Z`;
  return (
    <svg width={w} height={h} viewBox={`0 0 ${w} ${h}`}>
      {fill && <path d={fillD} fill={color} opacity=".14"/>}
      <path d={d} fill="none" stroke={color} strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"/>
    </svg>
  );
}
window.Sparkline = Sparkline;

// ----- SourceNote (muted data-source caption under charts/tables/cards) -----
// Single low-contrast line naming where a number comes from. See the source
// mapping in the app spec: Google Ads / Telmetrics / FreshNest CRM / GBP / GA4,
// "Calculated by LOCALACT" for derived, "Estimated by LOCALACT" for forecasts.
function SourceNote({ children, style }) {
  return (
    <div style={{ fontSize: 10.5, lineHeight: 1.3, color: 'var(--ink-4)', fontWeight: 400, marginTop: 8, ...style }}>
      {children}
    </div>
  );
}
window.SourceNote = SourceNote;

// ----- Score Ring -----
function ScoreRing({ value, max = 100, size = 120, label = "Score", color = "var(--terra)", track = "var(--cream-3)", textColor, labelColor }) {
  const r = size / 2 - 10;
  const circ = 2 * Math.PI * r;
  const pct = value / max;
  return (
    <div className="score-ring" style={{ width: size, height: size }}>
      <svg width={size} height={size}>
        <circle cx={size/2} cy={size/2} r={r} fill="none" stroke={track} strokeWidth="8"/>
        <circle cx={size/2} cy={size/2} r={r} fill="none" stroke={color} strokeWidth="8"
          strokeDasharray={circ} strokeDashoffset={circ * (1 - pct)}
          strokeLinecap="round"
          transform={`rotate(-90 ${size/2} ${size/2})`}
          style={{ transition: 'stroke-dashoffset 1s ease' }}/>
      </svg>
      <div className="val" style={textColor ? { color: textColor } : undefined}>
        {value}
        <div className="lbl" style={labelColor ? { color: labelColor } : undefined}>{label}</div>
      </div>
    </div>
  );
}
window.ScoreRing = ScoreRing;

// ----- Confetti -----
function confettiBurst() {
  const overlay = document.createElement('div');
  overlay.className = 'confetti-overlay';
  document.body.appendChild(overlay);
  const colors = ['#b35f18','#ce9e07','#0b7979','#4d6989','#a12118','#d5711c'];
  for (let i = 0; i < 80; i++) {
    const p = document.createElement('div');
    p.className = 'confetti-piece';
    p.style.left = Math.random() * 100 + 'vw';
    p.style.background = colors[i % colors.length];
    p.style.animationDelay = (Math.random() * 0.4) + 's';
    p.style.animationDuration = (2 + Math.random() * 1.2) + 's';
    p.style.transform = `rotate(${Math.random()*360}deg)`;
    p.style.borderRadius = i % 3 === 0 ? '50%' : '2px';
    overlay.appendChild(p);
  }
  setTimeout(() => overlay.remove(), 3400);
}
window.confettiBurst = confettiBurst;

// ----- Bar Chart -----
function BarChart({ data, labels, color = "var(--terra)", h = 160, highlight = -1 }) {
  const max = Math.max(...data) * 1.1;
  return (
    <div style={{ display: 'flex', alignItems: 'flex-end', gap: 6, height: h, padding: '0 2px' }}>
      {data.map((v, i) => {
        const height = (v / max) * h;
        return (
          <div key={i} style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 6 }}>
            <div style={{
              width: '100%',
              height: height + 'px',
              background: i === highlight ? color : `${color}`,
              opacity: i === highlight ? 1 : 0.7,
              borderRadius: '6px 6px 2px 2px',
              transition: 'all .3s',
              position: 'relative',
            }} title={`${labels[i]}: ${v}`}/>
            <div style={{ fontSize: 10, color: 'var(--ink-4)', fontFamily: 'var(--ff-mono)' }}>{labels[i]}</div>
          </div>
        );
      })}
    </div>
  );
}
window.BarChart = BarChart;

// ============================================================
// TOASTS, lightweight success/info feedback for button clicks
// ============================================================
const ToastBus = {
  subs: [],
  push(t) { this.subs.forEach(fn => fn(t)); },
  subscribe(fn) { this.subs.push(fn); return () => { this.subs = this.subs.filter(f => f !== fn); }; },
};
window.ToastBus = ToastBus;
window.toast = (title, msg, kind = 'ok') => ToastBus.push({ title, msg, kind, id: Date.now() + Math.random() });

function ToastLayer() {
  const [list, setList] = React.useState([]);
  React.useEffect(() => ToastBus.subscribe(t => {
    setList(l => [...l, t]);
    setTimeout(() => setList(l => l.filter(x => x.id !== t.id)), 3600);
  }), []);
  return (
    <ModalPortal><div style={{ position: 'fixed', bottom: 24, right: 24, zIndex: 10000, display: 'flex', flexDirection: 'column', gap: 10, pointerEvents: 'none' }}>
      {list.map(t => {
        const c = t.kind === 'ok' ? 'var(--ok)' : t.kind === 'warn' ? 'var(--marigold)' : t.kind === 'err' ? 'var(--terra)' : 'var(--sky)';
        return (
          <div key={t.id} style={{
            minWidth: 280, maxWidth: 360, padding: '12px 14px 12px 16px', background: 'var(--paper)',
            borderRadius: 12, border: '1px solid var(--cream-3)', boxShadow: 'var(--sh-2)',
            borderLeft: `3px solid ${c}`, pointerEvents: 'auto',
            animation: 'toast-in .28s cubic-bezier(.2,.9,.25,1.2)',
          }}>
            <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--ink)', marginBottom: 2 }}>{t.title}</div>
            {t.msg && <div style={{ fontSize: 12, color: 'var(--ink-3)', lineHeight: 1.5 }}>{t.msg}</div>}
          </div>
        );
      })}
      <style>{`@keyframes toast-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }`}</style>
    </div></ModalPortal>
  );
}
window.ToastLayer = ToastLayer;

// ============================================================
// MODAL PORTAL, renders children into document.body so position:fixed
// always anchors to the viewport (escapes transformed/filtered ancestors)
// ============================================================
function ModalPortal({ children }) {
  return ReactDOM.createPortal(children, document.body);
}
window.ModalPortal = ModalPortal;

// ============================================================
// MODAL, general purpose lightbox
// ============================================================
function Modal({ open, onClose, title, sub, children, footer, width = 560 }) {
  if (!open) return null;
  return (
    <ModalPortal><div style={{
      position: 'fixed', inset: 0, background: 'rgba(28,24,20,.42)',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      zIndex: 9000, padding: 20, animation: 'modal-bg .2s',
    }} onClick={onClose}>
      <div onClick={e => e.stopPropagation()} style={{
        background: 'var(--paper)', borderRadius: 16, width, maxWidth: '100%',
        maxHeight: '90vh', overflow: 'auto', boxShadow: 'var(--sh-2)',
        animation: 'modal-in .24s cubic-bezier(.2,.9,.25,1.1)',
      }}>
        <div style={{ padding: '20px 24px 14px', borderBottom: '1px solid var(--cream-2)', display: 'flex', alignItems: 'flex-start' }}>
          <div style={{ flex: 1 }}>
            <div style={{ fontFamily: 'var(--ff-display)', fontSize: 20, fontWeight: 500, letterSpacing: '-0.01em' }}>{title}</div>
            {sub && <div style={{ fontSize: 12.5, color: 'var(--ink-3)', marginTop: 4 }}>{sub}</div>}
          </div>
          <button onClick={onClose} style={{ background: 'var(--cream-2)', width: 28, height: 28, borderRadius: '50%', border: 'none', fontSize: 16, cursor: 'pointer', color: 'var(--ink-3)' }}>×</button>
        </div>
        <div style={{ padding: '20px 24px' }}>{children}</div>
        {footer && <div style={{ padding: '14px 24px', borderTop: '1px solid var(--cream-2)', background: 'var(--cream)', display: 'flex', gap: 8, justifyContent: 'flex-end' }}>{footer}</div>}
      </div>
      <style>{`
        @keyframes modal-bg { from { opacity: 0; } to { opacity: 1; } }
        @keyframes modal-in { from { opacity: 0; transform: translateY(12px) scale(.98); } to { opacity: 1; transform: none; } }
      `}</style>
    </div></ModalPortal>
  );
}
window.Modal = Modal;

// ============================================================
// COMMAND PALETTE, ⌘K search
// ============================================================
function CommandPalette({ open, onClose, onNav }) {
  const [q, setQ] = React.useState('');
  const items = [
    { label: 'Home', sub: 'Your dashboard', page: 'home', k: 'home' },
    { label: 'Results', sub: 'Channel performance', page: 'results', k: 'results' },
    { label: 'Strategy', sub: 'Your marketing strategy', page: 'services', k: 'services' },
    { label: 'Budget', sub: 'Monthly allocation', page: 'budget', k: 'budget' },
    { label: 'Listings', sub: '42 directories', page: 'listings', k: 'listings' },
    { label: 'Reviews', sub: '4.8★ · 428 reviews', page: 'reviews', k: 'reviews' },
    { label: 'Benchmarks', sub: 'How you compare', page: 'bench', k: 'bench' },
    { label: 'Invoice', sub: 'Billing & statements', page: 'invoice', k: 'invoice' },
    { label: 'Settings', sub: 'Account & prefs', page: 'settings', k: 'settings' },
    { label: 'Open Lenny chat', sub: 'Ask anything', action: () => window.dispatchEvent(new CustomEvent('open-lenny')), k: 'chat' },
  ];
  const filtered = items.filter(i => !q || (i.label + i.sub).toLowerCase().includes(q.toLowerCase()));
  React.useEffect(() => {
    if (!open) return;
    const h = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', h); return () => window.removeEventListener('keydown', h);
  }, [open]);
  if (!open) return null;
  return (
    <ModalPortal><div onClick={onClose} style={{ position: 'fixed', inset: 0, background: 'rgba(28,24,20,.4)', zIndex: 9500, display: 'flex', justifyContent: 'center', paddingTop: '14vh', animation: 'modal-bg .18s' }}>
      <div onClick={e => e.stopPropagation()} style={{ width: 560, maxWidth: '92%', height: 'fit-content', background: 'var(--paper)', borderRadius: 14, boxShadow: 'var(--sh-2)', overflow: 'hidden', animation: 'modal-in .22s' }}>
        <div style={{ padding: '14px 18px', borderBottom: '1px solid var(--cream-2)', display: 'flex', alignItems: 'center', gap: 10 }}>
          <span style={{ width: 16, height: 16, color: 'var(--ink-3)' }}>{Icon.search}</span>
          <input autoFocus value={q} onChange={e => setQ(e.target.value)} placeholder="Jump to a page, service, or action…" style={{ flex: 1, border: 'none', outline: 'none', fontSize: 15, fontFamily: 'var(--ff-ui)', background: 'transparent' }}/>
          <kbd style={{ fontSize: 10, background: 'var(--cream-2)', padding: '3px 6px', borderRadius: 4, color: 'var(--ink-3)' }}>esc</kbd>
        </div>
        <div style={{ maxHeight: 400, overflow: 'auto' }}>
          {filtered.length === 0 ? (
            <div style={{ padding: 40, textAlign: 'center', color: 'var(--ink-4)', fontSize: 13 }}>No matches, try a different keyword</div>
          ) : filtered.map((i, ix) => (
            <div key={ix} onClick={() => { if (i.page) onNav(i.page); if (i.action) i.action(); onClose(); }}
                 style={{ padding: '12px 18px', display: 'flex', alignItems: 'center', gap: 12, cursor: 'pointer', borderBottom: '1px solid var(--cream)' }}
                 onMouseEnter={e => e.currentTarget.style.background = 'var(--cream)'}
                 onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
              <div style={{ width: 18, height: 18, color: 'var(--ink-3)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>{Icon[i.k] || Icon.spark}</div>
              <div style={{ flex: 1 }}>
                <div style={{ fontSize: 13.5, fontWeight: 600 }}>{i.label}</div>
                <div style={{ fontSize: 11.5, color: 'var(--ink-3)' }}>{i.sub}</div>
              </div>
              <span style={{ fontSize: 11, color: 'var(--ink-4)' }}>↵</span>
            </div>
          ))}
        </div>
      </div>
    </div></ModalPortal>
  );
}
window.CommandPalette = CommandPalette;

// ============================================================
// SIMPLE DROPDOWN MENU
// ============================================================
function DropMenu({ label, options, onPick, value }) {
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);
  React.useEffect(() => {
    const h = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', h); return () => document.removeEventListener('mousedown', h);
  }, []);
  const current = options.find(o => o.v === value) || options[0];
  return (
    <div ref={ref} style={{ position: 'relative', display: 'inline-block' }}>
      <button className="btn btn-ghost btn-sm" onClick={() => setOpen(o => !o)}>
        {current ? current.l : label} ⌄
      </button>
      {open && (
        <div style={{ position: 'absolute', top: 'calc(100% + 4px)', right: 0, background: 'var(--paper)', border: '1px solid var(--cream-3)', borderRadius: 10, boxShadow: 'var(--sh-2)', minWidth: 180, overflow: 'hidden', zIndex: 50 }}>
          {options.map(o => (
            <div key={o.v} onClick={() => { onPick(o.v); setOpen(false); }}
                 style={{ padding: '10px 14px', fontSize: 12.5, cursor: 'pointer', background: value === o.v ? 'var(--cream-2)' : 'transparent', display: 'flex', alignItems: 'center', gap: 8 }}
                 onMouseEnter={e => { if (value !== o.v) e.currentTarget.style.background = 'var(--cream)'; }}
                 onMouseLeave={e => { if (value !== o.v) e.currentTarget.style.background = 'transparent'; }}>
              {value === o.v && <span style={{ color: 'var(--terra)' }}>✓</span>}
              <span style={{ flex: 1 }}>{o.l}</span>
              {o.meta && <span style={{ fontSize: 10.5, color: 'var(--ink-4)' }}>{o.meta}</span>}
            </div>
          ))}
        </div>
      )}
    </div>
  );
}
window.DropMenu = DropMenu;

// ----- Stacked channel bar -----
function ChannelBar({ segments, h = 8 }) {
  const total = segments.reduce((s, x) => s + x.v, 0);
  return (
    <div style={{ display: 'flex', height: h, borderRadius: h, overflow: 'hidden', background: 'var(--cream-2)' }}>
      {segments.map((s, i) => (
        <div key={i} style={{ width: (s.v/total*100)+'%', background: s.c }} title={`${s.l}: ${s.v}`}/>
      ))}
    </div>
  );
}
window.ChannelBar = ChannelBar;

// ----- KpiCard -----
function KpiCard({ label, value, delta, trend, color, dotColor, invert }) {
  const deltaNum = typeof delta === 'number' ? delta : parseFloat(String(delta).replace(/[^\d.-]/g,''));
  const up = invert ? deltaNum < 0 : deltaNum > 0;
  const deltaColor = up ? 'var(--ok)' : 'var(--terra)';
  return (
    <div className="card card-pad" style={{ position: 'relative', overflow: 'hidden' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 8 }}>
        {dotColor && <span style={{ width: 8, height: 8, borderRadius: '50%', background: dotColor }}/>}
        <div style={{ fontSize: 10.5, letterSpacing: '.08em', textTransform: 'uppercase', color: 'var(--ink-3)', fontWeight: 600 }}>{label}</div>
      </div>
      <div style={{ display: 'flex', alignItems: 'baseline', gap: 10, marginBottom: 10 }}>
        <div style={{ fontFamily: 'var(--ff-display)', fontSize: 36, fontWeight: 600, letterSpacing: '-0.028em', color: 'var(--ink)', lineHeight: 1, fontVariantNumeric: 'tabular-nums lining-nums' }}>{value}</div>
        {delta !== undefined && (
          <div style={{ fontSize: 12.5, fontWeight: 600, color: deltaColor, letterSpacing: '-0.008em' }}>
            {up ? '↑' : '↓'} {Math.abs(deltaNum)}{typeof delta === 'string' && delta.includes('%') ? '%' : '%'}
          </div>
        )}
      </div>
      {trend && Array.isArray(trend) && (
        <div style={{ display: 'flex', alignItems: 'flex-end', gap: 2, height: 22 }}>
          {trend.slice(-12).map((v, i) => {
            const max = Math.max(...trend);
            const pct = (v / max) * 100;
            return <div key={i} style={{ flex: 1, height: pct + '%', background: color || 'var(--terra)', opacity: 0.35 + (i / 16), borderRadius: 1, minHeight: 2 }}/>;
          })}
        </div>
      )}
    </div>
  );
}
window.KpiCard = KpiCard;

// ============================================================
// DATE RANGE PICKER, GA-style: presets sidebar + 2-month calendar
// ============================================================
// value: { preset: string, start: Date, end: Date, compare: boolean }
// onChange(value), fires on Apply only
// align: 'left' | 'right', picker alignment relative to trigger
// ------------------------------------------------------------
const DRP_MS_DAY = 86400000;
const drpStartOfDay = (d) => { const x = new Date(d); x.setHours(0,0,0,0); return x; };
const drpAddDays = (d, n) => { const x = new Date(d); x.setDate(x.getDate() + n); return x; };
const drpAddMonths = (d, n) => { const x = new Date(d); x.setMonth(x.getMonth() + n, 1); return x; };
const drpSameDay = (a, b) => a && b && a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
const drpFmtShort = (d) => `${d.getMonth() + 1}/${d.getDate()}/${d.getFullYear()}`;
const drpFmtMonth = (d) => d.toLocaleString(undefined, { month: 'long', year: 'numeric' });

// Resolve a preset key to { start, end } based on a "today" anchor.
function drpResolvePreset(key, today) {
  const t = drpStartOfDay(today);
  const dow = t.getDay(); // 0 = Sunday
  switch (key) {
    case 'today':       return { start: t, end: t };
    case 'yesterday':   { const y = drpAddDays(t, -1); return { start: y, end: y }; }
    case 'thisWeek':    return { start: drpAddDays(t, -dow), end: t };
    case 'last7':       return { start: drpAddDays(t, -6), end: t };
    case 'lastWeek':    { const sun = drpAddDays(t, -dow - 7); return { start: sun, end: drpAddDays(sun, 6) }; }
    case 'last14':      return { start: drpAddDays(t, -13), end: t };
    case 'thisMonth':   return { start: new Date(t.getFullYear(), t.getMonth(), 1), end: t };
    case 'last30':      return { start: drpAddDays(t, -29), end: t };
    case 'lastMonth':   { const s = new Date(t.getFullYear(), t.getMonth() - 1, 1); const e = new Date(t.getFullYear(), t.getMonth(), 0); return { start: s, end: e }; }
    case 'allTime':     return { start: new Date(2024, 0, 1), end: t };
    default:            return { start: drpAddDays(t, -29), end: t };
  }
}

const DRP_PRESETS = [
  { k: 'custom',     l: 'Custom' },
  { k: 'today',      l: 'Today' },
  { k: 'yesterday',  l: 'Yesterday' },
  { k: 'thisWeek',   l: 'This week (Sun–Today)' },
  { k: 'last7',      l: 'Last 7 days' },
  { k: 'lastWeek',   l: 'Last week (Sun–Sat)' },
  { k: 'last14',     l: 'Last 14 days' },
  { k: 'thisMonth',  l: 'This month' },
  { k: 'last30',     l: 'Last 30 days' },
  { k: 'lastMonth',  l: 'Last month' },
  { k: 'allTime',    l: 'All time' },
];

// Single calendar month grid
function DrpMonth({ monthDate, start, end, hover, onPick, onHover }) {
  const first = new Date(monthDate.getFullYear(), monthDate.getMonth(), 1);
  const startPad = first.getDay(); // 0 = Sunday
  const daysInMonth = new Date(monthDate.getFullYear(), monthDate.getMonth() + 1, 0).getDate();
  const cells = [];
  for (let i = 0; i < startPad; i++) cells.push(null);
  for (let d = 1; d <= daysInMonth; d++) cells.push(new Date(monthDate.getFullYear(), monthDate.getMonth(), d));
  while (cells.length % 7 !== 0) cells.push(null);

  const rangeEnd = end || hover;
  const inRange = (d) => start && rangeEnd && d >= drpStartOfDay(start) && d <= drpStartOfDay(rangeEnd) && !drpSameDay(d, start) && !drpSameDay(d, rangeEnd);

  return (
    <div style={{ flex: 1, minWidth: 0 }}>
      <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--ink)', textAlign: 'center', marginBottom: 10, letterSpacing: '-0.005em' }}>
        {drpFmtMonth(monthDate)}
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: 2, fontSize: 10.5, color: 'var(--ink-4)', marginBottom: 4, fontWeight: 600, letterSpacing: '0.04em' }}>
        {['S','M','T','W','T','F','S'].map((d, i) => <div key={i} style={{ textAlign: 'center', padding: '4px 0' }}>{d}</div>)}
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: 2 }}>
        {cells.map((d, i) => {
          if (!d) return <div key={i}/>;
          const isStart = drpSameDay(d, start);
          const isEnd = end && drpSameDay(d, end);
          const isHoverEnd = !end && hover && drpSameDay(d, hover);
          const between = inRange(d);
          const endpoint = isStart || isEnd || isHoverEnd;
          return (
            <div key={i}
                 onClick={() => onPick(d)}
                 onMouseEnter={() => onHover(d)}
                 style={{
                   position: 'relative',
                   height: 30,
                   display: 'flex', alignItems: 'center', justifyContent: 'center',
                   fontSize: 12,
                   fontVariantNumeric: 'tabular-nums',
                   color: endpoint ? 'var(--paper)' : between ? 'var(--ink)' : 'var(--ink-2)',
                   background: endpoint ? 'var(--terra)' : between ? 'var(--terra-soft)' : 'transparent',
                   borderRadius: endpoint ? 6 : between ? 0 : 6,
                   fontWeight: endpoint ? 600 : 500,
                   cursor: 'pointer',
                   transition: 'background .12s',
                 }}
                 onMouseOver={e => { if (!endpoint && !between) e.currentTarget.style.background = 'var(--cream-2)'; }}
                 onMouseOut={e => { if (!endpoint && !between) e.currentTarget.style.background = 'transparent'; }}>
              {d.getDate()}
            </div>
          );
        })}
      </div>
    </div>
  );
}

function DateRangePicker({ value, onChange, align = 'left' }) {
  const today = React.useMemo(() => drpStartOfDay(new Date(2026, 3, 27)), []); // April 27, 2026, matches mock data
  const safeValue = value || { preset: 'last30', ...drpResolvePreset('last30', today), compare: false };

  const [open, setOpen] = React.useState(false);
  const [draft, setDraft] = React.useState(safeValue);
  const [hover, setHover] = React.useState(null);
  const [pickingEnd, setPickingEnd] = React.useState(false); // true = next click sets end
  const [viewMonth, setViewMonth] = React.useState(new Date(today.getFullYear(), today.getMonth() - 1, 1));
  const [upToToday, setUpToToday] = React.useState('');
  const [upToYesterday, setUpToYesterday] = React.useState('');
  const ref = React.useRef(null);

  // Reset draft when opening
  React.useEffect(() => {
    if (open) {
      setDraft(safeValue);
      setPickingEnd(false);
      setHover(null);
      setUpToToday(''); setUpToYesterday('');
      setViewMonth(new Date(today.getFullYear(), today.getMonth() - 1, 1));
    }
  }, [open]);

  // Click outside
  React.useEffect(() => {
    if (!open) return;
    const h = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', h);
    return () => document.removeEventListener('mousedown', h);
  }, [open]);

  const triggerLabel = () => {
    if (safeValue.preset && safeValue.preset !== 'custom') {
      const p = DRP_PRESETS.find(x => x.k === safeValue.preset);
      if (p) return p.l;
    }
    return `${drpFmtShort(safeValue.start)} – ${drpFmtShort(safeValue.end)}`;
  };

  const pickPreset = (k) => {
    if (k === 'custom') { setDraft(d => ({ ...d, preset: 'custom' })); return; }
    const r = drpResolvePreset(k, today);
    setDraft({ preset: k, start: r.start, end: r.end, compare: draft.compare });
    setPickingEnd(false);
    setUpToToday(''); setUpToYesterday('');
  };

  const pickDay = (d) => {
    if (!pickingEnd) {
      setDraft(x => ({ ...x, preset: 'custom', start: d, end: null }));
      setPickingEnd(true);
    } else {
      // If user clicked before start, swap
      if (d < draft.start) {
        setDraft(x => ({ ...x, preset: 'custom', start: d, end: x.start }));
      } else {
        setDraft(x => ({ ...x, preset: 'custom', end: d }));
      }
      setPickingEnd(false);
    }
  };

  const onUpToToday = (v) => {
    setUpToToday(v); setUpToYesterday('');
    const n = parseInt(v, 10);
    if (!isNaN(n) && n > 0) {
      setDraft({ preset: 'custom', start: drpAddDays(today, -(n - 1)), end: today, compare: draft.compare });
    }
  };
  const onUpToYesterday = (v) => {
    setUpToYesterday(v); setUpToToday('');
    const n = parseInt(v, 10);
    if (!isNaN(n) && n > 0) {
      const y = drpAddDays(today, -1);
      setDraft({ preset: 'custom', start: drpAddDays(y, -(n - 1)), end: y, compare: draft.compare });
    }
  };

  const setStartInput = (s) => {
    const d = new Date(s); if (isNaN(d)) return;
    setDraft(x => ({ ...x, preset: 'custom', start: drpStartOfDay(d) }));
  };
  const setEndInput = (s) => {
    const d = new Date(s); if (isNaN(d)) return;
    setDraft(x => ({ ...x, preset: 'custom', end: drpStartOfDay(d) }));
  };

  const apply = () => {
    if (!draft.end) { window.toast && window.toast('Pick an end date', 'Click a second day to finish the range.', 'info'); return; }
    onChange && onChange(draft);
    setOpen(false);
  };

  const toggleCompare = () => {
    const next = !draft.compare;
    setDraft(x => ({ ...x, compare: next }));
    if (next) window.toast && window.toast('Compare mode on', 'Comparing to previous period of equal length.', 'info');
  };

  const dateInputVal = (d) => d ? `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}` : '';

  return (
    <div ref={ref} style={{ position: 'relative', display: 'inline-block' }}>
      <button onClick={() => setOpen(o => !o)}
              style={{ display: 'inline-flex', alignItems: 'center', gap: 8, padding: '7px 12px',
                       background: 'var(--paper)', color: 'var(--ink-2)',
                       border: '1px solid var(--line)', borderRadius: 8, fontSize: 12.5, fontWeight: 500,
                       cursor: 'pointer', fontFamily: 'inherit' }}>
        <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" style={{ color: 'var(--ink-4)' }}>
          <rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/>
        </svg>
        <span>{triggerLabel()}</span>
        {safeValue.compare && <span style={{ fontSize: 10, fontWeight: 600, padding: '1px 6px', background: 'var(--terra-soft)', color: 'var(--brand-navy)', borderRadius: 999 }}>vs prev</span>}
        <span style={{ fontSize: 9, opacity: .55 }}>▾</span>
      </button>

      {open && (
        <div style={{
          position: 'absolute', top: 'calc(100% + 6px)',
          [align === 'right' ? 'right' : 'left']: 0,
          background: 'var(--paper)', border: '1px solid var(--cream-3)',
          borderRadius: 10, boxShadow: 'var(--sh-2)',
          zIndex: 100, display: 'flex', minWidth: 640, overflow: 'hidden',
        }}>
          {/* LEFT, presets sidebar */}
          <div style={{ width: 180, borderRight: '1px solid var(--line)', padding: '8px 0', background: 'var(--cream)', display: 'flex', flexDirection: 'column' }}>
            <div style={{ flex: 1, overflow: 'auto' }}>
              {DRP_PRESETS.map(p => (
                <div key={p.k} onClick={() => pickPreset(p.k)}
                     style={{
                       padding: '7px 14px', fontSize: 12, cursor: 'pointer',
                       background: draft.preset === p.k ? 'var(--terra-soft)' : 'transparent',
                       color: draft.preset === p.k ? 'var(--brand-navy)' : 'var(--ink-2)',
                       fontWeight: draft.preset === p.k ? 600 : 500,
                       borderLeft: draft.preset === p.k ? '2px solid var(--terra)' : '2px solid transparent',
                     }}
                     onMouseEnter={e => { if (draft.preset !== p.k) e.currentTarget.style.background = 'var(--cream-2)'; }}
                     onMouseLeave={e => { if (draft.preset !== p.k) e.currentTarget.style.background = 'transparent'; }}>
                  {p.l}
                </div>
              ))}
            </div>
            <div style={{ padding: '10px 12px', borderTop: '1px solid var(--line)', display: 'flex', flexDirection: 'column', gap: 6 }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                <input type="text" value={upToToday} onChange={e => onUpToToday(e.target.value)} placeholder="N"
                       style={{ width: 36, padding: '4px 6px', border: '1px solid var(--line)', borderRadius: 5, fontSize: 11, fontFamily: 'inherit', textAlign: 'center', background: 'var(--paper)' }}/>
                <span style={{ fontSize: 10.5, color: 'var(--ink-3)' }}>days up to today</span>
              </div>
              <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                <input type="text" value={upToYesterday} onChange={e => onUpToYesterday(e.target.value)} placeholder="N"
                       style={{ width: 36, padding: '4px 6px', border: '1px solid var(--line)', borderRadius: 5, fontSize: 11, fontFamily: 'inherit', textAlign: 'center', background: 'var(--paper)' }}/>
                <span style={{ fontSize: 10.5, color: 'var(--ink-3)' }}>days up to yesterday</span>
              </div>
            </div>
          </div>

          {/* RIGHT, start/end inputs + 2-month calendar */}
          <div style={{ flex: 1, padding: 14, minWidth: 0 }}>
            <div style={{ display: 'flex', gap: 10, marginBottom: 14 }}>
              <div style={{ flex: 1 }}>
                <div style={{ fontSize: 9.5, color: 'var(--ink-4)', fontWeight: 600, letterSpacing: '0.06em', textTransform: 'uppercase', marginBottom: 4 }}>Start date</div>
                <input type="date" value={dateInputVal(draft.start)} onChange={e => setStartInput(e.target.value)}
                       style={{ width: '100%', padding: '6px 8px', border: '1px solid var(--line)', borderRadius: 6, fontSize: 12, fontFamily: 'inherit', background: 'var(--paper)' }}/>
              </div>
              <div style={{ flex: 1 }}>
                <div style={{ fontSize: 9.5, color: 'var(--ink-4)', fontWeight: 600, letterSpacing: '0.06em', textTransform: 'uppercase', marginBottom: 4 }}>End date</div>
                <input type="date" value={dateInputVal(draft.end)} onChange={e => setEndInput(e.target.value)}
                       style={{ width: '100%', padding: '6px 8px', border: '1px solid var(--line)', borderRadius: 6, fontSize: 12, fontFamily: 'inherit', background: 'var(--paper)' }}/>
              </div>
            </div>

            {/* Month nav */}
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
              <button onClick={() => setViewMonth(m => drpAddMonths(m, -1))}
                      style={{ width: 24, height: 24, borderRadius: 6, border: '1px solid var(--line)', background: 'var(--paper)', cursor: 'pointer', color: 'var(--ink-2)', fontSize: 13, lineHeight: 1, fontFamily: 'inherit' }}>‹</button>
              <div style={{ flex: 1 }}/>
              <button onClick={() => setViewMonth(m => drpAddMonths(m, 1))}
                      style={{ width: 24, height: 24, borderRadius: 6, border: '1px solid var(--line)', background: 'var(--paper)', cursor: 'pointer', color: 'var(--ink-2)', fontSize: 13, lineHeight: 1, fontFamily: 'inherit' }}>›</button>
            </div>
            <div style={{ display: 'flex', gap: 16 }} onMouseLeave={() => setHover(null)}>
              <DrpMonth monthDate={viewMonth} start={draft.start} end={draft.end} hover={pickingEnd ? hover : null} onPick={pickDay} onHover={setHover}/>
              <DrpMonth monthDate={drpAddMonths(viewMonth, 1)} start={draft.start} end={draft.end} hover={pickingEnd ? hover : null} onPick={pickDay} onHover={setHover}/>
            </div>

            {/* Footer */}
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginTop: 14, paddingTop: 12, borderTop: '1px solid var(--line)' }}>
              <label style={{ display: 'inline-flex', alignItems: 'center', gap: 8, fontSize: 12, color: 'var(--ink-2)', cursor: 'pointer', userSelect: 'none' }}>
                <span onClick={toggleCompare}
                      style={{ width: 30, height: 17, borderRadius: 999, background: draft.compare ? 'var(--terra)' : 'var(--cream-3)', position: 'relative', transition: 'background .15s', flexShrink: 0 }}>
                  <span style={{ position: 'absolute', top: 2, left: draft.compare ? 15 : 2, width: 13, height: 13, borderRadius: '50%', background: 'var(--paper)', transition: 'left .15s', boxShadow: '0 1px 2px rgba(0,0,0,.15)' }}/>
                </span>
                <span onClick={toggleCompare}>Compare</span>
              </label>
              <div style={{ display: 'flex', gap: 8 }}>
                <button onClick={() => setOpen(false)}
                        style={{ padding: '6px 14px', border: '1px solid var(--line)', borderRadius: 6, background: 'var(--paper)', color: 'var(--ink-2)', fontSize: 12, fontWeight: 500, cursor: 'pointer', fontFamily: 'inherit' }}>Cancel</button>
                <button onClick={apply}
                        style={{ padding: '6px 14px', border: '1px solid var(--terra)', borderRadius: 6, background: 'var(--terra)', color: 'var(--paper)', fontSize: 12, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit' }}>Apply</button>
              </div>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}
window.DateRangePicker = DateRangePicker;
window.DRP_PRESET_LABEL = (k) => (DRP_PRESETS.find(p => p.k === k) || {}).l;
