// === deck-shell.jsx — reproductor genérico (app.jsx del deck, parametrizado) ===
// Cada presentación define, ANTES de cargar este script:
//   window.DECK = { id:'video-05', title:'…', scenes:[{title, comp:()=>Escena01, dur, stop?}] }
// Este shell consume window.DECK.scenes y monta el mismo reproductor que el deck master.

const SLIDES = (window.DECK && window.DECK.scenes) || [];
const DECK_ID = (window.DECK && window.DECK.id) || 'deck';

const STAGE_H = 1080;
const STAGE_W_FULL = 1920; // lienzo interno fijo

const ASPECT_PRESETS = {
  '16:9': 1920,
  '4:3':  1440,
  '1:1':  1080,
  '4:5':   864,
  '9:16':  608,
};

function getAspectW(ratio){
  return ASPECT_PRESETS[ratio] || 1920;
}

function useScale(stageW){
  const [scale, setScale] = React.useState(1);
  React.useEffect(()=>{
    const calc = () => {
      const sx = window.innerWidth / stageW;
      const sy = window.innerHeight / STAGE_H;
      setScale(Math.min(sx, sy));
    };
    calc();
    window.addEventListener('resize', calc);
    return ()=> window.removeEventListener('resize', calc);
  }, [stageW]);
  return scale;
}

function SlideHost({ slideIdx, onSlideEnd }){
  // Si la escena trae locución (slide.audio), el AUDIO MANDA: el reloj t se lee del
  // currentTime del mp3. Si no hay audio (o falla la carga), reloj clásico por duración.
  const slide = SLIDES[slideIdx];
  const [audioFailed, setAudioFailed] = React.useState(false);
  React.useEffect(()=>{ setAudioFailed(false); }, [slideIdx]);
  if (slide.audio && !audioFailed) {
    return <AudioSlideHost key={'a'+slideIdx} slideIdx={slideIdx} onSlideEnd={onSlideEnd} onAudioFail={()=>setAudioFailed(true)}/>;
  }
  return <TimedSlideHost key={'t'+slideIdx} slideIdx={slideIdx} onSlideEnd={onSlideEnd}/>;
}

function TimedSlideHost({ slideIdx, onSlideEnd }){
  const slide = SLIDES[slideIdx];
  const SlideComp = slide.comp();
  const [resetKey, setResetKey] = React.useState(0);
  const [playing, setPlaying] = React.useState(true);
  const [progress, setProgress] = React.useState(0);
  const endedRef = React.useRef(false);

  React.useEffect(()=>{
    setResetKey(k => k+1);
    setProgress(0);
    endedRef.current = false;
  }, [slideIdx]);

  React.useEffect(()=>{
    window.__setPlaying = setPlaying;
    window.__progress = progress;
  });

  const handleProgress = React.useCallback((p)=>{
    setProgress(p);
    if (p >= 1 && endedRef.current === false) {
      endedRef.current = true;
      if (onSlideEnd) onSlideEnd();
    }
  }, [onSlideEnd]);

  return (
    // key={slideIdx}: remonta el Stage EN EL MISMO RENDER en que cambia el slide.
    <Stage key={slideIdx} duration={slide.dur} playing={playing} resetKey={resetKey} onProgress={handleProgress}>
      <InnerSlide SlideComp={SlideComp}/>
      <ProgressMirror progress={progress}/>
    </Stage>
  );
}

