/* =========================================================
   Tappt — mobile shell: profile (home) · avatar menu ·
   liquid-glass nav · sick analytics · screens
   (EditSheet / ShareSheet / ProModal live in app-edit.jsx)
   ========================================================= */
const {
  CARDS: MC, platById: mplat, BrandIcon: MBrand, AVATAR_COLORS: MAV,
  Store: MStore, genAnalytics: mGen, I: MI,
  DEFAULT_SECTIONS: MSECT, EditSheet: MEdit, ShareSheet: MShare, ProModal: MPro,
  PersonaSheet: MPersona, ConnectionsScreen: MConn,
  getPersonas: mGetPersonas, activePersona: mActive, applyPersona: mApply, syncActivePersona: mSync,
  personaIconKey: mPersonaIcon,
} = window;
const { useState: mUse, useRef: mRef } = React;
const mLayout = React.useLayoutEffect || React.useEffect;

/* NumTicker — counts up to `value` (Magic-UI "Number Ticker", React port).
   Re-animates whenever value changes (range/source toggles). format(n) lets
   callers reuse the screen's own fmt(); else commas. Reduced-motion = instant. */
function NumTicker({ value, format, decimals = 0, prefix = '', suffix = '', dur = 1100, from = 0, className }) {
  const [disp, setDisp] = mUse(from);
  const fromRef = mRef(from);
  const rafRef = mRef(0);
  React.useEffect(() => {
    const from = fromRef.current, to = value;
    if (from === to) return;
    if (window.matchMedia && matchMedia('(prefers-reduced-motion: reduce)').matches) {
      fromRef.current = to; setDisp(to); return;
    }
    const t0 = performance.now();
    const tick = (now) => {
      const p = Math.min(1, (now - t0) / dur);
      const e = 1 - Math.pow(1 - p, 3);
      const v = from + (to - from) * e;
      setDisp(v);
      if (p < 1) rafRef.current = requestAnimationFrame(tick);
      else { fromRef.current = to; setDisp(to); }
    };
    rafRef.current = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(rafRef.current);
  }, [value]);
  const shown = decimals > 0 ? Number(Number(disp).toFixed(decimals)) : Math.round(disp);
  const out = format ? format(shown)
    : prefix + (decimals > 0 ? shown.toFixed(decimals) : shown.toLocaleString()) + suffix;
  return <span className={className}>{out}</span>;
}

/* small avatar (cover image circle, else gradient + initial) */
/* video helper: mute, autoplay, and keep every cover/avatar video of the
   same duration phase-locked to one wall clock so they play in sync app-wide */
