/* App — responsive shell + 8-state machine, wired to the real engine (API + SSE). */
const { useState: useS, useEffect: useE, useRef: useRf } = React;
const K = window.K;

const initGen = () => ({ status:{ storyboard:'pending', hook:'pending', render:'pending' }, ms:{}, scenesShown:0, hookValue:0, hooksShown:false, selectedHook:0, renderPct:0 });

/* ---------- engine <-> design scene adapter ---------- */
const SAYROLE = { hero:'hook', accent:'hook', stat:'stat', label:'small', sub:'small' };
const D2E = { text:'say', bars:'compare', counter:'bignum', ring:'progress', checklist:'checklist', quote:'quote', timeline:'timeline', iconrow:'iconrow' };
const roleFor = (i, n) => i===0 ? 'Hook' : i===n-1 ? 'CTA' : i===n-2 ? 'Payoff' : 'Beat';
const engTreatment = (s) => s.kind!=='show' ? 'text' : ({ compare:'bars', bignum:'counter', progress:'ring', checklist:'checklist', quote:'quote', timeline:'timeline', iconrow:'iconrow' }[s.type] || 'text');
function engText(s) {
  if (s.kind!=='show') return (s.lines||[]).map(l=>l.text).join(' ');
  const d = s.data||{};
  if (s.type==='compare') return (d.bars||[]).map(b=>b.display||b.value).join('  vs  ');
  if (s.type==='bignum') return `${d.prefix||''}${d.value}${d.suffix||''}${d.label?' '+d.label:''}`.trim();
  if (s.type==='progress') return `${d.percent}%${d.label?' '+d.label:''}`.trim();
  if (s.type==='checklist') return (d.items||[]).join(' · ');
  if (s.type==='quote') return d.text||'';
  if (s.type==='timeline') return (d.events||[]).map(e=>`${e.when?e.when+': ':''}${e.label}`).join(' → ');
  if (s.type==='iconrow') return (d.icons||[]).map(i=>`${i.icon||''} ${i.label}`).join('  ');
  return s.title||'';
}
function engPayload(s) {
  if (s.kind!=='show') return { lines:(s.lines||[]).map(l=>({ t:l.text, role:SAYROLE[l.role]||'hook', accent:l.role==='accent' })) };
  const d = s.data||{};
  if (s.type==='compare') { const a=d.bars?.[0]||{}, b=d.bars?.[1]||{}; const mx=Math.max(Math.abs(a.value||0),Math.abs(b.value||0),1);
    return { title:s.title||'', a:{ label:a.label||'A', val:a.display||String(a.value??''), pct:Math.round(Math.abs(a.value||0)/mx*100) }, b:{ label:b.label||'B', val:b.display||String(b.value??''), pct:Math.round(Math.abs(b.value||0)/mx*100) } }; }
  if (s.type==='bignum') return { value:String(d.value??''), unit:d.suffix||'', caption:d.label||'' };
  if (s.type==='progress') return { pct:Math.round(d.percent||0), caption:d.label||'' };
  if (s.type==='checklist') return { items:(d.items||[]).slice(0,5) };
  if (s.type==='quote') return { text:d.text||'' };
  if (s.type==='timeline') { const e=d.events||[], f=e[0]||{}, l=e[e.length-1]||{}; return { a:{ val:f.when||'Then', label:f.label||'' }, b:{ val:l.when||'Now', label:l.label||'' } }; }
  if (s.type==='iconrow') return { items:(d.icons||[]).map(ic=>[ic.icon||'•', ic.label||'']) };
  return {};
}
const engToDesign = (s, i, n) => ({ id:s._id, role:roleFor(i,n), treatment:engTreatment(s), text:engText(s), payload:engPayload(s) });
function engDefault(type) {
  switch (type) {
    case 'compare':  return { kind:'show', type:'compare', durationSec:3, title:'Comparison', data:{ bars:[{label:'Then',value:100,display:'Then',tone:'bad'},{label:'Now',value:30,display:'Now',tone:'good'}] } };
    case 'bignum':   return { kind:'show', type:'bignum', durationSec:3, data:{ value:100, suffix:'', label:'the number' } };
    case 'progress': return { kind:'show', type:'progress', durationSec:3, data:{ percent:72, label:'' } };
    case 'checklist':return { kind:'show', type:'checklist', durationSec:3, data:{ items:['One','Two','Three'] } };
    case 'quote':    return { kind:'show', type:'quote', durationSec:3, data:{ text:'Quote' } };
    case 'timeline': return { kind:'show', type:'timeline', durationSec:4, data:{ events:[{when:'Then',label:'18 mo'},{when:'Now',label:'2.4 wk'}] } };
    case 'iconrow':  return { kind:'show', type:'iconrow', durationSec:3, data:{ icons:[{icon:'⚡',label:'Fast'},{icon:'💸',label:'Cheap'}] } };
    default:         return { kind:'say', anim:'slam', durationSec:2.4, lines:[{ text:'New beat', role:'hero' }] };
  }
}
const stripId = (s) => { const { _id, ...rest } = s; return rest; };

