/* =========================================================
   Tappt dashboard — shared parts: data, store, icons, preview
   (window-exported so app-main.jsx can use them)
   ========================================================= */
const { useState, useEffect, useRef, useCallback } = React;

/* ---- card catalogue (matches the marketing shop) ---- */
const CARDS = [
  { id: 'joker',          name: 'Joker',            tag: 'Wildcard · limited',  cat: 'drop', img: 'assets/cards/joker-front.png' },
  { id: 'americansuccess',name: 'American Success',  tag: 'CEO mode',            cat: 'drop', img: 'assets/cards/americansuccess-front.png' },
  { id: 'lovergirl',      name: 'Lover Girl',        tag: 'Y2K energy',          cat: 'drop', img: 'assets/cards/lovergirl-front.png' },
  { id: 'unbothered',     name: 'Unbothered',        tag: 'Beret on, phone off', cat: 'drop', img: 'assets/cards/unbothered-front.png' },
  { id: 'luckyduck',      name: 'Lucky Duck',        tag: 'Bag secured',         cat: 'drop', img: 'assets/cards/luckyduck-front.png' },
  { id: 'black',          name: 'Black',             tag: 'Matte. Quiet.',       cat: 'pro',  img: 'assets/cards/black-front.png' },
  { id: 'desert',         name: 'Desert',            tag: 'Sand-tone',           cat: 'pro',  img: 'assets/cards/desert-front.png' },
];

/* ---- link types ---- */
const LINK_TYPES = [
  { type: 'instagram', label: 'Instagram', icon: '◐', color: 'linear-gradient(135deg,#f58529,#dd2a7b 50%,#515bd4)', ph: '@yourhandle', base: 'instagram.com/' },
  { type: 'tiktok',    label: 'TikTok',    icon: '♪', color: '#000',     ph: '@yourhandle', base: 'tiktok.com/@' },
  { type: 'youtube',   label: 'YouTube',   icon: '▶', color: '#FF0000',  ph: 'Channel',     base: 'youtube.com/@' },
  { type: 'x',         label: 'X',         icon: '𝕏', color: '#000',     ph: '@yourhandle', base: 'x.com/' },
  { type: 'spotify',   label: 'Spotify',   icon: '♫', color: '#1DB954',  ph: 'Latest drop', base: 'open.spotify.com/' },
  { type: 'website',   label: 'Website',   icon: '◆', color: '#0B0B0C',  ph: 'yoursite.com', base: 'https://' },
  { type: 'email',     label: 'Email',     icon: '✉', color: '#1D6FE0',  ph: 'you@mail.com', base: 'mailto:' },
  { type: 'calendar',  label: 'Book a call', icon: '◉', color: '#E83E5C', ph: 'cal.com/you', base: 'https://' },
];
const typeMeta = (t) => LINK_TYPES.find((x) => x.type === t) || LINK_TYPES[5];

const AVATAR_COLORS = [
  'linear-gradient(135deg,#6B8299,#3a4f63)',
  'linear-gradient(135deg,#2BB8C4,#176b6b)',
  'linear-gradient(135deg,#8AFF6B,#1a7a3a)',
  'linear-gradient(135deg,#FF8A6B,#b8362e)',
  'linear-gradient(135deg,#E8FF59,#9aa82e)',
  'linear-gradient(135deg,#5CC8FF,#1f6a8a)',
  'linear-gradient(135deg,#FF6BC8,#a82e7a)',
];

/* ---- "backend" stub. Swap these for Supabase calls later. ---- */
/* IndexedDB media store — holds big media (video covers, uploaded photos,
   gallery clips) that blow the ~5MB localStorage quota. localStorage keeps
   only small `idb:<id>` tokens; the real dataURLs live here. This is why the
   video cover now survives a refresh. */