// === AudioSlideHost — reloj derivado del mp3 de locución (el audio manda) ===
function AudioSlideHost({ slideIdx, onSlideEnd, onAudioFail }){
  const slide = SLIDES[slideIdx];
  const SlideComp = slide.comp();
  const audioRef = React.useRef(null);
  const raf = React.useRef(0);
  const [t, setT] = React.useState(0);
  const [playing, setPlaying] = React.useState(true);
  const endedRef = React.useRef(false);

  React.useEffect(()=>{ window.__setPlaying = setPlaying; });

  // Crear el <audio> de la escena
  React.useEffect(()=>{
    endedRef.current = false;
    setT(0);
    const a = new Audio(slide.audio);
    a.preload = 'auto';
    audioRef.current = a;
    const onEnded = ()=>{
      if (endedRef.current) return;
      endedRef.current = true;
      if (onSlideEnd) onSlideEnd();
    };
    const onErr = ()=>{ if (onAudioFail) onAudioFail(); };
    a.addEventListener('ended', onEnded);
    a.addEventListener('error', onErr);
    // Reproducir con reintentos: el play() puede fallar por AbortError (cambio de escena)
    // o NotAllowedError (política de autoplay de Chrome en mitad de la secuencia). Si falla,
    // se reintenta unas veces y, como último recurso, al siguiente gesto del usuario — así
    // ninguna escena se queda muda por un bloqueo puntual del navegador.
    let rearmed = false;
    const rearm = ()=>{ if (audioRef.current===a && a.paused) a.play().catch(()=>{}); };
    const tryPlay = (retries)=>{
      if (audioRef.current !== a) return;
      a.play().catch((e)=>{
        if (e && e.name === 'AbortError') return;                 // cambio de escena: no insistir
        if (retries > 0) { setTimeout(()=>tryPlay(retries-1), 300); return; }
        if (!rearmed) {                                            // último recurso: al siguiente clic
          rearmed = true;
          window.addEventListener('pointerdown', rearm, { once:true });
          window.addEventListener('keydown', rearm, { once:true });
        }
      });
    };
    if (window.__audioUnlocked) tryPlay(4);
    const onUnlock = ()=> tryPlay(4);
    window.addEventListener('__audioUnlock', onUnlock);
    return ()=>{
      a.pause();
      a.removeEventListener('ended', onEnded);
      a.removeEventListener('error', onErr);
      window.removeEventListener('__audioUnlock', onUnlock);
      window.removeEventListener('pointerdown', rearm);
      window.removeEventListener('keydown', rearm);
      audioRef.current = null;
    };
  }, [slideIdx]);

  // Play/pausa
  React.useEffect(()=>{
    const a = audioRef.current; if (!a) return;
    if (playing && window.__audioUnlocked) a.play().catch(()=>{});
    else if (!playing) a.pause();
  }, [playing, slideIdx]);

  // Reloj: t = currentTime del audio; barra de progreso directa; velocidad del selector
  React.useEffect(()=>{
    let mounted = true;
    const loop = ()=>{
      if (!mounted) return;
      const a = audioRef.current;
      if (a) {
        const sp = (typeof window.__deckSpeed === 'number' && window.__deckSpeed > 0) ? window.__deckSpeed : 1;
        if (a.playbackRate !== sp) { try { a.playbackRate = sp; } catch(e){} }
        // Time-warp opcional: si la escena declara `anim` (duración de su coreografía) y el
        // audio dura más, el reloj de la escena se estira para repartir la animación por toda
        // la locución (suelo 0.55 — nunca más lento de ~1.8×; el resto, plano final quieto).
        const warp = (slide.anim && a.duration > 0) ? Math.max(0.55, slide.anim / a.duration) : 1;
        setT(a.currentTime * warp);
        const d = a.duration;
        const el = document.getElementById('progress-inner');
        if (el && d > 0) el.style.width = `${Math.min(100, (a.currentTime/d)*100)}%`;
      }
      raf.current = requestAnimationFrame(loop);
    };
    raf.current = requestAnimationFrame(loop);
    return ()=>{ mounted = false; cancelAnimationFrame(raf.current); };
  }, [slideIdx]);

  // Eventos de seek (QA / tools)
  React.useEffect(()=>{
    const onSet = (ev)=>{
      const tt = ev.detail && typeof ev.detail.t === 'number' ? ev.detail.t : 0;
      const a = audioRef.current;
      if (a) { try { a.currentTime = tt; } catch(e){} }
      setT(tt);
    };
    const onEnd = ()=>{
      const a = audioRef.current;
      if (a && a.duration) { try { a.currentTime = Math.max(0, a.duration - 0.02); } catch(e){} setT(a.duration || slide.dur); }
    };
    window.addEventListener('__deckSetTime', onSet);
    window.addEventListener('__deckSeekEnd', onEnd);
    return ()=>{ window.removeEventListener('__deckSetTime', onSet); window.removeEventListener('__deckSeekEnd', onEnd); };
  }, [slideIdx]);

  const duration = (audioRef.current && audioRef.current.duration) || slide.dur;
  return (
    <StageCtx.Provider value={{ t, duration, playing }}>
      <SlideComp t={t}/>
    </StageCtx.Provider>
  );
}

