// portfolio-admin.jsx
// Hidden admin mode for the live site.
//
// Activation:
//   • ?admin=1 query param  (or  #admin  hash)  → shows the AdminPanel drawer.
//   • Otherwise the site reads localStorage silently and renders the visitor view.
//
// Persistence:
//   • All edits are saved to localStorage under ARMATURE_STORE_KEY.
//   • The "Export JSON" button downloads the full store as a .json file so
//     edits can be moved between devices (since localStorage is per-browser-
//     per-device).
//   • "Import JSON" restores from that file.
//
// Same setter is shared with the design-tool TweaksPanel; when running inside
// the design tool's iframe, edits are also relayed to the parent via
// `__edit_mode_set_keys` so the EDITMODE-BEGIN block in the HTML is written
// back to disk on the host side.
//
// Exposed on window: useArmatureStore, AdminPanel, isAdminMode, ARMATURE_STORE_KEY.

const ARMATURE_STORE_KEY = 'armature.store.v1';

const ARMATURE_DEFAULTS = {
  accent: '#a8202c',
  density: 'regular',
  showreelId: '76979871',
  showreelSource: 'vimeo',
  workFilters: ['all', 'cinesteel', 'fullmetal', 'brand film', 'documentary'],
  siteLogo: '',     // data URL (or http URL) — replaces the "ARMATURE" wordmark in nav
  userProjects: [],
  creators: null,     // null → fall back to CREATORS seed
  brands: null,       // null → fall back to BRANDS seed
  managed: null,      // null → fall back to MANAGED_SEED
  subBrandClients: null, // null → fall back to SUB_BRAND_CLIENTS_SEED
  team: null,         // null → fall back to TEAM seed (with portrait/email overrides)
  links: { ...NAV_LINKS },
  text: {},           // { 'hero.lead': 'Films forged in ', ... } — inline copy overrides
  textStyles: {},     // { 'hero.lead': { font, color, fontSize }, ... } — per-id style overrides
  projectOverrides: {}, // { 'p01': { brand: 'Music Video', tag: '...' }, ... } — per-project field overrides (covers seed + user-added)
  subBrands: null,    // null → fall back to SUB_BRANDS seed
  services: null,     // null → fall back to SERVICES seed
  locations: null,    // null → fall back to LOCATION_SEED; array of { name, address }
  customFonts: {},    // { 'Display Name': dataURL } — uploaded fonts registered via @font-face
  adminPassword: 'admin123',
  sitePasswordEnabled: false,
  sitePassword: '',
};

// Studios shown on the contact page + footer. Two seeds matching the
// original hardcoded copy; admin can rewrite/add/remove freely.
const LOCATION_SEED = [
  { name: 'Lisbon',   address: 'Rua da Boavista 84\n1200-066\nrichard@armature.studio' },
  { name: 'New York', address: '79 Walker St, 4F\nNY 10013\nbookings@armature.studio' },
];

function isAdminMode() {
  if (typeof window === 'undefined') return false;
  try {
    const u = new URL(window.location.href);
    if (u.searchParams.get('admin') === '1') return true;
    if (u.hash === '#admin' || u.hash === '#/admin') return true;
  } catch {}
  return false;
}

function loadStore() {
  try {
    const raw = localStorage.getItem(ARMATURE_STORE_KEY);
    if (!raw) return cloneDefaults();
    const parsed = JSON.parse(raw);
    return {
      ...cloneDefaults(),
      ...parsed,
      links:            { ...NAV_LINKS, ...(parsed.links || {}) },
      text:             { ...(parsed.text || {}) },
      textStyles:       { ...(parsed.textStyles || {}) },
      projectOverrides: { ...(parsed.projectOverrides || {}) },
    };
  } catch {
    return cloneDefaults();
  }
}

function cloneDefaults() {
  return { ...ARMATURE_DEFAULTS, links: { ...NAV_LINKS }, userProjects: [], text: {}, textStyles: {}, projectOverrides: {} };
}

function persistStore(state) {
  try { localStorage.setItem(ARMATURE_STORE_KEY, JSON.stringify(state)); } catch {}
}

// Tracks the most recently fetched /api/store payload so DataTab can show
// "in sync" vs. "draft has unpublished changes" by comparing to the current
// in-memory store. null = haven't fetched yet (or fetch failed — local dev).
const PUBLISHED_REF = { current: null };

async function fetchPublishedStore() {
  try {
    const r = await fetch('/api/store', { cache: 'no-store' });
    if (!r.ok) return null;
    const j = await r.json();
    return (j && typeof j === 'object' && Object.keys(j).length) ? j : null;
  } catch { return null; }
}

async function publishStore(state, password) {
  const headers = { 'content-type': 'application/json', 'x-publish-password': password || '' };
  const body = JSON.stringify(state);
  // Prefer PUT; if the host returns 405 (e.g. Pages Function didn't deploy
  // and we're hitting the static fallback) retry as POST so we get a clearer
  // signal back from the Function itself.
  let r = await fetch('/api/store', { method: 'PUT', headers, body });
  if (r.status === 405) {
    r = await fetch('/api/store', { method: 'POST', headers, body });
  }
  if (r.status === 405) throw new Error('Pages Function not deployed (still 405)');
  if (r.status === 403) {
    let msg = 'Wrong password';
    try { const j = await r.json(); if (j && j.error) msg = j.error; } catch {}
    throw new Error(msg);
  }
  if (!r.ok) {
    let detail = '';
    try { const j = await r.json(); if (j && j.error) detail = ' — ' + j.error; } catch {}
    throw new Error('Publish failed (' + r.status + ')' + detail);
  }
  return await r.json();
}

function useArmatureStore() {
  const [state, setState] = React.useState(loadStore);
  const hasLocalDraft = React.useRef(!!localStorage.getItem(ARMATURE_STORE_KEY));
  // On mount, fetch the published state from the Cloudflare Pages Function.
  // Visitors (no localStorage draft) get replaced with the published state.
  // Admins keep their draft; we just stash the published value so DataTab
  // can show "unpublished changes" vs. "in sync".
  React.useEffect(() => {
    let cancelled = false;
    fetchPublishedStore().then(remote => {
      if (cancelled || !remote) return;
      PUBLISHED_REF.current = remote;
      if (!hasLocalDraft.current && !isAdminMode()) {
        const merged = { ...cloneDefaults(), ...remote, links: { ...NAV_LINKS, ...(remote.links || {}) } };
        setState(merged);
      }
    });
    return () => { cancelled = true; };
  }, []);
  const set = React.useCallback((keyOrObj, value) => {
    setState(prev => {
      const patch = typeof keyOrObj === 'string' ? { [keyOrObj]: value } : keyOrObj;
      const next = { ...prev, ...patch };
      // Merge nested links/text/textStyles explicitly so partial updates don't blow away other keys.
      if (patch.links)            next.links            = { ...prev.links, ...patch.links };
      if (patch.text)             next.text             = { ...prev.text,  ...patch.text  };
      if (patch.textStyles)       next.textStyles       = { ...prev.textStyles, ...patch.textStyles };
      if (patch.projectOverrides) next.projectOverrides = { ...prev.projectOverrides, ...patch.projectOverrides };
      persistStore(next);
      hasLocalDraft.current = true;
      // Also relay to the design-tool host (no-op in production).
      try { window.parent.postMessage({ type: '__edit_mode_set_keys', edits: patch }, '*'); } catch {}
      return next;
    });
  }, []);
  const replaceAll = React.useCallback((next) => {
    const merged = { ...cloneDefaults(), ...next, links: { ...NAV_LINKS, ...(next.links || {}) } };
    persistStore(merged);
    hasLocalDraft.current = true;
    setState(merged);
    try { window.parent.postMessage({ type: '__edit_mode_set_keys', edits: merged }, '*'); } catch {}
  }, []);
  const reset = React.useCallback(() => {
    const d = cloneDefaults();
    persistStore(d);
    hasLocalDraft.current = true;
    setState(d);
  }, []);
  return [state, set, { reset, replaceAll }];
}

// ────────────────────────────────────────────────────────────────────────
// Effective getters — merge admin store on top of seed data so the live
// site can render with stable defaults even when localStorage is empty.
// ────────────────────────────────────────────────────────────────────────
function effectiveTeam(store) {
  if (Array.isArray(store.team) && store.team.length) return store.team;
  // store.team is the canonical list once admin saves it; until then we
  // start from the TEAM seed verbatim.
  return TEAM;
}
function effectiveCreators(store) { return Array.isArray(store.creators) ? store.creators : CREATORS; }
function effectiveBrands(store)   { return Array.isArray(store.brands)   ? store.brands   : BRANDS; }
function effectiveManaged(store)  { return Array.isArray(store.managed)  ? store.managed  : MANAGED_SEED; }
function effectiveSubBrandClients(store) { return Array.isArray(store.subBrandClients) ? store.subBrandClients : SUB_BRAND_CLIENTS_SEED; }
function effectiveLinks(store)    { return { ...NAV_LINKS, ...(store.links || {}) }; }
function effectiveText(store)     { return { ...(store.text || {}) }; }
function effectiveSubBrands(store){
  return (Array.isArray(store.subBrands) && store.subBrands.length) ? store.subBrands : SUB_BRANDS;
}
function effectiveServices(store) {
  return (Array.isArray(store.services) && store.services.length) ? store.services : SERVICES;
}
function effectiveLocations(store){
  return (Array.isArray(store.locations) && store.locations.length) ? store.locations : LOCATION_SEED;
}

// ────────────────────────────────────────────────────────────────────────
// Inline text editing — Shopify/Squarespace style.
// When admin mode is on, <E id="..."> turns its children into a
// contentEditable element with a subtle dashed outline. Edits save on blur.
// Clicking an <E> also pops up a floating toolbar with font + color + size
// controls (rendered by <InlineEditorToolbar> at the App root).
// ────────────────────────────────────────────────────────────────────────
// Core / Google fonts loaded via the HTML <link>. macOS Sequoia / Sonoma /
// Ventura ship the rest — they render natively on visitors using a Mac and
// fall through the font-family chain on other OSes.
const FONT_CHOICES_BASE = [
  { label: 'Default', value: '' },
  // — Web (loaded via Google Fonts) —
  { label: 'Instrument Serif',   value: '"Instrument Serif", serif' },
  { label: 'Newsreader',         value: '"Newsreader", serif' },
  { label: 'Geist Sans',         value: '"Geist", "Inter", system-ui, sans-serif' },
  { label: 'JetBrains Mono',     value: '"JetBrains Mono", monospace' },
  // — macOS system serifs —
  { label: 'Baskerville',        value: 'Baskerville, "Baskerville Old Face", Georgia, serif' },
  { label: 'Big Caslon',         value: '"Big Caslon", "Hoefler Text", Georgia, serif' },
  { label: 'Didot',              value: 'Didot, "Bodoni 72", "Bodoni Moda", serif' },
  { label: 'Bodoni 72',          value: '"Bodoni 72", "Bodoni Moda", Didot, serif' },
  { label: 'Georgia',            value: 'Georgia, serif' },
  { label: 'Hoefler Text',       value: '"Hoefler Text", Baskerville, Georgia, serif' },
  { label: 'Optima',             value: 'Optima, "Lucida Grande", sans-serif' },
  { label: 'Palatino',           value: 'Palatino, "Palatino Linotype", Georgia, serif' },
  { label: 'Times New Roman',    value: '"Times New Roman", Times, serif' },
  // — macOS system sans —
  { label: 'Avenir',             value: 'Avenir, "Avenir Next", Futura, sans-serif' },
  { label: 'Avenir Next',        value: '"Avenir Next", Avenir, sans-serif' },
  { label: 'Futura',             value: 'Futura, "Trebuchet MS", sans-serif' },
  { label: 'Gill Sans',          value: '"Gill Sans", "Gill Sans MT", sans-serif' },
  { label: 'Helvetica',          value: 'Helvetica, Arial, sans-serif' },
  { label: 'Helvetica Neue',     value: '"Helvetica Neue", Helvetica, Arial, sans-serif' },
  { label: 'San Francisco',      value: '-apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif' },
  { label: 'Trebuchet MS',       value: '"Trebuchet MS", sans-serif' },
  // — macOS system display —
  { label: 'American Typewriter',value: '"American Typewriter", "Courier New", serif' },
  { label: 'Apple Chancery',     value: '"Apple Chancery", cursive' },
  { label: 'Chalkboard SE',      value: '"Chalkboard SE", "Marker Felt", sans-serif' },
  { label: 'Marker Felt',        value: '"Marker Felt", "Comic Sans MS", sans-serif' },
  { label: 'Snell Roundhand',    value: '"Snell Roundhand", "Apple Chancery", cursive' },
  // — macOS system mono —
  { label: 'Courier',            value: 'Courier, "Courier New", monospace' },
  { label: 'Menlo',              value: 'Menlo, Monaco, monospace' },
  { label: 'Monaco',             value: 'Monaco, Menlo, monospace' },
  { label: 'SF Mono',            value: '"SF Mono", Menlo, monospace' },
];

// Combine the base list with any uploaded fonts (from store.customFonts).
function buildFontChoices(customFonts) {
  const extra = Object.keys(customFonts || {}).map(name => ({
    label: name + ' (custom)',
    value: `"${name}", system-ui, sans-serif`,
  }));
  return [...FONT_CHOICES_BASE, ...(extra.length ? [{ label: '──── Custom ────', value: '', disabled: true }, ...extra] : [])];
}

// Backward-compat alias for any old code that imported FONT_CHOICES directly.
const FONT_CHOICES = FONT_CHOICES_BASE;