const MediaDB = {
  _p: null,
  open() {
    if (this._p) return this._p;
    this._p = new Promise((res, rej) => {
      const req = indexedDB.open('tappt_media', 1);
      req.onupgradeneeded = () => { if (!req.result.objectStoreNames.contains('m')) req.result.createObjectStore('m'); };
      req.onsuccess = () => res(req.result);
      req.onerror = () => rej(req.error);
    });
    return this._p;
  },
  async put(key, val) { const db = await this.open(); return new Promise((res, rej) => { const tx = db.transaction('m', 'readwrite'); tx.objectStore('m').put(val, key); tx.oncomplete = () => res(); tx.onerror = () => rej(tx.error); }); },
  async get(key) { const db = await this.open(); return new Promise((res, rej) => { const tx = db.transaction('m', 'readonly'); const r = tx.objectStore('m').get(key); r.onsuccess = () => res(r.result); r.onerror = () => rej(r.error); }); },
};
let _mediaSeq = 0;
/* replace large data: URLs anywhere in the user object with idb: tokens */
async function dehydrateMedia(user) {
  const clone = JSON.parse(JSON.stringify(user));
  const walk = async (obj) => {
    for (const k in obj) {
      const v = obj[k];
      if (typeof v === 'string' && v.startsWith('data:') && v.length > 24000) {
        const id = 'm' + Date.now() + '_' + (++_mediaSeq);
        try { await MediaDB.put(id, v); obj[k] = 'idb:' + id; }
        catch (e) { obj[k] = null; }   // idb failed — drop media so the lite write can't bust quota
      } else if (v && typeof v === 'object') { await walk(v); }
    }
  };
  await walk(clone);
  return clone;
}
/* SYNCHRONOUS: strip heavy data: URLs to keep a small snapshot that always fits localStorage.
   Preserves all structure/text/idb-tokens — only inlined big media is dropped from this snapshot
   (the async dehydrate restores it via IndexedDB right after). */
function stripBigMediaSync(user) {
  const clone = JSON.parse(JSON.stringify(user));
  const walk = (obj) => {
    for (const k in obj) {
      const v = obj[k];
      if (typeof v === 'string' && v.startsWith('data:') && v.length > 24000) { obj[k] = null; }
      else if (v && typeof v === 'object') { walk(v); }
    }
  };
  walk(clone);
  return clone;
}
/* swap idb: tokens back to their stored dataURLs */
async function hydrateMedia(user) {
  if (!user) return user;
  const walk = async (obj) => {
    for (const k in obj) {
      const v = obj[k];
      if (typeof v === 'string' && v.startsWith('idb:')) {
        try { const got = await MediaDB.get(v.slice(4)); if (got) obj[k] = got; } catch (e) {}
      } else if (v && typeof v === 'object') { await walk(v); }
    }
  };
  await walk(user);
  return user;
}