function InnerSlide({ SlideComp }){
  const t = useTime();
  return <SlideComp t={t}/>;
}

function ProgressMirror({ progress }){
  React.useEffect(()=>{
    const el = document.getElementById('progress-inner');
    if (el) el.style.width = `${Math.min(100, progress*100)}%`;
  }, [progress]);
  return null;
}

// === SlideLayer — entrada de cada diapositiva SIN parpadeo ===
// Cada slide recibe un key distinto (transitionKey), así que esto es un nodo NUEVO cada
// vez → la animación @keyframes se reproduce entera en cada cambio.
//
// El bug anterior: la opacidad vivía en un nodo PERSISTENTE y el flag `entering` llegaba
// un render tarde, así que la diapositiva se pintaba a plena vista un frame, se desvanecía
// (1→0) y volvía (0→1) — "aparece, desaparece, reaparece".
//
// Por qué @keyframes y no una CSS transition con estado JS: revelar vía requestAnimationFrame
// o setTimeout es frágil — en una pestaña de fondo el navegador estrangula AMBOS, y la slide
// puede tardar o quedarse invisible. Una animación CSS avanza por reloj de documento (no se
// atasca), y con estado base `opacity:1` + fill `backwards` el nodo es VISIBLE por defecto:
// si el navegador ignora la animación (p.ej. prefers-reduced-motion), la slide se ve igual.
(function injectDeckEnterKeyframes(){
  if (typeof document === 'undefined' || document.getElementById('deck-enter-kf')) return;
  const s = document.createElement('style');
  s.id = 'deck-enter-kf';
  s.textContent = '@keyframes deckSlideEnter{from{opacity:0;transform:translateY(28px) scale(0.985)}to{opacity:1;transform:translateY(0) scale(1)}}';
  document.head.appendChild(s);
})();

function SlideLayer({ children }){
  return (
    <div style={{
      position:'absolute', inset:0, opacity:1,
      animation:'deckSlideEnter 600ms cubic-bezier(0.22,1,0.36,1) backwards',
    }}>
      {children}
    </div>
  );
}