const __tvids = (window.__tapptVids = window.__tapptVids || []);
const __vsync = (el) => { if (!el || !el.duration || !isFinite(el.duration)) return; const t = (Date.now() / 1000) % el.duration; if (Math.abs(el.currentTime - t) > 0.25) { try { el.currentTime = t; } catch (e) {} } };
if (!window.__tvidTimer) { window.__tvidTimer = setInterval(() => { for (let i = __tvids.length - 1; i >= 0; i--) { const el = __tvids[i]; if (!el || !el.isConnected) { __tvids.splice(i, 1); continue; } __vsync(el); } }, 1000); }
const playMuted = (el) => {
  if (!el) return;
  el.muted = true; el.defaultMuted = true;
  if (__tvids.indexOf(el) === -1) __tvids.push(el);
  const start = () => { __vsync(el); const p = el.play(); if (p && p.catch) p.catch(() => {}); };
  if (el.readyState >= 1) start(); else el.addEventListener('loadedmetadata', start, { once: true });
};
window.playMuted = playMuted;
/* open a social icon's destination: stored handle appended to the platform base */
function joinSocial(base, handle) {
  const h = (handle || '').trim().replace(/^@+/, '');
  if (h && /^https?:\/\//i.test(h)) return h;
  if (!base) return h;
  const b = base.indexOf('http') === 0 ? base : 'https://' + base;
  if (!h) return b;
  return /[@/]$/.test(b) ? b + h : b + '/' + h;
}
function openSocial(user, id) {
  const p = mplat(id); if (!p) return;
  const h = ((user.socialData || {})[id] || '').trim();
  const url = h ? joinSocial(p.base, h) : (p.base ? (p.base.indexOf('http') === 0 ? p.base : 'https://' + p.base) : '');
  if (url) { try { window.open(url, '_blank'); } catch (e) {} }
}
window.openSocial = openSocial;
window.joinSocial = joinSocial;
function Avatar({ user, size = 40, coverFallback = false, fill = false }) {
  const initial = (user.name || user.handle || 'T').trim().charAt(0).toUpperCase();
  const dim = fill ? { width: '100%', height: '100%' } : { width: size, height: size };
  const av = (typeof user.avatar === 'string' && user.avatar.indexOf('idb:') === 0) ? null : user.avatar;
  if (av) return <img src={av} alt="" style={{ ...dim, borderRadius: '50%', objectFit: 'cover' }} />;
  // fall back to the cover photo as a profile pic when there's no avatar
  if (coverFallback) {
    const cov = (user.covers && user.covers[0]) || user.cover;
    const covOk = typeof cov === 'string' && cov.indexOf('idb:') !== 0;
    if (covOk && user.coverKind !== 'video') return <img src={cov} alt="" style={{ ...dim, borderRadius: '50%', objectFit: 'cover' }} />;
    if (covOk && user.coverKind === 'video') return <video ref={playMuted} src={cov} muted autoPlay loop playsInline style={{ ...dim, borderRadius: '50%', objectFit: 'cover' }} />;
  }
  return <div style={{ ...dim, borderRadius: '50%', background: user.avatarColor || MAV[0], display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff', fontFamily: 'var(--display)', fontWeight: 800, fontSize: size * 0.4 }}>{initial}</div>;
}

/* ===========================================================
   LIQUID GLASS BOTTOM NAV
   =========================================================== */
/* nav item catalogue + per-persona config resolver */
const NAV_POOL = {
  moments:     { icon: MI.spark, label: 'Moments' },
  connections: { icon: MI.users, label: 'Connections' },
  analytics:   { icon: MI.chart, label: 'Analytics' },
  sales:       { icon: MI.cart, label: 'Sales' },
  earnings:    { icon: MI.dollar, label: 'Earnings' },
  search:      { icon: MI.search, label: 'Discover' },
  profile:     { avatar: true, label: 'Profile' },
};
const RADIAL_POOL = {
  post:        { label: 'Post', icon: MI.plus },
  moment:      { label: 'Moment', icon: MI.spark },
  link:        { label: 'Link', icon: MI.link },
  product:     { label: 'Product', icon: MI.card },
  sales:       { label: 'Sales', icon: MI.cart },
  earnings:    { label: 'Earnings', icon: MI.dollar },
  qr:          { label: 'Share', icon: MI.qr },
  analytics:   { label: 'Analytics', icon: MI.chart },
  connections: { label: 'Connections', icon: MI.users },
};
const NAV_DEFAULT_BAR = ['moments', 'connections', 'analytics', 'profile'];
const NAV_DEFAULT_RADIAL = ['moment', 'link', 'product', 'sales', 'earnings', 'qr'];
function navCfg(user) {
  const pid = (window.activePersona ? (window.activePersona(user) || {}).id : null) || 'default';
  const byP = (user.navByPersona || {})[pid] || {};
  const bar = (user.pro && (byP.bar || user.navBar)) || NAV_DEFAULT_BAR;
  const radial = (user.pro && (byP.radial || user.radialActions)) || NAV_DEFAULT_RADIAL;
  return { pid, bar: bar.slice(0, 4), radial };
}
window.navCfg = navCfg;
window.NAV_POOL = NAV_POOL; window.RADIAL_POOL = RADIAL_POOL;
window.NAV_DEFAULT_BAR = NAV_DEFAULT_BAR; window.NAV_DEFAULT_RADIAL = NAV_DEFAULT_RADIAL;

function GlassNav({ tab, setTab, user, onCreate }) {
  const innerRef = mRef(null);
  const itemRefs = mRef({});
  const [blob, setBlob] = mUse({ left: 0, width: 50 });
  const bar = navCfg(user).bar;
  const left = bar.slice(0, 2), right = bar.slice(2);
  const mk = (id) => { const it = NAV_POOL[id] || NAV_POOL.moments; return { id, icon: it.icon, avatar: it.avatar }; };
  const items = [...left.map(mk), { id: 'create', create: true }, ...right.map(mk)];
  mLayout(() => {
    const el = itemRefs.current[tab], inner = innerRef.current;
    if (el && inner) {
      const ir = inner.getBoundingClientRect(), er = el.getBoundingClientRect();
      setBlob({ left: er.left - ir.left, width: er.width });
    }
  }, [tab]);
  return (
    <div className="glass-nav">
      <div className="glass-nav-inner" ref={innerRef}>
        <div className="glass-blob" style={{ left: blob.left + 4, width: blob.width - 8 }} />
        {items.map((it) => {
          const on = tab === it.id;
          if (it.create) return (
            <button key={it.id} ref={(r) => (itemRefs.current[it.id] = r)} className="glass-item create" onClick={onCreate}>{MI.plus({ width: 24, height: 24 })}</button>
          );
          return (
            <button key={it.id} ref={(r) => (itemRefs.current[it.id] = r)} className={'glass-item' + (on ? ' on' : '')} onClick={() => setTab(it.id)}>
              {it.avatar ? <span className="nav-av"><Avatar user={user} size={26} coverFallback fill /></span> : it.icon({ width: 23, height: 23 })}
            </button>
          );
        })}
      </div>
    </div>
  );
}

/* ===========================================================
   RADIAL CREATE MENU  (center button → fan of actions)
   =========================================================== */
const RadialIcons = {
  moment: MI.spark({ width: 25, height: 25 }),
  link: MI.link({ width: 25, height: 25 }),
  product: MI.card({ width: 25, height: 25 }),
  post: MI.plus({ width: 25, height: 25 }),
  qr: MI.qr({ width: 25, height: 25 }),
  sales: MI.cart({ width: 25, height: 25 }),
  earnings: MI.dollar({ width: 25, height: 25 }),
};
function RadialMenu({ onClose, onPick, user }) {
  const [open, setOpen] = mUse(false);
  mLayout(() => { const t = setTimeout(() => setOpen(true), 10); return () => clearTimeout(t); }, []);
  const ids = (user && window.navCfg ? window.navCfg(user).radial : NAV_DEFAULT_RADIAL);
  const actions = ids.map((id) => { const it = RADIAL_POOL[id] || RADIAL_POOL.post; return { id, label: it.label, icon: it.icon({ width: 25, height: 25 }) }; });
  // fan across the top: spread from -160° to -20° (left to right above the button)
  const N = actions.length;
  const A0 = -170, A1 = -10, R = 150;
  const close = () => { setOpen(false); setTimeout(onClose, 220); };
  return (
    <div className="radial-scrim" onClick={close}>
      <div className="radial-anchor">
        {actions.map((a, i) => {
          const ang = (A0 + (A1 - A0) * (i / (N - 1))) * Math.PI / 180;
          const x = Math.cos(ang) * R, y = Math.sin(ang) * R;
          return (
            <button key={a.id} className="radial-item"
              style={{ transform: open ? `translate(${x}px, ${y}px) scale(1)` : 'translate(0,0) scale(0.3)', opacity: open ? 1 : 0, transitionDelay: (open ? i * 32 : (N - i) * 18) + 'ms' }}
              onClick={(e) => { e.stopPropagation(); setOpen(false); setTimeout(() => onPick(a.id), 160); }}>
              <span className="ri-ic">{a.icon}</span>
              <span className="ri-lb">{a.label}</span>
            </button>
          );
        })}
        <button className={'radial-fab' + (open ? ' open' : '')} onClick={(e) => { e.stopPropagation(); close(); }}>{MI.plus({ width: 26, height: 26 })}</button>
      </div>
    </div>
  );
}

/* ===========================================================
   PROFILE AVATAR MENU  (theme · pro · actions)
   =========================================================== */
const MenuIcons = {
  bolt: <svg viewBox="0 0 24 24" fill="currentColor"><path d="M13 2 3 14h7l-1 8 10-12h-7z"/></svg>,
  palette: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="13.5" cy="6.5" r="1.2" fill="currentColor"/><circle cx="17.5" cy="10.5" r="1.2" fill="currentColor"/><circle cx="8.5" cy="7.5" r="1.2" fill="currentColor"/><circle cx="6.5" cy="12.5" r="1.2" fill="currentColor"/><path d="M12 2a10 10 0 1 0 0 20c1.1 0 2-.9 2-2 0-.5-.2-.9-.5-1.3-.3-.3-.5-.8-.5-1.2 0-1.1.9-2 2-2h2.5A4.5 4.5 0 0 0 22 11c0-5-4.5-9-10-9z"/></svg>,
  globe: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3c2.5 2.6 2.5 15.4 0 18M12 3c-2.5 2.6-2.5 15.4 0 18"/></svg>,
  link: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><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"/></svg>,
  qr: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinejoin="round"><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"/><path d="M14 14h3v3M21 21v.01M21 14v.01M14 21v.01"/></svg>,
  share: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M4 12v7a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-7"/><path d="M12 3v13M8 7l4-4 4 4"/></svg>,
  chart: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M5 21V10M12 21V4M19 21v-7"/></svg>,
  agency: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="9" cy="8" r="3.2"/><path d="M3 20a6 6 0 0 1 12 0M17 5.5a3 3 0 0 1 0 5.5M21 20a6 6 0 0 0-4-5.6"/></svg>,
  settings: MI.settings({ width: 20, height: 20 }),
  logout: MI.logout({ width: 20, height: 20 }),
  dollar: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="9"/><path d="M15 9a3 3 0 0 0-3-2c-1.7 0-3 1-3 2.3 0 3 6 1.5 6 4.4 0 1.4-1.3 2.3-3 2.3a3 3 0 0 1-3-2M12 5.5v1.5M12 17v1.5"/></svg>,
  cart: MI.cart({ width: 20, height: 20 }),
  pin: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 21s-6-5.3-6-10a6 6 0 0 1 12 0c0 4.7-6 10-6 10z"/><circle cx="12" cy="11" r="2.2"/></svg>,
  persona: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="9" cy="9" r="3.2"/><path d="M3.5 20a5.5 5.5 0 0 1 11 0"/><path d="M15 7.5a3 3 0 0 1 0 5.4M20.5 20a5.5 5.5 0 0 0-4-5.3"/></svg>,
  card: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinejoin="round"><rect x="2.5" y="5" width="19" height="14" rx="3"/><path d="M2.5 9.5h19" /></svg>,
  sun: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><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"/></svg>,
  moon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><path d="M21 12.8A9 9 0 1 1 11.2 3 7 7 0 0 0 21 12.8z"/></svg>,
  auto: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="12" cy="12" r="9"/><path d="M12 3v18" /><path d="M12 3a9 9 0 0 1 0 18z" fill="currentColor" stroke="none"/></svg>,
};

function ProfileMenu({ user, themeMode, setThemeMode, onClose, onShare, onAnalytics, onDesign, onEarnings, onSales, onConnections, onPersona, onCards, onCEO, onPro, onRate, onLogout, onNav, toast }) {
  const url = `tappt.io/${user.handle || 'you'}`;
  const item = (icon, label, onClick, opts = {}) => (
    <button className={'menu-item' + (opts.danger ? ' danger' : '')} onClick={onClick}>
      <span className="mi">{icon}</span>{label}
      {opts.right && <span className="right">{opts.right}</span>}
    </button>
  );
  return (
    <React.Fragment>
      <div className="menu-scrim" onClick={onClose} />
      <div className="pf-menu">
        <div className="pf-menu-head">
          <span className="av" style={{ background: user.avatarColor || MAV[0] }}><Avatar user={user} size={48} /></span>
          <div><div className="nm">{user.name || 'Your Name'}</div><div className="hd">@{user.handle || 'handle'}</div></div>
        </div>

        <div className="seg">
          {[['light', MenuIcons.sun, 'Light'], ['dark', MenuIcons.moon, 'Dark'], ['auto', MenuIcons.auto, 'Auto']].map(([m, ic, lb]) => (
            <button key={m} className={themeMode === m ? 'on' : ''} onClick={() => setThemeMode(m)}>{ic}{lb}</button>
          ))}
        </div>

        {!user.pro && (
          <button className="menu-pro" onClick={onPro}>
            <span className="ic">{MI.crown({ width: 16, height: 16 })}</span>
            <div><div className="t">Get Tappt Pro</div><div className="s">Unlock everything</div></div>
          </button>
        )}
        {user.pro && (
          <button className="menu-pro" onClick={onPro}>
            <span className="ic">{MI.crown({ width: 16, height: 16 })}</span>
            <div style={{ flex: 1 }}><div className="t">Tappt Pro active</div><div className="s">Manage subscription</div></div>
            <span style={{ fontFamily: 'var(--mono)', fontSize: 10, color: 'var(--ok)', fontWeight: 700 }}>✓ ON</span>
          </button>
        )}

        <div className="menu-sep" />
        {item(MenuIcons.globe, 'Language', () => toast('English (only option in demo)'), { right: <React.Fragment>English <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4"><path d="M6 9l6 6 6-6"/></svg></React.Fragment> })}
        {item(MenuIcons.link, 'Copy Profile URL', () => { navigator.clipboard && navigator.clipboard.writeText('https://' + url).catch(() => {}); toast('Link copied'); onClose(); })}
        {item(MenuIcons.share, 'Share Profile', () => { onShare('share'); })}
        <div className="menu-sep" />
        {window.isCEO && window.isCEO(user) && item(MenuIcons.bolt || MenuIcons.chart, 'Tappt CEO', () => { onClose(); onCEO && onCEO(); }, { right: <span className="menu-pro-tag" style={{ color: 'var(--accent)', background: 'rgba(232,255,89,0.16)' }}>OWNER</span> })}
        {item(MenuIcons.chart, 'Analytics', () => { onAnalytics(); onClose(); })}
        {item(MenuIcons.card || MenuIcons.chart, 'My Cards', () => { onClose(); onCards && onCards(); })}
        {item(MenuIcons.persona, 'Personas', () => { onClose(); onPersona(); })}
        {item(MenuIcons.palette, 'Customise nav', () => { if (!user.pro) { onPro(); return; } onClose(); onNav && onNav(); }, { right: user.pro ? null : <span className="pro-badge">PRO</span> })}
        {item(MenuIcons.agency, 'Agency', () => toast('Agency seats — coming soon'))}
        {item(MI.star ? MI.star({ width: 16, height: 16 }) : MenuIcons.settings, 'Rate Tappt', () => { onRate && onRate(); })}
        {item(MenuIcons.settings, 'Settings', () => toast('Settings — coming soon'))}
        <div className="menu-sep" />
        {item(MenuIcons.logout, 'Logout', onLogout, { danger: true })}
      </div>
    </React.Fragment>
  );
}

/* ===========================================================
   QR SHEET
   =========================================================== */
function QRSheet({ user, onClose, toast }) {
  const url = `tappt.io/${user.handle || 'you'}`;
  const qr = `https://api.qrserver.com/v1/create-qr-code/?size=320x320&margin=0&data=${encodeURIComponent('https://' + url)}`;
  return (
    <div className="sheet-bg" onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}>
      <div className="sheet">
        <div className="sheet-grab" />
        <div className="sheet-head"><span style={{ width: 36 }} /><h3 style={{ fontSize: 16 }}>Scan to connect</h3><button className="icon-btn" onClick={onClose} style={{ width: 36, height: 36 }}>✕</button></div>
        <div className="sheet-scroll" style={{ textAlign: 'center' }}>
          <div style={{ width: 244, margin: '8px auto 18px', padding: 18, borderRadius: 24, background: '#fff', boxShadow: '0 20px 50px -16px rgba(0,0,0,0.5)' }}>
            <img src={qr} alt="QR" style={{ width: '100%', display: 'block', borderRadius: 8 }} />
            <div style={{ marginTop: 12, fontFamily: 'var(--mono)', fontSize: 12, color: '#0B0B0C', fontWeight: 600 }}>{url}</div>
          </div>
          <p style={{ color: 'var(--fg-2)', fontSize: 14, maxWidth: '30ch', margin: '0 auto 20px' }}>Point any camera at this code to open your Tappt profile instantly.</p>
          <button className="btn btn-primary btn-block" onClick={() => toast('Saved to photos')}>{MI.arrow({ width: 17, height: 17 })} Save QR image</button>
        </div>
      </div>
    </div>
  );
}

/* ===========================================================
   OWN PROFILE  (home after login)
   =========================================================== */
function renderLink(l) {
  const p = mplat(l.type);
  const style = l.style || 'button';
  const isVid = l.mediaKind === 'video';
  const Media = () => isVid
    ? <video ref={window.playMuted} src={l.image} muted autoPlay loop playsInline />
    : <img src={l.image} alt="" />;
  if (style === 'big') return (
    <div className={'pf-feature' + (l.image ? ' has-media' : '')} key={l.id} style={!l.image ? { background: p.bg } : null}>
      {l.image && (
        <div className="pf-feat-media">
          <Media />
        </div>
      )}
      <div className="pf-feat-foot">
        {!l.image && <span className="pf-feat-ic"><MBrand id={l.type} size={24} /></span>}
        <span className="ttl">{l.label || p.label}</span>
      </div>
    </div>
  );
  if (style === 'grid') return (
    <div className={'pf-grid-card' + (l.image ? ' has-media' : '')} key={l.id} style={{ background: l.image ? '#141c28' : p.bg }}>
      {l.image && <Media />}
      <div className="ttl">{l.label || p.label}</div>
    </div>
  );
  if (style === 'medium') return (
    <div className={'pf-feature' + (l.image ? ' has-media' : '')} key={l.id} style={!l.image ? { background: p.bg } : null}>
      {l.image && <div className="pf-feat-media" style={{ aspectRatio: '3.2/1' }}><Media /></div>}
      <div className="pf-feat-foot">
        {!l.image && <span className="pf-feat-ic"><MBrand id={l.type} size={22} /></span>}
        <span className="ttl" style={{ fontSize: 15 }}>{l.label || p.label}</span>
      </div>
    </div>
  );
  if (style === 'small') return (
    <div className="pf-link" key={l.id} style={{ padding: '10px 12px' }}>
      <MBrand id={l.type} size={26} radius={7} /><span style={{ fontSize: 14 }}>{l.label || p.label}</span><span className="la">{MI.ext({ width: 15, height: 15 })}</span>
    </div>
  );
  return (
    <div className="pf-link" key={l.id}>{l.image ? <img className="pf-link-thumb" src={l.image} alt="" /> : <MBrand id={l.type} size={30} radius={8} />}{l.label || p.label}<span className="la">{MI.ext({ width: 16, height: 16 })}</span></div>
  );
}

/* render a list of featured links, grouping consecutive 'grid' items into 2-col rows.
   wrap(node, link) lets the editor add a remove (×) overlay. */
function renderLinks(links, wrap) {
  const id = (x) => x;
  const w = wrap || id;
  const out = [];
  let grid = [];
  const flush = () => {
    if (grid.length === 1) {
      // a lone grid card looks weird half-width — render it full-width
      const l = grid[0];
      out.push(w(renderLink({ ...l, style: 'big' }), l));
      grid = [];
    } else if (grid.length) {
      out.push(<div className="pf-grid" key={'g' + grid[0].id}>{grid.map((l) => w(renderLink(l), l))}</div>);
      grid = [];
    }
  };
  links.forEach((l) => {
    if ((l.style || 'button') === 'grid') { grid.push(l); }
    else { flush(); out.push(w(renderLink(l), l)); }
  });
  flush();
  return out;
}

/* ---- Moments / Gallery / Merch section renderers (shared live + edit) ---- */
/* relative time for moments */
function momentWhen(ts) {
  if (!ts) return 'now';
  const s = Math.floor((Date.now() - ts) / 1000);
  if (s < 60) return 'now';
  if (s < 3600) return Math.floor(s / 60) + 'm';
  if (s < 86400) return Math.floor(s / 3600) + 'h';
  if (s < 604800) return Math.floor(s / 86400) + 'd';
  return Math.floor(s / 604800) + 'w';
}

function MomentsRow({ user, pub, viewer, onEditMoment, onDeleteMoment, onSetComments, onMoments, onPost, toast }) {
  const moments = user.moments || [];
  const MomentCard = window.MomentCard;
  const composer = (!pub && window.MomentComposer && onPost) ? <window.MomentComposer user={user} onPost={onPost} toast={toast} /> : null;
  if (moments.length === 0) {
    if (!pub) {
      return <div className="moments-rowwrap">{composer}</div>;
    }
    return (
      <div className="sec-empty">
        <span className="se-ic">{MI.spark({ width: 26, height: 26 })}</span>
        <div className="se-t">No Moments yet</div>
        <div className="se-s">{(user.name ? user.name.split(' ')[0] : 'This creator') + " hasn't posted any Moments yet — check back soon."}</div>
      </div>
    );
  }
  // owner can edit/delete only in the dashboard (not pub). Comments persist when handlers given.
  const owner = !pub;
  const v = pub ? (viewer || null) : (viewer || user);
  return (
    <div className="moments-posts">
      {composer}
      {moments.map((m) => (
        <MomentCard key={m.id} user={user} moment={m} owner={owner} viewer={v}
          onEdit={owner && onEditMoment ? (patch) => onEditMoment(m.id, patch) : undefined}
          onDelete={owner && onDeleteMoment ? () => onDeleteMoment(m.id) : undefined}
          onComments={onSetComments ? (arr) => onSetComments(m.id, arr) : undefined}
          toast={toast} />
      ))}
    </div>
  );
}
function GalleryGrid({ user, size }) {
  const g = user.gallery || [];
  const sz = size || user.gallerySize || 'medium';
  if (g.length === 0) {
    return (
      <div className={'gallery-rail gs-' + sz}>
        {[0, 1, 2].map((i) => <div className="gallery-ph" key={i}>{MI.image ? MI.image({ width: 22, height: 22 }) : ''}</div>)}
      </div>
    );
  }
  return <div className={'gallery-rail gs-' + sz}>{g.map((m, i) => { const src = (m && m.src) || m; const ok = typeof src === 'string' && src.indexOf('idb:') !== 0; return (
    <div className="gallery-cell" key={i}>{ok && (m.kind === 'video' ? <video src={src} muted autoPlay loop playsInline /> : <img src={src} alt="" />)}</div>
  ); })}</div>;
}
const SAMPLE_PRODUCTS = [
  { name: 'Signature Hoodie', price: '£48', grad: 'linear-gradient(150deg,#3a3a42,#16161a)', tag: 'Apparel' },
  { name: 'Tappt Cap', price: '£22', grad: 'linear-gradient(150deg,#5b3dff,#2a1a6a)', tag: 'Headwear' },
  { name: '1:1 Mentor Call', price: '£120', grad: 'linear-gradient(150deg,#1f8a5b,#0e3a26)', tag: 'Booking' },
  { name: 'Preset Pack', price: '£15', grad: 'linear-gradient(150deg,#d4761f,#6a3a0e)', tag: 'Digital' },
];
/* ---- merch: layout (carousel/stack/grid) × card shape (card/wide/row) ---- */
function getMerch(user) {
  if (user.merchLayout) return { layout: user.merchLayout, shape: user.merchCard || 'card' };
  // migrate from old single merchStyle
  const s = user.merchStyle || 'card';
  if (s === 'grid') return { layout: 'grid', shape: 'card' };
  if (s === 'stack') return { layout: 'stack', shape: 'wide' };
  if (s === 'list') return { layout: 'stack', shape: 'row' };
  if (s === 'wide') return { layout: 'carousel', shape: 'wide' };
  if (s === 'row') return { layout: 'carousel', shape: 'row' };
  return { layout: 'carousel', shape: 'card' };
}
function ProductCard({ p, shape }) {
  const pimg = (typeof p.img === 'string' && p.img.indexOf('idb:') === 0) ? null : p.img;
  const media = pimg ? (p.imgKind === 'video'
    ? <video src={pimg} muted autoPlay loop playsInline />
    : <img src={pimg} alt="" />) : null;
  const bg = p.img ? '#141c28' : (p.grad || MAV[0]);
  const sh = shape || 'card';

  // POSTER — tall 3:4 portrait, full-bleed image, name/price overlaid at bottom
  if (sh === 'poster') {
    return (
      <div className="merch-card shape-poster">
        <div className="mc-media full poster" style={{ background: bg }}>
          {media}
          {p.badge && <span className="mc-badge">{p.badge}</span>}
          <div className="mc-poster-foot">
            <div className="mc-name">{p.name}</div>
            <div className="mc-poster-row"><span className="mc-price">{p.price}</span><button className="mc-add">Add</button></div>
          </div>
        </div>
      </div>
    );
  }
  // WIDE — landscape hero, overlaid name/price + Add
  if (sh === 'wide') {
    return (
      <div className="merch-card shape-wide">
        <div className="mc-media full" style={{ background: bg }}>
          {media}
          {p.badge && <span className="mc-badge">{p.badge}</span>}
          <div className="mc-wide-foot">
            <div className="mc-wide-text"><div className="mc-name">{p.name}</div><div className="mc-price">{p.price}</div></div>
            <button className="mc-add">Add</button>
          </div>
        </div>
      </div>
    );
  }
  // ROW — image left, info right
  if (sh === 'row') {
    return (
      <div className="merch-card shape-row">
        <div className="mc-row-media" style={{ background: bg }}>
          {media}
          {p.badge && <span className="mc-badge sm">{p.badge}</span>}
        </div>
        <div className="mc-row-info">
          <div><div className="mc-name">{p.name}</div>{p.desc && <div className="mc-desc">{p.desc}</div>}<div className="mc-price">{p.price}</div></div>
          <button className="mc-buy">Buy now</button>
        </div>
      </div>
    );
  }
  // CARD — vertical (square media on top, info below)
  return (
    <div className="merch-card shape-card">
      <div className="mc-media" style={{ background: bg }}>
        {media}
        {p.badge ? <span className="mc-badge sm">{p.badge}</span> : <span className="mc-tag">{p.tag || 'Shop'}</span>}
      </div>
      <div className="mc-info">
        <div className="mc-name">{p.name}</div>
        {p.desc && <div className="mc-desc">{p.desc}</div>}
        <div className="mc-row"><span className="mc-price">{p.price}</span><button className="mc-buy">Buy</button></div>
      </div>
    </div>
  );
}
function MerchRail({ user }) {
  // demo products show ONLY for the demo account; real accounts show their own (or none)
  const products = (user.products && user.products.length) ? user.products : (user.handle === 'maxxharland' ? SAMPLE_PRODUCTS : []);
  const { layout, shape } = getMerch(user);
  const cardShape = layout === 'grid' ? 'card' : shape; // grid only supports the card shape
  const swipeable = layout === 'carousel';
  const rotate = user.merchRotate && swipeable && products.length > 1;
  const ref = React.useRef(null);
  React.useEffect(() => {
    if (!rotate) return;
    const el = ref.current; if (!el) return;
    let timer, raf, paused = false, resumeT;
    const pauseNow = (ms) => { paused = true; clearTimeout(resumeT); resumeT = setTimeout(() => { paused = false; }, ms); };
    const onDown = () => { paused = true; clearTimeout(resumeT); };
    const onUp = () => { pauseNow(4000); };
    /* manual browse (wheel/drag/touch scroll) pauses auto-rotate, resumes ~3.5s after you stop */
    const onManual = () => pauseNow(3500);
    el.addEventListener('pointerdown', onDown); el.addEventListener('pointerup', onUp);
    el.addEventListener('wheel', onManual, { passive: true });
    el.addEventListener('touchmove', onManual, { passive: true });
    el.addEventListener('scroll', () => { if (!tweening) onManual(); }, { passive: true });
    let tweening = false;
    /* manual tween — native scrollTo({behavior:'smooth'}) is a no-op in some
       embedded webviews, so drive scrollLeft directly each frame. */
    const tweenTo = (target) => {
      cancelAnimationFrame(raf);
      tweening = true;
      const start = el.scrollLeft, dist = target - start, t0 = performance.now(), dur = 520;
      const tick = (now) => {
        const k = Math.min(1, (now - t0) / dur);
        const e = 1 - Math.pow(1 - k, 3); // easeOutCubic
        el.scrollLeft = start + dist * e;
        if (k < 1) raf = requestAnimationFrame(tick); else tweening = false;
      };
      raf = requestAnimationFrame(tick);
    };
    const advance = () => {
      if (paused) return;
      const card = el.querySelector('.merch-card');
      const step = card ? card.getBoundingClientRect().width + 14 : el.clientWidth * 0.7;
      const atEnd = el.scrollLeft >= el.scrollWidth - el.clientWidth - 4;
      tweenTo(atEnd ? 0 : el.scrollLeft + step);
    };
    timer = setInterval(advance, 2600);
    return () => { clearInterval(timer); clearTimeout(resumeT); cancelAnimationFrame(raf); el.removeEventListener('pointerdown', onDown); el.removeEventListener('pointerup', onUp); el.removeEventListener('wheel', onManual); el.removeEventListener('touchmove', onManual); };
  }, [rotate, products.length, layout, shape]);

  // nothing to show (real account with no products yet) — render no rail at all
  if (!products.length) return null;

  // SPOTLIGHT — first product is a full-width hero, the rest sit in a 2-col grid
  if (layout === 'spotlight') {
    const [hero, ...rest] = products;
    return (
      <div className="merch-spotlight">
        <div className="merch-rail mr-stack shape-is-wide"><ProductCard p={hero} shape="wide" /></div>
        {rest.length > 0 && (
          <div className="merch-rail mr-grid shape-is-card">
            {rest.map((p, i) => <ProductCard p={p} shape="card" key={i} />)}
          </div>
        )}
      </div>
    );
  }
  return (
    <div className={'merch-rail mr-' + layout + ' shape-is-' + cardShape + (rotate ? ' rotating' : '')} ref={ref}>
      {products.map((p, i) => <ProductCard p={p} shape={cardShape} key={i} />)}
    </div>
  );
}
function renderSection(id, user) {
  if (id === 'moments') return <MomentsRow user={user} />;
  if (id === 'embeds') return <EmbedRail user={user} />;
  if (id === 'gallery') return <GalleryGrid user={user} />;
  if (id === 'merch') return <MerchRail user={user} />;
  return null;
}
window.getMerch = getMerch;

/* build + download a vCard (.vcf) — native contact save on iOS/Android */
function saveContact(user, toast) {
  const url = `https://tappt.io/${user.handle || ''}`;
  const socialUrls = (user.socials || []).map((s) => { const p = mplat(s); return p && p.base ? 'item.URL:' + p.base : null; }).filter(Boolean);
  const lines = [
    'BEGIN:VCARD', 'VERSION:3.0',
    `FN:${user.name || user.handle || 'Tappt Contact'}`,
    `N:${(user.name || '').split(' ').slice(1).join(' ')};${(user.name || '').split(' ')[0]};;;`,
    user.phone ? `TEL;TYPE=CELL:${user.phone}` : null,
    user.email ? `EMAIL;TYPE=INTERNET:${user.email}` : null,
    user.bio ? `NOTE:${user.bio.replace(/\n/g, ' ')}` : null,
    `URL:${url}`,
    'END:VCARD',
  ].filter(Boolean);
  const blob = new Blob([lines.join('\r\n')], { type: 'text/vcard' });
  const a = document.createElement('a');
  a.href = URL.createObjectURL(blob);
  a.download = `${(user.handle || 'tappt')}.vcf`;
  document.body.appendChild(a); a.click(); a.remove();
  setTimeout(() => URL.revokeObjectURL(a.href), 1000);
  toast && toast('Contact card downloaded');
}

function DropCountdown({ date }) {
  const [now, setNow] = mUse(Date.now());
  React.useEffect(() => { const t = setInterval(() => setNow(Date.now()), 1000); return () => clearInterval(t); }, []);
  if (!date) return null;
  const diff = new Date(date).getTime() - now;
  if (isNaN(diff)) return null;
  if (diff <= 0) return <span className="db-time">LIVE NOW</span>;
  const d = Math.floor(diff / 864e5), h = Math.floor(diff % 864e5 / 36e5), m = Math.floor(diff % 36e5 / 6e4), s = Math.floor(diff % 6e4 / 1000);
  return <span className="db-time">{d > 0 ? d + 'd ' : ''}{String(h).padStart(2, '0')}:{String(m).padStart(2, '0')}:{String(s).padStart(2, '0')}</span>;
}

function OwnProfile({ user, onEdit, onDesign, onShare, onMenu, onMoments, onPersona, toast, pub, viewer, onEditMoment, onDeleteMoment, onSetComments, onPostMoment, onLead, onTip }) {  const card = MC.find((c) => c.id === user.activeCard);
  const [exOpen, setExOpen] = mUse(false);
  const [tipOpen, setTipOpen] = mUse(false);
  const exc = window.exConf ? window.exConf(user) : { on: false };
  const sectionsRaw = (user.sections && user.sections.length) ? user.sections : MSECT;
  // inject exchange/tip into the order if enabled but missing (so they're draggable, not stuck at bottom)
  const sections = (() => {
    let s = sectionsRaw.slice();
    if (user.pro && exc.on && !exc.sticky && !s.some((x) => x.id === 'exchange')) s = s.concat([{ id: 'exchange', label: 'Exchange', on: true }]);
    if (user.pro && user.tipsOn && !s.some((x) => x.id === 'tip')) s = s.concat([{ id: 'tip', label: 'Tip jar', on: true }]);
    return s;
  })();
  const secHas = (id) => (sections || []).some((s) => s.id === id);
  const son = (id) => { const s = sections.find((x) => x.id === id); return s ? s.on : false; };
  const links = (user.links || []).filter((l) => l.active !== false);
  // NO demo fallback — a real account with no socials shows none (not Instagram/Snapchat).
  const socials = user.socials || [];
  const followers = user.followers || '';
  const coverGrad = user.avatarColor || MAV[0];
  const anim = (user.pro && user.profileAnim && user.profileAnim !== 'none') ? ' anim-' + user.profileAnim : '';
  const persona = mActive(user);
  const hasPersonas = (user.personas && user.personas.length > 1);
  const isPro = !!user.pro;
  const ringOn = isPro && user.avatarRing;
  const pt = user.profileTheme || 'default';
  // Light/Dark are FREE profile themes; the rest are Pro "ptheme-*" overlays.
  const proThemeOn = isPro && pt !== 'default' && pt !== 'light' && pt !== 'dark';
  const themeCls = proThemeOn ? ' ptheme-' + pt : '';
  // The profile renders in ITS OWN light/dark, set here on the root — so it never flips
  // when the owner toggles their dashboard (menu) light/dark. Light theme → light; Classic,
  // Dark and all Pro themes → dark (the profile's natural look).
  const profileTheme = pt === 'light' ? 'light' : 'dark';
  const headFont = isPro ? (user.headlineFont || 'display') : 'display';
  const nameFontFamily = headFont === 'serif' ? "'Instrument Serif', serif" : headFont === 'mono' ? 'var(--mono)' : headFont === 'round' ? "'Quicksand', system-ui, sans-serif" : 'var(--display)';
  const linkAnimCls = (isPro && user.linkAnim && user.linkAnim !== 'none') ? ' la-on la-' + user.linkAnim : '';
  const iconAnimCls = (isPro && user.iconAnim && user.iconAnim !== 'none') ? ' ia-on ia-' + user.iconAnim : '';
  const btnStyleCls = ' btnst-' + (user.buttonStyle || 'solid');
  const btnRadCls = ' brad-' + (user.buttonRadius || 'md');
  const wpCls = user.wallpaper ? ' wp-' + user.wallpaper : '';
  const fxCls = (user.cardEffect && user.cardEffect !== 'none') ? ' fx-' + user.cardEffect : '';
  const _cov = (typeof user.cover === 'string' && user.cover.indexOf('idb:') === 0) ? null : user.cover;
  const coverCls = _cov ? ' has-cover' : ' no-cover';
  return (
    <div className={'appscroll' + themeCls + linkAnimCls + iconAnimCls + btnStyleCls + btnRadCls + wpCls + fxCls + coverCls} data-theme={profileTheme}>
      <div className="pf-cover">
        <div className={'pf-cover-media' + anim} style={{ background: coverGrad }}>
          {(() => { const cov = (typeof user.cover === 'string' && user.cover.indexOf('idb:') === 0) ? null : user.cover; return cov && (user.coverKind === 'video'
            ? <video ref={playMuted} src={cov} muted autoPlay loop playsInline />
            : <img src={cov} alt="" />); })()}
          <div className="pf-cover-tint" />
        </div>
        <div className="pf-topbar">
          {pub ? null : (
            <React.Fragment>
              <button className="glass-pill tappt-link-pill" onClick={onShare}>{MI.link({ width: 15, height: 15 })} <img className="tlp-logo" src="assets/logo-white.png" alt="Tappt" /> Link</button>
              <div className="pf-topbar-right">
                <button className="glass-pill pill-icon" onClick={onDesign} aria-label="Design">{MenuIcons.palette}</button>
                <button className="glass-pill" onClick={onEdit}>{MI.edit({ width: 14, height: 14 })} Edit</button>
                <button className="pf-avatar-btn" onClick={onMenu} aria-label="Menu"><Avatar user={user} coverFallback fill /></button>
              </div>
            </React.Fragment>
          )}
        </div>
        <div className={'pf-cover-foot' + (user.align === 'left' ? ' align-left' : '')}>
          {!pub && (
            <button className="persona-chip" onClick={onPersona}>
              <span className="pc-emoji">{MI[mPersonaIcon ? mPersonaIcon(persona) : 'star']({ width: 15, height: 15 })}</span>{persona.label}
              <span className="pc-caret"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.6"><path d="M6 9l6 6 6-6"/></svg></span>
            </button>
          )}
          {isPro && user.drop && user.drop.on && user.drop.text && (
            <div className="drop-banner"><span className="db-dot" />{user.drop.text}<DropCountdown date={user.drop.date} /></div>
          )}
          {user.showAvatar && (() => { const av = (typeof user.avatar === 'string' && user.avatar.indexOf('idb:') === 0) ? null : user.avatar; return (
            <div className={'pf-avatar-photo' + (ringOn ? ' ring' : '')}>{av ? <img src={av} alt="" /> : <span className="pf-av-fallback" style={{ background: user.avatarColor || MAV[0] }}>{(user.name || 'T').trim().charAt(0).toUpperCase()}</span>}</div>
          ); })()}
          <div className={'pf-name' + (isPro ? ' has-seal' : '')} style={{ fontFamily: nameFontFamily }}>{user.name || 'Your Name'}{isPro && (
            <svg className="verified-seal" viewBox="0 0 24 24" aria-label="Verified">
              <defs><linearGradient id="vseal" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stopColor="var(--brand)"/><stop offset="1" stopColor="var(--brand-2)"/></linearGradient></defs>
              <path fill="url(#vseal)" d="M23 12l-2.44-2.79.34-3.69-3.61-.82-1.89-3.2L12 2.96 8.6 1.5 6.71 4.69 3.1 5.5l.34 3.7L1 12l2.44 2.79-.34 3.7 3.61.82L8.6 22.5l3.4-1.47 3.4 1.46 1.89-3.19 3.61-.82-.34-3.69z"/>
              <path fill="#0B0B0C" d="M10.09 16.72l-3.8-3.81 1.48-1.48 2.32 2.33 5.85-5.87 1.48 1.48z"/>
            </svg>
          )}</div>
          <div className="pf-handle">@{user.handle || 'handle'}</div>
          {(user.company || user.role) && (
            <div className="pf-company">{[user.role, user.company].filter(Boolean).join(' · ')}</div>
          )}
          {user.socialRotate
            ? <div className="pf-socials rotate"><div className="pf-soc-track">{[].concat(socials, socials, socials, socials, socials, socials).map((s, i) => (<button className="pf-social" key={s + '-' + i} style={{ padding: 0, background: 'none' }} onClick={() => openSocial(user, s)}><MBrand id={s} size={44} /></button>))}</div></div>
            : <div className="pf-socials">{socials.map((s) => (<button className="pf-social" key={s} style={{ padding: 0, background: 'none' }} onClick={() => openSocial(user, s)}><MBrand id={s} size={44} /></button>))}</div>}
          {(user.actions || (user.saveContact ? ['save'] : [])).length > 0 && (
            <div className="pf-actions">
              {(user.actions || (user.saveContact ? ['save'] : [])).map((a) => {
                const m = (window.ACTIONS || {})[a]; if (!m) return null;
                const primary = a === 'save' || a === 'subscribe';
                const val = (user.actionData || {})[a];
                const onTap = () => {
                  if (a === 'save') return saveContact(user, toast);
                  if (a === 'phone' && val) return (window.location.href = 'tel:' + val);
                  if (a === 'email') return (window.location.href = 'mailto:' + (val || ''));
                  if (a === 'whatsapp' && val) return window.open('https://wa.me/' + val.replace(/[^0-9]/g, ''), '_blank');
                  if (a === 'tip') return setTipOpen(true);
                  if (a === 'share') {
                    if (onShare) return onShare();
                    if (navigator.share) return navigator.share({ url: location.href }).catch(() => {});
                    if (navigator.clipboard) navigator.clipboard.writeText(location.href).catch(() => {});
                    return toast && toast('Link copied');
                  }
                  toast && toast(m.label);
                };
                if (a === 'share') {
                  return (
                    <button className="glass-pill" key={a} onClick={onTap}>{MI.share ? MI.share({ width: 15, height: 15 }) : (MI.link ? MI.link({ width: 15, height: 15 }) : '↗')} {m.label}</button>
                  );
                }
                return (
                  <button className="pf-action" key={a} onClick={onTap}>
                    <span className="pa-ic" style={{ background: m.c }}>{m.icon}</span>{m.label}
                  </button>
                );
              })}
            </div>
          )}
        </div>
      </div>
      <div className="pf-body">
        {(sections || []).filter((s) => s.on || (s.id === 'moments' && !user.pro)).map((s) => {
          if (s.id === 'bio') {
            if (!user.bio) return null;
            return (
              <div className={'pf-section pf-bio-section' + (user.align === 'left' ? ' align-left' : '')} key="bio">
                <p className="pf-bio-body">{user.bio}</p>
              </div>
            );
          }
          if (s.id === 'featured') {
            if (links.length === 0) return null; // empty → render nothing
            return (
              <div className="pf-section" key="featured">
                <div className="pf-section-h">Featured</div>
                {renderLinks(links)}
              </div>
            );
          }
          if (s.id === 'moments') return <div className="pf-section" key="moments"><div className="pf-section-h">Moments</div><MomentsRow user={user} pub={pub} viewer={pub ? (viewer || null) : user} onEditMoment={onEditMoment} onDeleteMoment={onDeleteMoment} onSetComments={onSetComments} onMoments={onMoments} onPost={onPostMoment} toast={toast} /></div>;
          if (s.id === 'embeds') { if (!(user.embeds || []).some((x) => x && x.url)) return null; return <div className="pf-section" key="embeds"><div className="pf-section-h">Featured media</div><EmbedRail user={user} /></div>; }
          if (s.id === 'merch') return <div className="pf-section" key="merch"><div className="pf-section-h">Shop</div><MerchRail user={user} /></div>;
          if (s.id === 'gallery') return <div className="pf-section" key="gallery"><div className="pf-section-h">Gallery</div><GalleryGrid user={user} /></div>;
          if (s.id === 'exchange') {
            if (!(user.pro && exc.on) || exc.sticky) return null;
            return (
              <div className="pf-section" key="exchange">
                {exc.display === 'card' ? (
                  <button className="ex-cta-card" onClick={() => setExOpen(true)}>
                    <span className="ex-cta-card-ic">{MI.mail ? MI.mail({ width: 22, height: 22 }) : '✉'}</span>
                    <span className="ex-cta-card-meta"><b>{exc.headline || 'Exchange contact'}</b><span>{exc.subtext || 'Tap to share your details'}</span></span>
                    {MI.arrow ? MI.arrow({ width: 18, height: 18 }) : '→'}
                  </button>
                ) : exc.display === 'link' ? (
                  <button className="ex-cta-link" onClick={() => setExOpen(true)}>
                    <span className="ex-cta-link-ic">{MI.mail ? MI.mail({ width: 18, height: 18 }) : '✉'}</span>
                    <span className="ex-cta-link-t">Exchange contact</span>
                    {MI.arrow ? MI.arrow({ width: 16, height: 16 }) : '→'}
                  </button>
                ) : (
                  <button className="ex-cta-btn" onClick={() => setExOpen(true)}>{MI.refresh ? MI.refresh({ width: 17, height: 17 }) : null} Exchange Contact</button>
                )}
              </div>
            );
          }
          if (s.id === 'tip') {
            if (!(user.pro && user.tipsOn) || (window.tipConf && window.tipConf(user).display === 'corner')) return null;
            return window.TipButton ? <React.Fragment key="tip"><window.TipButton user={user} onOpen={() => setTipOpen(true)} /></React.Fragment> : null;
          }
          return null;
        })}
      </div>
      {user.pro && exc.on && exc.sticky && <button className="ex-sticky" onClick={() => setExOpen(true)}>{MI.refresh ? MI.refresh({ width: 18, height: 18 }) : null} Exchange Contact</button>}
      {exOpen && window.ExchangeForm && <window.ExchangeForm user={user} onClose={() => setExOpen(false)} onSubmit={(lead) => { onLead && onLead(lead); }} />}
      {user.pro && user.tipsOn && window.tipConf && window.tipConf(user).display === 'corner' && window.TipButton && <window.TipButton user={user} onOpen={() => setTipOpen(true)} />}
      {tipOpen && window.TipSheet && <window.TipSheet user={user} onClose={() => setTipOpen(false)} onTip={(tip) => { onTip && onTip(tip); }} />}
    </div>
  );
}

/* lead capture form — actually stores submissions to user.leads (demo-functional) */
function LeadCaptureForm({ user }) {
  const [email, setEmail] = mUse('');
  const [phone, setPhone] = mUse('');
  const [sent, setSent] = mUse(false);
  const first = user.name ? user.name.split(' ')[0] : 'me';
  const submit = () => {
    if (!email.trim()) return;
    try {
      const u = window.Store.getUser() || {};
      const lead = { id: (window.newId ? window.newId() : 'l' + Date.now()), email: email.trim(), phone: phone.trim(), ts: Date.now(), source: 'Profile · Stay in touch', via: 'bio' };
      const conn = window.leadToConn ? window.leadToConn(lead) : lead;
      window.Store.saveUser({ ...u, connections: [conn, ...(u.connections || [])] });
      if (window.Store.saveConn) window.Store.saveConn(conn);
    } catch (e) {}
    setSent(true);
  };
  if (sent) return (
    <div className="lc-card lc-done"><div className="lc-check">{MI.check ? MI.check({ width: 22, height: 22 }) : '✓'}</div><div className="lc-t">You're in</div><div className="lc-s">{first} will be in touch soon.</div></div>
  );
  return (
    <div className="lc-card">
      <div className="lc-t">Leave your details</div>
      <div className="lc-s">{first}'ll reach out — no spam, ever.</div>
      <input className="lc-input" type="email" placeholder="Your email" value={email} onChange={(e) => setEmail(e.target.value)} />
      <input className="lc-input" placeholder="Your number (optional)" value={phone} onChange={(e) => setPhone(e.target.value)} />
      <button className="lc-btn" onClick={submit}>Send to {first}</button>
    </div>
  );
}

/* ===========================================================
   DESIGN SCREEN  (appearance home — themes, link anims, ring, font)
   =========================================================== */
function DesignPanel({ draft, onChange, onApply, onCancel, isPro, onPro }) {
  const guard = (patch) => { if (!isPro) { onPro(); return; } onChange(patch); };
  const THEMES = window.PROFILE_THEMES || [];
  const LANIMS = window.LINK_ANIMS || [];
  const IANIMS = window.ICON_ANIMS || [];
  const FONTS = [['display', 'Bricolage', 'var(--display)'], ['serif', 'Editorial', "'Instrument Serif', serif"], ['mono', 'Mono', 'var(--mono)'], ['round', 'Round', "'Quicksand', system-ui, sans-serif"]];
  const theme = draft.profileTheme || 'default';
  const lanim = draft.linkAnim || 'none';
  const ianim = draft.iconAnim || 'none';
  const font = draft.headlineFont || 'display';

  // draggable height — pull up/down to reveal more/less of the live profile
  const [h, setH] = mUse(46);   // % of frame height
  const drag = mRef(null);
  const onGrabStart = (e) => {
    const startY = (e.touches ? e.touches[0].clientY : e.clientY);
    const startH = h;
    const frame = document.querySelector('.appframe');
    const fh = frame ? frame.getBoundingClientRect().height : window.innerHeight;
    const move = (ev) => {
      const y = (ev.touches ? ev.touches[0].clientY : ev.clientY);
      let next = startH + ((startY - y) / fh) * 100;
      next = Math.max(22, Math.min(82, next));
      setH(next);
    };
    const up = () => {
      document.removeEventListener('pointermove', move); document.removeEventListener('pointerup', up);
      document.removeEventListener('touchmove', move); document.removeEventListener('touchend', up);
    };
    document.addEventListener('pointermove', move); document.addEventListener('pointerup', up);
    document.addEventListener('touchmove', move, { passive: true }); document.addEventListener('touchend', up);
  };

  return (
    <div className="dpanel" style={{ height: h + '%' }} ref={drag}>
      <div className="dpanel-grab" onPointerDown={onGrabStart} onTouchStart={onGrabStart}><span /></div>
      <div className="dpanel-head">
        <button className="dpanel-x" onClick={onCancel} aria-label="Cancel">{MI.close ? MI.close({ width: 18, height: 18 }) : '✕'}</button>
        <span className="dpanel-title">Design</span>
        <button className="dpanel-apply" onClick={onApply}>Apply</button>
      </div>
      <div className="dpanel-scroll">
        {!isPro && <button className="dz-upsell dpanel-upsell" onClick={onPro}><span className="pro-badge">PRO</span><span>Unlock every theme, animation &amp; font</span>{MI.arrow({ width: 16, height: 16 })}</button>}

        <div className="dpanel-row">
          <div className="dpanel-lbl">Theme</div>
          <div className="dpanel-track">
            {THEMES.map((t) => {
              const freeTheme = t.id === 'default' || t.free;
              return (
              <button key={t.id} className={'theme-card dpc' + (theme === t.id ? ' sel' : '')} onClick={() => { if (freeTheme || isPro) onChange({ profileTheme: t.id }); else onPro(); }}>
                <span className={'theme-prev tcard th-' + t.id}><span className="tc-bar" /><span className="tc-bar" /><span className="tc-bar short" /></span>
                <span className="theme-lb">{t.label}{!isPro && !freeTheme && <span className="tc-lock">{MI.lock ? MI.lock({ width: 9, height: 9 }) : ''}</span>}</span>
              </button>
              );
            })}
          </div>
        </div>

        <div className="dpanel-row">
          <div className="dpanel-lbl">Button style</div>
          <div className="dpanel-track dpt-grid3">
            {[['solid', 'Solid'], ['glass', 'Glass'], ['outline', 'Outline']].map(([id, lb]) => (
              <button key={id} className={'theme-card dpc btn-style-card' + ((draft.buttonStyle || 'solid') === id ? ' sel' : '')} onClick={() => onChange({ buttonStyle: id })}>
                <span className={'theme-prev btnprev bp-' + id}><span className="bpb" /></span>
                <span className="theme-lb">{lb}</span>
              </button>
            ))}
          </div>
        </div>

        <div className="dpanel-row">
          <div className="dpanel-lbl">Corner roundness</div>
          <div className="dpanel-track dpt-grid4">
            {[['sharp', 4], ['md', 11], ['round', 17], ['pill', 22]].map(([id, r]) => (
              <button key={id} className={'rad-card dpc' + ((draft.buttonRadius || 'md') === id ? ' sel' : '')} onClick={() => onChange({ buttonRadius: id })}>
                <span className="rad-prev" style={{ borderTopLeftRadius: r, borderTopRightRadius: r }} />
              </button>
            ))}
          </div>
        </div>

        <div className="dpanel-row">
          <div className="dpanel-lbl">Wallpaper</div>
          <div className="dpanel-track">
            <button className={'wp-card dpc' + (!(draft.wallpaper) ? ' sel' : '')} onClick={() => onChange({ wallpaper: '' })}>
              <span className="wp-prev wp-none-prev" />
            </button>
            {['tappt', 'indigo', 'violet', 'ember', 'ocean', 'plum', 'dusk', 'forest', 'aurora', 'solar', 'mono'].map((id) => (
              <button key={id} className={'wp-card dpc' + (draft.wallpaper === id ? ' sel' : '')} onClick={() => onChange({ wallpaper: id })}>
                <span className={'wp-prev wp-prev-' + id} />
              </button>
            ))}
          </div>
        </div>

        <div className="dpanel-row">
          <div className="dpanel-lbl">Effects</div>
          <div className="dpanel-track">
            {[['none', 'None'], ['beam', 'Beam'], ['glow', 'Glow'], ['shine', 'Shine'], ['pulse', 'Pulse']].map(([id, lb]) => (
              <button key={id} className={'fx-card dpc' + ((draft.cardEffect || 'none') === id ? ' sel' : '')} onClick={() => onChange({ cardEffect: id })}>
                <span className={'fx-prev fx-prev-' + id} />
                <span className="theme-lb">{lb}</span>
              </button>
            ))}
          </div>
        </div>

        <div className="dpanel-row">
          <div className="dpanel-track">
            {LANIMS.map((a) => (
              <button key={a.id} className={'lanim-card dpc' + (lanim === a.id ? ' sel' : '')} onClick={() => guard({ linkAnim: a.id })}>
                <span className={'lanim-prev la-' + a.id}><span className="lap-row" /><span className="lap-row" /></span>
                <span className="lanim-lb">{a.label}</span>
              </button>
            ))}
          </div>
        </div>

        <div className="dpanel-row">
          <div className="dpanel-lbl">Icon animation</div>
          <div className="dpanel-track">
            {IANIMS.map((a) => (
              <button key={a.id} className={'lanim-card dpc' + (ianim === a.id ? ' sel' : '')} onClick={() => guard({ iconAnim: a.id })}>
                <span className="ianim-prev"><span className={'ianim-dot ia-' + a.id} /></span>
                <span className="lanim-lb">{a.label}</span>
              </button>
            ))}
          </div>
        </div>

        <div className="dpanel-row">
          <div className="dpanel-lbl">Headline font</div>
          <div className="dpanel-track">
            {FONTS.map(([id, lb, ff]) => (
              <button key={id} className={'font-card dpc-font' + (font === id ? ' sel' : '')} style={{ fontFamily: ff }} onClick={() => guard({ headlineFont: id })}>Aa<span className="font-lb">{lb}</span></button>
            ))}
            <button className={'dz-ringbtn' + (draft.avatarRing ? ' on' : '')} onClick={() => guard({ avatarRing: !draft.avatarRing })}>{MI.spark ? MI.spark({ width: 16, height: 16 }) : '✦'}<span>Avatar ring</span><span className={'switch' + (isPro && draft.avatarRing ? ' on' : '')} /></button>
          </div>
        </div>
      </div>
    </div>
  );
}

/* ===========================================================
   SICK ANALYTICS
   =========================================================== */
function AnalyticsScreen({ user, onEarnings, onPro }) {
  const isPro = !!user.pro;
  const [range, setRange] = mUse('30D');
  const RANGES = isPro ? ['24H', '7D', '30D', '90D', '12M', 'All'] : ['7D', '30D'];
  const [source, setSource] = mUse('all');   // all · card · bio
  // ---- real analytics from the events table (demo numbers stay for @maxxharland) ----
  const [real, setReal] = mUse(null);
  const isDemo = (user.handle === 'maxxharland');
  React.useEffect(() => {
    if (isDemo || !user.id || !window.TapptDB || !window.TapptDB.available()) { setReal(null); return; }
    const days = range === '24H' ? 1 : range === '7D' ? 7 : range === '30D' ? 30 : range === '90D' ? 90 : range === '12M' ? 365 : 1460;
    const since = new Date(Date.now() - days * 864e5).toISOString();
    let alive = true;
    window.TapptDB.analytics(user.id, since).then((rows) => {
      if (!alive) return;
      if (!rows) { setReal(null); return; }
      const f = rows.filter((r) => source === 'all' ? true : r.source === source);
      const isView = (r) => r.kind === 'view' || !r.kind;
      const views = f.filter(isView);
      const buckets = new Array(Math.min(days, 30)).fill(0);
      const span = days * 864e5;
      views.forEach((r) => { const age = Date.now() - new Date(r.created_at).getTime(); const bi = Math.min(buckets.length - 1, Math.max(0, Math.floor((1 - age / span) * buckets.length))); buckets[bi]++; });
      setReal({
        rows: f, total: views.length, days: buckets,
        saves: f.filter((r) => r.kind === 'save_contact').length,
        clicks: f.filter((r) => r.kind === 'link_click').length,
        card: views.filter((r) => r.source === 'card').length,
        bio: views.filter((r) => r.source === 'bio').length,
        geo: (() => {
          const m = {}; views.forEach((r) => { const c = r.country || 'Unknown'; m[c] = (m[c] || 0) + 1; });
          const tot = views.length || 1;
          return Object.keys(m).map((c) => ({ c: c, v: Math.round((m[c] / tot) * 100), n: m[c] })).sort((x, y) => y.n - x.n).slice(0, 5);
        })(),
      });
    }).catch(() => setReal(null));
    return () => { alive = false; };
  }, [range, source, user.id, isDemo]);
  const srcSeed = source === 'card' ? 0 : source === 'bio' ? 4 : 0;
  const rngSeed = { '24H': 1, '7D': 3, '30D': 9, '90D': 14, '12M': 18, 'All': 21 }[range] || 9;
  const seed = (user.handle || 'tappt').length + rngSeed + srcSeed;
  const demo = mGen(seed);
  const useReal = !!(real && real.total > 0);
  // Real accounts ONLY ever show real data (zeros until they get taps) — never the generated
  // demo numbers. The demo handle (@maxxharland) keeps the lively generated showcase numbers.
  const zeroA = { days: new Array(12).fill(0), taps: 0, views: 0, saves: 0 };
  const a = isDemo
    ? demo
    : (useReal ? Object.assign({}, demo, { days: real.days.length ? real.days : new Array(12).fill(0), taps: real.total, views: real.total, saves: real.saves }) : zeroA);
  const max = Math.max(...a.days, 1);
  const srcMult = source === 'all' ? 1 : source === 'card' ? 0.62 : 0.38;   // card 62% / bio 38% of traffic
  const demoRaw = (range === '24H' ? Math.round(demo.taps * 0.08) : range === '7D' ? Math.round(demo.taps * 0.4) : range === '90D' ? demo.taps * 3 : range === '12M' ? demo.taps * 12 : range === 'All' ? demo.taps * 6 : demo.taps);
  const totalRaw = isDemo ? demoRaw : (real ? real.total : 0);
  const totalTaps = isDemo ? Math.round(totalRaw * srcMult) : (real ? real.total : 0);
  const showDelta = isDemo || useReal;
  const fmt = (n) => n >= 1000 ? (n / 1000).toFixed(1) + 'k' : '' + n;
  const isCard = source === 'card';
  const isBio = source === 'bio';
  const metricLabel = isBio ? 'Total link views' : isCard ? 'Total card taps' : 'Total taps + views';

  const card = MC.find((c) => c.id === user.activeCard);
  const links = (user.links || []).filter((l) => l.active !== false);
  const topItems = [
    ...(card ? [{ type: card.id, name: card.name, brand: false, v: 100 }] : []),
    ...links.slice(0, 4).map((l, i) => ({ type: l.type, name: l.label || mplat(l.type).label, brand: true, v: 84 - i * 17 })),
  ].slice(0, 5);
  if (topItems.length === 0) topItems.push({ type: 'instagram', name: 'Instagram', brand: true, v: 80 });

  const FLAGS = { 'United Kingdom': '🇬🇧', 'United States': '🇺🇸', 'Germany': '🇩🇪', 'France': '🇫🇷', 'Spain': '🇪🇸', 'Italy': '🇮🇹', 'Netherlands': '🇳🇱', 'Ireland': '🇮🇪', 'Canada': '🇨🇦', 'Australia': '🇦🇺', 'United Arab Emirates': '🇦🇪', 'UAE': '🇦🇪', 'India': '🇮🇳', 'Brazil': '🇧🇷', 'Mexico': '🇲🇽', 'Japan': '🇯🇵', 'Sweden': '🇸🇪', 'Norway': '🇳🇴', 'Switzerland': '🇨🇭', 'Portugal': '🇵🇹', 'Poland': '🇵🇱', 'Unknown': '🌍' };
  const demoGeo = [
    { c: 'United Kingdom', f: '🇬🇧', v: 48 }, { c: 'United States', f: '🇺🇸', v: 27 },
    { c: 'Germany', f: '🇩🇪', v: 12 }, { c: 'UAE', f: '🇦🇪', v: 8 }, { c: 'Other', f: '🌍', v: 5 },
  ];
  const geo = isDemo
    ? demoGeo
    : ((useReal && real.geo && real.geo.length) ? real.geo.map((g) => ({ c: g.c, f: FLAGS[g.c] || '🌍', v: g.v })) : []);

  return (
    <div className="appscroll"><div className="screen">
      <div className="screen-top"><div className="st-logo"><h3 style={{ fontFamily: 'var(--display)', fontWeight: 800, fontSize: 22, letterSpacing: '-0.02em' }}>Analytics</h3></div></div>

      <div className="an-range">{RANGES.map((r) => <button key={r} className={range === r ? 'on' : ''} onClick={() => setRange(r)}>{r}</button>)}{!isPro && <button className="an-range-pro" onClick={onPro}>{MI.crown ? MI.crown({ width: 12, height: 12 }) : '✦'} 90D·12M</button>}</div>

      {/* source toggle: All / Card / Bio Link */}
      <div className="an-source">
        {[['all', 'All', null], ['card', 'NFC Card', 'card'], ['bio', 'Bio Link', 'link']].map(([id, lbl, ic]) => (
          <button key={id} className={source === id ? 'on' : ''} onClick={() => setSource(id)}>
            {ic && MI[ic] ? MI[ic]({ width: 14, height: 14 }) : null}{lbl}
          </button>
        ))}
      </div>

      <div className="an-hero">
        <div className="lbl" style={{ display: 'flex', alignItems: 'center', gap: 6 }}>{isBio ? MI.link({ width: 14, height: 14 }) : isCard ? MI.card({ width: 14, height: 14 }) : MI.sparkles({ width: 14, height: 14 })} {metricLabel}</div>
        <div className="an-big"><NumTicker value={totalTaps} format={fmt} /></div>
        <div className="an-delta" style={{ visibility: showDelta ? 'visible' : 'hidden' }}>▲ {isBio ? '14.6' : isCard ? '21.3' : '18.2'}% vs previous {range}</div>
        <div className="an-spark">{a.days.map((v, i) => <i key={i} className={v === max ? 'hi' : ''} style={{ height: `${(v / max) * 100}%` }} />)}</div>
      </div>

      {/* source mix (only on All) */}
      {source === 'all' && (
        <div className="an-mix">
          <button className="an-mix-seg card" onClick={() => setSource('card')}>
            <span className="amx-ic">{MI.card({ width: 15, height: 15 })}</span>
            <span className="amx-meta"><b>{fmt(Math.round(totalRaw * 0.62))}</b><span>NFC Card · 62%</span></span>
          </button>
          <button className="an-mix-seg bio" onClick={() => setSource('bio')}>
            <span className="amx-ic">{MI.link({ width: 15, height: 15 })}</span>
            <span className="amx-meta"><b>{fmt(Math.round(totalRaw * 0.38))}</b><span>Bio Link · 38%</span></span>
          </button>
        </div>
      )}

      <div className="an-mini">
        <div className="m"><div className="m-ic">{MI.eye({ width: 16, height: 16 })}</div><div className="mv"><NumTicker value={a.views} format={fmt} /></div><div className="ml">Profile views</div></div>
        <div className="m"><div className="m-ic">{MI.userPlus({ width: 16, height: 16 })}</div><div className="mv"><NumTicker value={a.saves} format={fmt} /></div><div className="ml">Contacts saved</div></div>
        <div className="m"><div className="m-ic">{MI.pointer({ width: 16, height: 16 })}</div><div className="mv">{isDemo ? (58 + (seed % 9)) : (useReal && real.total ? Math.round((real.clicks / real.total) * 100) : 0)}%</div><div className="ml">{isBio ? 'Click-through' : 'Tap-through'}</div></div>
      </div>

      <div className="an-panel">
        <h4><span className="h4-ic">{MI.chart({ width: 15, height: 15 })}</span>{isBio ? 'Views over time' : isCard ? 'Card taps over time' : 'Taps over time'} <span className="live">LIVE</span></h4>
        <div className="bars">{a.days.map((v, i) => <div className="bar" key={i} style={{ height: `${(v / max) * 100}%` }} />)}</div>
      </div>

      <div className="an-panel">
        <h4><span className="h4-ic">{MI.trophy({ width: 15, height: 15 })}</span>Top performing</h4>
        {topItems.map((t, i) => (
          <div className="an-row" key={i}>
            <span className="ico">{t.brand ? <MBrand id={t.type} size={30} radius={9} /> : <span style={{ width: 30, height: 30, borderRadius: 9, overflow: 'hidden', display: 'block', background: '#000' }}>{card && <img src={card.img} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />}</span>}</span>
            <span className="rn">{t.name}</span>
            <span className="an-bar-track"><span className="an-bar-fill" style={{ width: t.v + '%' }} /></span>
            <span className="rv">{Math.round(totalTaps * t.v / 260)}</span>
          </div>
        ))}
      </div>

      <div className={'an-panel' + (isPro ? '' : ' an-locked')}>
        <h4><span className="h4-ic">{MI.globe ? MI.globe({ width: 15, height: 15 }) : MI.search({ width: 15, height: 15 })}</span>Where {isBio ? 'views' : 'taps'} come from</h4>
        {geo.map((g) => (
          <div className="an-row" key={g.c}>
            <span className="flag">{g.f}</span><span className="rn" style={{ width: 110 }}>{g.c}</span>
            <span className="an-bar-track"><span className="an-bar-fill" style={{ width: g.v + '%' }} /></span>
            <span className="rv">{g.v}%</span>
          </div>
        ))}
        {geo.length === 0 && <div style={{ padding: '14px 4px', color: 'var(--fg-3)', fontSize: 13 }}>No location data yet — it appears as people tap your card or open your link.</div>}
        {!isPro && <div className="an-locked-veil"><span className="alv-t">Geography is a Pro insight</span><button className="alv-btn" onClick={onPro}>{MI.crown ? MI.crown({ width: 13, height: 13 }) : '✦'} Unlock with Pro</button></div>}
      </div>

      <div className={'an-panel' + (isPro ? '' : ' an-locked')}>
        <h4><span className="h4-ic">{MI.card({ width: 15, height: 15 })}</span>Devices</h4>
        <div className="an-donut-wrap">
          <div className="an-donut" style={{ background: 'conic-gradient(var(--accent) 0 62%, var(--brand) 62% 88%, var(--fg-3) 88% 100%)' }}>
            <div className="ctr"><b>62%</b><span>iOS</span></div>
          </div>
          <div className="an-legend">
            <div className="lg"><span className="dot" style={{ background: 'var(--accent)' }} />iOS<span className="lv">62%</span></div>
            <div className="lg"><span className="dot" style={{ background: 'var(--brand)' }} />Android<span className="lv">26%</span></div>
            <div className="lg"><span className="dot" style={{ background: 'var(--fg-3)' }} />Web<span className="lv">12%</span></div>
          </div>
        </div>
        {!isPro && <div className="an-locked-veil"><span className="alv-t">Device breakdown is Pro</span><button className="alv-btn" onClick={onPro}>{MI.crown ? MI.crown({ width: 13, height: 13 }) : '✦'} Unlock with Pro</button></div>}
      </div>

      <div className="an-panel" style={{ marginBottom: 6 }}>
        <h4><span className="h4-ic">{MI.dollar({ width: 15, height: 15 })}</span>Creator earnings</h4>
        <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
          <div style={{ flex: 1 }}>
            <div style={{ fontFamily: 'var(--display)', fontWeight: 800, fontSize: 28, letterSpacing: '-0.02em' }}><NumTicker value={284.20} decimals={2} prefix="£" /></div>
            <div style={{ fontFamily: 'var(--mono)', fontSize: 11, color: 'var(--ok)' }}>available to withdraw</div>
          </div>
          <button className="cp" style={{ background: 'var(--bg-3)', padding: '10px 16px', borderRadius: 12 }} onClick={onEarnings}>Open Earnings →</button>
        </div>
      </div>
    </div></div>
  );
}

/* ===========================================================
   EARNINGS (separate from analytics)
   =========================================================== */
function PayoutSetup({ user, toast }) {
  const db = window.TapptDB;
  const [st, setSt] = mUse({ enabled: !!user.payoutsEnabled, onboarded: !!user.payoutsOnboarded, dashUrl: null, loading: false, checking: true });
  React.useEffect(() => {
    let live = true;
    if (!db || !db.available()) { setSt((s) => ({ ...s, checking: false })); return; }
    db.connectStatus().then((r) => {
      if (!live) return;
      if (r) setSt((s) => ({ ...s, enabled: !!r.payouts_enabled, onboarded: !!r.onboarded, dashUrl: r.dashboard_url || null, checking: false }));
      else setSt((s) => ({ ...s, checking: false }));
    }).catch(() => { if (live) setSt((s) => ({ ...s, checking: false })); });
    return () => { live = false; };
  }, []);
  const start = async () => {
    if (!db || !db.available()) { toast && toast('Sign in to set up payouts'); return; }
    setSt((s) => ({ ...s, loading: true }));
    try {
      const r = await db.connectOnboard();
      if (r && r.url) { window.location.href = r.url; return; }
      throw new Error('no url');
    } catch (e) { setSt((s) => ({ ...s, loading: false })); toast && toast("Couldn't open Stripe setup"); }
  };
  if (st.enabled) {
    return (
      <div className="payout-live">
        <span className="pl-badge">{MI.check ? MI.check({ width: 15, height: 15 }) : '✓'}</span>
        <div className="pl-meta"><b>Payouts active</b><span>Earnings transfer to your bank automatically via Stripe</span></div>
        {st.dashUrl && <a className="pl-link" href={st.dashUrl} target="_blank" rel="noreferrer">Stripe →</a>}
      </div>
    );
  }
  return (
    <div className="payout-setup">
      <div className="ps-head">
        <span className="ps-ic">{MI.dollar ? MI.dollar({ width: 18, height: 18 }) : '£'}</span>
        <div className="ps-meta"><b>Set up payouts</b><span>Connect a bank account to withdraw your earnings</span></div>
      </div>
      <button className="btn btn-primary btn-block" onClick={start} disabled={st.loading}>
        {st.loading ? 'Opening Stripe…' : (st.onboarded ? 'Finish payout setup' : 'Set up payouts with Stripe')}
      </button>
      <div className="ps-legal">{MI.lock ? MI.lock({ width: 12, height: 12 }) : '🔒'} Bank-level security · Stripe holds your details, never Tappt</div>
    </div>
  );
}

function EarningsScreen({ user, toast, onPro }) {
  const a = mGen((user.handle || 'tappt').length + 11);
  const refUrl = `tappt.io/r/${user.handle || 'you'}`;
  const [showQR, setShowQR] = mUse(false);
  const isPro = !!user.pro;
  const rate = isPro ? 8 : 5;
  const isDemo = (user.handle === 'maxxharland');
  const qrImg = `https://api.qrserver.com/v1/create-qr-code/?size=320x320&margin=0&data=${encodeURIComponent('https://' + refUrl)}`;

  // real earnings for a signed-in creator (referral_payouts + tips). Demo @maxxharland keeps
  // the showcase numbers; every real account starts at £0 with an empty activity feed.
  const [real, setReal] = mUse(null);
  React.useEffect(() => {
    if (isDemo || !user.id || !window.TapptDB || !window.TapptDB.available || !window.TapptDB.available()) { setReal(null); return; }
    let alive = true;
    (async () => {
      const sb = window.TapptDB.client;
      let txns = [], balance = 0, pending = 0, allTime = 0, refCount = 0;
      try {
        const rp = await sb.from('referral_payouts').select('amount,status,buyer_email,created_at').or('referrer.eq.' + user.id + ',referrer_handle.eq.' + user.handle).order('created_at', { ascending: false }).limit(50);
        (rp.data || []).forEach((r) => {
          const amt = parseFloat(r.amount) || 0; refCount++;
          const st = r.status === 'pending' ? 'pending' : (r.status === 'void' || r.status === 'cancelled' ? 'void' : 'cleared');
          if (st === 'pending') pending += amt; else if (st === 'cleared') balance += amt;
          if (st !== 'void') allTime += amt;
          txns.push({ t: 'Referral bonus', s: 'Card order', v: '+£' + amt.toFixed(2), d: '', ic: 'ticket', st: st, _at: r.created_at });
        });
      } catch (e) {}
      try {
        const tp = await sb.from('tips').select('amount,created_at').eq('owner', user.id).order('created_at', { ascending: false }).limit(50);
        (tp.data || []).forEach((t) => {
          const amt = parseFloat(t.amount) || 0; balance += amt; allTime += amt;
          txns.push({ t: 'Profile tip', s: 'Supporter', v: '+£' + amt.toFixed(2), d: '', ic: 'gift', st: 'cleared', _at: t.created_at });
        });
      } catch (e) {}
      txns.sort((x, y) => new Date(y._at || 0) - new Date(x._at || 0));
      if (alive) setReal({ balance: balance, pending: pending, allTime: allTime, refCount: refCount, txns: txns.slice(0, 8) });
    })();
    return () => { alive = false; };
  }, [isDemo, user.id, user.handle]);

  const demoTxns = [
    { t: 'Referral bonus', s: '@jaydn ordered a Joker card', v: '+£' + rate + '.00', d: 'Today', ic: 'ticket', st: 'pending' },
    { t: 'Profile tip', s: 'Anonymous supporter', v: '+£5.00', d: 'Today', ic: 'gift', st: 'cleared' },
    { t: 'Referral bonus', s: '@sofiaa ordered a Carbon card', v: '+£' + rate + '.00', d: 'Yesterday', ic: 'ticket', st: 'pending' },
    { t: 'Affiliate — merch', s: '3 sales · Tappt store', v: '+£18.40', d: '2 days ago', ic: 'cart', st: 'cleared' },
    { t: 'Profile tip', s: 'From @themaxlife', v: '+£8.00', d: '4 days ago', ic: 'gift', st: 'cleared' },
  ];
  const txns = isDemo ? demoTxns : (real ? real.txns : []);
  const balance = isDemo ? 86.40 : (real ? real.balance : 0);
  const pending = isDemo ? 18 : (real ? real.pending : 0);
  const allTime = isDemo ? 187 : (real ? real.allTime : 0);
  const refCount = isDemo ? 14 : (real ? real.refCount : 0);
  const stPill = { pending: { t: 'Clears in 14d', c: 'var(--gold,#E8C75A)' }, cleared: { t: 'Cleared', c: 'var(--ok)' }, void: { t: 'Void', c: 'var(--fg-3)' } };
  return (
    <div className="appscroll"><div className="screen">
      <div className="screen-top"><div className="st-logo"><h3 style={{ fontFamily: 'var(--display)', fontWeight: 800, fontSize: 22, letterSpacing: '-0.02em' }}>Earnings</h3></div>
        <span className="eyebrow" style={{ display: 'flex', alignItems: 'center', gap: 6 }}><span style={{ width: 7, height: 7, borderRadius: '50%', background: 'var(--ok)' }} /> LIVE</span></div>

      <div className="an-earn">
        <div className="el">Available to withdraw</div>
        <div className="ev"><NumTicker value={balance} decimals={2} prefix="£" /></div>
        {pending > 0 && <div className="es">£{pending} pending · clears after the 14-day refund window</div>}
      </div>
      <PayoutSetup user={user} toast={toast} />

      {/* Refer & Earn — the night-out flywheel */}
      <div className="refer-card">
        <div className="refer-top">
          <span className="refer-badge">REFER &amp; EARN</span>
          <span className="refer-rate">£{rate}<span>/card</span></span>
        </div>
        <div className="refer-h">Flex your card. Get paid.</div>
        <p className="refer-p">Tap someone's phone, they grab their own card from your link — you earn £{rate} and they get 10% off. Bag a few on a night out and the drinks pay for themselves.</p>
        <div className="refer-link">
          <span className="rl-ico">{MI.ticket({ width: 15, height: 15 })}</span>
          <span className="rl-url">{refUrl}</span>
          <button className="rl-copy" onClick={() => { navigator.clipboard && navigator.clipboard.writeText('https://' + refUrl).catch(() => {}); toast && toast('Referral link copied'); }}>Copy</button>
        </div>
        <div className="refer-actions">
          <button className="btn btn-dark" style={{ flex: 1 }} onClick={() => toast && toast('Opening share…')}>{MI.link({ width: 16, height: 16 })} Share</button>
          <button className="btn btn-dark refer-qr-btn" onClick={() => setShowQR(true)} aria-label="Show QR code">{MI.qr ? MI.qr({ width: 17, height: 17 }) : '▦'} QR</button>
        </div>
        <div className="refer-steps">
          {[['1', 'Tap', 'Tap your card on their phone'], ['2', 'They buy', 'They check out within 24h, 10% off'], ['3', 'You earn', '£' + rate + ' lands in your balance']].map(([n, t, s]) => (
            <div className="refer-step" key={n}>
              <span className="rs-n">{n}</span>
              <div><div className="rs-t">{t}</div><div className="rs-s">{s}</div></div>
            </div>
          ))}
        </div>
        {!isPro && (
          <button className="refer-pro-nudge" onClick={onPro}>
            <span className="pro-badge">PRO</span>
            <span>Go <b>Pro</b> — unlimited profiles, motion &amp; deeper analytics. From <b>£5.99/mo</b>.</span>
            {MI.arrow({ width: 16, height: 16 })}
          </button>
        )}
      </div>

      {showQR && (
        <div className="sheet-bg" onClick={(e) => { if (e.target === e.currentTarget) setShowQR(false); }}>
          <div className="sheet">
            <div className="sheet-grab" />
            <div className="sheet-head"><span style={{ width: 36 }} /><h3 style={{ fontSize: 16 }}>Scan to claim 10% off</h3><button className="icon-btn" onClick={() => setShowQR(false)} style={{ width: 36, height: 36 }}>✕</button></div>
            <div className="sheet-scroll" style={{ textAlign: 'center' }}>
              <div style={{ width: 244, margin: '8px auto 18px', padding: 18, borderRadius: 24, background: '#fff', boxShadow: '0 20px 50px -16px rgba(0,0,0,0.5)' }}>
                <img src={qrImg} alt="Referral QR" style={{ width: '100%', display: 'block', borderRadius: 8 }} />
                <div style={{ marginTop: 12, fontFamily: 'var(--mono)', fontSize: 12, color: '#0B0B0C', fontWeight: 600 }}>{refUrl}</div>
              </div>
              <p style={{ color: 'var(--fg-2)', fontSize: 14, maxWidth: '30ch', margin: '0 auto 20px' }}>Hold this up — they scan it, get <b>10% off</b> for 24h, and you earn <b>£5</b> when they order.</p>
              <button className="btn btn-primary btn-block" onClick={() => { navigator.clipboard && navigator.clipboard.writeText('https://' + refUrl).catch(() => {}); toast && toast('Referral link copied'); }}>{MI.link({ width: 16, height: 16 })} Copy link instead</button>
            </div>
          </div>
        </div>
      )}

      <div className="an-mini" style={{ marginBottom: 22 }}>
        <div className="m"><div className="mv">£{isDemo ? '187' : (Math.round(allTime * 100) / 100)}</div><div className="ml">All-time</div></div>
        <div className="m"><div className="mv">{refCount}</div><div className="ml">Referrals</div></div>
        <div className="m"><div className="mv">£{rate}</div><div className="ml">Per card</div></div>
      </div>

      <div className="an-panel">
        <h4>How you earn</h4>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
          {[['ticket', 'Referrals', 'Earn £' + rate + ' when someone orders a card from your link'], ['gift', 'Tips', 'Fans tip you straight from your profile'], ['cart', 'Affiliate', 'Commission on Tappt merch you share']].map(([ic, t, s]) => (
            <div key={t} style={{ display: 'flex', gap: 12, alignItems: 'flex-start' }}>
              <span className="egrad-ic" style={{ width: 34, height: 34, borderRadius: 10, background: 'var(--bg-3)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--accent)', flex: 'none' }}>{MI[ic]({ width: 18, height: 18 })}</span>
              <div><div style={{ fontWeight: 700, fontSize: 14 }}>{t}</div><div style={{ fontSize: 12.5, color: 'var(--fg-3)' }}>{s}</div></div>
            </div>
          ))}
        </div>
      </div>

      <div className="an-panel">
        <h4>Recent activity</h4>
        {txns.length === 0 && (
          <div style={{ padding: '22px 4px 10px', textAlign: 'center', color: 'var(--fg-3)', fontSize: 13.5, lineHeight: 1.5 }}>
            No earnings yet — share your link and your first <b style={{ color: 'var(--fg-2)' }}>£{rate}</b> referral lands here.
          </div>
        )}
        {txns.map((tx, i) => (
          <div className="an-row" key={i} style={{ gap: 12 }}>
            <span className="egrad-ic" style={{ width: 34, height: 34, borderRadius: 10, background: 'var(--bg-3)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--accent)', flex: 'none' }}>{MI[tx.ic] ? MI[tx.ic]({ width: 17, height: 17 }) : MI.dollar({ width: 17, height: 17 })}</span>
            <div style={{ flex: 1, minWidth: 0 }}><div style={{ fontWeight: 700, fontSize: 13.5 }}>{tx.t}</div><div style={{ fontSize: 11.5, color: 'var(--fg-3)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{tx.s}</div></div>
            <div style={{ textAlign: 'right', flex: 'none' }}><div style={{ fontFamily: 'var(--mono)', fontWeight: 700, fontSize: 13, color: tx.st === 'void' ? 'var(--fg-3)' : 'var(--ok)', textDecoration: tx.st === 'void' ? 'line-through' : 'none' }}>{tx.v}</div><div style={{ fontSize: 10, color: (stPill[tx.st] || stPill.cleared).c, fontWeight: 700 }}>{(stPill[tx.st] || stPill.cleared).t}</div></div>
          </div>
        ))}
      </div>
    </div></div>
  );
}

/* ===========================================================
   SALES  (Shop command centre — revenue, orders, best-sellers, payouts)
   =========================================================== */
function SalesScreen({ user, toast, onPro, onManage }) {
  const isPro = !!user.pro;
  const [range, setRange] = mUse('30D');
  const RANGES = isPro ? ['24H', '7D', '30D', '90D', '12M', 'All'] : ['7D', '30D'];
  const rngSeed = { '24H': 2, '7D': 5, '30D': 13, '90D': 19, '12M': 24, 'All': 27 }[range] || 13;
  const seed = (user.handle || 'tappt').length + rngSeed;
  const a = mGen(seed);
  const products = (user.products && user.products.length) ? user.products : (user.handle === 'maxxharland' ? SAMPLE_PRODUCTS : []);
  // No products yet (real new account) — show an empty state instead of crashing on
  // modulo-by-zero in the orders/sellers math below.
  if (!products.length) {
    return (
      <div className="appscroll"><div className="screen">
        <div className="screen-top"><div className="st-logo"><h3 style={{ fontFamily: 'var(--display)', fontWeight: 800, fontSize: 22, letterSpacing: '-0.02em' }}>Sales</h3></div>
          <button className="icon-btn" onClick={onManage} title="Manage products">{MI.edit({ width: 19, height: 19 })}</button>
        </div>
        <div style={{ textAlign: 'center', padding: '64px 24px', color: 'var(--fg-3)' }}>
          <div style={{ display: 'inline-flex', marginBottom: 14, opacity: 0.5 }}>{(MI.bag || MI.tag || MI.cart || MI.gift || MI.spark)({ width: 36, height: 36 })}</div>
          <div style={{ fontFamily: 'var(--display)', fontWeight: 800, fontSize: 19, color: 'var(--fg)', marginBottom: 6 }}>No sales yet</div>
          <p style={{ fontSize: 14, lineHeight: 1.5, maxWidth: 264, margin: '0 auto 20px' }}>Add products to your profile and your orders, revenue and payouts will show up here.</p>
          <button className="btn btn-primary" onClick={onManage}>Add products</button>
        </div>
      </div></div>
    );
  }
  const rnd = (i) => { let s = (seed + i * 37) * 9301 + 49297; s = ((s % 233280) + 233280) % 233280; return s / 233280; };
  const mult = range === '24H' ? 0.06 : range === '7D' ? 0.32 : range === '90D' ? 3.1 : range === '12M' ? 12.4 : range === 'All' ? 6.4 : 1;
  const sellers = products.map((p, i) => {
    const units = Math.max(1, Math.round((rnd(i) * 46 + 5) * mult));
    const unit = priceNum(p);
    return { ...p, units, unit, revenue: units * unit };
  }).sort((x, y) => y.revenue - x.revenue);
  const totalRev = sellers.reduce((s, p) => s + p.revenue, 0);
  const totalOrders = sellers.reduce((s, p) => s + p.units, 0);
  const aov = totalOrders ? totalRev / totalOrders : 0;
  const maxRev = Math.max(...sellers.map((s) => s.revenue), 1);
  const maxDay = Math.max(...a.days, 1);
  const gbp = (n) => '£' + Math.round(n).toLocaleString('en-GB');
  const conv = (3 + (seed % 4)) + '.' + (seed % 10);

  const buyers = ['@jaydn', '@sofiaa', '@miacreates', '@d.osei', '@amara.o', '@themaxlife', '@leoo', '@priya.k'];
  const statuses = [['Paid', 'paid'], ['Shipped', 'shipped'], ['Delivered', 'done'], ['Processing', 'proc']];
  const whenLbl = ['Just now', '2h ago', '5h ago', 'Yesterday', '2 days ago', '3 days ago'];
  const orders = Array.from({ length: 6 }, (_, i) => {
    const p = sellers[i % sellers.length];
    const st = statuses[Math.floor(rnd(i + 3) * statuses.length)];
    return { p, buyer: buyers[(seed + i) % buyers.length], st, when: whenLbl[i] };
  });

  const available = totalRev * 0.42;
  const connected = user.payoutsConnected !== false;
  const pimg = (p) => (typeof p.img === 'string' && p.img.indexOf('idb:') !== 0) ? p.img : null;
  const lowStock = sellers.filter((s, i) => rnd(i + 9) < 0.3).slice(0, 2);

  return (
    <div className="appscroll"><div className="screen">
      <div className="screen-top"><div className="st-logo"><h3 style={{ fontFamily: 'var(--display)', fontWeight: 800, fontSize: 22, letterSpacing: '-0.02em' }}>Sales</h3></div>
        <button className="icon-btn" onClick={onManage} title="Manage products">{MI.edit({ width: 19, height: 19 })}</button>
      </div>

      {!connected && (
        <div className="stripe-card">
          <div className="stripe-head">
            <span className="stripe-logo">{MI.dollar ? MI.dollar({ width: 18, height: 18 }) : '£'}</span>
            <div className="stripe-meta"><b>Set up payouts</b><span>Connect Stripe to take payments &amp; tips</span></div>
            <span className="stripe-badge">Not connected</span>
          </div>
          <div className="stripe-steps">
            <div className="stripe-step done">{MI.check ? MI.check({ width: 13, height: 13 }) : '✓'}<span>Create your Tappt account</span></div>
            <div className="stripe-step active">{MI.dollar ? MI.dollar({ width: 13, height: 13 }) : '2'}<span>Verify identity &amp; bank with Stripe</span></div>
            <div className="stripe-step">{MI.cart ? MI.cart({ width: 13, height: 13 }) : '3'}<span>Start getting paid</span></div>
          </div>
          <button className="stripe-cta" onClick={() => toast('Stripe onboarding opens here once the backend is wired')}>{MI.refresh ? MI.refresh({ width: 16, height: 16 }) : ''} Finish setup with Stripe</button>
          <div className="stripe-legal">{MI.lock ? MI.lock({ width: 12, height: 12 }) : ''} Stripe handles all ID &amp; bank details securely — Tappt never sees them.</div>
        </div>
      )}

      <div className="an-range">{RANGES.map((r) => <button key={r} className={range === r ? 'on' : ''} onClick={() => setRange(r)}>{r}</button>)}{!isPro && <button className="an-range-pro" onClick={onPro}>{MI.crown ? MI.crown({ width: 12, height: 12 }) : '✦'} 90D·12M</button>}</div>

      <div className="an-hero">
        <div className="lbl" style={{ display: 'flex', alignItems: 'center', gap: 6 }}>{MI.cart ? MI.cart({ width: 14, height: 14 }) : '£'} Revenue · {range}</div>
        <div className="an-big">{gbp(totalRev)}</div>
        <div className="an-delta">▲ {12 + (seed % 14)}.{seed % 9}% vs previous {range}</div>
        <div className="an-spark">{a.days.map((v, i) => <i key={i} className={v === maxDay ? 'hi' : ''} style={{ height: `${(v / maxDay) * 100}%` }} />)}</div>
      </div>

      <div className="an-mini">
        <div className="m"><div className="m-ic">{MI.cart ? MI.cart({ width: 16, height: 16 }) : '£'}</div><div className="mv">{totalOrders}</div><div className="ml">Orders</div></div>
        <div className="m"><div className="m-ic">{MI.dollar ? MI.dollar({ width: 16, height: 16 }) : '£'}</div><div className="mv">{gbp(aov)}</div><div className="ml">Avg order</div></div>
        <div className="m"><div className="m-ic">{MI.pointer({ width: 16, height: 16 })}</div><div className="mv">{conv}%</div><div className="ml">Conversion</div></div>
      </div>

      {/* payouts */}
      <div className="an-panel payout-panel">
        <h4><span className="h4-ic">{MI.dollar ? MI.dollar({ width: 15, height: 15 }) : '£'}</span>Payouts {connected && <span className="payout-chip">{MI.check ? MI.check({ width: 11, height: 11 }) : '✓'} Stripe · ····4242</span>}</h4>
        <div className="payout-row">
          <div><div className="payout-big">{gbp(available)}</div><div className="payout-sub">available · next payout {range === '7D' ? 'Fri' : 'in 3 days'}</div></div>
          <button className="btn btn-primary btn-sm" onClick={() => toast(connected ? 'Withdrawal requested' : 'Connect Stripe first')}>Withdraw</button>
        </div>
        <div className="payout-fee">
          <span className="pf-fee-l">{MI.card ? MI.card({ width: 14, height: 14 }) : ''} Platform fee</span>
          <span className="pf-fee-v">{isPro ? '4%' : '10%'} per sale{!isPro && <button className="pf-fee-up" onClick={onPro}>{MI.crown ? MI.crown({ width: 11, height: 11 }) : '✦'} Pro → 4%</button>}</span>
        </div>
      </div>

      {/* tips */}
      {user.tipsOn && (
        <div className="an-panel">
          <h4><span className="h4-ic">{MI.heart ? MI.heart({ width: 15, height: 15 }) : '♥'}</span>Tips</h4>
          <div className="an-mini" style={{ marginBottom: 0 }}>
            <div className="m"><div className="mv">{gbp(totalRev * 0.14)}</div><div className="ml">Tips · {range}</div></div>
            <div className="m"><div className="mv">{Math.max(3, Math.round(totalOrders * 0.6))}</div><div className="ml">Tippers</div></div>
            <div className="m"><div className="mv">{gbp(Math.max(3, (totalRev * 0.14) / Math.max(3, Math.round(totalOrders * 0.6))))}</div><div className="ml">Avg tip</div></div>
          </div>
        </div>
      )}

      {/* revenue over time */}
      <div className="an-panel">
        <h4><span className="h4-ic">{MI.chart({ width: 15, height: 15 })}</span>Revenue over time <span className="live">LIVE</span></h4>
        <div className="bars">{a.days.map((v, i) => <div className="bar" key={i} style={{ height: `${(v / maxDay) * 100}%` }} />)}</div>
      </div>

      {/* best sellers */}
      <div className="an-panel">
        <h4><span className="h4-ic">{MI.trophy({ width: 15, height: 15 })}</span>Best sellers</h4>
        {sellers.slice(0, 5).map((s, i) => (
          <div className="an-row" key={i}>
            <span className="ico">{pimg(s) ? <span className="seller-thumb"><img src={pimg(s)} alt="" /></span> : <span className="seller-thumb" style={{ background: s.grad || 'var(--bg-4)' }} />}</span>
            <span className="rn">{s.name || 'Product'}<span className="seller-units">{s.units} sold</span></span>
            <span className="an-bar-track"><span className="an-bar-fill" style={{ width: (s.revenue / maxRev) * 100 + '%' }} /></span>
            <span className="rv">{gbp(s.revenue)}</span>
          </div>
        ))}
      </div>

      {/* recent orders */}
      <div className="an-panel">
        <h4><span className="h4-ic">{MI.cart ? MI.cart({ width: 15, height: 15 }) : '£'}</span>Recent orders</h4>
        <div className="sale-orders">
          {orders.map((o, i) => (
            <div className="sale-order" key={i}>
              <span className="so-thumb">{pimg(o.p) ? <img src={pimg(o.p)} alt="" /> : <span style={{ width: '100%', height: '100%', display: 'block', background: o.p.grad || 'var(--bg-4)' }} />}</span>
              <div className="so-meta"><div className="so-name">{o.p.name || 'Product'}</div><div className="so-buyer">{o.buyer} · {o.when}</div></div>
              <div className="so-right"><div className="so-amt">{gbp(o.p.unit)}</div><span className={'ord-status st-' + o.st[1]}>{o.st[0]}</span></div>
            </div>
          ))}
        </div>
      </div>

      {/* low stock */}
      {lowStock.length > 0 && (
        <div className="an-panel">
          <h4><span className="h4-ic">{MI.flame ? MI.flame({ width: 15, height: 15 }) : '!'}</span>Running low</h4>
          {lowStock.map((s, i) => (
            <div className="an-row" key={i}>
              <span className="ico">{pimg(s) ? <span className="seller-thumb"><img src={pimg(s)} alt="" /></span> : <span className="seller-thumb" style={{ background: s.grad || 'var(--bg-4)' }} />}</span>
              <span className="rn">{s.name || 'Product'}</span>
              <span className="lowstock-tag">{Math.round(rnd(i + 20) * 6) + 2} left</span>
            </div>
          ))}
        </div>
      )}

      <button className="sale-manage" onClick={onManage}>{MI.edit({ width: 16, height: 16 })} Manage products &amp; layout</button>
      <div style={{ height: 16 }} />
    </div></div>
  );
}

/* ===========================================================
   MOMENTS  (Tappt's take on "Shouts")
   =========================================================== */
function MomentsScreen({ user, onPost, onEditMoment, onDeleteMoment, onSetComments, toast }) {
  const [text, setText] = mUse('');
  const [img, setImg] = mUse(null);
  const [imgKind, setImgKind] = mUse('image');
  const [notifOpen, setNotifOpen] = mUse(false);
  const [seenBump, setSeenBump] = mUse(0);
  const unread = (window.unreadNotifCount ? window.unreadNotifCount(user) : 0) + seenBump * 0;
  const [focusMoment, setFocusMoment] = mUse(null);
  const [composeOpen, setComposeOpen] = mUse(false);
  const fileRef = React.useRef(null);
  const camRef = React.useRef(null);
  const mine = user.moments || [];
  const MomentCard = window.MomentCard;

  const openMoment = (momentId) => {
    setFocusMoment(momentId);
    setTimeout(() => {
      const el = document.querySelector('[data-moment-id="' + momentId + '"]');
      const scroller = el && el.closest('.appscroll');
      if (el && scroller) scroller.scrollTop = Math.max(0, el.offsetTop - 70);
    }, 80);
  };

  const pickMedia = (e) => {
    const f = e.target.files && e.target.files[0];
    if (!f) return;
    const kind = f.type.startsWith('video') ? 'video' : 'image';
    const r = new FileReader();
    r.onload = () => { setImg(r.result); setImgKind(kind); };
    r.readAsDataURL(f);
    e.target.value = '';
  };
  const post = () => {
    if (!text.trim() && !img) return;
    const m = { id: (window.newMomentId ? window.newMomentId() : 'm' + Date.now()), cap: text.trim(), img: img, imgKind: imgKind, ts: Date.now(), taps: 0 };
    onPost && onPost(m);
    setText(''); setImg(null); setImgKind('image');
    toast && toast('Moment posted');
  };

  // demo-only community feed — real accounts don't see fake posts
  const community = user.handle === 'maxxharland' ? [
    { who: 'Tappt', cap: 'The Joker Edition just dropped — 500 pcs worldwide.', grad: 'linear-gradient(135deg,#8AFF6B,#1a3a1a)', taps: '2.1k', when: '2h' },
    { who: 'Sofiaa', cap: 'Tapped my way through 40 intros at the summit. Zero paper.', grad: 'linear-gradient(135deg,#f58529,#dd2a7b)', taps: '840', when: '5h' },
  ] : [];

  return (
    <div className="appscroll"><div className="screen">
      <div className="screen-top"><div className="st-logo"><h3 style={{ fontFamily: 'var(--display)', fontWeight: 800, fontSize: 22, letterSpacing: '-0.02em' }}>Moments</h3></div><button className="icon-btn notif-bell" onClick={() => setNotifOpen(true)} aria-label="Notifications">{MI.bell({ width: 20, height: 20 })}{unread > 0 && <span className="notif-badge">{unread > 9 ? '9+' : unread}</span>}</button></div>
      {notifOpen && window.NotificationsSheet && <window.NotificationsSheet user={user} toast={toast} onOpenMoment={(id) => openMoment(id)} onClose={() => { setNotifOpen(false); setSeenBump((n) => n + 1); }} />}

      {window.DashStrip && <window.DashStrip user={user} toast={toast} top />}

      {/* your own posted moments */}
      {mine.length > 0 && <div className="mc-sectlabel">Your Moments</div>}
      {mine.map((m, i) => (
        <MomentCard key={m.id} user={user} moment={m} owner viewer={user}
          autoOpen={focusMoment === m.id} bfDelay={Math.min(i, 5) * 0.06}
          onEdit={(patch) => onEditMoment && onEditMoment(m.id, patch)}
          onDelete={() => onDeleteMoment && onDeleteMoment(m.id)}
          onComments={(arr) => onSetComments && onSetComments(m.id, arr)}
          toast={toast} />
      ))}

      {community.length > 0 && <div className="mc-sectlabel">From the community</div>}
      {community.map((p, i) => (
        <MomentCard key={'c' + i} user={{ name: p.who, handle: p.who.toLowerCase(), avatarColor: p.grad, pro: true }}
          moment={{ id: 'comm' + i, cap: p.cap, ts: Date.now() - (i + 1) * 7200000, taps: 0, comments: [] }}
          owner={false} viewer={user} toast={toast} bfDelay={Math.min(i + mine.length, 6) * 0.06} />
      ))}
      {composeOpen && (
        <div className="sheet-bg exchange-bg" onClick={(e) => { if (e.target === e.currentTarget) setComposeOpen(false); }}>
          <div className="exchange-sheet new-moment-sheet">
            <div className="nm-head"><h2 className="ex-h">New Moment</h2></div>
            <div className="nm-body">
              {window.MomentComposer && <window.MomentComposer user={user} onPost={(m) => { onPost && onPost(m); setComposeOpen(false); }} toast={toast} />}
              <button className="ex-notnow" onClick={() => setComposeOpen(false)}>Cancel</button>
            </div>
          </div>
        </div>
      )}
      <button className="moments-fab" onClick={() => setComposeOpen(true)} aria-label="New moment">{MI.plus({ width: 26, height: 26 })}</button>
    </div></div>
  );
}
function SearchScreen() {
  return (
    <div className="appscroll"><div className="screen">
      <div className="screen-top"><div className="st-logo"><h3 style={{ fontFamily: 'var(--display)', fontWeight: 800, fontSize: 22 }}>Discover</h3></div></div>
      <div className="search-bar" style={{ marginBottom: 22 }}>{MI.search({ width: 17, height: 17 })}<input placeholder="Search creators, drops…" /></div>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
        {MC.slice(0, 4).map((c) => (
          <div key={c.id} style={{ borderRadius: 16, overflow: 'hidden', border: '1px solid var(--line)', background: 'var(--bg-2)' }}>
            <div style={{ aspectRatio: '1.586/1', background: '#000' }}><img src={c.img} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover' }} /></div>
            <div style={{ padding: '10px 12px', fontWeight: 700, fontSize: 13 }}>{c.name}</div>
          </div>
        ))}
      </div>
    </div></div>
  );
}
function CreateScreen() {
  const plats = ['youtube', 'instagram', 'tiktok', 'x'];
  return (
    <div className="appscroll"><div className="screen">
      <div className="screen-h">Post To All</div><div className="screen-sub">One click, post to every platform.</div>
      <div className="post-drop">{MI.plus({ width: 40, height: 40 })}<span>Select a video</span></div>
      <input className="input" placeholder="Add a caption…" style={{ marginBottom: 20 }} />
      <div className="post-platforms">
        {plats.map((id) => { const p = mplat(id); return (
          <div className="post-row" key={id}><MBrand id={id} size={34} radius={9} /><div className="pmeta"><div className="pn">{p.label}</div><div className="ps">No account connected</div></div><span className="switch" /></div>
        ); })}
      </div>
      <button className="btn btn-primary btn-block" style={{ marginTop: 22 }}>{MI.arrow({ width: 17, height: 17 })} Post to all</button>
    </div></div>
  );
}

/* ===========================================================
   DESKTOP SHELL — left sidebar + live phone preview (LinkMe-style).
   Mounts only at >=1080px; mobile is left completely untouched.
   =========================================================== */
function useDesktop() {
  const q = '(min-width: 1080px)';
  const [d, setD] = React.useState(() => typeof window !== 'undefined' && window.matchMedia && window.matchMedia(q).matches);
  React.useEffect(() => {
    if (!window.matchMedia) return;
    const mq = window.matchMedia(q);
    const on = () => setD(mq.matches);
    on();
    mq.addEventListener ? mq.addEventListener('change', on) : mq.addListener(on);
    return () => { mq.removeEventListener ? mq.removeEventListener('change', on) : mq.removeListener(on); };
  }, []);
  return d;
}

const DESK_NAV = ['profile', 'moments', 'connections', 'analytics', 'sales', 'earnings'];

function DeskSidebar({ tab, setTab, user, editing, onCreate, onMenu, onShare }) {
  const persona = (window.activePersona ? window.activePersona(user) : null) || {};
  return (
    <aside className="desk-side">
      <div className="desk-brand">
        <img className="db-logo dark-logo" src="assets/logo-white.png" alt="Tappt" />
        <img className="db-logo light-logo" src="assets/logo-black.png" alt="Tappt" />
      </div>
      <button className="desk-create" onClick={onCreate}>{MI.plus({ width: 19, height: 19 })}<span>Create</span></button>
      <nav className="desk-nav">
        {DESK_NAV.map((id) => {
          const it = NAV_POOL[id];
          const on = tab === id && !editing;
          return (
            <button key={id} className={'desk-nav-item' + (on ? ' on' : '')} onClick={() => setTab(id)}>
              <span className="dni-ic">{it.avatar ? <span className="dni-av"><Avatar user={user} size={24} coverFallback fill /></span> : it.icon({ width: 21, height: 21 })}</span>
              <span className="dni-l">{it.label}</span>
            </button>
          );
        })}
      </nav>
      <div className="desk-side-foot">
        <button className="desk-foot-btn" onClick={onShare}>{(MI.share || MI.qr)({ width: 18, height: 18 })}<span>Share profile</span></button>
        <button className="desk-user" onClick={onMenu}>
          <Avatar user={user} size={36} coverFallback />
          <span className="du-meta"><b>{user.name || 'You'}</b><i>@{user.handle}</i></span>
          {MI.settings ? MI.settings({ width: 17, height: 17 }) : '⚙'}
        </button>
      </div>
    </aside>
  );
}

function DeskPreview({ user, onShare }) {
  return (
    <aside className="desk-preview">
      <div className="dpv-head">
        <span className="dpv-live"><i></i> Live profile</span>
        <button className="dpv-url" onClick={onShare} title="Share your profile">tappt.io/<b>{user.handle}</b></button>
      </div>
      <div className="dpv-phone">
        <div className="dpv-screen">
          <OwnProfile user={user} pub viewer={user} toast={() => {}} />
        </div>
      </div>
    </aside>
  );
}

/* ===========================================================
   APP SHELL
   =========================================================== */
function Dashboard({ user, setUser, onLogout, themeMode, setThemeMode, toast }) {
  const [tab, setTab] = mUse('profile');
  const desktop = useDesktop();
  const [sheet, setSheet] = mUse(null);   // 'share' | 'pro' | 'qr' | 'persona'
  const [editing, setEditing] = mUse(false);
  const [menu, setMenu] = mUse(false);
  const [radial, setRadial] = mUse(false);
  const [designOpen, setDesignOpen] = mUse(false);
  const [designDraft, setDesignDraft] = mUse(null);
  const openDesign = () => { setEditing(false); setTab('profile'); setDesignDraft({ profileTheme: user.profileTheme || 'default', linkAnim: user.linkAnim || 'none', iconAnim: user.iconAnim || 'none', headlineFont: user.headlineFont || 'display', avatarRing: !!user.avatarRing, avatarColor: user.avatarColor, buttonStyle: user.buttonStyle || 'solid', buttonRadius: user.buttonRadius || 'md', wallpaper: user.wallpaper || '', cardEffect: user.cardEffect || 'none' }); setDesignOpen(true); };
  const [rating, setRating] = mUse(false);
  const update = (patch) => { const next = { ...user, ...patch }; setUser(next); const ok = MStore.saveUser(next); if (ok === false) toast && toast('Saved — video cover too big to keep after reload'); };
  const XEdit = window.EditProfile;
  // keep a live ref so delayed (simulated) updates don't use stale state
  const userRef = React.useRef(user); userRef.current = user;
  // iOS/Android native: trust the store's entitlement as the source of truth for Pro
  React.useEffect(() => {
    if (!(window.isAppleIAP && window.isAppleIAP()) || !window.TapptIAP || !window.TapptIAP.available()) return;
    window.TapptIAP.isPro(userRef.current && userRef.current.id).then((pro) => {
      if (pro === true && !userRef.current.pro) { const next = { ...userRef.current, pro: true }; setUser(next); MStore.saveUser(next); }
    }).catch(() => {});
  }, []);
  const patchMoment = (id, fn) => { const u = userRef.current; const next = { ...u, moments: (u.moments || []).map((m) => m.id === id ? fn(m) : m) }; userRef.current = next; setUser(next); MStore.saveUser(next); const nm = next.moments.find((m) => m.id === id); if (nm) MStore.persistMoment(nm); };
  // when you post, a little community engagement rolls in so notifications light up (demo)
  const simulateEngagement = (id) => {
    setTimeout(() => patchMoment(id, (m) => ({ ...m, taps: (m.taps || 0) + 1 + Math.floor(Math.random() * 4), likedTs: Date.now() })), 2200);
    setTimeout(() => patchMoment(id, (m) => ({ ...m, taps: (m.taps || 0) + 6 + Math.floor(Math.random() * 30), likedTs: Date.now() })), 5200);
    setTimeout(() => patchMoment(id, (m) => ({ ...m, comments: [...(m.comments || []), { id: 'sc' + Date.now(), name: 'Sofia Marchetti', handle: 'sofiaa', avatarColor: 'linear-gradient(135deg,#f58529,#dd2a7b)', text: 'This is sick 🔥', ts: Date.now(), likes: 0, liked: false, replies: [] }] })), 7800);
  };
  const mEditMoment = (id, patch) => { const moments = (user.moments || []).map((m) => m.id === id ? { ...m, ...patch } : m); update({ moments }); const nm = moments.find((m) => m.id === id); if (nm) MStore.persistMoment(nm); };
  const mDeleteMoment = (id) => { update({ moments: (user.moments || []).filter((m) => m.id !== id) }); MStore.deleteMoment(id); };
  const mSetComments = (id, arr) => { update({ moments: (user.moments || []).map((m) => m.id === id ? { ...m, comments: arr } : m) }); MStore.syncMomentComments(id, arr); };
  const mPostMoment = (m) => { update({ moments: [m, ...(user.moments || [])] }); MStore.createMoment(m); if (user.handle === 'maxxharland') simulateEngagement(m.id); };

  const showPreview = desktop && !(tab === 'profile' && !editing && !designOpen);
  return (
    <div className={'appwrap' + (desktop ? ' is-desktop' : '') + (showPreview ? ' has-preview' : '')}>
      {desktop && <DeskSidebar tab={tab} setTab={(t) => { setTab(t); setEditing(false); }} user={user} editing={editing} onCreate={() => setRadial(true)} onMenu={() => setMenu(true)} onShare={() => setSheet('share')} />}
      <div className="appframe" data-tab={editing ? 'edit' : tab} data-screen-label={'App · ' + (editing ? 'edit profile' : tab)}>
        {tab === 'profile'   && !editing && <OwnProfile user={designOpen && designDraft ? { ...user, ...designDraft } : user} onEdit={() => setEditing(true)} onDesign={openDesign} onShare={() => setSheet('share')} onMenu={() => setMenu(true)} onMoments={() => setTab('moments')} onPersona={() => setSheet('persona')} toast={toast} onEditMoment={mEditMoment} onDeleteMoment={mDeleteMoment} onSetComments={mSetComments} onPostMoment={mPostMoment} onLead={(lead) => { const conn = window.leadToConn ? window.leadToConn(lead) : lead; update({ connections: [conn, ...(user.connections || [])] }); MStore.saveConn && MStore.saveConn(conn); toast('New connection'); }} onTip={(tip) => { const t = { ...tip, id: (window.newId ? window.newId() : (tip.id || 'tip' + Date.now())) }; update({ tips: [t, ...(user.tips || [])] }); MStore.saveTip && MStore.saveTip(t); toast('Tip received · £' + t.amount + ' 🎉'); }} />}
        {tab === 'profile'   && editing && <XEdit user={user} openPro={() => setSheet('pro')} onCancel={() => setEditing(false)} onSave={(d) => { update(mSync({ ...user, ...d })); setEditing(false); toast('Profile saved'); }} toast={toast} />}
        {tab === 'moments'   && <MomentsScreen user={user} onPost={mPostMoment} onEditMoment={mEditMoment} onDeleteMoment={mDeleteMoment} onSetComments={mSetComments} toast={toast} />}
        {tab === 'search'    && <SearchScreen />}
        {tab === 'create'    && <CreateScreen />}
        {tab === 'analytics' && <AnalyticsScreen user={user} onEarnings={() => setTab('earnings')} onPro={() => setSheet('pro')} />}
        {tab === 'earnings'  && <EarningsScreen user={user} toast={toast} onPro={() => setSheet('pro')} />}
        {tab === 'sales'     && <SalesScreen user={user} toast={toast} onPro={() => setSheet('pro')} onManage={() => { setTab('profile'); setEditing(true); toast('Manage your products in the Shop section'); }} />}
        {tab === 'connections' && <MConn user={user} toast={toast} setUser={setUser} onPro={() => setSheet('pro')} />}

        {!editing && !designOpen && !desktop && <GlassNav tab={tab} setTab={setTab} user={user} onCreate={() => setRadial(true)} />}
        {window.UploadOverlay && <window.UploadOverlay />}

        {designOpen && designDraft && (
          <DesignPanel draft={designDraft} isPro={!!user.pro}
            onChange={(patch) => setDesignDraft((d) => ({ ...d, ...patch }))}
            onPro={() => { setDesignOpen(false); setSheet('pro'); }}
            onApply={() => { update(designDraft); setDesignOpen(false); toast('Design applied'); }}
            onCancel={() => setDesignOpen(false)} />
        )}

        {radial && (
          <RadialMenu user={user} onClose={() => setRadial(false)} onPick={(id) => {
            setRadial(false);
            if (id === 'post') setTab('create');
            else if (id === 'moment') setTab('moments');
            else if (id === 'qr') setSheet('qr');
            else if (id === 'sales') setTab('sales');
            else if (id === 'earnings') setTab('earnings');
            else if (id === 'analytics') setTab('analytics');
            else if (id === 'connections') setTab('connections');
            else if (id === 'product' || id === 'link') { setTab('profile'); setEditing(true); toast(id === 'product' ? 'Add a product in your Shop section' : 'Add a link in Featured'); }
          }} />
        )}

        {menu && (
          <ProfileMenu user={user} themeMode={themeMode} setThemeMode={setThemeMode}
            onClose={() => setMenu(false)}
            onShare={(which) => { setMenu(false); setSheet(which); }}
            onAnalytics={() => setTab('analytics')}
            onDesign={() => { setMenu(false); openDesign(); }}
            onEarnings={() => setTab('earnings')}
            onSales={() => setTab('sales')}
            onConnections={() => setTab('connections')}
            onPersona={() => setSheet('persona')}
            onCards={() => setSheet('mycards')}
            onCEO={() => setSheet('ceo')}
            onNav={() => setSheet('navcustomize')}
            onPro={() => { setMenu(false); setSheet('pro'); }}
            onRate={() => { setMenu(false); setRating(true); }}
            onLogout={onLogout} toast={toast} />
        )}

        {rating && <RatingPrompt onClose={() => setRating(false)} toast={toast} />}

        {sheet === 'share' && <MShare user={user} onClose={() => setSheet(null)} onSave={(p) => update(p)} toast={toast} />}
        {sheet === 'qr'    && <QRSheet user={user} onClose={() => setSheet(null)} toast={toast} />}
        {sheet === 'pro'   && <MPro user={user} onClose={() => setSheet(null)} onUpgrade={(plan) => { if (window.isAppleIAP && window.isAppleIAP()) { if (!window.TapptIAP || !window.TapptIAP.available()) { toast && toast('Purchases unavailable here'); return; } toast && toast('Opening App Store…'); window.TapptIAP.buyPro(plan, user.id).then((ok) => { if (ok) { update({ pro: true }); setSheet(null); toast && toast('Welcome to Pro ✦'); } }).catch(() => { toast && toast("Purchase didn't complete"); }); return; } var base = (plan === 'monthly') ? 'https://buy.stripe.com/9B600l7V78Sc7kc7tObII02' : 'https://buy.stripe.com/dRm6oJejvc4odIAbK4bII03'; var qs = []; if (user.id) qs.push('client_reference_id=' + encodeURIComponent(user.id)); if (user.email) qs.push('prefilled_email=' + encodeURIComponent(user.email)); var url = base + (qs.length ? '?' + qs.join('&') : ''); try { window.open(url, '_blank'); } catch (e) {} toast && toast('Opening secure checkout…'); }} onRestore={() => { if (!window.TapptIAP || !window.TapptIAP.available()) { toast && toast('Nothing to restore here'); return; } toast && toast('Restoring…'); window.TapptIAP.restore(user.id).then((ok) => { if (ok) { update({ pro: true }); setSheet(null); toast && toast('Pro restored ✦'); } else toast && toast('No purchases to restore'); }).catch(() => toast && toast('Restore failed')); }} onCancel={() => { update({ pro: false }); toast('Pro cancelled'); }} toast={toast} />}
        {sheet === 'mycards' && window.CardsScreen && <window.CardsScreen user={user} setUser={setUser} toast={toast} onClose={() => setSheet(null)} />}
        {sheet === 'ceo' && window.CEOConsole && <window.CEOConsole user={user} toast={toast} onClose={() => setSheet(null)} />}
        {sheet === 'navcustomize' && window.NavCustomizeSheet && <window.NavCustomizeSheet user={user} onClose={() => setSheet(null)} toast={toast} onSave={({ bar, radial, pid }) => { update({ navByPersona: Object.assign({}, user.navByPersona || {}, { [pid]: { bar, radial } }) }); }} />}
        {sheet === 'persona' && <MPersona user={user}
          onClose={() => setSheet(null)}
          onSwitch={(id) => { update(mApply(mSync(user), id)); setSheet(null); }}
          onNew={(preset) => { const id = preset.label.toLowerCase().replace(/[^a-z0-9]/g, '') + Date.now().toString().slice(-4); const base = mActive(user); const synced = mSync(user); const np = { id, label: preset.label, icon: preset.icon || 'star', accent: base.accent, bio: base.bio, socials: [...(base.socials || [])], links: (base.links || []).map((l) => ({ ...l, id: l.id + '_' + id })), saveContact: base.saveContact }; update(mApply({ ...synced, personas: [...mGetPersonas(synced), np] }, id)); }}
          onEditPersona={(id) => { update(mApply(mSync(user), id)); setSheet(null); setEditing(true); }}
          onPro={() => setSheet('pro')}
          toast={toast} />}
      </div>
      {showPreview && <DeskPreview user={user} onShare={() => setSheet('share')} />}
    </div>
  );
}

/* ===========================================================
   APP-ICON TILE + RATING PROMPT  ("Are you enjoying Tappt?")
   The AppIconTile is the placeholder for the real app icon —
   swap the inner glyph for the final icon art when ready.
   =========================================================== */
function AppIconTile({ size = 96 }) {
  return (
    <img className="app-icon-img" src="assets/icons/icon-mark-bw.png" alt="Tappt"
      style={{ width: size, height: size, borderRadius: size * 0.225, display: 'block' }} />
  );
}
function RatingPrompt({ onClose, toast }) {
  const [stars, setStars] = mUse(0);
  const [hover, setHover] = mUse(0);
  const yes = () => { onClose(); setTimeout(() => toast && toast('Thanks! Opening the App Store…'), 120); };
  const no = () => { onClose(); setTimeout(() => toast && toast('Thanks — we\'ll keep improving'), 120); };
  return (
    <div className="sheet-bg" onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}>
      <div className="sheet rating-sheet">
        <div className="sheet-grab" />
        <div className="rating-body">
          <div className="rating-icon-wrap"><AppIconTile size={96} /></div>
          <h2 className="rating-h">Are you<br />enjoying Tappt?</h2>
          <p className="rating-p">We'd love to know if you're happy with your experience on Tappt.</p>
          <div className="rating-stars">
            {[1, 2, 3, 4, 5].map((n) => (
              <button key={n} className={'rating-star' + ((hover || stars) >= n ? ' on' : '')}
                onMouseEnter={() => setHover(n)} onMouseLeave={() => setHover(0)}
                onClick={() => { setStars(n); }}>
                {MI.star({ width: 30, height: 30 })}
              </button>
            ))}
          </div>
          <button className="btn btn-primary btn-block rating-yes" onClick={stars >= 4 || stars === 0 ? yes : no}>
            {stars > 0 && stars < 4 ? 'Send feedback' : 'Yes'}
          </button>
          <button className="rating-no" onClick={no}>No</button>
        </div>
      </div>
    </div>
  );
}

window.Dashboard = Dashboard;
window.renderLink = renderLink;
window.renderLinks = renderLinks;
window.momentWhen = momentWhen;
window.playMuted = playMuted;
window.MAV = MAV;
window.renderSection = renderSection;
window.OwnProfile = OwnProfile;
window.MomentsRow = MomentsRow;
window.Avatar = Avatar;
window.GalleryGrid = GalleryGrid;
window.MerchRail = MerchRail;
window.ProductCard = ProductCard;
window.saveContact = saveContact;