const Store = {
  KEY: 'tappt_app_v1',
  /* one-time colour migration: the old purple default → Tappt brand steel-blue.
     Runs on every read so any account (saved local or pulled from cloud) that still
     carries the legacy purple gets corrected automatically. No purple for anyone. */
  migrateColors(user) {
    if (!user || typeof user !== 'object') return user;
    const PURPLES = ['linear-gradient(135deg,#7C5CFF,#3a1f8a)', 'linear-gradient(135deg, #7C5CFF, #3a1f8a)', '#7C5CFF', '#7c5cff'];
    const BRAND = 'linear-gradient(135deg,#6B8299,#3a4f63)';
    if (PURPLES.indexOf(user.avatarColor) !== -1 || !user.avatarColor) user.avatarColor = BRAND;
    return user;
  },
  read() {
    try { return JSON.parse(localStorage.getItem(this.KEY)) || {}; }
    catch (e) { return {}; }
  },
  write(data) {
    try { localStorage.setItem(this.KEY, JSON.stringify(data)); return true; }
    catch (e) { return false; }
  },
  getUser() { const u = this.read().user || null; return u ? this.migrateColors(u) : null; },
  hydrateMedia, dehydrateMedia,
  /* push the latest profile to Supabase (cloud sync) when signed in */
  async syncRemote(user) {
    try {
      if (!window.TapptDB || !window.TapptDB.available()) return false;
      // uid from the LOCAL session (no network) so a save never silently bails on a hiccup
      let uid = await window.TapptDB.sessionUid();
      if (!uid) { const au = await window.TapptDB.currentUser(); uid = au && au.id; }
      if (uid) return await window.TapptDB.saveProfile(uid, user);
      return false;
    } catch (e) { /* offline / not signed in — localStorage already has it */ return false; }
  },
  /* ---- MOMENTS: route CRUD to dedicated tables when signed in ---- */
  async _uid() {
    try { if (window.TapptDB && window.TapptDB.available()) { const au = await window.TapptDB.currentUser(); return (au && au.id) || null; } } catch (e) {}
    return null;
  },
  _mom(fn) { (async () => { try { const uid = await this._uid(); if (uid) await fn(uid); } catch (e) { /* offline — localStorage already has it */ } })(); },
  /* pull the signed-in user's moments from the cloud (null = not signed in / offline) */
  async loadMoments() {
    const uid = await this._uid();
    if (!uid || !window.TapptDB.moments) return null;
    try { return await window.TapptDB.moments.list(uid); } catch (e) { return null; }
  },
  createMoment(m) { this._mom((uid) => window.TapptDB.moments.create(m, uid)); },
  deleteMoment(id) { this._mom(() => window.TapptDB.moments.remove(id)); },
  syncMomentComments(id, arr) { this._mom((uid) => window.TapptDB.moments.syncComments(id, arr, uid)); },
  /* persist a whole moment (caption/media/taps + its comment tree) — used by edits + simulated engagement */
  persistMoment(m) {
    this._mom(async (uid) => {
      await window.TapptDB.moments.update(m.id, { cap: m.cap, imgKind: m.imgKind, taps: m.taps, editedTs: m.editedTs }, uid);
      await window.TapptDB.moments.syncComments(m.id, m.comments || [], uid);
    });
  },
  /* ---- COLLECTIONS: connections / leads / tips / cards on their own tables ---- */
  async loadCollections() {
    const uid = await this._uid();
    if (!uid || !window.TapptDB.collections) return null;
    const safe = async (name) => { try { return await window.TapptDB.collections.list(name, uid); } catch (e) { return null; } };
    const [connections, leads, tips, cards] = await Promise.all([safe('connections'), safe('leads'), safe('tips'), safe('cards')]);
    const out = {};
    if (connections && connections.length) out.connections = connections;
    if (leads && leads.length) out.leads = leads;
    if (tips && tips.length) out.tips = tips;
    if (cards && cards.length) out.cards = cards;
    return out;
  },
  saveConn(c) { this._mom((uid) => window.TapptDB.collections.upsert('connections', c, uid)); },
  saveLead(l) { this._mom((uid) => window.TapptDB.collections.upsert('leads', l, uid)); },
  removeLead(id) { this._mom(() => window.TapptDB.collections.remove('leads', id)); },
  clearLeads() { this._mom((uid) => window.TapptDB.collections.clearAll('leads', uid)); },
  saveTip(t) { this._mom((uid) => window.TapptDB.collections.upsert('tips', t, uid)); },
  saveCard(c) { this._mom((uid) => window.TapptDB.collections.upsert('cards', c, uid)); },
  removeConn(id) { this._mom(() => window.TapptDB.collections.remove('connections', id)); },
  /* load the signed-in user's profile from Supabase into the Store */
  async loadRemote() {
    try {
      if (!window.TapptDB || !window.TapptDB.available()) return null;
      const au = await window.TapptDB.currentUser();
      if (!au || !au.id) return null;
      const prof = await window.TapptDB.loadProfile(au.id);
      if (prof) { const d = this.read(); d.user = Object.assign({}, d.user, prof); d.session = true; this.write(d); }
      return prof;
    } catch (e) { return null; }
  },
  /* async: externalise big media to IndexedDB, then persist tokens to localStorage */
  saveUser(user) {
    const self = this;
    const seq = (this._seq = (this._seq || 0) + 1);
    // 1. SYNCHRONOUS write of a media-stripped snapshot — guaranteed small enough to fit,
    //    so the latest STRUCTURE (links, gallery items, text, idb tokens) always persists
    //    even if a fast reload happens or IndexedDB later fails.
    try {
      const d = self.read();
      d.user = stripBigMediaSync(user);
      if (!self.write(d)) { /* even stripped failed — leave prior */ }
    } catch (e) {}
    // 2. ASYNC: externalise big media to IndexedDB, then persist the full lite (tokenised)
    //    version — but only if this is still the most recent save (guards races).
    (async () => {
      try {
        const lite = await dehydrateMedia(user);
        if (seq !== self._seq) return;
        const d = self.read(); d.user = lite; self.write(d);
      } catch (e) { /* sync snapshot above already persisted structure */ }
    })();
    // 3. CLOUD: push to Supabase (fire-and-forget; no media blobs — those go to Storage later)
    self.syncRemote(user);
    return true;
  },
  /* write the profile to localStorage ONLY (no cloud sync) — used when hydrating FROM the
     cloud on boot, so we don't echo a just-loaded profile straight back up. */
  cacheUser(user) {
    try { const d = this.read(); d.user = stripBigMediaSync(user); d.session = true; this.write(d); } catch (e) {}
    (async () => { try { const lite = await dehydrateMedia(user); const d = this.read(); d.user = lite; d.session = true; this.write(d); } catch (e) {} })();
  },
  /* wipe the local user + session entirely — used on logout AND when boot finds no Supabase
     session, so a previous account's (or the demo's) data can never leak to the next person. */
  clearLocalUser() { try { const d = this.read(); delete d.user; d.session = false; this.write(d); } catch (e) {} },
  async logout() { this.clearLocalUser(); try { if (window.TapptDB) await window.TapptDB.signOut(); } catch (e) {} },
  setSession(v) { const d = this.read(); d.session = v; this.write(d); },
  hasSession() { return !!this.read().session; },
};