function App(){
  const [aspectRatio, setAspectRatio] = React.useState(()=> (window.__TWEAKS && window.__TWEAKS.aspectRatio) || '16:9');
  const stageW = getAspectW(aspectRatio);
  const scale = useScale(stageW);
  const cropOffset = (STAGE_W_FULL - stageW) / 2;

  // Siempre arranca en la primera diapositiva: al recargar NO se recuerda dónde ibas.
  const [slide, setSlide] = React.useState(0);
  const [showOverview, setShowOverview] = React.useState(false);
  const [navVisible, setNavVisible] = React.useState(true);
  const [playing, setPlaying] = React.useState(true);
  const [transitionKey, setTransitionKey] = React.useState(0);
  const [editMode, setEditMode] = React.useState(false);
  const [isFull, setIsFull] = React.useState(false);
  const hideTimer = React.useRef();

  // Pantalla completa: un único toggle para el botón Y la tecla F. El listener
  // `fullscreenchange` mantiene `isFull` (y el icono) en sincronía sea cual sea la
  // vía de salida — botón, tecla F, o Esc del propio navegador.
  const toggleFullscreen = React.useCallback(()=>{
    if (document.fullscreenElement) document.exitFullscreen();
    else document.documentElement.requestFullscreen().catch(()=>{});
  }, []);
  React.useEffect(()=>{
    const onFsChange = ()=> setIsFull(!!document.fullscreenElement);
    document.addEventListener('fullscreenchange', onFsChange);
    return ()=> document.removeEventListener('fullscreenchange', onFsChange);
  }, []);

  // Desbloqueo de audio: los navegadores exigen un gesto antes del primer play().
  // Si el deck lleva locución, mostramos overlay ▶ hasta el primer gesto.
  const hasAudio = React.useMemo(()=> SLIDES.some(s => s.audio), []);
  const [audioUnlocked, setAudioUnlocked] = React.useState(!hasAudio);
  React.useEffect(()=>{
    if (!hasAudio) { window.__audioUnlocked = true; return; }
    const unlock = ()=>{
      if (window.__audioUnlocked) return;
      window.__audioUnlocked = true;
      setAudioUnlocked(true);
      window.dispatchEvent(new CustomEvent('__audioUnlock'));
    };
    window.addEventListener('pointerdown', unlock);
    window.addEventListener('keydown', unlock);
    return ()=>{ window.removeEventListener('pointerdown', unlock); window.removeEventListener('keydown', unlock); };
  }, [hasAudio]);

  // Protocolo edit mode — registrar listener antes de anunciar
  React.useEffect(()=>{
    const onMsg = (e) => {
      if (!e.data || typeof e.data !== 'object') return;
      if (e.data.type === '__activate_edit_mode') setEditMode(true);
      else if (e.data.type === '__deactivate_edit_mode') setEditMode(false);
    };
    window.addEventListener('message', onMsg);
    window.parent.postMessage({type:'__edit_mode_available'}, '*');
    return ()=> window.removeEventListener('message', onMsg);
  }, []);

  const updateTweak = React.useCallback((key, value)=>{
    if (key === 'aspectRatio') setAspectRatio(value);
    window.parent.postMessage({type:'__edit_mode_set_keys', edits:{[key]: value}}, '*');
  }, []);

  React.useEffect(()=>{
    setTransitionKey(k=>k+1);
  }, [slide]);

  const advanceTimer = React.useRef();

  const go = React.useCallback((n)=>{
    clearTimeout(advanceTimer.current);
    setPlaying(true); // salir de una pausa de reflexión reanuda la reproducción
    setSlide(clamp(n, 0, SLIDES.length-1));
  }, []);

  // Selector de ritmo: 0.5× (pausado) · 1× · 2× (rápido)
  const [speed, setSpeed] = React.useState(()=>{
    const saved = parseFloat(localStorage.getItem(DECK_ID + ':speed') || '1');
    return [0.5, 1, 2].includes(saved) ? saved : 1;
  });
  React.useEffect(()=>{
    window.__deckSpeed = speed;
    localStorage.setItem(DECK_ID + ':speed', String(speed));
  }, [speed]);

  // Autoplay: al agotar la duración del slide, avanza solo tras un pequeño hold.
  const handleSlideEnd = React.useCallback(()=>{
    const enabled = !(window.__TWEAKS && window.__TWEAKS.autoAdvance === false);
    if (!enabled) return;
    setSlide(s => {
      if (SLIDES[s] && SLIDES[s].stop) { setPlaying(false); return s; }
      clearTimeout(advanceTimer.current);
      advanceTimer.current = setTimeout(()=>{
        setSlide(x => (x < SLIDES.length-1 ? x+1 : x));
      }, 800);
      return s;
    });
  }, []);

  React.useEffect(()=>{
    if (playing === false) clearTimeout(advanceTimer.current);
  }, [playing]);

  React.useEffect(()=> ()=> clearTimeout(advanceTimer.current), []);

  React.useEffect(()=>{
    window.__deckGoTo = (n) => { clearTimeout(advanceTimer.current); setSlide(clamp(n, 0, SLIDES.length-1)); };
    window.__deckSlideCount = SLIDES.length;
    window.__deckTitles = SLIDES.map(s => s.title);
    window.__deckSeekEnd = () => {
      clearTimeout(advanceTimer.current);
      const ev = new CustomEvent('__deckSeekEnd');
      window.dispatchEvent(ev);
    };
  }, []);

  React.useEffect(()=>{
    const onKey = (e) => {
      if (e.key === 'ArrowRight' || e.key === 'PageDown') { go(slide+1); e.preventDefault(); }
      else if (e.key === 'ArrowLeft' || e.key === 'PageUp') { go(slide-1); e.preventDefault(); }
      else if (e.key === ' ') { setPlaying(p=>!p); e.preventDefault(); }
      else if (e.key === 'Escape') { setShowOverview(s=>!s); }
      else if (e.key === 'f' || e.key === 'F') { toggleFullscreen(); }
      else if (/^[1-9]$/.test(e.key)) { go(parseInt(e.key,10)-1); }
      else if (e.key === '0') { go(9); }
    };
    window.addEventListener('keydown', onKey);
    return ()=> window.removeEventListener('keydown', onKey);
  }, [slide, go, toggleFullscreen]);

  React.useEffect(()=>{
    const onMove = () => {
      setNavVisible(true);
      clearTimeout(hideTimer.current);
      hideTimer.current = setTimeout(()=>setNavVisible(false), 2400);
    };
    window.addEventListener('mousemove', onMove);
    onMove();
    return ()=> { window.removeEventListener('mousemove', onMove); clearTimeout(hideTimer.current); };
  }, []);

  React.useEffect(()=>{
    window.__setPlaying && window.__setPlaying(playing);
  }, [playing]);

  if (!SLIDES.length) {
    return <div style={{position:'absolute',inset:0,display:'flex',alignItems:'center',justifyContent:'center',fontFamily:"'JetBrains Mono',monospace",color:'#64748B'}}>window.DECK.scenes vacío</div>;
  }

  return (
    <div>
      <div className="stage-wrap">
        <div style={{position:'relative',width:stageW,height:STAGE_H,flexShrink:0,transform:`scale(${scale})`,transformOrigin:'center center',overflow:'hidden',background:'#fff',boxShadow:'0 40px 120px rgba(0,133,255,0.18), 0 8px 30px rgba(15,23,42,0.06)'}}>
          <div className="stage" style={{position:'absolute',left:-cropOffset,top:0,width:STAGE_W_FULL,height:STAGE_H,background:'#fff',boxShadow:'none'}}>
            <SlideLayer key={transitionKey}>
              <SlideHost slideIdx={slide} onSlideEnd={handleSlideEnd}/>
            </SlideLayer>
          </div>
        </div>
      </div>

      <div className="progress-outer"><div id="progress-inner" className="progress-inner" style={{width:0}}/></div>

      <div className="nav" style={{opacity:navVisible?1:0,pointerEvents:navVisible?'auto':'none'}}>
        <button onClick={()=>go(slide-1)} title="Anterior (←)">
          <svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M9 3 L4 7 L9 11" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"/></svg>
        </button>
        <button onClick={()=>setPlaying(p=>!p)} title="Play/Pausa (Espacio)">
          {playing ? (
            <svg width="12" height="12" viewBox="0 0 12 12" fill="none"><rect x="2" y="2" width="3" height="8" fill="currentColor"/><rect x="7" y="2" width="3" height="8" fill="currentColor"/></svg>
          ) : (
            <svg width="12" height="12" viewBox="0 0 12 12" fill="none"><polygon points="3,2 10,6 3,10" fill="currentColor"/></svg>
          )}
        </button>
        <button onClick={()=>go(slide+1)} title="Siguiente (→)">
          <svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M5 3 L10 7 L5 11" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"/></svg>
        </button>
        <div className="count">{String(slide+1).padStart(2,'0')} / {String(SLIDES.length).padStart(2,'0')}</div>
        {[0.5,1,2].map(v => (
          <button key={v} onClick={()=>setSpeed(v)} title={`Ritmo ${v}×`} style={{
            fontFamily:"'JetBrains Mono',monospace", fontSize:10, fontWeight:700, width:'auto', padding:'0 8px',
            color: speed===v ? '#fff' : undefined,
            background: speed===v ? 'rgba(0,133,255,0.85)' : undefined,
          }}>{String(v).replace('0.5','½')}×</button>
        ))}
        <button onClick={()=>setShowOverview(true)} title="Vista general (ESC)">
          <svg width="12" height="12" viewBox="0 0 12 12" fill="none"><rect x="1" y="1" width="4" height="4" fill="currentColor"/><rect x="7" y="1" width="4" height="4" fill="currentColor"/><rect x="1" y="7" width="4" height="4" fill="currentColor"/><rect x="7" y="7" width="4" height="4" fill="currentColor"/></svg>
        </button>
        <button onClick={toggleFullscreen} title={isFull ? 'Salir de pantalla completa (F)' : 'Pantalla completa (F)'}>
          {isFull ? (
            <svg width="13" height="13" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"><path d="M4.5 1.5V4.5H1.5M7.5 1.5V4.5H10.5M4.5 10.5V7.5H1.5M7.5 10.5V7.5H10.5"/></svg>
          ) : (
            <svg width="13" height="13" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"><path d="M1.5 4.5V1.5H4.5M10.5 4.5V1.5H7.5M1.5 7.5V10.5H4.5M10.5 7.5V10.5H7.5"/></svg>
          )}
        </button>
      </div>

      <div className="slide-idx">{String(slide+1).padStart(2,'0')} · {(SLIDES[slide].title||'').toUpperCase()}</div>
      <div className="hint">← → NAVEGAR · ESPACIO PAUSA · ESC VISTA · F PANTALLA COMPLETA</div>

      {!audioUnlocked && (
        <div style={{position:'fixed', inset:0, zIndex:80, background:'rgba(8,10,16,0.88)', backdropFilter:'blur(14px)', display:'flex', flexDirection:'column', alignItems:'center', justifyContent:'center', gap:26, cursor:'pointer'}}
             onClick={()=>{ /* el listener global de pointerdown hace el unlock */ }}>
          <div style={{width:120, height:120, borderRadius:100, background:'linear-gradient(135deg,#0085FF,#56E16A)', display:'flex', alignItems:'center', justifyContent:'center', boxShadow:'0 20px 70px rgba(0,133,255,0.5)'}}>
            <svg width="44" height="44" viewBox="0 0 24 24" fill="none"><polygon points="7,4 21,12 7,20" fill="#fff"/></svg>
          </div>
          <div style={{textAlign:'center'}}>
            <div style={{fontFamily:"'IBM Plex Sans',sans-serif", fontSize:26, fontWeight:700, color:'#fff', letterSpacing:'-0.02em'}}>{(window.DECK && window.DECK.title) || 'Reproducir'}</div>
            <div style={{fontFamily:"'JetBrains Mono',monospace", fontSize:12, color:'rgba(255,255,255,0.55)', letterSpacing:'2px', marginTop:10}}>CLIC PARA REPRODUCIR CON VOZ</div>
          </div>
        </div>
      )}

      {showOverview && (
        <div className="overview" onClick={(e)=>{ if(e.target.classList.contains('overview')) setShowOverview(false); }}>
          {SLIDES.map((s,i)=>(
            <div key={i} className="ov-thumb" onClick={()=>{ go(i); setShowOverview(false); }}>
              <div className="ov-title">{s.title}</div>
              <div style={{position:'absolute',inset:0,background: i===slide ? 'rgba(0,133,255,0.08)' : 'linear-gradient(135deg,#F8FAFC,#F0F7FF)', display:'flex',alignItems:'center',justifyContent:'center',fontFamily:"'IBM Plex Sans',sans-serif",fontSize:48,fontWeight:800,color:'rgba(0,133,255,0.15)'}}>{String(i+1).padStart(2,'0')}</div>
              <div className="ov-label">{String(i+1).padStart(2,'0')} · {s.dur}s</div>
            </div>
          ))}
        </div>
      )}

      {editMode && (
        <TweaksPanel aspectRatio={aspectRatio} onChange={updateTweak}/>
      )}
    </div>
  );
}

