/* =========================================================
   Tappt — INLINE edit mode (edits live on the profile page,
   not a popup). Sticky Cancel / Save bar, Change Cover sheet,
   inline placeholder tiles + add-link forms.
   ========================================================= */
const {
  platById: xplat, BrandIcon: XBrand, AVATAR_COLORS: XAV, I: XI,
  DEFAULT_SECTIONS: XSECT, renderLink: xRenderLink, AddLinkSheet: XAddLink,
  activePersona: xActive, EmbedEditor,
} = window;
const { useState: xUse, useRef: xRef, useEffect: xEffect } = React;

/* detect a platform id from a pasted URL (for the corner brand icon) */
function detectPlatform(url) {
  const u = (url || '').toLowerCase();
  const map = [['instagram', 'instagram'], ['tiktok', 'tiktok'], ['youtu', 'youtube'],
    ['x.com', 'x'], ['twitter', 'x'], ['spotify', 'spotify'], ['linkedin', 'linkedin'],
    ['facebook', 'facebook'], ['fb.com', 'facebook'], ['snapchat', 'snapchat'], ['twitch', 'twitch'],
    ['discord', 'discord'], ['t.me', 'telegram'], ['wa.me', 'whatsapp'], ['whatsapp', 'whatsapp'],
    ['paypal', 'paypal'], ['cash.app', 'cashapp'], ['venmo', 'venmo'], ['soundcloud', 'soundcloud'],
    ['pinterest', 'pinterest'], ['threads', 'threads'], ['calendly', 'calendly']];
  for (const [needle, id] of map) if (u.includes(needle)) return id;
  return 'website';
}

/* adult / NSFW domains banned from Tappt links */
const ADULT_DOMAINS = ['onlyfans', 'fansly', 'fancentro', 'manyvids', 'justfor.fans', 'justforfans',
  'adultwork', 'pornhub', 'xvideos', 'xnxx', 'xhamster', 'redtube', 'youporn', 'spankbang',
  'brazzers', 'chaturbate', 'stripchat', 'bongacams', 'cam4', 'myfreecams', 'livejasmin',
  'clips4sale', 'iwantclips', 'loyalfans', 'fanvue', 'admireme', 'ismyguy', 'unlocked.app',
  'avn.', 'redgifs', 'rule34', 'e-hentai', 'nhentai', 'fapello', 'erome'];
function isAdultUrl(url) {
  const u = (url || '').toLowerCase().replace(/^https?:\/\//, '');
  if (ADULT_DOMAINS.some((d) => u.includes(d))) return true;
  // loose keyword guard on the host portion only (before first slash)
  const host = u.split('/')[0];
  return /\b(porn|xxx|nsfw|escort|hookup|camgirl)\b/.test(host);
}

/* read an image/video file → dataURL. Images are downscaled+compressed via
   canvas so big photos don't blow the localStorage quota (the reason saving
   a cover photo / profile pic was silently failing). Video passes through. */
function readMedia(file, cb) {
  const kind = file.type.startsWith('video') ? 'video' : 'image';
  const big = file.size > 1.2 * 1024 * 1024;   // only show overlay for sizeable files
  const emit = (pct, done) => { try { window.dispatchEvent(new CustomEvent('tappt-upload', { detail: { pct, done, kind } })); } catch (e) {} };
  // after producing a local data URL, upload to Supabase Storage (if signed in) and
  // re-emit the public URL so it persists cross-device. Falls back to the data URL.
  const cloud = (dataUrl, k) => {
    try {
      if (window.TapptDB && window.TapptDB.available()) {
        window.TapptDB.uploadMedia(dataUrl, k === 'video' ? 'mp4' : 'jpg')
          .then((url) => { if (url) cb(url, k); }).catch(() => {});
      }
    } catch (e) {}
  };
  if (big) emit(1, false);
  if (kind === 'video') {
    const r = new FileReader();
    r.onprogress = (e) => { if (big && e.lengthComputable) emit(Math.max(1, Math.round((e.loaded / e.total) * 100)), false); };
    r.onload = () => { emit(100, true); cb(r.result, kind); cloud(r.result, kind); };
    r.onerror = () => { emit(100, true); };
    r.readAsDataURL(file);
    return;
  }
  const r = new FileReader();
  r.onprogress = (e) => { if (big && e.lengthComputable) emit(Math.max(1, Math.round((e.loaded / e.total) * 80)), false); };
  r.onload = () => {
    const img = new Image();
    img.onload = () => {
      if (big) emit(92, false);
      const MAX = 1280;
      let { width: w, height: h } = img;
      if (w > MAX || h > MAX) { const s = MAX / Math.max(w, h); w = Math.round(w * s); h = Math.round(h * s); }
      const c = document.createElement('canvas'); c.width = w; c.height = h;
      c.getContext('2d').drawImage(img, 0, 0, w, h);
      let out;
      try { out = c.toDataURL('image/jpeg', 0.82); } catch (e) { out = r.result; }
      emit(100, true); cb(out, 'image'); cloud(out, 'image');
    };
    img.onerror = () => { emit(100, true); cb(r.result, 'image'); cloud(r.result, 'image'); };
    img.src = r.result;
  };
  r.readAsDataURL(file);
}
window.readMedia = readMedia;

/* action-button catalogue — white glyphs on solid brand tiles (consistent in light+dark) */
const Aic = {
  save: <svg viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H7a4 4 0 0 0-4 4v2"/><circle cx="9.5" cy="8" r="3.5"/><path d="M19 8v6M22 11h-6"/></svg>,
  phone: <svg viewBox="0 0 24 24" fill="#fff"><path d="M6.6 10.8a15.5 15.5 0 0 0 6.6 6.6l2.2-2.2c.3-.3.7-.4 1-.2 1.1.4 2.3.6 3.6.6.6 0 1 .4 1 1V20c0 .6-.4 1-1 1A17 17 0 0 1 3 4c0-.6.5-1 1-1h3.5c.6 0 1 .4 1 1 0 1.2.2 2.4.6 3.6.1.4 0 .8-.3 1z"/></svg>,
  email: <svg viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="5" width="18" height="14" rx="2.5"/><path d="M4 7l8 6 8-6"/></svg>,
  whatsapp: <svg viewBox="0 0 24 24" fill="#fff"><path d="M19.05 4.91A9.82 9.82 0 0 0 12.04 2C6.58 2 2.13 6.45 2.13 11.91c0 1.75.46 3.45 1.32 4.95L2.05 22l5.25-1.38a9.9 9.9 0 0 0 4.74 1.21h.004c5.46 0 9.91-4.45 9.91-9.91 0-2.65-1.03-5.14-2.9-7.01zM12.04 20.15h-.004a8.2 8.2 0 0 1-4.18-1.15l-.3-.18-3.11.82.83-3.04-.2-.31a8.2 8.2 0 0 1-1.26-4.38c0-4.54 3.7-8.23 8.24-8.23 2.2 0 4.27.86 5.83 2.42a8.18 8.18 0 0 1 2.41 5.82c0 4.54-3.69 8.24-8.23 8.24zm4.52-6.16c-.25-.12-1.47-.72-1.69-.81-.23-.08-.39-.12-.56.12-.17.25-.64.81-.79.97-.14.17-.29.19-.54.06-.25-.12-1.05-.39-1.99-1.23-.74-.66-1.23-1.47-1.38-1.72-.14-.25-.02-.38.11-.5.11-.11.25-.29.37-.43.12-.14.17-.25.25-.41.08-.17.04-.31-.02-.43-.06-.12-.56-1.34-.76-1.84-.2-.48-.41-.42-.56-.43l-.48-.01c-.17 0-.43.06-.66.31-.23.25-.86.85-.86 2.07 0 1.22.89 2.4 1.01 2.56.12.17 1.75 2.67 4.23 3.74.59.26 1.05.41 1.41.52.59.19 1.13.16 1.56.1.48-.07 1.47-.6 1.68-1.18.21-.58.21-1.07.14-1.18-.06-.11-.22-.17-.47-.29z"/></svg>,
  share: <svg viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>,
  qr: <svg viewBox="0 0 24 24" fill="none" stroke="#fff" 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>,
  subscribe: <svg viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M11.562 3.266a.5.5 0 0 1 .876 0L15.39 8.87a1 1 0 0 0 1.516.294L21.183 5.5a.5.5 0 0 1 .798.519l-2.834 10.246a1 1 0 0 1-.956.734H5.81a1 1 0 0 1-.957-.734L2.02 6.02a.5.5 0 0 1 .798-.519l4.276 3.664a1 1 0 0 0 1.516-.294z"/><path d="M5 21h14"/></svg>,
  gift: <svg viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="8" width="18" height="4" rx="1"/><path d="M12 8v13M19 12v7a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-7M7.5 8a2.5 2.5 0 0 1 0-5C11 3 12 8 12 8s1-5 4.5-5a2.5 2.5 0 0 1 0 5"/></svg>,
};
const ACTIONS = {
  save:      { label: 'Save Contact', c: '#3A3A42', icon: Aic.save, desc: 'Saves your number & email to their phone' },
  phone:     { label: 'Call', c: '#34C759', icon: Aic.phone, desc: 'One tap to call you', needs: 'tel', ph: '+44 7700 900000', field: 'Phone number' },
  email:     { label: 'Email', c: '#1D6FE0', icon: Aic.email, desc: 'Open a new email to you', needs: 'email', ph: 'you@email.com', field: 'Email address' },
  whatsapp:  { label: 'WhatsApp', c: '#25D366', icon: Aic.whatsapp, desc: 'Message you on WhatsApp', needs: 'tel', ph: '+44 7700 900000', field: 'WhatsApp number' },
  share:     { label: 'Share', c: '#6B8299', icon: Aic.share, desc: 'Share your profile' },
  qr:        { label: 'QR Code', c: '#0B0B0C', icon: Aic.qr, desc: 'Show a scannable QR of your profile' },
  subscribe: { label: 'Subscribe', c: 'linear-gradient(135deg,#E8C75A,#b8902a)', icon: Aic.subscribe, desc: 'Paid subscription / VIP (Pro)' },
  tip:       { label: 'Send a Tip', c: '#FF6BC8', icon: (Aic.gift || Aic.heart), desc: 'Opens your tip jar — set it up in Tips' },
};
window.ACTIONS = ACTIONS;

/* ===========================================================
   ACTION PICKER  (choose buttons; prompts for input when needed)
   =========================================================== */
function ActionPicker({ active, data, onToggle, onSetData, onReorder, onClose }) {
  const CATALOGUE = ['save', 'phone', 'email', 'whatsapp', 'tip', 'share', 'qr', 'subscribe'];
  const [editing, setEditing] = xUse(null);   // id awaiting input
  const [val, setVal] = xUse('');
  // order shown in the sheet — active ones keep their saved order, then the rest
  const [order, setOrder] = xUse(() => {
    const act = (active || []).filter((id) => CATALOGUE.includes(id));
    return [...act, ...CATALOGUE.filter((id) => !act.includes(id))];
  });
  const [dragId, setDragId] = xUse(null);
  const [overId, setOverId] = xUse(null);
  const [capWarn, setCapWarn] = xUse(false);
  const MAX_ACTIONS = 4;
  const atCap = (active || []).length >= MAX_ACTIONS;
  const dragActive = xRef(false);
  const dragIdRef = xRef(null);
  const overIdRef = xRef(null);
  const orderRef = xRef(order);
  orderRef.current = order;

  const startDrag = (e, id) => {
    if (dragActive.current) return;
    dragActive.current = true;
    e.preventDefault(); e.stopPropagation();
    dragIdRef.current = id; overIdRef.current = null;
    setDragId(id);
    const move = (ev) => {
      const pt = ev.touches ? ev.touches[0] : ev;
      if (!pt) return;
      const el = document.elementFromPoint(pt.clientX, pt.clientY);
      const row = el && el.closest ? el.closest('[data-actid]') : null;
      const oid = row ? row.getAttribute('data-actid') : null;
      overIdRef.current = oid; setOverId(oid);
    };
    const up = () => {
      dragActive.current = false;
      document.removeEventListener('pointermove', move); document.removeEventListener('pointerup', up);
      document.removeEventListener('touchmove', move); document.removeEventListener('touchend', up);
      const cur = dragIdRef.current, ov = overIdRef.current;
      setDragId(null); setOverId(null);
      dragIdRef.current = null; overIdRef.current = null;
      if (cur && ov && cur !== ov) {
        const list = orderRef.current;
        const from = list.indexOf(cur), to = list.indexOf(ov);
        if (from < 0 || to < 0) return;
        const next = [...list]; const [m] = next.splice(from, 1); next.splice(to, 0, m);
        setOrder(next);
        if (onReorder) onReorder(next.filter((id) => (active || []).includes(id)));
      }
    };
    document.addEventListener('pointermove', move); document.addEventListener('pointerup', up);
    document.addEventListener('touchmove', move, { passive: false }); document.addEventListener('touchend', up);
  };

  if (editing) {
    const m = ACTIONS[editing];
    return (
      <div className="sheet-bg" onClick={(e) => { if (e.target === e.currentTarget) setEditing(null); }}>
        <div className="sheet">
          <div className="sheet-grab" />
          <div className="sheet-head">
            <button className="glass-pill" style={{ background: 'var(--bg-3)', color: 'var(--fg)', border: '1px solid var(--line)' }} onClick={() => setEditing(null)}>{XI.back({ width: 15, height: 15 })} Back</button>
            <h3>{m.label}</h3><span style={{ width: 56 }} />
          </div>
          <div className="sheet-scroll">
            <div style={{ display: 'flex', justifyContent: 'center', margin: '6px 0 18px' }}>
              <span className="ao-ic" style={{ width: 64, height: 64, borderRadius: 18, background: m.c }}>{m.icon}</span>
            </div>
            <div className="field"><label>{m.field}</label>
              <input className="input" type={m.needs === 'email' ? 'email' : 'tel'} value={val} placeholder={m.ph} onChange={(e) => setVal(e.target.value)} /></div>
            <p style={{ fontSize: 12.5, color: 'var(--fg-3)', margin: '-4px 2px 18px' }}>Visitors tap “{m.label}” and it {editing === 'email' ? 'opens an email to you' : editing === 'whatsapp' ? 'opens a WhatsApp chat with you' : 'dials this number'}.</p>
            <button className="btn btn-primary btn-block" onClick={() => { if (!val.trim()) return; const adding = !active.includes(editing); if (adding && atCap) { setEditing(null); setCapWarn(true); setTimeout(() => setCapWarn(false), 2200); return; } onSetData(editing, val.trim()); if (adding) onToggle(editing); setEditing(null); }}>{active.includes(editing) ? 'Update' : 'Add button'}</button>
          </div>
        </div>
      </div>
    );
  }

  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: 56 }} />
          <h3>Action Buttons</h3>
          <button className="glass-pill" style={{ background: 'var(--accent)', color: 'var(--accent-ink)', border: 'none' }} onClick={onClose}>Done</button>
        </div>
        <div className="sheet-scroll">
          {capWarn && <div className="ao-capwarn">You can show up to {MAX_ACTIONS} action buttons — remove one to add another.</div>}
          <div className="action-list">
            {order.map((id) => {
              const m = ACTIONS[id]; const on = active.includes(id);
              const handle = () => {
                const on = active.includes(id);
                if (!on && atCap) { setCapWarn(true); setTimeout(() => setCapWarn(false), 2200); return; }
                if (m.needs) { setVal(data[id] || ''); setEditing(id); return; }
                onToggle(id);
              };
              return (
                <div className={'action-opt' + (overId === id ? ' over' : '') + (dragId === id ? ' dragging' : '')} key={id} data-actid={id}>
                  <span className="ao-grip" title="Drag to reorder" onPointerDown={(e) => startDrag(e, id)} onTouchStart={(e) => startDrag(e, id)}>{XI.grip ? XI.grip({ width: 16, height: 16 }) : '⋮⋮'}</span>
                  <button className="ao-main" onClick={handle}>
                    <span className={'ao-ic' + (id === 'share' ? ' ao-ic-glass' : '')} style={id === 'share' ? null : { background: m.c }}>{m.icon}</span>
                    <div className="ao-meta">
                      <div className="ao-t">{m.label}</div>
                      <div className="ao-s">{on && m.needs && data[id] ? data[id] : m.desc}</div>
                    </div>
                    <span className={'ao-add' + (on ? ' on' : '') + (!on && atCap ? ' disabled' : '')}>{on ? '✓' : '+'}</span>
                  </button>
                </div>
              );
            })}
          </div>
          <div className="ao-foot">Drag the handle to reorder · tap to toggle</div>
        </div>
      </div>
    </div>
  );
}

