// app.jsx — Time Card + Box Rental generator. Mode switch, live overlay previews,
// per-mode saved lists, shared profile/signature, Tweaks (incl. workspace layout).
const { useState: useS, useEffect: useE, useMemo, useRef: useR } = React;

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "uiFont": "Plus Jakarta Sans",
  "formFont": "Helvetica",
  "baseSize": 14,
  "density": "regular",
  "accent": "#3f7d5c",
  "palette": "forest",
  "previewScale": 100,
  "dark": false
}/*EDITMODE-END*/;

const UI_FONTS = ['Hanken Grotesk', 'Archivo', 'Spline Sans', 'IBM Plex Sans', 'Manrope', 'Plus Jakarta Sans', 'Space Grotesk', 'DM Sans', 'Sora', 'Bricolage Grotesque'];
// Fonts printed ON the form. The first three are pdf-lib standard fonts (instant,
// no fetch). The rest are real fonts embedded into the PDF via fontkit so the
// download matches the preview. css = preview stack.
const FORM_FONTS = [
  { value: 'Helvetica', label: 'Helvetica (sans)', css: "'Helvetica Neue',Helvetica,Arial,sans-serif" },
  { value: 'Times', label: 'Times (serif)', css: "'Times New Roman',Times,Georgia,serif" },
  { value: 'Courier', label: 'Courier (mono)', css: "'Courier New',Courier,monospace" },
  { value: 'Architects Daughter', label: 'Architects Daughter', css: "'Architects Daughter',cursive" },
  { value: 'Patrick Hand', label: 'Patrick Hand', css: "'Patrick Hand',cursive" },
  { value: 'Kalam', label: 'Kalam (handwritten)', css: "'Kalam',cursive" },
  { value: 'Permanent Marker', label: 'Permanent Marker', css: "'Permanent Marker',cursive" },
  { value: 'Special Elite', label: 'Special Elite (typewriter)', css: "'Special Elite',monospace" },
  { value: 'Courier Prime', label: 'Courier Prime (typewriter)', css: "'Courier Prime',monospace" },
  { value: 'Anton', label: 'Anton (bold)', css: "'Anton',sans-serif" },
  { value: 'Bebas Neue', label: 'Bebas Neue (tall)', css: "'Bebas Neue',sans-serif" },
  { value: 'Pacifico', label: 'Pacifico (script)', css: "'Pacifico',cursive" },
  { value: 'Lobster', label: 'Lobster (script)', css: "'Lobster',cursive" },
];
const ACCENTS = ['#3f7d5c', '#2f6f7a', '#a8763e', '#7c5e8c', '#9a5a4c', '#b54a4a', '#2f5fa8', '#c08a2e', '#127a6b', '#5b6cc4', '#8a5a2b', '#1f1f1f'];

