const { useState, useEffect, useRef, useCallback } = React;
const { Plus, Link2, Copy, Check, ChevronLeft, Trash2,
  FileSpreadsheet, Download, X, AlertCircle, CheckCircle2, Clock,
  Circle, ArrowRight, Radio, Loader2, Upload, ChevronDown
} = LucideReact;

const KITT_SOUND = '/kitt.mp3';

// ─── SUPABASE ─────────────────────────────────────────────────────────────────
const SUPABASE_URL = 'https://ekbbtkaozdixzlwsegsy.supabase.co';
const SUPABASE_KEY = 'sb_publishable_pbBHyMsNfZpqKZePbsI_tA_wQJjeF6E';
const db = window.supabase.createClient(SUPABASE_URL, SUPABASE_KEY);

// ─── PARSE MODE ──────────────────────────────────────────────────────────────
// false    = local heuristics (PDF.js + SheetJS + PapaParse — free, no API)
// 'claude' = Cloudflare Worker → Anthropic API
// 'gemini' = Gemini 1.5 Flash free tier (1,500 req/day)
const PARSE_MODE = false;

/* ── REVERT TO AI PARSING ─────────────────────────────────────────────────────
   Uncomment one block below and set PARSE_MODE to 'claude' or 'gemini'

// ── Claude via Cloudflare Worker ──────────────────────────────────────────────
// const WORKER_URL = 'https://riderflow-api.sparkling-glitter-3e8c.workers.dev';
// const PARSE_PROMPT = `Parse this production pull sheet into JSON. No markdown, no preamble, just valid JSON:
// {"title":"artist/show/venue name or Imported Pull Sheet","rows":[{"section":"exact section name","item":"short item name","qty":"quantity as number or empty string","spec":"make, model, details","owner":"vendor|venue|artist|"}]}
// Rules: one row per distinct item, preserve exact section names, skip headers/footers/totals/page numbers, vendor=rental gear, venue=house systems, artist=personal instruments/licenses.`;
// const toB64 = f => new Promise((res,rej) => { const r=new FileReader(); r.onload=()=>res(r.result.split(',')[1]); r.onerror=rej; r.readAsDataURL(f); });
// async function callClaudeAPI(content) {
//   const resp = await fetch(WORKER_URL, { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({ model:'claude-sonnet-4-20250514', max_tokens:8000, messages:[{role:'user',content}] }) });
//   if (!resp.ok) throw new Error(`API ${resp.status}`);
//   const d = await resp.json();
//   const t = (d.content||[]).filter(b=>b.type==='text').map(b=>b.text).join('');
//   const c = t.replace(/```json\s* /g,'').replace(/```\s*$/g,'').trim();
//   try { return JSON.parse(c); } catch { const m=c.match(/\{[\s\S]*\}/); if(m) return JSON.parse(m[0]); throw new Error('Bad JSON'); }
// }
// async function parseFileViaClaude(file) {
//   const n=file.name.toLowerCase();
//   if (n.endsWith('.csv')||file.type==='text/csv') { const t=await file.text(); const p=Papa.parse(t,{skipEmptyLines:true}); return callClaudeAPI([{type:'text',text:`${PARSE_PROMPT}\n\n${p.data.map(r=>r.join('\t')).join('\n')}`}]); }
//   if (n.endsWith('.xlsx')||n.endsWith('.xls')) { const buf=await file.arrayBuffer(); const wb=XLSX.read(buf,{type:'array'}); const s=wb.SheetNames.map(sn=>`---${sn}---\n${XLSX.utils.sheet_to_csv(wb.Sheets[sn],{FS:'\t'})}`).join('\n'); return callClaudeAPI([{type:'text',text:`${PARSE_PROMPT}\n\n${s}`}]); }
//   if (n.endsWith('.pdf')||file.type==='application/pdf') { const data=await toB64(file); return callClaudeAPI([{type:'document',source:{type:'base64',media_type:'application/pdf',data}},{type:'text',text:PARSE_PROMPT}]); }
//   throw new Error('Unsupported file type');
// }

// ── Gemini 1.5 Flash (free: 1,500 req/day, 1M tokens/min) ───────────────────
// const GEMINI_KEY = 'YOUR_KEY_HERE';
// const GEMINI_URL = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent';
// const GEMINI_PROMPT = PARSE_PROMPT; // reuse same prompt as Claude
// async function parseFileViaGemini(file) {
//   const toB64g = f => new Promise((res,rej)=>{const r=new FileReader();r.onload=()=>res({b64:r.result.split(',')[1],mt:f.type});r.onerror=rej;r.readAsDataURL(f);});
//   const n=file.name.toLowerCase();
//   const {b64}=await toB64g(file);
//   const mimeType=n.endsWith('.pdf')?'application/pdf':n.endsWith('.csv')?'text/plain':'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
//   const resp=await fetch(`${GEMINI_URL}?key=${GEMINI_KEY}`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({contents:[{parts:[{inline_data:{mime_type:mimeType,data:b64}},{text:GEMINI_PROMPT}]}]})});
//   const d=await resp.json();
//   const t=(d.candidates?.[0]?.content?.parts||[]).map(p=>p.text||'').join('');
//   const c=t.replace(/```json\s* /g,'').replace(/```\s*$/g,'').trim();
//   try{return JSON.parse(c);}catch{const m=c.match(/\{[\s\S]*\}/);if(m)return JSON.parse(m[0]);throw new Error('Bad JSON');}
// }

──────────────────────────────────────────────────────── END AI BLOCK */



// ─── CONSTANTS ───────────────────────────────────────────────────────────────

const ROLES = [
  { id: 'artist', label: 'Artist / Mgmt',     short: 'Artist', color: '#a78bfa' },
  { id: 'venue',  label: 'Venue / Promoter',  short: 'Venue',  color: '#60a5fa' },
  { id: 'vendor', label: 'Production Vendor', short: 'Vendor', color: '#fbbf24' },
];

const STATUSES = [
  { id: 'pending',   label: 'Pending',   color: '#9ca3af', bg: 'rgba(156,163,175,0.14)' },
  { id: 'confirmed', label: 'Confirmed', color: '#00ff00', bg: 'rgba(0,255,0,0.14)'     },
  { id: 'issue',     label: 'Issue',     color: '#f87171', bg: 'rgba(248,113,113,0.16)' },
  { id: 'na',        label: 'N/A',       color: '#6a6a75', bg: 'rgba(106,106,117,0.14)' },
];

const COLUMNS = [
  { id: 'item',   name: 'Item',        width: 220, type: 'text',   tip: null },
  { id: 'qty',    name: 'Qty',         width: 60,  type: 'qty',    tip: null },
  { id: 'spec',   name: 'Rider Spec',  width: 280, type: 'text',   tip: 'Copied directly from the rider — exactly what the artist requested' },
  { id: 'actual', name: 'Provision',   width: 280, type: 'text',   tip: 'What you are actually providing — fill this in during your advance' },
  { id: 'status', name: 'Status',      width: 140, type: 'status', tip: 'Click to cycle: Pending → Confirmed → Issue → N/A' },
  { id: 'owner',  name: 'Provided By', width: 130, type: 'owner',  tip: 'Who supplies this item: Vendor, Venue, or Artist' },
  { id: 'notes',  name: 'Notes',       width: 280, type: 'text',   tip: null },
];

// ─── PARSER ──────────────────────────────────────────────────────────────────

// Init PDF.js worker (loaded via CDN in index.html)
if (typeof pdfjsLib !== 'undefined') {
  pdfjsLib.GlobalWorkerOptions.workerSrc =
    'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
}

// Map raw column header text → internal field name (null = not recognized)
// Expand these regexes as you train on more real-world riders
function mapColumnHeader(str) {
  const s = str.toLowerCase().replace(/[^a-z\s]/g, ' ').trim();
  if (/\b(qty|quantity|count|pcs|pieces|num|amount|no\.?)\b/.test(s))              return 'qty';
  if (/\b(actual|provided|provision|supplied|substitut|fulfil|got|have)\b/.test(s)) return 'actual';
  if (/\b(owner|responsibility|resp|source|provided by|by whom|who)\b/.test(s))    return 'owner';
  if (/\b(note|comment|remark|additional|info|memo|special)\b/.test(s))            return 'notes';
  if (/\b(spec|specification|make|model|detail|requirement|brand|description|desc)\b/.test(s)) return 'spec';
  if (/\b(item|equipment|gear|name|device|product|what)\b/.test(s))                return 'item';
  return null;
}

// Map raw owner text → vendor / venue / artist / ''
// Handles shorthand: V=vendor, H=house/venue, A or T=artist/talent
function normalizeOwner(raw) {
  if (!raw) return '';
  const s = raw.toString().toLowerCase().trim();
  if (/^v$/.test(s)    || /\b(vendor|production|rental|prod|supplied by vendor)\b/.test(s)) return 'vendor';
  if (/^h$/.test(s)    || /\b(venue|house|promoter|local|hall|room|in.?house)\b/.test(s))   return 'venue';
  if (/^[at]$/.test(s) || /\b(artist|band|tour|talent|management|mgmt|backline own)\b/.test(s)) return 'artist';
  return '';
}

// Known section-header vocabulary from production riders
// Add new keywords here as you train on real riders
const SECTION_RE = /^(foh|front\s*of\s*house|mon(?:itor)?|iem|backline|drum|guitar|bass|key(?:board)?|synth|piano|organ|perc(?:ussion)?|light(?:ing)?|video|pa\b|audio|stage|cater(?:ing)?|hospitality|power|rf\b|wireless|playback|cable|di\b|mic(?:rophone)?|vocal|misc(?:ellaneous)?|rack|amp(?:lifier)?|speaker|sub(?:woofer)?|production|advance|dressing|security|crew|fly(?:ing)?\s*rig|rigging|pyro|sfx|truss|intercom)/i;

// Is this spreadsheet/PDF row a section header rather than a data row?
function isSectionRow(cells) {
  const nonempty = cells.filter(c => c && c.trim());
  if (!nonempty.length) return false;
  if (nonempty.length > 2) return false; // data rows generally have 3+ cells filled
  const v = nonempty[0].trim();
  if (v.length < 2 || /^\d+$/.test(v)) return false; // lone numbers are never sections
  if (nonempty.length === 1) {
    if (v === v.toUpperCase() && v.length >= 3) return true; // ALL CAPS → section
    if (SECTION_RE.test(v.split(/\s+/)[0]))     return true; // known keyword
  }
  return false;
}

// ── XLS / CSV: 2-D array → { title, rows } ──────────────────────────────────
function normalizeTable(data2d, sheetName) {
  const rows = data2d
    .map(r => r.map(c => (c === null || c === undefined) ? '' : String(c).trim()))
    .filter(r => r.some(c => c));
  if (!rows.length) return { title: sheetName || 'Imported Rider', rows: [] };

  // Find first header row (≥2 recognized column keywords, within first 10 rows)
  let headerIdx = -1;
  let colMap = {}; // colIndex → field
  for (let ri = 0; ri < Math.min(rows.length, 10); ri++) {
    const seen = new Set(), hits = {};
    let n = 0;
    rows[ri].forEach((cell, ci) => {
      const f = mapColumnHeader(cell);
      if (f && !seen.has(f)) { hits[ci] = f; seen.add(f); n++; }
    });
    if (n >= 2) { headerIdx = ri; colMap = hits; break; }
  }

  // Title: text from rows above the header, or sheet name
  let title = sheetName || 'Imported Rider';
  if (headerIdx > 0) {
    const cand = rows.slice(0, headerIdx)
      .map(r => r.filter(c => c).join(' '))
      .filter(s => s.length > 2).join(' — ');
    if (cand.length > 2) title = cand;
  }

  let currentSection = 'General';
  const out = [];
  const dataStart = headerIdx >= 0 ? headerIdx + 1 : 0;

  for (let ri = dataStart; ri < rows.length; ri++) {
    const cells = rows[ri];
    if (!cells.some(c => c)) continue;

    if (isSectionRow(cells)) {
      currentSection = cells.find(c => c.trim()) || currentSection;
      continue;
    }

    if (headerIdx >= 0) {
      const r = { section: currentSection };
      for (const [ci, field] of Object.entries(colMap)) r[field] = cells[ci] || '';
      if (!r.item && !r.spec) continue;
      out.push(r);
    } else {
      // No header found: first cell = item, rest joined = spec
      const filled = cells.filter(c => c);
      if (!filled.length) continue;
      out.push({ section: currentSection, item: filled[0], qty: '', spec: filled.slice(1).join(' '), owner: '' });
    }
  }

  return { title, rows: out };
}

// ── PDF.js helpers ───────────────────────────────────────────────────────────

// Assign a text item's X coordinate to the index of the nearest column anchor
function nearestColIdx(x, colXs) {
  let best = 0, bestDist = Infinity;
  for (let i = 0; i < colXs.length; i++) {
    const d = Math.abs(x - colXs[i]);
    if (d < bestDist) { bestDist = d; best = i; }
  }
  return best;
}

// ── PDF text extraction + table detection ────────────────────────────────────
async function parsePdfFile(file) {
  if (typeof pdfjsLib === 'undefined') throw new Error('PDF.js not loaded — refresh and try again');

  const buf = await file.arrayBuffer();
  const pdf = await pdfjsLib.getDocument({ data: new Uint8Array(buf) }).promise;

  const Y_TOL = 4; // px: items within this Y-range are on the same visual row
  const allRows = []; // array of arrays: [{x, y, text}, …] sorted top→bottom, left→right

  for (let pi = 1; pi <= pdf.numPages; pi++) {
    const page = await pdf.getPage(pi);
    const vp   = page.getViewport({ scale: 1.5 });
    const tc   = await page.getTextContent();

    // Group text items by Y bucket (flip PDF coord: 0=bottom → 0=top)
    const buckets = {};
    for (const item of tc.items) {
      if (!item.str || !item.str.trim()) continue;
      const x   = item.transform[4];
      const y   = vp.height - item.transform[5];
      const key = Math.round(y / Y_TOL);
      (buckets[key] || (buckets[key] = [])).push({ x, y, text: item.str.trim() });
    }
    Object.keys(buckets).map(Number).sort((a, b) => a - b)
      .forEach(k => allRows.push(buckets[k].sort((a, b) => a.x - b.x)));
  }

  if (!allRows.length) return { title: file.name.replace(/\.[^.]+$/, ''), rows: [] };

  // Find first header row (≥2 recognized column keywords, within first 20 rows)
  let headerRowIdx = -1;
  let colXs = [], colFields = [], headerSig = '';

  for (let ri = 0; ri < Math.min(allRows.length, 20); ri++) {
    const row  = allRows[ri];
    const seen = new Set();
    const hits = row
      .map(i => ({ x: i.x, field: mapColumnHeader(i.text) }))
      .filter(h => h.field && !seen.has(h.field) && seen.add(h.field));
    if (hits.length >= 2) {
      headerRowIdx = ri;
      colXs        = hits.map(h => h.x);
      colFields    = hits.map(h => h.field);
      headerSig    = row.map(i => i.text.toLowerCase()).join('|');
      break;
    }
  }

  // Title from rows above header, or strip extension from filename
  let title = file.name.replace(/\.[^.]+$/, '');
  if (headerRowIdx > 0) {
    const cand = allRows.slice(0, headerRowIdx)
      .map(r => r.map(i => i.text).join(' ').trim())
      .filter(s => s.length > 2 && !/^page\s*\d/i.test(s))
      .join(' — ');
    if (cand.length > 2) title = cand;
  }

  // No table structure → fall back to free-form bullet parse
  if (headerRowIdx < 0) return parseFreeFormPdf(allRows, title);

  // Structured table parse: assign each text item to nearest column anchor
  let currentSection = 'General';
  const out = [];

  for (let ri = headerRowIdx + 1; ri < allRows.length; ri++) {
    const row = allRows[ri];
    if (!row.length) continue;

    // Skip repeated header rows (multi-page PDFs often repeat the header)
    if (row.map(i => i.text.toLowerCase()).join('|') === headerSig) continue;

    const cells = Array(colFields.length).fill('');
    for (const item of row) {
      const ci = nearestColIdx(item.x, colXs);
      cells[ci] = cells[ci] ? cells[ci] + ' ' + item.text : item.text;
    }

    if (isSectionRow(cells)) {
      currentSection = cells.find(c => c && c.trim()) || currentSection;
      continue;
    }

    const r = { section: currentSection };
    colFields.forEach((f, ci) => { r[f] = cells[ci].trim(); });
    if (!r.item && !r.spec) continue;
    out.push(r);
  }

  return { title, rows: out };
}