function TweaksPanel({ aspectRatio, onChange }){
  const presets = [
    { id:'16:9', label:'16:9',  sub:'Desktop · YouTube', w:64, h:36 },
    { id:'4:3',  label:'4:3',   sub:'Clásico',           w:56, h:42 },
    { id:'1:1',  label:'1:1',   sub:'Instagram feed',    w:48, h:48 },
    { id:'4:5',  label:'4:5',   sub:'Instagram vert.',   w:40, h:50 },
    { id:'9:16', label:'9:16',  sub:'Stories · Reels',   w:28, h:50 },
  ];
  return (
    <div style={{
      position:'fixed', right:24, top:24, zIndex:100, width:260,
      background:'rgba(14,18,28,0.92)', backdropFilter:'blur(18px)',
      border:'1px solid rgba(255,255,255,0.08)', borderRadius:14,
      padding:'16px 16px 14px', boxShadow:'0 20px 60px rgba(0,0,0,0.5)',
      color:'#fff', fontFamily:"'IBM Plex Sans',sans-serif",
    }}>
      <div style={{display:'flex',alignItems:'center',justifyContent:'space-between',marginBottom:12}}>
        <div style={{fontSize:13,fontWeight:700,letterSpacing:0.3}}>Tweaks</div>
        <div style={{fontSize:10,color:'rgba(255,255,255,0.4)',fontFamily:"'JetBrains Mono',monospace",letterSpacing:1}}>FORMATO</div>
      </div>
      <div style={{fontSize:11,color:'rgba(255,255,255,0.55)',marginBottom:10,lineHeight:1.35}}>
        Recorta el ancho para adaptarlo a formatos verticales. La altura se mantiene fija.
      </div>
      <div style={{display:'flex',flexDirection:'column',gap:6}}>
        {presets.map(p => {
          const active = p.id === aspectRatio;
          return (
            <button key={p.id} onClick={()=>onChange('aspectRatio', p.id)} style={{
              display:'flex',alignItems:'center',gap:12,padding:'8px 10px',
              background: active ? 'rgba(0,133,255,0.18)' : 'rgba(255,255,255,0.04)',
              border:`1px solid ${active ? 'rgba(0,133,255,0.5)' : 'rgba(255,255,255,0.08)'}`,
              borderRadius:10, color:'#fff', cursor:'pointer', fontFamily:'inherit', textAlign:'left',
              transition:'all 150ms',
            }}>
              <div style={{width:56,height:52,display:'flex',alignItems:'center',justifyContent:'center',flexShrink:0}}>
                <div style={{width:p.w,height:p.h,background: active ? 'linear-gradient(135deg,#0085FF,#56E16A)' : 'rgba(255,255,255,0.15)',borderRadius:3,transition:'all 150ms'}}/>
              </div>
              <div style={{flex:1,minWidth:0}}>
                <div style={{fontSize:13,fontWeight:700,letterSpacing:0.2}}>{p.label}</div>
                <div style={{fontSize:10,color:'rgba(255,255,255,0.5)',marginTop:1}}>{p.sub}</div>
              </div>
              {active && (
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" style={{flexShrink:0}}>
                  <path d="M4 12 L10 18 L20 6" stroke="#56E16A" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"/>
                </svg>
              )}
            </button>
          );
        })}
      </div>
      <div style={{marginTop:12,paddingTop:10,borderTop:'1px solid rgba(255,255,255,0.08)',fontSize:10,color:'rgba(255,255,255,0.45)',fontFamily:"'JetBrains Mono',monospace",letterSpacing:0.5}}>
        {getAspectW(aspectRatio)} × {STAGE_H} px
      </div>
    </div>
  );
}

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