// One-click full-UI palettes. 'forest' reproduces the built-in default exactly.
// Each sets the workspace + chrome surfaces (light mode) and its own accent.
const PALETTES = {
  forest:    { label: 'Forest paper',     bg: '#d3c9b3', panel: '#fbf9f4', surface: '#ffffff', bg2: '#efeade', sel: '#efeadd', field: '#ffffff', ink: '#2c2820', ink2: '#544d40', muted: '#8a8068', line: '#d6ccb8', line2: '#e7e0d0', accent: '#3f7d5c', accentPress: '#336449' },
  slate:     { label: 'Slate & teal',     bg: '#c4c9cf', panel: '#f7f9fa', surface: '#ffffff', bg2: '#e7ebee', sel: '#e3e8ec', field: '#ffffff', ink: '#22282e', ink2: '#48515a', muted: '#7c8590', line: '#cdd4da', line2: '#e2e7eb', accent: '#2f6f7a', accentPress: '#255861' },
  blueprint: { label: 'Blueprint',        bg: '#b9c2cf', panel: '#f5f7fb', surface: '#ffffff', bg2: '#e4e9f1', sel: '#dde4ee', field: '#ffffff', ink: '#1f2733', ink2: '#3f4a5a', muted: '#727d8e', line: '#c7cfdb', line2: '#e0e5ee', accent: '#2f5fa8', accentPress: '#264d88' },
  amber:     { label: 'Charcoal & amber', bg: '#cdc7bb', panel: '#faf8f3', surface: '#ffffff', bg2: '#ece6da', sel: '#e7e1d4', field: '#ffffff', ink: '#292621', ink2: '#4d473c', muted: '#877e6c', line: '#d3cabb', line2: '#e6e0d2', accent: '#c08a2e', accentPress: '#a0731f' },
  rose:      { label: 'Rose clay',        bg: '#d8c6c2', panel: '#fcf7f6', surface: '#ffffff', bg2: '#f0e4e2', sel: '#ecdedb', field: '#ffffff', ink: '#2e2522', ink2: '#574842', muted: '#93827c', line: '#ddccc7', line2: '#ece0dc', accent: '#9a5a4c', accentPress: '#7e483c' },
  plum:      { label: 'Lavender ink',     bg: '#ccc4d0', panel: '#f9f7fb', surface: '#ffffff', bg2: '#e9e3ef', sel: '#e4dcec', field: '#ffffff', ink: '#28232e', ink2: '#4c4458', muted: '#847a92', line: '#d2cadb', line2: '#e6e0ee', accent: '#7c5e8c', accentPress: '#634a70' },
  sand:      { label: 'Sand & rust',      bg: '#d9cdb6', panel: '#fbf8f1', surface: '#ffffff', bg2: '#efe7d6', sel: '#ebe1cd', field: '#ffffff', ink: '#2c271d', ink2: '#524a39', muted: '#8c8166', line: '#d8ccb2', line2: '#e9e1cd', accent: '#b06a3a', accentPress: '#8f5226' },
  mono:      { label: 'Mono ink',         bg: '#c9c9cc', panel: '#f8f8f9', surface: '#ffffff', bg2: '#e9e9ec', sel: '#e3e3e7', field: '#ffffff', ink: '#1f1f22', ink2: '#45454a', muted: '#7d7d84', line: '#d0d0d5', line2: '#e6e6ea', accent: '#1f1f1f', accentPress: '#000000' },
};
const PALETTE_SURFACE_KEYS = ['--bg','--panel','--paper','--surface','--bg2','--sel','--field','--field-focus','--ink','--ink2','--muted','--line','--line2','--header','--header-line','--on-header','--on-header-muted'];
const DENSITY = { compact: 0.82, regular: 1, comfy: 1.18 };