// Free-form / bullet-list parse — used when no clear table header is found
// Handles: bullet lists, numbered lists, DJ/tech riders, narrative riders
function parseFreeFormPdf(allRows, title) {
  // Bullet chars — includes Wingdings \uf0b7 used in older/Word-generated PDFs
  const BULLET_RE    = /^[\u2022\u2023\u25E6\u2043\u2219\uf0b7•·▪▸▶○●◦\-\*]\s*/;
  const NUMBERED_ROW = /^\d+[\.\)]\s+/;

  // Quantity patterns — priority order
  // QTY_PAREN handles "(4 x)" with optional space: "(2x)" "(4 x)" "(2)"
  // QTY_DASH handles "1 - DIGICO QUANTUM 338" (common in tech riders)
  const QTY_SPELLED  = /^(one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve|sixteen|twenty)\s+\((\d+)\)\s+(.+)/i;
  const QTY_PAREN    = /^\((\d+)\s*[xX×]?\s*\)\s+(.+)/;
  const QTY_NX_DOT   = /^(\d+)[xX]\.?\s+(.+)/;
  const QTY_INLINE   = /^(\d+)\s*[xX×]\s*(.+)/;
  const QTY_DASH     = /^(\d{1,3})\s*-\s+(.+)/;        // "1 - DIGICO QUANTUM 338"
  const DASH_SPLIT   = /\s+[-–—]\s+/;

  // Lines to drop entirely: contact info, page refs, tax form language
  const SKIP_RE      = /(@\w|phone:|tel:|fax:|www\.|https?:|initial_+|page\s+\d+\s+of\s+\d+|copyright|\ball\s+rights\s+reserved\b|taxpayer\s+id|backup\s+withhold|social\s+security\s+number|form\s+w.9|\birs\b|under\s+penalties\s+of\s+perjury)/i;

  // Page-break continuation headers — "HOSPITALITY RIDER CONTINUED..." etc.
  // These appear at top of new pages; keep current section, don't rename it
  const CONTINUATION_RE = /\b(continu(?:ed|ation|es)|cont'?\.?d|pg\.?\s*\d+)\b/i;

  // Sections whose items we drop entirely (non-equipment content)
  const SKIP_SECTION_RE = /\b(labor|labour|security|hospitality|catering|food\s*(?:&|and)\s*bev|dressing|parking|runner|ground\s+transport|billing|ticketing|advert|marketing|contact|interview|press|merch(?:andise)?|production\s+office|schedule|itinerary|hotel|accommodat|signature|payment|per\s+diem|tax\s+form|w.9|general\s+overview|introduction)\b/i;

  // Item-level: skip when the item text describes a person/staff role
  const PERSONNEL_RE = /^(stagehand|stage\s*hand|security\s*(?:guard)?|runner|driver|director|sound\s+engineer|lighting\s+engineer|video\s+engineer|\bld\b|\bvj\b|electrician|rigger|fire\s+marshal+|production\s+manager|tour\s+manager|stage\s+manager|crew\s+chief|backline\s+tech)/i;

  // Owner context — sets who provides subsequent items
  const VENDOR_CTX   = /\b(purchaser|promoter|venue|local\s+prod(?:uction)?|house)\s+(must|shall|will|agrees?\s+to)\s+(supply|provide|furnish|bring)/i;
  const ARTIST_CTX   = /\b(artist|band|act|we|tour)\s+(carry|carries|brings?|require[sd]?|provides?|travel[ls]?\s+with)/i;
  const VENDOR_HDR   = /^(local\s+production\s+provides?|purchaser\s+provides?|venue\s+provides?|house\s+provides?|promoter\s+provides?)/i;
  const ARTIST_HDR   = /^(artist\s+carr(?:y|ies)|band\s+carr(?:y|ies)|[\w\s]+\s+carr(?:y|ies)|backline\s+brought\s+by\s+(?:band|artist|us))/i;

  let currentSection = 'General';
  let blockOwner     = '';
  const out = [];

  for (const row of allRows) {
    const raw = row.map(i => i.text).join(' ').trim();
    if (!raw || raw.length < 2) continue;
    if (SKIP_RE.test(raw)) continue;

    // Owner context (before section logic)
    if (VENDOR_CTX.test(raw)) blockOwner = 'vendor';
    else if (ARTIST_CTX.test(raw)) blockOwner = 'artist';
    if (VENDOR_HDR.test(raw)) { blockOwner = 'vendor'; continue; }
    if (ARTIST_HDR.test(raw)) { blockOwner = 'artist'; continue; }

    // ── Section detection — always runs so we can exit skip-sections ───────
    let isSection = false;

    if (/^[A-Z0-9\s\/&\-\.]{3,40}$/.test(raw) && !/^\d/.test(raw)) {
      // ALL-CAPS header: only update section if it's NOT a page-break "CONTINUED" line
      if (!CONTINUATION_RE.test(raw)) currentSection = raw;
      isSection = true;
    } else {
      const nms = raw.match(/^[A-Z0-9]{1,2}[\.\)]\s+([A-Z].{1,50}?)[:.]?\s*$/);
      if (nms) { currentSection = nms[1].trim(); isSection = true; }
      else if (SECTION_RE.test(raw.split(/\s+/)[0]) && raw.length < 55) {
        if (!CONTINUATION_RE.test(raw)) currentSection = raw;
        isSection = true;
      }
    }
    if (isSection) continue;

    // ── Drop non-equipment sections ───────────────────────────────────────
    if (SKIP_SECTION_RE.test(currentSection)) continue;

    // ── Item extraction ───────────────────────────────────────────────────
    const isBullet = BULLET_RE.test(raw);
    let text = raw.replace(BULLET_RE, '').replace(NUMBERED_ROW, '').trim();
    if (!text || text.length < 2) continue;

    let qty = '';
    const sm = text.match(QTY_SPELLED);
    if (sm)       { qty = sm[2]; text = sm[3].trim(); }
    else {
      const pm = text.match(QTY_PAREN);
      if (pm)     { qty = pm[1]; text = pm[2].trim(); }
      else {
        const dm = text.match(QTY_NX_DOT);
        if (dm)   { qty = dm[1]; text = dm[2].trim(); }
        else {
          const qm = text.match(QTY_INLINE);
          if (qm) { qty = qm[1]; text = qm[2].trim(); }
          else {
            const dsh = text.match(QTY_DASH);
            if (dsh) { qty = dsh[1]; text = dsh[2].trim(); }
          }
        }
      }
    }

    // Non-bullet items without a qty are almost always prose — skip them
    if (!isBullet && !qty) continue;

    const parts = text.split(DASH_SPLIT);
    const item  = parts[0].trim();
    const spec  = parts.length > 1 ? parts.slice(1).join(' — ').trim() : '';

    if (!item || item.length < 2 || /^\d+$/.test(item)) continue;
    if (PERSONNEL_RE.test(item)) continue;

    out.push({ section: currentSection, item, qty, spec, owner: blockOwner });
  }

  return { title, rows: out };
}

// ── Entry point ──────────────────────────────────────────────────────────────
async function parseFile(file) {
  if (PARSE_MODE === 'claude') throw new Error('Claude parse mode: uncomment WORKER_URL block above');
  if (PARSE_MODE === 'gemini') throw new Error('Gemini parse mode: uncomment GEMINI_KEY block above');

  if (file.size > 30 * 1024 * 1024) throw new Error('File too large (30 MB max)');
  const n = file.name.toLowerCase();

  if (n.endsWith('.csv') || file.type === 'text/csv') {
    const t = await file.text();
    const p = Papa.parse(t, { header: false, skipEmptyLines: true });
    return normalizeTable(p.data, n.replace(/\.csv$/i, ''));
  }

  if (n.endsWith('.xlsx') || n.endsWith('.xls')) {
    const buf  = await file.arrayBuffer();
    const wb   = XLSX.read(buf, { type: 'array' });
    const sn   = wb.SheetNames[0];
    const data = XLSX.utils.sheet_to_json(wb.Sheets[sn], { header: 1, defval: '' });
    return normalizeTable(data, sn);
  }

  if (n.endsWith('.pdf') || file.type === 'application/pdf') {
    return parsePdfFile(file);
  }

  throw new Error('Unsupported file type — upload CSV, XLS, XLSX, or PDF');
}

function normalizeRows(parsed) {
  return (parsed.rows || []).map(r => ({
    id: genId(),
    section: r.section || 'Other',
    item: r.item || '', qty: r.qty || '', spec: r.spec || '', actual: '', status: 'pending',
    owner: normalizeOwner(r.owner || ''), notes: ''
  })).filter(r => r.item || r.spec);
}

// ─── STORAGE ─────────────────────────────────────────────────────────────────
// Strategy: Supabase primary (when authenticated) + localStorage cache/fallback.
// Talent / vendor views never need auth — they pull show data by ID.

const _lsGet = k => { try { return localStorage.getItem(k); } catch { return null; } };
const _lsSet = (k, v) => { try { localStorage.setItem(k, v); } catch {} };
const _lsDel = k => { try { localStorage.removeItem(k); } catch {} };

// Helper: get current session without throwing
const getSession = async () => {
  try { const { data } = await db.auth.getSession(); return data?.session || null; }
  catch { return null; }
};

const loadShows = async () => {
  const session = await getSession();
  if (session) {
    try {
      const { data, error } = await db.from('shows')
        .select('id, name, created_at')
        .order('created_at', { ascending: false });
      if (!error && data) {
        const list = data.map(s => ({ id: s.id, name: s.name, createdAt: new Date(s.created_at).getTime() }));
        _lsSet('fr_shows', JSON.stringify(list));
        return list;
      }
    } catch {}
  }
  const r = _lsGet('fr_shows'); try { return r ? JSON.parse(r) : []; } catch { return []; }
};

// localStorage-only index (Supabase list is loaded fresh each time)
const saveShows = list => _lsSet('fr_shows', JSON.stringify(list));

const loadShow = async id => {
  const session = await getSession();
  if (session) {
    try {
      const { data, error } = await db.from('shows').select('*').eq('id', id).single();
      if (!error && data) {
        const show = { id: data.id, name: data.name, createdAt: new Date(data.created_at).getTime(), rows: data.rows || [] };
        _lsSet(`fr_show_${id}`, JSON.stringify(show));
        return show;
      }
    } catch {}
  }
  const r = _lsGet(`fr_show_${id}`); try { return r ? JSON.parse(r) : null; } catch { return null; }
};

const saveShow = async (id, show) => {
  _lsSet(`fr_show_${id}`, JSON.stringify(show));
  const session = await getSession();
  if (session) {
    try {
      await db.from('shows').upsert({
        id,
        user_id: session.user.id,
        name: show.name,
        rows: show.rows || [],
        created_at: show.createdAt ? new Date(show.createdAt).toISOString() : new Date().toISOString(),
      }, { onConflict: 'id' });
    } catch {}
  }
};

const deleteShowFromDb = async id => {
  _lsDel(`fr_show_${id}`);
  const session = await getSession();
  if (session) {
    try { await db.from('shows').delete().eq('id', id); } catch {}
  }
};

// ─── UTILS ───────────────────────────────────────────────────────────────────

const genId   = () => Math.random().toString(36).slice(2, 10) + Date.now().toString(36).slice(-4);
const fmtRel  = ts => { const d = Date.now() - ts; if (d < 60e3) return 'just now'; if (d < 3.6e6) return `${Math.floor(d/60e3)}m ago`; if (d < 864e5) return `${Math.floor(d/3.6e6)}h ago`; return `${Math.floor(d/864e5)}d ago`; };
const nav     = p => { window.location.hash = p; };
const roleOf  = id => ROLES.find(r => r.id === id);
const statusOf = id => STATUSES.find(s => s.id === id) || STATUSES[0];

// Extracted as top-level so it's never recreated per render
function parseRoute() {
  const h = window.location.hash.slice(1);
  const mTalent = h.match(/^\/show\/([a-z0-9]+)\/talent$/);
  const mVendor = h.match(/^\/show\/([a-z0-9]+)\/vendor$/);
  const mAdmin  = h.match(/^\/show\/([a-z0-9]+)$/);
  if (mTalent) return { name: 'talent', id: mTalent[1] };
  if (mVendor) return { name: 'vendor', id: mVendor[1] };
  if (mAdmin)  return { name: 'show',   id: mAdmin[1]  };
  return { name: 'landing' };
}

function useRoute() {
  const [r, setR] = useState(parseRoute);
  useEffect(() => {
    const fn = () => setR(parseRoute());
    window.addEventListener('hashchange', fn);
    return () => window.removeEventListener('hashchange', fn);
  }, []);
  return r;
}

function useIsMobile(bp = 640) {
  const [m, setM] = useState(() => window.innerWidth < bp);
  useEffect(() => {
    const fn = () => setM(window.innerWidth < bp);
    window.addEventListener('resize', fn, { passive: true });
    return () => window.removeEventListener('resize', fn);
  }, [bp]);
  return m;
}

function useTheme() {
  const [theme, setTheme] = useState(() => localStorage.getItem('rf_theme') || 'dark');
  useEffect(() => {
    document.documentElement.classList.toggle('theme-light', theme === 'light');
    localStorage.setItem('rf_theme', theme);
  }, [theme]);
  const toggle = () => setTheme(t => t === 'dark' ? 'light' : 'dark');
  return [theme, toggle];
}

// ─── CSS ─────────────────────────────────────────────────────────────────────

const CSS = `
@import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Archivo:wght@700;900&family=JetBrains+Mono:wght@400;500;700&family=Manrope:wght@400;500;600;700&display=swap');

:root {
  --bg: #000000; --bg-1: #050508; --surface: #0d0d12; --surface-2: #13131a; --surface-3: #1a1a24;
  --border: #1a1a24; --border-strong: #252535;
  --text: #ccdacc; --text-2: #5e845e; --text-3: #334a33;
  --accent: #bb2020; --accent-dim: rgba(187,32,32,0.12); --accent-glow: rgba(187,32,32,0.35);
  --ok: #33aa55; --ok-dim: rgba(51,170,85,0.12); --err: #bb3333;
  --green-glow: 0 0 10px rgba(51,170,85,0.15);
}
* { box-sizing: border-box; }
html, body, #root { height: 100%; margin: 0; padding: 0; }
.root { font-family: 'Manrope', sans-serif; background: var(--bg); color: var(--text); min-height: 100vh; -webkit-font-smoothing: antialiased; }
.fd { font-family: 'Bebas Neue', sans-serif; letter-spacing: .04em; font-weight: 400; }
.fm { font-family: 'JetBrains Mono', monospace; }

/* SCROLLBAR */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 8px; }

/* KITT DASHBOARD BUTTONS */
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; padding: 11px 16px; border-radius: 4px; font-weight: 700; font-size: 13px; border: 2px solid transparent; cursor: pointer; transition: all 80ms ease; min-height: 44px; user-select: none; font-family: 'JetBrains Mono', monospace; white-space: nowrap; letter-spacing: .04em; text-transform: uppercase; }
.btn:disabled { opacity: 0.4; cursor: not-allowed; }

/* ── KITT button system — muted backlit, dark fills, colored text ── */
.btn-secondary { background: linear-gradient(180deg,#1e4a1e,#0f2a0f); color: #55cc77; border-color: #1e3a1e; box-shadow: inset 0 1px 0 rgba(255,255,255,.06); }
.btn-secondary:hover:not(:disabled) { background: linear-gradient(180deg,#264f26,#173517); color: #66dd88; border-color: #265a26; }
.btn-secondary:active:not(:disabled) { transform: translateY(1px); }

.btn-amber { background: linear-gradient(180deg,#3a2800,#221700); color: #cc9922; border-color: #3a2a00; box-shadow: inset 0 1px 0 rgba(255,255,255,.06); }
.btn-amber:hover:not(:disabled) { background: linear-gradient(180deg,#473200,#2a1d00); color: #ddaa33; border-color: #4a3300; }
.btn-amber:active:not(:disabled) { transform: translateY(1px); }

.btn-blue { background: linear-gradient(180deg,#0d2035,#071420); color: #5599cc; border-color: #0d2035; box-shadow: inset 0 1px 0 rgba(255,255,255,.06); }
.btn-blue:hover:not(:disabled) { background: linear-gradient(180deg,#132840,#0b1c2e); color: #66aadd; border-color: #1a3050; }
.btn-blue:active:not(:disabled) { transform: translateY(1px); }

.btn-primary { background: linear-gradient(180deg,#2e0a0a,#1a0505); color: #cc4444; border-color: #2e0a0a; box-shadow: inset 0 1px 0 rgba(255,255,255,.06); }
.btn-primary:hover:not(:disabled) { background: linear-gradient(180deg,#3d0e0e,#230808); color: #dd5555; border-color: #3d1010; }
.btn-primary:active:not(:disabled) { transform: translateY(1px); }

.btn-ghost { background: transparent; color: var(--text-2); border-color: transparent; }
.btn-ghost:hover:not(:disabled) { background: var(--surface-2); color: var(--ok); border-color: var(--border-strong); }

.btn-sm { padding: 6px 12px; min-height: 36px; font-size: 11px; }

/* KITT STATUS INDICATOR BUTTONS */
.kitt-btn {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 6px 14px; border-radius: 6px; font-family: 'JetBrains Mono', monospace;
  font-size: 10px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  border: 2px solid transparent; cursor: default; min-height: 32px;
}
.kitt-btn-yellow { background: linear-gradient(180deg,#3a2800,#221700); color: #cc9922; border-color: #3a2a00; }
.kitt-btn-red    { background: linear-gradient(180deg,#2e0a0a,#1a0505); color: #cc4444; border-color: #2e0a0a; }
.kitt-btn-green  { background: linear-gradient(180deg,#1e4a1e,#0f2a0f); color: #55cc77; border-color: #1e3a1e; }
/* .kitt-btn-off unused — removed */

.input { width: 100%; padding: 12px 14px; border-radius: 8px; background: var(--surface-2); color: var(--ok); border: 2px solid #1a2e1a; font-family: 'JetBrains Mono', monospace; font-size: 14px; min-height: 44px; transition: all 120ms; }
.input:focus { outline: none; border-color: var(--ok); box-shadow: 0 0 0 2px rgba(51,170,85,0.15); color: #55cc88; }
.input::placeholder { color: var(--text-3); }

.chip { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border-radius: 999px; font-size: 11px; font-weight: 600; font-family: 'JetBrains Mono', monospace; letter-spacing: 0.04em; text-transform: uppercase; border: 1px solid transparent; }
.tag  { display: inline-flex; align-items: center; gap: 5px; padding: 3px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; font-family: 'JetBrains Mono', monospace; letter-spacing: 0.04em; text-transform: uppercase; }

/* GRID */
.grid-wrap { overflow-x: auto; overflow-y: visible; border: 1px solid var(--border-strong); border-radius: 10px; background: var(--surface); -webkit-overflow-scrolling: touch; }
.grid-table { display: inline-grid; min-width: 100%; }
.grid-cell, .grid-hcell { border-right: 1px solid var(--border); border-bottom: 1px solid var(--border); min-height: 48px; display: flex; align-items: stretch; background: var(--surface); position: relative; }
.grid-cell:last-child, .grid-hcell:last-child { border-right: none; }
.grid-hcell { background: #050508; font-family: 'JetBrains Mono', monospace; font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em; color: var(--text-2); padding: 0 8px; align-items: center; border-bottom: 1px solid var(--border-strong); position: sticky; top: 0; z-index: 10; overflow: hidden; user-select: none; }
.col-resize-handle { position: absolute; right: 0; top: 0; bottom: 0; width: 6px; cursor: col-resize; z-index: 2; }
.col-resize-handle:hover, .col-resize-handle:active { background: rgba(51,170,85,0.15); }
input[type=number]::-webkit-outer-spin-button, input[type=number]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
input[type=number] { -moz-appearance: textfield; }
.cell-in { flex: 1; padding: 10px 12px; display: flex; align-items: center; min-width: 0; }
.cell-tx { width: 100%; background: transparent; border: none; color: var(--text); font-family: inherit; font-size: 14px; resize: none; padding: 0; line-height: 1.4; min-height: 20px; }
.cell-tx:focus { outline: none; }
.cell-tx::placeholder { color: var(--text-3); }
.grid-cell:hover { background: var(--surface-2); }
.grid-cell:focus-within { background: var(--surface-3); box-shadow: inset 0 0 0 1px rgba(51,170,85,0.2); z-index: 2; }
.row-num { font-family: 'JetBrains Mono', monospace; font-size: 10px; color: var(--text-3); min-width: 36px; user-select: none; display: flex; align-items: center; justify-content: center; background: #050508; border-right: 1px solid var(--border); border-bottom: 1px solid var(--border); position: sticky; left: 0; z-index: 4; }
.grid-hcell.row-num { z-index: 13; }

/* DROPDOWNS */
.sel-wrap { position: relative; width: 100%; }
.sel-btn { width: 100%; text-align: left; padding: 5px 10px; background: transparent; border: none; color: var(--text); font-family: inherit; font-size: 13px; cursor: pointer; min-height: 32px; display: flex; align-items: center; justify-content: space-between; border-radius: 4px; }
.sel-btn:hover { background: var(--surface-3); }
.sel-menu { position: absolute; top: calc(100% + 4px); left: 0; min-width: 180px; background: var(--surface-2); border: 1px solid var(--border-strong); border-radius: 8px; padding: 4px; z-index: 100; box-shadow: 0 12px 32px rgba(0,0,0,0.7); }
.sel-opt { display: flex; align-items: center; gap: 10px; padding: 9px 10px; border-radius: 4px; cursor: pointer; font-size: 13px; color: var(--text); }
.sel-opt:hover { background: var(--surface-3); }

/* SYNC DOT */
.sync-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--ok); }
.sync-dot.saving { background: var(--accent); animation: pulse 0.8s ease-in-out infinite; }

/* MODAL */
.modal-bg { position: fixed; inset: 0; background: rgba(0,0,0,0.8); display: flex; align-items: flex-end; justify-content: center; z-index: 200; backdrop-filter: blur(8px); }
.modal-panel { background: var(--surface); border: 1px solid var(--border-strong); border-top: 1px solid rgba(51,170,85,0.3); border-radius: 20px 20px 0 0; width: 100%; max-width: 520px; max-height: 90vh; overflow-y: auto; animation: slideUp 220ms cubic-bezier(0.16,1,0.3,1); }
@media(min-width:640px){ .modal-bg { align-items: center; } .modal-panel { border-radius: 16px; border: 1px solid var(--border-strong); box-shadow: 0 8px 40px rgba(0,0,0,0.8); } }

/* GREEN SCAN GRID BACKGROUND */
.bg-grid {
  background-image:
    linear-gradient(rgba(51,170,85,0.03) 1px, transparent 1px),
    linear-gradient(90deg, rgba(51,170,85,0.03) 1px, transparent 1px);
  background-size: 40px 40px;
}

/* (legacy .scanner-track / .kitt-led classes removed — scanner uses inline styles) */

/* ANIMATIONS */
@keyframes fadeIn      { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }
@keyframes slideUp     { from { transform: translateY(100%); } to { transform: translateY(0); } }
@keyframes pulse       { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }
@keyframes spin        { to { transform: rotate(360deg); } }
@keyframes greenPulse  { 0%,100% { opacity: .7; } 50% { opacity: 1; } }
@keyframes miamiScroll { from { transform: translateX(0); } to { transform: translateX(-200px); } }
.fade-in { animation: fadeIn 180ms ease-out; }
.green-pulse { animation: greenPulse 2s ease-in-out infinite; }

.safe-b { padding-bottom: calc(20px + env(safe-area-inset-bottom, 0px)); }

/* ─── MOBILE ─────────────────────────────────────────────────────── */
@media (max-width: 639px) {
  .btn-sm { min-height: 40px; font-size: 11px; }
  .mob-hide { display: none !important; }
  .topbar-inner { flex-wrap: wrap; row-gap: 6px; }
  .grid-wrap { border-radius: 6px; }
  .modal-panel { max-height: 95vh; }
}

/* ─── THEME VARIABLES ─────────────────────────────────────────────── */
:root {
  --topbar-bg:   rgba(10,10,11,.96);
  --dropdown-bg: #0a0a0b;
}

/* ─── MIAMI VICE — LIGHT THEME ────────────────────────────────────── */
/* Dark = Knight Rider (KITT black/red/green). Light = Miami Vice (teal/pink/lavender). */
.theme-light {
  --bg:           #f4f0ff;
  --bg-1:         #ede8ff;
  --surface:      #ffffff;
  --surface-2:    #f2edff;
  --surface-3:    #e8e2f8;
  --border:       #d8d0f0;
  --border-strong:#c0b8e8;
  --text:         #1a0e38;
  --text-2:       #5a3888;
  --text-3:       #9880c0;
  --accent:       #d01070;
  --accent-dim:   rgba(208,16,112,0.1);
  --ok:           #00897b;
  --ok-dim:       rgba(0,137,123,0.1);
  --err:          #c62460;
  --topbar-bg:    rgba(244,240,255,.97);
  --dropdown-bg:  #ffffff;
  --green-glow:   0 0 10px rgba(0,137,123,0.15);
}
.theme-light .btn-secondary { background: linear-gradient(180deg,#d0f5f0,#b8ede8); color: #006057; border-color: #99e0d8; box-shadow: none; }
.theme-light .btn-secondary:hover:not(:disabled) { background: linear-gradient(180deg,#bff0ea,#a0e8e0); color: #004d44; border-color: #77d8cc; }
.theme-light .btn-primary   { background: linear-gradient(180deg,#ffe0ee,#ffcce4); color: #a00050; border-color: #ffaacc; box-shadow: none; }
.theme-light .btn-primary:hover:not(:disabled)   { background: linear-gradient(180deg,#ffcce4,#ffb8d8); color: #880040; border-color: #ff88bb; }
.theme-light .btn-amber     { background: linear-gradient(180deg,#fff0d8,#ffe4b8); color: #7a4400; border-color: #f5c888; box-shadow: none; }
.theme-light .btn-amber:hover:not(:disabled)     { background: linear-gradient(180deg,#ffe8c0,#ffd8a0); color: #663800; border-color: #f0b870; }
.theme-light .btn-blue      { background: linear-gradient(180deg,#e0ecff,#ccdeff); color: #1a3880; border-color: #99bbff; box-shadow: none; }
.theme-light .btn-ghost     { background: transparent; color: var(--text-2); border-color: transparent; }
.theme-light .btn-ghost:hover:not(:disabled) { background: var(--surface-3); color: var(--ok); border-color: var(--border-strong); }
.theme-light .grid-hcell    { background: #ede8ff !important; color: var(--text-2); }
.theme-light .row-num       { background: #ede8ff !important; color: var(--text-3); }
.theme-light .grid-cell     { background: var(--surface); }
.theme-light .grid-cell:hover { background: var(--surface-2); }
.theme-light .grid-cell:focus-within { background: var(--surface-2); box-shadow: inset 0 0 0 1px rgba(0,137,123,0.2); }
.theme-light .sync-dot      { background: var(--ok); }
.theme-light .sync-dot.saving { background: var(--accent); animation: pulse 0.8s ease-in-out infinite; }
.theme-light .bg-grid {
  background-image:
    linear-gradient(rgba(0,137,123,0.04) 1px, transparent 1px),
    linear-gradient(90deg, rgba(0,137,123,0.04) 1px, transparent 1px);
}
.theme-light .input { background: var(--surface-2); border-color: var(--border-strong); color: var(--text); }
.theme-light .input:focus { border-color: var(--ok); box-shadow: 0 0 0 2px rgba(0,137,123,0.12); color: var(--text); }
.theme-light .input::placeholder { color: var(--text-3); }
.theme-light .modal-panel   { background: var(--surface); border-color: var(--border-strong); }
.theme-light .sel-btn       { color: var(--text); }
.theme-light .sel-menu      { background: var(--surface); border-color: var(--border-strong); box-shadow: 0 12px 32px rgba(120,80,200,0.15); }
.theme-light .sel-opt       { color: var(--text); }
.theme-light .sel-opt:hover { background: var(--surface-3); }
.theme-light .cell-tx       { color: var(--text); }
`;