// Mounts @font-face rules for every uploaded font so the page (and the inline
// editor preview) can render them.
function FontRegistrar({ customFonts }) {
  const css = React.useMemo(() => {
    const lines = [];
    for (const [name, dataUrl] of Object.entries(customFonts || {})) {
      if (!dataUrl) continue;
      // Format hint from the data URI — woff2 / woff / opentype / truetype.
      let fmt = 'truetype';
      if (/^data:font\/woff2/.test(dataUrl) || /\.woff2/.test(name)) fmt = 'woff2';
      else if (/^data:font\/woff/.test(dataUrl)) fmt = 'woff';
      else if (/^data:font\/otf|^data:application\/font-sfnt/.test(dataUrl) || /\.otf$/i.test(name)) fmt = 'opentype';
      lines.push(`@font-face { font-family: "${name}"; src: url("${dataUrl}") format("${fmt}"); font-display: swap; }`);
    }
    return lines.join('\n');
  }, [customFonts]);
  if (!css) return null;
  return <style>{css}</style>;
}

function PasswordGate({ title = 'Enter password', onUnlock, onCancel }) {
  const [val, setVal] = React.useState('');
  const [err, setErr] = React.useState('');
  function submit() {
    if (!val) return;
    const ok = onUnlock(val);
    if (!ok) setErr('Incorrect password');
  }
  return (
    <div style={{ position: 'fixed', inset: 0, zIndex: 2147483647, background: '#0a0606', display: 'flex', alignItems: 'center', justifyContent: 'center', flexDirection: 'column', gap: 14 }}>
      <h2 style={{ fontFamily: '"Instrument Serif", serif', fontSize: 26, color: '#f0e8dc', margin: 0, letterSpacing: '-.01em' }}>{title}</h2>
      <input
        type="password"
        value={val}
        onChange={e => setVal(e.target.value)}
        onKeyDown={e => { if (e.key === 'Enter') submit(); }}
        placeholder="Password"
        style={{ background: 'rgba(240,232,220,.06)', border: '1px solid rgba(240,232,220,.18)', color: '#f0e8dc', padding: '10px 14px', fontSize: 13, borderRadius: 2, width: 240, outline: 'none' }}
      />
      {err && <div style={{ color: '#e8a87c', fontSize: 11 }}>{err}</div>}
      <button onClick={submit} style={{ all: 'unset', cursor: 'pointer', padding: '10px 18px', background: '#a8202c', color: '#0a0606', fontFamily: '"JetBrains Mono", monospace', fontSize: 11, letterSpacing: '.18em', textTransform: 'uppercase', borderRadius: 2 }}>Unlock</button>
      {onCancel && <button onClick={onCancel} style={{ all: 'unset', cursor: 'pointer', color: 'rgba(240,232,220,.45)', fontSize: 11 }}>Cancel</button>}
    </div>
  );
}

const EditCtx = React.createContext({
  adminOn: false,
  text: {},
  textStyles: {},
  customFonts: {},
  selectedId: null,
  selectedRect: null,
  setText: () => {},
  setStyle: () => {},
  select: () => {},
});

// Build the inline-style overrides for a given id from the store.
function stylesFor(textStyles, id) {
  const s = textStyles && textStyles[id];
  if (!s) return null;
  const out = {};
  if (s.font)     out.fontFamily = s.font;
  if (s.color)    out.color = s.color;
  if (s.fontSize) out.fontSize = s.fontSize;
  return out;
}

function E({ id, as = 'span', children, style, ...rest }) {
  const ctx = React.useContext(EditCtx);
  const value = (ctx.text && ctx.text[id] != null) ? ctx.text[id] : (typeof children === 'string' ? children : (children ?? ''));
  const Tag = as;
  const ref = React.useRef(null);
  const overrideStyle = stylesFor(ctx.textStyles, id) || {};
  const mergedStyle = { ...style, ...overrideStyle };

  // Keep the contentEditable node's text synced with store value, but
  // never re-render on every keystroke (that would clobber the cursor).
  React.useEffect(() => {
    if (ctx.adminOn && ref.current && ref.current.innerText !== String(value)) {
      ref.current.innerText = String(value);
    }
  }, [value, ctx.adminOn]);

  if (!ctx.adminOn) {
    return <Tag style={mergedStyle} {...rest}>{value}</Tag>;
  }

  const isSelected = ctx.selectedId === id;
  return (
    <Tag
      ref={ref}
      data-edit-id={id}
      contentEditable
      suppressContentEditableWarning
      style={{
        ...mergedStyle,
        outline: isSelected
          ? '1px solid #a8202c'
          : '1px dashed rgba(168,32,44,.5)',
        outlineOffset: 3,
        borderRadius: 2,
        cursor: 'text',
      }}
      onFocus={(e) => {
        const r = e.currentTarget.getBoundingClientRect();
        ctx.select(id, { left: r.left, top: r.top, right: r.right, bottom: r.bottom });
      }}
      onClick={(e) => {
        const r = e.currentTarget.getBoundingClientRect();
        ctx.select(id, { left: r.left, top: r.top, right: r.right, bottom: r.bottom });
      }}
      onBlur={(e) => {
        const t = e.currentTarget.innerText;
        if (t !== String(value)) ctx.setText(id, t);
      }}
      onKeyDown={(e) => {
        // Enter blurs (saves). Shift+Enter inserts a newline normally.
        if (e.key === 'Enter' && !e.shiftKey) {
          e.preventDefault();
          e.currentTarget.blur();
        }
        // Escape cancels edit by reverting innerText and blurring.
        if (e.key === 'Escape') {
          e.preventDefault();
          if (ref.current) ref.current.innerText = String(value);
          e.currentTarget.blur();
        }
      }}
      {...rest}
    />
  );
}