/* ---- Personas: one card, multiple swappable profiles ----
   Each persona holds the *shareable* slice (bio/socials/links/contact/accent).
   The active persona is mirrored into user-level fields so OwnProfile,
   public.html and ShareSheet keep reading the same keys unchanged. */
const PERSONA_PRESETS = [
  { icon: 'moon', label: 'Personal' },
  { icon: 'briefcase', label: 'Work' },
  { icon: 'palette', label: 'Creator' },
  { icon: 'dumbbell', label: 'Gym' },
  { icon: 'plane', label: 'Travel' },
];
/* migrate any legacy emoji to a Lucide icon key */
const PERSONA_EMOJI_MAP = { '⭐': 'star', '🌙': 'moon', '💼': 'briefcase', '🎨': 'palette', '🏋️': 'dumbbell', '🏋': 'dumbbell', '✈️': 'plane', '✈': 'plane' };
function personaIconKey(p) {
  if (!p) return 'star';
  if (p.icon && I[p.icon]) return p.icon;
  if (p.emoji && PERSONA_EMOJI_MAP[p.emoji]) return PERSONA_EMOJI_MAP[p.emoji];
  return 'star';
}
/* map signup account type → the sensible default persona (label + icon).
   This makes a new user's first persona feel "made for them" — and they can
   rename/re-icon it any time in My Personas. */
const ACCT_PERSONA = {
  creator: { icon: 'palette', label: 'Creator' },
  business: { icon: 'briefcase', label: 'Business' },
  personal: { icon: 'moon', label: 'Personal' },
};
function getPersonas(user) {
  if (user.personas && user.personas.length) return user.personas;
  const base = ACCT_PERSONA[user.accountType] || { icon: 'star', label: 'Main' };
  return [{
    id: 'main', label: base.label, icon: base.icon,
    accent: user.avatarColor || AVATAR_COLORS[0],
    bio: user.bio || '', socials: user.socials || [],
    links: user.links || [], saveContact: !!user.saveContact,
  }];
}
function activePersona(user) {
  const ps = getPersonas(user);
  return ps.find((p) => p.id === user.activePersona) || ps[0];
}
/* return a user object with the given persona's content mirrored to top level */
function applyPersona(user, id) {
  const ps = getPersonas(user);
  const p = ps.find((x) => x.id === id) || ps[0];
  return {
    ...user, personas: ps, activePersona: p.id,
    bio: p.bio, socials: p.socials, links: p.links,
    saveContact: p.saveContact, avatarColor: p.accent || user.avatarColor,
  };
}
/* write current top-level shareable fields back into the active persona slot */
function syncActivePersona(user) {
  const ps = getPersonas(user);
  const aid = activePersona(user).id;
  const personas = ps.map((p) => p.id === aid
    ? { ...p, bio: user.bio, socials: user.socials, links: user.links, saveContact: !!user.saveContact, accent: user.avatarColor }
    : p);
  return { ...user, personas, activePersona: aid };
}

/* uuid for new moments so the local id == the table primary key (edits/deletes match) */
function newMomentId() {
  try { if (window.crypto && crypto.randomUUID) return crypto.randomUUID(); } catch (e) {}
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { const r = Math.random() * 16 | 0; const v = c === 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); });
}

/* fabricated analytics so the dashboard feels alive */
function genAnalytics(seed = 7) {
  let s = seed;
  const rnd = () => { s = (s * 9301 + 49297) % 233280; return s / 233280; };
  const days = Array.from({ length: 14 }, () => Math.floor(rnd() * 70) + 12);
  const taps = days.reduce((a, b) => a + b, 0);
  return { days, taps, views: Math.floor(taps * 1.8), saves: Math.floor(taps * 0.32) };
}