// ─── BURGER / STAGE ──────────────────────────────────────────────────────────

function Burger({ color = 'currentColor' }) {
  return (
    <svg viewBox="0 0 10 10" style={{ display:'inline', width:'0.68em', height:'0.73em', verticalAlign:'top', marginTop:'-0.09em', marginLeft:'0.06em', marginRight:'0.06em' }}>
      <rect x="0" y="0"    width="10" height="1.5" fill={color} rx="0.3"/>
      <rect x="0" y="4.25" width="10" height="1.5" fill={color} rx="0.3"/>
      <rect x="0" y="8.5"  width="10" height="1.5" fill={color} rx="0.3"/>
    </svg>
  );
}

// ─── SHARED UI ───────────────────────────────────────────────────────────────

function StatusChip({ status }) {
  const s = statusOf(status);
  return (
    <span className="chip" style={{ color: s.color, background: s.bg, borderColor: s.color + '30' }}>
      {status === 'confirmed' && <CheckCircle2 size={11} />}
      {status === 'pending'   && <Clock size={11} />}
      {status === 'issue'     && <AlertCircle size={11} />}
      {status === 'na'        && <Circle size={11} />}
      {s.label}
    </span>
  );
}

function RoleTag({ role, size = 'md' }) {
  const r = roleOf(role);
  if (!r) return <span className="tag" style={{ color: 'var(--text-3)', background: 'var(--surface-2)' }}>—</span>;
  return <span className="tag" style={{ color: r.color, background: r.color + '1a', border: `1px solid ${r.color}30`, fontSize: size === 'sm' ? '10px' : '11px' }}>
    {size === 'sm' ? r.short : r.label}
  </span>;
}