/* ===========================================================
   PRODUCT EDITOR  (detailed bottom sheet)
   =========================================================== */
function ProductEditor({ initial, onSave, onClose, onDelete, merchLayout, merchCard, onSetLayout, onSetCard }) {
  const [p, setP] = xUse(initial || { name: '', price: '', tag: 'Shop', desc: '', img: null, link: '', badge: '', grad: 'linear-gradient(150deg,#3a3a42,#16161a)' });
  const set = (patch) => setP((s) => ({ ...s, ...patch }));
  const fileRef = xRef(null);
  const GRADS = ['linear-gradient(150deg,#3a3a42,#16161a)', 'linear-gradient(150deg,#5b3dff,#2a1a6a)', 'linear-gradient(150deg,#1f8a5b,#0e3a26)', 'linear-gradient(150deg,#d4761f,#6a3a0e)', 'linear-gradient(150deg,#d6336c,#6a1233)'];
  const TAGS = ['Apparel', 'Headwear', 'Digital', 'Booking', 'Service', 'Shop'];
  const LAYOUTS = [['carousel', 'Carousel'], ['stack', 'Stack'], ['grid', 'Grid'], ['spotlight', 'Spotlight']];
  const SHAPES = [['card', 'Classic'], ['wide', 'Banner'], ['row', 'Strip'], ['poster', 'Poster']];
  const layout = merchLayout || 'carousel';
  const locked = layout === 'grid' || layout === 'spotlight';
  const shape = layout === 'grid' ? 'card' : (layout === 'spotlight' ? 'wide' : (merchCard || 'card'));
  const PC = window.ProductCard;
  const previewProduct = { ...p, name: p.name || 'Product name', price: p.price || '£0' };
  return (
    <div className="sheet-bg" onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}>
      <div className="sheet">
        <div className="sheet-grab" />
        <div className="sheet-head">
          <button className="glass-pill" style={{ background: 'var(--bg-3)', color: 'var(--fg)', border: '1px solid var(--line)' }} onClick={onClose}>{XI.back({ width: 15, height: 15 })} Cancel</button>
          <h3>{initial ? 'Edit Product' : 'New Product'}</h3>
          <button className="glass-pill" style={{ background: 'var(--accent)', color: 'var(--accent-ink)', border: 'none' }} onClick={() => { if (!p.name) { onClose(); return; } onSave(p); }}>Save</button>
        </div>
        <div className="sheet-scroll">
          {/* product image */}
          <label className="pe-media" style={{ background: p.img ? '#141c28' : p.grad }}>
            {p.img && (p.imgKind === 'video' ? <video src={p.img} muted autoPlay loop playsInline /> : <img src={p.img} alt="" />)}
            <span className="pe-cam">{XI.image({ width: 18, height: 18 })} {p.img ? 'Change' : 'Add photo / video'}</span>
            <input ref={fileRef} type="file" accept="image/*,video/*" style={{ display: 'none' }} onChange={(e) => { const f = e.target.files && e.target.files[0]; if (f) readMedia(f, (src, k) => set({ img: src, imgKind: k })); e.target.value = ''; }} />
          </label>
          {!p.img && (
            <div className="pe-grads">
              {GRADS.map((g) => <button key={g} className={'pe-grad' + (p.grad === g ? ' on' : '')} style={{ background: g }} onClick={() => set({ grad: g })} />)}
            </div>
          )}
          <div className="field"><label>Product name</label><input className="input" value={p.name} placeholder="Signature Hoodie" onChange={(e) => set({ name: e.target.value })} /></div>
          <div style={{ display: 'flex', gap: 10 }}>
            <div className="field" style={{ flex: 1 }}><label>Price</label><input className="input" value={p.price} placeholder="£48" onChange={(e) => set({ price: e.target.value })} /></div>
            <div className="field" style={{ flex: 1 }}><label>Category</label>
              <select className="input" value={p.tag} onChange={(e) => set({ tag: e.target.value })}>{TAGS.map((t) => <option key={t} value={t}>{t}</option>)}</select></div>
          </div>
          <div className="pe-displayhint">
            <span className="pe-dh-ic"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" 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"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg></span>
            <span className="pe-dh-tx">Layout &amp; card style are set on your profile — choose how all products display under <b>Merch &amp; Products</b> after saving.</span>
          </div>
          <div className="field"><label>Badge <span style={{ color: 'var(--fg-3)', fontWeight: 400 }}>(optional)</span></label><input className="input" value={p.badge} maxLength={12} placeholder="20% OFF · WINTER20 · NEW" onChange={(e) => set({ badge: e.target.value })} /></div>
          <div className="field"><label>Description</label><textarea className="textarea" value={p.desc} maxLength={140} placeholder="Heavyweight, oversized fit. Ships worldwide." onChange={(e) => set({ desc: e.target.value })} /></div>
          <div className="field"><label>Buy / checkout link</label><div className="input-prefix"><span className="pfx">https://</span><input className="input" value={p.link} placeholder="yourstore.com/hoodie" onChange={(e) => set({ link: e.target.value })} /></div></div>
          <button className="btn btn-primary btn-block" onClick={() => { if (!p.name) { onClose(); return; } onSave(p); }}>{initial ? 'Save changes' : 'Add product'}</button>
          {initial && <button className="btn btn-ghost btn-block" style={{ marginTop: 10, color: 'var(--danger)' }} onClick={onDelete}>Delete product</button>}
        </div>
      </div>
    </div>
  );
}