/* ---- icons (lucide-ish, stroked) ---- */
const I = {
  link: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><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>,
  user: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><circle cx="12" cy="8" r="4"/><path d="M4 21v-1a6 6 0 0 1 6-6h4a6 6 0 0 1 6 6v1"/></svg>,
  card: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><rect x="2" y="5" width="20" height="14" rx="2"/><path d="M2 10h20"/></svg>,
  chart: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M3 3v16a2 2 0 0 0 2 2h16"/><path d="M18 17V9M13 17V5M8 17v-3"/></svg>,
  settings: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.6 1.6 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.6 1.6 0 0 0-2.7 1.1V21a2 2 0 0 1-4 0v-.1A1.6 1.6 0 0 0 7 19.4a1.6 1.6 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.6 1.6 0 0 0-1.1-2.7H1a2 2 0 0 1 0-4h.1A1.6 1.6 0 0 0 2.6 7a1.6 1.6 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.6 1.6 0 0 0 1.8.3H7a1.6 1.6 0 0 0 1-1.5V1a2 2 0 0 1 4 0v.1a1.6 1.6 0 0 0 2.7 1.1 1.6 1.6 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.6 1.6 0 0 0-.3 1.8V7a1.6 1.6 0 0 0 1.5 1H23a2 2 0 0 1 0 4h-.1a1.6 1.6 0 0 0-1.5 1z"/></svg>,
  plus: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M12 5v14M5 12h14"/></svg>,
  image: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><rect x="3" y="3" width="18" height="18" rx="2.5"/><circle cx="8.5" cy="8.5" r="1.6"/><path d="M21 16l-5-5L5 21"/></svg>,
  qr: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinejoin="round" {...p}><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>,
  edit: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4z"/></svg>,
  trash: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/></svg>,
  dots: (p) => <svg viewBox="0 0 24 24" fill="currentColor" {...p}><circle cx="5" cy="12" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="19" cy="12" r="2"/></svg>,
  comment: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/></svg>,
  grip: (p) => <svg viewBox="0 0 24 24" fill="currentColor" {...p}><circle cx="9" cy="6" r="1.6"/><circle cx="15" cy="6" r="1.6"/><circle cx="9" cy="12" r="1.6"/><circle cx="15" cy="12" r="1.6"/><circle cx="9" cy="18" r="1.6"/><circle cx="15" cy="18" r="1.6"/></svg>,
  ext: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M7 17 17 7M7 7h10v10"/></svg>,
  logout: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4M16 17l5-5-5-5M21 12H9"/></svg>,
  check: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M20 6 9 17l-5-5"/></svg>,
  clock: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>,
  close: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M18 6 6 18M6 6l12 12"/></svg>,
  expand: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"/></svg>,
  arrow: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M5 12h14M13 6l6 6-6 6"/></svg>,
  spark: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M12 3v4M12 17v4M3 12h4M17 12h4M5.6 5.6l2.8 2.8M15.6 15.6l2.8 2.8M18.4 5.6l-2.8 2.8M8.4 15.6l-2.8 2.8"/></svg>,
  home: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M3 9.5 12 2l9 7.5"/><path d="M5 9v11a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V9"/></svg>,
  search: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>,
  locate: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><circle cx="12" cy="12" r="3.2"/><path d="M12 2v3M12 19v3M2 12h3M19 12h3"/></svg>,
  filter: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M3 5h18M6 12h12M10 19h4"/></svg>,
  pin: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0z"/><circle cx="12" cy="10" r="3"/></svg>,
  box: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><path d="m3.3 7 8.7 5 8.7-5M12 22V12"/></svg>,
  truck: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M14 18V6a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v11a1 1 0 0 0 1 1h2M14 9h4l3 3v5a1 1 0 0 1-1 1h-1"/><circle cx="7" cy="18" r="2"/><circle cx="17" cy="18" r="2"/></svg>,
  camera: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>,
  users: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75"/></svg>,
  dollar: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>,
  back: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M19 12H5M12 19l-7-7 7-7"/></svg>,
  refresh: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><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>,
  lock: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>,
  wifi: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M5 12.55a11 11 0 0 1 14 0M8.5 16.1a6 6 0 0 1 7 0M2 8.82a15 15 0 0 1 20 0"/><path d="M12 20h.01"/></svg>,
  bolt: (p) => <svg viewBox="0 0 24 24" fill="currentColor" {...p}><path d="M13 2 3 14h7l-1 8 10-12h-7z"/></svg>,
  sync: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M4 9h13l-3-3M20 15H7l3 3"/></svg>,
  mail: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-10 5L2 7"/></svg>,
  phone: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.96.36 1.9.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.9.34 1.85.57 2.81.7A2 2 0 0 1 22 16.92z"/></svg>,
  chevron: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="m6 9 6 6 6-6"/></svg>,
  help: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><path d="M12 17h.01"/></svg>,
  company: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><rect x="4" y="3" width="16" height="18" rx="2"/><path d="M9 8h.01M9 12h.01M9 16h.01M15 8h.01M15 12h.01M15 16h.01"/></svg>,
  linkedin: (p) => <svg viewBox="0 0 24 24" fill="currentColor" {...p}><path d="M20.45 20.45h-3.56v-5.57c0-1.33-.02-3.04-1.85-3.04-1.85 0-2.14 1.45-2.14 2.94v5.67H9.34V9h3.42v1.56h.05c.48-.9 1.64-1.85 3.37-1.85 3.6 0 4.27 2.37 4.27 5.45v6.29zM5.34 7.43a2.06 2.06 0 1 1 0-4.13 2.06 2.06 0 0 1 0 4.13zM7.12 20.45H3.55V9h3.57v11.45z"/></svg>,
  moments: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .962 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.962 0z"/><path d="M20 3v4M22 5h-4M4 17v2M5 18H3"/></svg>,
  /* ---- exact Lucide glyphs (lucide.dev) ---- */
  sparkles: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .962 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.962 0z"/><path d="M20 3v4M22 5h-4M4 17v2M5 18H3"/></svg>,
  crown: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><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>,
  heart: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"/></svg>,
  bell: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M10.268 21a2 2 0 0 0 3.464 0M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326"/></svg>,
  video: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="m16 13 5.223 3.482a.5.5 0 0 0 .777-.416V7.87a.5.5 0 0 0-.752-.432L16 10.5"/><rect x="2" y="6" width="14" height="12" rx="2"/></svg>,
  message: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M7.9 20A9 9 0 1 0 4 16.1L2 22z"/></svg>,
  copy: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>,
  whatsapp: (p) => <svg viewBox="0 0 24 24" fill="currentColor" {...p}><path d="M17.47 14.38c-.3-.15-1.76-.87-2.03-.97-.27-.1-.47-.15-.67.15-.2.3-.77.97-.94 1.17-.17.2-.35.22-.65.07-.3-.15-1.26-.46-2.4-1.48-.89-.79-1.49-1.77-1.66-2.07-.17-.3-.02-.46.13-.61.13-.13.3-.35.45-.52.15-.17.2-.3.3-.5.1-.2.05-.37-.02-.52-.08-.15-.67-1.61-.92-2.21-.24-.58-.49-.5-.67-.51l-.57-.01c-.2 0-.52.07-.79.37-.27.3-1.04 1.01-1.04 2.47s1.06 2.87 1.21 3.07c.15.2 2.1 3.2 5.08 4.49.71.31 1.26.49 1.69.62.71.23 1.36.2 1.87.12.57-.08 1.76-.72 2.01-1.41.25-.69.25-1.28.17-1.41-.07-.13-.27-.2-.57-.35zM12.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.01A9.82 9.82 0 0 0 12.04 2z"/></svg>,
  flame: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M12 3q1 4 4 6.5t3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5c1.667 0 3-1.333 3-3.5q0-3-2-5z"/></svg>,
  trophy: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M6 9H4.5a2.5 2.5 0 0 1 0-5H6M18 9h1.5a2.5 2.5 0 0 0 0-5H18M4 22h16M10 14.66V17c0 .55-.47.98-.97 1.21C7.85 18.75 7 20.24 7 22M14 14.66V17c0 .55.47.98.97 1.21C16.15 18.75 17 20.24 17 22M18 2H6v7a6 6 0 0 0 12 0z"/></svg>,
  ticket: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2z"/><path d="M13 5v2M13 17v2M13 11v2"/></svg>,
  gift: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><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 8M16.5 8a2.5 2.5 0 0 0 0-5C9 3 12 8 12 8"/></svg>,
  cart: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><circle cx="8" cy="21" r="1"/><circle cx="19" cy="21" r="1"/><path d="M2.05 2.05h2l2.66 12.42a2 2 0 0 0 2 1.58h9.78a2 2 0 0 0 1.95-1.57l1.65-7.43H5.12"/></svg>,
  star: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.12 2.12 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.12 2.12 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.12 2.12 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.12 2.12 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.12 2.12 0 0 0 1.597-1.16z"/></svg>,
  calendar: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg>,
  coffee: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M10 2v2M14 2v2M16 8a1 1 0 0 1 1 1v8a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4V9a1 1 0 0 1 1-1h12zM16 8h1a3 3 0 0 1 0 6h-1"/></svg>,
  music: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg>,
  dollarsign: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>,
  globe: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20M2 12h20"/></svg>,
  eye: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0"/><circle cx="12" cy="12" r="3"/></svg>,
  userPlus: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M19 8v6M22 11h-6"/></svg>,
  pointer: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M12.586 12.586 19 19M3.688 3.037a.497.497 0 0 0-.651.651l6.5 15.999a.501.501 0 0 0 .947-.062l1.569-6.083a2 2 0 0 1 1.448-1.479l6.124-1.579a.5.5 0 0 0 .063-.947z"/></svg>,
  moon: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9"/></svg>,
  briefcase: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M16 20V4a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"/><rect x="2" y="6" width="20" height="14" rx="2"/></svg>,
  palette: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><circle cx="13.5" cy="6.5" r=".5" fill="currentColor"/><circle cx="17.5" cy="10.5" r=".5" fill="currentColor"/><circle cx="8.5" cy="7.5" r=".5" fill="currentColor"/><circle cx="6.5" cy="12.5" r=".5" fill="currentColor"/><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.563-2.512 5.563-5.563C21.5 6.012 17.5 2 12 2"/></svg>,
  dumbbell: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M14.4 14.4 9.6 9.6M18.657 21.485a2 2 0 1 1-2.829-2.828l-1.767 1.768a2 2 0 1 1-2.829-2.829l6.364-6.364a2 2 0 1 1 2.829 2.829l-1.768 1.767a2 2 0 1 1 2.828 2.829zM5.343 2.515a2 2 0 1 0-2.828 2.829l6.364 6.364a2 2 0 1 0 2.829-2.828L11.939 7.11a2 2 0 1 0-2.83-2.829z"/><path d="m2.5 21.5 1.4-1.4M21.5 2.5l-1.4 1.4"/></svg>,
  plane: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M17.8 19.2 16 11l3.5-3.5C21 6 21.5 4 21 3c-1-.5-3 0-4.5 1.5L13 8 4.8 6.2c-.5-.1-.9.1-1.1.5l-.3.5c-.2.5-.1 1 .3 1.3L9 12l-2 3H4l-1 1 3 2 2 3 1-1v-3l3-2 3.5 5.3c.3.4.8.5 1.3.3l.5-.2c.4-.3.6-.7.5-1.2z"/></svg>,
};