// Floating toolbar — mounted once at the App root. Shows font / color / size
// controls anchored to the currently selected <E>. Position is the element's
// bounding rect when it was clicked.
function InlineEditorToolbar() {
  const ctx = React.useContext(EditCtx);
  if (!ctx.adminOn || !ctx.selectedId || !ctx.selectedRect) return null;
  const id = ctx.selectedId;
  const cur = (ctx.textStyles && ctx.textStyles[id]) || {};
  const r = ctx.selectedRect;

  // Position the bar just below the element (clamped to the viewport).
  const top = Math.min(window.innerHeight - 64, Math.max(8, r.bottom + 8));
  const left = Math.min(window.innerWidth - 460 - 8, Math.max(8, r.left));

  function patch(p) { ctx.setStyle(id, { ...cur, ...p }); }

  return (
    <div
      onMouseDown={(e) => e.preventDefault() /* keep focus in the <E> */}
      style={{
        position: 'fixed', top, left, zIndex: 2147483646,
        background: '#0a0606', color: '#f0e8dc',
        border: '1px solid #a8202c', borderRadius: 2,
        padding: '8px 10px', display: 'flex', gap: 10, alignItems: 'center',
        font: '11px/1 "JetBrains Mono", monospace',
        boxShadow: '0 12px 32px rgba(0,0,0,.5)',
      }}
    >
      <span style={{ letterSpacing: '.18em', textTransform: 'uppercase', color: 'rgba(240,232,220,.55)' }}>{id}</span>
      <select
        value={cur.font || ''}
        onChange={(e) => patch({ font: e.target.value })}
        style={{ background: 'rgba(240,232,220,.06)', color: '#f0e8dc', border: '1px solid rgba(240,232,220,.18)', padding: '4px 6px', borderRadius: 2, maxWidth: 180 }}
      >
        {buildFontChoices(ctx.customFonts).map((f, i) => (
          <option key={f.label + i} value={f.value} disabled={f.disabled}>{f.label}</option>
        ))}
      </select>
      <label style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
        Color
        <input type="color" value={cur.color || '#f0e8dc'}
          onChange={(e) => patch({ color: e.target.value })}
          style={{ width: 28, height: 22, padding: 0, border: 0, background: 'transparent', cursor: 'pointer' }}
        />
      </label>
      <label style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
        Size
        <input type="number" min={8} max={240}
          value={parseInt(cur.fontSize, 10) || ''}
          placeholder="auto"
          onChange={(e) => {
            const v = e.target.value;
            patch({ fontSize: v ? (Number(v) + 'px') : '' });
          }}
          style={{ width: 56, background: 'rgba(240,232,220,.06)', color: '#f0e8dc', border: '1px solid rgba(240,232,220,.18)', padding: '4px 6px', borderRadius: 2 }}
        />
      </label>
      <button
        onClick={() => patch({ font: '', color: '', fontSize: '' })}
        style={{ all: 'unset', cursor: 'pointer', color: 'rgba(240,232,220,.55)', padding: '4px 6px', borderRadius: 2, border: '1px solid rgba(240,232,220,.18)' }}
        title="Clear style overrides for this element"
      >reset</button>
      <button
        onClick={() => ctx.select(null, null)}
        style={{ all: 'unset', cursor: 'pointer', color: 'rgba(240,232,220,.55)', padding: '4px 6px' }}
        title="Close"
      >✕</button>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────────
// AdminPanel — fixed right-side drawer with tabs.
// ────────────────────────────────────────────────────────────────────────
const ADMIN_CSS = `
  .adm-root{position:fixed;inset:0 0 0 auto;width:340px;max-width:100vw;z-index:2147483646;
    display:flex;flex-direction:column;
    background:#0a0606;color:#f0e8dc;
    border-left:1px solid rgba(240,232,220,.10);
    box-shadow:-24px 0 64px rgba(0,0,0,.5);
    font:11px/1.45 "Geist","Inter",system-ui,sans-serif;}
  .adm-root.collapsed{transform:translateX(calc(100% - 36px));transition:transform .25s ease}
  .adm-root.open{transform:translateX(0);transition:transform .25s ease}
  .adm-tab-btn{position:absolute;left:-1px;top:14px;transform:translateX(-100%);
    background:#a8202c;color:#0a0606;border:0;cursor:pointer;
    padding:8px 10px;writing-mode:vertical-rl;text-orientation:mixed;
    font:600 10px/1 "JetBrains Mono",monospace;letter-spacing:.22em;text-transform:uppercase;}
  .adm-hd{display:flex;align-items:center;justify-content:space-between;
    padding:8px 12px;border-bottom:1px solid rgba(240,232,220,.10);flex-shrink:0;}
  .adm-hd h1{font:400 16px/1 "Instrument Serif",serif;margin:0;letter-spacing:-.01em}
  .adm-hd .sub{font:9px/1 "JetBrains Mono",monospace;letter-spacing:.18em;
    color:rgba(240,232,220,.45);text-transform:uppercase;margin-top:3px;}
  .adm-x{all:unset;cursor:pointer;color:rgba(240,232,220,.5);font-size:14px;padding:3px 6px;border-radius:4px;}
  .adm-x:hover{background:rgba(240,232,220,.06);color:#f0e8dc}
  .adm-tabs{display:flex;flex-wrap:nowrap;gap:0;border-bottom:1px solid rgba(240,232,220,.10);flex-shrink:0;overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none;}
  .adm-tabs::-webkit-scrollbar{display:none}
  .adm-tab{all:unset;cursor:pointer;flex:0 0 auto;text-align:center;padding:7px 8px;
    font:10px/1 "JetBrains Mono",monospace;letter-spacing:.1em;text-transform:uppercase;
    color:rgba(240,232,220,.55);border-bottom:2px solid transparent;white-space:nowrap;}
  .adm-tab:hover{color:#f0e8dc;background:rgba(240,232,220,.03)}
  .adm-tab.active{color:#f0e8dc;border-bottom-color:#a8202c}
  .adm-body{flex:1;overflow-y:auto;overflow-x:hidden;padding:14px;display:flex;flex-direction:column;gap:12px;
    scrollbar-width:thin;scrollbar-color:rgba(168,32,44,.3) transparent;}
  .adm-body::-webkit-scrollbar{width:6px}
  .adm-body::-webkit-scrollbar-thumb{background:rgba(168,32,44,.3);border-radius:3px}
  .adm-sect{font:600 9px/1 "JetBrains Mono",monospace;letter-spacing:.22em;text-transform:uppercase;
    color:rgba(240,232,220,.55);padding-bottom:6px;border-bottom:1px solid rgba(240,232,220,.08);margin-top:6px}
  .adm-sect:first-child{margin-top:0}
  .adm-lbl{font:9px/1 "JetBrains Mono",monospace;letter-spacing:.16em;text-transform:uppercase;
    color:rgba(240,232,220,.5);display:block;margin-bottom:4px}
  .adm-field{all:unset;display:block;width:100%;box-sizing:border-box;
    background:rgba(240,232,220,.04);border:1px solid rgba(240,232,220,.10);
    color:#f0e8dc;padding:7px 9px;border-radius:2px;
    font:12px/1.4 "Geist",system-ui,sans-serif;}
  .adm-field:focus{border-color:#a8202c;background:rgba(240,232,220,.06)}
  textarea.adm-field{resize:vertical;min-height:70px;line-height:1.5}
  .adm-btn{all:unset;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;gap:6px;
    padding:8px 12px;background:#a8202c;color:#0a0606;
    font:600 9.5px/1 "JetBrains Mono",monospace;letter-spacing:.18em;text-transform:uppercase;
    border-radius:2px;text-align:center}
  .adm-btn.ghost{background:transparent;color:#f0e8dc;border:1px solid rgba(240,232,220,.18)}
  .adm-btn.ghost:hover{border-color:#a8202c;color:#a8202c}
  .adm-btn:disabled{opacity:.4;cursor:not-allowed}
  .adm-row{display:flex;gap:8px;align-items:center}
  .adm-row.grow > *{flex:1;min-width:0}
  .adm-card{background:rgba(240,232,220,.03);border:1px solid rgba(240,232,220,.08);
    border-radius:2px;padding:10px;display:flex;flex-direction:column;gap:8px}
  .adm-img-slot{width:100%;aspect-ratio:1;background:rgba(240,232,220,.05);
    border:1px dashed rgba(240,232,220,.18);border-radius:2px;
    display:flex;align-items:center;justify-content:center;cursor:pointer;
    background-size:cover;background-position:center;background-repeat:no-repeat;
    color:rgba(240,232,220,.4);font:9px/1.3 "JetBrains Mono",monospace;letter-spacing:.16em;text-transform:uppercase;
    text-align:center;position:relative;overflow:hidden;transition:border-color .15s}
  .adm-img-slot:hover{border-color:#a8202c}
  .adm-img-slot .clear{position:absolute;top:5px;right:5px;background:rgba(10,6,6,.75);color:#f0e8dc;
    border:0;padding:3px 7px;border-radius:2px;font:9px/1 "JetBrains Mono",monospace;letter-spacing:.14em;cursor:pointer}
  .adm-img-slot.has-img{border-style:solid;border-color:rgba(240,232,220,.18)}
  .adm-img-slot.portrait{aspect-ratio:3/4}
  .adm-img-slot.logo{aspect-ratio:16/9}
  .adm-swatch{display:inline-block;width:16px;height:16px;border-radius:2px;border:1px solid rgba(240,232,220,.18);vertical-align:middle}
  .adm-mini-x{all:unset;cursor:pointer;color:rgba(240,232,220,.4);padding:0 5px;font-size:13px}
  .adm-mini-x:hover{color:#a8202c}
  .adm-help{font:10px/1.45 "Geist",system-ui,sans-serif;color:rgba(240,232,220,.5);margin-top:-2px}
  .adm-toast{position:fixed;bottom:24px;right:364px;background:#0a0606;color:#f0e8dc;
    border:1px solid #a8202c;padding:10px 16px;border-radius:2px;z-index:2147483647;
    font:11px/1 "JetBrains Mono",monospace;letter-spacing:.14em;text-transform:uppercase;
    box-shadow:0 12px 32px rgba(0,0,0,.5)}
`;

function AdminPanel({ store, set, replaceAll, reset, busy, err, progress, onAddVimeo, onRemoveVimeo, onMoveVideo, onPatchVideo, allProjects, onPatchProject, onResetProject, onSetShowreel, onClose, onAddUserProject, onRemoveUserProjectByVid }) {
  const [tab, setTab] = React.useState('videos');
  const [open, setOpen] = React.useState(true);
  const [toast, setToast] = React.useState('');
  const toastT = React.useRef(null);
  function flash(msg) {
    setToast(msg);
    clearTimeout(toastT.current);
    toastT.current = setTimeout(() => setToast(''), 2200);
  }
  React.useEffect(() => () => clearTimeout(toastT.current), []);

  const team      = effectiveTeam(store);
  const creators  = effectiveCreators(store);
  const brands    = effectiveBrands(store);
  const links     = effectiveLinks(store);
  const subBrands = effectiveSubBrands(store);
  const services  = effectiveServices(store);
  const locations = effectiveLocations(store);

  function exportJSON() {
    const blob = new Blob([JSON.stringify(store, null, 2)], { type: 'application/json' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'armature-site-' + new Date().toISOString().slice(0, 10) + '.json';
    document.body.appendChild(a); a.click(); document.body.removeChild(a);
    URL.revokeObjectURL(url);
    flash('Exported');
  }
  function importJSON(file) {
    const r = new FileReader();
    r.onload = () => {
      try {
        const parsed = JSON.parse(String(r.result));
        replaceAll(parsed);
        flash('Imported · reloading');
        setTimeout(() => window.location.reload(), 600);
      } catch (e) {
        flash('Import failed');
      }
    };
    r.readAsText(file);
  }
  function copyShareLink() {
    const u = new URL(window.location.href);
    u.searchParams.delete('admin');
    u.hash = '';
    navigator.clipboard?.writeText(u.toString());
    flash('Visitor URL copied');
  }

  return (
    <>
      <style>{ADMIN_CSS}</style>
      <div className={`adm-root adm-drawer ${open ? 'open' : 'collapsed'}`}>
        <button className="adm-tab-btn" onClick={() => setOpen(o => !o)}>
          {open ? 'Hide admin' : 'Admin ▸'}
        </button>

        <div className="adm-hd">
          <div>
            <h1>Site admin</h1>
            <div className="sub">ARMATURE · localStorage</div>
          </div>
          <button className="adm-x" onClick={() => { if (onClose) onClose(); else setOpen(false); }} title="Close admin">✕</button>
        </div>

        <div className="adm-tabs">
          {[
            ['videos',   'All Work'],
            ['media',    'Manage media'],
            ['team',     'Team'],
            ['services', 'Services'],
            ['brands',   'Sub-brands'],
            ['studios',  'Studios'],
            ['clients',  'Clients'],
            ['links',    'Links'],
            ['look',     'Look'],
            ['access',   'Access'],
            ['data',     'Data'],
          ].map(([k, label]) => (
            <button key={k}
              className={`adm-tab ${tab === k ? 'active' : ''}`}
              onClick={() => setTab(k)}>{label}</button>
          ))}
        </div>

        <div className="adm-body">
          {tab === 'videos' && (
            <VideosTab
              store={store} set={set}
              busy={busy} err={err} progress={progress}
              onAddVimeo={onAddVimeo}
              onRemoveVimeo={onRemoveVimeo}
              onMoveVideo={onMoveVideo}
              onPatchVideo={onPatchVideo}
              allProjects={allProjects}
              onPatchProject={onPatchProject}
              onResetProject={onResetProject}
              onSetShowreel={onSetShowreel}
            />
          )}
          {tab === 'media' && (
            <MediaTab
              allProjects={allProjects}
              team={team}
              userProjects={store.userProjects || []}
              projectOverrides={store.projectOverrides || {}}
              onPatchVideo={onPatchVideo}
              onRemoveVimeo={onRemoveVimeo}
              onPatchProject={onPatchProject}
              onResetProject={onResetProject}
              onPatchTeam={(v) => set('team', v)}
              onAddUserProject={onAddUserProject}
              onRemoveUserProjectByVid={onRemoveUserProjectByVid}
            />
          )}
          {tab === 'team' && (
            <TeamTab team={team} onChange={(v) => set('team', v)} />
          )}
          {tab === 'services' && (
            <ServicesTab services={services} onChange={(v) => set('services', v)} />
          )}
          {tab === 'brands' && (
            <SubBrandsTab subBrands={subBrands} onChange={(v) => set('subBrands', v)} />
          )}
          {tab === 'studios' && (
            <StudiosTab locations={locations} onChange={(v) => set('locations', v)} />
          )}
          {tab === 'clients' && (
            <ClientsTab
              creators={creators}
              brands={brands}
              managed={effectiveManaged(store)}
              subBrandClients={effectiveSubBrandClients(store)}
              onCreators={(v) => set('creators', v)}
              onBrands={(v) => set('brands', v)}
              onManaged={(v) => set('managed', v)}
              onSubBrandClients={(v) => set('subBrandClients', v)}
            />
          )}
          {tab === 'links' && (
            <LinksTab links={links} onChange={(v) => set('links', v)} />
          )}
          {tab === 'look' && (
            <LookTab store={store} set={set} />
          )}
          {tab === 'access' && (
            <AccessTab store={store} set={set} />
          )}
          {tab === 'data' && (
            <DataTab
              store={store}
              replaceAll={replaceAll}
              exportJSON={exportJSON}
              importJSON={importJSON}
              reset={() => { if (confirm('Reset all admin edits to defaults?')) { reset(); flash('Reset'); }}}
              copyShareLink={copyShareLink}
            />
          )}
        </div>
      </div>
      {toast && <div className="adm-toast">{toast}</div>}
    </>
  );
}

// ─── Videos tab ────────────────────────────────────────────────────────────
function VideosTab({ store, set, busy, err, progress, onAddVimeo, onRemoveVimeo, onMoveVideo, onPatchVideo, allProjects, onPatchProject, onResetProject, onSetShowreel }) {
  const [text, setText] = React.useState('');
  const [expandedIdx, setExpandedIdx] = React.useState(null);
  const [expandedProjId, setExpandedProjId] = React.useState(null);
  const lines = text.split(/[\n,]+/).map(s => s.trim()).filter(Boolean);
  const userProjects = store.userProjects || [];
  const overrides = store.projectOverrides || {};
  return (
    <>
      <div className="adm-sect">Showreel</div>
      <label className="adm-lbl">Vimeo or YouTube URL (used in the hero)</label>
      <div className="adm-row grow">
        <input
          className="adm-field"
          defaultValue={store.showreelSource === 'youtube' ? ('https://youtu.be/' + store.showreelId) : store.showreelId}
          placeholder="vimeo.com/76979871 or youtube.com/watch?v=…"
          onBlur={(e) => onSetShowreel(e.target.value)}
          onKeyDown={(e) => { if (e.key === 'Enter') e.currentTarget.blur(); }}
        />
      </div>

      <div className="adm-sect">Add videos to the grid</div>
      <textarea
        className="adm-field"
        value={text}
        onChange={(e) => setText(e.target.value)}
        placeholder={'Paste one or more Vimeo or YouTube URLs (one per line)\nhttps://vimeo.com/76979871\nhttps://www.youtube.com/watch?v=dQw4w9WgXcQ'}
      />
      <button className="adm-btn" disabled={busy || lines.length === 0}
        onClick={() => { onAddVimeo(lines); setText(''); }}>
        {busy
          ? (progress ? `Fetching · ${progress.done}/${progress.total}` : 'Fetching…')
          : (lines.length > 1 ? `Add ${lines.length} videos` : 'Add video')}
      </button>
      {err && <div style={{ color: '#e8a87c', fontSize: 11, whiteSpace: 'pre-wrap' }}>{err}</div>}
      <div className="adm-help">Title &amp; thumbnail are auto-fetched (Vimeo oEmbed also returns runtime).</div>

      <div className="adm-sect">Work-grid filters</div>
      <div className="adm-help">
        The chips above the grid. Clicking a chip filters tiles whose brand/tag/client matches the chip text.
        Keep <b>all</b> as the first item — it shows everything.
      </div>
      <WorkFiltersEditor
        value={Array.isArray(store.workFilters) && store.workFilters.length ? store.workFilters : ARMATURE_DEFAULTS.workFilters}
        onChange={(v) => set('workFilters', v)}
      />

      {userProjects.length > 0 && (
        <>
          <div className="adm-sect">Your videos ({userProjects.length})</div>
          <div className="adm-help">Click a row to edit the hover badge (brand/tag), title, client, or year.</div>
          {userProjects.map((u, i) => {
            const isOpen = expandedIdx === i;
            return (
              <div key={i} className="adm-card" style={{ padding: 8, gap: 6 }}>
                <div className="adm-row" style={{ alignItems: 'center', cursor: 'pointer' }}
                  onClick={() => setExpandedIdx(isOpen ? null : i)}>
                  <div style={{
                    width: 56, height: 32, flexShrink: 0,
                    background: u.thumb ? `url(${u.thumb}) center/cover` : 'rgba(240,232,220,.08)',
                  }} />
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 12, color: '#f0e8dc', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{u.title}</div>
                    <div style={{ fontSize: 10, color: 'rgba(240,232,220,.45)', fontFamily: '"JetBrains Mono", monospace' }}>{u.youtubeId ? 'youtube' : 'vimeo'} · {u.brand || '—'} · {u.tag || '—'}</div>
                  </div>
                  <button className="adm-mini-x" disabled={i === 0} onClick={(e) => { e.stopPropagation(); onMoveVideo(i, -1); }} title="Move up" style={{ opacity: i === 0 ? .3 : 1 }}>↑</button>
                  <button className="adm-mini-x" disabled={i === userProjects.length - 1} onClick={(e) => { e.stopPropagation(); onMoveVideo(i, 1); }} title="Move down" style={{ opacity: i === userProjects.length - 1 ? .3 : 1 }}>↓</button>
                  <button className="adm-mini-x" onClick={(e) => { e.stopPropagation(); onRemoveVimeo(i); }} title="Remove">×</button>
                </div>
                {isOpen && (
                  <div style={{ display: 'flex', flexDirection: 'column', gap: 8, paddingTop: 4, borderTop: '1px solid rgba(240,232,220,.08)' }}>
                    <div>
                      <label className="adm-lbl">Title</label>
                      <input className="adm-field" value={u.title || ''}
                        onChange={(e) => onPatchVideo(i, { title: e.target.value })} />
                    </div>
                    <div className="adm-row grow">
                      <div style={{ flex: 1 }}>
                        <label className="adm-lbl">Brand (hover badge — left)</label>
                        <input className="adm-field" value={u.brand || ''}
                          placeholder="CINESTEEL / FULLMETAL / MUSIC VIDEO / …"
                          onChange={(e) => onPatchVideo(i, { brand: e.target.value })} />
                      </div>
                      <div style={{ flex: 1 }}>
                        <label className="adm-lbl">Tag (project page)</label>
                        <input className="adm-field" value={u.tag || ''}
                          placeholder="Film / Short / Spec ad / …"
                          onChange={(e) => onPatchVideo(i, { tag: e.target.value })} />
                      </div>
                    </div>
                    <div className="adm-row grow">
                      <div style={{ flex: 2 }}>
                        <label className="adm-lbl">Client</label>
                        <input className="adm-field" value={u.client || ''}
                          onChange={(e) => onPatchVideo(i, { client: e.target.value })} />
                      </div>
                      <div style={{ flex: 1 }}>
                        <label className="adm-lbl">Year</label>
                        <input className="adm-field" type="number" value={u.year || ''}
                          onChange={(e) => onPatchVideo(i, { year: Number(e.target.value) || u.year })} />
                      </div>
                    </div>
                  </div>
                )}
              </div>
            );
          })}
        </>
      )}

      {Array.isArray(allProjects) && allProjects.length > 0 && (
        <>
          <div className="adm-sect">All grid tiles ({allProjects.length})</div>
          <div className="adm-help">Edit the hover badge (brand), tag, title, client, or year on any tile — seed films included. Click a row to expand.</div>
          {allProjects.map((p) => {
            const isOpen = expandedProjId === p.id;
            const o = overrides[p.id] || {};
            const isHidden = !!o._hidden;
            const hasEdits = Object.keys(o).filter(k => k !== '_hidden').length > 0;
            return (
              <div key={p.id} className="adm-card" style={{ padding: 8, gap: 6, opacity: isHidden ? 0.45 : 1 }}>
                <div className="adm-row" style={{ alignItems: 'center', cursor: isHidden ? 'default' : 'pointer' }}
                  onClick={() => { if (!isHidden) setExpandedProjId(isOpen ? null : p.id); }}>
                  <div style={{
                    width: 56, height: 32, flexShrink: 0,
                    background: p.thumb ? `url(${p.thumb}) center/cover` : 'rgba(168,32,44,.18)',
                  }} />
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 12, color: '#f0e8dc', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                      {p.title}
                      {isHidden && <span style={{ color: 'rgba(240,232,220,.4)', fontSize: 9, marginLeft: 4 }}>● hidden</span>}
                      {!isHidden && hasEdits && <span style={{ color: '#a8202c', fontSize: 9, marginLeft: 4 }}>● edited</span>}
                    </div>
                    <div style={{ fontSize: 10, color: 'rgba(240,232,220,.45)', fontFamily: '"JetBrains Mono", monospace' }}>
                      {p.id} · {p.brand || '—'} · {p.tag || '—'}
                    </div>
                  </div>
                  {(isHidden || hasEdits) && (
                    <button className="adm-mini-x" onClick={(e) => { e.stopPropagation(); onResetProject(p.id); }} title={isHidden ? 'Restore to grid' : 'Reset edits'}>↺</button>
                  )}
                  {!isHidden && (
                    <button className="adm-mini-x" onClick={(e) => { e.stopPropagation(); onPatchProject(p.id, { _hidden: true }); }} title="Remove from grid">×</button>
                  )}
                </div>
                {isOpen && (
                  <div style={{ display: 'flex', flexDirection: 'column', gap: 8, paddingTop: 4, borderTop: '1px solid rgba(240,232,220,.08)' }}>
                    <div>
                      <label className="adm-lbl">Title</label>
                      <input className="adm-field" value={p.title || ''}
                        onChange={(e) => onPatchProject(p.id, { title: e.target.value })} />
                    </div>
                    <div className="adm-row grow">
                      <div style={{ flex: 1 }}>
                        <label className="adm-lbl">Brand (hover badge)</label>
                        <input className="adm-field" value={p.brand || ''}
                          placeholder="CINESTEEL / FULLMETAL / MUSIC VIDEO / SPEC AD / …"
                          onChange={(e) => onPatchProject(p.id, { brand: e.target.value })} />
                      </div>
                      <div style={{ flex: 1 }}>
                        <label className="adm-lbl">Tag (project page)</label>
                        <input className="adm-field" value={p.tag || ''}
                          placeholder="Film / Short / Spec ad / …"
                          onChange={(e) => onPatchProject(p.id, { tag: e.target.value })} />
                      </div>
                    </div>
                    <div className="adm-row grow">
                      <div style={{ flex: 2 }}>
                        <label className="adm-lbl">Client</label>
                        <input className="adm-field" value={p.client || ''}
                          onChange={(e) => onPatchProject(p.id, { client: e.target.value })} />
                      </div>
                      <div style={{ flex: 1 }}>
                        <label className="adm-lbl">Year</label>
                        <input className="adm-field" type="number" value={p.year || ''}
                          onChange={(e) => onPatchProject(p.id, { year: Number(e.target.value) || p.year })} />
                      </div>
                    </div>
                  </div>
                )}
              </div>
            );
          })}
        </>
      )}

      <div className="adm-sect">Campaign &amp; production photos</div>
      <div className="adm-help">Photos shown in the home page campaigns grid, below the video work. Use the Caption field as the client / campaign name — it appears on hover and in the lightbox.</div>
      <MemberPhotos
        photos={store.homePhotos || []}
        onChange={(v) => set('homePhotos', v)}
      />
    </>
  );
}

// Single editable row used by MediaTab. Renders thumb + summary; click to expand
// into brand / tag / title / client / year inputs. `onPatch` writes one field at
// a time; `onRemove` (optional) deletes the entry.
function MediaRow({ p, label, isOverridden, onPatch, onRemove, onReset, expanded, onToggle }) {
  return (
    <div className="adm-card" style={{ padding: 8, gap: 6 }}>
      <div className="adm-row" style={{ alignItems: 'center', cursor: 'pointer' }}
        onClick={onToggle}>
        <div style={{
          width: 56, height: 32, flexShrink: 0,
          background: p.thumb ? `url(${p.thumb}) center/cover` : 'rgba(168,32,44,.18)',
        }} />
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 12, color: '#f0e8dc', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
            {p.title || 'Untitled'}
            {isOverridden && <span style={{ color: '#a8202c', fontSize: 9, marginLeft: 4 }}>● edited</span>}
          </div>
          <div style={{ fontSize: 10, color: 'rgba(240,232,220,.45)', fontFamily: '"JetBrains Mono", monospace' }}>
            {label} · {p.brand || '—'} · {p.tag || '—'}
          </div>
        </div>
        {onReset && isOverridden && (
          <button className="adm-mini-x" onClick={(e) => { e.stopPropagation(); onReset(); }} title="Reset overrides">↺</button>
        )}
        {onRemove && (
          <button className="adm-mini-x" onClick={(e) => { e.stopPropagation(); onRemove(); }} title="Remove">×</button>
        )}
      </div>
      {expanded && (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 8, paddingTop: 4, borderTop: '1px solid rgba(240,232,220,.08)' }}>
          <div>
            <label className="adm-lbl">Title</label>
            <input className="adm-field" value={p.title || ''}
              onChange={(e) => onPatch({ title: e.target.value })} />
          </div>
          <div className="adm-row grow">
            <div style={{ flex: 1 }}>
              <label className="adm-lbl">Brand (hover badge — left)</label>
              <input className="adm-field" value={p.brand || ''}
                placeholder="CINESTEEL / FULLMETAL / MUSIC VIDEO / …"
                onChange={(e) => onPatch({ brand: e.target.value })} />
            </div>
            <div style={{ flex: 1 }}>
              <label className="adm-lbl">Tag (project page)</label>
              <input className="adm-field" value={p.tag || ''}
                placeholder="Film / Short / Spec ad / …"
                onChange={(e) => onPatch({ tag: e.target.value })} />
            </div>
          </div>
          <div className="adm-row grow">
            <div style={{ flex: 2 }}>
              <label className="adm-lbl">Client</label>
              <input className="adm-field" value={p.client || ''}
                onChange={(e) => onPatch({ client: e.target.value })} />
            </div>
            <div style={{ flex: 1 }}>
              <label className="adm-lbl">Year</label>
              <input className="adm-field" type="number" value={p.year || ''}
                onChange={(e) => onPatch({ year: Number(e.target.value) || p.year })} />
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

// Popup listing all team member inline videos so the admin can promote them to
// the home grid or remove them. Floats over the admin drawer (position:fixed).
function TeamMediaPopup({ team, userProjects, onClose, onAddToHome, onRemoveFromHome, onPatchTeamEntry }) {
  const [openEdit, setOpenEdit] = React.useState(null);

  // Build a Set of videoIds already promoted to home.
  const onHomeIds = React.useMemo(() => {
    const s = new Set();
    for (const u of userProjects) {
      if (u._fromMember) {
        const vid = u.youtubeId || u.vimeoId || u.videoId;
        if (vid) s.add(vid);
      }
    }
    return s;
  }, [userProjects]);

  const sections = (team || []).map((m, memberIdx) => {
    const entries = (m.projects || [])
      .map((mp, j) => ({ mp, j }))
      .filter(({ mp }) => !mp.projectId && (mp.youtubeId || mp.vimeoId || mp.videoId));
    return { member: m, memberIdx, entries };
  }).filter(s => s.entries.length > 0);

  return (
    <div onClick={onClose} style={{
      position: 'fixed', inset: 0, zIndex: 2147483648,
      background: 'rgba(10,6,6,.75)', display: 'flex', alignItems: 'flex-start', justifyContent: 'flex-end',
    }}>
      <div onClick={(e) => e.stopPropagation()} style={{
        width: 340, height: '100%', background: '#16090b',
        borderLeft: '1px solid rgba(240,232,220,.1)',
        display: 'flex', flexDirection: 'column', overflow: 'hidden',
      }}>
        <div style={{ padding: '14px 16px', borderBottom: '1px solid rgba(240,232,220,.1)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexShrink: 0 }}>
          <div style={{ fontFamily: '"JetBrains Mono", monospace', fontSize: 10.5, letterSpacing: '.18em', color: '#f0e8dc', textTransform: 'uppercase' }}>Team media</div>
          <button onClick={onClose} style={{ all: 'unset', cursor: 'pointer', color: 'rgba(240,232,220,.5)', fontSize: 16, lineHeight: 1 }}>✕</button>
        </div>
        <div style={{ flex: 1, overflowY: 'auto', padding: '12px 12px 24px' }}>
          <div style={{ fontSize: 10.5, color: 'rgba(240,232,220,.45)', marginBottom: 14, lineHeight: 1.5 }}>
            Add team member videos to the home grid, or remove them. Edit brand / tag so the hover badge reads correctly on home.
          </div>
          {sections.length === 0 && (
            <div style={{ fontSize: 11, color: 'rgba(240,232,220,.35)', fontStyle: 'italic' }}>No inline videos on team members yet. Add them from the Team tab.</div>
          )}
          {sections.map(({ member, memberIdx, entries }) => (
            <div key={member.id} style={{ marginBottom: 16 }}>
              <div style={{ fontFamily: '"JetBrains Mono", monospace', fontSize: 9.5, letterSpacing: '.18em', color: 'rgba(240,232,220,.5)', textTransform: 'uppercase', marginBottom: 8, paddingBottom: 4, borderBottom: '1px solid rgba(240,232,220,.07)' }}>
                {member.name}
              </div>
              {entries.map(({ mp, j }, rowIdx) => {
                const vid = mp.youtubeId || mp.vimeoId || mp.videoId;
                const onHome = onHomeIds.has(vid);
                const editKey = member.id + '_' + j;
                const isEditing = openEdit === editKey;
                return (
                  <div key={editKey} className="adm-card" style={{ padding: 8, gap: 6, marginBottom: 6 }}>
                    <div className="adm-row" style={{ alignItems: 'center', gap: 8 }}>
                      <div style={{
                        width: 48, height: 28, flexShrink: 0, borderRadius: 1,
                        background: mp.thumb ? `url(${mp.thumb}) center/cover` : 'rgba(168,32,44,.18)',
                      }} />
                      <div style={{ flex: 1, minWidth: 0 }}>
                        <div style={{ fontSize: 11.5, color: '#f0e8dc', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{mp.title || 'Untitled'}</div>
                        <div style={{ fontSize: 9.5, color: 'rgba(240,232,220,.4)', fontFamily: '"JetBrains Mono", monospace' }}>
                          {mp.brand || '—'} · {mp.tag || '—'}
                        </div>
                      </div>
                      <button
                        onClick={() => setOpenEdit(isEditing ? null : editKey)}
                        style={{ all: 'unset', cursor: 'pointer', fontSize: 9.5, fontFamily: '"JetBrains Mono", monospace', letterSpacing: '.1em', color: 'rgba(240,232,220,.45)', padding: '3px 6px', border: '1px solid rgba(240,232,220,.15)', borderRadius: 2, flexShrink: 0 }}>
                        edit
                      </button>
                      <button
                        onClick={() => onHome ? onRemoveFromHome(vid) : onAddToHome(mp, member.id)}
                        style={{
                          all: 'unset', cursor: 'pointer', fontSize: 9.5,
                          fontFamily: '"JetBrains Mono", monospace', letterSpacing: '.1em',
                          color: onHome ? '#0a0606' : '#f0e8dc',
                          background: onHome ? '#a8202c' : 'rgba(240,232,220,.12)',
                          padding: '3px 8px', borderRadius: 2, flexShrink: 0,
                          border: onHome ? '1px solid #a8202c' : '1px solid rgba(240,232,220,.15)',
                        }}>
                        {onHome ? 'on home ✓' : '+ home'}
                      </button>
                    </div>
                    {isEditing && (
                      <div style={{ display: 'flex', flexDirection: 'column', gap: 6, paddingTop: 6, borderTop: '1px solid rgba(240,232,220,.08)' }}>
                        <div className="adm-row grow">
                          <div style={{ flex: 1 }}>
                            <label className="adm-lbl">Brand</label>
                            <input className="adm-field" value={mp.brand || ''}
                              placeholder="CINESTEEL / FULLMETAL / …"
                              onChange={(e) => onPatchTeamEntry(memberIdx, j, { brand: e.target.value })} />
                          </div>
                          <div style={{ flex: 1 }}>
                            <label className="adm-lbl">Tag</label>
                            <input className="adm-field" value={mp.tag || ''}
                              placeholder="Film / Spot / …"
                              onChange={(e) => onPatchTeamEntry(memberIdx, j, { tag: e.target.value })} />
                          </div>
                        </div>
                        <div>
                          <label className="adm-lbl">Title</label>
                          <input className="adm-field" value={mp.title || ''}
                            onChange={(e) => onPatchTeamEntry(memberIdx, j, { title: e.target.value })} />
                        </div>
                        <div className="adm-row grow">
                          <div style={{ flex: 2 }}>
                            <label className="adm-lbl">Client</label>
                            <input className="adm-field" value={mp.client || ''}
                              onChange={(e) => onPatchTeamEntry(memberIdx, j, { client: e.target.value })} />
                          </div>
                          <div style={{ flex: 1 }}>
                            <label className="adm-lbl">Year</label>
                            <input className="adm-field" type="number" value={mp.year || ''}
                              onChange={(e) => onPatchTeamEntry(memberIdx, j, { year: Number(e.target.value) || mp.year })} />
                          </div>
                        </div>
                      </div>
                    )}
                  </div>
                );
              })}
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

// Cross-section media manager. Home videos list with sequential numbering +
// a "Team media" button that opens TeamMediaPopup to promote/remove member videos.
// Seed films section lets you retag built-in placeholders via projectOverrides.
function MediaTab({ allProjects, team, userProjects, projectOverrides, onPatchVideo, onRemoveVimeo, onPatchProject, onResetProject, onPatchTeam, onAddUserProject, onRemoveUserProjectByVid }) {
  const [openId, setOpenId] = React.useState(null);
  const [showPopup, setShowPopup] = React.useState(false);
  function toggle(id) { setOpenId(prev => prev === id ? null : id); }

  // Patch a member's inline entry in-place (immutable team update).
  function patchMemberEntry(memberIdx, entryIdx, patch) {
    const nextTeam = team.map((m, i) => {
      if (i !== memberIdx) return m;
      const list = (m.projects || []).slice();
      list[entryIdx] = { ...list[entryIdx], ...patch };
      return { ...m, projects: list };
    });
    onPatchTeam(nextTeam);
  }

  // Add a member video to the home grid (prepend to userProjects).
  function addToHome(mp, memberId) {
    const vid = mp.youtubeId || mp.vimeoId || mp.videoId;
    if (!vid) return;
    const entry = {
      title: mp.title || 'Untitled',
      client: mp.client || '',
      role: mp.role || mp.memberRole || '',
      year: mp.year || new Date().getFullYear(),
      runtime: mp.runtime || '',
      brand: mp.brand || 'CINESTEEL',
      tag: mp.tag || 'Film',
      source: mp.source || (mp.youtubeId ? 'youtube' : 'vimeo'),
      videoId: vid,
      vimeoId: mp.vimeoId,
      youtubeId: mp.youtubeId,
      thumb: mp.thumb,
      hue: mp.hue ?? 0,
      _fromMember: memberId,
    };
    onAddUserProject(entry);
  }

  // Remove a promoted member video from userProjects by its videoId.
  function removeFromHome(vid) {
    onRemoveUserProjectByVid(vid);
  }

  const homeRows = userProjects.map((u, i) => ({ key: 'h_' + i, u, i }));
  const seedRows = (allProjects || []).filter(p => /^p\d+$/.test(p.id));

  // Count of inline member entries across all members (for the button label).
  const totalMemberVids = (team || []).reduce((n, m) =>
    n + (m.projects || []).filter(mp => !mp.projectId && (mp.youtubeId || mp.vimeoId || mp.videoId)).length, 0);

  return (
    <>
      <div className="adm-sect">Manage media</div>
      <div className="adm-help">
        Home grid videos with numbered order. Use <b>Team media</b> to promote a member's video onto the home grid or take it off.
      </div>

      <div className="adm-sect" style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
        <span>Home page videos ({homeRows.length})</span>
        <button className="adm-btn" style={{ margin: 0, padding: '4px 10px', fontSize: 10 }}
          onClick={() => setShowPopup(true)}>
          Team media ({totalMemberVids})
        </button>
      </div>
      {homeRows.length === 0 && (
        <div className="adm-help">No videos on the home grid yet. Add them from the <b>Videos</b> tab or promote from <b>Team media</b>.</div>
      )}
      {homeRows.map(({ key, u, i }) => (
        <MediaRow
          key={key}
          p={u}
          label={String(i + 1).padStart(3, '0') + ' · ' + (u.youtubeId ? 'youtube' : 'vimeo') + (u._fromMember ? ' · ' + u._fromMember : '')}
          isOverridden={false}
          expanded={openId === key}
          onToggle={() => toggle(key)}
          onPatch={(patch) => onPatchVideo(i, patch)}
          onRemove={() => onRemoveVimeo(i)}
        />
      ))}


      {showPopup && (
        <TeamMediaPopup
          team={team}
          userProjects={userProjects}
          onClose={() => setShowPopup(false)}
          onAddToHome={addToHome}
          onRemoveFromHome={removeFromHome}
          onPatchTeamEntry={patchMemberEntry}
        />
      )}
    </>
  );
}

function WorkFiltersEditor({ value, onChange }) {
  const [draft, setDraft] = React.useState('');
  function update(i, v) {
    const next = value.slice(); next[i] = v; onChange(next);
  }
  function remove(i) {
    const next = value.slice(); next.splice(i, 1); onChange(next);
  }
  function move(i, dir) {
    const j = i + dir;
    if (j < 0 || j >= value.length) return;
    const next = value.slice();
    [next[i], next[j]] = [next[j], next[i]];
    onChange(next);
  }
  function add() {
    const v = draft.trim();
    if (!v) return;
    onChange([...value, v]);
    setDraft('');
  }
  return (
    <>
      {value.map((f, i) => (
        <div key={i} className="adm-row" style={{ alignItems: 'center' }}>
          <input className="adm-field" value={f} onChange={(e) => update(i, e.target.value)} />
          <button className="adm-mini-x" disabled={i === 0} onClick={() => move(i, -1)} title="Move up" style={{ opacity: i === 0 ? .3 : 1 }}>↑</button>
          <button className="adm-mini-x" disabled={i === value.length - 1} onClick={() => move(i, 1)} title="Move down" style={{ opacity: i === value.length - 1 ? .3 : 1 }}>↓</button>
          <button className="adm-mini-x" onClick={() => remove(i)} title="Remove">×</button>
        </div>
      ))}
      <div className="adm-row grow">
        <input className="adm-field" value={draft} onChange={(e) => setDraft(e.target.value)}
          placeholder="music video, short film, spec ad, …"
          onKeyDown={(e) => { if (e.key === 'Enter') add(); }} />
        <button className="adm-btn" disabled={!draft.trim()} onClick={add} style={{ flex: 'none' }}>+ Add</button>
      </div>
    </>
  );
}

// ─── Sub-brands tab — edit the home cards (FULLMETAL / CINESTEEL / etc.) ─
function SubBrandsTab({ subBrands, onChange }) {
  function patch(i, p) {
    const next = subBrands.slice();
    next[i] = { ...next[i], ...p };
    onChange(next);
  }
  function remove(i) {
    const next = subBrands.slice();
    next.splice(i, 1);
    onChange(next);
  }
  function move(i, dir) {
    const j = i + dir;
    if (j < 0 || j >= subBrands.length) return;
    const next = subBrands.slice();
    [next[i], next[j]] = [next[j], next[i]];
    onChange(next);
  }
  function add() {
    onChange([...subBrands, {
      id: 'brand_' + Math.random().toString(36).slice(2, 7),
      name: 'New brand',
      href: '',
      tagline: 'Short tagline goes here.',
      focus: 'Focus · Tags',
    }]);
  }
  return (
    <>
      <div className="adm-help">
        Cards shown on the home page under <b>"ARMATURE is the frame…"</b> and links in the
        nav strip + footer. Drives the FULLMETAL/CINESTEEL slots on the About page too.
      </div>
      {subBrands.map((b, i) => (
        <div key={b.id || i} className="adm-card">
          <div className="adm-row" style={{ alignItems: 'center' }}>
            <div style={{ flex: 1, minWidth: 0, fontFamily: '"JetBrains Mono", monospace', fontSize: 10, color: 'rgba(240,232,220,.45)', letterSpacing: '.16em', textTransform: 'uppercase' }}>
              {String(i + 1).padStart(2, '0')} / {String(subBrands.length).padStart(2, '0')}
            </div>
            <button className="adm-mini-x" disabled={i === 0} onClick={() => move(i, -1)} title="Move up" style={{ opacity: i === 0 ? .3 : 1 }}>↑</button>
            <button className="adm-mini-x" disabled={i === subBrands.length - 1} onClick={() => move(i, 1)} title="Move down" style={{ opacity: i === subBrands.length - 1 ? .3 : 1 }}>↓</button>
            <button className="adm-mini-x" onClick={() => remove(i)} title="Remove">×</button>
          </div>
          <div>
            <label className="adm-lbl">Name</label>
            <input className="adm-field" value={b.name || ''}
              onChange={(e) => patch(i, { name: e.target.value })} />
          </div>
          <div>
            <label className="adm-lbl">Link (external URL)</label>
            <input className="adm-field" type="url" value={b.href || ''}
              placeholder="https://fullmetal.store"
              onChange={(e) => patch(i, { href: e.target.value })} />
          </div>
          <div>
            <label className="adm-lbl">Tagline</label>
            <textarea className="adm-field" rows={2} value={b.tagline || ''}
              onChange={(e) => patch(i, { tagline: e.target.value })} />
          </div>
          <div>
            <label className="adm-lbl">Focus line (e.g. "Direction · Production")</label>
            <input className="adm-field" value={b.focus || ''}
              onChange={(e) => patch(i, { focus: e.target.value })} />
          </div>
        </div>
      ))}
      <button className="adm-btn" onClick={add}>+ Add sub-brand</button>
    </>
  );
}

// ─── Studios tab — locations on contact page + footer ──────────────────────
function StudiosTab({ locations, onChange }) {
  function patch(i, p) {
    const next = locations.slice();
    next[i] = { ...next[i], ...p };
    onChange(next);
  }
  function remove(i) {
    const next = locations.slice();
    next.splice(i, 1);
    onChange(next);
  }
  function move(i, dir) {
    const j = i + dir;
    if (j < 0 || j >= locations.length) return;
    const next = locations.slice();
    [next[i], next[j]] = [next[j], next[i]];
    onChange(next);
  }
  function add() {
    onChange([...locations, { name: 'New city', address: 'Street\nPostal\nemail@armature.studio' }]);
  }
  return (
    <>
      <div className="adm-help">
        Studio locations shown on the contact page and as a list in the footer. Add one,
        many, or none. Use linebreaks in the address — they render as separate lines.
      </div>
      {locations.map((loc, i) => (
        <div key={i} className="adm-card">
          <div className="adm-row" style={{ alignItems: 'center' }}>
            <div style={{ flex: 1, minWidth: 0, fontFamily: '"JetBrains Mono", monospace', fontSize: 10, color: 'rgba(240,232,220,.45)', letterSpacing: '.16em', textTransform: 'uppercase' }}>
              Studio {String(i + 1).padStart(2, '0')}
            </div>
            <button className="adm-mini-x" disabled={i === 0} onClick={() => move(i, -1)} title="Move up" style={{ opacity: i === 0 ? .3 : 1 }}>↑</button>
            <button className="adm-mini-x" disabled={i === locations.length - 1} onClick={() => move(i, 1)} title="Move down" style={{ opacity: i === locations.length - 1 ? .3 : 1 }}>↓</button>
            <button className="adm-mini-x" onClick={() => remove(i)} title="Remove">×</button>
          </div>
          <div>
            <label className="adm-lbl">City / label</label>
            <input className="adm-field" value={loc.name || ''}
              placeholder="Lisbon"
              onChange={(e) => patch(i, { name: e.target.value })} />
          </div>
          <div>
            <label className="adm-lbl">Address (multi-line)</label>
            <textarea className="adm-field" rows={4} value={loc.address || ''}
              placeholder={'Rua da Boavista 84\n1200-066\nrichard@armature.studio'}
              onChange={(e) => patch(i, { address: e.target.value })} />
          </div>
        </div>
      ))}
      <button className="adm-btn" onClick={add}>+ Add studio</button>
    </>
  );
}

// ─── Services tab — categories + per-category items ────────────────────────
function ServicesTab({ services, onChange }) {
  const [expandedIdx, setExpandedIdx] = React.useState(null);

  function patch(i, p) {
    const next = services.slice();
    next[i] = { ...next[i], ...p };
    onChange(next);
  }
  function remove(i) {
    const next = services.slice();
    next.splice(i, 1);
    onChange(next);
  }
  function move(i, dir) {
    const j = i + dir;
    if (j < 0 || j >= services.length) return;
    const next = services.slice();
    [next[i], next[j]] = [next[j], next[i]];
    onChange(next);
  }
  function add() {
    const k = String(services.length + 1).padStart(2, '0');
    onChange([...services, { k, title: 'New category', tag: 'Subtitle · tags', body: 'Short description of what this practice covers.', items: [] }]);
  }
  function patchItem(catIdx, itemIdx, p) {
    const items = (services[catIdx].items || []).slice();
    items[itemIdx] = { ...items[itemIdx], ...p };
    patch(catIdx, { items });
  }
  function removeItem(catIdx, itemIdx) {
    const items = (services[catIdx].items || []).slice();
    items.splice(itemIdx, 1);
    patch(catIdx, { items });
  }
  function moveItem(catIdx, itemIdx, dir) {
    const items = (services[catIdx].items || []).slice();
    const j = itemIdx + dir;
    if (j < 0 || j >= items.length) return;
    [items[itemIdx], items[j]] = [items[j], items[itemIdx]];
    patch(catIdx, { items });
  }
  function addItem(catIdx) {
    const items = (services[catIdx].items || []).slice();
    items.push({ name: 'New offering', note: 'Short note.' });
    patch(catIdx, { items });
  }
  return (
    <>
      <div className="adm-help">
        Categories shown on the <b>Services</b> page. Add categories as the studio
        grows (e.g. <i>Photography</i>, <i>Sound</i>, <i>Production</i>) and list the
        specific offerings inside each. The Services page renders them in order.
      </div>
      {services.map((cat, i) => {
        const isOpen = expandedIdx === i;
        const items = cat.items || [];
        return (
          <div key={i} className="adm-card">
            <div className="adm-row" style={{ alignItems: 'center', cursor: 'pointer' }}
              onClick={() => setExpandedIdx(isOpen ? null : i)}>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: 12, color: '#f0e8dc' }}>{cat.k} · {cat.title}</div>
                <div style={{ fontSize: 10, color: 'rgba(240,232,220,.45)', fontFamily: '"JetBrains Mono", monospace' }}>{cat.tag} · {items.length} item{items.length === 1 ? '' : 's'}</div>
              </div>
              <button className="adm-mini-x" disabled={i === 0} onClick={(e) => { e.stopPropagation(); move(i, -1); }} title="Move up" style={{ opacity: i === 0 ? .3 : 1 }}>↑</button>
              <button className="adm-mini-x" disabled={i === services.length - 1} onClick={(e) => { e.stopPropagation(); move(i, 1); }} title="Move down" style={{ opacity: i === services.length - 1 ? .3 : 1 }}>↓</button>
              <button className="adm-mini-x" onClick={(e) => { e.stopPropagation(); remove(i); }} title="Remove">×</button>
            </div>
            {isOpen && (
              <div style={{ display: 'flex', flexDirection: 'column', gap: 8, paddingTop: 6, borderTop: '1px solid rgba(240,232,220,.08)' }}>
                <div className="adm-row grow">
                  <div style={{ flex: '0 0 60px' }}>
                    <label className="adm-lbl">Number</label>
                    <input className="adm-field" value={cat.k || ''}
                      onChange={(e) => patch(i, { k: e.target.value })} />
                  </div>
                  <div style={{ flex: 1 }}>
                    <label className="adm-lbl">Title</label>
                    <input className="adm-field" value={cat.title || ''}
                      onChange={(e) => patch(i, { title: e.target.value })} />
                  </div>
                </div>
                <div>
                  <label className="adm-lbl">Tag line</label>
                  <input className="adm-field" value={cat.tag || ''}
                    placeholder="Film · Motion · Editorial"
                    onChange={(e) => patch(i, { tag: e.target.value })} />
                </div>
                <div>
                  <label className="adm-lbl">Body description</label>
                  <textarea className="adm-field" rows={3} value={cat.body || ''}
                    onChange={(e) => patch(i, { body: e.target.value })} />
                </div>

                <div className="adm-sect" style={{ marginTop: 4 }}>Offerings ({items.length})</div>
                {items.map((it, j) => (
                  <div key={j} className="adm-card" style={{ padding: 8, gap: 6, background: 'rgba(240,232,220,.02)' }}>
                    <div className="adm-row" style={{ alignItems: 'center' }}>
                      <div style={{ flex: 1, fontFamily: '"JetBrains Mono", monospace', fontSize: 10, color: 'rgba(240,232,220,.45)', letterSpacing: '.16em', textTransform: 'uppercase' }}>
                        Offering {String(j + 1).padStart(2, '0')}
                      </div>
                      <button className="adm-mini-x" disabled={j === 0} onClick={() => moveItem(i, j, -1)} title="Move up" style={{ opacity: j === 0 ? .3 : 1 }}>↑</button>
                      <button className="adm-mini-x" disabled={j === items.length - 1} onClick={() => moveItem(i, j, 1)} title="Move down" style={{ opacity: j === items.length - 1 ? .3 : 1 }}>↓</button>
                      <button className="adm-mini-x" onClick={() => removeItem(i, j)} title="Remove">×</button>
                    </div>
                    <input className="adm-field" placeholder="Name (e.g. Direction)" value={it.name || ''}
                      onChange={(e) => patchItem(i, j, { name: e.target.value })} />
                    <input className="adm-field" placeholder="Short note shown under the name" value={it.note || ''}
                      onChange={(e) => patchItem(i, j, { note: e.target.value })} />
                  </div>
                ))}
                <button className="adm-btn ghost" onClick={() => addItem(i)}>+ Add offering</button>
              </div>
            )}
          </div>
        );
      })}
      <button className="adm-btn" onClick={add}>+ Add category</button>
    </>
  );
}

// ─── Team tab — portrait + email + per-member media ────────────────────────
function TeamTab({ team, onChange }) {
  const [mediaMemberId, setMediaMemberId] = React.useState(null);

  function patch(i, p) {
    const next = team.slice();
    next[i] = { ...next[i], ...p };
    onChange(next);
  }
  function onFile(i, file) {
    if (!file) return;
    const r = new FileReader();
    r.onload = () => patch(i, { portrait: String(r.result) });
    r.readAsDataURL(file);
  }
  function setMemberProjects(i, list) { patch(i, { projects: list }); }
  function setMemberPhotos(i, list)   { patch(i, { photos:   list }); }

  const mediaMember = mediaMemberId ? team.find(m => m.id === mediaMemberId) : null;
  const mediaIndex = mediaMember ? team.findIndex(m => m.id === mediaMemberId) : -1;

  return (
    <>
      <div className="adm-help">
        Upload a portrait, set the email (used by <b>LET'S WORK</b>), and manage
        videos + photos from the media panel.
      </div>
      {team.map((m, i) => (
        <div key={m.id} className="adm-card">
          <div className="adm-row" style={{ alignItems: 'flex-start' }}>
            <label className="adm-img-slot portrait has-img"
              style={{
                width: 80, flexShrink: 0, aspectRatio: '3/4',
                backgroundImage: m.portrait ? `url(${m.portrait})` : 'none',
              }}
              title="Click to upload portrait"
            >
              {!m.portrait && 'Drop\nportrait'}
              {m.portrait && (
                <button className="clear" onClick={(e) => { e.preventDefault(); e.stopPropagation(); patch(i, { portrait: '' }); }}>clear</button>
              )}
              <input type="file" accept="image/*" style={{ display: 'none' }}
                onChange={(e) => onFile(i, e.target.files?.[0])} />
            </label>
            <div style={{ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', gap: 8 }}>
              <div>
                <label className="adm-lbl">Name</label>
                <input className="adm-field" value={m.name || ''}
                  onChange={(e) => patch(i, { name: e.target.value })} />
              </div>
              <div>
                <label className="adm-lbl">Alias (optional — shown as "aka …" on their page)</label>
                <input className="adm-field" value={m.alias || ''}
                  placeholder="RICH"
                  onChange={(e) => patch(i, { alias: e.target.value })} />
              </div>
              <div>
                <label className="adm-lbl">Title</label>
                <input className="adm-field" value={m.title || ''}
                  onChange={(e) => patch(i, { title: e.target.value })} />
              </div>
              <div>
                <label className="adm-lbl">Email (LET'S WORK button copies this)</label>
                <input className="adm-field" type="email" value={m.email || ''}
                  placeholder="name@armature.studio"
                  onChange={(e) => patch(i, { email: e.target.value })} />
              </div>
              <div>
                <label className="adm-lbl">Link (optional — small ↗ on their tile opens this in a new tab)</label>
                <input className="adm-field" type="url" value={m.link || ''}
                  placeholder="https://instagram.com/…"
                  onChange={(e) => patch(i, { link: e.target.value })} />
              </div>
              <div>
                <label className="adm-lbl">Short bio</label>
                <textarea className="adm-field" value={m.bio || ''} rows={3}
                  onChange={(e) => patch(i, { bio: e.target.value })} />
              </div>
            </div>
          </div>

          <div className="adm-row" style={{ flexWrap: 'wrap' }}>
            <button className="adm-btn ghost" onClick={() => setMediaMemberId(m.id)}>
              Manage media · {(m.projects || []).length} videos · {(m.photos || []).length} photos
            </button>
          </div>
        </div>
      ))}

      {mediaMember && (
        <MemberMediaModal
          member={mediaMember}
          onProjects={(list) => setMemberProjects(mediaIndex, list)}
          onPhotos={(list) => setMemberPhotos(mediaIndex, list)}
          onClose={() => setMediaMemberId(null)}
        />
      )}
    </>
  );
}

// Photography editor — paste external URLs (Cloudinary, S3, Imgur, etc.), upload
// files, or bulk-paste a list of image URLs. Each entry: { id, src, caption, year, alt }.
function MemberPhotos({ photos, onChange }) {
  const [url, setUrl] = React.useState('');
  const [bulkUrl, setBulkUrl] = React.useState('');
  const [err, setErr] = React.useState('');
  const fileRef = React.useRef(null);

  function addUrl() {
    setErr('');
    const v = url.trim();
    if (!v) return;
    if (!/^https?:\/\//i.test(v)) {
      setErr('Use an https:// URL — e.g. Cloudinary, S3, ImageKit, Bunny CDN.');
      return;
    }
    onChange([...photos, { id: 'ph_' + Date.now().toString(36), src: v, caption: '', year: new Date().getFullYear(), alt: '' }]);
    setUrl('');
  }
  function addBulkUrls() {
    setErr('');
    const raw = bulkUrl.trim();
    if (!raw) return;
    const lines = raw.split(/[\n,]+/).map(s => s.trim()).filter(Boolean);
    const valid = [];
    const bad = [];
    lines.forEach((line) => {
      if (/^https?:\/\//i.test(line)) valid.push(line);
      else bad.push(line);
    });
    if (!valid.length) { setErr('No valid URLs found. Use https:// links.'); return; }
    const added = valid.map((src, i) => ({
      id: 'ph_' + Date.now().toString(36) + '_' + i,
      src,
      caption: '',
      year: new Date().getFullYear(),
      alt: '',
    }));
    onChange([...photos, ...added]);
    setBulkUrl('');
    if (bad.length) setErr('Skipped ' + bad.length + ' invalid line(s). Added ' + valid.length + '.');
  }
  function addFile(files) {
    setErr('');
    if (!files || !files.length) return;
    const list = Array.from(files);
    let pending = list.length;
    const added = [];
    list.forEach((file, k) => {
      if (!file.type.startsWith('image/')) { pending--; return; }
      const r = new FileReader();
      r.onload = () => {
        added.push({
          id: 'ph_' + Date.now().toString(36) + '_' + k,
          src: String(r.result), caption: '', year: new Date().getFullYear(), alt: '',
        });
        pending--;
        if (pending === 0) {
          onChange([...photos, ...added]);
          if (fileRef.current) fileRef.current.value = '';
        }
      };
      r.readAsDataURL(file);
    });
  }
  function patchEntry(j, p) {
    const next = photos.slice();
    next[j] = { ...next[j], ...p };
    onChange(next);
  }
  function remove(j) {
    const next = photos.slice();
    next.splice(j, 1);
    onChange(next);
  }
  function move(j, dir) {
    const k = j + dir;
    if (k < 0 || k >= photos.length) return;
    const next = photos.slice();
    [next[j], next[k]] = [next[k], next[j]];
    onChange(next);
  }
  return (
    <>
      <div className="adm-help">
        Paste external image URLs (recommended — keeps the site small) or upload from disk.
        Photographer / hybrid shooter members get a <b>Photography</b> grid on their page.
      </div>

      {/* Bulk add — keeps the admin clean when adding many photos at once. */}
      <div className="adm-sect">Bulk add URLs</div>
      <textarea
        className="adm-field"
        rows={4}
        value={bulkUrl}
        onChange={(e) => setBulkUrl(e.target.value)}
        placeholder={'Paste multiple image URLs (one per line or comma-separated)\nhttps://res.cloudinary.com/…/photo1.jpg\nhttps://res.cloudinary.com/…/photo2.jpg'}
      />
      <button className="adm-btn" disabled={!bulkUrl.trim()} onClick={addBulkUrls}>
        + Add {bulkUrl.split(/[\n,]+/).filter(s => s.trim()).length || ''} URL(s)
      </button>

      <div className="adm-sect">One by one</div>
      <div className="adm-row grow" style={{ marginTop: 4 }}>
        <input className="adm-field" placeholder="https://res.cloudinary.com/…/photo.jpg"
          value={url} onChange={(e) => setUrl(e.target.value)}
          onKeyDown={(e) => { if (e.key === 'Enter') addUrl(); }} />
        <button className="adm-btn" disabled={!url.trim()} onClick={addUrl} style={{ flex: 'none' }}>+ Add URL</button>
      </div>
      <div className="adm-row">
        <input ref={fileRef} type="file" accept="image/*" multiple
          onChange={(e) => addFile(e.target.files)}
          style={{ all: 'unset', display: 'block', color: '#f0e8dc', fontSize: 11, flex: 1 }} />
      </div>

      {photos.length > 0 && (
        <>
          <div className="adm-sect">Photos ({photos.length})</div>
          {photos.map((p, j) => (
            <div key={p.id || j} className="adm-card" style={{ padding: 8, gap: 6 }}>
              <div className="adm-row" style={{ alignItems: 'flex-start' }}>
                <div style={{
                  width: 80, height: 80, flexShrink: 0, borderRadius: 2,
                  background: p.src ? `#0a0606 url(${p.src}) center/cover no-repeat` : 'rgba(240,232,220,.06)',
                  border: '1px solid rgba(240,232,220,.08)',
                }} />
                <div style={{ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', gap: 6 }}>
                  <input className="adm-field" placeholder="Caption (shown under photo on hover)"
                    value={p.caption || ''}
                    onChange={(e) => patchEntry(j, { caption: e.target.value })} />
                  <div className="adm-row grow">
                    <input className="adm-field" placeholder="Year" type="number"
                      value={p.year || ''}
                      onChange={(e) => patchEntry(j, { year: Number(e.target.value) || p.year })} />
                    <input className="adm-field" placeholder="alt text (a11y)"
                      value={p.alt || ''}
                      onChange={(e) => patchEntry(j, { alt: e.target.value })} />
                  </div>
                </div>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
                  <button className="adm-mini-x" disabled={j === 0} onClick={() => move(j, -1)} title="Move up" style={{ opacity: j === 0 ? .3 : 1 }}>↑</button>
                  <button className="adm-mini-x" disabled={j === photos.length - 1} onClick={() => move(j, 1)} title="Move down" style={{ opacity: j === photos.length - 1 ? .3 : 1 }}>↓</button>
                  <button className="adm-mini-x" onClick={() => remove(j)} title="Remove">×</button>
                </div>
              </div>
            </div>
          ))}
        </>
      )}

      {err && <div style={{ color: '#e8a87c', fontSize: 11 }}>{err}</div>}
    </>
  );
}

// Member video editor — paste a Vimeo URL, fetch metadata, push an inline
// project entry into member.projects with a custom role. Inline entries can
// also have their title and client overridden so the auto-fetched values
// don't lock you to the original uploader name.
function MemberVideos({ member, onChange }) {
  const [url, setUrl] = React.useState('');
  const [role, setRole] = React.useState('Director');
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState('');

  const list = member.projects || [];

  async function add() {
    if (!url.trim()) return;
    setErr(''); setBusy(true);
    try {
      const meta = await fetchVideoMeta(url);
      const entry = {
        id: 'mv_' + meta.videoId + '_' + Date.now().toString(36),
        source: meta.source,
        videoId: meta.videoId,
        vimeoId:   meta.source === 'vimeo'   ? meta.videoId : undefined,
        youtubeId: meta.source === 'youtube' ? meta.videoId : undefined,
        title: meta.title,
        thumb: meta.thumb,
        runtime: meta.runtime,
        client: meta.author || '—',
        year: meta.year,
        brand: 'CINESTEEL',
        tag: 'Film',
        role: role || 'Director',
        hue: Math.floor(Math.random() * 30),
      };
      onChange([...list, entry]);
      setUrl(''); setRole('Director');
    } catch (e) {
      setErr(e.message || 'Failed to fetch');
    }
    setBusy(false);
  }

  function patchEntry(j, p) {
    const next = list.slice();
    next[j] = { ...next[j], ...p };
    onChange(next);
  }
  function remove(j) {
    const next = list.slice();
    next.splice(j, 1);
    onChange(next);
  }
  function move(j, dir) {
    const k = j + dir;
    if (k < 0 || k >= list.length) return;
    const next = list.slice();
    [next[j], next[k]] = [next[k], next[j]];
    onChange(next);
  }

  return (
    <>
      <div className="adm-help">
        Add a Vimeo or YouTube URL → title &amp; thumbnail auto-fill. Set the member's role on that film.
        For inline entries you can also override the title and client/brand name.
      </div>

      {list.map((p, j) => {
        const isInline = !!(p.vimeoId || p.youtubeId || p.videoId);
        const roleVal = p.role || p.memberRole || '—';
        const thumb = p.thumb || '';
        return (
          <div key={p.id || (p.projectId + '_' + j)} className="adm-card" style={{ padding: 8, gap: 6 }}>
            <div className="adm-row" style={{ alignItems: 'center' }}>
              <div style={{
                width: 80, height: 48, flexShrink: 0,
                background: thumb ? `url(${thumb}) center/cover` : 'rgba(168,32,44,.18)',
              }} />
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: 11, color: 'rgba(240,232,220,.45)', fontFamily: '"JetBrains Mono", monospace' }}>
                  {isInline ? 'inline video' : 'seed · ' + p.projectId}
                </div>
              </div>
              <button className="adm-mini-x" disabled={j === 0} onClick={() => move(j, -1)} style={{ opacity: j === 0 ? .3 : 1 }} title="Move up">↑</button>
              <button className="adm-mini-x" disabled={j === list.length - 1} onClick={() => move(j, 1)} style={{ opacity: j === list.length - 1 ? .3 : 1 }} title="Move down">↓</button>
              <button className="adm-mini-x" onClick={() => remove(j)} title="Remove">×</button>
            </div>
            <div className="adm-row grow">
              <div style={{ flex: 1 }}>
                <label className="adm-lbl">Brand (hover badge)</label>
                <input className="adm-field" value={p.brand || ''}
                  placeholder="CINESTEEL / FULLMETAL / …"
                  onChange={(e) => patchEntry(j, { brand: e.target.value })} />
              </div>
              <div style={{ flex: 1 }}>
                <label className="adm-lbl">Tag</label>
                <input className="adm-field" value={p.tag || ''}
                  placeholder="Film / Spot / …"
                  onChange={(e) => patchEntry(j, { tag: e.target.value })} />
              </div>
            </div>
            <div className="adm-row grow">
              <div style={{ flex: 1 }}>
                <label className="adm-lbl">Title</label>
                <input className="adm-field" value={p.customTitle || p.title || ''}
                  placeholder={p.title || 'Title'}
                  onChange={(e) => patchEntry(j, { customTitle: e.target.value })} />
              </div>
              <div style={{ flex: 1 }}>
                <label className="adm-lbl">Client</label>
                <input className="adm-field" value={p.customClient || p.client || ''}
                  placeholder={p.client || 'Client'}
                  onChange={(e) => patchEntry(j, { customClient: e.target.value })} />
              </div>
            </div>
            <div className="adm-row grow">
              <div style={{ flex: 1 }}>
                <label className="adm-lbl">Role on this film</label>
                <input className="adm-field" value={roleVal}
                  onChange={(e) => patchEntry(j, { role: e.target.value })} />
              </div>
              <div style={{ flex: '0 0 80px' }}>
                <label className="adm-lbl">Year</label>
                <input className="adm-field" type="number" value={p.year || ''}
                  onChange={(e) => patchEntry(j, { year: Number(e.target.value) || p.year })} />
              </div>
            </div>
          </div>
        );
      })}

      <div className="adm-sect" style={{ marginTop: 8 }}>Add video</div>
      <div className="adm-row grow">
        <input className="adm-field" placeholder="vimeo.com/… or youtube.com/watch?v=…"
          value={url} onChange={(e) => setUrl(e.target.value)}
          onKeyDown={(e) => { if (e.key === 'Enter') add(); }} />
      </div>
      <div className="adm-row grow">
        <input className="adm-field" placeholder="Role on this film (e.g. Director · DP)"
          value={role} onChange={(e) => setRole(e.target.value)}
          onKeyDown={(e) => { if (e.key === 'Enter') add(); }} />
        <button className="adm-btn" disabled={busy || !url.trim()} onClick={add} style={{ flex: 'none' }}>
          {busy ? 'Fetching…' : '+ Add'}
        </button>
      </div>
      {err && <div style={{ color: '#e8a87c', fontSize: 11 }}>{err}</div>}
    </>
  );
}

// ─── Media manager modal — contained overlay for videos + photos ───────────
function MemberMediaModal({ member, onProjects, onPhotos, onClose }) {
  const [tab, setTab] = React.useState('videos');
  return (
    <div style={{
      position: 'fixed', inset: 0, zIndex: 2147483647,
      background: 'rgba(10,6,6,.92)',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      padding: '24px 18px',
    }}>
      <div style={{
        width: '100%', maxWidth: 560, maxHeight: '90vh',
        background: '#0a0606', border: '1px solid rgba(240,232,220,.12)',
        display: 'flex', flexDirection: 'column', borderRadius: 2,
        boxShadow: '0 24px 64px rgba(0,0,0,.6)',
      }}>
        <div style={{
          display: 'flex', alignItems: 'center', justifyContent: 'space-between',
          padding: '12px 14px', borderBottom: '1px solid rgba(240,232,220,.10)',
          flexShrink: 0,
        }}>
          <div>
            <div style={{ fontFamily: '"Instrument Serif", serif', fontSize: 18, color: '#f0e8dc' }}>{member.name}</div>
            <div style={{ fontFamily: '"JetBrains Mono", monospace', fontSize: 9, letterSpacing: '.18em', color: 'rgba(240,232,220,.45)', textTransform: 'uppercase', marginTop: 2 }}>Manage media</div>
          </div>
          <button onClick={onClose} style={{ all: 'unset', cursor: 'pointer', color: 'rgba(240,232,220,.5)', fontSize: 18, padding: '4px 8px' }}>✕</button>
        </div>

        <div style={{ display: 'flex', borderBottom: '1px solid rgba(240,232,220,.10)', flexShrink: 0 }}>
          {['videos', 'photos'].map((k) => (
            <button key={k}
              onClick={() => setTab(k)}
              style={{
                all: 'unset', cursor: 'pointer', flex: 1, textAlign: 'center',
                padding: '10px 6px', fontFamily: '"JetBrains Mono", monospace', fontSize: 10,
                letterSpacing: '.14em', textTransform: 'uppercase',
                color: tab === k ? '#f0e8dc' : 'rgba(240,232,220,.45)',
                borderBottom: '2px solid ' + (tab === k ? '#a8202c' : 'transparent'),
              }}
            >{k}</button>
          ))}
        </div>

        <div style={{ flex: 1, overflowY: 'auto', overflowX: 'hidden', padding: 14, display: 'flex', flexDirection: 'column', gap: 12 }}>
          {tab === 'videos' && (
            <MemberVideos member={member} onChange={onProjects} />
          )}
          {tab === 'photos' && (
            <MemberPhotos photos={member.photos || []} onChange={onPhotos} />
          )}
        </div>
      </div>
    </div>
  );
}

// ─── Clients tab — Creators + Brands + Managed + Sub Brands, with logo upload ─────────────────────
function ClientsTab({ creators, brands, managed, subBrandClients, onCreators, onBrands, onManaged, onSubBrandClients }) {
  return (
    <>
      <div className="adm-sect">Creators</div>
      <div className="adm-help">Independents you collaborate with.</div>
      <ClientListEditor items={creators} onChange={onCreators} kind="creator" />
      <div className="adm-sect">Brands</div>
      <div className="adm-help">Brands and agencies you've made work for.</div>
      <ClientListEditor items={brands} onChange={onBrands} kind="brand" />
      <div className="adm-sect">Managed</div>
      <div className="adm-help">Brands you help run — end-to-end production, Shopify, operations.</div>
      <ClientListEditor items={managed} onChange={onManaged} kind="managed" />
      <div className="adm-sect">Sub Brands</div>
      <div className="adm-help">Fully owned and operated — marketing to shipping.</div>
      <ClientListEditor items={subBrandClients} onChange={onSubBrandClients} kind="sub brand" />
    </>
  );
}

function ClientListEditor({ items, onChange, kind }) {
  function patch(i, p) {
    const next = items.slice();
    next[i] = { ...next[i], ...p };
    onChange(next);
  }
  function add() {
    onChange([...items, { name: '', logo: '', link: '' }]);
  }
  function remove(i) {
    const next = items.slice();
    next.splice(i, 1);
    onChange(next);
  }
  function onFile(i, file) {
    if (!file) return;
    const r = new FileReader();
    r.onload = () => patch(i, { logo: String(r.result) });
    r.readAsDataURL(file);
  }
  return (
    <>
      {items.map((c, i) => (
        <div key={i} className="adm-card">
          <div className="adm-row" style={{ alignItems: 'flex-start' }}>
            <label className="adm-img-slot logo has-img"
              style={{
                width: 80, flexShrink: 0, aspectRatio: '1',
                backgroundImage: c.logo ? `url(${c.logo})` : 'none',
                backgroundColor: c.logo ? '#0a0606' : 'rgba(240,232,220,.05)',
                backgroundSize: 'contain',
              }}
              title="Click to upload logo"
            >
              {!c.logo && 'Drop\nlogo'}
              {c.logo && (
                <button className="clear" onClick={(e) => { e.preventDefault(); e.stopPropagation(); patch(i, { logo: '' }); }}>clear</button>
              )}
              <input type="file" accept="image/*" style={{ display: 'none' }}
                onChange={(e) => onFile(i, e.target.files?.[0])} />
            </label>
            <div style={{ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', gap: 8 }}>
              <input className="adm-field" placeholder="Name"
                value={c.name || ''} onChange={(e) => patch(i, { name: e.target.value })} />
              <input className="adm-field" placeholder="https:// link"
                value={c.link || ''} onChange={(e) => patch(i, { link: e.target.value })} />
              <div className="adm-row" style={{ justifyContent: 'flex-end' }}>
                <button className="adm-btn ghost" style={{ padding: '6px 10px' }}
                  onClick={() => remove(i)}>Remove</button>
              </div>
            </div>
          </div>
        </div>
      ))}
      <button className="adm-btn ghost" onClick={add}>+ Add {kind}</button>
    </>
  );
}

// ─── Links tab — Store + sub-brand URLs ────────────────────────────────────
function LinksTab({ links, onChange }) {
  function setLink(k, v) { onChange({ ...links, [k]: v }); }
  const rows = [
    ['store',     'Store',         'Shopify URL — used by the STORE nav link & footer'],
    ['fullmetal', 'FULLMETAL',     'Sub-brand link in nav, hero strip, about, footer'],
    ['cinesteel', 'CINESTEEL',     'Sub-brand link in nav, hero strip, about, footer'],
    ['instagram', 'Instagram',     'Used on the Contact page'],
  ];
  return (
    <>
      <div className="adm-help">
        Change the outbound URL behind each nav link / footer link. Leave blank to use the default.
      </div>
      {rows.map(([k, label, help]) => (
        <div key={k} className="adm-card">
          <label className="adm-lbl">{label}</label>
          <input className="adm-field" value={links[k] || ''}
            placeholder={NAV_LINKS[k] || 'https://'}
            onChange={(e) => setLink(k, e.target.value)} />
          <div className="adm-help" style={{ marginTop: 0 }}>{help}</div>
        </div>
      ))}
    </>
  );
}

// ─── Look tab — site logo + accent + density ───────────────────────────────
function LookTab({ store, set }) {
  const palette = ['#a8202c', '#6b0d14', '#d92435', '#e8a87c', '#f0e8dc'];
  function onLogoFile(file) {
    if (!file) return;
    const r = new FileReader();
    r.onload = () => set('siteLogo', String(r.result));
    r.readAsDataURL(file);
  }
  return (
    <>
      <div className="adm-sect">Site logo</div>
      <div className="adm-help">
        Replaces the <b>ARMATURE</b> wordmark in the top-left of the nav and the footer.
        Use a transparent PNG / SVG. Leave empty to fall back to the wordmark.
      </div>
      <label className="adm-img-slot has-img"
        style={{
          width: '100%', aspectRatio: '16/5',
          backgroundImage: store.siteLogo ? `url(${store.siteLogo})` : 'none',
          backgroundColor: '#0a0606',
          backgroundSize: 'contain',
        }}
      >
        {!store.siteLogo && 'Click to upload logo'}
        {store.siteLogo && (
          <button className="clear" onClick={(e) => { e.preventDefault(); e.stopPropagation(); set('siteLogo', ''); }}>clear</button>
        )}
        <input type="file" accept="image/*,image/svg+xml" style={{ display: 'none' }}
          onChange={(e) => onLogoFile(e.target.files?.[0])} />
      </label>

      <div className="adm-sect">Accent color</div>
      <div className="adm-row" style={{ flexWrap: 'wrap', gap: 6 }}>
        {palette.map(c => (
          <button key={c} onClick={() => set('accent', c)} style={{
            all: 'unset', cursor: 'pointer',
            width: 36, height: 36, borderRadius: 2,
            background: c,
            outline: store.accent === c ? '2px solid #f0e8dc' : '1px solid rgba(240,232,220,.18)',
            outlineOffset: 1,
          }} title={c} />
        ))}
      </div>
      <input className="adm-field" value={store.accent}
        onChange={(e) => set('accent', e.target.value)}
        placeholder="#a8202c" />

      <div className="adm-sect">Grid density</div>
      <div className="adm-row">
        {['regular', 'compact'].map(d => (
          <button key={d}
            className={'adm-btn ' + (store.density === d ? '' : 'ghost')}
            style={{ flex: 1 }}
            onClick={() => set('density', d)}>{d}</button>
        ))}
      </div>

      <div className="adm-sect">Custom fonts</div>
      <div className="adm-help">
        Upload <code>.ttf</code>, <code>.otf</code>, <code>.woff</code>, or <code>.woff2</code>.
        The font is embedded in your site data, registered with @font-face on every load,
        and appears in the inline editor's font dropdown.
      </div>
      <FontsUploader
        fonts={store.customFonts || {}}
        onChange={(v) => set('customFonts', v)}
      />
    </>
  );
}

function FontsUploader({ fonts, onChange }) {
  const [name, setName] = React.useState('');
  const [pendingFile, setPendingFile] = React.useState(null);
  const [err, setErr] = React.useState('');
  const fileRef = React.useRef(null);

  function defaultNameFor(file) {
    return (file.name || 'Font')
      .replace(/\.(ttf|otf|woff2?|TTF|OTF|WOFF2?)$/, '')
      .replace(/[-_]+/g, ' ')
      .replace(/\s+/g, ' ')
      .trim();
  }
  function pick(file) {
    setErr('');
    if (!file) return;
    if (!/\.(ttf|otf|woff2?|TTF|OTF|WOFF2?)$/.test(file.name)) {
      setErr('Unsupported file. Use .ttf, .otf, .woff, or .woff2.');
      return;
    }
    setPendingFile(file);
    if (!name) setName(defaultNameFor(file));
  }
  function commit() {
    if (!pendingFile || !name.trim()) { setErr('Pick a file and give it a name.'); return; }
    const r = new FileReader();
    r.onload = () => {
      onChange({ ...(fonts || {}), [name.trim()]: String(r.result) });
      setPendingFile(null);
      setName('');
      if (fileRef.current) fileRef.current.value = '';
    };
    r.readAsDataURL(pendingFile);
  }
  function remove(key) {
    const next = { ...(fonts || {}) };
    delete next[key];
    onChange(next);
  }
  const names = Object.keys(fonts || {});
  return (
    <>
      {names.length > 0 && names.map((k) => (
        <div key={k} className="adm-row" style={{ alignItems: 'center', padding: 8, background: 'rgba(240,232,220,.03)', border: '1px solid rgba(240,232,220,.08)', borderRadius: 2 }}>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 18, fontFamily: `"${k}", serif` }}>{k}</div>
            <div style={{ fontSize: 10, color: 'rgba(240,232,220,.45)', fontFamily: '"JetBrains Mono", monospace', letterSpacing: '.14em', textTransform: 'uppercase' }}>
              Aa Bb Cc 123 — preview
            </div>
          </div>
          <button className="adm-mini-x" onClick={() => remove(k)} title="Remove">×</button>
        </div>
      ))}
      <div className="adm-card" style={{ padding: 10, gap: 6, marginTop: 4 }}>
        <label className="adm-lbl">Font display name</label>
        <input className="adm-field" value={name} onChange={(e) => setName(e.target.value)}
          placeholder="e.g. Helvetica Neue, Garamond Premier…" />
        <label className="adm-lbl" style={{ marginTop: 4 }}>Font file</label>
        <input ref={fileRef} type="file" accept=".ttf,.otf,.woff,.woff2,font/ttf,font/otf,font/woff,font/woff2,application/font-sfnt,application/font-woff"
          onChange={(e) => pick(e.target.files?.[0])}
          style={{ all: 'unset', display: 'block', color: '#f0e8dc', fontSize: 11 }} />
        {err && <div style={{ color: '#e8a87c', fontSize: 11 }}>{err}</div>}
        <button className="adm-btn" disabled={!pendingFile || !name.trim()} onClick={commit}>
          {pendingFile ? `Add "${name || '…'}"` : 'Pick a font file first'}
        </button>
        <div className="adm-help" style={{ marginTop: 0 }}>
          Tip: macOS system fonts already work without uploading — just pick them from the
          inline editor's font dropdown.
        </div>
      </div>
    </>
  );
}

// ─── Data tab — export / import / reset ────────────────────────────────────
function DataTab({ store, replaceAll, exportJSON, importJSON, reset, copyShareLink }) {
  const fileRef = React.useRef(null);
  const [pubPw, setPubPw] = React.useState('');
  const [pubStatus, setPubStatus] = React.useState('');
  const [pubBusy, setPubBusy] = React.useState(false);
  const [published, setPublished] = React.useState(() => PUBLISHED_REF.current);

  // Refresh the published snapshot if it arrives after mount.
  React.useEffect(() => {
    if (published) return;
    const t = setInterval(() => {
      if (PUBLISHED_REF.current) { setPublished(PUBLISHED_REF.current); clearInterval(t); }
    }, 500);
    return () => clearInterval(t);
  }, [published]);

  // Deep-ish equality on the slices that matter for "in sync".
  const inSync = React.useMemo(() => {
    if (!published) return null;
    try { return JSON.stringify(stripVolatile(store)) === JSON.stringify(stripVolatile(published)); }
    catch { return false; }
  }, [store, published]);

  async function doPublish() {
    setPubBusy(true); setPubStatus('Publishing…');
    try {
      await publishStore(store, pubPw);
      PUBLISHED_REF.current = JSON.parse(JSON.stringify(store));
      setPublished(PUBLISHED_REF.current);
      setPubStatus('Published ✓');
      setPubPw('');
    } catch (e) {
      setPubStatus('Failed: ' + (e.message || 'unknown'));
    } finally {
      setPubBusy(false);
    }
  }
  async function doPull() {
    if (!confirm('Discard local drafts and reload the published state from the live site?')) return;
    const remote = await fetchPublishedStore();
    if (!remote) { setPubStatus('Nothing published yet'); return; }
    PUBLISHED_REF.current = remote;
    setPublished(remote);
    replaceAll(remote);
    setPubStatus('Pulled live state ↓');
  }

  return (
    <>
      <div className="adm-sect">Publish to live site</div>
      <div className="adm-help">
        Edits stay in <b>this browser</b> as a draft until you publish. Publishing pushes the
        current state to the live site so all visitors see it. Requires the publish password
        you set in your Cloudflare Pages environment variables (<code>PUBLISH_PASSWORD</code>).
      </div>
      <div style={{
        display: 'flex', alignItems: 'center', gap: 8, marginTop: 8, marginBottom: 8,
        fontFamily: '"JetBrains Mono", monospace', fontSize: 10.5, letterSpacing: '.14em', textTransform: 'uppercase',
      }}>
        {published === null && <span style={{ color: '#a89880' }}>● Checking live site…</span>}
        {published !== null && inSync === true && <span style={{ color: '#7fb274' }}>● In sync</span>}
        {published !== null && inSync === false && <span style={{ color: '#e8a87c' }}>● Draft has unpublished changes</span>}
      </div>
      <label className="adm-lbl">Publish password</label>
      <input className="adm-field" type="password" value={pubPw}
        onChange={(e) => setPubPw(e.target.value)}
        placeholder="••••••"
        onKeyDown={(e) => { if (e.key === 'Enter' && pubPw) doPublish(); }} />
      <button className="adm-btn" onClick={doPublish} disabled={!pubPw || pubBusy}>
        {pubBusy ? 'Publishing…' : 'Publish to live site ↑'}
      </button>
      <button className="adm-btn ghost" onClick={doPull} disabled={pubBusy}>Pull live state ↓</button>
      {pubStatus && <div style={{ fontSize: 11, color: '#a89880', marginTop: 6 }}>{pubStatus}</div>}

      <div className="adm-sect">Backup &amp; sync between devices</div>
      <div className="adm-help">
        Export the full store as JSON for a manual backup or to move drafts between devices
        without publishing.
      </div>
      <button className="adm-btn ghost" onClick={exportJSON}>Export JSON ↓</button>
      <button className="adm-btn ghost" onClick={() => fileRef.current?.click()}>Import JSON ↑</button>
      <input ref={fileRef} type="file" accept="application/json,.json"
        style={{ display: 'none' }}
        onChange={(e) => {
          const f = e.target.files?.[0];
          if (f) importJSON(f);
          e.target.value = '';
        }}
      />

      <div className="adm-sect">Visitor link</div>
      <div className="adm-help">Share this URL with people. The admin panel is hidden unless they add ?admin=1.</div>
      <button className="adm-btn ghost" onClick={copyShareLink}>Copy visitor URL</button>

      <div className="adm-sect">Reset</div>
      <button className="adm-btn ghost" onClick={reset}>Reset all edits</button>
    </>
  );
}

// Strip non-content keys before comparing local draft vs. published — passwords,
// transient flags, etc. that we never want to count as "out of sync".
function stripVolatile(s) {
  if (!s || typeof s !== 'object') return s;
  const { adminPassword, sitePassword, sitePasswordEnabled, ...rest } = s;
  return rest;
}

function AccessTab({ store, set }) {
  const [adminPw, setAdminPw] = React.useState('');
  const [adminConfirm, setAdminConfirm] = React.useState('');
  const [sitePw, setSitePw] = React.useState('');
  const [msg, setMsg] = React.useState('');

  function updateAdmin() {
    if (adminPw !== adminConfirm) { setMsg('Admin passwords do not match'); return; }
    if (!adminPw) { setMsg('Admin password cannot be empty'); return; }
    set('adminPassword', adminPw);
    setAdminPw(''); setAdminConfirm('');
    setMsg('Admin password updated');
  }
  function updateSite() {
    if (store.sitePasswordEnabled && !sitePw && !store.sitePassword) { setMsg('Site password cannot be empty when enabled'); return; }
    if (sitePw) set('sitePassword', sitePw);
    setSitePw('');
    setMsg('Site password updated');
  }

  return (
    <>
      <div className="adm-sect">Admin access</div>
      <div className="adm-help">The admin panel is always password protected. Change the password below.</div>
      <label className="adm-lbl">New admin password</label>
      <input className="adm-field" type="password" value={adminPw} onChange={e => setAdminPw(e.target.value)} placeholder="••••••" />
      <label className="adm-lbl">Confirm admin password</label>
      <input className="adm-field" type="password" value={adminConfirm} onChange={e => setAdminConfirm(e.target.value)} placeholder="••••••" />
      <button className="adm-btn" onClick={updateAdmin} disabled={!adminPw || !adminConfirm}>Update admin password</button>

      <div className="adm-sect" style={{ marginTop: 14 }}>Site-wide protection</div>
      <div className="adm-help">Require a password for all visitors to view the site.</div>
      <div className="adm-row" style={{ alignItems: 'center', gap: 10 }}>
        <button
          className={'adm-btn ' + (store.sitePasswordEnabled ? '' : 'ghost')}
          onClick={() => set('sitePasswordEnabled', true)}
          style={{ flex: 1 }}
        >On</button>
        <button
          className={'adm-btn ' + (!store.sitePasswordEnabled ? '' : 'ghost')}
          onClick={() => set('sitePasswordEnabled', false)}
          style={{ flex: 1 }}
        >Off</button>
      </div>

      {store.sitePasswordEnabled && (
        <>
          <label className="adm-lbl" style={{ marginTop: 10 }}>Site password</label>
          <input className="adm-field" type="password" value={sitePw} onChange={e => setSitePw(e.target.value)} placeholder="••••••" />
          <button className="adm-btn" onClick={updateSite} disabled={!sitePw && !store.sitePassword}>Update site password</button>
        </>
      )}

      {msg && <div style={{ color: '#e8a87c', fontSize: 11, marginTop: 4 }}>{msg}</div>}
    </>
  );
}

Object.assign(window, {
  ARMATURE_STORE_KEY, ARMATURE_DEFAULTS,
  isAdminMode, loadStore, persistStore, useArmatureStore,
  effectiveTeam, effectiveCreators, effectiveBrands, effectiveLinks, effectiveText,
  effectiveSubBrands, effectiveServices, effectiveLocations, effectiveManaged, effectiveSubBrandClients,
  LOCATION_SEED, MANAGED_SEED, SUB_BRAND_CLIENTS_SEED,
  EditCtx, E, InlineEditorToolbar, FontRegistrar,
  AdminPanel, PasswordGate, AccessTab, MediaTab, MediaRow, TeamMediaPopup,
});