// ── Entitlement (access to clean PDF export) ──────────────────────────────────
// Default-OPEN today, so the app behaves exactly as before (no regression).
// When the server + auth land: flip ENTITLEMENT_DEFAULT to false and have the
// auth layer write a `tg_entitlement` token to localStorage — that token then
// drives who can export. Shape: {"entitled":true,"email":"x@y.com","comped":false,"exp":1750000000000}
// (exp optional, ms epoch; past exp → falls back to default). See HANDOVER-PAYMENTS.md.
const ENTITLEMENT_DEFAULT = true;
function readEntitlement() {
  try {
    const raw = localStorage.getItem('tg_entitlement');
    if (raw) {
      const e = JSON.parse(raw);
      if (e && typeof e.entitled === 'boolean') {
        if (e.exp && Date.now() > e.exp) return ENTITLEMENT_DEFAULT; // expired → fall back
        return e.entitled;
      }
    }
  } catch (_) {}
  return ENTITLEMENT_DEFAULT;
}

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);

  const [mode, setMode] = useS('tc');               // 'tc' | 'kit'
  const trial = React.useMemo(() => {
    try { return new URLSearchParams(window.location.search).get('try') === '1'; } catch (e) { return false; }
  }, []);
  // Does this user have paid/comped access? (default-open until the server lands)
  const entitled = React.useMemo(readEntitlement, []);
  // Export is disabled while locked: either the sample sandbox OR not entitled.
  const locked = trial || !entitled;
  const [profile, setProfile] = useS(() => trial ? { ...DEFAULT_PROFILE } : Store.getProfile());
  const [cards, setCards] = useS(() => trial ? [] : Store.getCards());
  const [recents, setRecents] = useS(() => trial ? [] : Store.getRecents());
  const [kits, setKits] = useS(() => trial ? [] : Store.getKits());

  const [activeId, setActiveId] = useS(null);
  const [card, setCard] = useS({ ...DEFAULT_CARD });
  const [activeKitId, setActiveKitId] = useS(null);
  const [kit, setKit] = useS({ ...DEFAULT_KIT });

  const [profileOpen, setProfileOpen] = useS(false);
  const [libMenuOpen, setLibMenuOpen] = useS(false);
  const [instrOpen, setInstrOpen] = useS(false);
  const [sigOpen, setSigOpen] = useS(false);
  const [annotateMode, setAnnotateMode] = useS(false);
  const [toast, setToast] = useS('');
  const showToast = (m) => { setToast(m); setTimeout(() => setToast(''), 2600); };

  // auto-fit preview to pane width (paper width differs by form)
  const paneRef = useR(null);
  const [fit, setFit] = useS(1);
  const paperW = mode === 'kit' ? KIT_FORM.w : 792;
  const paperH = mode === 'kit' ? KIT_FORM.h : 612;
  const recomputeFit = React.useCallback(() => {
    const el = paneRef.current;
    if (!el) return;
    // clientWidth/Height INCLUDE padding; the form must fit the content box, so
    // subtract the stage's actual padding before dividing by the bare paper size.
    const cs = getComputedStyle(el);
    const padX = parseFloat(cs.paddingLeft) + parseFloat(cs.paddingRight);
    const padY = parseFloat(cs.paddingTop) + parseFloat(cs.paddingBottom);
    const fw = (el.clientWidth - padX) / paperW;
    const fh = (el.clientHeight - padY) / paperH;
    setFit(Math.max(0.3, Math.min(2.0, fw, fh)));
  }, [paperW, paperH]);
  useE(() => {
    // ResizeObserver doesn't reliably fire on attach here, so measure imperatively
    // after layout/fonts settle, and also listen to window resize.
    const timers = [
      requestAnimationFrame(recomputeFit),
      setTimeout(recomputeFit, 60),
      setTimeout(recomputeFit, 200),
      setTimeout(recomputeFit, 500),
    ];
    const onResize = () => recomputeFit();
    window.addEventListener('resize', onResize);
    let ro = null;
    const el = paneRef.current;
    if (el && typeof ResizeObserver !== 'undefined') { ro = new ResizeObserver(recomputeFit); ro.observe(el); }
    return () => {
      cancelAnimationFrame(timers[0]); timers.slice(1).forEach(clearTimeout);
      window.removeEventListener('resize', onResize); if (ro) ro.disconnect();
    };
  }, [recomputeFit, mode]);

  useE(() => {
    if (trial) return;              // sample mode: never write over the real saved profile
    const ok = Store.saveProfile(profile);
    if (!ok && profile.signature) showToast('⚠ Signature too large to save — try a simpler/smaller image');
  }, [profile]);
  // On boot, adopt the theme chosen on the main pages (shared 'tg_theme' key).
  useE(() => {
    try {
      const saved = localStorage.getItem('tg_theme');
      if (saved === 'dark' || saved === 'light') {
        const wantDark = saved === 'dark';
        if (wantDark !== t.dark) setTweak('dark', wantDark);
      }
    } catch (e) {}
  }, []);
  // Mirror the generator's theme choice back to the shared key so the main pages match.
  useE(() => {
    try { localStorage.setItem('tg_theme', t.dark ? 'dark' : 'light'); } catch (e) {}
  }, [t.dark]);
  useE(() => {
    const r = document.documentElement;
    r.style.setProperty('--ui-font', `'${t.uiFont}'`);
    const ff = (FORM_FONTS.find(f => f.value === t.formFont) || FORM_FONTS[0]);
    r.style.setProperty('--form-font', ff.css);
    r.style.setProperty('--base', t.baseSize + 'px');
    r.style.setProperty('--dsc', DENSITY[t.density] || 1);
    // one-click palette: apply surface vars in LIGHT mode only (inline vars would
    // otherwise out-rank the dark stylesheet). In dark, clear them so the dark
    // block governs; the accent still applies in both modes.
    const pal = PALETTES[t.palette] || PALETTES.forest;
    const surf = {
      '--bg': pal.bg, '--panel': pal.panel, '--paper': pal.panel, '--surface': pal.surface,
      '--bg2': pal.bg2, '--sel': pal.sel, '--field': pal.field, '--field-focus': pal.field,
      '--ink': pal.ink, '--ink2': pal.ink2, '--muted': pal.muted, '--line': pal.line, '--line2': pal.line2,
      '--header': pal.panel, '--header-line': pal.line, '--on-header': pal.ink, '--on-header-muted': pal.muted,
    };
    if (t.dark) { PALETTE_SURFACE_KEYS.forEach(k => r.style.removeProperty(k)); }
    else { Object.entries(surf).forEach(([k, v]) => r.style.setProperty(k, v)); }
    r.style.setProperty('--accent', t.accent);
    r.setAttribute('data-theme', t.dark ? 'dark' : 'light');
  }, [t]);

  const parsed = useMemo(() => parseTimecard(card.pasteText, { weekEnding: card.weekEnding, autoMeal: card.autoMeal !== false }), [card.pasteText, card.weekEnding, card.autoMeal]);
  const weekRows = useMemo(() => buildWeekRows(parsed), [parsed]);

  // ── time card actions ──
  const newCard = () => { setActiveId(null); setCard({ ...DEFAULT_CARD }); };
  const saveCard = () => {
    if (trial) {                     // sample mode: saving is disabled — send to checkout
      showToast('Saving needs access. Get access to save your timecards.');
      setTimeout(() => { window.location.href = 'index.html#checkout'; }, 1400);
      return;
    }
    const id = activeId || newCardId();
    // stash a tiny summary so the Library page can show hrs/OT/PD without re-parsing
    const h = cardHours({ ...card });
    const days = effectivePerDiemDays(card, parsed);
    const rate = card.perDiemRate !== '' ? card.perDiemRate : profile.perDiemRate;
    const sum = { hours: h.total, ot: h.ot, pd: perDiemTotal(rate, days) };
    const rec = { id, ...card, weekEndingStr: parsed.weekEndingStr, sum, updatedAt: Date.now() };
    setCards(Store.upsertCard(rec));
    if (card.workCity || card.workState) setRecents(Store.addRecent(card.workCity, card.workState));
    setActiveId(id);
    showToast('Time card saved');
  };
  const openCard = (id) => {
    const c = (trial ? cards : Store.getCards()).find(x => x.id === id); if (!c) return;
    setActiveId(id);
    const fields = Object.assign({}, c);
    delete fields.weekEndingStr; delete fields.updatedAt; delete fields.id;
    setCard({ ...DEFAULT_CARD, ...fields });
  };
  const deleteCard = (id) => { setCards(trial ? cards.filter(c => c.id !== id) : Store.deleteCard(id)); if (id === activeId) newCard(); };

  // ── kit form actions ──
  const newKit = () => { setActiveKitId(null); setKit({ ...DEFAULT_KIT }); };
  const saveKit = () => {
    if (trial) {                     // sample mode: saving is disabled — send to checkout
      showToast('Saving needs access. Get access to save your forms.');
      setTimeout(() => { window.location.href = 'index.html#checkout'; }, 1400);
      return;
    }
    const id = activeKitId || newKitId();
    const we = parseWeekEnding(kit.weekEnding);
    const rec = { id, ...kit, weekEndingStr: we ? fmtDateMMDDYY(we) : '', sum: { rate: kit.rentalRate, per: kit.rentalPer }, updatedAt: Date.now() };
    setKits(Store.upsertKit(rec));
    setActiveKitId(id);
    showToast('Kit form saved');
  };
  const openKit = (id) => {
    const k = (trial ? kits : Store.getKits()).find(x => x.id === id); if (!k) return;
    setActiveKitId(id);
    const fields = Object.assign({}, k);
    delete fields.weekEndingStr; delete fields.updatedAt; delete fields.id;
    setKit({ ...DEFAULT_KIT, ...fields });
  };
  const deleteKit = (id) => { setKits(trial ? kits.filter(k => k.id !== id) : Store.deleteKit(id)); if (id === activeKitId) newKit(); };

  // ── auto-save: persist as you type (debounced). No "Save" button needed. ──
  const [savedTick, setSavedTick] = useS(0);   // bumps each time an autosave lands (drives the "Saved" pulse)
  const cardTimer = useR(null);
  const kitTimer = useR(null);

  useE(() => {
    if (trial || isKit) return;
    const meaningful = (card.pasteText && card.pasteText.trim()) || card.projectTitle || card.weekEnding;
    if (!meaningful) return;
    clearTimeout(cardTimer.current);
    cardTimer.current = setTimeout(() => {
      const id = activeId || newCardId();
      const h = cardHours({ ...card });
      const days = effectivePerDiemDays(card, parsed);
      const rate = card.perDiemRate !== '' ? card.perDiemRate : profile.perDiemRate;
      const sum = { hours: h.total, ot: h.ot, pd: perDiemTotal(rate, days) };
      const rec = { id, ...card, weekEndingStr: parsed.weekEndingStr, sum, updatedAt: Date.now() };
      setCards(Store.upsertCard(rec));
      if (card.workCity || card.workState) setRecents(Store.addRecent(card.workCity, card.workState));
      if (!activeId) setActiveId(id);
      setSavedTick(t => t + 1);
    }, 700);
    return () => clearTimeout(cardTimer.current);
  }, [card, activeId, isKit, trial]);

  useE(() => {
    if (trial || !isKit) return;
    const meaningful = kit.company || kit.kitType || kit.rentalRate || (kit.inventory && kit.inventory.trim()) || kit.weekEnding;
    if (!meaningful) return;
    clearTimeout(kitTimer.current);
    kitTimer.current = setTimeout(() => {
      const id = activeKitId || newKitId();
      const we = parseWeekEnding(kit.weekEnding);
      const rec = { id, ...kit, weekEndingStr: we ? fmtDateMMDDYY(we) : '', sum: { rate: kit.rentalRate, per: kit.rentalPer }, updatedAt: Date.now() };
      setKits(Store.upsertKit(rec));
      if (!activeKitId) setActiveKitId(id);
      setSavedTick(t => t + 1);
    }, 700);
    return () => clearTimeout(kitTimer.current);
  }, [kit, activeKitId, isKit, trial]);

  // ── boot: honor deep-link params from the Library page ──
  // index.html?mode=kit · ?open=<id> (id prefix picks the form) · ?profile=1
  useE(() => {
    try {
      const q = new URLSearchParams(window.location.search);
      const openId = q.get('open');
      if (openId) {
        if (openId.indexOf('kit_') === 0) { setMode('kit'); openKit(openId); }
        else { setMode('tc'); openCard(openId); }
      } else {
        const m = q.get('mode');
        if (m === 'kit') setMode('kit'); else if (m === 'tc') setMode('tc');
      }
      if (q.get('profile') === '1') setProfileOpen(true);
    } catch (e) {}
  }, []);

  // ── work-location autofill ──
  const onCity = (v) => {
    const region = lookupRegion(v);
    setCard(p => ({ ...p, workCity: v, workState: (region && !p.workState) ? region : p.workState }));
  };
  const onRegionBlur = () => setCard(p => ({ ...p, workState: normalizeRegion(p.workState) }));
  const onUseRecent = (r) => setCard(p => ({ ...p, workCity: r.city, workState: r.state }));

  const onBackup = () => {
    try { const fn = window.exportBackup(); showToast('Backed up · ' + fn); }
    catch (e) { showToast('Backup failed: ' + e.message); }
  };
  const onRestore = (file) => {
    const r = new FileReader();
    r.onload = () => {
      let obj;
      try { obj = JSON.parse(r.result); }
      catch (e) { showToast('⚠ Not a valid backup file (could not read JSON).'); return; }
      const v = window.validateBackup(obj);
      if (!v.ok) { showToast('⚠ ' + v.error); return; }   // bad file → touch nothing
      const when = obj.exportedAt ? new Date(obj.exportedAt).toLocaleDateString() : 'unknown date';
      if (!window.confirm(`This will replace your current profile and saved time cards with this backup (from ${when}). Continue?`)) return;
      const res = window.applyBackup(obj);
      if (!res.ok) { showToast('⚠ ' + (res.error || 'Restore failed.')); return; }
      showToast('Restored — reloading…');
      setTimeout(() => location.reload(), 700);
    };
    r.onerror = () => showToast('⚠ Could not read that file.');
    r.readAsText(file);
  };

  const generatePDF = async (e) => {
    if (locked) {
      showToast(trial
        ? 'Sample mode · exporting is disabled. Get access to download clean PDFs.'
        : 'Exporting needs access. Get access to download clean PDFs.');
      setTimeout(() => { window.location.href = 'index.html#checkout'; }, 1400);
      return;
    }
    if (e && e.currentTarget && window.paydayFX) window.paydayFX(e.currentTarget);
    try {
      const tcInitials = (card.initials || profile.initials || 'ABC');
      const kitInitials = (kit.initials || profile.initials || 'ABC');
      const fname = mode === 'kit'
        ? await window.generateKitPDF({ profile, kit, initials: kitInitials, formFont: t.formFont })
        : await window.generateTimecardPDF({ profile, card, parsed, weekRows, initials: tcInitials, formFont: t.formFont });
      showToast('Downloaded ' + fname);
    } catch (e) { showToast('PDF error: ' + e.message); console.error(e); }
  };

  const profileReady = profile.fullName && profile.occupation;
  const isKit = mode === 'kit';
  const wePill = isKit ? (parseWeekEnding(kit.weekEnding) ? fmtDateMMDDYY(parseWeekEnding(kit.weekEnding)) : '') : parsed.weekEndingStr;
  const fnameHint = isKit
    ? kitFilename(kit.weekEnding, kit.initials || profile.initials || 'ABC', kit.kitType, profile.fileNamePatternKit)
    : pdfFilename(parsed.weekEnding, card.initials || profile.initials || 'ABC', profile.fileNamePatternTC, card.projectTitle);

  // library (saved + new) — relocated out of a sidebar into a dock / dropdown
  const libLabel = isKit ? 'Kit forms' : 'Time cards';
  const libNewLabel = isKit ? 'kit form' : 'time card';
  const byRecent = (arr) => [...arr].sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0));
  // Time cards carry an hours/OT tally + their project (= folder). Kits stay flat.
  const tcSummary = useMemo(() => {
    let hours = 0, ot = 0; const projects = new Set();
    for (const c of cards) { const h = cardHours(c); hours += h.total; ot += h.ot; projects.add((c.projectTitle || 'Untitled').trim() || 'Untitled'); }
    return { hours, ot, cards: cards.length, projects: projects.size };
  }, [cards]);
  const libItems = isKit
    ? byRecent(kits).map(k => ({ id: k.id, title: (k.kitType ? k.kitType + ' Kit' : 'Kit') + (k.company ? ' · ' + k.company : ''), sub: 'W/E ' + (k.weekEndingStr || '—') }))
    : byRecent(cards).map(c => { const h = cardHours(c); return { id: c.id, title: c.projectTitle || 'Untitled', sub: 'W/E ' + (c.weekEndingStr || '—'), project: (c.projectTitle || 'Untitled').trim() || 'Untitled', hours: h.total, ot: h.ot }; });
  const libActive = isKit ? activeKitId : activeId;
  const libOpen = isKit ? openKit : openCard;
  const libDelete = isKit ? deleteKit : deleteCard;
  const libNew = isKit ? newKit : newCard;

  const sigControl = (
    <Section title="Signature">
      {profile.signature ? (
        <div className="sig-inline">
          <div className="sig-thumb"><img src={profile.signature} alt="signature" /></div>
          <Button onClick={() => setSigOpen(true)}>Change signature</Button>
          <button className="link-btn" onClick={() => setProfile(p => ({ ...p, signature: '' }))}>Remove</button>
        </div>
      ) : (
        <Button onClick={() => setSigOpen(true)}>+ Add signature</Button>
      )}
    </Section>
  );

  return (
    <div className={'app' + (locked ? ' is-trial' : '')}>
      {locked && (
        <div className="trial-bar">
          <span className="trial-badge">{trial ? 'SAMPLE' : 'LOCKED'}</span>
          <span className="trial-msg">{trial
            ? "You're trying a sample. Filling and previewing is free. Exporting a clean PDF needs access."
            : 'Filling and previewing is free. Exporting a clean PDF needs access.'}</span>
          <a className="trial-cta" href="index.html#checkout">Get access</a>
        </div>
      )}
      <header className="topbar">
        <div className="brand">
          <a className="brand-home" href={locked ? 'index.html' : 'Library.html'} title={locked ? 'Home' : 'Back to your library'} aria-label={locked ? 'Home' : 'Back to your library'}>
            <img className="brand-word light" src="assets/tg-wordmark.svg" alt="Timecard Genny" />
            <img className="brand-word dark" src="assets/tg-wordmark-dark.svg" alt="Timecard Genny" />
          </a>
          <span className="brand-sub">{isKit ? 'Kit rental' : 'Time card'}</span>
          {!locked && (
          <a className="customize-btn instr-btn" href="Library.html" title="Your library">
            <span className="cz-glyph" aria-hidden="true"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path></svg></span>Library
          </a>
          )}
          <button className="customize-btn instr-btn" onClick={() => { setInstrOpen(true); try { window.stickerPop && window.stickerPop('assets/sticker-face.png'); } catch (e) {} }} title="How to use">
            <span className="cz-glyph" aria-hidden="true">?</span>How to
          </button>
        </div>
        <nav className="mode-tabs">
          <button className={'mode-tab' + (!isKit ? ' on' : '')} onClick={() => setMode('tc')}>Time Card</button>
          <button className={'mode-tab' + (isKit ? ' on' : '')} onClick={() => setMode('kit')}>Kit Rental</button>
        </nav>
        <div className="topbar-right">
          {wePill && (
            <div className="we-pill">
              <span className="we-label">Week ending</span>
              <span className="we-val mono">{wePill}</span>
            </div>
          )}
          <button className="customize-btn" onClick={() => window.postMessage({ type: '__activate_edit_mode' }, '*')} title="Fonts, colors & layout">
            <span className="cz-glyph" aria-hidden="true">✦</span>Customize
          </button>
          <button className={'profile-chip' + (profileReady ? '' : ' empty')} onClick={() => setProfileOpen(true)}>
            <span className="pc-avatar">{(profile.fullName || '?').slice(0, 1).toUpperCase()}</span>
            <span className="pc-text">
              <span className="pc-name">{profile.fullName || 'Set up profile'}</span>
              <span className="pc-occ muted sm">{profile.occupation}</span>
            </span>
          </button>
        </div>
      </header>

      <div className="body">
        <div className="editor">
          {isKit ? (
            <>
              <KitEditor profile={profile} kit={kit} setKit={setKit} />
              {sigControl}
              <div className="editor-actions">
                {trial
                  ? <Button onClick={saveKit}>Save form</Button>
                  : <span className="autosave" key={savedTick}>{activeKitId ? <><span className="as-check">✓</span> Saved to your library</> : 'Saves automatically as you type'}</span>}
                <Button variant="primary" onClick={generatePDF}>{locked ? '🔒 Export (needs access)' : 'Generate PDF'}</Button>
              </div>            </>
          ) : (
            <>
              <TimeCardFields card={card} setCard={setCard} parsed={parsed} profile={profile} setProfile={setProfile} />
              <LocationFields card={card} setCard={setCard} recents={recents} onCity={onCity} onUseRecent={onUseRecent} onRegionBlur={onRegionBlur} />
              <PasteInput value={card.pasteText} onChange={(v) => setCard(p => ({ ...p, pasteText: v }))} parsed={parsed}
                autoMeal={card.autoMeal} onAutoMeal={(v) => setCard(p => ({ ...p, autoMeal: v }))} />
              <PayFields profile={profile} card={card} setCard={setCard} parsed={parsed} />
              {sigControl}
              <div className="editor-actions">
                {trial
                  ? <Button onClick={saveCard}>Save card</Button>
                  : <span className="autosave" key={savedTick}>{activeId ? <><span className="as-check">✓</span> Saved to your library</> : 'Saves automatically as you type'}</span>}
                <Button variant="primary" onClick={generatePDF}>{locked ? '🔒 Export (needs access)' : 'Generate PDF'}</Button>
              </div>
            </>
          )}
          <p className="fname-hint mono">{fnameHint}</p>
        </div>

        <main className="preview-pane">
          <div className="preview-stage" ref={paneRef}>
            <div className="annot-toolbar">
              <button type="button" className={'annot-toggle' + (annotateMode ? ' on' : '')}
                onClick={() => setAnnotateMode(v => !v)}
                title="Click anywhere on the form to type a note">
                <span className="at-glyph" aria-hidden="true">✎</span>
                {annotateMode ? 'Done adding text' : 'Add text'}
              </button>
              {annotateMode && <span className="annot-hint">Click any blank spot on the form to type · drag ⎇ to move</span>}
            </div>
            <div className="preview-scaler" style={{ '--pscale': (fit * t.previewScale / 100) }}>
              {locked && (
                <div className="trial-wm" aria-hidden="true">
                  <div className="trial-wm-inner">
                    <span className="trial-wm-line">SAMPLE &middot; NOT FOR SUBMISSION</span>
                    <span className="trial-wm-line">SAMPLE &middot; NOT FOR SUBMISSION</span>
                    <span className="trial-wm-line">SAMPLE &middot; NOT FOR SUBMISSION</span>
                  </div>
                </div>
              )}
              {isKit
                ? <KitFormOverlay profile={profile} kit={kit} annotateMode={annotateMode} uiScale={fit * t.previewScale / 100}
                    onAnnotations={(items) => setKit(p => ({ ...p, annotations: items }))} />
                : <FormOverlay profile={profile} card={card} parsed={parsed} weekRows={weekRows} annotateMode={annotateMode} uiScale={fit * t.previewScale / 100}
                    onAnnotations={(items) => setCard(p => ({ ...p, annotations: items }))} />}
            </div>
          </div>
          <LibraryDock label={libLabel} items={libItems} activeId={libActive} onOpen={libOpen} onDelete={libDelete} onNew={libNew} onMore={() => { window.location.href = locked ? 'index.html#checkout' : 'Library.html'; }} />
        </main>
      </div>

      <ProfileModal open={profileOpen} profile={profile} setProfile={setProfile} onClose={() => setProfileOpen(false)} onEditSignature={() => setSigOpen(true)} onBackup={onBackup} onRestore={onRestore} />
      <InstructionsModal open={instrOpen} onClose={() => setInstrOpen(false)} />
      <SignatureModal open={sigOpen} current={profile.signature}
        onClose={() => setSigOpen(false)}
        onApply={(url) => {
          // persist immediately (belt-and-suspenders) AND update state
          const ok = Store.saveSignature(url);
          setProfile(p => ({ ...p, signature: url }));
          showToast(ok ? 'Signature saved' : '⚠ Signature too large to save');
        }} />
      <Toast msg={toast} />

      <TweaksPanel>
        <TweakSection label="Workspace" />
        <div className="theme-switch" role="radiogroup" aria-label="Theme">
          <button type="button" className={'theme-opt' + (!t.dark ? ' on' : '')} data-k="light"
            aria-pressed={!t.dark} onClick={() => setTweak('dark', false)}><span className="ts-dot"></span>Light</button>
          <button type="button" className={'theme-opt' + (t.dark ? ' on' : '')} data-k="dark"
            aria-pressed={t.dark} onClick={() => setTweak('dark', true)}><span className="ts-dot"></span>Dark</button>
        </div>
        <TweakSlider label="Preview scale" value={t.previewScale} min={70} max={130} step={5} unit="%" onChange={v => setTweak('previewScale', v)} />
        <TweakSection label="Fonts" />
        <TweakSelect label="Interface font" value={t.uiFont} options={UI_FONTS} onChange={v => setTweak('uiFont', v)} />
        <div className="twk-help">Buttons, labels &amp; body text</div>
        <TweakSelect label="Form font" value={t.formFont} options={FORM_FONTS} onChange={v => setTweak('formFont', v)} />
        <div className="twk-help">Everything printed on the time card &amp; kit</div>
        <TweakSlider label="Text size" value={t.baseSize} min={12} max={18} step={1} unit="px" onChange={v => setTweak('baseSize', v)} />
        <TweakSection label="Density & color" />
        <TweakRadio label="Density" value={t.density} options={['compact', 'regular', 'comfy']} onChange={v => setTweak('density', v)} />
        <div className="pal-field">
          <div className="tweak-label">Theme</div>
          <div className="pal-grid">
            {Object.entries(PALETTES).map(([key, p]) => (
              <button type="button" key={key} title={p.label}
                className={'pal-swatch' + (t.palette === key ? ' on' : '')}
                onClick={() => setTweak({ palette: key, accent: p.accent })}
                style={{ background: p.bg }}>
                <span className="pal-chip" style={{ background: p.panel, borderColor: p.line }}></span>
                <span className="pal-dot" style={{ background: p.accent }}></span>
              </button>
            ))}
          </div>
        </div>
        <TweakColor label="Accent" value={t.accent} options={ACCENTS} onChange={v => setTweak('accent', v)} />
      </TweaksPanel>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