/* ===========================================================
   INLINE ADD-LINK FORM  (expands inside a placeholder tile)
   variant: 'big' | 'grid' | 'button' | 'multi'
   =========================================================== */
function InlineLinkForm({ variant, onAdd, onCancel, onOpenSocial, initial }) {
  const [img, setImg] = xUse((initial && initial.image) || null);
  const [kind, setKind] = xUse((initial && initial.mediaKind) || 'image');
  const [url, setUrl] = xUse((initial && initial.url) || '');
  const [title, setTitle] = xUse((initial && initial.label) || '');
  const [plat, setPlat] = xUse((initial && initial.type && initial.type !== 'link') ? initial.type : null);
  const [crop, setCrop] = xUse(null);   // { src } awaiting reposition
  const [fetching, setFetching] = xUse(false);
  const [blocked, setBlocked] = xUse(false);   // adult URL entered
  const autoImgRef = xRef(false);       // true once we auto-filled, so manual picks aren't overwritten
  const lastFetchRef = xRef('');
  const showThumb = variant === 'big' || variant === 'grid' || variant === 'button';
  const thumbAspect = variant === 'big' ? '1.6 / 1' : '1 / 1';   // matches .pf-feat-media (1.6/1) / grid (1/1)

  // Auto-fetch the link's preview image + title (like Linktree) — ONLY for custom website
  // URLs (not social/app links, which use brand icons). Debounced; never overwrites a
  // thumbnail the user picked themselves. Adult URLs are blocked outright.
  xEffect(() => {
    if (plat) return;                                   // social links use brand icons
    const u = (url || '').trim();
    if (isAdultUrl(u)) { setBlocked(true); return; }
    setBlocked(false);
    if (!u || u.length < 4 || !/\./.test(u)) return;     // looks like a domain?
    if (detectPlatform(u) !== 'website') return;         // skip app/social URLs — website only
    if (lastFetchRef.current === u) return;
    const t = setTimeout(async () => {
      if (!window.TapptDB || !window.TapptDB.available || !window.TapptDB.available()) return;
      lastFetchRef.current = u;
      setFetching(true);
      const data = await window.TapptDB.linkPreview(u);
      setFetching(false);
      if (!data) return;
      // only auto-fill the thumbnail if the user hasn't set one (or we set the last one)
      if (data.image && (!img || autoImgRef.current)) { setImg(data.image); setKind('image'); autoImgRef.current = true; }
      if (data.title) setTitle((prev) => prev && prev.trim() ? prev : data.title);
    }, 700);
    return () => clearTimeout(t);
  }, [url, plat]);

  const submit = () => {
    if (!url && !title) { onCancel(); return; }
    if (isAdultUrl(url)) { setBlocked(true); return; }   // adult links are banned
    const type = plat || detectPlatform(url);
    const style = variant === 'multi' ? 'small' : variant;
    onAdd({ id: (initial && initial.id) || (Date.now() + Math.random()), type, label: title || xplat(type).label, url, style, active: true, image: img, mediaKind: img ? kind : null });
  };

  const onPickSocial = (link) => {
    const p = xplat(link.type);
    setPlat(link.type);
    const h = (link.handle || link.url || '');
    setUrl(window.joinSocial ? window.joinSocial(p.base, h) : h);
    setTitle((t) => t || p.label);
  };

  return (
    <div className={'inline-form vf-' + variant}>
      {plat ? (
        <div className="if-social-chip">
          <XBrand id={plat} size={30} />
          <span>{xplat(plat).label}</span>
          <button className="if-chip-x" onClick={() => { setPlat(null); setUrl(''); }} aria-label="Remove">✕</button>
        </div>
      ) : (
        <button className="if-social-cta" onClick={() => onOpenSocial && onOpenSocial(onPickSocial)}>
          <span className="if-cta-ic">{XI.link ? XI.link({ width: 15, height: 15 }) : '+'}</span>
          Adding a social app as a link?
        </button>
      )}
      {showThumb && (
        <label className={'if-thumb' + (variant === 'button' ? ' if-thumb-sm' : '')}>
          {img
            ? (kind === 'video' ? <video src={img} muted autoPlay loop playsInline /> : <img src={img} alt="" />)
            : <span className="if-thumb-empty">{fetching ? <span className="if-thumb-spin" /> : XI.image({ width: 22, height: 22 })}<span>{fetching ? 'Fetching preview…' : (variant === 'button' ? 'Add a thumbnail (optional)' : 'Tap to add thumbnail')}</span></span>}
          <span className="if-cam">{XI.image({ width: 15, height: 15 })}</span>
          <input type="file" accept="image/*,video/*" style={{ display: 'none' }} onChange={(e) => { const f = e.target.files && e.target.files[0]; if (!f) return; autoImgRef.current = false; readMedia(f, (src, k) => { if (k === 'video') { setImg(src); setKind(k); } else { setCrop({ src: src }); } }); }} />
        </label>
      )}
      {crop && window.PgCropper && <window.PgCropper src={crop.src} aspect={thumbAspect} onCancel={() => setCrop(null)} onDone={(out) => { autoImgRef.current = false; setImg(out); setKind('image'); setCrop(null); }} />}
      {!plat && <input className="input if-url" placeholder="Paste URL (e.g. tappt.io)" value={url} onChange={(e) => setUrl(e.target.value)} />}
      <textarea className="input if-title" placeholder="Link title" value={title} rows={2} maxLength={300} onChange={(e) => setTitle(e.target.value.slice(0, 300))} />
      <div className="if-titlecount">{title.length}/300</div>
      {blocked && <div className="if-blocked">Adult content links aren't allowed on Tappt.</div>}
      <div className="if-actions">
        <button className="btn btn-ghost btn-sm" onClick={onCancel}>Cancel</button>
        <button className="btn btn-primary btn-sm" disabled={blocked} onClick={submit}>{initial ? 'Save' : 'Add Link'}</button>
      </div>
    </div>
  );
}

/* ===========================================================
   CHANGE-COVER SHEET
   =========================================================== */
function CoverSheet({ onClose, onPick, hasCover }) {
  const opt = (icon, t, s, kind) => (
    <button className="cover-opt" onClick={() => onPick(kind)}>
      <span className="co-ic">{icon}</span>
      <div><div className="co-t">{t}</div><div className="co-s">{s}</div></div>
    </button>
  );
  return (
    <div className="sheet-bg" onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}>
      <div className="sheet cover-sheet" style={{ maxHeight: 'auto' }}>
        <div className="sheet-grab" />
        <div className="sheet-scroll" style={{ paddingTop: 8 }}>
          <div className="pf-section-h" style={{ marginBottom: 14 }}>Cover Options</div>
          {opt(XI.image({ width: 22, height: 22 }), 'Change Photo', 'Replace your cover image', 'image')}
          {opt(<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="2.5" y="6" width="13" height="12" rx="2.5"/><path d="M16 10l5.5-3v10L16 14"/></svg>, 'Change Video Cover', 'Looping video as your background', 'video')}
          {opt(<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="6" width="14" height="14" rx="2.5"/><path d="M20 7v11M7 14l3-3 3 3 2-2 2 2"/></svg>, 'Add Photo Carousel', 'Auto-rotating cover images', 'carousel')}
          {hasCover && opt(<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 6h18M8 6V4h8v2M19 6l-1 14H6L5 6"/></svg>, 'Remove Cover', 'Show the background instead', 'remove')}
          <button className="btn btn-ghost btn-block" style={{ marginTop: 8 }} onClick={onClose}>Cancel</button>
        </div>
      </div>
    </div>
  );
}

/* ===========================================================
   INLINE EDIT PROFILE  (replaces OwnProfile while editing)
   =========================================================== */
function EditProfile({ user, onCancel, onSave, openPro, toast }) {
  const persona = xActive(user);
  const [d, setD] = xUse({
    ...user,
    name: user.name || '', handle: user.handle || '', bio: user.bio || persona.bio || '',
    avatarColor: user.avatarColor || XAV[0], avatar: user.avatar || null,
    showAvatar: user.showAvatar || false,
    cover: user.cover || null, coverKind: user.coverKind || 'image', covers: user.covers || [],
    align: user.align || 'centered',
    sections: (user.sections && user.sections.length) ? user.sections : XSECT,
    links: user.links || [],
    socials: user.socials || [],
    socialData: user.socialData || {},
    saveContact: user.saveContact || false,
    actions: user.actions || [],
    actionData: user.actionData || {},
    products: user.products || [],
    gallerySize: user.gallerySize || 'medium',
    merchStyle: user.merchStyle || 'card',
    merchRotate: user.merchRotate || false,
    profileAnim: user.profileAnim || 'none',
    profileTheme: user.profileTheme || 'default',
    buttonStyle: user.buttonStyle || 'solid',
    buttonRadius: user.buttonRadius || 'md',
    wallpaper: user.wallpaper || '',
    cardEffect: user.cardEffect || 'none',
    avatarRing: user.avatarRing || false,
    headlineFont: user.headlineFont || 'display',
    verified: user.verified || false,
    spotlight: user.spotlight || false,
    drop: user.drop || { on: false, text: '', date: '' },
  });
  const set = (p) => setD((s) => ({ ...s, ...p }));
  // keep design fields (button style/radius, wallpaper, theme, effects) in sync with the live
  // user object so changes applied in the Design panel reflect in the editor preview immediately
  xEffect(() => {
    setD((s) => ({ ...s,
      buttonStyle: user.buttonStyle || 'solid', buttonRadius: user.buttonRadius || 'md',
      wallpaper: user.wallpaper || '', cardEffect: user.cardEffect || 'none',
      profileTheme: user.profileTheme || 'default', profileAnim: user.profileAnim || 'none',
      linkAnim: user.linkAnim || 'none', iconAnim: user.iconAnim || 'none',
      headlineFont: user.headlineFont || 'display', avatarRing: user.avatarRing || false,
    }));
  }, [user.buttonStyle, user.buttonRadius, user.wallpaper, user.cardEffect, user.profileTheme, user.profileAnim, user.linkAnim, user.iconAnim, user.headlineFont, user.avatarRing]);
  // live handle availability + validity (only when changed from the saved one)
  const origHandle = (user.handle || '').toLowerCase();
  const [handleStatus, setHandleStatus] = xUse('idle'); // idle | checking | ok | taken | short
  xEffect(() => {
    const h = (d.handle || '').toLowerCase();
    if (h === origHandle) { setHandleStatus('idle'); return; }
    if (h.length < 3) { setHandleStatus('short'); return; }
    setHandleStatus('checking');
    let alive = true;
    const t = setTimeout(async () => {
      let free = true;
      try { if (window.TapptDB && window.TapptDB.available()) free = await window.TapptDB.handleAvailable(h); } catch (e) {}
      if (alive) setHandleStatus(free ? 'ok' : 'taken');
    }, 450);
    return () => { alive = false; clearTimeout(t); };
  }, [d.handle]);
  const [coverSheet, setCoverSheet] = xUse(false);
  const [adding, setAdding] = xUse(null);      // 'big' | 'grid' | 'button' | 'multi'
  const [socialPicker, setSocialPicker] = xUse(false);
  const [actionPicker, setActionPicker] = xUse(false);
  const [followupSheet, setFollowupSheet] = xUse(false);
  const [exchangeSheet, setExchangeSheet] = xUse(false);
  const [tipSheet, setTipSheet] = xUse(false);
  const [productEditor, setProductEditor] = xUse(null); // null | 'new' | index
  const [addSheet, setAddSheet] = xUse(false);
  const [galCrop, setGalCrop] = xUse(null);   // single gallery image awaiting reposition
  const galAspect = d.gallerySize === 'small' ? '1 / 1' : d.gallerySize === 'large' ? '4 / 5' : '3 / 4';
  const coverInput = xRef(null);
  const avatarInput = xRef(null);
  const galleryInput = xRef(null);
  const merchRailRef = xRef(null);
  const secDragIdx = xRef(null);
  const [secOver, setSecOver] = xUse(null);
  const [secDragging, setSecDragging] = xUse(null);
  const moveSection = (from, to) => {
    if (from === to || from == null || to == null) return;
    const next = [...d.sections];
    const [m] = next.splice(from, 1);
    next.splice(to, 0, m);
    set({ sections: next });
  };
  /* pointer-based drag (HTML5 drag events don't fire on touch devices) */
  const secDragActive = xRef(false);
  const startSecDrag = (e, secId) => {
    if (secDragActive.current) return;
    secDragActive.current = true;
    e.preventDefault();
    setSecDragging(secId);
    const move = (ev) => {
      const pt = ev.touches ? ev.touches[0] : ev;
      if (!pt) return;
      const el = document.elementFromPoint(pt.clientX, pt.clientY);
      const block = el && el.closest ? el.closest('[data-secid]') : null;
      const overId = block ? block.getAttribute('data-secid') : null;
      if (overId) setSecOver(overId);
      /* auto-scroll the sheet when dragging near its top/bottom edge */
      const sc = document.querySelector('.edit-scroll');
      if (sc) {
        const r = sc.getBoundingClientRect();
        const EDGE = 90;
        if (pt.clientY > r.bottom - EDGE) sc.scrollTop += Math.min(18, (pt.clientY - (r.bottom - EDGE)) / 4);
        else if (pt.clientY < r.top + EDGE) sc.scrollTop -= Math.min(18, ((r.top + EDGE) - pt.clientY) / 4);
      }
    };
    const up = () => {
      secDragActive.current = false;
      setSecDragging((curId) => {
        setSecOver((overId) => {
          if (curId && overId && curId !== overId) {
            setD((s) => {
              // exchange/tip render as synthetic blocks not always present in sections —
              // materialize them so they can be dragged/reordered like any other block
              let secs = [...s.sections];
              const ensure = (id, label) => { if (!secs.some((x) => x.id === id)) secs.push({ id: id, label: label, on: true }); };
              if (curId === 'exchange' || overId === 'exchange') ensure('exchange', 'Exchange');
              if (curId === 'tip' || overId === 'tip') ensure('tip', 'Tip jar');
              const from = secs.findIndex((x) => x.id === curId);
              const to = secs.findIndex((x) => x.id === overId);
              if (from < 0 || to < 0) return s;
              const [m] = secs.splice(from, 1);
              secs.splice(to, 0, m);
              return { ...s, sections: secs };
            });
          }
          return null;
        });
        return null;
      });
      window.removeEventListener('pointermove', move);
      window.removeEventListener('pointerup', up);
      window.removeEventListener('touchmove', move);
      window.removeEventListener('touchend', up);
    };
    window.addEventListener('pointermove', move);
    window.addEventListener('pointerup', up);
    window.addEventListener('touchmove', move, { passive: false });
    window.addEventListener('touchend', up);
  };

  /* auto-rotate the edit-mode merch preview when toggled on */
  xEffect(() => {
    if (!d.merchRotate || (d.products || []).length < 2) return;
    const el = merchRailRef.current; if (!el) return;
    let raf, paused = false;
    const onDown = () => { paused = true; }; const onUp = () => { setTimeout(() => (paused = false), 2500); };
    el.addEventListener('pointerdown', onDown); el.addEventListener('pointerup', onUp);
    const step = () => {
      if (!paused && el) {
        el.scrollLeft += 0.5;
        if (el.scrollLeft >= el.scrollWidth - el.clientWidth - 1) el.scrollLeft = 0;
      }
      raf = requestAnimationFrame(step);
    };
    raf = requestAnimationFrame(step);
    return () => { cancelAnimationFrame(raf); el.removeEventListener('pointerdown', onDown); el.removeEventListener('pointerup', onUp); };
  }, [d.merchRotate, (d.products || []).length, d.merchLayout, d.merchCard, d.merchStyle]);

  const son = (id) => { const s = d.sections.find((x) => x.id === id); return s ? s.on : false; };
  const toggleSection = (id) => set({ sections: d.sections.map((s) => s.id === id ? { ...s, on: !s.on } : s) });
  const removeLink = (id) => set({ links: d.links.filter((l) => l.id !== id) });
  const setLinkStyle = (id, style) => set({ links: d.links.map((l) => l.id === id ? { ...l, style } : l) });
  const [editingLink, setEditingLink] = xUse(null);
  const removeSocial = (id) => set({ socials: d.socials.filter((s) => s !== id) });
  const addLink = (link) => { set({ links: [...d.links, link] }); setAdding(null); toast && toast('Link added'); };
  const [featSocialCb, setFeatSocialCb] = xUse(null);
  const openFeatSocial = (cb) => setFeatSocialCb(() => cb);

  const anim = (user.pro && d.profileAnim && d.profileAnim !== 'none') ? ' anim-' + d.profileAnim : '';
  const coverGrad = d.avatarColor || XAV[0];
  const initial = (d.name || 'T').trim().charAt(0).toUpperCase();
  const linkCount = d.links.length;

  /* wrap a rendered link with edit (pencil) + remove (×) overlays */
  const wrapRemove = (node, l) => {
    if (editingLink === l.id) {
      const v = (l.style === 'small') ? 'multi' : (l.style || 'button');
      return (
        <div className="add-tile open" key={'e' + l.id}>
          <InlineLinkForm variant={v} initial={l} onCancel={() => setEditingLink(null)} onOpenSocial={openFeatSocial}
            onAdd={(nl) => { set({ links: d.links.map((x) => x.id === l.id ? { ...nl, id: l.id } : x) }); setEditingLink(null); toast && toast('Link updated'); }} />
        </div>
      );
    }
    return (
      <div className="edit-link-wrap" key={'w' + l.id}>
        {node}
        <button className="lk-edit" onClick={() => setEditingLink(l.id)} aria-label="Edit">{XI.edit ? XI.edit({ width: 13, height: 13 }) : '✎'}</button>
        <button className="rm-x" onClick={() => removeLink(l.id)} aria-label="Remove">{XI.close ? XI.close({ width: 14, height: 14 }) : '✕'}</button>
      </div>
    );
  };

  /* pick cover media of a given kind */
  const pickCover = (kind) => {
    setCoverSheet(false);
    if (kind === 'remove') { set({ cover: null, coverKind: 'image', covers: [] }); toast && toast('Cover removed'); return; }
    if (kind === 'image' || kind === 'carousel') { coverInput.current.accept = 'image/*'; coverInput.current.multiple = kind === 'carousel'; coverInput.current.dataset.kind = kind; coverInput.current.click(); }
    else { coverInput.current.accept = 'video/*'; coverInput.current.multiple = false; coverInput.current.dataset.kind = 'video'; coverInput.current.click(); }
  };

  const doSave = () => {
    if (handleStatus === 'taken') { toast && toast('That username is taken — pick another'); return; }
    if (handleStatus === 'short') { toast && toast('Username is too short'); return; }
    onSave(d);
  };
  return (
    <React.Fragment>
      {/* TOP BAR — back (cancel) left, title centre, tick (save) right */}
      <div className="edit-topbar">
        <button className="et-circle" onClick={onCancel} aria-label="Back">{XI.back ? XI.back({ width: 20, height: 20 }) : '‹'}</button>
        <span className="et-title">Edit Profile</span>
        <button className="et-circle et-save" onClick={doSave} aria-label="Save">{XI.check ? XI.check({ width: 20, height: 20 }) : '✓'}</button>
      </div>
      <div className={'appscroll edit-scroll btnst-' + (d.buttonStyle || 'solid') + ' brad-' + (d.buttonRadius || 'md') + (d.wallpaper ? ' wp-' + d.wallpaper : '') + (d.cardEffect && d.cardEffect !== 'none' ? ' fx-' + d.cardEffect : '')} data-theme={(d.profileTheme || 'default') === 'light' ? 'light' : 'dark'}>
        {/* COVER — photo only, with Change Cover */}
        <div className="pf-cover edit-cover-slim">
          <div className={'pf-cover-media' + anim} style={{ background: coverGrad }}>
            {d.cover && (d.coverKind === 'video'
              ? <video ref={window.playMuted} src={d.cover} muted autoPlay loop playsInline />
              : <img src={d.cover} alt="" />)}
            <div className="pf-cover-tint" />
          </div>
          {d.coverKind === 'video' && d.cover && <span className="cover-badge">{XI.image ? '◉ Video' : 'Video'}</span>}
          <div className="pf-cover-foot">
            <div className="change-cover-wrap">
              <button className="change-cover-pill" onClick={() => setCoverSheet(true)}>
                {XI.image({ width: 15, height: 15 })} Change Cover
              </button>
              {d.cover && <button className="cover-remove-x" title="Remove cover" onClick={() => set({ cover: null, coverKind: 'image', covers: [] })}>{XI.close ? XI.close({ width: 13, height: 13 }) : '✕'}</button>}
            </div>
          </div>
        </div>

        {/* IDENTITY PANEL — name, handle, role, alignment, socials, actions on a clean card */}
        <div className={'edit-identity' + (d.align === 'left' ? ' align-left' : '')}>
          {/* avatar — optional, off by default */}
          {d.showAvatar ? (
            <div className="edit-avatar-wrap">
              <button className="edit-avatar" onClick={() => avatarInput.current.click()}>
                <span className="ea-img" style={{ background: d.avatarColor }}>
                  {d.avatar ? <img src={d.avatar} alt="" /> : <span className="ea-initial">{initial}</span>}
                </span>
                <span className="ea-cam">{XI.image({ width: 15, height: 15 })}</span>
              </button>
              <button className="ea-remove" title="Hide profile photo" onClick={() => set({ showAvatar: false })}>{XI.close ? XI.close({ width: 13, height: 13 }) : '✕'}</button>
            </div>
          ) : (
            <button className="add-avatar-pill" onClick={() => { set({ showAvatar: true }); if (!d.avatar) avatarInput.current.click(); }}>
              {XI.plus ? XI.plus({ width: 14, height: 14 }) : '+'} Add profile photo
            </button>
          )}
          <input ref={avatarInput} type="file" accept="image/*" style={{ display: 'none' }} onChange={(e) => { const f = e.target.files && e.target.files[0]; if (f) readMedia(f, (src) => set({ avatar: src })); }} />

          {/* editable name / handle (pencil affordance) */}
          <div className="edit-field ef-name"><input className="edit-name" value={d.name} placeholder="Your Name" onChange={(e) => set({ name: e.target.value })} /><span className="ef-pencil">{XI.edit({ width: 13, height: 13 })}</span></div>
          <div className="edit-field ef-handle"><div className="edit-handle">tappt.io/<input value={d.handle} placeholder="handle" style={{ width: Math.min(Math.max((d.handle || 'handle').length, 6) + 1, 20) + 'ch' }} onChange={(e) => set({ handle: e.target.value.toLowerCase().replace(/[^a-z0-9_.]/g, '') })} /></div><span className="ef-pencil">{XI.edit({ width: 12, height: 12 })}</span></div>
          {handleStatus !== 'idle' && (
            <div className={'edit-handle-status ' + (handleStatus === 'ok' ? 'ok' : handleStatus === 'checking' ? 'checking' : 'bad')}>
              {handleStatus === 'checking' ? 'Checking…' : handleStatus === 'ok' ? '✓ Available' : handleStatus === 'short' ? 'Too short' : 'Taken — try another'}
            </div>
          )}

          {/* business: role @ company */}
          {user.accountType === 'business' && (
            <div className="edit-field ef-company">
              <input className="edit-company" value={d.role || ''} placeholder="Role (e.g. Founder)" onChange={(e) => set({ role: e.target.value })} />
              <span className="ef-company-at">at</span>
              <input className="edit-company" value={d.company || ''} placeholder="Company" onChange={(e) => set({ company: e.target.value })} />
            </div>
          )}

          <div className="edit-divider" />

          {/* alignment toggle */}
          <div className="edit-row-label">Layout</div>
          <div className="seg align-seg edit-align">
            <button className={d.align === 'left' ? 'on' : ''} onClick={() => set({ align: 'left' })}>Left Aligned</button>
            <button className={d.align === 'centered' ? 'on' : ''} onClick={() => set({ align: 'centered' })}>Centered</button>
          </div>

          <div className="edit-divider" />

          {/* social icons w/ remove + add */}
          <div className="edit-row-label">Social icons</div>
          <div className="pf-socials edit-socials">
            {d.socials.map((s) => (
              <span className="es-soc" key={s}>
                <XBrand id={s} size={44} />
                <button className="es-soc-x" onClick={() => removeSocial(s)} aria-label="Remove">✕</button>
              </span>
            ))}
            <button className="es-soc-add" onClick={() => setSocialPicker(true)}>+</button>
          </div>
          {d.socials.length > 2 && (
            <button className="edit-contact-toggle" style={{ marginTop: 12 }} onClick={() => set({ socialRotate: !d.socialRotate })}>
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 12a9 9 0 0 1 15-6.7L21 8M21 3v5h-5M21 12a9 9 0 0 1-15 6.7L3 16M3 21v-5h5"/></svg>
              Rotate social icons
              <span className={'switch' + (d.socialRotate ? ' on' : '')} style={{ marginLeft: 'auto' }} />
            </button>
          )}

          <div className="edit-divider" />

          {/* action buttons (what visitors can do) */}
          <div className="edit-row-label">Action buttons</div>
          <div className="edit-actions">
            <div className="edit-actions-row">
              {d.actions.map((a) => {
                const m = ACTIONS[a]; if (!m) return null;
                return (
                  <span className="ea-chip ea-chip-glass" key={a}>
                    <span className="ea-chip-ic" style={a === 'share' ? null : { background: m.c }}>{m.icon}</span>{m.label}
                    <button className="ea-chip-x" onClick={() => set({ actions: d.actions.filter((x) => x !== a) })}>✕</button>
                  </span>
                );
              })}
              <button className="ea-add" onClick={() => setActionPicker(true)}>{XI.plus({ width: 16, height: 16 })} Action</button>
            </div>
            <div className="ea-hint">Buttons visitors tap — save your contact, call, message…</div>
          </div>
        </div>

        {/* FEATURED LINKS */}
        <div className="pf-body">
          {/* all sections in one ordered, drag-reorderable list (Featured incl.) */}
          <div className="edit-sec-previews">
            {(() => {
              let secs = d.sections.slice();
              if (user.pro && d.exchange && d.exchange.on && !(d.exchange.sticky) && !secs.some((x) => x.id === 'exchange')) secs = secs.concat([{ id: 'exchange', label: 'Exchange', on: true }]);
              if (user.pro && d.tipsOn && !secs.some((x) => x.id === 'tip')) secs = secs.concat([{ id: 'tip', label: 'Tip jar', on: true }]);
              // only show blocks the user has turned ON (cleaner page) — the rest live in “Add content”.
              // Moments is always present (it can't be removed on free).
              return secs.filter((x) => x.on || x.id === 'moments');
            })().map((s) => {
              const realIdx = d.sections.indexOf(s);
              return (
              <div className={'edit-sec-block' + (secOver === s.id ? ' over' : '') + (secDragging === s.id ? ' dragging' : '')} key={s.id} data-secid={s.id}>
                <div className="edit-sec-head">
                  <span className="sec-grip" title="Drag to reorder"
                    onPointerDown={(e) => startSecDrag(e, s.id)}
                    onTouchStart={(e) => startSecDrag(e, s.id)}>{XI.grip ? XI.grip({ width: 17, height: 17 }) : '⋮⋮'}</span>
                  <span className="pf-section-h" style={{ margin: 0, flex: 1 }}>{s.label}</span>
                  {s.id === 'moments' && !user.pro
                    ? <button className="hide-pill hide-pill-pro" onClick={() => openPro && openPro()} title="Hiding Moments is a Pro feature">{XI.crown ? XI.crown({ width: 13, height: 13 }) : '✦'} Always on</button>
                    : <button className="hide-pill" onClick={() => toggleSection(s.id)}>{s.on ? 'Hide' : 'Show'}</button>}
                </div>
                {s.id === 'bio' ? (
                  <div className={'esb-preview' + (s.on ? '' : ' is-off')}>
                    <textarea className="bio-section-input" rows={3} maxLength={600} value={d.bio} placeholder="Write your bio… tell people who you are, what you do, and why they should connect." onChange={(e) => set({ bio: e.target.value })} />
                    <div className="bio-charcount">{(d.bio || '').length}/600</div>
                    {!s.on && <div className="esb-veil">Hidden — tap Show to display</div>}
                  </div>
                ) : s.id === 'featured' ? (
                  <div className={'esb-preview' + (s.on ? '' : ' is-off')}>
                    {window.renderLinks(d.links, wrapRemove)}
                    <div className="add-tiles">
                      {adding === 'big'
                        ? <div className="add-tile big open"><InlineLinkForm variant="big" onAdd={addLink} onCancel={() => setAdding(null)} onOpenSocial={openFeatSocial} /></div>
                        : <button className="add-tile big" onClick={() => setAdding('big')}><span className="at-ico">{XI.image({ width: 28, height: 28 })}</span><span className="at-lbl">+ Add Big Thumbnail Link</span></button>}
                      <div className="add-tiles-row">
                        {adding === 'grid'
                          ? <div className="add-tile small open"><InlineLinkForm variant="grid" onAdd={addLink} onCancel={() => setAdding(null)} onOpenSocial={openFeatSocial} /></div>
                          : <button className="add-tile small" onClick={() => setAdding('grid')}><span className="at-ico">{XI.image({ width: 20, height: 20 })}</span><span className="at-lbl">+ Add Small Thumbnail Link</span></button>}
                        <button className="add-tile small" onClick={() => setAdding('grid')}><span className="at-ico">{XI.image({ width: 20, height: 20 })}</span><span className="at-lbl">+ Add Small Thumbnail Link</span></button>
                      </div>
                      {adding === 'button'
                        ? <div className="add-tile row open"><InlineLinkForm variant="button" onAdd={addLink} onCancel={() => setAdding(null)} onOpenSocial={openFeatSocial} /></div>
                        : <button className="add-tile row" onClick={() => setAdding('button')}><span className="at-lbl">+ Add Link</span><span className="at-ico-sm">{XI.link({ width: 16, height: 16 })}</span></button>}
                    </div>
                    {!s.on && <div className="esb-veil">Hidden — tap Show to display</div>}
                  </div>
                ) : s.id === 'embeds' ? (
                  <div className={'esb-preview' + (s.on ? '' : ' is-off')}>
                    <EmbedEditor d={d} set={set} />
                    {!s.on && <div className="esb-veil">Hidden — tap Show to display</div>}
                  </div>
                ) : s.id === 'gallery' ? (
                  <div className={'esb-preview' + (s.on ? '' : ' is-off')}>
                    <div className="gallery-size-bar">
                      {['small', 'medium', 'large'].map((sz) => (
                        <button key={sz} className={(d.gallerySize || 'medium') === sz ? 'on' : ''} onClick={() => set({ gallerySize: sz })}>{sz[0].toUpperCase() + sz.slice(1)}</button>
                      ))}
                    </div>
                    <div className={'gallery-rail gs-' + (d.gallerySize || 'medium')}>
                      {(d.gallery || []).map((m, i) => (
                        <div className="gallery-cell" key={i}>
                          {m.kind === 'video' ? <video src={m.src} muted autoPlay loop playsInline /> : <img src={m.src || m} alt="" />}
                          <button className="gc-x" onClick={() => set({ gallery: (d.gallery || []).filter((_, j) => j !== i) })}>✕</button>
                        </div>
                      ))}
                      <button className="gallery-add" onClick={() => galleryInput.current.click()}>{XI.image({ width: 22, height: 22 })}<span>Add photo/video</span></button>
                    </div>
                    {!s.on && <div className="esb-veil">Hidden — tap Show to display</div>}
                  </div>
                ) : s.id === 'merch' ? (
                  <div className={'esb-preview' + (s.on ? '' : ' is-off')}>
                    {(() => {
                      const gm = (window.getMerch ? window.getMerch(d) : { layout: 'carousel', shape: 'card' });
                      const layout = d.merchLayout || gm.layout;
                      const shape = layout === 'grid' ? 'card' : (d.merchCard || gm.shape);
                      const setLayout = (l) => set({ merchLayout: l, merchCard: d.merchCard || gm.shape });
                      const setShape = (sh) => set({ merchCard: sh, merchLayout: layout });
                      return (
                        <React.Fragment>
                          <div className="merch-pick-label">Layout</div>
                          <div className="merch-style-bar">
                            {[['carousel', 'Carousel'], ['stack', 'Stack'], ['grid', 'Grid'], ['spotlight', 'Spotlight']].map(([id, lb]) => (
                              <button key={id} className={layout === id ? 'on' : ''} onClick={() => setLayout(id)}>{lb}</button>
                            ))}
                          </div>
                          <div className="merch-pick-label">Card style {(layout === 'grid' || layout === 'spotlight') && <span className="mpl-note">· {layout === 'grid' ? 'Grid uses Classic' : 'Spotlight is preset'}</span>}</div>
                          <div className={'merch-style-bar' + ((layout === 'grid' || layout === 'spotlight') ? ' is-locked' : '')}>
                            {[['card', 'Classic'], ['wide', 'Banner'], ['row', 'Strip'], ['poster', 'Poster']].map(([id, lb]) => (
                              <button key={id} className={shape === id ? 'on' : ''} disabled={layout === 'grid' || layout === 'spotlight'} onClick={() => setShape(id)}>{lb}</button>
                            ))}
                          </div>
                          {layout === 'spotlight' ? (
                            <div className="esb-spotlight-wrap">
                              <div className="merch-spotlight">
                                {(d.products || []).length > 0 && (
                                  <div className="merch-rail mr-stack shape-is-wide">
                                    <div className="merch-edit-wrap" onClick={() => setProductEditor(0)} role="button">
                                      {window.ProductCard({ p: { ...d.products[0], name: d.products[0].name || 'Untitled', price: d.products[0].price || '—' }, shape: 'wide' })}
                                      <span className="mc-edit-badge">{XI.edit({ width: 13, height: 13 })}</span>
                                    </div>
                                  </div>
                                )}
                                {(d.products || []).length > 1 && (
                                  <div className="merch-rail mr-grid shape-is-card">
                                    {(d.products || []).slice(1).map((p, i) => (
                                      <div className="merch-edit-wrap" key={i} onClick={() => setProductEditor(i + 1)} role="button">
                                        {window.ProductCard({ p: { ...p, name: p.name || 'Untitled', price: p.price || '—' }, shape: 'card' })}
                                        <span className="mc-edit-badge">{XI.edit({ width: 13, height: 13 })}</span>
                                      </div>
                                    ))}
                                  </div>
                                )}
                              </div>
                              <button className="merch-add merch-add-block" onClick={() => setProductEditor('new')}>{XI.plus({ width: 22, height: 22 })}<span>Add product</span></button>
                            </div>
                          ) : (
                          <div className={'merch-rail mr-' + layout + ' shape-is-' + shape} ref={merchRailRef}>
                            {(d.products || []).map((p, i) => (
                              <div className="merch-edit-wrap" key={i} onClick={() => setProductEditor(i)} role="button">
                                {window.ProductCard({ p: { ...p, name: p.name || 'Untitled', price: p.price || '—' }, shape: shape })}
                                <span className="mc-edit-badge">{XI.edit({ width: 13, height: 13 })}</span>
                              </div>
                            ))}
                            <button className="merch-add" onClick={() => setProductEditor('new')}>{XI.plus({ width: 26, height: 26 })}<span>Add product</span></button>
                          </div>
                          )}
                        </React.Fragment>
                      );
                    })()}
                    {(d.products || []).length > 1 && (d.merchLayout || (window.getMerch ? window.getMerch(d).layout : 'carousel')) === 'carousel' && (
                      <button className="merch-rotate" onClick={() => set({ merchRotate: !d.merchRotate })}>
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 12a9 9 0 0 1 15-6.7L21 8M21 3v5h-5M21 12a9 9 0 0 1-15 6.7L3 16M3 21v-5h5"/></svg>
                        Auto-rotate carousel
                        <span className={'switch' + (d.merchRotate ? ' on' : '')} style={{ marginLeft: 'auto' }} />
                      </button>
                    )}
                    {(d.products || []).length === 0 && <div className="esb-veil-soft">Tap “Add product” to start your shop</div>}
                    {!s.on && <div className="esb-veil">Hidden — tap Show to display</div>}
                  </div>
                ) : s.id === 'exchange' ? (
                  <div className={'esb-preview' + (s.on ? '' : ' is-off')}>
                    <button className="ex-cta-link" style={{ pointerEvents: 'none' }}>
                      <span className="ex-cta-link-ic">{XI.mail ? XI.mail({ width: 18, height: 18 }) : '✉'}</span>
                      <span className="ex-cta-link-t">{(d.exchange && d.exchange.headline) || 'Exchange contact'}</span>
                      {XI.arrow ? XI.arrow({ width: 16, height: 16 }) : '→'}
                    </button>
                    <button className="add-tile-cta" style={{ marginTop: 10 }} onClick={() => setExchangeSheet(true)}>{XI.edit ? XI.edit({ width: 15, height: 15 }) : ''} Edit exchange form</button>
                    {!s.on && <div className="esb-veil">Hidden — tap Show to display</div>}
                  </div>
                ) : (
                  <div className={'esb-preview' + (s.on ? '' : ' is-off')}>
                    {window.renderSection(s.id, user)}
                    {!s.on && <div className="esb-veil">Hidden — tap Show to display</div>}
                  </div>
                )}
              </div>
              );
            })}
          </div>

          {/* Pro tools moved into the Add content sheet (LinkMe-style grouped) */}

          <div style={{ height: 90 }} />
        </div>

        <input ref={coverInput} type="file" style={{ display: 'none' }} onChange={(e) => {
          const files = e.target.files; if (!files || !files.length) return;
          const kind = coverInput.current.dataset.kind;
          if (kind === 'carousel') {
            const arr = []; let n = files.length;
            [...files].forEach((f) => readMedia(f, (src) => { arr.push(src); if (--n === 0) set({ covers: arr, cover: arr[0], coverKind: 'image' }); }));
          } else {
            readMedia(files[0], (src, k) => set({ cover: src, coverKind: kind === 'video' ? 'video' : 'image', covers: [] }));
          }
          e.target.value = '';
        }} />
        <input ref={galleryInput} type="file" accept="image/*,video/*" multiple style={{ display: 'none' }} onChange={(e) => {
          const files = e.target.files; if (!files || !files.length) return;
          // upload straight in (like any normal platform) — no cropper
          const arr = [...(d.gallery || [])]; let n = files.length;
          [...files].forEach((f) => readMedia(f, (src, k) => { arr.push({ src, kind: k }); if (--n === 0) set({ gallery: arr }); }));
          e.target.value = '';
        }} />
      </div>

      {/* ADD CONTENT FAB (steel blue) */}
      <button className="edit-addfab" onClick={() => setAddSheet(true)}>{XI.plus ? XI.plus({ width: 18, height: 18 }) : '+'} Add content</button>
      {addSheet && (
        <div className="sheet-bg" onClick={(e) => { if (e.target === e.currentTarget) setAddSheet(false); }}>
          <div className="sheet">
            <div className="sheet-grab" />
            <div className="sheet-head">
              <span style={{ width: 56 }} />
              <h3>Add content</h3>
              <button className="glass-pill" style={{ background: 'var(--accent)', color: 'var(--accent-ink)', border: 'none' }} onClick={() => setAddSheet(false)}>Done</button>
            </div>
            <div className="sheet-scroll">
              <p style={{ color: 'var(--fg-2)', fontSize: 13.5, margin: '0 2px 16px', lineHeight: 1.5 }}>Choose what shows on your profile. Tap a block to add it — then drag to reorder on the page.</p>
              {(() => {
                const PRO_BLOCKS = { moments: false, merch: true };
                const GROUPS = [
                  { t: 'Content', ids: ['bio', 'featured', 'embeds', 'moments', 'gallery'] },
                  { t: 'Commerce', ids: ['merch'] },
                ];
                const byId = {}; d.sections.forEach((s) => { byId[s.id] = s; });
                const proBadge = <span className="addc-pro"><span className="pro-badge">Pro</span></span>;
                return GROUPS.map((g) => (
                  <div className="addc-group" key={g.t}>
                    <div className="addc-gh">{g.t}</div>
                    {g.ids.map((id) => {
                      const s = byId[id]; if (!s) return null;
                      const on = s.on || s.id === 'moments';
                      const isPro = PRO_BLOCKS[id];
                      const locked = (s.id === 'moments' || isPro) && !user.pro;
                      return (
                        <button key={s.id} className={'addc-row' + (on ? ' on' : '')} onClick={() => { if (locked) { openPro && openPro(); return; } if (!s.on) toggleSection(s.id); setAddSheet(false); }}>
                          <span className="addc-ic">{XI.plus ? XI.plus({ width: 17, height: 17 }) : '+'}</span>
                          <span className="addc-meta"><span className="addc-t">{s.label}{isPro && proBadge}</span><span className="addc-s">{on ? 'On your profile' : 'Tap to add'}</span></span>
                          <span className={'addc-add' + (on ? ' on' : '')}>{on ? '✓' : '+'}</span>
                        </button>
                      );
                    })}
                  </div>
                ));
              })()}
              {/* Pro tools — now grouped inside the sheet (LinkMe-style) */}
              <div className="addc-group">
                <div className="addc-gh">Pro tools</div>
                <button className="addc-row" onClick={() => { if (!user.pro) { openPro && openPro(); return; } const on = !(d.exchange && d.exchange.on); set({ exchange: Object.assign({}, window.exConf({ exchange: d.exchange }), { on: on }) }); if (on) { setAddSheet(false); setExchangeSheet(true); } }}>
                  <span className="addc-ic">{XI.sync ? XI.sync({ width: 17, height: 17 }) : '⇄'}</span>
                  <span className="addc-meta"><span className="addc-t">Contact exchange<span className="addc-pro"><span className="pro-badge">Pro</span></span></span><span className="addc-s">{(d.exchange && d.exchange.on) ? 'On · tap to customise fields &amp; CRM' : 'Let visitors leave their details — lands in Leads'}</span></span>
                  {user.pro ? ((d.exchange && d.exchange.on) ? <span className="addc-edit" onClick={(ev) => { ev.stopPropagation(); setAddSheet(false); setExchangeSheet(true); }}>{XI.edit ? XI.edit({ width: 15, height: 15 }) : 'Edit'}</span> : <span className="addc-add">+</span>) : <span className="addc-add">+</span>}
                </button>
                <button className="addc-row addc-sub" onClick={() => { if (!user.pro) { openPro && openPro(); return; } if (!(d.exchange && d.exchange.on)) { set({ exchange: Object.assign({}, window.exConf({ exchange: d.exchange }), { on: true }) }); } setAddSheet(false); setFollowupSheet(true); }} style={(d.exchange && d.exchange.on) ? null : { opacity: 0.5 }}>
                  <span className="addc-sub-tick" />
                  <span className="addc-ic">{XI.mail ? XI.mail({ width: 17, height: 17 }) : '✉'}</span>
                  <span className="addc-meta"><span className="addc-t">Auto follow-up email</span><span className="addc-s">{!(d.exchange && d.exchange.on) ? 'Turn on Contact exchange first' : (d.followupOn ? 'On — sent after they exchange' : 'Auto-send a message after they exchange')}</span></span>
                  <span className="addc-add">{XI.arrow ? XI.arrow({ width: 15, height: 15 }) : '›'}</span>
                </button>
                <button className="addc-row" onClick={() => { if (!user.pro) { openPro && openPro(); return; } const on = !(d.tipsOn); const patch = { tipsOn: on }; if (on && !d.sections.some((x) => x.id === 'tip')) patch.sections = d.sections.concat([{ id: 'tip', label: 'Tip jar', on: true }]); set(patch); if (on) { setAddSheet(false); setTipSheet(true); } }}>
                  <span className="addc-ic">{XI.heart ? XI.heart({ width: 17, height: 17 }) : '♥'}</span>
                  <span className="addc-meta"><span className="addc-t">Collect tips<span className="addc-pro"><span className="pro-badge">Pro</span></span></span><span className="addc-s">{d.tipsOn ? 'On · tap to set amounts &amp; message' : 'Let fans send you a tip from your profile'}</span></span>
                  {user.pro ? (d.tipsOn ? <span className="addc-edit" onClick={(ev) => { ev.stopPropagation(); setAddSheet(false); setTipSheet(true); }}>{XI.edit ? XI.edit({ width: 15, height: 15 }) : 'Edit'}</span> : <span className="addc-add">+</span>) : <span className="addc-add">+</span>}
                </button>
              </div>
            </div>
          </div>
        </div>
      )}

      {coverSheet && <CoverSheet onClose={() => setCoverSheet(false)} onPick={pickCover} hasCover={!!d.cover} />}
      {socialPicker && <XAddLink mode="social" onClose={() => setSocialPicker(false)} onAdd={(link) => { if (!d.socials.includes(link.type)) set({ socials: [...d.socials, link.type], socialData: { ...(d.socialData || {}), [link.type]: link.handle || link.url || '' } }); else set({ socialData: { ...(d.socialData || {}), [link.type]: link.handle || link.url || '' } }); setSocialPicker(false); toast && toast('Icon added'); }} />}
      {featSocialCb && <XAddLink mode="social" onClose={() => setFeatSocialCb(null)} onAdd={(link) => { featSocialCb(link); setFeatSocialCb(null); }} />}
      {actionPicker && <ActionPicker active={d.actions} data={d.actionData} onReorder={(next) => set({ actions: next })} onSetData={(id, v) => set({ actionData: { ...d.actionData, [id]: v } })} onToggle={(id) => set({ actions: d.actions.includes(id) ? d.actions.filter((x) => x !== id) : [...d.actions, id] })} onClose={() => setActionPicker(false)} />}
      {exchangeSheet && window.ExchangeConfigSheet && <window.ExchangeConfigSheet user={user} draft={d.exchange} onChange={(patch) => { const nextEx = Object.assign({}, window.exConf({ exchange: d.exchange }), patch); const inFlow = nextEx.on && !nextEx.sticky && (nextEx.display === 'link' || nextEx.display === 'card'); let secs = d.sections.filter((s) => s.id !== 'exchange'); if (inFlow) secs = secs.concat([{ id: 'exchange', label: 'Exchange', on: true }]); set({ exchange: nextEx, sections: secs }); }} onClose={() => setExchangeSheet(false)} />}
      {tipSheet && window.TipConfigSheet && <window.TipConfigSheet draft={d} onChange={(patch) => set(patch)} onClose={() => setTipSheet(false)} />}
      {followupSheet && (
        <div className="sheet-bg" onClick={(e) => { if (e.target === e.currentTarget) setFollowupSheet(false); }}>
          <div className="sheet">
            <div className="sheet-grab" />
            <div className="sheet-head">
              <button className="glass-pill" style={{ background: 'var(--bg-3)', color: 'var(--fg)', border: '1px solid var(--line)' }} onClick={() => setFollowupSheet(false)}>{XI.back({ width: 15, height: 15 })} Back</button>
              <h3>Follow-up email</h3>
              <button className="glass-pill" style={{ background: 'var(--accent)', color: 'var(--accent-ink)', border: 'none' }} onClick={() => { set({ followupOn: !!(d.followupMsg && d.followupMsg.trim()) }); setFollowupSheet(false); toast && toast('Follow-up saved'); }}>{XI.check({ width: 15, height: 15 })} Save</button>
            </div>
            <div className="sheet-scroll">
              <p style={{ color: 'var(--fg-2)', fontSize: 13.5, margin: '0 2px 16px', lineHeight: 1.5 }}>When someone exchanges contact with you and leaves their email, Tappt sends them this automatically — so you never lose the connection.</p>
              <div className="field"><label>Subject</label><input className="input" placeholder="Great to meet you, {{firstName}}!" value={d.followupSubject || ''} onChange={(e) => set({ followupSubject: e.target.value })} /></div>
              <div className="field"><label>Message</label><textarea className="textarea" id="fup-msg" style={{ minHeight: 130 }} placeholder={"Hey {{firstName}} — really enjoyed our chat. Here's my profile again: tappt.io/" + (d.handle || 'you') + " — let's keep in touch."} value={d.followupMsg || ''} onChange={(e) => set({ followupMsg: e.target.value })} /></div>
              <div className="fup-tags">
                <span className="fup-tags-l">Insert</span>
                {[['{{firstName}}', 'First name'], ['{{name}}', 'Full name'], ['{{company}}', 'Company'], ['{{myName}}', 'Your name']].map(([tag, lb]) => (
                  <button key={tag} className="fup-tag" onClick={() => { const ta = document.getElementById('fup-msg'); const cur = d.followupMsg || ''; const pos = ta && ta.selectionStart != null ? ta.selectionStart : cur.length; const next = cur.slice(0, pos) + tag + cur.slice(pos); set({ followupMsg: next }); }}>{lb}</button>
                ))}
              </div>
              <div className="field"><label>Send</label>
                <div className="fup-delay">
                  {[['0', 'Instantly'], ['60', 'After 1 hr'], ['1440', 'Next day'], ['4320', 'In 3 days']].map(([min, lb]) => (
                    <button key={min} className={'fup-delay-opt' + ((String(d.followupDelay || '0')) === min ? ' on' : '')} onClick={() => set({ followupDelay: min })}>{lb}</button>
                  ))}
                </div>
              </div>
              <button className="ept-row" style={{ marginBottom: 10 }} onClick={() => set({ followupCc: !d.followupCc })}>
                <span className="ept-ic">{XI.user ? XI.user({ width: 17, height: 17 }) : '◔'}</span>
                <span className="ept-meta"><span className="ept-t">Copy me in</span><span className="ept-s">BCC yourself on every follow-up</span></span>
                <span className={'switch' + (d.followupCc ? ' on' : '')} />
              </button>
              <button className="ept-row" onClick={() => set({ followupCard: !d.followupCard })}>
                <span className="ept-ic">{XI.card ? XI.card({ width: 17, height: 17 }) : '▭'}</span>
                <span className="ept-meta"><span className="ept-t">Attach my digital card</span><span className="ept-s">Adds a tap-to-save vCard button to the email</span></span>
                <span className={'switch' + (d.followupCard ? ' on' : '')} />
              </button>
              <button className="ept-row" onClick={() => set({ followupNudge: !d.followupNudge })}>
                <span className="ept-ic">{XI.refresh ? XI.refresh({ width: 17, height: 17 }) : '↻'}</span>
                <span className="ept-meta"><span className="ept-t">Send a 2nd nudge</span><span className="ept-s">{d.followupNudge ? 'A gentle reminder if they don\u2019t reply' : 'Follow up again if there\u2019s no reply'}</span></span>
                <span className={'switch' + (d.followupNudge ? ' on' : '')} />
              </button>
              {d.followupNudge && (
                <div className="field" style={{ marginTop: -2 }}><label>Nudge after</label>
                  <div className="fup-delay">
                    {[['2880', '2 days'], ['4320', '3 days'], ['10080', '1 week'], ['20160', '2 weeks']].map(([min, lb]) => (
                      <button key={min} className={'fup-delay-opt' + ((String(d.followupNudgeDelay || '4320')) === min ? ' on' : '')} onClick={() => set({ followupNudgeDelay: min })}>{lb}</button>
                    ))}
                  </div>
                </div>
              )}
              <div className="field"><label>File new leads as</label>
                <div className="fup-stages">
                  {(((user.leadStages && user.leadStages.length) ? user.leadStages : (window.EX_STAGES || [])).map((s) => (
                    <button key={s.id} className={'fup-stage' + ((d.followupStage || 'new') === s.id ? ' on' : '')} onClick={() => set({ followupStage: s.id })}><span className="ld-dot" style={{ background: s.color }} />{s.label}</button>
                  )))}
                </div>
              </div>
              <div className="fup-preview">
                <div className="fup-preview-l">Preview</div>
                <div className="fup-preview-sub">{(d.followupSubject || 'Great to meet you, {{firstName}}!').replace(/\{\{firstName\}\}/g, 'Sofia').replace(/\{\{name\}\}/g, 'Sofia Marchetti').replace(/\{\{company\}\}/g, 'Acme').replace(/\{\{myName\}\}/g, d.name || 'You')}</div>
                <div className="fup-preview-body">{(d.followupMsg || ("Hey {{firstName}} — really enjoyed our chat. Here's my profile: tappt.io/" + (d.handle || 'you'))).replace(/\{\{firstName\}\}/g, 'Sofia').replace(/\{\{name\}\}/g, 'Sofia Marchetti').replace(/\{\{company\}\}/g, 'Acme').replace(/\{\{myName\}\}/g, d.name || 'You')}</div>
              </div>
              <label className="edit-contact-toggle" style={{ marginTop: 14 }} onClick={() => set({ followupOn: !d.followupOn })}>
                {XI.mail({ width: 17, height: 17 })} Send automatically
                <span className={'switch' + (d.followupOn ? ' on' : '')} style={{ marginLeft: 'auto' }} />
              </label>
            </div>
          </div>
        </div>
      )}
      {productEditor !== null && (
        <ProductEditor
          initial={productEditor === 'new' ? null : d.products[productEditor]}
          merchLayout={d.merchLayout || (window.getMerch ? window.getMerch(d).layout : 'carousel')}
          merchCard={d.merchCard || (window.getMerch ? window.getMerch(d).shape : 'card')}
          onSetLayout={(l) => set({ merchLayout: l, merchCard: d.merchCard || (window.getMerch ? window.getMerch(d).shape : 'card') })}
          onSetCard={(sh) => set({ merchCard: sh, merchLayout: d.merchLayout || (window.getMerch ? window.getMerch(d).layout : 'carousel') })}
          onClose={() => setProductEditor(null)}
          onSave={(prod) => { const arr = [...(d.products || [])]; if (productEditor === 'new') arr.push(prod); else arr[productEditor] = prod; set({ products: arr }); setProductEditor(null); toast && toast('Product saved'); }}
          onDelete={() => { set({ products: (d.products || []).filter((_, j) => j !== productEditor) }); setProductEditor(null); toast && toast('Product removed'); }}
        />
      )}
    </React.Fragment>
  );
}

window.EditProfile = EditProfile;