/* ---------- shared bits ---------- */
function Logo({ compact }) {
  return (
    <div className="flex items-center gap-2.5">
      <div className="w-8 h-8 rounded-[10px] grid place-items-center bg-accent shrink-0"><window.Icon.Bolt size={17} className="text-black"/></div>
      <div className="leading-none">
        <div className="text-[16px] font-bold tracking-tight">Kinetic</div>
        {!compact && <div className="text-[10px] text-faint tracking-wide mt-0.5">Talk it. Watch it move.</div>}
      </div>
    </div>
  );
}

function PreviewCaption({ view, gen, reRendering, rrPct }) {
  const I = window.Icon;
  if (view==='compose') return <span className="text-[12px] text-faint">Pick a style — the preview is live</span>;
  if (view==='scripting'||view==='planning') return <span className="text-[12px] text-faint">your reel is taking shape…</span>;
  if (view==='storyboard') return <span className="text-[12px] text-faint">previewing your scenes</span>;
  if (view==='generating') return <span className="text-[12px] text-accent font-mono tnum">rendering {gen.renderPct}%</span>;
  if (view==='error') return <span className="text-[12px] text-alert">render hit a snag</span>;
  if (view==='result') return reRendering
    ? <span className="text-[12px] text-accent font-mono tnum">re-rendering {rrPct}%</span>
    : <span className="text-[12px] text-success flex items-center justify-center gap-1.5"><I.Check size={13}/> ready to post</span>;
  return null;
}

function MiniChip({ mode, style, scenes }) {
  return (
    <div className="w-[34px] shrink-0 rounded-[7px] overflow-hidden ring-1 ring-white/10">
      <window.ReelPreview mode={mode} style={style} scenes={scenes} framed={false}/>
    </div>
  );
}

function MobilePreviewBar({ view, mode, style, scenes, gen, totalMs, stages, reRendering, rrPct, onExpand }) {
  const I = window.Icon;
  let title='Live preview', detail=K.STYLES.find(s=>s.id===style.id).name, tone='text-faint';
  if (view==='scripting'){ title='Writing your script'; detail='please wait'; }
  else if (view==='planning'){ title='Designing storyboard'; detail='please wait'; }
  else if (view==='storyboard'){ title='Storyboard'; detail=`${scenes.length} scenes`; }
  else if (view==='generating'){ const a=stages.find(s=>gen.status[s.id]==='active'); const dc=stages.filter(s=>gen.status[s.id]==='done').length;
    title = a?a.label:'Starting'; detail = a?.id==='render'?`${Math.round(gen.renderPct)}%`:`step ${Math.min(dc+1,stages.length)}/${stages.length}`; tone='text-accent'; }
  else if (view==='result'){ title = reRendering?'Re-rendering':'Reel ready'; detail = reRendering?`${rrPct}%`:'ready to post'; tone = reRendering?'text-accent':'text-success'; }
  else if (view==='error'){ title='Render hit a snag'; detail='tap to view'; tone='text-alert'; }

  const active = view==='generating' || (view==='result'&&reRendering);
  return (
    <button onClick={onExpand} className="lg:hidden w-full flex items-center gap-3 px-4 py-2.5 bg-bg/85 backdrop-blur border-b border-border active:bg-surface/60 transition-colors">
      <MiniChip mode={mode} style={style} scenes={scenes}/>
      <div className="flex-1 min-w-0 text-left">
        <div className="flex items-center gap-1.5">
          {active && <span className="w-1.5 h-1.5 rounded-full bg-accent shrink-0" style={{ animation:'kpulse 1.2s infinite' }}/>}
          <span className="text-[13px] font-semibold truncate">{title}</span>
        </div>
        <div className={`text-[11px] font-mono tnum ${tone}`}>{detail}{view==='generating' && ` · ${(totalMs/1000).toFixed(1)}s`}</div>
      </div>
      <span className="shrink-0 flex items-center gap-1 text-[11px] text-muted px-2 py-1.5 rounded-lg bg-surface border border-border"><I.Maximize size={12}/> Preview</span>
    </button>
  );
}