function Sel({ value, options, onChange, renderValue, renderOption, placeholder = '—' }) {
  const [open, setOpen] = useState(false);
  const ref = useRef(null);
  useEffect(() => {
    if (!open) return;
    const fn = e => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', fn);
    return () => document.removeEventListener('mousedown', fn);
  }, [open]);
  const cur = options.find(o => o.id === value);
  return (
    <div className="sel-wrap" ref={ref}>
      <button className="sel-btn" onClick={e => { e.stopPropagation(); setOpen(o => !o); }}>
        {cur ? (renderValue ? renderValue(cur) : cur.label) : <span style={{ color: 'var(--text-3)' }}>{placeholder}</span>}
        <ChevronDown size={14} style={{ color: 'var(--text-3)', flexShrink: 0 }} />
      </button>
      {open && (
        <div className="sel-menu fade-in">
          {options.map(o => (
            <div key={o.id} className="sel-opt" onClick={() => { onChange(o.id); setOpen(false); }}>
              {renderOption ? renderOption(o) : o.label}
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

// ─── GRID ─────────────────────────────────────────────────────────────────────

function Grid({ rows, onCellChange, onDeleteRow, onAddRowToSection, filterActive = false }) {
  const isMobile = useIsMobile();
  const [colWidths, setColWidths] = useState(() => Object.fromEntries(COLUMNS.map(c => [c.id, c.width])));
  const gt = `36px ${COLUMNS.map(c => `${colWidths[c.id]}px`).join(' ')} 44px`;

  const startResize = (e, colId) => {
    e.preventDefault();
    const startX = e.clientX;
    const startW = colWidths[colId];
    const onMove = ev => setColWidths(prev => ({ ...prev, [colId]: Math.max(50, startW + ev.clientX - startX) }));
    const onUp = () => { window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseup', onUp); };
    window.addEventListener('mousemove', onMove);
    window.addEventListener('mouseup', onUp);
  };

  // Group rows preserving original section order
  const sectionOrder = [];
  const sectionMap = new Map();
  rows.forEach(row => {
    const sec = row.section || 'Other';
    if (!sectionMap.has(sec)) { sectionMap.set(sec, []); sectionOrder.push(sec); }
    sectionMap.get(sec).push(row);
  });

  if (isMobile) {
    return (
      <div>
        {rows.length === 0
          ? <div style={{ padding: 40, textAlign: 'center', color: 'var(--text-3)', fontFamily: 'var(--mono)', fontSize: 13 }}>{filterActive ? 'No items match the current filter.' : 'No rows yet. Tap Import (···) to add a rider.'}</div>
          : sectionOrder.map(sec => {
              const sRows = sectionMap.get(sec);
              return (
                <div key={sec} style={{ marginBottom: 28 }}>
                  {/* Section header */}
                  <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '8px 0', borderBottom: '2px solid var(--border-strong)', marginBottom: 10 }}>
                    <span className="fm" style={{ fontSize: 11, fontWeight: 700, letterSpacing: '.14em', color: 'var(--text-2)', textTransform: 'uppercase', flex: 1 }}>{sec}</span>
                    <button onClick={() => onAddRowToSection(sec)} className="btn btn-secondary btn-sm" style={{ padding: '2px 10px', minHeight: 0, fontSize: 10, gap: 4 }}>
                      <Plus size={10} /> ADD
                    </button>
                  </div>
                  {/* Row cards */}
                  <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
                    {sRows.map((row, idx) => (
                      <div key={row.id} style={{ background: 'var(--surface)', border: `1px solid ${row.status === 'confirmed' ? 'rgba(0,255,0,0.2)' : row.status === 'issue' ? 'rgba(248,113,113,0.2)' : 'var(--border)'}`, borderRadius: 8, padding: '12px 14px' }}>
                        {/* Item name row */}
                        <div style={{ display: 'flex', alignItems: 'flex-start', gap: 8, marginBottom: 10 }}>
                          <span className="fm" style={{ fontSize: 10, color: 'var(--text-3)', minWidth: 16, paddingTop: 2 }}>{idx + 1}</span>
                          <textarea
                            value={row.item || ''} onChange={e => onCellChange(row.id, 'item', e.target.value)}
                            placeholder="Item name"
                            rows={1}
                            style={{ flex: 1, background: 'transparent', border: 'none', color: '#e2e2e8', fontFamily: 'Manrope, sans-serif', fontSize: 15, fontWeight: 700, resize: 'none', outline: 'none', padding: 0, lineHeight: 1.3 }}
                          />
                          <button onClick={() => { if (window.confirm('Delete row?')) onDeleteRow(row.id); }} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-3)', padding: 4, flexShrink: 0 }}>
                            <Trash2 size={14} />
                          </button>
                        </div>
                        {/* Qty + Status + Owner row */}
                        <div style={{ display: 'grid', gridTemplateColumns: '80px 1fr 1fr', gap: 8, marginBottom: 10 }}>
                          {/* Qty */}
                          <div>
                            <div className="fm" style={{ fontSize: 9, color: 'var(--text-3)', letterSpacing: '.08em', textTransform: 'uppercase', marginBottom: 4 }}>Qty</div>
                            <div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
                              <button onClick={() => onCellChange(row.id, 'qty', String(Math.max(0, (parseInt(row.qty) || 1) - 1)))} style={{ background: 'var(--surface-3)', border: '1px solid var(--border-strong)', color: 'var(--ok)', width: 26, height: 26, borderRadius: 3, cursor: 'pointer', fontSize: 16, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>−</button>
                              <span className="fm" style={{ width: 24, textAlign: 'center', fontSize: 14, fontWeight: 700, color: 'var(--text)' }}>{row.qty || '—'}</span>
                              <button onClick={() => onCellChange(row.id, 'qty', String((parseInt(row.qty) || 0) + 1))} style={{ background: 'var(--surface-3)', border: '1px solid var(--border-strong)', color: 'var(--ok)', width: 26, height: 26, borderRadius: 3, cursor: 'pointer', fontSize: 16, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>+</button>
                            </div>
                          </div>
                          {/* Status */}
                          <div>
                            <div className="fm" style={{ fontSize: 9, color: 'var(--text-3)', letterSpacing: '.08em', textTransform: 'uppercase', marginBottom: 4 }}>Status</div>
                            <Sel value={row.status || 'pending'} options={STATUSES} onChange={v => onCellChange(row.id, 'status', v)}
                              renderValue={o => <StatusChip status={o.id} />}
                              renderOption={o => <StatusChip status={o.id} />} />
                          </div>
                          {/* Owner */}
                          <div>
                            <div className="fm" style={{ fontSize: 9, color: 'var(--text-3)', letterSpacing: '.08em', textTransform: 'uppercase', marginBottom: 4 }}>Provided By</div>
                            <Sel value={row.owner || ''} options={ROLES} placeholder="Assign…" onChange={v => onCellChange(row.id, 'owner', v)}
                              renderValue={o => <RoleTag role={o.id} size="sm" />}
                              renderOption={o => <RoleTag role={o.id} size="sm" />} />
                          </div>
                        </div>
                        {/* Spec */}
                        <div style={{ marginBottom: 8 }}>
                          <div className="fm" style={{ fontSize: 9, color: 'var(--text-3)', letterSpacing: '.08em', textTransform: 'uppercase', marginBottom: 3 }}>Rider Spec</div>
                          <textarea value={row.spec || ''} onChange={e => onCellChange(row.id, 'spec', e.target.value)} placeholder="—" rows={1}
                            style={{ width: '100%', background: 'transparent', border: 'none', color: '#8a8aa0', fontFamily: 'JetBrains Mono, monospace', fontSize: 12, resize: 'none', outline: 'none', padding: 0, lineHeight: 1.4, boxSizing: 'border-box' }} />
                        </div>
                        {/* Actual */}
                        <div style={{ marginBottom: 8 }}>
                          <div className="fm" style={{ fontSize: 9, color: 'var(--text-3)', letterSpacing: '.08em', textTransform: 'uppercase', marginBottom: 3 }}>Provision</div>
                          <textarea value={row.actual || ''} onChange={e => onCellChange(row.id, 'actual', e.target.value)} placeholder="—" rows={1}
                            style={{ width: '100%', background: 'transparent', border: 'none', color: '#c0c0d0', fontFamily: 'JetBrains Mono, monospace', fontSize: 12, resize: 'none', outline: 'none', padding: 0, lineHeight: 1.4, boxSizing: 'border-box' }} />
                        </div>
                        {/* Notes */}
                        <div>
                          <div className="fm" style={{ fontSize: 9, color: 'var(--text-3)', letterSpacing: '.08em', textTransform: 'uppercase', marginBottom: 3 }}>Notes</div>
                          <textarea value={row.notes || ''} onChange={e => onCellChange(row.id, 'notes', e.target.value)} placeholder="—" rows={1}
                            style={{ width: '100%', background: 'transparent', border: 'none', color: 'var(--text-2)', fontFamily: 'JetBrains Mono, monospace', fontSize: 12, resize: 'none', outline: 'none', padding: 0, lineHeight: 1.4, boxSizing: 'border-box' }} />
                        </div>
                      </div>
                    ))}
                  </div>
                </div>
              );
            })
        }
      </div>
    );
  }

  return (
    <div>
      <div className="grid-wrap">
        <div className="grid-table" style={{ gridTemplateColumns: gt }}>
          {/* header */}
          <div className="grid-hcell row-num">#</div>
          {COLUMNS.map(c => (
            <div key={c.id} className="grid-hcell" style={{ position: 'relative' }} title={c.tip || undefined}>
              {c.name}
              {c.tip && <span style={{ marginLeft: 3, opacity: 0.35, fontSize: 9, cursor: 'help' }}>?</span>}
              <div className="col-resize-handle" onMouseDown={e => startResize(e, c.id)} />
            </div>
          ))}
          <div className="grid-hcell" style={{ background: 'var(--bg-1)' }} />

          {rows.length === 0
            ? <div style={{ gridColumn: '1 / -1', padding: 40, textAlign: 'center', color: 'var(--text-3)' }}>{filterActive ? 'No items match the current filter.' : 'No rows yet. Drop a rider PDF above.'}</div>
            : sectionOrder.flatMap(sec => {
                const sRows = sectionMap.get(sec);
                return [
                  /* section header */
                  <div key={`sh-${sec}`} style={{ gridColumn: '1 / -1', background: 'var(--surface-2)', borderTop: '2px solid var(--border-strong)', borderBottom: '1px solid var(--border)', padding: '7px 14px', display: 'flex', alignItems: 'center', gap: 12 }}>
                    <span className="fm" style={{ fontSize: 10, fontWeight: 700, letterSpacing: '.14em', color: 'var(--text-2)', textTransform: 'uppercase' }}>{sec}</span>
                    <button onClick={() => onAddRowToSection(sec)} className="btn btn-secondary btn-sm" style={{ padding: '2px 8px', minHeight: 0, fontSize: 10, gap: 4 }}>
                      <Plus size={10} /> ADD ITEM
                    </button>
                  </div>,
                  /* rows */
                  ...sRows.flatMap((row, idx) => [
                    <div key={`${row.id}-n`} className="row-num">{idx + 1}</div>,
                    ...COLUMNS.map(c => <GridCell key={`${row.id}-${c.id}`} row={row} col={c} onChange={v => onCellChange(row.id, c.id, v)} />),
                    <div key={`${row.id}-x`} className="grid-cell" style={{ padding: 0 }}>
                      <button className="btn btn-ghost" style={{ width: '100%', height: '100%', minHeight: 0, borderRadius: 0 }} onClick={() => { if (window.confirm('Delete row?')) onDeleteRow(row.id); }}>
                        <Trash2 size={13} />
                      </button>
                    </div>
                  ])
                ];
              })
          }
        </div>
      </div>
    </div>
  );
}

function GridCell({ row, col, onChange }) {
  const rowBg = row.status === 'confirmed' ? 'rgba(0,255,0,0.04)'
              : row.status === 'issue'     ? 'rgba(248,113,113,0.06)'
              : '';
  if (col.type === 'qty') {
    const val = parseInt(row.qty, 10) || 0;
    const step = n => onChange(String(Math.max(0, val + n)));
    return (
      <div className="grid-cell" style={{ overflow: 'hidden', background: rowBg }}>
        <div style={{ display: 'flex', alignItems: 'center', width: '100%', height: '100%' }}>
          <button onClick={() => step(-1)} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-3)', width: 22, flexShrink: 0, fontSize: 15, lineHeight: 1, fontFamily: 'monospace', display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%' }}>−</button>
          <input type="number" min="0" value={row.qty || ''} onChange={e => onChange(e.target.value)}
            placeholder="—"
            style={{ flex: 1, minWidth: 0, background: 'transparent', border: 'none', outline: 'none', color: 'var(--text)', fontFamily: 'JetBrains Mono, monospace', fontSize: 13, fontWeight: 700, textAlign: 'center', padding: 0 }} />
          <button onClick={() => step(1)} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-3)', width: 22, flexShrink: 0, fontSize: 15, lineHeight: 1, fontFamily: 'monospace', display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%' }}>+</button>
        </div>
      </div>
    );
  }
  if (col.type === 'status') {
    const CYCLE = ['pending', 'confirmed', 'issue', 'na'];
    const cur   = CYCLE.indexOf(row.status || 'pending');
    const cycle = () => onChange(CYCLE[(cur + 1) % CYCLE.length]);
    const s     = statusOf(row.status || 'pending');
    return (
      <div className="grid-cell" style={{ background: s.bg, cursor: 'pointer' }} onClick={cycle} title="Click to cycle status">
        <div className="cell-in" style={{ gap: 6 }}>
          <StatusChip status={row.status || 'pending'} />
          <span className="fm" style={{ fontSize: 10, color: 'var(--text-3)', opacity: 0.55 }}>↻</span>
        </div>
      </div>
    );
  }
  if (col.type === 'owner') return (
    <div className="grid-cell" style={{ background: rowBg }}><div className="cell-in">
      <Sel value={row.owner || ''} options={ROLES} placeholder="Assign…" onChange={onChange}
        renderValue={o => <RoleTag role={o.id} size="sm" />}
        renderOption={o => <RoleTag role={o.id} size="sm" />} />
    </div></div>
  );
  return (
    <div className="grid-cell" style={{ background: rowBg }}><div className="cell-in">
      <textarea className="cell-tx" value={row[col.id] || ''} onChange={e => onChange(e.target.value)} rows={1} placeholder="—"
        style={{ fontFamily: col.id === 'item' || col.id === 'section' ? 'Manrope,sans-serif' : 'inherit', fontWeight: col.id === 'item' ? 600 : 400 }} />
    </div></div>
  );
}

// ─── FILE ROW (with KITT scanner) ────────────────────────────────────────────

const AUDIO_VOL = 0.6;

function useKittSound(enabled) {
  const audioRef   = useRef(null);
  const ctxRef     = useRef(null);
  const gainRef    = useRef(null);
  const stopTimer  = useRef(null);

  // Lazily wire the audio element through a Web Audio GainNode so that
  // stop() can schedule a sample-accurate ramp instead of using setInterval.
  const ensureAudio = useCallback(() => {
    if (audioRef.current) return;
    const audio = new Audio(KITT_SOUND);
    audioRef.current = audio;
    try {
      const ctx  = new (window.AudioContext || window.webkitAudioContext)();
      const gain = ctx.createGain();
      gain.gain.value = AUDIO_VOL;
      gain.connect(ctx.destination);
      ctx.createMediaElementSource(audio).connect(gain);
      ctxRef.current  = ctx;
      gainRef.current = gain;
    } catch {
      // No Web Audio — fall back to audio.volume
      audio.volume = AUDIO_VOL;
    }
  }, []);

  const play = useCallback(() => {
    if (!enabled) return;
    try {
      ensureAudio();
      clearTimeout(stopTimer.current);
      const ctx  = ctxRef.current;
      const gain = gainRef.current;
      if (ctx && gain) {
        if (ctx.state === 'suspended') ctx.resume();
        gain.gain.cancelScheduledValues(ctx.currentTime);
        gain.gain.setValueAtTime(AUDIO_VOL, ctx.currentTime);
      } else {
        audioRef.current.volume = AUDIO_VOL;
      }
      audioRef.current.currentTime = 0;
      audioRef.current.play().catch(() => {});
    } catch {}
  }, [enabled, ensureAudio]);

  const stop = useCallback(() => {
    try {
      if (!audioRef.current) return;
      clearTimeout(stopTimer.current);
      const ctx  = ctxRef.current;
      const gain = gainRef.current;
      if (ctx && gain) {
        // Sample-accurate 80ms fade — zero clicks guaranteed
        gain.gain.cancelScheduledValues(ctx.currentTime);
        gain.gain.setValueAtTime(gain.gain.value, ctx.currentTime);
        gain.gain.linearRampToValueAtTime(0, ctx.currentTime + 0.08);
        stopTimer.current = setTimeout(() => {
          audioRef.current.pause();
          audioRef.current.currentTime = 0;
          gain.gain.cancelScheduledValues(ctx.currentTime);
          gain.gain.setValueAtTime(AUDIO_VOL, ctx.currentTime);
        }, 90);
      } else {
        // Fallback without Web Audio
        audioRef.current.pause();
        audioRef.current.currentTime = 0;
      }
    } catch {}
  }, []);

  // Stop any in-flight audio the moment the button is toggled off
  useEffect(() => {
    if (!enabled) stop();
  }, [enabled]); // eslint-disable-line

  return { play, stop };
}

// Single rAF loop — all state in a mutable ref so nothing is ever stale.
// stopping is read via stoppingRef.current on every tick, play/stop via refs.
// This eliminates the "jump back" bug caused by the old setInterval capturing
// a stale stopping=false closure at the moment the prop changed.
function KittScanner({ numLeds = 18, soundEnabled = true, stopping = false }) {
  // One render state drives the whole animation — minimal re-renders
  const [frame, setFrame] = useState({ pos: 0, opacity: 1, finished: false });

  const { play, stop } = useKittSound(soundEnabled);
  const playRef     = useRef(play);     playRef.current     = play;
  const stopRef     = useRef(stop);     stopRef.current     = stop;
  const stoppingRef = useRef(stopping); stoppingRef.current = stopping;

  const spread = 2;

  useEffect(() => {
    const STEP_MS = 75;   // ms between LED advances
    const FADE_MS = 380;  // fade-out duration

    // Fully mutable — no closure captures, no stale values
    const s = { pos: 0, dir: 1, phase: 'scan', fadeStart: null, lastStep: null };
    let rafId;

    playRef.current();

    const tick = (now) => {
      // ── Scan phase ──────────────────────────────────────────────────────────
      if (s.phase === 'scan') {
        if (s.lastStep === null) s.lastStep = now;
        const prevPos = s.pos;

        // Fixed-step advance — handles dropped frames without drift
        const steps = Math.floor((now - s.lastStep) / STEP_MS);
        for (let i = 0; i < steps; i++) {
          s.lastStep += STEP_MS;
          const next = s.pos + s.dir;

          if (next >= numLeds - 1) {
            s.pos = numLeds - 1;
            if (!stoppingRef.current) { s.dir = -1; playRef.current(); }
            else { s.phase = 'fade'; s.fadeStart = now; stopRef.current(); break; }
          } else if (next <= 0) {
            s.pos = 0;
            if (!stoppingRef.current) { s.dir =  1; playRef.current(); }
            else { s.phase = 'fade'; s.fadeStart = now; stopRef.current(); break; }
          } else {
            s.pos = next;
          }
        }

        if (s.phase === 'scan') {
          // Re-render only when position changed (~13fps), not every rAF tick (60fps)
          if (s.pos !== prevPos) setFrame({ pos: s.pos, opacity: 1, finished: false });
          rafId = requestAnimationFrame(tick);
          return;
        }
        // Fell through to fade — render once at the final edge position
        setFrame({ pos: s.pos, opacity: 1, finished: false });
      }

      // ── Fade phase ──────────────────────────────────────────────────────────
      const t      = Math.min(1, (now - s.fadeStart) / FADE_MS);
      const eased  = 1 - Math.pow(1 - t, 3); // easeOutCubic — smooth deceleration
      const opacity = 1 - eased;

      if (t >= 1) {
        setFrame({ pos: s.pos, opacity: 1, finished: true });
        return; // animation complete — stop the rAF loop
      }

      setFrame({ pos: s.pos, opacity, finished: false });
      rafId = requestAnimationFrame(tick);
    };

    rafId = requestAnimationFrame(tick);
    return () => { cancelAnimationFrame(rafId); stopRef.current(); };
  }, []); // eslint-disable-line — mount once; live values accessed exclusively via refs

  const { pos, opacity, finished } = frame;

  return (
    <div style={{
      display: 'flex', alignItems: 'center', gap: 3,
      padding: '5px 6px', background: '#000',
      border: '1px solid #160000', borderRadius: 4,
      margin: '0 0 8px', opacity
    }}>
      {Array.from({ length: numLeds }, (_, i) => {
        const dist       = Math.abs(i - pos);
        const on         = !finished && dist <= spread;
        const brightness = on ? Math.max(0, 1 - dist * 0.35) : 0;
        return (
          <div key={i} style={{
            flex: 1, height: 10, borderRadius: 2,
            background: on
              ? `rgba(200,${Math.max(0, 20 - dist * 8)},0,${brightness})`
              : '#160000',
            boxShadow: on
              ? `0 0 ${4 * brightness}px ${2 * brightness}px rgba(180,30,0,${brightness * 0.5})`
              : 'none',
            // Disable transition when finished so the unlit track appears cleanly
            transition: finished ? 'none' : 'background 50ms, box-shadow 50ms'
          }} />
        );
      })}
    </div>
  );
}

// ─── MIAMI VICE SCANNER ──────────────────────────────────────────────────────
// Jazz-cup aesthetic: scrolling teal wave + purple zigzag on light bg
function MiamiScanner({ stopping }) {
  const [visible, setVisible] = useState(true);
  const [fading,  setFading]  = useState(false);
  useEffect(() => {
    if (stopping) {
      setFading(true);
      const t = setTimeout(() => setVisible(false), 500);
      return () => clearTimeout(t);
    } else {
      setVisible(true);
      setFading(false);
    }
  }, [stopping]);
  if (!visible) return <div style={{ height: 28 }} />;
  // 4 tiles × 200px each; animation shifts left 200px per cycle = seamless loop
  const tiles = [0, 1, 2, 3];
  return (
    <div style={{
      height: 28, borderRadius: 4, overflow: 'hidden', position: 'relative',
      background: '#f8f4ff', border: '1px solid rgba(0,150,136,0.18)',
      opacity: fading ? 0 : 1, transition: 'opacity 500ms ease',
    }}>
      <div style={{
        position: 'absolute', inset: 0, display: 'flex',
        animation: 'miamiScroll 1.3s linear infinite',
        willChange: 'transform',
      }}>
        {tiles.map(i => (
          <svg key={i} viewBox="0 0 200 28" width="200" height="28" style={{ flexShrink: 0 }}>
            {/* Teal brushstroke — main wave */}
            <path d="M-10 22 Q30 2 70 16 Q110 28 150 10 Q185 -2 215 18"
              stroke="#00bcd4" strokeWidth="10" fill="none" strokeLinecap="round" opacity="0.85"/>
            {/* Teal second stroke — texture */}
            <path d="M-10 26 Q25 12 60 22 Q100 30 140 16 Q175 6 215 24"
              stroke="#00acc1" strokeWidth="5" fill="none" strokeLinecap="round" opacity="0.45"/>
            {/* Purple zigzag — the squiggle */}
            <path d="M0 26 L18 14 L36 26 L54 14 L72 26 L90 14 L108 26 L126 14 L144 26 L162 14 L180 26 L200 14"
              stroke="#7b2fbe" strokeWidth="2.5" fill="none"
              strokeLinecap="round" strokeLinejoin="round" opacity="0.78"/>
          </svg>
        ))}
      </div>
    </div>
  );
}

function FileRow({ f, onRemove, soundEnabled = true, theme = 'dark' }) {
  return (
    <div style={{ background: 'var(--surface-2)', borderRadius: 8, border: `1px solid ${f.status === 'done' ? 'rgba(51,170,85,.3)' : f.status === 'error' ? 'rgba(187,51,51,.3)' : 'var(--border-strong)'}`, overflow: 'hidden' }}>
      <div style={{ padding: '10px 0 10px 12px', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, minWidth: 0 }}>
          {f.status === 'done'    && <CheckCircle2 size={13} style={{ color: 'var(--ok)', flexShrink: 0 }} />}
          {f.status === 'error'   && <AlertCircle  size={13} style={{ color: 'var(--err)', flexShrink: 0 }} />}
          {f.status === 'parsing' && <div style={{ width: 13, flexShrink: 0 }} />}
          <span className="fm" style={{ fontSize: 12, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', color: f.status === 'done' ? 'var(--ok)' : 'var(--text-2)' }}>{f.name}</span>
        </div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 6, flexShrink: 0 }}>
          {f.status === 'done'    && <span className="fm" style={{ fontSize: 10, color: '#55cc77', letterSpacing: '.08em', textTransform: 'uppercase', whiteSpace: 'nowrap' }}>{f.rowCount} ROWS</span>}
          {f.status === 'error'   && <span className="fm" style={{ fontSize: 10, color: '#cc4444', letterSpacing: '.08em', textTransform: 'uppercase' }}>FAILED</span>}
          {f.status === 'parsing' && <span className="fm" style={{ fontSize: 10, color: '#cc9922', letterSpacing: '.08em', textTransform: 'uppercase' }}>SCANNING</span>}
          {onRemove
            ? <button onClick={onRemove} className="btn btn-ghost" style={{ width: 36, height: 36, padding: 0, minHeight: 0, flexShrink: 0 }}><X size={14} /></button>
            : <div style={{ width: 36, flexShrink: 0 }} />}
        </div>
      </div>
      <div style={{ margin: '0 12px 10px' }}>
        {theme === 'light'
          ? <MiamiScanner stopping={f.status !== 'parsing'} />
          : <KittScanner soundEnabled={soundEnabled} stopping={f.status !== 'parsing'} />}
      </div>
    </div>
  );
}

// ─── AUTH SCREEN ──────────────────────────────────────────────────────────────

function AuthScreen() {
  const [mode,    setMode]    = useState('login'); // 'login' | 'signup'
  const [email,   setEmail]   = useState('');
  const [pass,    setPass]    = useState('');
  const [busy,    setBusy]    = useState(false);
  const [err,     setErr]     = useState('');
  const [msg,     setMsg]     = useState('');
  const [theme, toggleTheme]  = useTheme();

  const isLight = theme === 'light';

  const submit = async e => {
    e.preventDefault();
    if (!email.trim() || !pass) { setErr('Email and password required.'); return; }
    setErr(''); setMsg(''); setBusy(true);
    try {
      let res;
      if (mode === 'login') {
        res = await db.auth.signInWithPassword({ email: email.trim(), password: pass });
      } else {
        res = await db.auth.signUp({ email: email.trim(), password: pass });
      }
      if (res.error) { setErr(res.error.message); setBusy(false); return; }
      if (mode === 'signup' && !res.data.session) {
        setMsg('✓ Check your email to confirm your account, then log in.');
        setMode('login'); setPass('');
      }
      // If login succeeded, onAuthStateChange in App will handle transition
    } catch (ex) { setErr(ex.message); }
    setBusy(false);
  };

  const accentColor  = isLight ? '#d01070' : '#cc4444';
  const okColor      = isLight ? '#00897b' : '#33aa55';
  const labelColor   = isLight ? '#4a2a7a' : 'var(--text-2)';

  return (
    <div style={{
      minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center',
      background: 'var(--bg)', padding: '24px 16px',
    }}>
      {/* theme toggle top-right */}
      <button
        onClick={toggleTheme}
        className="btn btn-ghost btn-sm"
        style={{ position: 'fixed', top: 16, right: 16, width: 36, padding: 0, fontSize: 15 }}
        title={theme === 'dark' ? 'Miami Vice mode' : 'Knight Rider mode'}
      >
        {theme === 'dark' ? '🌴' : '🚨'}
      </button>

      <div style={{
        width: '100%', maxWidth: 380,
        background: 'var(--surface)', border: '1px solid var(--border-strong)',
        borderRadius: 12, padding: '36px 32px 32px',
        boxShadow: isLight
          ? '0 8px 40px rgba(120,50,180,0.12)'
          : '0 8px 40px rgba(0,0,0,0.7), 0 0 0 1px rgba(187,32,32,0.08)',
      }}>
        {/* Logo */}
        <div style={{ textAlign: 'center', marginBottom: 28 }}>
          <div className="fm" style={{ fontSize: 22, fontWeight: 700, letterSpacing: '.08em', lineHeight: 1 }}>
            {isLight ? (
              <><span style={{ color: '#00bcd4' }}>RIDER</span><span style={{ color: '#7b2fbe' }}>/</span><span style={{ color: '#00bcd4' }}>FLOW</span></>
            ) : (
              <><span style={{ color: '#cc4444' }}>RIDER</span><span style={{ color: '#ffffff' }}>/</span><span style={{ color: '#cc4444' }}>FLOW</span></>
            )}
          </div>
          <div className="fm" style={{ fontSize: 10, color: 'var(--text-3)', marginTop: 6, textTransform: 'uppercase', letterSpacing: '.12em' }}>
            {mode === 'login' ? 'Admin Sign In' : 'Create Account'}
          </div>
        </div>

        <form onSubmit={submit} style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
          {/* Email */}
          <div>
            <label className="fm" style={{ fontSize: 10, fontWeight: 700, color: labelColor, textTransform: 'uppercase', letterSpacing: '.08em', display: 'block', marginBottom: 6 }}>Email</label>
            <input
              type="email" autoComplete="username" required
              value={email} onChange={e => setEmail(e.target.value)}
              placeholder="you@example.com"
              className="input"
              style={{ borderColor: isLight ? 'rgba(0,150,136,0.4)' : undefined }}
              disabled={busy}
            />
          </div>
          {/* Password */}
          <div>
            <label className="fm" style={{ fontSize: 10, fontWeight: 700, color: labelColor, textTransform: 'uppercase', letterSpacing: '.08em', display: 'block', marginBottom: 6 }}>Password</label>
            <input
              type="password" autoComplete={mode === 'login' ? 'current-password' : 'new-password'} required
              value={pass} onChange={e => setPass(e.target.value)}
              placeholder="••••••••"
              className="input"
              style={{ borderColor: isLight ? 'rgba(0,150,136,0.4)' : undefined }}
              disabled={busy}
            />
          </div>

          {/* Error / message */}
          {err && (
            <div className="fm" style={{ fontSize: 11, color: accentColor, background: isLight ? 'rgba(208,16,112,0.07)' : 'rgba(187,32,32,0.1)', border: `1px solid ${isLight ? 'rgba(208,16,112,0.2)' : 'rgba(187,32,32,0.2)'}`, borderRadius: 6, padding: '8px 12px' }}>
              {err}
            </div>
          )}
          {msg && (
            <div className="fm" style={{ fontSize: 11, color: okColor, background: isLight ? 'rgba(0,137,123,0.07)' : 'rgba(51,170,85,0.1)', border: `1px solid ${isLight ? 'rgba(0,137,123,0.2)' : 'rgba(51,170,85,0.2)'}`, borderRadius: 6, padding: '8px 12px' }}>
              {msg}
            </div>
          )}

          {/* Submit */}
          <button
            type="submit"
            className={`btn ${isLight ? 'btn-amber' : 'btn-primary'}`}
            style={{ width: '100%', marginTop: 4 }}
            disabled={busy}
          >
            {busy
              ? <><Loader2 size={14} style={{ animation: 'spin 1s linear infinite' }} /> {mode === 'login' ? 'Signing in…' : 'Creating…'}</>
              : mode === 'login' ? 'Sign In' : 'Create Account'
            }
          </button>
        </form>

        {/* Mode toggle */}
        <div style={{ marginTop: 20, textAlign: 'center' }}>
          <button
            onClick={() => { setMode(m => m === 'login' ? 'signup' : 'login'); setErr(''); setMsg(''); }}
            className="btn btn-ghost btn-sm"
            style={{ fontSize: 11, color: 'var(--text-2)' }}
          >
            {mode === 'login' ? 'No account? Sign up' : 'Have an account? Sign in'}
          </button>
        </div>
      </div>
    </div>
  );
}

// ─── LANDING ─────────────────────────────────────────────────────────────────

function Landing({ session }) {
  const [shows,        setShows]        = useState([]);
  const [dragOver,     setDragOver]     = useState(false);
  const [modal,        setModal]        = useState(false);
  const [showName,     setShowName]     = useState('');
  const [fileStates,   setFileStates]   = useState([]);
  const [parseResult,  setParseResult]  = useState(null);
  const [soundOn,      setSoundOn]      = useState(true);
  const [pendingDelete,setPendingDelete] = useState(null);
  const [theme, toggleTheme]            = useTheme();
  const inputRef = useRef(null);
  const store    = useRef({});
  const rowsRef  = useRef({}); // per-file parsed rows, keyed by id

  useEffect(() => { loadShows().then(setShows); }, []);

  // Recompute parseResult from whatever files are still in fileStates
  const recomputeResult = (states) => {
    const goodIds = states.filter(s => s.status === 'done').map(s => s.id);
    const allRows = goodIds.flatMap(id => rowsRef.current[id]?.rows || []);
    if (allRows.length) {
      const title = rowsRef.current[goodIds[0]]?.title || '';
      setParseResult({ title, rows: allRows });
    } else {
      setParseResult(null);
    }
  };

  const startParse = async files => {
    const arr = Array.from(files);
    if (!arr.length) return;
    setModal(true);
    setShowName('');
    setParseResult(null);
    const entries = arr.map(f => { const id = genId(); store.current[id] = f; return { id, name: f.name, status: 'parsing' }; });
    setFileStates(prev => {
      const next = [...prev.filter(f => f.status !== 'parsing'), ...entries];
      return next;
    });

    const results = await Promise.all(entries.map(async e => {
      try {
        const raw  = await parseFile(store.current[e.id]);
        const rows = normalizeRows(raw);
        rowsRef.current[e.id] = { rows, title: raw.title };
        setFileStates(prev => prev.map(f => f.id === e.id ? { ...f, status: 'done', rowCount: rows.length } : f));
        return { ok: true, id: e.id, title: raw.title, rows };
      } catch (err) {
        setFileStates(prev => prev.map(f => f.id === e.id ? { ...f, status: 'error', error: err.message } : f));
        return { ok: false };
      }
    }));

    const good = results.filter(r => r.ok);
    if (good.length) {
      const allRows = good.flatMap(d => d.rows);
      const title   = good[0].title || '';
      setParseResult({ title, rows: allRows });
      if (title) setShowName(title);
    }
  };

  const removeFile = (id) => {
    const next = fileStates.filter(f => f.id !== id);
    delete store.current[id];
    delete rowsRef.current[id];
    setFileStates(next);
    recomputeResult(next);
  };

  const create = async () => {
    const n = showName.trim(); if (!n) return;
    const id = genId();
    const show = { id, name: n, createdAt: Date.now(), rows: parseResult?.rows || [] };
    await saveShow(id, show);
    const next = [{ id, name: n, createdAt: Date.now() }, ...shows];
    await saveShows(next);
    setModal(false);
    nav(`/show/${id}`);
  };

  const deleteShow = async (id) => {
    if (pendingDelete !== id) { setPendingDelete(id); return; }
    setPendingDelete(null);
    const next = shows.filter(s => s.id !== id);
    setShows(next);
    saveShows(next);
    await deleteShowFromDb(id);
  };

  const closeModal = () => { setModal(false); setFileStates([]); setParseResult(null); store.current = {}; rowsRef.current = {}; };
  const anyParsing = fileStates.some(f => f.status === 'parsing');

  return (
    <div className="fade-in" style={{ minHeight: '100vh', position: 'relative', background: 'var(--bg)' }}>
      {/* GREEN SCAN GRID */}
      <div className="bg-grid" style={{ position: 'fixed', inset: 0, pointerEvents: 'none', zIndex: 0 }} />
      <div style={{ position: 'relative', maxWidth: 720, margin: '0 auto', padding: '32px 20px 80px', zIndex: 1 }}>

        {/* NAV */}
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 56 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
            {/* Logo — Knight Rider dark / Miami Vice light */}
            <div style={{
              padding: '6px 14px', height: 36, borderRadius: 4,
              background: theme === 'light'
                ? 'linear-gradient(180deg,#d0f5f0,#b8ede8)'
                : 'linear-gradient(180deg,#2e0a0a,#1a0505)',
              border: theme === 'light' ? '2px solid #99e0d8' : '2px solid #2e0a0a',
              boxShadow: 'inset 0 1px 0 rgba(255,255,255,.06)',
              display: 'flex', alignItems: 'center', justifyContent: 'center',
              cursor: 'default', userSelect: 'none', gap: 0,
              transition: 'all 200ms ease',
            }}>
              <span className="fm" style={{ fontSize: 11, fontWeight: 700, letterSpacing: '.1em', lineHeight: 1 }}>
                {theme === 'light' ? (
                  <>
                    <span style={{ color: '#00bcd4' }}>RIDER</span>
                    <span style={{ color: '#7b2fbe' }}>/</span>
                    <span style={{ color: '#00bcd4' }}>FLOW</span>
                  </>
                ) : (
                  <>
                    <span style={{ color: '#cc4444' }}>RIDER</span>
                    <span style={{ color: '#ffffff' }}>/</span>
                    <span style={{ color: '#cc4444' }}>FLOW</span>
                  </>
                )}
              </span>
            </div>
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
            <button onClick={toggleTheme} className="btn btn-ghost btn-sm" style={{ width: 36, padding: 0, fontSize: 15 }} title={theme === 'dark' ? 'Miami Vice mode' : 'Knight Rider mode'}>
              {theme === 'dark' ? '🌴' : '🚨'}
            </button>
            {session && (
              <button
                onClick={() => db.auth.signOut()}
                className="btn btn-ghost btn-sm"
                style={{ fontSize: 11, color: 'var(--text-3)', padding: '6px 10px' }}
                title={`Signed in as ${session.user.email}`}
              >
                Sign out
              </button>
            )}
          </div>
        </div>

        {/* HERO */}
        <div style={{ textAlign: 'center', marginBottom: 44 }}>
          <div style={{ display: 'inline-flex', alignItems: 'center', gap: 6, padding: '5px 14px', borderRadius: 6, marginBottom: 28, background: 'rgba(51,170,85,0.07)', border: '1px solid rgba(51,170,85,0.2)', fontSize: 10, fontWeight: 700, color: 'var(--ok)', fontFamily: 'JetBrains Mono', textTransform: 'uppercase', letterSpacing: '.12em' }}>
            <Radio size={10} /> Real-time rider management
          </div>
          <h1 style={{ fontFamily: "'Bebas Neue', sans-serif", fontSize: 'clamp(40px,10vw,108px)', margin: '0 0 20px', lineHeight: 1, fontWeight: 400, letterSpacing: '.04em', whiteSpace: 'nowrap' }}>
            {theme === 'light' ? (
              <>
                <span style={{ color: '#00bcd4' }}>RIDER</span>
                <span style={{ color: '#7b2fbe' }}>/</span>
                <span style={{ color: '#00bcd4' }}>FLOW</span>
              </>
            ) : (
              <>
                <span style={{ color: '#cc4444' }}>RIDER</span>
                <span style={{ color: '#ffffff' }}>/</span>
                <span style={{ color: '#cc4444' }}>FLOW</span>
              </>
            )}
          </h1>
          <p style={{ color: 'var(--text-2)', fontSize: 15, maxWidth: 600, margin: '0 auto', textAlign: 'center' }}>From vendor to talent and back... all without leaving your browser.</p>
        </div>

        {/* DROP ZONE */}
        <div
          onDragEnter={e => { e.preventDefault(); setDragOver(true); }}
          onDragOver={e  => { e.preventDefault(); setDragOver(true); }}
          onDragLeave={e => { e.preventDefault(); setDragOver(false); }}
          onDrop={e      => { e.preventDefault(); setDragOver(false); startParse(e.dataTransfer.files); }}
          onClick={() => inputRef.current?.click()}
          style={{
            border: `2px dashed ${dragOver ? 'var(--ok)' : 'var(--border-strong)'}`,
            borderRadius: 12, padding: '44px 24px', textAlign: 'center', cursor: 'pointer', marginBottom: 16,
            background: dragOver ? 'var(--ok-dim)' : 'var(--surface)',
            transition: 'all 150ms ease',
            boxShadow: 'none'
          }}
        >
          <Upload size={32} style={{ color: dragOver ? 'var(--ok)' : 'var(--text-3)', display: 'block', margin: '0 auto 12px' }} />
          <div className="fd" style={{ fontSize: 22, color: dragOver ? 'var(--ok)' : 'var(--text)', marginBottom: 6, textShadow: dragOver ? '0 0 20px var(--ok)' : 'none', whiteSpace: 'normal' }}>
            {dragOver ? 'INITIATING SCAN…' : 'Drop rider file — or click to select'}
          </div>
          <div className="fm" style={{ fontSize: 10, color: 'var(--text-2)', letterSpacing: '.1em', textTransform: 'uppercase', marginBottom: 4 }}>PDF · XLSX · XLS · CSV</div>
          <div className="fm" style={{ fontSize: 9, color: 'var(--text-3)', letterSpacing: '.08em', textTransform: 'uppercase' }}>Local heuristic scan — no AI // max 30mb</div>
        </div>
        <input ref={inputRef} type="file" hidden multiple accept=".pdf,.xlsx,.xls,.csv"
          onChange={e => { startParse(e.target.files); e.target.value = ''; }} />

        {/* RECENTS */}
        {shows.length > 0 && <>
          <div style={{ fontSize: 11, color: 'var(--text-3)', letterSpacing: '.12em', textTransform: 'uppercase', marginBottom: 12, marginTop: 32 }} className="fm">Recent</div>
          <div style={{ display: 'grid', gap: 8 }}>
            {shows.map(s => {
              const isDel = pendingDelete === s.id;
              return (
                <div key={s.id} style={{ display: 'flex', alignItems: 'stretch', background: 'var(--surface)', border: `1px solid ${isDel ? '#3a0000' : 'var(--border)'}`, borderRadius: 12, overflow: 'hidden', transition: 'border-color 150ms' }}>
                  {/* Nav area */}
                  <button onClick={() => { setPendingDelete(null); nav(`/show/${s.id}`); }}
                    style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '14px 18px', background: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit', fontSize: 14, color: 'var(--text)', textAlign: 'left' }}
                    onMouseEnter={e => e.currentTarget.style.background = 'rgba(255,255,255,0.03)'}
                    onMouseLeave={e => e.currentTarget.style.background = 'none'}
                  >
                    <div>
                      <div style={{ fontWeight: 600 }}>{s.name}</div>
                      <div className="fm" style={{ fontSize: 11, color: 'var(--text-3)', marginTop: 3 }}>{fmtRel(s.createdAt)}</div>
                    </div>
                    <ArrowRight size={16} style={{ color: 'var(--text-3)', flexShrink: 0 }} />
                  </button>
                  {/* Delete button — tap once to arm (red), tap again to confirm */}
                  <button
                    onClick={() => deleteShow(s.id)}
                    onMouseLeave={() => { if (isDel) setPendingDelete(null); }}
                    className={`btn ${isDel ? 'btn-primary' : 'btn-ghost'}`}
                    style={{ width: 48, padding: 0, minHeight: 0, borderRadius: 0, borderLeft: `1px solid ${isDel ? '#3a0000' : 'var(--border)'}`, flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', transition: 'all 150ms' }}
                    title={isDel ? 'Confirm delete' : 'Delete show'}
                  >
                    {isDel
                      ? <span className="fm" style={{ fontSize: 9, letterSpacing: '.06em' }}>DEL?</span>
                      : <X size={14} />}
                  </button>
                </div>
              );
            })}
          </div>
        </>}
      </div>

      {/* NAMING MODAL */}
      {modal && (
        <div className="modal-bg" onClick={e => { if (e.target === e.currentTarget) closeModal(); }}>
          <div className="modal-panel safe-b" onClick={e => e.stopPropagation()}>
            <div style={{ padding: '20px 20px 0', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
              <div className="fd" style={{ fontSize: 22 }}>
                {anyParsing
                  ? fileStates.length === 1
                    ? 'Scanning your rider…'
                    : `Scanning your ${fileStates.length} riders…`
                  : 'New show'}
              </div>
              <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
                <button
                  onClick={() => setSoundOn(s => !s)}
                  className={`btn btn-sm ${soundOn ? 'btn-blue' : 'btn-ghost'}`}
                  title={soundOn ? 'Mute' : 'Unmute'}
                >
                  {soundOn ? 'MUTE' : 'UNMUTE'}
                </button>
                <button className="btn btn-ghost" style={{ width: 40, height: 40, padding: 0 }} onClick={closeModal}><X size={18} /></button>
              </div>
            </div>
            <div style={{ padding: 20, display: 'grid', gap: 12 }}>
              {fileStates.map(f => <FileRow key={f.id} f={f} soundEnabled={soundOn} theme={theme} onRemove={f.status !== 'parsing' ? () => removeFile(f.id) : null} />)}
              <div>
                <label className="fm" style={{ fontSize: 11, color: 'var(--text-2)', display: 'block', marginBottom: 8, letterSpacing: '.06em', textTransform: 'uppercase' }}>Show name</label>
                <input className="input" placeholder="e.g. The Lumineers — Cabo 2026" value={showName}
                  onChange={e => setShowName(e.target.value)} autoFocus
                  onKeyDown={e => e.key === 'Enter' && showName.trim() && !anyParsing && create()} />
              </div>
              <button className="btn btn-primary" style={{ width: '100%' }} disabled={!showName.trim() || anyParsing} onClick={create}>
                {anyParsing
                  ? <><Loader2 size={15} style={{ animation: 'spin 1s linear infinite' }} /> Scanning…</>
                  : parseResult
                    ? <><ArrowRight size={15} /> Create show · {parseResult.rows.length} items</>
                    : <><Plus size={15} /> Create blank show</>}
              </button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

// ─── SHOW VIEW ────────────────────────────────────────────────────────────────

function ShowView({ id, session }) {
  const [show,       setShow]      = useState(null);
  const [loading,    setLoading]   = useState(true);
  const [saving,     setSaving]    = useState(false);
  const [dragOver,   setDragOver]  = useState(false);
  const [fileStates, setFileStates]= useState([]);
  const [shareModal, setShareModal]= useState(null); // 'talent' | 'vendor'
  const [letterhead,    setLetterhead]   = useState(null);
  const [settingsOpen,  setSettingsOpen] = useState(false);
  const [exportOpen,    setExportOpen]   = useState(false);
  const [filterStatus,  setFilterStatus] = useState('all');
  const [filterSection, setFilterSection]= useState('all');
  const [theme, toggleTheme]             = useTheme();
  const inputRef       = useRef(null);
  const letterheadRef  = useRef(null);
  const settingsRef    = useRef(null);
  const exportRef      = useRef(null);
  const store          = useRef({});
  const saveTimer      = useRef(null);

  useEffect(() => {
    const lh = localStorage.getItem(`fr_lh_${id}`);
    if (lh) setLetterhead(lh);
  }, [id]);

  // Close dropdowns on outside click
  useEffect(() => {
    const handler = e => {
      if (settingsRef.current && !settingsRef.current.contains(e.target)) setSettingsOpen(false);
      if (exportRef.current   && !exportRef.current.contains(e.target))   setExportOpen(false);
    };
    document.addEventListener('mousedown', handler);
    return () => document.removeEventListener('mousedown', handler);
  }, []);

  const uploadLetterhead = e => {
    const file = e.target.files[0];
    if (!file) return;
    const reader = new FileReader();
    reader.onload = () => { setLetterhead(reader.result); localStorage.setItem(`fr_lh_${id}`, reader.result); };
    reader.readAsDataURL(file);
    e.target.value = '';
  };

  const clearLetterhead = () => { setLetterhead(null); localStorage.removeItem(`fr_lh_${id}`); };

  useEffect(() => {
    loadShow(id).then(s => { setShow(s); setLoading(false); });
  }, [id]);

  const persist = useCallback((next) => {
    setSaving(true);
    clearTimeout(saveTimer.current);
    saveTimer.current = setTimeout(async () => { await saveShow(id, next); setSaving(false); }, 600);
  }, [id]);

  const updateCell = (rowId, colId, val) => setShow(prev => {
    const next = { ...prev, rows: prev.rows.map(r => r.id === rowId ? { ...r, [colId]: val } : r) };
    persist(next); return next;
  });

  const deleteRow = rowId => setShow(prev => {
    const next = { ...prev, rows: prev.rows.filter(r => r.id !== rowId) };
    persist(next); return next;
  });

  const addRowToSection = section => setShow(prev => {
    const rows = prev.rows;
    const lastIdx = rows.reduce((acc, r, i) => (r.section || 'Other') === section ? i : acc, -1);
    const insertAt = lastIdx === -1 ? rows.length : lastIdx + 1;
    const newRow = { id: genId(), section, item: '', qty: '', spec: '', actual: '', status: 'pending', owner: '', notes: '' };
    const next = { ...prev, rows: [...rows.slice(0, insertAt), newRow, ...rows.slice(insertAt)] };
    persist(next); return next;
  });

  const addFiles = async files => {
    const arr = Array.from(files); if (!arr.length) return;
    const entries = arr.map(f => { const fid = genId(); store.current[fid] = f; return { id: fid, name: f.name, status: 'parsing' }; });
    setFileStates(prev => [...prev, ...entries]);
    await Promise.all(entries.map(async e => {
      try {
        const raw  = await parseFile(store.current[e.id]);
        const rows = normalizeRows(raw);
        setFileStates(prev => prev.map(f => f.id === e.id ? { ...f, status: 'done', rowCount: rows.length } : f));
        setShow(prev => { const next = { ...prev, rows: [...prev.rows, ...rows] }; persist(next); return next; });
      } catch (err) {
        setFileStates(prev => prev.map(f => f.id === e.id ? { ...f, status: 'error', error: err.message } : f));
      }
    }));
  };

  const exportCSV = () => {
    if (!show) return;
    const header = COLUMNS.map(c => `"${c.name}"`).join(',');
    const body   = (show.rows || []).map(r => COLUMNS.map(c => {
      let v = r[c.id] || '';
      if (c.id === 'status') v = statusOf(v).label;
      if (c.id === 'owner')  v = roleOf(v)?.label || '';
      return `"${String(v).replace(/"/g, '""')}"`;
    }).join(',')).join('\n');
    const csv = header + '\n' + body;
    try {
      const a = Object.assign(document.createElement('a'), { href: URL.createObjectURL(new Blob([csv], { type: 'text/csv' })), download: `${(show.name || 'rider').replace(/[^a-z0-9]+/gi, '-')}.csv` });
      document.body.appendChild(a); a.click(); document.body.removeChild(a);
    } catch { navigator.clipboard.writeText(csv).then(() => alert('Copied to clipboard')); }
  };

  const exportPDF = () => {
    if (!show) return;
    const generate = () => {
      const sectionOrder = [];
      const sectionMap = new Map();
      (show.rows || []).forEach(r => {
        const s = r.section || 'Other';
        if (!sectionMap.has(s)) { sectionMap.set(s, []); sectionOrder.push(s); }
        sectionMap.get(s).push(r);
      });
      const el = document.createElement('div');
      const dateStr = new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
      el.innerHTML = `<html><head><style>
        *{box-sizing:border-box;}
        body{font-family:-apple-system,Helvetica,sans-serif;margin:0;padding:0;color:#111;font-size:12px;}
        .lh-img{width:100%;max-height:110px;object-fit:contain;object-position:left;display:block;margin-bottom:16px;}
        .show-hdr{border-bottom:2px solid #222;padding-bottom:8px;margin-bottom:18px;}
        .show-title{font-size:17px;font-weight:700;}
        .show-date{font-size:9px;color:#888;margin-top:3px;}
        .sec-block{margin-bottom:16px;}
        .sec-title{font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.1em;color:#333;padding:4px 8px;background:#efefef;border-bottom:1px solid #ccc;}
        table{width:100%;border-collapse:collapse;}
        th{font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:#888;padding:4px 8px;text-align:left;border-bottom:1px solid #e0e0e0;background:#fafafa;}
        td{padding:5px 8px;border-bottom:1px solid #f0f0f0;vertical-align:top;line-height:1.4;}
        tr:last-child td{border-bottom:none;}
        tr{page-break-inside:avoid;}
        .status{display:inline-block;padding:2px 6px;border-radius:3px;font-size:9px;font-weight:700;text-transform:uppercase;color:#fff;}
        .status.confirmed{background:#00aa00;}.status.pending{background:#999;}.status.issue{background:#dd3333;}.status.na{background:#aaa;}
      </style></head><body>
        ${letterhead ? `<img class="lh-img" src="${letterhead}" />` : ''}
        <div class="show-hdr">
          <div class="show-title">${show.name || 'Production Rider'}</div>
          <div class="show-date">${dateStr}</div>
        </div>
        ${sectionOrder.map(sec => {
          const srows = sectionMap.get(sec);
          return `<div class="sec-block">
            <div class="sec-title">${sec}</div>
            <table>
              <tr><th>Item</th><th style="width:34px;text-align:center">Qty</th><th>Rider Spec</th><th>Actual Provision</th><th style="width:72px">Status</th><th>Notes</th></tr>
              ${srows.map(r => `<tr>
                <td style="font-weight:600">${r.item||'—'}</td>
                <td style="text-align:center">${r.qty||'—'}</td>
                <td style="color:#555">${r.spec||'—'}</td>
                <td>${r.actual||'—'}</td>
                <td><span class="status ${r.status||'pending'}">${r.status||'pending'}</span></td>
                <td style="color:#777">${r.notes||'—'}</td>
              </tr>`).join('')}
            </table>
          </div>`;
        }).join('')}
      </body></html>`;

      const filename = `${(show.name||'rider').replace(/[^a-z0-9]+/gi,'-')}.pdf`;
      const opt = { margin: [16, 10, 14, 10], filename, image: { type: 'jpeg', quality: .98 }, html2canvas: { scale: 2 }, jsPDF: { orientation: 'portrait', unit: 'mm', format: 'a4' } };

      window.html2pdf().set(opt).from(el).toPdf().get('pdf').then(pdf => {
        const total = pdf.internal.getNumberOfPages();
        const pw = pdf.internal.pageSize.getWidth();
        const ph = pdf.internal.pageSize.getHeight();
        for (let i = 1; i <= total; i++) {
          pdf.setPage(i);
          pdf.setFontSize(8); pdf.setTextColor(80, 80, 80);
          pdf.text(show.name || '', 10, 9);
          pdf.setTextColor(170, 170, 170);
          pdf.text(`${i} of ${total}`, pw / 2, ph - 5, { align: 'center' });
        }
      }).save();
    };
    if (!window.html2pdf) {
      const s = document.createElement('script');
      s.src = 'https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js';
      s.onload = generate;
      document.head.appendChild(s);
    } else { generate(); }
  };

  if (loading) return <div style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center' }}><Loader2 size={24} style={{ animation: 'spin 1s linear infinite', color: 'var(--accent)' }} /></div>;
  if (!show)   return <div style={{ padding: 40 }}>Not found</div>;

  const total     = (show.rows || []).length;
  const confirmed = (show.rows || []).filter(r => r.status === 'confirmed').length;
  const issues    = (show.rows || []).filter(r => r.status === 'issue').length;
  const pending   = (show.rows || []).filter(r => !r.status || r.status === 'pending').length;
  const naCount   = (show.rows || []).filter(r => r.status === 'na').length;
  const pct       = total > 0 ? Math.round((confirmed / total) * 100) : 0;
  const anyParsing = fileStates.some(f => f.status === 'parsing');
  const hasRows   = total > 0;
  const allSections = [...new Set((show.rows || []).map(r => r.section || 'Other'))];
  const filteredRows = (show.rows || []).filter(r => {
    if (filterStatus !== 'all' && (r.status || 'pending') !== filterStatus) return false;
    if (filterSection !== 'all' && (r.section || 'Other') !== filterSection) return false;
    return true;
  });

  return (
    <div className="fade-in" style={{ minHeight: '100vh' }}>
      {/* TOPBAR */}
      <div style={{ position: 'sticky', top: 0, zIndex: 50, background: 'var(--topbar-bg)', backdropFilter: 'blur(12px)', borderBottom: '1px solid var(--border)' }}>
        <div style={{ maxWidth: 1400, margin: '0 auto', padding: '10px 16px', display: 'flex', alignItems: 'center', gap: 8 }}>
          {/* Back */}
          <button className="btn btn-secondary btn-sm" style={{ width: 36, padding: 0, flexShrink: 0 }} onClick={() => nav('/')}>
            <ChevronLeft size={16} />
          </button>
          {/* Title */}
          <div style={{ flex: 1, minWidth: 0, margin: '0 4px' }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
              <div className="fd" style={{ fontSize: 16, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{show.name}</div>
              <span className="fm" style={{ fontSize: 9, fontWeight: 700, letterSpacing: '.12em', color: '#cc4444', background: '#1e0606', border: '1px solid #2e0a0a', borderRadius: 4, padding: '1px 6px', flexShrink: 0 }}>ADMIN</span>
            </div>
            <div className="fm" style={{ fontSize: 10, color: 'var(--text-3)', marginTop: 1, display: 'flex', alignItems: 'center', gap: 8 }}>
              <span className={`sync-dot${saving ? ' saving' : ''}`} />
              {saving ? 'SAVING…' : 'SYNCED'}
              {hasRows && <><span>·</span><span>{total} items</span></>}
            </div>
          </div>
          {/* ··· Settings dropdown */}
          <div ref={settingsRef} style={{ position: 'relative', flexShrink: 0 }}>
            <div
              onDragEnter={e => { e.preventDefault(); setDragOver(true); setSettingsOpen(true); }}
              onDragOver={e  => { e.preventDefault(); setDragOver(true); }}
              onDragLeave={e => { e.preventDefault(); setDragOver(false); }}
              onDrop={e      => { e.preventDefault(); setDragOver(false); addFiles(e.dataTransfer.files); setSettingsOpen(false); }}
              onClick={() => setSettingsOpen(o => !o)}
              className="btn btn-secondary btn-sm"
              style={{ cursor: 'pointer', background: dragOver ? 'rgba(0,255,0,0.08)' : undefined }}
            >
              {dragOver ? <Upload size={13} /> : '···'}
            </div>
            {settingsOpen && (
              <div style={{
                position: 'absolute', top: 'calc(100% + 6px)', right: 0, zIndex: 200,
                background: 'var(--dropdown-bg)', border: '1px solid var(--border-strong)', borderRadius: 6,
                minWidth: 180, padding: 4,
                boxShadow: '0 8px 24px rgba(0,0,0,.7), 0 0 0 1px rgba(0,255,0,.06)'
              }}>
                {/* Import */}
                <div
                  onDragEnter={e => { e.preventDefault(); setDragOver(true); }}
                  onDragOver={e  => { e.preventDefault(); setDragOver(true); }}
                  onDragLeave={e => { e.preventDefault(); setDragOver(false); }}
                  onDrop={e      => { e.preventDefault(); setDragOver(false); addFiles(e.dataTransfer.files); setSettingsOpen(false); }}
                  onClick={() => { inputRef.current?.click(); setSettingsOpen(false); }}
                  className="fm"
                  style={{
                    display: 'flex', alignItems: 'center', gap: 8, padding: '8px 12px',
                    cursor: 'pointer', borderRadius: 4, color: 'var(--ok)',
                    fontSize: 11, fontWeight: 700, letterSpacing: '.08em', textTransform: 'uppercase',
                    background: dragOver ? 'rgba(0,255,0,0.08)' : 'transparent'
                  }}
                  onMouseEnter={e => e.currentTarget.style.background = 'rgba(0,255,0,0.06)'}
                  onMouseLeave={e => e.currentTarget.style.background = dragOver ? 'rgba(0,255,0,0.08)' : 'transparent'}
                >
                  <Upload size={13} /> {dragOver ? 'Drop rider here…' : 'Import rider'}
                </div>
                {/* Divider */}
                <div style={{ height: 1, background: 'var(--border)', margin: '4px 8px' }} />
                {/* Letterhead */}
                {letterhead ? (
                  <div className="fm" style={{
                    display: 'flex', alignItems: 'center', gap: 8, padding: '8px 12px',
                    cursor: 'default', borderRadius: 4, color: '#ffcc00',
                    fontSize: 11, fontWeight: 700, letterSpacing: '.08em', textTransform: 'uppercase'
                  }}>
                    <img src={letterhead} style={{ height: 18, maxWidth: 48, objectFit: 'contain', borderRadius: 2 }} />
                    <span style={{ flex: 1 }}>Letterhead set</span>
                    <button onClick={clearLetterhead} style={{ background: 'none', border: 'none', cursor: 'pointer', color: '#ffcc00', padding: 0, display: 'flex', opacity: 0.65 }}><X size={12} /></button>
                  </div>
                ) : (
                  <div
                    onClick={() => { letterheadRef.current?.click(); setSettingsOpen(false); }}
                    className="fm"
                    style={{
                      display: 'flex', alignItems: 'center', gap: 8, padding: '8px 12px',
                      cursor: 'pointer', borderRadius: 4, color: '#ffcc00',
                      fontSize: 11, fontWeight: 700, letterSpacing: '.08em', textTransform: 'uppercase'
                    }}
                    onMouseEnter={e => e.currentTarget.style.background = 'rgba(255,204,0,0.06)'}
                    onMouseLeave={e => e.currentTarget.style.background = 'transparent'}
                  >
                    <Upload size={13} /> Upload letterhead
                  </div>
                )}
              </div>
            )}
          </div>

          {/* EXPORT ▾ dropdown */}
          <div ref={exportRef} style={{ position: 'relative', flexShrink: 0 }}>
            <button
              className="btn btn-amber btn-sm"
              style={{ cursor: 'pointer' }}
              onClick={() => setExportOpen(o => !o)}
            >
              Export ▾
            </button>
            {exportOpen && (
              <div style={{
                position: 'absolute', top: 'calc(100% + 6px)', right: 0, zIndex: 200,
                background: 'var(--dropdown-bg)', border: '1px solid var(--border-strong)', borderRadius: 6,
                minWidth: 160, padding: 4,
                boxShadow: '0 8px 24px rgba(0,0,0,.7), 0 0 0 1px rgba(255,204,0,.06)'
              }}>
                <div
                  onClick={() => { exportCSV(); setExportOpen(false); }}
                  className="fm"
                  style={{
                    display: 'flex', alignItems: 'center', gap: 8, padding: '8px 12px',
                    cursor: 'pointer', borderRadius: 4, color: 'var(--ok)',
                    fontSize: 11, fontWeight: 700, letterSpacing: '.08em', textTransform: 'uppercase'
                  }}
                  onMouseEnter={e => e.currentTarget.style.background = 'rgba(0,255,0,0.06)'}
                  onMouseLeave={e => e.currentTarget.style.background = 'transparent'}
                >
                  <Download size={13} /> CSV
                </div>
                {!anyParsing && hasRows && (
                  <div
                    onClick={() => { exportPDF(); setExportOpen(false); }}
                    className="fm"
                    style={{
                      display: 'flex', alignItems: 'center', gap: 8, padding: '8px 12px',
                      cursor: 'pointer', borderRadius: 4, color: '#ffcc00',
                      fontSize: 11, fontWeight: 700, letterSpacing: '.08em', textTransform: 'uppercase'
                    }}
                    onMouseEnter={e => e.currentTarget.style.background = 'rgba(255,204,0,0.06)'}
                    onMouseLeave={e => e.currentTarget.style.background = 'transparent'}
                  >
                    <FileSpreadsheet size={13} /> PDF
                  </div>
                )}
              </div>
            )}
          </div>

          {/* Theme toggle */}
          <button onClick={toggleTheme} className="btn btn-ghost btn-sm" style={{ width: 36, padding: 0, flexShrink: 0, fontSize: 15 }} title={theme === 'dark' ? 'Miami Vice mode' : 'Knight Rider mode'}>
            {theme === 'dark' ? '🌴' : '🚨'}
          </button>
          {session && (
            <button onClick={() => db.auth.signOut()} className="btn btn-ghost btn-sm mob-hide" style={{ fontSize: 11, color: 'var(--text-3)', padding: '6px 10px', flexShrink: 0 }} title={`Signed in as ${session.user.email}`}>
              Sign out
            </button>
          )}
          {/* Primary actions */}
          {!anyParsing && hasRows && <>
            <button className="btn btn-blue btn-sm mob-hide"    style={{ flexShrink: 0 }} onClick={() => setShareModal('talent')}><Link2 size={14} /> Talent</button>
            <button className="btn btn-primary btn-sm mob-hide" style={{ flexShrink: 0 }} onClick={() => setShareModal('vendor')}><ArrowRight size={14} /> Vendor</button>
          </>}

          {/* Hidden inputs */}
          <input ref={inputRef} type="file" hidden multiple accept=".pdf,.xlsx,.xls,.csv" onChange={e => { addFiles(e.target.files); e.target.value = ''; }} />
          <input ref={letterheadRef} type="file" hidden accept="image/*" onChange={uploadLetterhead} />
        </div>
        {/* SUMMARY BAR */}
        {hasRows && (
          <div style={{ borderTop: '1px solid var(--border)', background: 'rgba(0,0,0,0.25)', padding: '7px 16px' }}>
            <div style={{ maxWidth: 1400, margin: '0 auto' }}>
              <div style={{ height: 3, borderRadius: 2, background: 'var(--surface-3)', overflow: 'hidden', marginBottom: 7 }}>
                <div style={{ display: 'flex', height: '100%' }}>
                  <div style={{ width: `${(confirmed/total)*100}%`, background: 'var(--ok)', transition: 'width .5s ease' }} />
                  <div style={{ width: `${(issues/total)*100}%`, background: 'var(--err)', transition: 'width .5s ease' }} />
                  <div style={{ width: `${(naCount/total)*100}%`, background: 'var(--surface-3)', transition: 'width .5s ease' }} />
                </div>
              </div>
              <div style={{ display: 'flex', alignItems: 'center', gap: 14, flexWrap: 'wrap' }}>
                <span className="fm" style={{ fontSize: 10, color: 'var(--text-3)', letterSpacing: '.06em' }}>{total} ITEMS</span>
                {confirmed > 0 && <span className="fm" style={{ fontSize: 10, color: 'var(--ok)', letterSpacing: '.06em' }}>✓ {confirmed} CONFIRMED</span>}
                {pending   > 0 && <span className="fm" style={{ fontSize: 10, color: 'var(--text-3)', letterSpacing: '.06em' }}>· {pending} PENDING</span>}
                {issues    > 0 && <span className="fm" style={{ fontSize: 10, color: 'var(--err)', letterSpacing: '.06em' }}>! {issues} ISSUES</span>}
                {naCount   > 0 && <span className="fm" style={{ fontSize: 10, color: 'var(--text-3)', letterSpacing: '.06em', opacity: 0.6 }}>— {naCount} N/A</span>}
                <span className="fm" style={{ marginLeft: 'auto', fontSize: 12, fontWeight: 700, color: pct === 100 ? 'var(--ok)' : 'var(--text-2)', letterSpacing: '.04em' }}>{pct}%</span>
              </div>
            </div>
          </div>
        )}
        {/* FILTER BAR */}
        {hasRows && (
          <div style={{ borderTop: '1px solid var(--border)', padding: '5px 16px' }}>
            <div style={{ maxWidth: 1400, margin: '0 auto', display: 'flex', gap: 5, alignItems: 'center', flexWrap: 'wrap' }}>
              {[
                { id: 'all',       label: 'All',       count: total,     cls: 'btn-secondary' },
                { id: 'pending',   label: 'Pending',   count: pending,   cls: 'btn-amber'     },
                { id: 'confirmed', label: 'Confirmed', count: confirmed, cls: 'btn-secondary' },
                { id: 'issue',     label: 'Issues',    count: issues,    cls: 'btn-primary'   },
                { id: 'na',        label: 'N/A',       count: naCount,   cls: 'btn-ghost'     },
              ].filter(f => f.id === 'all' || f.count > 0).map(f => (
                <button key={f.id} onClick={() => setFilterStatus(f.id)}
                  className={`btn btn-sm ${filterStatus === f.id ? f.cls : 'btn-ghost'}`}
                  style={{ padding: '2px 10px', minHeight: 26, fontSize: 10, gap: 5 }}>
                  {f.label}
                  {f.id !== 'all' && f.count > 0 && <span style={{ opacity: 0.65, fontWeight: 400 }}>{f.count}</span>}
                </button>
              ))}
              {allSections.length > 1 && <>
                <div style={{ height: 16, width: 1, background: 'var(--border)', margin: '0 3px', flexShrink: 0 }} />
                <select value={filterSection} onChange={e => setFilterSection(e.target.value)} className="fm"
                  style={{ background: 'var(--surface-2)', border: `1px solid ${filterSection !== 'all' ? 'var(--ok)' : 'var(--border-strong)'}`, color: filterSection !== 'all' ? 'var(--ok)' : 'var(--text-2)', borderRadius: 4, padding: '4px 8px', fontSize: 10, fontWeight: 700, letterSpacing: '.04em', cursor: 'pointer', outline: 'none', fontFamily: 'JetBrains Mono, monospace' }}>
                  <option value="all">All Sections</option>
                  {allSections.map(s => <option key={s} value={s}>{s}</option>)}
                </select>
              </>}
              {(filterStatus !== 'all' || filterSection !== 'all') && (
                <button onClick={() => { setFilterStatus('all'); setFilterSection('all'); }}
                  className="btn btn-ghost btn-sm" style={{ padding: '2px 8px', minHeight: 26, fontSize: 10 }}>
                  ✕ Clear
                </button>
              )}
            </div>
          </div>
        )}
        {/* Parse progress — only shown during upload/scan */}
        {fileStates.length > 0 && (
          <div style={{ borderTop: '1px solid var(--border)', padding: '8px 16px', display: 'flex', flexDirection: 'column', gap: 6 }}>
            <div style={{ maxWidth: 1400, margin: '0 auto', width: '100%', display: 'flex', flexDirection: 'column', gap: 6 }}>
              {fileStates.map(f => (
                <FileRow key={f.id} f={f} theme={theme} />
              ))}
            </div>
          </div>
        )}
      </div>

      <div style={{ maxWidth: 1400, margin: '0 auto', padding: '20px 16px 80px' }}>
        {/* THE GRID */}
        <Grid rows={filteredRows} onCellChange={updateCell} onDeleteRow={deleteRow} onAddRowToSection={addRowToSection} filterActive={filterStatus !== 'all' || filterSection !== 'all'} />
      </div>

      {/* SHARE MODALS */}
      {shareModal === 'talent' && (
        <ShareModal
          title="Share with Talent"
          subtitle="Talent gets a magic link to review and edit the rider directly in RIDER/FLOW"
          accessLabel="✏️  Can edit qty, add notes, and approve items"
          accessColor="var(--artist)"
          showId={id}
          viewPath="/talent"
          onClose={() => setShareModal(null)}
          primaryAction={{ label: 'Copy magic link', icon: <Link2 size={15} />, action: 'copy' }}
        />
      )}
      {shareModal === 'vendor' && (
        <ShareModal
          title="Share with Vendor"
          subtitle="Vendor sees their items with current status, or download a clean PDF"
          accessLabel="📋  Read-only — vendor items, status, and notes"
          accessColor="var(--vendor)"
          showId={id}
          show={show}
          viewPath="/vendor"
          onClose={() => setShareModal(null)}
          primaryAction={{ label: 'Copy magic link', icon: <Link2 size={15} />, action: 'copy' }}
          secondaryAction={{ label: 'Download PDF', icon: <Download size={15} />, action: 'pdf' }}
        />
      )}
    </div>
  );
}

// ─── TALENT VIEW ─────────────────────────────────────────────────────────────

function TalentRow({ row, rowIdx, onChange, isMobile }) {
  const cycleStatus = () => {
    const order = ['pending', 'confirmed', 'issue'];
    const cur = order.indexOf(row.status || 'pending');
    onChange(rowIdx, 'status', order[(cur + 1) % order.length]);
  };
  const st = statusOf(row.status);
  const borderColor = row.status === 'confirmed' ? 'rgba(0,255,0,0.2)' : row.status === 'issue' ? 'rgba(248,113,113,0.25)' : 'var(--border)';
  const qMinus = () => onChange(rowIdx, 'qty', String(Math.max(0, (parseInt(row.qty) || 1) - 1)));
  const qPlus  = () => onChange(rowIdx, 'qty', String((parseInt(row.qty) || 0) + 1));
  const cellLbl = txt => <div className="fm" style={{ fontSize: 9, color: 'var(--text-3)', letterSpacing: '.08em', textTransform: 'uppercase', marginBottom: 3 }}>{txt}</div>;
  const btnBase = { background: 'var(--surface-3)', border: '1px solid var(--border-strong)', color: '#60a5fa', borderRadius: 3, cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 14, fontFamily: 'var(--mono)', lineHeight: 1, flexShrink: 0 };

  if (isMobile) {
    return (
      <div style={{ background: 'var(--surface)', borderRadius: 8, border: `1px solid ${borderColor}`, padding: '14px', transition: 'border-color .2s' }}>
        {/* Item + Qty */}
        <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 12, marginBottom: 12 }}>
          <div className="fm" style={{ fontSize: 15, fontWeight: 700, color: '#e2e2e8', flex: 1, lineHeight: 1.3 }}>{row.item || '—'}</div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 6, flexShrink: 0 }}>
            <button onClick={qMinus} style={{ ...btnBase, width: 32, height: 32 }}>−</button>
            <span className="fm" style={{ width: 32, textAlign: 'center', fontSize: 16, fontWeight: 700, color: '#aaddff' }}>{row.qty || '—'}</span>
            <button onClick={qPlus}  style={{ ...btnBase, width: 32, height: 32 }}>+</button>
          </div>
        </div>
        {/* Status — full width tap target */}
        <button onClick={cycleStatus} className="fm" style={{
          display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 10,
          width: '100%', padding: '11px 0', borderRadius: 6, cursor: 'pointer', border: 'none',
          background: st.bg, color: st.color, fontSize: 13, fontWeight: 700,
          letterSpacing: '.06em', textTransform: 'uppercase', marginBottom: 12
        }}>
          {row.status === 'confirmed' ? '✓' : row.status === 'issue' ? '!' : '·'} {st.label}
          <span style={{ fontSize: 9, opacity: 0.5, fontWeight: 400 }}>tap to cycle</span>
        </button>
        {/* Spec */}
        {row.spec && <div style={{ marginBottom: 10 }}>
          {cellLbl('Rider Spec')}
          <div className="fm" style={{ fontSize: 13, color: '#8a8aa0', lineHeight: 1.4 }}>{row.spec}</div>
        </div>}
        {/* Actual */}
        <div style={{ marginBottom: 10 }}>
          {cellLbl('Actual Provision')}
          <div className="fm" style={{ fontSize: 13, color: row.actual ? '#c0c0d0' : '#3a3a50', fontStyle: row.actual ? 'normal' : 'italic' }}>{row.actual || 'Not yet filled'}</div>
        </div>
        {/* Notes */}
        <div>
          {cellLbl('Notes')}
          <input value={row.notes || ''} onChange={e => onChange(rowIdx, 'notes', e.target.value)} placeholder="Add note…" className="fm"
            style={{ width: '100%', background: 'var(--surface-2)', border: '1px solid var(--border-strong)', borderRadius: 4, color: '#e2e2e8', fontSize: 14, padding: '9px 10px', outline: 'none', boxSizing: 'border-box' }} />
        </div>
      </div>
    );
  }

  // Desktop layout (existing 6-col grid)
  return (
    <div style={{ display: 'grid', gridTemplateColumns: '1.4fr 90px 1.6fr 1.6fr 120px 1.4fr', gap: 10, background: 'var(--surface)', borderRadius: 6, padding: '12px 14px', border: `1px solid ${borderColor}`, transition: 'border-color .2s' }}>
      <div>
        {cellLbl('Item')}
        <div className="fm" style={{ fontSize: 13, fontWeight: 600, color: '#e2e2e8' }}>{row.item || '—'}</div>
      </div>
      <div>
        {cellLbl('Qty')}
        <div style={{ display: 'flex', alignItems: 'center', gap: 5 }}>
          <button onClick={qMinus} style={{ ...btnBase, width: 22, height: 22 }}>−</button>
          <span className="fm" style={{ width: 28, textAlign: 'center', fontSize: 13, fontWeight: 700, color: '#aaddff' }}>{row.qty || '—'}</span>
          <button onClick={qPlus}  style={{ ...btnBase, width: 22, height: 22 }}>+</button>
        </div>
      </div>
      <div>
        {cellLbl('Rider Spec')}
        <div className="fm" style={{ fontSize: 12, color: '#8a8aa0' }}>{row.spec || '—'}</div>
      </div>
      <div>
        {cellLbl('Actual Provision')}
        <div className="fm" style={{ fontSize: 12, color: row.actual ? '#c0c0d0' : '#3a3a50', fontStyle: row.actual ? 'normal' : 'italic' }}>{row.actual || 'Not yet filled'}</div>
      </div>
      <div>
        {cellLbl('Status')}
        <button onClick={cycleStatus} className="fm" style={{
          display: 'inline-flex', alignItems: 'center', gap: 5,
          padding: '3px 10px', borderRadius: 4, cursor: 'pointer', border: 'none',
          background: st.bg, color: st.color,
          fontSize: 10, fontWeight: 700, letterSpacing: '.08em', textTransform: 'uppercase', transition: 'all .15s'
        }}>
          {row.status === 'confirmed' ? '✓' : row.status === 'issue' ? '!' : '·'} {st.label}
        </button>
      </div>
      <div>
        {cellLbl('Notes')}
        <input value={row.notes || ''} onChange={e => onChange(rowIdx, 'notes', e.target.value)} placeholder="Add note…" className="fm"
          style={{ width: '100%', background: 'transparent', border: 'none', borderBottom: '1px solid var(--border-strong)', color: '#e2e2e8', fontSize: 12, padding: '2px 0', outline: 'none', boxSizing: 'border-box' }} />
      </div>
    </div>
  );
}

function TalentView({ id }) {
  const [show, setShow] = useState(null);
  const [loading, setLoading] = useState(true);
  const [saving, setSaving] = useState(false);
  const saveTimer = useRef(null);
  const isMobile = useIsMobile();

  useEffect(() => {
    const raw = localStorage.getItem(`fr_show_${id}`);
    if (raw) setShow(JSON.parse(raw));
    setLoading(false);
  }, [id]);

  const save = updated => {
    setSaving(true);
    clearTimeout(saveTimer.current);
    saveTimer.current = setTimeout(() => {
      localStorage.setItem(`fr_show_${id}`, JSON.stringify(updated));
      setSaving(false);
    }, 600);
  };

  const updateCell = (rowIdx, key, val) => {
    setShow(prev => {
      const rows = prev.rows.map((r, i) => i === rowIdx ? { ...r, [key]: val } : r);
      const updated = { ...prev, rows };
      save(updated);
      return updated;
    });
  };

  if (loading) return <div style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center' }}><Loader2 size={24} style={{ animation: 'spin 1s linear infinite', color: '#60a5fa' }} /></div>;
  if (!show)   return <div style={{ padding: 40, color: '#fff' }}>Show not found.</div>;

  const sections  = [...new Set((show.rows || []).map(r => r.section || 'Other'))];
  const confirmed = (show.rows || []).filter(r => r.status === 'confirmed').length;
  const issues    = (show.rows || []).filter(r => r.status === 'issue').length;
  const pending   = (show.rows || []).filter(r => !r.status || r.status === 'pending').length;

  return (
    <div className="fade-in" style={{ minHeight: '100vh' }}>
      {/* TOPBAR */}
      <div style={{ position: 'sticky', top: 0, zIndex: 50, background: 'rgba(10,10,11,.95)', backdropFilter: 'blur(12px)', borderBottom: '1px solid var(--border)' }}>
        <div style={{ maxWidth: 1200, margin: '0 auto', padding: '10px 16px', display: 'flex', alignItems: 'center', gap: 8 }}>
          <button className="btn btn-blue btn-sm" style={{ width: 36, padding: 0, flexShrink: 0 }} onClick={() => nav('/')}>
            <ChevronLeft size={16} />
          </button>
          <div style={{ flex: 1, minWidth: 0, margin: '0 4px' }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
              <div className="fd" style={{ fontSize: 16, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{show.name}</div>
              <span className="fm" style={{ fontSize: 9, fontWeight: 700, letterSpacing: '.12em', color: '#5599cc', background: '#07141f', border: '1px solid #0d2035', borderRadius: 4, padding: '1px 6px', flexShrink: 0 }}>TALENT</span>
            </div>
            <div className="fm" style={{ fontSize: 10, color: 'var(--text-3)', marginTop: 1, display: 'flex', alignItems: 'center', gap: 8 }}>
              <span className={`sync-dot${saving ? ' saving' : ''}`} />
              {saving ? 'SAVING…' : 'SYNCED'}
              {confirmed > 0 && <><span>·</span><span style={{ color: 'var(--ok)' }}>{confirmed} confirmed</span></>}
              {issues    > 0 && <><span>·</span><span style={{ color: 'var(--err)' }}>{issues} issues</span></>}
              {pending   > 0 && <><span>·</span><span style={{ color: 'var(--text-3)' }}>{pending} pending</span></>}
            </div>
          </div>
        </div>
      </div>

      <div style={{ maxWidth: 1200, margin: '0 auto', padding: '20px 16px 80px' }}>
        {/* Instruction banner */}
        <div style={{ background: 'rgba(85,153,204,0.08)', border: '1px solid rgba(85,153,204,0.22)', borderRadius: 8, padding: '12px 16px', marginBottom: 28, display: 'flex', alignItems: 'center', gap: 12 }}>
          <span style={{ fontSize: 18 }}>ℹ️</span>
          <div className="fm" style={{ fontSize: 12, color: '#7ab8e8', lineHeight: 1.5 }}>
            Review each item. Adjust quantities if needed, add notes, and tap <strong>Status</strong> to cycle: <strong style={{ color: '#9ca3af' }}>Pending</strong> → <strong style={{ color: '#00ff00' }}>Confirmed</strong> → <strong style={{ color: '#f87171' }}>Issue</strong>.
          </div>
        </div>

        {sections.map(sec => {
          const rows = (show.rows || []).map((r, i) => ({ ...r, _idx: i })).filter(r => (r.section || 'Other') === sec);
          return (
            <div key={sec} style={{ marginBottom: 32 }}>
              <div className="fm" style={{ fontSize: 11, fontWeight: 700, letterSpacing: '.14em', textTransform: 'uppercase', color: '#60a5fa', padding: '6px 0', borderBottom: '1px solid rgba(85,153,204,0.2)', marginBottom: 8 }}>{sec}</div>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
                {rows.map(row => <TalentRow key={row._idx} row={row} rowIdx={row._idx} onChange={updateCell} isMobile={isMobile} />)}
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

// ─── VENDOR VIEW ─────────────────────────────────────────────────────────────

function VendorView({ id }) {
  const [show, setShow] = useState(null);
  const [letterhead, setLetterhead] = useState(null);
  const [loading, setLoading] = useState(true);
  const [showAll, setShowAll] = useState(false);
  const isMobile = useIsMobile();

  useEffect(() => {
    const raw = localStorage.getItem(`fr_show_${id}`);
    const lh  = localStorage.getItem(`fr_lh_${id}`);
    if (raw) setShow(JSON.parse(raw));
    if (lh)  setLetterhead(lh);
    setLoading(false);
  }, [id]);

  const exportVendorPDF = () => {
    const generate = () => {
      const rows = (show?.rows || []).filter(r => showAll || r.owner === 'vendor' || !r.owner);
      const bySection = rows.reduce((acc, r) => {
        const s = r.section || 'Other';
        if (!acc[s]) acc[s] = [];
        acc[s].push(r);
        return acc;
      }, {});

      const el = document.createElement('div');
      el.innerHTML = `<!DOCTYPE html><html><head><style>
        * { box-sizing: border-box; }
        body { font-family: -apple-system, Arial, sans-serif; margin: 0; padding: 40px; color: #111; }
        .lh-hdr { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 28px; padding-bottom: 16px; border-bottom: 2px solid #ddd; }
        .show-name { font-size: 22px; font-weight: 800; margin-bottom: 4px; }
        .show-sub { font-size: 11px; color: #888; }
        .section { margin-bottom: 22px; page-break-inside: avoid; }
        .sec-title { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .1em; color: #333; border-bottom: 1px solid #e0e0e0; padding-bottom: 5px; margin-bottom: 8px; }
        table { width: 100%; border-collapse: collapse; font-size: 12px; }
        th { text-align: left; font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: .07em; color: #888; padding: 0 8px 6px 0; border-bottom: 1px solid #e8e8e8; }
        td { padding: 7px 8px 7px 0; border-bottom: 1px solid #f0f0f0; vertical-align: top; }
        tr:last-child td { border-bottom: none; }
        .badge { display: inline-block; padding: 2px 7px; border-radius: 3px; font-size: 9px; font-weight: 700; text-transform: uppercase; }
        .badge-confirmed { background: #e6f9e6; color: #007a00; }
        .badge-pending   { background: #f0f0f0; color: #666; }
        .badge-issue     { background: #fde8e8; color: #c00; }
        .badge-na        { background: #f5f5f5; color: #999; }
      </style></head><body>
        <div class="lh-hdr">
          <div>
            ${letterhead ? `<img src="${letterhead}" style="height:48px;object-fit:contain;margin-bottom:8px;display:block;" />` : ''}
            <div class="show-name">${show?.name || 'Production Rider'}</div>
            <div class="show-sub">Production Advance · Generated ${new Date().toLocaleDateString('en-US', { year:'numeric', month:'long', day:'numeric' })}</div>
          </div>
          <div style="text-align:right;font-size:11px;color:#888;">
            <div style="font-size:28px;font-weight:900;color:#111;line-height:1">${rows.length}</div>
            items
          </div>
        </div>
        ${Object.entries(bySection).map(([sec, srows]) => `
          <div class="section">
            <div class="sec-title">${sec}</div>
            <table>
              <tr><th style="width:180px">Item</th><th style="width:36px;text-align:center">Qty</th><th>Rider Spec</th><th>Actual Provision</th><th style="width:80px">Status</th><th>Notes</th></tr>
              ${srows.map(r => `<tr>
                <td style="font-weight:600">${r.item || '—'}</td>
                <td style="text-align:center">${r.qty || '—'}</td>
                <td style="color:#555">${r.spec || '—'}</td>
                <td>${r.actual || '—'}</td>
                <td><span class="badge badge-${r.status || 'pending'}">${r.status || 'pending'}</span></td>
                <td style="color:#777">${r.notes || '—'}</td>
              </tr>`).join('')}
            </table>
          </div>`).join('')}
      </body></html>`;

      const filename = `${(show?.name || 'rider').replace(/[^a-z0-9]+/gi, '-')}-vendor.pdf`;
      const opt = { margin: [14, 10, 14, 10], filename, image: { type: 'jpeg', quality: .98 }, html2canvas: { scale: 2 }, jsPDF: { orientation: 'portrait', unit: 'mm', format: 'a4' } };
      window.html2pdf().set(opt).from(el).toPdf().get('pdf').then(pdf => {
        const total = pdf.internal.getNumberOfPages();
        const pw = pdf.internal.pageSize.getWidth();
        const ph = pdf.internal.pageSize.getHeight();
        for (let i = 1; i <= total; i++) {
          pdf.setPage(i);
          pdf.setFontSize(8); pdf.setTextColor(80, 80, 80);
          pdf.text(show?.name || '', 10, 9);
          pdf.setTextColor(170, 170, 170);
          pdf.text(`${i} of ${total}`, pw / 2, ph - 5, { align: 'center' });
        }
      }).save();
    };
    if (!window.html2pdf) {
      const s = document.createElement('script');
      s.src = 'https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js';
      s.onload = generate;
      document.head.appendChild(s);
    } else { generate(); }
  };

  if (loading) return <div style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center' }}><Loader2 size={24} style={{ animation: 'spin 1s linear infinite', color: '#ffcc00' }} /></div>;
  if (!show)   return <div style={{ padding: 40, color: '#fff' }}>Show not found.</div>;

  const allRows     = show.rows || [];
  const vendorRows  = allRows.filter(r => r.owner === 'vendor' || !r.owner);
  const displayRows = showAll ? allRows : vendorRows;
  const sections    = [...new Set(displayRows.map(r => r.section || 'Other'))];
  const confirmed   = displayRows.filter(r => r.status === 'confirmed').length;
  const issues      = displayRows.filter(r => r.status === 'issue').length;

  return (
    <div className="fade-in" style={{ minHeight: '100vh' }}>
      {/* TOPBAR */}
      <div style={{ position: 'sticky', top: 0, zIndex: 50, background: 'rgba(10,10,11,.95)', backdropFilter: 'blur(12px)', borderBottom: '1px solid var(--border)' }}>
        <div style={{ maxWidth: 1200, margin: '0 auto', padding: '10px 16px', display: 'flex', alignItems: 'center', gap: 8 }}>
          <button className="btn btn-amber btn-sm" style={{ width: 36, padding: 0, flexShrink: 0 }} onClick={() => nav('/')}>
            <ChevronLeft size={16} />
          </button>
          <div style={{ flex: 1, minWidth: 0, margin: '0 4px' }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
              {letterhead && <img src={letterhead} style={{ height: 20, maxWidth: 60, objectFit: 'contain', borderRadius: 2, opacity: 0.9 }} />}
              <div className="fd" style={{ fontSize: 16, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{show.name}</div>
              <span className="fm" style={{ fontSize: 9, fontWeight: 700, letterSpacing: '.12em', color: '#cc9922', background: '#1e1500', border: '1px solid #3a2800', borderRadius: 4, padding: '1px 6px', flexShrink: 0 }}>VENDOR</span>
            </div>
            <div className="fm" style={{ fontSize: 10, color: 'var(--text-3)', marginTop: 1, display: 'flex', alignItems: 'center', gap: 8 }}>
              <span>{displayRows.length} items</span>
              {confirmed > 0 && <><span>·</span><span style={{ color: 'var(--ok)' }}>{confirmed} confirmed</span></>}
              {issues    > 0 && <><span>·</span><span style={{ color: 'var(--err)' }}>{issues} issues</span></>}
            </div>
          </div>
          {/* Toggle all/vendor */}
          <button className="btn btn-secondary btn-sm" style={{ flexShrink: 0 }} onClick={() => setShowAll(a => !a)}>
            {showAll ? 'Vendor Only' : 'All Items'}
          </button>
          {/* Download PDF */}
          <button className="btn btn-amber btn-sm" style={{ flexShrink: 0 }} onClick={exportVendorPDF}>
            <Download size={13} /> PDF
          </button>
        </div>
      </div>

      <div style={{ maxWidth: 1200, margin: '0 auto', padding: '20px 16px 80px' }}>
        {!showAll && vendorRows.length < allRows.length && (
          <div style={{ background: 'rgba(204,153,0,0.07)', border: '1px solid rgba(204,153,0,0.2)', borderRadius: 8, padding: '10px 16px', marginBottom: 24, display: 'flex', alignItems: 'center', gap: 10 }}>
            <span style={{ fontSize: 16 }}>📋</span>
            <div className="fm" style={{ fontSize: 12, color: '#c8a800' }}>
              Showing <strong>{vendorRows.length} vendor items</strong> — {allRows.length - vendorRows.length} artist/venue items hidden. <button onClick={() => setShowAll(true)} style={{ background: 'none', border: 'none', color: '#ffcc00', cursor: 'pointer', textDecoration: 'underline', fontSize: 12, padding: 0, fontFamily: 'var(--mono)' }}>Show all</button>
            </div>
          </div>
        )}

        {sections.map(sec => {
          const rows = displayRows.map((r, i) => ({ ...r, _idx: i })).filter(r => (r.section || 'Other') === sec);
          return (
            <div key={sec} style={{ marginBottom: 32 }}>
              <div className="fm" style={{ fontSize: 11, fontWeight: 700, letterSpacing: '.14em', textTransform: 'uppercase', color: '#cc9900', padding: '6px 0', borderBottom: '1px solid rgba(204,153,0,0.2)', marginBottom: 8 }}>{sec}</div>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
                {rows.map(row => <VendorRow key={row._idx} row={row} isMobile={isMobile} />)}
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

function VendorRow({ row, isMobile }) {
  const st = statusOf(row.status);
  const borderColor = row.status === 'confirmed' ? 'rgba(0,255,0,0.15)' : row.status === 'issue' ? 'rgba(248,113,113,0.2)' : 'var(--border)';
  const cellLbl = txt => <div className="fm" style={{ fontSize: 9, color: 'var(--text-3)', letterSpacing: '.08em', textTransform: 'uppercase', marginBottom: 3 }}>{txt}</div>;
  const statusBadge = (
    <span className="fm" style={{ display: 'inline-flex', alignItems: 'center', gap: 4, padding: '3px 9px', borderRadius: 4, background: st.bg, color: st.color, fontSize: 10, fontWeight: 700, letterSpacing: '.08em', textTransform: 'uppercase' }}>
      {row.status === 'confirmed' ? '✓' : row.status === 'issue' ? '!' : '·'} {st.label}
    </span>
  );

  if (isMobile) {
    return (
      <div style={{ background: 'var(--surface)', borderRadius: 8, border: `1px solid ${borderColor}`, padding: '14px' }}>
        {/* Item + Qty */}
        <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 12, marginBottom: 10 }}>
          <div>
            <div className="fm" style={{ fontSize: 15, fontWeight: 700, color: '#e2e2e8', marginBottom: 6, lineHeight: 1.3 }}>{row.item || '—'}</div>
            {statusBadge}
          </div>
          <div className="fm" style={{ fontSize: 28, fontWeight: 900, color: '#ffd366', flexShrink: 0, lineHeight: 1 }}>{row.qty || '—'}</div>
        </div>
        {row.spec && <div style={{ marginBottom: 8 }}>
          {cellLbl('Rider Spec')}
          <div className="fm" style={{ fontSize: 13, color: '#8a8aa0', lineHeight: 1.4 }}>{row.spec}</div>
        </div>}
        <div style={{ marginBottom: row.notes ? 8 : 0 }}>
          {cellLbl('Actual Provision')}
          <div className="fm" style={{ fontSize: 13, color: row.actual ? '#c0c0d0' : '#3a3a50', fontStyle: row.actual ? 'normal' : 'italic' }}>{row.actual || 'Not filled'}</div>
        </div>
        {row.notes && <div>
          {cellLbl('Notes')}
          <div className="fm" style={{ fontSize: 13, color: '#c0c0d0' }}>{row.notes}</div>
        </div>}
      </div>
    );
  }

  // Desktop layout
  return (
    <div style={{ display: 'grid', gridTemplateColumns: '1.4fr 70px 1.6fr 1.6fr 110px 1.4fr', gap: 10, background: 'var(--surface)', borderRadius: 6, padding: '11px 14px', border: `1px solid ${borderColor}` }}>
      <div>
        {cellLbl('Item')}
        <div className="fm" style={{ fontSize: 13, fontWeight: 600, color: '#e2e2e8' }}>{row.item || '—'}</div>
      </div>
      <div>
        {cellLbl('Qty')}
        <div className="fm" style={{ fontSize: 13, fontWeight: 700, color: '#ffd366' }}>{row.qty || '—'}</div>
      </div>
      <div>
        {cellLbl('Rider Spec')}
        <div className="fm" style={{ fontSize: 12, color: '#8a8aa0' }}>{row.spec || '—'}</div>
      </div>
      <div>
        {cellLbl('Actual Provision')}
        <div className="fm" style={{ fontSize: 12, color: row.actual ? '#c0c0d0' : '#3a3a50', fontStyle: row.actual ? 'normal' : 'italic' }}>{row.actual || 'Not filled'}</div>
      </div>
      <div>
        {cellLbl('Status')}
        {statusBadge}
      </div>
      <div>
        {cellLbl('Notes')}
        <div className="fm" style={{ fontSize: 12, color: row.notes ? '#c0c0d0' : '#3a3a50', fontStyle: row.notes ? 'normal' : 'italic' }}>{row.notes || '—'}</div>
      </div>
    </div>
  );
}

// ─── SHARE MODAL ─────────────────────────────────────────────────────────────

function ShareModal({ title, subtitle, accessLabel, accessColor, showId, show, onClose, primaryAction, secondaryAction, viewPath }) {
  const [copied,   setCopied]   = useState(false);
  const [pdfReady, setPdfReady] = useState(false);

  const url = `${window.location.origin}${window.location.pathname}#/show/${showId}${viewPath || ''}`;

  const copyLink = async () => {
    try { await navigator.clipboard.writeText(url); } catch { prompt('Copy this link:', url); }
    setCopied(true);
    setTimeout(() => setCopied(false), 2000);
  };

  const downloadPDF = () => {
    const generate = () => {
      const bySection = (show?.rows || []).reduce((acc, r) => {
        const s = r.section || 'Other';
        if (!acc[s]) acc[s] = [];
        acc[s].push(r);
        return acc;
      }, {});

      const el = document.createElement('div');
      el.innerHTML = `<!DOCTYPE html><html><head><style>
        body{font-family:-apple-system,sans-serif;margin:0;padding:40px;color:#111;}
        .hdr{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:32px;padding-bottom:20px;border-bottom:3px solid #ff1a1a;}
        .brand{font-family:Arial,sans-serif;font-weight:900;font-size:28px;letter-spacing:-.02em;}
        .brand span{color:#ff1a1a;}
        .section{margin-bottom:24px;page-break-inside:avoid;}
        .sec-title{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.1em;color:#ff1a1a;border-bottom:1px solid #eee;padding-bottom:6px;margin-bottom:10px;}
        .row{display:grid;grid-template-columns:1fr 2fr 2fr 100px;gap:10px;padding:8px 10px;margin-bottom:3px;border-radius:4px;font-size:12px;border-left:3px solid #ddd;}
        .row.confirmed{border-left-color:#00cc00;background:#f0fff0;}
        .row.issue{border-left-color:#ee4444;background:#fff0f0;}
        .lbl{font-size:10px;font-weight:700;color:#888;margin-bottom:2px;}
        .badge{display:inline-block;padding:2px 6px;border-radius:3px;font-size:9px;font-weight:700;text-transform:uppercase;color:white;}
        .badge.confirmed{background:#00cc00;} .badge.pending{background:#999;} .badge.issue{background:#ee4444;}
        .footer{margin-top:40px;padding-top:12px;border-top:1px solid #eee;font-size:10px;color:#aaa;display:flex;justify-content:space-between;}
      </style></head><body>
        <div class="hdr">
          <div>
            <div class="brand">FLOW<span>/</span>RIDER</div>
            <div style="font-size:11px;color:#888;margin-top:2px;">Jay Siegan Presents · Production Advance</div>
            <div style="font-size:18px;font-weight:700;margin-top:6px;">${show?.name || ''}</div>
            <div style="font-size:11px;color:#666;margin-top:3px;">Generated ${new Date().toLocaleDateString('en-US',{weekday:'long',year:'numeric',month:'long',day:'numeric'})}</div>
          </div>
          <div style="text-align:right;">
            <div style="font-size:10px;color:#888;">TOTAL ITEMS</div>
            <div style="font-size:36px;font-weight:900;">${show?.rows?.length || 0}</div>
            <div style="font-size:11px;color:#00aa00;">${(show?.rows||[]).filter(r=>r.status==='confirmed').length} confirmed</div>
          </div>
        </div>
        ${Object.entries(bySection).map(([sec, rows]) => `
          <div class="section">
            <div class="sec-title">${sec} (${rows.length})</div>
            ${rows.map(r => `
              <div class="row ${r.status||'pending'}">
                <div><div class="lbl">Item</div>${r.item||'—'}</div>
                <div><div class="lbl">Rider Spec</div>${r.spec||'—'}</div>
                <div><div class="lbl">Actual Provision</div>${r.actual||'—'}</div>
                <div><div class="lbl">Status</div><span class="badge ${r.status||'pending'}">${r.status||'pending'}</span></div>
              </div>`).join('')}
          </div>`).join('')}
        <div class="footer"><span>RIDER/FLOW · Jay Siegan Presents</span><span>${show?.name||''} · Confidential</span></div>
      </body></html>`;

      const opt = { margin: 10, filename: `JSP-${(show?.name||'rider').replace(/[^a-z0-9]+/gi,'-')}.pdf`, image: { type: 'jpeg', quality: .98 }, html2canvas: { scale: 2 }, jsPDF: { orientation: 'portrait', unit: 'mm', format: 'a4' } };
      window.html2pdf().set(opt).from(el).save();
      setPdfReady(true);
    };

    if (!window.html2pdf) {
      const s = document.createElement('script');
      s.src = 'https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js';
      s.onload = generate;
      document.head.appendChild(s);
    } else { generate(); }
  };

  const handle = action => {
    if (action === 'copy') copyLink();
    if (action === 'pdf')  downloadPDF();
  };

  return (
    <div className="modal-bg" onClick={e => { if (e.target === e.currentTarget) onClose(); }}>
      <div className="modal-panel safe-b" onClick={e => e.stopPropagation()}>
        <div style={{ padding: '20px 20px 0', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
          <div>
            <div className="fd" style={{ fontSize: 22 }}>{title}</div>
            <div style={{ fontSize: 13, color: 'var(--text-2)', marginTop: 4 }}>{subtitle}</div>
          </div>
          <button className="btn btn-ghost" style={{ width: 40, height: 40, padding: 0, minHeight: 40, flexShrink: 0 }} onClick={onClose}><X size={18} /></button>
        </div>
        <div style={{ padding: 20, display: 'grid', gap: 12 }}>
          {/* URL preview */}
          <div style={{ display: 'flex', background: 'var(--bg-1)', border: '1px solid var(--border)', borderRadius: 10, overflow: 'hidden' }}>
            <div className="fm" style={{ flex: 1, padding: '10px 14px', fontSize: 11, color: 'var(--text-2)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{url}</div>
            <button className="btn btn-secondary btn-sm" style={{ borderRadius: 0, borderLeft: '1px solid var(--border)', minHeight: 44, border: 'none', borderLeft: '1px solid var(--border)' }} onClick={copyLink}>
              {copied ? <><Check size={13} /> Copied!</> : <><Copy size={13} /> Copy</>}
            </button>
          </div>
          {/* Access level */}
          <div style={{ padding: '10px 14px', background: 'var(--surface-2)', border: '1px solid var(--border)', borderRadius: 10 }}>
            <div className="fm" style={{ fontSize: 10, color: 'var(--text-3)', marginBottom: 4, letterSpacing: '.06em', textTransform: 'uppercase' }}>Access level</div>
            <div style={{ fontWeight: 600, color: accessColor }}>{accessLabel}</div>
          </div>
          {/* Actions */}
          {viewPath && (
            <button className="btn btn-blue" style={{ width: '100%' }} onClick={() => { onClose(); nav(`/show/${showId}${viewPath}`); }}>
              <ArrowRight size={15} /> Preview {viewPath === '/talent' ? 'Talent' : 'Vendor'} View
            </button>
          )}
          <button className="btn btn-primary" style={{ width: '100%' }} onClick={() => handle(primaryAction.action)}>
            {primaryAction.icon} {primaryAction.action === 'copy' && copied ? 'Copied!' : pdfReady && primaryAction.action === 'pdf' ? 'Downloaded ✓' : primaryAction.label}
          </button>
          {secondaryAction && (
            <button className="btn btn-secondary" style={{ width: '100%' }} onClick={() => handle(secondaryAction.action)}>
              {secondaryAction.icon} {secondaryAction.action === 'copy' && copied ? 'Copied!' : secondaryAction.label}
            </button>
          )}
        </div>
      </div>
    </div>
  );
}

// ─── ROOT ─────────────────────────────────────────────────────────────────────

function App() {
  const route = useRoute();
  // undefined = still resolving, null = no session, object = authenticated
  const [session, setSession] = useState(undefined);

  useEffect(() => {
    // Get initial session
    db.auth.getSession().then(({ data }) => setSession(data.session || null));
    // Listen for auth changes (login, logout, token refresh)
    const { data: { subscription } } = db.auth.onAuthStateChange((_event, sess) => setSession(sess || null));
    return () => subscription.unsubscribe();
  }, []);

  // Talent / vendor views are always public
  const isPublic = route.name === 'talent' || route.name === 'vendor';

  // Show loading spinner while session resolves
  if (session === undefined) {
    return (
      <div className="root">
        <style>{CSS}</style>
        <div style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
          <Loader2 size={24} style={{ animation: 'spin 1s linear infinite', color: 'var(--accent)' }} />
        </div>
      </div>
    );
  }

  // Admin routes require auth
  const needsAuth = !isPublic && !session;

  return (
    <div className="root">
      <style>{CSS}</style>
      {needsAuth
        ? <AuthScreen />
        : <>
            {route.name === 'landing' && <Landing session={session} />}
            {route.name === 'show'    && <ShowView id={route.id} session={session} />}
            {route.name === 'talent'  && <TalentView id={route.id} />}
            {route.name === 'vendor'  && <VendorView id={route.id} />}
          </>
      }
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(React.createElement(App));
