/* Studio 76 Atelier — Apparel Bench. Upload a garment photo (or a sketch) → AI spec,
   technical flat / tech-pack, front·back·detail plate, coloured illustration, and "see it worn"
   on a model (Instagram-ready), plus variations, reforms and guided edits.
   Talks only to apparel.php (keys server-side). */

const WEAR = [
  { k: 'full',   label: 'Full length' },
  { k: 'runway', label: 'Runway' },
  { k: 'half',   label: 'Waist up' },
  { k: 'back',   label: 'Back view' },
  { k: 'seated', label: 'Seated' },
  { k: 'detail', label: 'Fabric detail' },
  { k: 'street', label: 'Street style' },
];

const MODELS = [
  { k: 'woman', label: 'On her' },
  { k: 'man',   label: 'On him' },
];

const LOOKS = [
  { k: 'editorial',  label: 'Editorial' },
  { k: 'indian',     label: 'Indian' },
  { k: 'millennial', label: 'Millennial' },
  { k: 'glam',       label: 'Glam' },
  { k: 'studio',     label: 'Studio' },
  { k: 'goldenhour', label: 'Golden hour' },
];

const FORMS = [
  { k: 'dress',    label: 'As a dress' },
  { k: 'top',      label: 'As a top' },
  { k: 'skirt',    label: 'As a skirt' },
  { k: 'trousers', label: 'As trousers' },
  { k: 'jacket',   label: 'As a jacket' },
  { k: 'jumpsuit', label: 'As a jumpsuit' },
];

/* Guided-edit vocab — discrete controls, each = one precise change on the zoomed render. */
const NECKLINES = ['Crew', 'V-neck', 'Square', 'Sweetheart', 'Halter', 'Boat', 'Off-shoulder', 'Cowl', 'Collared'];
const SLEEVES   = ['Sleeveless', 'Cap', 'Short', 'Three-quarter', 'Long', 'Puff', 'Bishop', 'Bell'];
const FABRICS   = ['Silk', 'Satin', 'Cotton', 'Linen', 'Velvet', 'Chiffon', 'Crepe', 'Denim', 'Wool', 'Tulle'];
const FITS      = ['Slim', 'Tailored', 'Relaxed', 'Oversized'];
const COLORS    = [
  { name: 'Ivory', hex: '#F3EEE3' }, { name: 'Black', hex: '#1B130D' }, { name: 'Emerald', hex: '#0F6B4F' },
  { name: 'Ruby red', hex: '#8E1B2E' }, { name: 'Blush', hex: '#E7C3C0' }, { name: 'Royal blue', hex: '#27408B' },
  { name: 'Sapphire navy', hex: '#1C2B4A' }, { name: 'Champagne', hex: '#E4D2A8' }, { name: 'Sage', hex: '#A7B79A' },
  { name: 'Terracotta', hex: '#C2552B' },
];
const DETAILS = [
  { k: 'belt',       op: 'add',    params: { element: 'a slim matching waist belt' },   label: '+ Belt' },
  { k: 'pockets',    op: 'add',    params: { element: 'discreet side-seam pockets' },     label: '+ Pockets' },
  { k: 'ruffle',     op: 'add',    params: { element: 'a soft ruffle along the hem' },    label: '+ Ruffle hem' },
  { k: 'sleeveless', op: 'remove', params: { element: 'the sleeves' },                    label: '– Sleeves' },
];
const SEL0 = { length: null, neckline: null, sleeve: null, fabric: null, color: null, fit: null, details: [], note: '' };
function cap1(s) { return s ? s.charAt(0).toUpperCase() + s.slice(1) : s; }

/* aspect of a generated key, for the refine (perfect-it) pass */
function aspectFor(key) {
  if (key === 'views') return '3:2';
  if (key.slice(0, 4) === 'mag_') return '3:4';
  if (key.slice(0, 2) === 'w_') return '4:5';
  return '3:4';
}

async function benchApi(action, payload) {
  const ctrl = new AbortController();
  const timer = setTimeout(() => ctrl.abort(), 95000);
  let r;
  try {
    r = await fetch('apparel.php', { method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(Object.assign({ action }, payload)), signal: ctrl.signal });
  } catch (e) {
    clearTimeout(timer);
    throw new Error(e && e.name === 'AbortError' ? 'That took too long — the studio is busy. Please try again.' : 'Network hiccup — please try again.');
  }
  clearTimeout(timer);
  let j = null; try { j = await r.json(); } catch (e) {}
  if (!r.ok || !j || j.error) throw new Error((j && j.error) || ('The studio is busy right now (' + r.status + ') — please try again.'));
  return j;
}