function PreviewSheet({ mode, style, scenes, videoSrc, onClose, caption }) {
  const I = window.Icon;
  return (
    <div className="lg:hidden fixed inset-0 z-[55] flex flex-col bg-bg/95 backdrop-blur-md pt-safe pb-safe anim-floatup" onClick={onClose}>
      <div className="flex items-center justify-between px-4 h-14 shrink-0">
        <span className="text-[13px] font-semibold">Preview</span>
        <button onClick={onClose} className="w-9 h-9 rounded-full grid place-items-center bg-surface border border-border text-muted"><I.X size={18}/></button>
      </div>
      <div className="flex-1 min-h-0 flex flex-col items-center justify-center px-8 pb-6" onClick={e=>e.stopPropagation()}>
        <div style={{ width:'min(300px, calc((100dvh - 200px) * 0.5625))' }}>
          <window.ReelPreview mode={mode} style={style} scenes={scenes} videoSrc={videoSrc} controls={mode==='done'}/>
        </div>
        <div className="mt-5 h-5">{caption}</div>
      </div>
    </div>
  );
}

/* ---------- App ---------- */
function App() {
  const I = window.Icon;
  const [view, setView] = useS('compose');
  const [inputMode, setInputMode] = useS('type');
  const [idea, setIdea] = useS(K.DEFAULT_IDEA);
  const [recording, setRecording] = useS(false);
  const [hasAudio, setHasAudio] = useS(false);
  const [audioId, setAudioId] = useS(null);
  const [styleId, setStyleId] = useS('bold');
  const [detail, setDetail] = useS('medium');
  const [script, setScript] = useS(K.SCRIPT_TEXT);
  const [rewriting, setRewriting] = useS(false);
  const [board, setBoard] = useS([]);           // engine scenes — source of truth
  const [gen, setGen] = useS(initGen());
  const [totalMs, setTotalMs] = useS(0);
  const [reRendering, setReRendering] = useS(false);
  const [rrPct, setRrPct] = useS(0);
  const [showPro, setShowPro] = useS(false);
  const [showSheet, setShowSheet] = useS(false);
  const [jobId, setJobId] = useS(null);
  const [errMsg, setErrMsg] = useS('');
  const cancel = useRf(false);
  const scrollRef = useRf(null);

  const styleObj = K.STYLES.find(s=>s.id===styleId);
  const isVoice = inputMode==='talk' && hasAudio;
  const scenes = board.map((s,i)=>engToDesign(s,i,board.length));   // derived design scenes
  const previewScenes = scenes.map(s=>({ ...s, dur:2200 }));
  const scrollTop = ()=>{ if(scrollRef.current) scrollRef.current.scrollTop=0; };

  function sseFollow(id, onEvent) {
    return new Promise((resolve, reject) => {
      const es = new EventSource('/api/events/' + id);
      es.onmessage = (e) => {
        const { stage, payload } = JSON.parse(e.data);
        if (stage==='done') { es.close(); resolve(payload); return; }
        if (stage==='error') { es.close(); reject(new Error(payload.message||'failed')); return; }
        try { onEvent && onEvent(stage, payload); } catch {}
      };
      es.onerror = () => { es.close(); reject(new Error('connection lost')); };
    });
  }

  /* audio upload (Talk mode) */
  async function onAudio(fileOrBlob) {
    try { const r = await fetch('/api/upload', { method:'POST', body:fileOrBlob }); const j = await r.json(); if (j.error) throw new Error(j.error); setAudioId(j.audioId); setHasAudio(true); }
    catch (e) { alert('Upload failed: ' + e.message); }
  }
  const clearAudio = ()=>{ setAudioId(null); setHasAudio(false); };

  /* compose → script */
  async function writeScript() {
    cancel.current=false; setView('scripting'); scrollTop();
    try {
      const body = isVoice && audioId ? { audioId, style:styleId, detail } : { idea, style:styleId, detail };
      const r = await fetch('/api/script', { method:'POST', headers:{'content-type':'application/json'}, body:JSON.stringify(body) });
      const j = await r.json(); if (j.error) throw new Error(j.error);
      const res = await sseFollow(j.id);
      if (!cancel.current) { setScript(res.scriptText || K.SCRIPT_TEXT); setView('script'); scrollTop(); }
    } catch (e) { if (!cancel.current) { alert(e.message); setView('compose'); } }
  }

  async function rewrite() {
    setRewriting(true);
    try {
      const body = isVoice && audioId ? { audioId, style:styleId, detail } : { idea, style:styleId, detail };
      const r = await fetch('/api/script', { method:'POST', headers:{'content-type':'application/json'}, body:JSON.stringify(body) });
      const j = await r.json(); if (j.error) throw new Error(j.error);
      const res = await sseFollow(j.id); setScript(res.scriptText || script);
    } catch (e) { alert(e.message); } finally { setRewriting(false); }
  }

  /* script → storyboard */
  async function planScenes() {
    cancel.current=false; setView('planning'); scrollTop();
    try {
      const r = await fetch('/api/storyboard', { method:'POST', headers:{'content-type':'application/json'}, body:JSON.stringify({ script, style:styleId, detail }) });
      const j = await r.json(); if (j.error) throw new Error(j.error);
      const res = await sseFollow(j.id);
      const out = (res.scenes||[]).map((s,i)=>({ ...s, _id:'sc'+i+'_'+Math.random().toString(36).slice(2,7) }));
      if (!cancel.current) { setBoard(out); setView('storyboard'); scrollTop(); }
    } catch (e) { if (!cancel.current) { alert(e.message); setView('script'); } }
  }

  /* storyboard → generating → result */
  async function animate() {
    cancel.current=false; setErrMsg(''); setGen(initGen()); setView('generating'); scrollTop();
    const t0=performance.now(); const timer=setInterval(()=>setTotalMs(performance.now()-t0),90);
    const sstart={};
    try {
      const r = await fetch('/api/generate', { method:'POST', headers:{'content-type':'application/json'}, body:JSON.stringify({ storyboard:{ scenes: board.map(stripId) }, style:styleId, detail }) });
      const j = await r.json(); if (j.error) throw new Error(j.error);
      setJobId(j.id);
      const n = board.length;
      const res = await sseFollow(j.id, (stage, payload) => {
        if (payload.status==='start') sstart[stage]=performance.now();
        if (stage==='storyboard') {
          if (payload.status==='start') { setGen(g=>({ ...g, status:{ ...g.status, storyboard:'active' } })); let i=0; const iv=setInterval(()=>{ i++; setGen(g=>({ ...g, scenesShown:Math.min(i,n) })); if(i>=n) clearInterval(iv); }, 160); }
          if (payload.status==='done') setGen(g=>({ ...g, status:{ ...g.status, storyboard:'done' }, scenesShown:n, ms:{ ...g.ms, storyboard:performance.now()-(sstart.storyboard||t0) } }));
        }
        if (stage==='hook') {
          if (payload.status==='start') setGen(g=>({ ...g, status:{ ...g.status, hook:'active' } }));
          if (payload.status==='done') {
            const variants = payload.variants||[];
            window.K.HOOKS = variants.map(v=>({ text:v.line, score:v.score, reason:v.reason, best:v.line===payload.chosen }));
            window.K.BASE_HOOK = { text:payload.current||K.BASE_HOOK.text, score:payload.score||0 };
            const best = Math.max(0, window.K.HOOKS.findIndex(h=>h.best));
            setGen(g=>({ ...g, status:{ ...g.status, hook:'done' }, hookValue:payload.score||0, hooksShown:true, selectedHook:best, ms:{ ...g.ms, hook:performance.now()-(sstart.hook||t0) } }));
          }
        }
        if (stage==='render') {
          if (payload.status==='start') setGen(g=>({ ...g, status:{ ...g.status, render:'active' } }));
          if (payload.status==='progress') setGen(g=>({ ...g, renderPct:payload.pct }));
          if (payload.status==='done') setGen(g=>({ ...g, status:{ ...g.status, render:'done' }, renderPct:100, ms:{ ...g.ms, render:performance.now()-(sstart.render||t0) } }));
        }
      });
      if (res.caption) window.K.CAPTION = res.caption;
      if (res.hashtags) window.K.HASHTAGS = res.hashtags.map(h=>'#'+String(h).replace(/^#/,''));
      setTotalMs(performance.now()-t0); await new Promise(r=>setTimeout(r,400));
      if (!cancel.current) { setView('result'); scrollTop(); }
    } catch (e) { if (!cancel.current) { setErrMsg(e.message||'render failed'); setView('error'); } } finally { clearInterval(timer); }
  }

  /* free re-render (hook and/or style) */
  async function reRender() {
    if (reRendering) return; cancel.current=false; setReRendering(true); setRrPct(0);
    try {
      const hookChoice = gen.selectedHook>=0 ? gen.selectedHook+1 : undefined;
      const r = await fetch('/api/rerender', { method:'POST', headers:{'content-type':'application/json'}, body:JSON.stringify({ jobId, hookChoice, style:styleId }) });
      const j = await r.json(); if (j.error) throw new Error(j.error);
      await sseFollow(j.id, (stage, payload)=>{ if (stage==='render' && payload.status==='progress') setRrPct(payload.pct); if (stage==='render' && payload.status==='done') setRrPct(100); });
      setJobId(j.id);
    } catch (e) { alert(e.message); } finally { setReRendering(false); }
  }

  /* storyboard editing (operates on the engine board) */
  async function swapTreatment(i, designId) {
    const engType = D2E[designId] || 'say';
    const ctxText = engText(board[i] || {});
    setBoard(b => b.map((s,k)=> k===i ? { ...engDefault(engType), _id:s._id } : s));     // optimistic
    try {
      const r = await fetch('/api/recast', { method:'POST', headers:{'content-type':'application/json'}, body:JSON.stringify({ text:ctxText, type:engType, script }) });
      const j = await r.json(); if (j.error || !j.scene) throw new Error(j.error||'no scene');
      setBoard(b => b.map((s,k)=> k===i ? { ...j.scene, _id:s._id } : s));
    } catch (e) { /* keep optimistic default */ }
  }
  const moveScene = (i,dir)=>setBoard(b=>{ const j=i+dir; if(j<0||j>=b.length) return b; const a=[...b]; [a[i],a[j]]=[a[j],a[i]]; return a; });
  const removeScene = (i)=>setBoard(b=>b.length<=1?b:b.filter((_,k)=>k!==i));
  const addScene = ()=>setBoard(b=>[...b, { ...engDefault('say'), _id:'sc'+b.length+'_'+Math.random().toString(36).slice(2,7) }]);

  const download = ()=>{ if(!jobId) return; const a=document.createElement('a'); a.href='/api/video/'+jobId; a.download='kinetic-reel.mp4'; document.body.appendChild(a); a.click(); a.remove(); };
  const goCompose = ()=>{ cancel.current=true; setReRendering(false); setGen(initGen()); setTotalMs(0); setHasAudio(false); setAudioId(null); setBoard([]); setJobId(null); setView('compose'); scrollTop(); };
  const selectHook = (i)=>setGen(g=>({ ...g, selectedHook:i }));

  /* preview mode by view */
  let previewMode='idle', previewPct=0;
  if (view==='compose'||view==='storyboard') previewMode='idle';
  else if (view==='scripting'||view==='planning') previewMode='skeleton';
  else if (view==='generating'){ const r=gen.status.render; previewMode=(r==='active'||r==='done')?'render':'skeleton'; previewPct=gen.renderPct; }
  else if (view==='result'){ previewMode=reRendering?'render':'done'; previewPct=rrPct; }
  else if (view==='error'){ previewMode='render'; previewPct=64; }
  const videoSrc = (view==='result' && !reRendering && jobId) ? '/api/video/'+jobId : null;

  /* sticky CTA per view */
  const canCompose = isVoice || idea.trim().length>4;
  let cta=null;
  if (view==='compose') cta={ label:'Write my script', sub:'you’ll review it', icon:<I.Pencil size={17}/>, onClick:writeScript, disabled:!canCompose };
  else if (view==='script') cta={ label:'Plan the scenes', icon:<I.ArrowRight size={17}/>, onClick:planScenes, right:true };
  else if (view==='storyboard') cta={ label:'Animate this', icon:<I.Zap size={17}/>, onClick:animate, right:true };

  const desktopCaption = <PreviewCaption view={view} gen={gen} reRendering={reRendering} rrPct={rrPct}/>;

  return (
    <div className="h-[100dvh] flex flex-col bg-bg text-text overflow-hidden relative">
      <div aria-hidden className="pointer-events-none fixed inset-0 z-0 overflow-hidden">
        <div className="absolute -top-1/3 left-1/2 -translate-x-1/2 w-[900px] h-[900px] rounded-full opacity-[0.06]" style={{ background:'radial-gradient(circle, #FFE600, transparent 62%)', animation:'glowdrift 22s ease-in-out infinite' }}/>
      </div>

      <header className="relative z-30 shrink-0 h-[56px] px-4 sm:px-5 flex items-center justify-between border-b border-border bg-bg/80 backdrop-blur pt-safe">
        <Logo/>
        <div className="flex items-center gap-2">
          <button onClick={goCompose} className="hidden sm:flex items-center gap-1.5 text-[12.5px] text-muted hover:text-text px-3 py-2 rounded-lg hover:bg-surface transition-colors"><I.Plus size={14}/> New reel</button>
          <button onClick={goCompose} className="sm:hidden w-9 h-9 grid place-items-center text-muted hover:text-text rounded-lg hover:bg-surface transition-colors"><I.Plus size={18}/></button>
          <button onClick={()=>setShowPro(true)} className="flex items-center gap-1.5 text-[12.5px] font-semibold text-accent border border-accent/30 hover:bg-accent/10 px-3 py-2 rounded-lg transition-colors"><I.Crown size={13}/> Pro</button>
        </div>
      </header>

      <main className="relative z-10 flex-1 flex min-h-0">
        <section className="flex-1 min-w-0 flex flex-col">
          <MobilePreviewBar view={view} mode={previewMode} style={styleObj} scenes={previewScenes} gen={gen} totalMs={totalMs} stages={K.STAGES} reRendering={reRendering} rrPct={rrPct} onExpand={()=>setShowSheet(true)}/>

          {view==='generating' && (
            <div className="hidden lg:flex shrink-0 px-8 xl:px-10 pt-6 pb-3 items-start justify-between gap-4 border-b border-border/60">
              <div className="min-w-0">
                <div className="flex items-center gap-2 text-[11px] font-semibold tracking-[0.14em] text-accent uppercase mb-1.5 whitespace-nowrap"><span className="w-1.5 h-1.5 rounded-full bg-accent" style={{ animation:'kpulse 1.2s infinite' }}/> Live board</div>
                <h1 className="text-[22px] font-bold tracking-tight whitespace-nowrap">Building your reel</h1>
              </div>
              <window.StateWidget gen={gen} stages={K.STAGES} totalMs={totalMs}/>
            </div>
          )}

          <div ref={scrollRef} className="flex-1 overflow-y-auto px-4 sm:px-6 lg:px-8 xl:px-10 py-6">
            <div className="max-w-[640px] mx-auto" style={{ paddingBottom: cta?'96px':'8px' }}>
              {view==='compose' && <window.Compose K={K} inputMode={inputMode} setInputMode={setInputMode} idea={idea} setIdea={setIdea} recording={recording} setRecording={setRecording} hasAudio={hasAudio} setHasAudio={setHasAudio} onAudio={onAudio} clearAudio={clearAudio} styleId={styleId} setStyleId={setStyleId} detail={detail} setDetail={setDetail}/>}
              {view==='scripting' && <window.ScriptingLoader K={K} voice={isVoice}/>}
              {view==='script' && <window.ScriptReview K={K} script={script} setScript={setScript} onRewrite={rewrite} rewriting={rewriting}/>}
              {view==='planning' && <window.PlanningLoader K={K}/>}
              {view==='storyboard' && <window.Storyboard K={K} scenes={scenes} style={styleObj} onSwap={swapTreatment} onMove={moveScene} onRemove={removeScene} onAdd={addScene}/>}
              {view==='generating' && <window.LiveBoard K={K} gen={gen} style={styleObj} scenes={previewScenes} stages={K.STAGES} onSelectHook={selectHook}/>}
              {view==='result' && <window.Result K={K} scenes={scenes} gen={gen} style={styleObj} styleId={styleId} setStyleId={setStyleId} onSelectHook={selectHook} onReRender={reRender} onDownload={download} onEditStoryboard={()=>{ setView('storyboard'); scrollTop(); }} onMakeAnother={goCompose} onOpenPro={()=>setShowPro(true)}/>}
              {view==='error' && <window.ErrorCard message={errMsg} onRetry={animate} onBack={()=>{ setView('storyboard'); scrollTop(); }}/>}
            </div>
          </div>

          {cta && (
            <div className="shrink-0 border-t border-border bg-bg/90 backdrop-blur px-4 sm:px-6 lg:px-8 py-3 pb-safe">
              <div className="max-w-[640px] mx-auto">
                <button onClick={cta.onClick} disabled={cta.disabled}
                  className={`group w-full rounded-2xl py-3.5 px-5 flex items-center justify-center gap-2.5 text-[15.5px] font-semibold transition-all ${cta.disabled?'bg-hi text-faint cursor-not-allowed':'bg-accent text-black hover:brightness-105 active:scale-[.99] shadow-[0_8px_30px_-8px_rgba(255,230,0,.4)]'}`}>
                  {!cta.right && cta.icon}{cta.label}
                  {cta.sub && <span className="opacity-60 font-normal text-[12px]">· {cta.sub}</span>}
                  {cta.right && <span className="group-hover:translate-x-0.5 transition-transform">{cta.icon}</span>}
                </button>
              </div>
            </div>
          )}
        </section>

        <aside className="hidden lg:flex shrink-0 w-[380px] xl:w-[430px] border-l border-border bg-[#0F0F14] flex-col items-center justify-center px-8 py-6">
          <div style={{ width:'min(290px, calc((100dvh - 240px) * 0.5625))' }}>
            <window.ReelPreview mode={previewMode} style={styleObj} scenes={previewScenes} renderPct={previewPct} videoSrc={videoSrc} controls={previewMode==='done'}/>
          </div>
          <div className="mt-5 text-center h-5">{desktopCaption}</div>
        </aside>
      </main>

      {showPro && <window.ProModal onClose={()=>setShowPro(false)}/>}
      {showSheet && <PreviewSheet mode={previewMode} style={styleObj} scenes={previewScenes} videoSrc={videoSrc} caption={desktopCaption} onClose={()=>setShowSheet(false)}/>}
    </div>
  );
}

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