/* ---- Apple / Google glyphs ---- */
function AppleGlyph() {
  return <svg viewBox="0 0 24 24" fill="currentColor"><path d="M17.05 12.04c-.02-2.32 1.9-3.43 1.98-3.48-1.08-1.58-2.76-1.8-3.35-1.82-1.43-.14-2.79.84-3.51.84-.74 0-1.85-.82-3.04-.8-1.56.02-3.01.91-3.81 2.31-1.62 2.81-.41 6.96 1.17 9.24.77 1.12 1.69 2.37 2.89 2.32 1.16-.05 1.6-.75 3-.75 1.39 0 1.79.75 3.02.72 1.25-.02 2.04-1.13 2.8-2.26.88-1.3 1.25-2.56 1.27-2.63-.03-.01-2.43-.93-2.45-3.69zM14.94 5.27c.64-.78 1.07-1.85.95-2.92-.92.04-2.04.61-2.7 1.38-.59.68-1.11 1.77-.97 2.82 1.03.08 2.08-.52 2.72-1.28z"/></svg>;
}
function GoogleGlyph() {
  return <svg viewBox="0 0 24 24"><path fill="#4285F4" d="M22 12.2c0-.7-.06-1.4-.18-2H12v3.8h5.6a4.8 4.8 0 0 1-2.08 3.15v2.6h3.36C20.84 18 22 15.4 22 12.2z"/><path fill="#34A853" d="M12 22c2.7 0 4.97-.9 6.63-2.42l-3.36-2.6c-.93.63-2.13 1-3.27 1-2.52 0-4.65-1.7-5.42-3.98H3.13v2.68A10 10 0 0 0 12 22z"/><path fill="#FBBC05" d="M6.58 13.99a6 6 0 0 1 0-3.83V7.48H3.13a10 10 0 0 0 0 9.04l3.45-2.53z"/><path fill="#EA4335" d="M12 6.18c1.47 0 2.79.5 3.83 1.5l2.87-2.87A9.96 9.96 0 0 0 12 2a10 10 0 0 0-8.87 5.48l3.45 2.68C7.35 7.88 9.48 6.18 12 6.18z"/></svg>;
}