function downscale(file, max) {
  return new Promise((res, rej) => {
    const fr = new FileReader();
    fr.onerror = () => rej(new Error('read'));
    fr.onload = () => {
      const img = new Image();
      img.onload = () => {
        const s = Math.min(1, max / Math.max(img.width, img.height));
        const w = Math.round(img.width * s), h = Math.round(img.height * s);
        const c = document.createElement('canvas'); c.width = w; c.height = h;
        c.getContext('2d').drawImage(img, 0, 0, w, h);
        res(c.toDataURL('image/jpeg', 0.85));
      };
      img.onerror = () => rej(new Error('img'));
      img.src = fr.result;
    };
    fr.readAsDataURL(file);
  });
}

function isUrl(x) { return typeof x === 'string' && x.slice(0, 5) === 'data:'; }

function Bench() {
  const [stage, setStage] = React.useState('idle');
  const [entry, setEntry] = React.useState('piece');
  const [src, setSrc]     = React.useState(null);
  const [id, setId]       = React.useState(null);
  const [spec, setSpec]   = React.useState(null);
  const [out, setOut]     = React.useState({});
  const [look, setLook]   = React.useState('editorial');
  const [model, setModel] = React.useState('woman');
  const [shots, setShots] = React.useState([]);
  const [morphs, setMorphs] = React.useState([]);
  const [fixes, setFixes] = React.useState([]);
  const [fixN, setFixN]   = React.useState(0);
  const [varN, setVarN]   = React.useState(0);
  const [varText, setVarText] = React.useState('');
  const [saving, setSaving]   = React.useState(false);
  const [savedN, setSavedN]   = React.useState('');
  const [savedUrl, setSavedUrl] = React.useState('');
  const [drag, setDrag]   = React.useState(false);
  const [err, setErr]     = React.useState('');
  const [urlText, setUrlText] = React.useState('');
  const [modalSrc, setModalSrc] = React.useState('');
  const [modalKey, setModalKey] = React.useState('');
  const [sel, setSel]   = React.useState(SEL0);
  const [cap, setCap]   = React.useState(null);
  const [copied, setCopied] = React.useState(false);
  const fileRef = React.useRef(null);
  const savedSetRef = React.useRef(null);
  const savingRef = React.useRef(false);
  const aspectsRef = React.useRef({});

  async function ingestRealize(srcPayload) {
    const a1 = await benchApi('analyze', srcPayload);
    if (!a1.id) throw new Error('Could not read that sketch — please try again.');
    const r = await benchApi('realize', { id: a1.id });
    setSrc(r.dataUrl);
    const a2 = await benchApi('analyze', { image: r.dataUrl });
    if (!a2.id) throw new Error('Could not finish reading — please try again.');
    setId(a2.id); setSpec(a2.spec);
  }

  function resetForIngest(sketch) {
    setErr(''); setEntry(sketch ? 'sketch' : 'piece'); setStage('analyzing');
    setOut({}); setSpec(null); setId(null); setShots([]); setMorphs([]); setFixes([]); setVarN(0); setSavedUrl(''); savedSetRef.current = null;
  }

  async function handleFile(file, sketch) {
    if (!file || !/^image\//.test(file.type)) { setErr('Please choose an image file.'); return; }
    resetForIngest(sketch);
    try {
      const dataUrl = await downscale(file, 1280);
      setSrc(dataUrl);
      if (sketch) { await ingestRealize({ image: dataUrl }); }
      else { const j = await benchApi('analyze', { image: dataUrl }); if (!j.id) throw new Error('Could not read that — please try again.'); setId(j.id); setSpec(j.spec); }
      setStage('ready');
    } catch (e) { setErr(e.message || 'Could not read that image.'); setStage('idle'); }
  }

  async function handleUrl(url, sketch) {
    url = (url || '').trim();
    if (!/^https?:\/\//i.test(url)) { setErr('Paste a valid image URL.'); return; }
    resetForIngest(sketch);
    setSrc(url);
    try {
      if (sketch) { await ingestRealize({ imageUrl: url }); }
      else { const j = await benchApi('analyze', { imageUrl: url }); if (!j.id) throw new Error('Could not read that — please try again.'); setId(j.id); setSpec(j.spec); }
      setStage('ready');
    } catch (e) { setErr(e.message || 'Could not bring that image in.'); setStage('idle'); }
  }

  React.useEffect(() => {
    const u = new URLSearchParams(window.location.search).get('img');
    if (u) { setUrlText(u); handleUrl(u); }
  }, []);

  async function gen(key, action, payload) {
    if (!id) return;
    setOut(o => Object.assign({}, o, { [key]: 'loading' }));
    try { const j = await benchApi(action, Object.assign({ id }, payload)); setOut(o => Object.assign({}, o, { [key]: j.dataUrl })); }
    catch (e) { setOut(o => Object.assign({}, o, { [key]: null })); setErr(e.message || 'Generation failed.'); }
  }

  function genWear(place) {
    const w = WEAR.find(x => x.k === place), l = LOOKS.find(x => x.k === look);
    const key = 'w_' + place + '_' + look + '_' + model;
    const lookLabel = l.label + (model === 'man' ? ' · Him' : '');
    if (!shots.find(s => s.key === key)) setShots(prev => [{ key, placeLabel: w.label, lookLabel }].concat(prev));
    gen(key, 'wear', { place, look, model });
  }

  function genMagazine() {
    const l = LOOKS.find(x => x.k === look);
    const key = 'mag_' + look + '_' + model;
    const lookLabel = l.label + (model === 'man' ? ' · Him' : '');
    if (!shots.find(s => s.key === key)) setShots(prev => [{ key, placeLabel: 'Magazine cover', lookLabel }].concat(prev));
    gen(key, 'magazine', { look, model });
  }

  function genEditSet(ops, label) {
    const srcKey = modalKey, base = out[srcKey];
    if (!id || !srcKey || !isUrl(base) || !ops.length) return;
    const aspect = aspectsRef.current[srcKey] || aspectFor(srcKey);
    const n = fixN + 1; setFixN(n);
    const key = 'fix_' + n;
    aspectsRef.current[key] = aspect;
    setFixes(prev => [{ key, label }].concat(prev));
    closeModal();
    setOut(o => Object.assign({}, o, { [key]: 'loading' }));
    benchApi('edit', { id, image: base, ops, aspect })
      .then(j => setOut(o => Object.assign({}, o, { [key]: j.dataUrl })))
      .catch(e => { setOut(o => Object.assign({}, o, { [key]: null })); setErr(e.message || 'Edit failed.'); });
  }

  function applyEdits() {
    const ops = [], parts = [];
    if (sel.length)   { ops.push({ op: 'length',   params: { dir: sel.length } }); parts.push(sel.length === 'longer' ? 'Longer' : 'Shorter'); }
    if (sel.neckline) { ops.push({ op: 'neckline', params: { to: sel.neckline } }); parts.push(cap1(sel.neckline) + ' neck'); }
    if (sel.sleeve)   { ops.push({ op: 'sleeve',   params: { to: sel.sleeve } });  parts.push(cap1(sel.sleeve) + ' sleeve'); }
    if (sel.fabric)   { ops.push({ op: 'fabric',   params: { to: sel.fabric } });  parts.push(cap1(sel.fabric)); }
    if (sel.color)    { ops.push({ op: 'color',    params: { to: sel.color } });   parts.push(cap1(sel.color)); }
    if (sel.fit)      { ops.push({ op: 'fit',      params: { to: sel.fit } });     parts.push(cap1(sel.fit) + ' fit'); }
    sel.details.forEach(k => { const d = DETAILS.find(x => x.k === k); if (d) { ops.push({ op: d.op, params: d.params }); parts.push(d.label); } });
    const note = sel.note.trim();
    if (note) { ops.push({ op: 'note', params: { text: note } }); parts.push('“' + (note.length > 24 ? note.slice(0, 24) + '…' : note) + '”'); }
    if (!ops.length) return;
    let label = parts.slice(0, 3).join(' · ');
    if (parts.length > 3) label += ' +' + (parts.length - 3);
    genEditSet(ops, label);
  }

  function closeModal() { setModalSrc(''); setModalKey(''); setCap(null); setCopied(false); setSel(SEL0); }
  function capFull(c) { return (c.caption || '') + '\n\n' + (c.hashtags || []).map(h => '#' + String(h).replace(/^#/, '').replace(/\s+/g, '')).join(' '); }
  function loadCaption() {
    if (!id) return;
    setCap('loading'); setCopied(false);
    benchApi('caption', { id }).then(j => setCap(j)).catch(e => { setCap(null); setErr(e.message || 'Could not write a caption.'); });
  }
  function copyCap(c) {
    const t = capFull(c);
    const done = () => { setCopied(true); setTimeout(() => setCopied(false), 1800); };
    try { navigator.clipboard.writeText(t).then(done, done); } catch (e) { done(); }
  }

  function genVar() {
    if (!id) return;
    const n = varN + 1; setVarN(n);
    const text = varText.trim();
    const key = 'var_' + n;
    const label = text ? ('“' + (text.length > 42 ? text.slice(0, 42) + '…' : text) + '”') : ('Variation ' + n);
    setMorphs(prev => [{ key, label }].concat(prev));
    gen(key, 'variation', { n, instructions: text });
    setVarText('');
  }
  function genForm(form) {
    const f = FORMS.find(x => x.k === form), key = 'form_' + form;
    if (!morphs.find(m => m.key === key)) setMorphs(prev => [{ key, label: f.label }].concat(prev));
    gen(key, 'reform', { form });
  }

  async function save() {
    if (!id || saving || savingRef.current) return;
    savingRef.current = true;
    setSaving(true); setSavedUrl(''); setSavedN(''); setErr('');
    try {
      const start = await benchApi('save_start', { id, spec, original: isUrl(src) ? src : '', setid: savedSetRef.current || undefined });
      savedSetRef.current = start.setid;
      const items = [];
      if (isUrl(out.blueprint)) items.push({ key: 'blueprint', label: 'Technical flat', url: out.blueprint });
      if (isUrl(out.views)) items.push({ key: 'views', label: 'Front · Back · Detail', url: out.views });
      if (isUrl(out.realize)) items.push({ key: 'realize', label: 'Made real', url: out.realize });
      if (isUrl(out.rendering)) items.push({ key: 'rendering', label: 'Fashion illustration', url: out.rendering });
      shots.forEach(s => { if (isUrl(out[s.key])) items.push({ key: s.key, label: s.placeLabel + ' · ' + s.lookLabel, url: out[s.key] }); });
      morphs.forEach(m => { if (isUrl(out[m.key])) items.push({ key: m.key, label: m.label, url: out[m.key] }); });
      fixes.forEach(f => { if (isUrl(out[f.key])) items.push({ key: f.key, label: 'Perfected · ' + f.label, url: out[f.key] }); });
      let n = 0;
      for (const it of items) { await benchApi('save_item', { setid: start.setid, key: it.key, label: it.label, dataUrl: it.url }); n++; setSavedN(n + '/' + items.length); }
      try {
        const k = 'studio76_apparel_sets', arr = JSON.parse(localStorage.getItem(k) || '[]').filter(s => s.id !== start.setid);
        arr.unshift({ id: start.setid, url: start.url, title: (spec && spec.garment_type) || 'Saved look', n: items.length, created: Date.now() });
        localStorage.setItem(k, JSON.stringify(arr.slice(0, 60)));
      } catch (e) {}
      setSavedUrl(start.url);
    } catch (e) { setErr(e.message || 'Save failed.'); }
    savingRef.current = false;
    setSaving(false);
  }

  function reset() { setStage('idle'); setSrc(null); setId(null); setSpec(null); setOut({}); setShots([]); setMorphs([]); setFixes([]); setFixN(0); setModalSrc(''); setModalKey(''); setCap(null); setCopied(false); setSel(SEL0); aspectsRef.current = {}; setVarN(0); setSaving(false); setSavedN(''); setSavedUrl(''); savedSetRef.current = null; savingRef.current = false; setErr(''); }

  const meas = spec && spec.estimated_measurements_cm ? [spec.estimated_measurements_cm.bust && ('bust ' + spec.estimated_measurements_cm.bust), spec.estimated_measurements_cm.waist && ('waist ' + spec.estimated_measurements_cm.waist), spec.estimated_measurements_cm.length && ('length ' + spec.estimated_measurements_cm.length)].filter(Boolean).join(' · ') : '';
  const selCount = (sel.length ? 1 : 0) + (sel.neckline ? 1 : 0) + (sel.sleeve ? 1 : 0) + (sel.fabric ? 1 : 0) + (sel.color ? 1 : 0) + (sel.fit ? 1 : 0) + sel.details.length + (sel.note.trim() ? 1 : 0);

  const Row = ({ l, v }) => v ? <div className="bz-row"><span className="bz-k">{l}</span><span className="bz-v">{v}</span></div> : null;
  const Tile = ({ keyName, title, sub }) => {
    const v = out[keyName];
    return (
      <div className="bz-tile">
        <div className="bz-tile-frame">
          {v === 'loading'
            ? <div className="bz-skel"><span className="bz-spin"></span><span>{sub || 'Working…'}</span></div>
            : (typeof v === 'string')
              ? <img src={v} alt={title} style={{cursor:'zoom-in'}} onClick={() => { setModalSrc(v); setModalKey(keyName); setCap(null); setCopied(false); setSel(SEL0); }} />
              : <div className="bz-empty"><span>{sub}</span></div>}
        </div>
        <div className="bz-tile-foot">
          <span className="bz-tile-t">{title}</span>
          {(typeof v === 'string' && v !== 'loading') && <a className="bz-dl" href={v} download={'studio76-' + keyName + '.png'}>Download</a>}
        </div>
      </div>
    );
  };

  return (
    <main className="bz">
      <header className="bz-head">
        <a className="bz-logo" href="/atelier.html" style={{letterSpacing:'0.12em', textTransform:'uppercase'}}>Studio<span> 76</span></a>
        <span className="bz-headlinks"><a href="/atelier.html">Atelier</a><a href="/handbag.html">Handbags</a><a href="/atelier-looks.html">Saved looks</a><a href="/blueprint.html">Jewellery</a><span className="bz-eyebrow">✦ Apparel Bench</span></span>
      </header>

      <section className="bz-hero">
        <h1 className="bz-title">From a photo to a <span className="bz-italic">pattern.</span></h1>
        <p className="bz-sub">Upload any garment — a photo, a screenshot, a runway look you saved. Our atelier reads it, drafts a
          technical flat, breaks down the spec, and shows it <span className="bz-italic">worn on a model</span>, made to your measurements.</p>
      </section>

      {stage === 'idle' && (
        <div className="bz-entry">
          <button className={'bz-entry-tab' + (entry === 'piece' ? ' on' : '')} onClick={() => setEntry('piece')}>
            <b>A photo of a garment</b><span>Remake something that already exists</span>
          </button>
          <button className={'bz-entry-tab' + (entry === 'sketch' ? ' on' : '')} onClick={() => setEntry('sketch')}>
            <b>A sketch or idea</b><span>Bring a drawing to life as a finished piece</span>
          </button>
        </div>
      )}

      {stage === 'idle' && (
        <div className={'bz-drop' + (drag ? ' over' : '')}
          onClick={() => fileRef.current && fileRef.current.click()}
          onDragOver={e => { e.preventDefault(); setDrag(true); }}
          onDragLeave={() => setDrag(false)}
          onDrop={e => { e.preventDefault(); setDrag(false); handleFile(e.dataTransfer.files[0], entry === 'sketch'); }}>
          <div className="bz-drop-icon">
            <svg viewBox="0 0 24 24" width="34" height="34" fill="none"><path d="M12 16V5m0 0L8 9m4-4 4 4" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"/><path d="M4 15v3a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-3" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"/></svg>
          </div>
          <p className="bz-drop-t">{entry === 'sketch' ? 'Drop your sketch here, or ' : 'Drop a photo here, or '}<span>browse</span></p>
          <p className="bz-drop-s">{entry === 'sketch' ? 'A drawing, croquis or concept — we’ll render it as a finished piece.' : 'JPG / PNG · a clear, well-lit shot works best'}</p>
          <input ref={fileRef} type="file" accept="image/*" hidden onChange={e => handleFile(e.target.files[0], entry === 'sketch')} />
        </div>
      )}

      {stage === 'idle' && (
        <div className="bz-paste">
          <input className="bz-input" value={urlText} placeholder={entry === 'sketch' ? '…or paste a sketch image URL' : '…or paste an image URL (e.g. from Pinterest)'}
            onChange={e => setUrlText(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') handleUrl(urlText, entry === 'sketch'); }} />
          <button className="bz-btn sm" disabled={!urlText.trim()} onClick={() => handleUrl(urlText, entry === 'sketch')}>Bring it in</button>
        </div>
      )}

      {stage === 'idle' && (
        <p style={{textAlign:'center',margin:'16px 0 0',fontSize:'13.5px'}}>
          <a href="/clip-atelier.html" target="_blank" rel="noopener" style={{color:'var(--accent)',fontWeight:600,textDecoration:'none',borderBottom:'1.5px solid currentColor',paddingBottom:'1px'}}>✦ Clip a look from Pinterest in one tap ↗</a>
        </p>
      )}

      {err && <p className="bz-err">{err}</p>}

      {stage !== 'idle' && (
        <div className="bz-stage">
          <aside className="bz-left">
            <div className="bz-orig bz-tile-frame">{src ? <img src={src} alt="Your garment" style={{cursor:'zoom-in'}} onClick={() => { setModalSrc(src); setModalKey(''); setCap(null); setCopied(false); setSel(SEL0); }} /> : <div className="bz-skel"><span className="bz-spin"></span></div>}</div>
            {spec ? (
              <div className="bz-spec">
                <span className="bz-spec-k">✦ Atelier reading</span>
                <h2 className="bz-piece">{spec.garment_type || 'Garment'}</h2>
                <Row l="Silhouette" v={spec.silhouette} />
                <Row l="Fabric" v={[spec.fabric, spec.fabric_detail].filter(Boolean).join(' · ')} />
                <Row l="Colour" v={[spec.color, spec.print_pattern].filter(Boolean).join(' · ')} />
                <Row l="Neckline" v={spec.neckline} />
                <Row l="Sleeves" v={spec.sleeves} />
                <Row l="Length" v={spec.length} />
                <Row l="Closure" v={spec.closures} />
                <Row l="Fit" v={spec.fit} />
                <Row l="Trims" v={spec.trims} />
                <Row l="Construction" v={spec.construction_notes} />
                <Row l="Era" v={spec.era_style} />
                <Row l="Occasion" v={spec.occasion} />
                <Row l="Approx fit" v={meas} />
                <Row l="Size" v={spec.size} />
                {spec.remake_notes && <p className="bz-notes">{spec.remake_notes}</p>}
                <button className="bz-reset" onClick={reset}>Try another photo</button>
              </div>
            ) : (
              <div className="bz-spec bz-spec-skel"><span className="bz-spin"></span><p>{entry === 'sketch' ? 'Bringing your sketch to life…' : 'Reading your garment…'}</p></div>
            )}
          </aside>

          <div className="bz-right">
            <div className="bz-saverow">
              <button className="bz-btn" disabled={!id || saving} onClick={save}>{saving ? ('Saving… ' + savedN) : (savedUrl ? 'Update this look' : 'Save this look')}</button>
              {savedUrl
                ? <a className="bz-saved" href={savedUrl} target="_blank" rel="noopener">View / share your look ↗</a>
                : <span className="bz-saverow-h">Stores the spec, flat &amp; every render at a shareable link.</span>}
            </div>
            <div className="bz-actions">
              <button className="bz-btn" disabled={!id || out.blueprint === 'loading'} onClick={() => gen('blueprint', 'draw')}>Draft tech flat</button>
              <button className="bz-btn" disabled={!id || out.views === 'loading'} onClick={() => gen('views', 'views')}>Front · Back · Detail</button>
              <button className="bz-btn" disabled={!id || out.rendering === 'loading'} onClick={() => gen('rendering', 'render_sketch')}>✦ Fashion illustration</button>
            </div>

            <div className="bz-grid">
              <Tile keyName="blueprint" title="Technical flat" sub="Tap “Draft tech flat”" />
              <Tile keyName="views" title="Front · Back · Detail" sub="Tap “Front · Back · Detail”" />
              <Tile keyName="rendering" title="Fashion illustration" sub="Tap “Fashion illustration” — a hand-painted croquis" />
            </div>

            <div className="bz-wear">
              <span className="bz-spec-k">✦ See it worn — Instagram-ready</span>
              <p className="bz-wear-h">Pick who wears it and a look, then tap a shot. Mix &amp; match to build a campaign.</p>
              <div className="bz-looks">
                {MODELS.map(m => (
                  <button key={m.k} className={'bz-look bz-who' + (model === m.k ? ' on' : '')} onClick={() => setModel(m.k)}>{m.label}</button>
                ))}
                <span className="bz-looksep"></span>
                {LOOKS.map(l => (
                  <button key={l.k} className={'bz-look' + (look === l.k ? ' on' : '')} onClick={() => setLook(l.k)}>{l.label}</button>
                ))}
              </div>
              <div className="bz-chips">
                {WEAR.map(w => {
                  const k = 'w_' + w.k + '_' + look + '_' + model;
                  return (
                    <button key={w.k} className="bz-chip" disabled={!id || out[k] === 'loading'} onClick={() => genWear(w.k)}>
                      {out[k] === 'loading' ? '…' : w.label}
                    </button>
                  );
                })}
                {(() => { const k = 'mag_' + look + '_' + model; return (
                  <button className="bz-chip bz-chip-mag" disabled={!id || out[k] === 'loading'} onClick={genMagazine}>
                    {out[k] === 'loading' ? '…' : '✦ Magazine cover'}
                  </button>
                ); })()}
              </div>
              {shots.length > 0 && (
                <div className="bz-grid">
                  {shots.map(s => <Tile key={s.key} keyName={s.key} title={s.placeLabel + ' · ' + s.lookLabel} sub="" />)}
                </div>
              )}
            </div>

            <div className="bz-wear">
              <span className="bz-spec-k">✦ Make it a collection — variations &amp; forms</span>
              <p className="bz-wear-h">Tell us what to change, or just spin off a fresh take — then reimagine it as a matching dress, top, skirt…</p>
              <div className="bz-varbox">
                <input className="bz-input" value={varText} maxLength={400}
                  placeholder="e.g. make it floor-length, softer drape, deeper colour… (optional)"
                  onChange={e => setVarText(e.target.value)}
                  onKeyDown={e => { if (e.key === 'Enter') genVar(); }} />
                <button className="bz-btn sm" disabled={!id} onClick={genVar}>Create variation</button>
              </div>
              <div className="bz-chips">
                {FORMS.map(f => {
                  const k = 'form_' + f.k;
                  return <button key={f.k} className="bz-chip" disabled={!id || out[k] === 'loading'} onClick={() => genForm(f.k)}>{out[k] === 'loading' ? '…' : f.label}</button>;
                })}
              </div>
              {morphs.length > 0 && (
                <div className="bz-grid">
                  {morphs.map(m => <Tile key={m.key} keyName={m.key} title={m.label} sub="" />)}
                </div>
              )}
            </div>

            {fixes.length > 0 && (
              <div className="bz-wear">
                <span className="bz-spec-k">✦ Perfected takes</span>
                <p className="bz-wear-h">Refined renders — zoom any image and tell the atelier what to change to make more.</p>
                <div className="bz-grid">
                  {fixes.map(f => <Tile key={f.key} keyName={f.key} title={f.label} sub="" />)}
                </div>
              </div>
            )}
          </div>
        </div>
      )}

      {modalSrc && (
        <div className="bz-modal" onClick={closeModal}>
          <span className="bz-modal-x" onClick={closeModal}>×</span>
          <div className="bz-modal-inner" onClick={e => e.stopPropagation()}>
            <img src={modalSrc} alt="" />

            <div className="bz-modal-side">
            {modalKey && isUrl(out[modalKey]) && (
              <div className="bz-edit">
                <span className="bz-spec-k">✦ Refine this design</span>
                <p className="bz-edit-h">Stack as many changes as you like, then Apply — they’re built in a single pass, the rest of the garment held steady. The new take lands in “Perfected takes”.</p>

                <div className="bz-edit-grp">
                  <span className="bz-edit-l">Length</span>
                  <div className="bz-erow">
                    <button className={'bz-chip sm' + (sel.length === 'longer' ? ' on' : '')} onClick={() => setSel(s => ({ ...s, length: s.length === 'longer' ? null : 'longer' }))}>Longer ↓</button>
                    <button className={'bz-chip sm' + (sel.length === 'shorter' ? ' on' : '')} onClick={() => setSel(s => ({ ...s, length: s.length === 'shorter' ? null : 'shorter' }))}>Shorter ↑</button>
                  </div>
                </div>

                <div className="bz-edit-grp">
                  <span className="bz-edit-l">Neckline</span>
                  <div className="bz-erow">
                    {NECKLINES.map(c => { const v = c.toLowerCase(); return <button key={c} className={'bz-chip sm' + (sel.neckline === v ? ' on' : '')} onClick={() => setSel(s => ({ ...s, neckline: s.neckline === v ? null : v }))}>{c}</button>; })}
                  </div>
                </div>

                <div className="bz-edit-grp">
                  <span className="bz-edit-l">Sleeves</span>
                  <div className="bz-erow">
                    {SLEEVES.map(st => { const v = st.toLowerCase(); return <button key={st} className={'bz-chip sm' + (sel.sleeve === v ? ' on' : '')} onClick={() => setSel(s => ({ ...s, sleeve: s.sleeve === v ? null : v }))}>{st}</button>; })}
                  </div>
                </div>

                <div className="bz-edit-grp">
                  <span className="bz-edit-l">Fabric</span>
                  <div className="bz-erow">
                    {FABRICS.map(m => { const v = m.toLowerCase(); return <button key={m} className={'bz-chip sm' + (sel.fabric === v ? ' on' : '')} onClick={() => setSel(s => ({ ...s, fabric: s.fabric === v ? null : v }))}>{m}</button>; })}
                  </div>
                </div>

                <div className="bz-edit-grp">
                  <span className="bz-edit-l">Colour</span>
                  <div className="bz-erow">
                    {COLORS.map(c => { const v = c.name.toLowerCase(); return <button key={c.name} className={'bz-chip sm bz-swatch' + (sel.color === v ? ' on' : '')} onClick={() => setSel(s => ({ ...s, color: s.color === v ? null : v }))}><span className="bz-dot" style={{background:c.hex}}></span>{c.name}</button>; })}
                  </div>
                </div>

                <div className="bz-edit-grp">
                  <span className="bz-edit-l">Fit</span>
                  <div className="bz-erow">
                    {FITS.map(m => { const v = m.toLowerCase(); return <button key={m} className={'bz-chip sm' + (sel.fit === v ? ' on' : '')} onClick={() => setSel(s => ({ ...s, fit: s.fit === v ? null : v }))}>{m}</button>; })}
                  </div>
                </div>

                <div className="bz-edit-grp">
                  <span className="bz-edit-l">Detailing</span>
                  <div className="bz-erow">
                    {DETAILS.map(d => <button key={d.k} className={'bz-chip sm' + (sel.details.indexOf(d.k) >= 0 ? ' on' : '')} onClick={() => setSel(s => ({ ...s, details: s.details.indexOf(d.k) >= 0 ? s.details.filter(x => x !== d.k) : s.details.concat(d.k) }))}>{d.label}</button>)}
                  </div>
                </div>

                <div className="bz-edit-grp">
                  <span className="bz-edit-l">Anything else (optional)</span>
                  <div className="bz-erow bz-evar">
                    <input className="bz-input" value={sel.note} maxLength={240}
                      placeholder="in your own words — e.g. add a slit, softer shoulders…"
                      onChange={e => setSel(s => ({ ...s, note: e.target.value }))}
                      onKeyDown={e => { if (e.key === 'Enter') applyEdits(); }} />
                  </div>
                </div>

                <div className="bz-edit-foot">
                  <button className="bz-chip sm bz-chip-mag" disabled={!selCount} onClick={applyEdits}>✦ Apply{selCount ? ' ' + selCount + ' change' + (selCount > 1 ? 's' : '') : ' changes'}</button>
                  {selCount > 0 && <button className="bz-link" onClick={() => setSel(SEL0)}>clear</button>}
                </div>
              </div>
            )}

            {spec && (
              <div className="bz-ig">
                <span className="bz-spec-k">✦ Post to Instagram</span>
                {!cap && <p className="bz-edit-h">We’ll write the caption &amp; hashtags — download the image and post it in seconds.</p>}
                {!cap && <button className="bz-chip sm bz-chip-mag" onClick={loadCaption}>✦ Write a caption</button>}
                {cap === 'loading' && <p className="bz-edit-h"><span className="bz-spin sm" style={{display:'inline-block',verticalAlign:'middle',marginRight:8}}></span>Writing a caption…</p>}
                {cap && cap !== 'loading' && (
                  <React.Fragment>
                    <textarea className="bz-ig-text" readOnly value={capFull(cap)} onClick={e => e.target.select()} />
                    <div className="bz-erow">
                      <button className="bz-chip sm bz-chip-mag" onClick={() => copyCap(cap)}>{copied ? 'Copied ✓' : 'Copy caption'}</button>
                      <a className="bz-chip sm" href={modalSrc} download={'studio76-' + (modalKey || 'garment') + '.png'}>Download image</a>
                      <button className="bz-link" onClick={loadCaption}>rewrite</button>
                    </div>
                  </React.Fragment>
                )}
              </div>
            )}
            </div>
          </div>
        </div>
      )}
    </main>
  );
}

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