/* =========================================================
   LIVE BIO PREVIEW  (the phone screen — mirrors live profile)
   ========================================================= */
function BioPreview({ user, scale }) {
  if (!user) return null;
  const links = (user.links || []).filter((l) => l.active !== false);
  const initial = (user.name || user.handle || 'T').trim().charAt(0).toUpperCase();
  const card = CARDS.find((c) => c.id === user.activeCard);
  return (
    <div className="bio">
      <div className="bio-hero">
        <div className="bio-hero-bg">
          {card
            ? <img src={card.img} alt="" />
            : <div style={{ width: '100%', height: '100%', background: user.avatarColor || AVATAR_COLORS[0] }} />}
        </div>
        <div className="bio-av" style={{ background: user.avatarColor || AVATAR_COLORS[0] }}>{initial}</div>
      </div>
      <div className="bio-body">
        <div className="bio-name">
          {user.name || 'Your Name'}
          {(user.verified || user.pro) && <svg width="15" height="15" viewBox="0 0 24 24" style={{ flex: 'none', marginLeft: 5, verticalAlign: '-2px' }} aria-label="Verified"><path fill="var(--brand)" 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="bio-handle">@{user.handle || 'handle'}</div>
        {user.bio && <div className="bio-desc">{user.bio}</div>}
        {links.length > 0 && (
          <div className="bio-links">
            {links.map((l) => {
              const m = typeMeta(l.type);
              return (
                <div className="bio-link" key={l.id}>
                  <span className="li" style={{ background: m.color }}>{m.icon}</span>
                  {l.label || m.label}
                  <span className="la">{I.ext({ width: 13, height: 13 })}</span>
                </div>
              );
            })}
          </div>
        )}
        {links.length === 0 && (
          <div style={{ marginTop: 18, padding: '18px 14px', border: '1px dashed rgba(255,255,255,0.15)', borderRadius: 13, textAlign: 'center', fontSize: 12, color: '#8a8a90' }}>
            Add links to see them here ↓
          </div>
        )}
        <div className="bio-foot">TAPPT · YOUR WORLD. ONE PROFILE.</div>
      </div>
    </div>
  );
}

/* ---- marketing-style theme toggle ---- */
function ThemeToggle({ theme, onToggle }) {
  return (
    <button className="theme-tog" onClick={onToggle} aria-label="Toggle theme">
      <svg className="moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
      <svg className="sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><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>
    </button>
  );
}

/* ===========================================================
   PLATFORM + PRO PRICING
   The iOS app is the SAME web code wrapped in Capacitor, so Apple's
   IAP commission only applies when running inside the native iOS shell.
   We bump the price ONLY there to absorb the cut; web + Android web
   keep the £7 / £59 Stripe price. (Apple may snap £x.97 → £x.99 at the
   App Store price-point step — set the closest point in App Store Connect.)
   =========================================================== */
function tapptPlatform() {
  try {
    if (window.Capacitor && typeof window.Capacitor.getPlatform === 'function') {
      return window.Capacitor.getPlatform(); // 'ios' | 'android' | 'web'
    }
  } catch (e) {}
  return 'web';
}
function isAppleIAP() { return tapptPlatform() === 'ios'; }

// store-specific Pro pricing. `via` tells the upgrade handler which checkout to use.
const PRO_PRICING_WEB = {
  monthly: '£7.99', monthlyNum: 7.99,
  yearly: '£71.88', yearlyNum: 71.88,
  yearlyPerMo: '£5.99/mo', save: 'save 25%',
  via: 'stripe',
};
const PRO_PRICING_IOS = {
  monthly: '£7.99', monthlyNum: 7.99,
  yearly: '£71.88', yearlyNum: 71.88,
  yearlyPerMo: '£5.99/mo', save: 'save 25%',
  via: 'iap',
};
function proPricing() { return isAppleIAP() ? PRO_PRICING_IOS : PRO_PRICING_WEB; }

/* convert an exchange/lead-shaped record into a connection-shaped record (exchanges now
   land in Connections — the inbox — and get promoted to Leads manually) */
function leadToConn(l) {
  const place = l.metAt || (l.location && l.location.place) || '';
  return {
    id: l.id || newMomentId(), name: l.name || '', handle: l.handle || '',
    persona: l.persona || (l.company ? 'Work' : 'Personal'),
    place: place, when: 'Just now', got: [], tags: l.tags || [],
    photo: l.photo || null, accent: l.accent || 'linear-gradient(135deg,#6B8299,#3a4f63)',
    lat: (l.location && l.location.lat != null) ? l.location.lat : (l.lat != null ? l.lat : null),
    lng: (l.location && l.location.lng != null) ? l.location.lng : (l.lng != null ? l.lng : null),
    location: l.location || null, via: l.via || 'bio', source: l.source || 'Exchange',
    email: l.email || '', phone: l.phone || '', company: l.company || '',
    role: l.jobTitle || l.role || '', jobTitle: l.jobTitle || l.role || '',
    linkedin: l.linkedin || '', website: l.website || '', answer: l.answer || '',
    custom: l.custom || [], note: l.notes || l.note || '', ts: l.ts || Date.now(),
    icon: l.company ? 'briefcase' : 'moon',
  };
}

/* expose to app-main.jsx */
Object.assign(window, {
  CARDS, LINK_TYPES, typeMeta, AVATAR_COLORS, Store, genAnalytics,
  I, AppleGlyph, GoogleGlyph, BioPreview, ThemeToggle,
  PERSONA_PRESETS, getPersonas, activePersona, applyPersona, syncActivePersona, personaIconKey,
  newMomentId, newId: newMomentId, leadToConn,
  tapptPlatform, isAppleIAP, proPricing,
  useState, useEffect, useRef, useCallback,
});
