/* ---------- Tipografía pixel (autoalojada) ----------
   'Minecraftia'/'Minecraft Regular' son nombres de fuentes que casi nadie tiene
   instaladas en el sistema (y la de Minecraft es propiedad de Mojang, no
   redistribuible); sin un @font-face real esos nombres nunca hacían match y la
   pila caía en Lucida Console/monospace, que NO es una fuente de píxeles. Aquí
   se autoaloja "Pixelify Sans" (Google Fonts, licencia SIL Open Font License —
   libre de redistribuir) en public/assets/fonts, servida desde el propio origen
   para cumplir la CSP (`font-src 'self'`, sin permitir CDNs externos). Se
   mantienen 'Minecraftia'/'Minecraft Regular' PRIMERO en la pila por si el
   usuario las tiene instaladas de verdad (frecuente entre jugadores/modders de
   Minecraft), y Pixelify Sans queda como la que garantiza el efecto pixel
   incluso si no las tiene.
   OJO: esta pila es SOLO de marca (--pixel). Ver el bloque de tipografía en
   :root: una fuente de mapa de bits escalada no es legible en el texto de
   trabajo, y ahí va --ui. */
@font-face{
  font-family:'Pixelify Sans'; font-style:normal; font-weight:400; font-display:swap;
  src:url('../assets/fonts/PixelifySans-400.woff2') format('woff2');
}
@font-face{
  font-family:'Pixelify Sans'; font-style:normal; font-weight:700; font-display:swap;
  src:url('../assets/fonts/PixelifySans-700.woff2') format('woff2');
}
/* 'LM Roman 8' (Latin Modern, licencia GUST Font License — libre) autoalojada
   por la misma razón que Pixelify Sans arriba: la CSP exige font-src 'self'.
   Es la misma fuente que ya trae instalada MiKTeX en esta máquina (la usa el
   PDF por defecto vía newpx); acá se convierte el .otf de MiKTeX a .woff2
   para el rótulo "EMI" de la portada. */
@font-face{
  font-family:'LM Roman 8'; font-style:normal; font-weight:400; font-display:swap;
  src:url('../assets/fonts/LMRoman8-Regular.woff2') format('woff2');
}
:root{
    --paper:#f6f3ec; --paper-2:#efeadf; --ink:#1b2333; --ink-soft:#556074;
    --line:#d8d2c4; --line-soft:#e7e2d6; --panel:#fffdf7; --panel-2:#f2eee3;
    --accent:#3d4aa0; --accent-2:#5866c9; --accent-soft:#e6e8f6;
    --truss:#c0392b; --beam:#5a5f6b; --support:#1b2333; --load:#c0392b; --pload:#1565c0;
    --dload:#7d3cc0; --dim:#8a8577; --grid:#e4dfd2; --grid-axis:#b9b19c; --settle:#0b7285;
    --ok:#2e7d52; --danger:#b4402f;
    --shadow:0 1px 2px rgba(20,25,40,.08),0 6px 24px rgba(20,25,40,.06);
    --radius:10px;
    /* ---------- Tipografía: tres papeles, tres pilas ----------
       Antes los tres compartían la fuente de píxeles, y eso es lo que hacía la
       interfaz difícil de leer: Minecraftia/Pixelify Sans son fuentes de MAPA DE
       BITS pensadas para un tamaño fijo pequeño, así que escaladas a los 11-13 px
       de una etiqueta o a los 10 px de una cota del lienzo salen dentadas y
       confunden los caracteres parecidos (I/l/1, O/0). En una herramienta de
       cálculo, donde lo que se lee son números, unidades y símbolos, eso no es un
       detalle estético.
         --pixel  IDENTIDAD: el logotipo y el titular de la portada, que van a 19 px
                  y a 32-52 px, que es justo donde una fuente de píxeles se lee
                  bien y aporta el carácter de la marca.
         --ui     TRABAJO: todo lo demás de la interfaz. Pila del SISTEMA, sin
                  descargar nada: son las fuentes que cada plataforma ha afinado
                  para texto de interfaz a tamaño pequeño.
         --mono   DATOS Y CÓDIGO: el panel de LaTeX, las tablas de resultados, las
                  cotas y las reacciones del lienzo. Monoespaciada de verdad, que
                  es lo que alinea las columnas de cifras. */
    --pixel:'Minecraftia','Minecraft Regular','Pixelify Sans','Lucida Console',monospace;
    --ui:system-ui,-apple-system,'Segoe UI',Roboto,'Helvetica Neue','Noto Sans',Arial,sans-serif;
    --mono:'Cascadia Code','Consolas','SF Mono',ui-monospace,'DejaVu Sans Mono','Liberation Mono',monospace;
  }
  @media (prefers-color-scheme: dark){
    :root{
      --paper:#12151c; --paper-2:#0d1016; --ink:#e9ecf5; --ink-soft:#9aa3b8;
      --line:#2a3040; --line-soft:#20252f; --panel:#171b24; --panel-2:#12161e;
      --accent:#8b9bff; --accent-2:#a6b2ff; --accent-soft:#232a3f;
      --truss:#ff6b5a; --beam:#9aa1b3; --support:#cdd3e2; --load:#ff6b5a; --pload:#64b5f6;
      --dload:#c48bff; --dim:#6b7386; --grid:#20252f; --grid-axis:#39414f; --settle:#3bc9db;
      --shadow:0 1px 2px rgba(0,0,0,.4),0 8px 30px rgba(0,0,0,.35);
    }
  }
  :root[data-theme="light"]{
    --paper:#f6f3ec; --paper-2:#efeadf; --ink:#1b2333; --ink-soft:#556074;
    --line:#d8d2c4; --line-soft:#e7e2d6; --panel:#fffdf7; --panel-2:#f2eee3;
    --accent:#3d4aa0; --accent-2:#5866c9; --accent-soft:#e6e8f6;
    --truss:#c0392b; --beam:#5a5f6b; --support:#1b2333; --load:#c0392b; --pload:#1565c0;
    --dload:#7d3cc0; --dim:#8a8577; --grid:#e4dfd2; --grid-axis:#b9b19c; --settle:#0b7285;
  }
  :root[data-theme="dark"]{
    --paper:#12151c; --paper-2:#0d1016; --ink:#e9ecf5; --ink-soft:#9aa3b8;
    --line:#2a3040; --line-soft:#20252f; --panel:#171b24; --panel-2:#12161e;
    --accent:#8b9bff; --accent-2:#a6b2ff; --accent-soft:#232a3f;
    --truss:#ff6b5a; --beam:#9aa1b3; --support:#cdd3e2; --load:#ff6b5a; --pload:#64b5f6;
    --dload:#c48bff; --dim:#6b7386; --grid:#20252f; --grid-axis:#39414f; --settle:#3bc9db;
  }
  *{box-sizing:border-box}
  /* El `display:none` que trae el atributo `hidden` viene de la hoja del
     navegador y lo pisa CUALQUIER regla de clase — `.btn{display:flex}` sin ir
     más lejos. Sin esto, marcar `hidden` un botón no lo oculta: sigue ahí,
     visible y ocupando sitio. Es un fallo silencioso y fácil de repetir, así
     que se corrige una vez para toda la aplicación. */
  [hidden]{display:none !important}
  html,body{height:100%}
  body{
    margin:0; font-family:var(--ui);
    color:var(--ink); background:var(--paper);
    display:flex; flex-direction:column; overflow:hidden;
    -webkit-font-smoothing:antialiased;
  }
  /* ---------- Header ---------- */
  header{
    display:flex; align-items:center; gap:16px; padding:10px 18px;
    background:linear-gradient(180deg,var(--panel),var(--panel-2));
    border-bottom:1px solid var(--line);
    /* header es un flex ITEM de <body> (column flex): sin esto, su ancho
       mínimo automático es el de su contenido SIN envolver (min-width:auto
       de flexbox), así que en móvil desbordaba el viewport en vez de dejar
       que su propio flex-wrap:wrap partiera .hbtns a una segunda fila. */
    min-width:0;
  }
  .brand{display:flex; align-items:baseline; gap:8px; user-select:none}
  .brand .facemark{width:52px; height:auto; color:var(--ink); flex-shrink:0; align-self:center}
  .brand .facemark .delx-eye{transition:transform .12s ease-out}
  /* El logotipo y el titular de la portada son los DOS únicos sitios con la
     fuente de píxeles: van a tamaño grande, que es donde se lee bien, y son los
     que le dan carácter a la marca. El resto de la interfaz va en --ui. */
  .brand .logo{
    font-family:var(--pixel); font-weight:700; font-size:19px; letter-spacing:.5px;
    color:var(--ink);
  }
  .brand .logo em{font-style:italic; color:var(--accent)}
  .brand .tag{font-size:12px; color:var(--ink-soft); font-family:var(--ui); font-style:italic}
  .struct-badge{
    margin-left:4px; font-family:var(--ui); font-size:13px; color:var(--accent);
    background:var(--accent-soft); border:1px solid var(--line);
    padding:3px 11px; border-radius:999px; white-space:nowrap;
  }
  header .spacer{flex:1}
  .hbtns{display:flex; gap:8px; align-items:center}
  /* ---------- Buttons ---------- */
  .btn{
    font:inherit; font-size:13px; color:var(--ink); cursor:pointer;
    background:var(--panel); border:1px solid var(--line); border-radius:8px;
    padding:7px 12px; display:inline-flex; align-items:center; gap:7px;
    transition:.12s ease; white-space:nowrap; line-height:1;
  }
  .btn:hover{border-color:var(--accent); color:var(--accent)}
  .btn:active{transform:translateY(1px)}
  .btn:disabled{opacity:.4; cursor:not-allowed; pointer-events:none}
  .btn.primary{background:var(--accent); border-color:var(--accent); color:#fff}
  .btn.primary:hover{background:var(--accent-2); color:#fff}
  .btn.ghost{background:transparent}
  .btn.icon{width:34px; height:34px; padding:0; justify-content:center}
  .btn .k{font-size:15px}
  /* ---------- Layout ---------- */
  /* position:relative desde el principio (antes solo en móvil): el cartel de
     "suelta la imagen aquí" se ancla al lienzo, y sin esto se anclaba al
     viewport y cubría la pantalla entera. */
  .stage{flex:1; display:flex; min-height:0; position:relative}
  /* Tool rail */
  .rail{
    width:210px; flex-shrink:0; background:var(--panel); border-right:1px solid var(--line);
    padding:14px 12px; overflow-y:auto; display:flex; flex-direction:column; gap:18px;
    transition:width .16s ease, padding .16s ease, opacity .12s ease;
  }
  /* Panel plegable: el contenido del rail/inspector se colapsa a 0 (recortado
     por overflow, sin reflow raro de los inputs al 100%), pero el ASA que lo
     abre y cierra queda SIEMPRE visible entre el panel y el lienzo — así el
     usuario nunca pierde de dónde volver a abrirlo. */
  .rail.collapsed{width:0; padding-left:0; padding-right:0; border-right:none; opacity:0; overflow:hidden}
  /* Conmutador de panel: un icono en la cabecera, al estilo del botón de barra
     lateral de cualquier editor. Antes era una tira vertical de alto completo
     pegada al lienzo, que robaba ancho de forma permanente para no hacer nada
     más que abrir y cerrar. Plegado se atenúa, para que se vea de un vistazo
     si el panel está guardado. */
  .panel-toggle svg{width:19px; height:19px}
  .panel-toggle.collapsed{color:var(--ink-soft); opacity:.65}
  .group{display:flex; flex-direction:column; gap:8px}
  .group > .lbl{
    font-family:var(--ui); font-size:11px; letter-spacing:.14em; text-transform:uppercase;
    color:var(--ink-soft); font-weight:700; padding-left:2px;
  }
  .tool{
    display:flex; align-items:center; gap:11px; padding:9px 11px; border-radius:9px;
    border:1px solid transparent; cursor:pointer; font-size:13.5px; color:var(--ink);
    background:transparent; text-align:left; transition:.12s; user-select:none;
  }
  .tool:hover{background:var(--panel-2)}
  .tool .ic{
    width:26px; height:26px; flex-shrink:0; display:grid; place-items:center;
    border-radius:7px; background:var(--panel-2); color:var(--ink-soft);
  }
  .tool .ic svg{width:17px; height:17px}
  .tool .key{margin-left:auto; font-size:10.5px; color:var(--ink-soft); font-family:var(--mono);
    border:1px solid var(--line); border-radius:5px; padding:1px 5px}
  .tool.active{background:var(--accent-soft); border-color:var(--accent); color:var(--accent); font-weight:600}
  .tool.active .ic{background:var(--accent); color:#fff}
  .tool.danger.active{background:rgba(180,64,47,.12); border-color:var(--danger); color:var(--danger)}
  .tool.danger.active .ic{background:var(--danger)}
  .seg{display:flex; background:var(--panel-2); border:1px solid var(--line); border-radius:8px; padding:3px; gap:3px}
  .seg button{
    flex:1; font:inherit; font-size:12.5px; padding:6px 4px; border:none; border-radius:6px;
    background:transparent; color:var(--ink-soft); cursor:pointer; transition:.12s;
  }
  .seg button.on{background:var(--panel); color:var(--accent); box-shadow:var(--shadow); font-weight:600}
  .row{display:flex; gap:8px; align-items:center}
  .toggle{
    display:flex; align-items:center; justify-content:space-between; gap:10px;
    font-size:13px; padding:7px 9px; border-radius:8px; cursor:pointer; user-select:none;
  }
  .toggle:hover{background:var(--panel-2)}
  .switch{width:36px; height:20px; border-radius:999px; background:var(--line); position:relative; transition:.15s; flex-shrink:0}
  .switch::after{content:""; position:absolute; top:2px; left:2px; width:16px; height:16px; border-radius:50%; background:#fff; transition:.15s; box-shadow:0 1px 2px rgba(0,0,0,.3)}
  .toggle.on .switch{background:var(--accent)}
  .toggle.on .switch::after{left:18px}
  /* Selección múltiple: la propiedad no coincide en todos los elementos. El
     interruptor se dibuja a medias para no afirmar ni un sí ni un no. */
  .toggle.mix .switch{background:var(--ink-soft)}
  .toggle.mix .switch::after{left:10px}
  /* Canvas */
  .canvas-wrap{flex:1; position:relative; min-width:0; background:var(--paper)}
  /* Avance de la pulsación mantenida sobre Seleccionar, que activa la
     selección múltiple táctil (wireTouchMarqueeHold en core.js). Va en el
     borde de ARRIBA del lienzo y no en el propio botón porque ahí lo tapa el
     dedo justo mientras hay que mirarlo. La duración la fija el JS en
     --hold-ms para no repetir TOUCH_MARQUEE_HOLD_MS aquí. */
  .canvas-wrap.holding::before{
    content:""; position:absolute; left:0; top:0; height:2px; width:100%;
    z-index:15; background:var(--accent); transform-origin:left;
    animation:holdProgress var(--hold-ms,1750ms) linear forwards;
  }
  @keyframes holdProgress{from{transform:scaleX(0)} to{transform:scaleX(1)}}
  /* Barra de herramientas ACOPLADA bajo la cabecera (antes flotaba sobre el
     lienzo y tapaba justo la franja donde se dibuja). Es `relative` porque los
     globos de variantes se posicionan respecto de ella. */
  .toolbar{
    position:relative; z-index:20; flex-shrink:0;
    display:flex; gap:3px; align-items:center; padding:5px 10px;
    background:var(--panel-2); border-bottom:1px solid var(--line);
  }
  .toolbar #toolGroup{display:flex; gap:3px; align-items:center}
  /* Globo de variantes: se abre bajo el botón que lo gobierna. JS le pone
     left/top (en coordenadas de PANTALLA); aquí solo el aspecto.
     `fixed` y no `absolute` a propósito: la barra puede llevar overflow para
     desplazarse, y un contenedor con overflow RECORTA a sus descendientes
     absolutos —incluso los que caen por debajo—, así que el globo desaparecía
     entero. Sacándolo del flujo con fixed ya no hay recorte que valga. */
  .toolpop{
    position:fixed; z-index:40; display:none; flex-direction:column; gap:3px;
    background:var(--panel); border:1px solid var(--line); border-radius:10px;
    padding:5px; box-shadow:0 8px 28px rgba(0,0,0,.34); min-width:150px;
  }
  .toolpop.open{display:flex}
  .toolpop button{
    font:inherit; font-size:13px; text-align:left; white-space:nowrap;
    padding:9px 12px; border:1px solid transparent; border-radius:7px;
    background:transparent; color:var(--ink); cursor:pointer; transition:.12s;
  }
  .toolpop button:hover{background:var(--panel-2)}
  .toolpop button.on{background:var(--accent-soft); border-color:var(--accent); color:var(--accent); font-weight:600}
  /* Opción que ahora mismo no aplica: se queda EN SU SITIO, apagada. Ocultarla
     hacía creer que no existe (la rótula, sin ninguna barra dibujada todavía). */
  .toolpop button.off{opacity:.42; cursor:default}
  .toolpop button.off:hover{background:transparent}
  /* Globo SOLO de iconos (apoyos): rejilla de símbolos, sin texto, y debajo los
     campos que necesite el tipo elegido. */
  .toolpop.popicons{min-width:0}
  .popicon-row{display:grid; grid-template-columns:repeat(4,auto); gap:3px}
  .popicon-row button{padding:7px; display:grid; place-items:center}
  .popicon-row button svg{width:26px; height:26px; display:block}
  .toolpop .popfields{margin-top:5px; border-top:1px solid var(--line-soft); padding-top:6px}
  .toolpop .popfields .field{margin-bottom:6px}
  .toolpop .popfields .field:last-child{margin-bottom:0}
  .toolpop .popfields input{width:100%}
  /* Triangulito: avisa de que ese botón esconde opciones debajo */
  .tbtn{position:relative}
  .tbtn-caret{
    position:absolute; right:3px; bottom:3px; width:0; height:0;
    border-left:4px solid transparent; border-top:4px solid currentColor;
    opacity:.55;
  }
  .tbtn{
    width:38px; height:38px; border-radius:9px; border:1px solid transparent; flex-shrink:0;
    background:transparent; color:var(--ink-soft); cursor:pointer;
    display:grid; place-items:center; transition:.12s;
  }
  .tbtn svg{width:20px; height:20px}
  .tbtn:hover{background:var(--panel-2); color:var(--ink)}
  .tbtn.active{background:var(--accent); color:#fff; border-color:var(--accent)}
  .tbtn:disabled{opacity:.32; cursor:default; pointer-events:none}
  .tbtn.touch-marquee-on{position:relative}
  .tbtn.touch-marquee-on::after{content:""; position:absolute; top:3px; right:3px; width:7px; height:7px; border-radius:50%; background:var(--accent); box-shadow:0 0 0 1.5px var(--panel)}
  /* El botón de Seleccionar tiene que quedarse con el gesto del dedo: en el
     móvil la barra se desliza en horizontal (overflow-x, más abajo) y el
     navegador le robaba el gesto para desplazarla, cancelando el puntero a
     mitad de la pulsación mantenida que activa la selección múltiple
     táctil (ver wireTouchMarqueeHold en core.js). Gana a la regla
     `touch-action:manipulation` de más abajo por especificidad. Solo este
     botón: la barra se sigue deslizando arrastrando desde cualquier otro. */
  .tbtn[data-tool="select"]{touch-action:none}
  .tbtn.danger:hover{color:var(--danger)}
  .tbtn.danger.active{background:var(--danger); color:#fff; border-color:var(--danger)}
  .tsep{width:1px; height:22px; background:var(--line); margin:0 3px; flex-shrink:0}
  /* Los botones de la barra que son un carácter (↺ ↻) y no un SVG */
  .tbtn-glyph{font-size:17px; line-height:1}
  svg#board{width:100%; height:100%; display:block; touch-action:none; cursor:default}
  svg#board.mode-connect,svg#board.mode-nload,svg#board.mode-dload,svg#board.mode-hinge{cursor:pointer}
  svg#board.mode-delete{cursor:not-allowed}
  svg#board.panning{cursor:grabbing}
  .hud{
    position:absolute; left:14px; bottom:12px; display:flex; gap:8px; align-items:center;
    font-size:12px; color:var(--ink-soft); font-family:var(--mono);
    background:var(--panel); border:1px solid var(--line); border-radius:8px; padding:5px 10px;
    box-shadow:var(--shadow); pointer-events:none;
  }
  .zoomctl{position:absolute; right:14px; bottom:12px; display:flex; gap:6px}
  /* ---- mando lateral para variar la carga con AVM (refreshAvmVary) ----
     Deslizadores VERTICALES pegados al borde derecho del lienzo: con el móvil
     en la mano, es donde llega el pulgar sin tapar el dibujo. Van por debajo
     del inspector (z-index 30) para que, si el usuario lo abre, no queden
     flotando encima. `touch-action:none` en la pista es imprescindible: sin él
     el navegador se queda el gesto para desplazar la página y la bolita no
     sigue al dedo. La barra de progreso se llena DESDE EL CERO (mitad de la
     pista), no desde abajo, porque el recorrido es simétrico y el signo —el
     sentido de la carga— se lee de un vistazo por el lado que se llena. */
  /* Minimalista, a petición del usuario: sin recuadro, sin cabecera y sin el
     número —el valor ya se lee en el propio dibujo, junto a la carga, y en el
     panel de Propiedades—. Queda solo el rótulo que distingue una pista de la
     otra y la pista misma. Como el contenedor ya no tiene fondo, deja pasar el
     puntero (`pointer-events:none`) y solo la pista lo recibe: si no, el hueco
     transparente entre las dos barritas se comería clics del lienzo. */
  .avmvary{
    position:absolute; right:14px; top:50%; transform:translateY(-50%); z-index:20;
    display:flex; gap:18px; align-items:flex-start; pointer-events:none;
  }
  .avmv-item{display:flex; flex-direction:column; align-items:center; gap:7px}
  .avmv-lbl{font-size:10.5px; color:var(--ink-soft); white-space:nowrap}
  .avmv-track{
    position:relative; width:10px; height:168px; border-radius:6px;
    background:var(--panel-2); border:1px solid var(--line);
    cursor:pointer; touch-action:none; pointer-events:auto;
  }
  /* marca del cero, para saber dónde se invierte el sentido */
  .avmv-track::after{content:""; position:absolute; left:-3px; right:-3px; top:50%;
    height:1px; background:var(--line); opacity:.9}
  .avmv-prog{position:absolute; left:1px; right:1px; border-radius:5px;
    background:var(--accent); opacity:.5; pointer-events:none}
  .avmv-thumb{
    position:absolute; left:50%; width:21px; height:21px; margin-left:-10.5px; margin-bottom:-10.5px;
    border-radius:50%; background:var(--accent); border:2px solid var(--panel);
    box-shadow:var(--shadow); pointer-events:none;
  }
  .avmv-track.dragging .avmv-thumb{transform:scale(1.12)}
  @media (pointer:coarse){
    /* con el dedo: bolita de 30 px y pista más larga, que es lo que hace
       manejable el ajuste fino en el móvil */
    .avmvary{right:10px; gap:24px}
    .avmv-track{width:12px; height:200px}
    .avmv-thumb{width:30px; height:30px; margin-left:-15px; margin-bottom:-15px}
  }
  .hint{
    position:absolute; left:50%; top:62px; transform:translateX(-50%);
    background:var(--ink); color:var(--paper); font-size:12.5px; padding:7px 14px;
    border-radius:999px; box-shadow:var(--shadow); opacity:0; transition:.2s; pointer-events:none;
    font-family:var(--ui); z-index:19; white-space:nowrap; max-width:calc(100% - 24px); overflow:hidden; text-overflow:ellipsis;
  }
  .hint.show{opacity:.94}
  /* Inspector */
  .inspector{
    width:340px; flex-shrink:0; background:var(--panel); border-left:1px solid var(--line);
    display:flex; flex-direction:column; min-height:0;
    transition:width .16s ease, opacity .12s ease;
  }
  .inspector.collapsed{width:0; border-left:none; opacity:0; overflow:hidden}
  .tabs{display:flex; border-bottom:1px solid var(--line)}
  .tabs button{
    flex:1; font:inherit; font-size:13px; padding:11px; border:none; background:transparent;
    color:var(--ink-soft); cursor:pointer; font-family:var(--ui); letter-spacing:.02em;
    border-bottom:2px solid transparent;
  }
  .tabs button.on{color:var(--accent); border-bottom-color:var(--accent); font-weight:700}
  .tabpane{flex:1; overflow-y:auto; padding:16px; display:none}
  .tabpane.on{display:block}
  .card{background:var(--panel-2); border:1px solid var(--line); border-radius:var(--radius); padding:14px; margin-bottom:14px}
  .card h3{margin:0 0 12px; font-family:var(--ui); font-size:15px; font-weight:700; display:flex; align-items:center; gap:8px}
  .card h3 .dot{width:9px; height:9px; border-radius:50%; background:var(--accent)}
  .field{display:flex; flex-direction:column; gap:5px; margin-bottom:11px}
  .field label{font-size:12px; color:var(--ink-soft); font-weight:600}
  .field .fx{font-family:var(--ui); font-style:italic}
  input[type=text],input[type=number],select{
    font:inherit; font-size:13px; color:var(--ink); background:var(--panel);
    border:1px solid var(--line); border-radius:7px; padding:8px 10px; width:100%;
  }
  input:focus,select:focus{outline:none; border-color:var(--accent); box-shadow:0 0 0 3px var(--accent-soft)}
  /* campo con expresión aritmética inválida: el modelo conserva el valor
     anterior, así que se marca el campo en vez de aceptar una entrada rota */
  input.numexpr.bad{border-color:var(--danger); color:var(--danger)}
  /* ---- flechitas ▲▼ de las entradas de VALOR de carga (attachSpin) ----
     Los campos son .numexpr (type=text, para poder teclear "2*3" y el signo
     menos en el teclado del móvil), así que el incrementador nativo de
     input[type=number] no existe: se dibuja aparte, superpuesto a la derecha
     del campo, y el paso lo aplica attachSpin disparando un evento `input`
     —el mismo camino que teclear— para que el modelo y el lienzo se enteren. */
  .spinwrap{position:relative; display:block}
  .spinwrap>input{padding-right:26px}
  .spin{position:absolute; right:5px; top:50%; transform:translateY(-50%);
    display:flex; flex-direction:column; gap:1px}
  .spin button{cursor:pointer; border:1px solid var(--line); background:var(--panel-2);
    color:var(--ink-soft); font-size:7px; line-height:1; padding:2px 3px; border-radius:3px;
    width:17px; display:flex; align-items:center; justify-content:center;
    -webkit-user-select:none; user-select:none; touch-action:none}
  .spin button:hover{background:var(--accent-soft); color:var(--accent); border-color:var(--accent)}
  .spin button:active{background:var(--accent); color:var(--panel)}
  @media (pointer:coarse){
    /* con el dedo, el par de flechas ocupa el alto del campo (dos blancos de
       ~22px cada uno): más pequeño no se acierta */
    .spinwrap>input{padding-right:34px}
    .spin{right:3px}
    .spin button{width:24px; padding:4px 3px; font-size:8px}
  }
  /* tabla de numeración (nudos y GDL) del método con restricciones cinemáticas */
  .numtable{width:100%; border-collapse:collapse; font-size:12.5px}
  .numtable th,.numtable td{padding:3px 4px; text-align:center; border-bottom:1px solid var(--line)}
  .numtable th{color:var(--ink-soft); font-weight:600}
  .numtable td:first-child,.numtable th:first-child{text-align:left}
  .numtable input{width:100%; padding:4px 5px; text-align:center}
  .numtable .fixedcell{color:var(--ink-soft)}
  .numtable input::placeholder{color:var(--ink-soft); opacity:.55}
  /* aviso de numeración: ámbar cuando solo avisa (nudos), rojo cuando impide
     calcular (GDL) — el color distingue de un vistazo "revisa esto" de "esto
     no se va a calcular" */
  .numwarn{margin-top:8px; padding:8px 10px; border-radius:7px; font-size:12px; line-height:1.5;
    border:1px solid var(--warn,#b8860b); background:rgba(184,134,11,.10); color:var(--ink)}
  .numwarn.block{border-color:var(--danger); background:rgba(180,64,47,.10)}
  .numwarn b{display:block; margin-bottom:3px}
  .numwarn ul{margin:0 0 4px 16px; padding:0}
  .grid2{display:grid; grid-template-columns:1fr 1fr; gap:10px}
  .muted{color:var(--ink-soft); font-size:12.5px; line-height:1.6}
  .empty{text-align:center; color:var(--ink-soft); padding:30px 10px; font-family:var(--ui); font-style:italic}
  .empty .big{font-size:34px; opacity:.5; margin-bottom:8px}
  .loadlist{display:flex; flex-direction:column; gap:6px}
  .loadchip{display:flex; align-items:center; gap:8px; font-size:12.5px; background:var(--panel); border:1px solid var(--line); border-radius:7px; padding:6px 9px}
  .loadchip .rm{margin-left:auto; cursor:pointer; color:var(--danger); font-weight:700; border:none; background:none; font-size:15px; line-height:1}
  /* edición de cargas directamente en el panel de propiedades (sin ventana emergente) */
  .loadedit{display:flex; flex-direction:column; gap:7px; background:var(--panel); border:1px solid var(--line); border-radius:7px; padding:9px}
  .loadedit .loadedit-head{display:flex; align-items:center; justify-content:space-between; font-size:12px; color:var(--ink-soft); font-weight:600}
  .loadedit .loadedit-head .rm{cursor:pointer; color:var(--danger); font-weight:700; border:none; background:none; font-size:15px; line-height:1}
  .loadedit .loadedit-head .secName{flex:1; min-width:0; border:none; background:transparent; color:var(--ink); font:inherit; font-weight:600; padding:2px 4px; border-radius:5px}
  .loadedit .loadedit-head .secName:hover,.loadedit .loadedit-head .secName:focus{background:var(--panel-2); outline:none}
  .toggle.disabled{cursor:not-allowed; opacity:.65}
  .toggle.disabled:hover{background:transparent}
  .codebar{display:flex; gap:8px; padding:12px 14px; border-bottom:1px solid var(--line); flex-wrap:wrap}
  pre#code{
    flex:1; margin:0; overflow:auto; padding:14px 16px; font-family:var(--mono); font-size:11.5px;
    line-height:1.55; color:var(--ink); background:var(--paper-2); white-space:pre; tab-size:2;
  }
  pre#code .cmd{color:var(--accent)}
  pre#code .cm{color:var(--ink-soft); font-style:italic}
  pre#code .env{color:var(--dload); font-weight:600}
  .toast{
    position:fixed; bottom:20px; left:50%; transform:translateX(-50%) translateY(20px);
    background:var(--ink); color:var(--paper); padding:10px 18px; border-radius:10px;
    font-size:13px; box-shadow:var(--shadow); opacity:0; transition:.25s; pointer-events:none; z-index:50;
  }
  .toast.show{opacity:1; transform:translateX(-50%) translateY(0)}
  .stat{display:flex; justify-content:space-between; font-size:12.5px; padding:4px 0; border-bottom:1px dashed var(--line-soft)}
  .stat:last-child{border:none}
  .stat b{font-family:var(--ui)}
  .minibtn{font-size:11.5px; padding:4px 9px}
  .quickdirs{display:flex; gap:6px; margin-top:7px}
  .quickdirs button{
    flex:1; font:inherit; font-size:12px; padding:6px 0; border:1px solid var(--line);
    border-radius:7px; background:var(--panel-2); color:var(--ink-soft); cursor:pointer; transition:.12s;
  }
  .quickdirs button:hover{border-color:var(--accent); color:var(--accent); background:var(--accent-soft)}
  .quickdirs button.on{border-color:var(--accent); color:var(--accent); background:var(--accent-soft); font-weight:600}
  /* ============================================================
     LA MASCOTA AVISA (estructura inestable)
     ============================================================
     Aparece junto al lienzo, no en el centro: el usuario acaba de ver caerse su
     estructura y tiene que seguir viéndola mientras lee el aviso. */
  .mascot{
    position:fixed; left:50%; bottom:26px; transform:translate(-50%,14px);
    z-index:60; display:flex; align-items:flex-end; gap:10px;
    opacity:0; pointer-events:none; transition:opacity .25s, transform .25s;
  }
  .mascot.show{opacity:1; transform:translate(-50%,0); pointer-events:auto; cursor:pointer}
  .mascot-face{color:var(--ink); flex:0 0 auto}
  .mascot-face .facemark{width:58px; height:auto; display:block}
  /* el guiño: el ojo derecho se cierra y se abre un par de veces */
  .mascot.show .facemark .delx-eye:nth-of-type(2){
    transform-box:fill-box; transform-origin:center; animation:delxWink 2.2s ease-in-out .35s 2;
  }
  @keyframes delxWink{0%,38%{transform:scaleY(1)}44%{transform:scaleY(.12)}50%,100%{transform:scaleY(1)}}
  .mascot-bubble{
    position:relative; max-width:min(340px,72vw); background:var(--panel); color:var(--ink);
    border:1px solid var(--line); border-radius:12px; padding:10px 13px; font-size:13px;
    line-height:1.5; box-shadow:var(--shadow);
  }
  .mascot-bubble::after{                     /* pico del globo, hacia la carita */
    content:""; position:absolute; right:-7px; bottom:14px; width:12px; height:12px;
    background:var(--panel); border-right:1px solid var(--line); border-top:1px solid var(--line);
    transform:rotate(45deg);
  }
  /* ============================================================
     VENTANA EMERGENTE DE CARGA
     ============================================================
     La misma ventana para toda carga (nodal, puntual sobre barra y repartida):
     se abre al colocarla y con doble clic o doble toque sobre una ya puesta.
     Va centrada y no a pie de puntero: con el dedo, el teclado del móvil sube
     desde abajo y una ventana anclada al toque quedaría debajo de él. */
  .ldpop-back{
    position:fixed; inset:0; background:rgba(10,14,22,.52); z-index:97;
    display:none; align-items:center; justify-content:center; padding:14px;
  }
  .ldpop-back.open{display:flex}
  .ldpop{
    /* el ancho lo pide el contenido, no al revés: las entradas numéricas de la
       ventana son cortas (un valor y un ángulo, no una expresión larga), así
       que estirarlas a todo lo ancho solo servía para hacer la ventana enorme */
    width:max-content; max-width:min(340px,94vw); max-height:88vh; max-height:88dvh; background:var(--panel);
    border:1px solid var(--line); border-radius:12px; overflow:hidden;
    display:flex; flex-direction:column; box-shadow:0 12px 60px rgba(0,0,0,.4);
  }
  .ldpop-head{
    display:flex; align-items:center; gap:8px; padding:9px 12px;
    border-bottom:1px solid var(--line); background:var(--panel-2);
  }
  .ldpop-head .t{flex:1; font-family:var(--ui); font-weight:700; color:var(--ink); font-size:13.5px}
  .ldpop-head .x{
    border:none; background:none; color:var(--ink-soft); cursor:pointer;
    font-size:19px; line-height:1; padding:0 2px;
  }
  .ldpop-head .x:hover{color:var(--danger)}
  .ldpop-body{padding:12px; overflow:auto; display:flex; flex-direction:column; gap:9px}
  .ldpop-body .field{margin-bottom:0}
  /* entradas cortas: caben holgadamente 8–9 cifras más las flechitas */
  .ldpop-body .field input{width:132px}
  .ldpop-body .grid2{grid-template-columns:auto auto; justify-content:start}
  .ldpop-body .analyze-note,.ldpop-body .senso{max-width:290px}
  .ldpop-foot{
    display:flex; gap:8px; padding:9px 12px; border-top:1px solid var(--line);
    background:var(--panel-2);
  }
  .ldpop-foot .del{color:var(--danger)}
  .ldpop-foot .ok{margin-left:auto}
  /* entradas a la izquierda, reloj a la derecha: en columna la ventana crecía
     a lo alto sin aprovechar el ancho que ya ocupa el dial */
  .lprow{display:flex; gap:11px; align-items:flex-start}
  .lprow .lpcol{flex:1 1 auto; min-width:0; display:flex; flex-direction:column; gap:9px}
  .lprow .lpder{flex:0 0 auto; display:flex; flex-direction:column; gap:6px; align-items:center}
  .lprow .angdial{padding-top:2px}
  .lprow .angsvg{width:134px; height:123px}
  /* el reloj sigue a la vista cuando no manda (dirección fijada por la barra),
     pero atenuado y sordo al puntero: si desapareciera, la ventana cambiaría
     de tamaño cada vez que se alterna de modo */
  .lpDial.off,.field.off{opacity:.34; pointer-events:none}
  @media (max-width:420px){          /* móvil estrecho: el reloj baja debajo */
    .lprow{flex-wrap:wrap; justify-content:center}
    .lprow .lpcol{flex:1 1 100%}
  }
  /* modos de dirección de una fuerza de vano, en iconos */
  .dirseg{display:flex; gap:5px; justify-content:center}
  .dirseg button{
    display:grid; place-items:center; padding:5px; cursor:pointer; transition:.12s;
    border:1px solid var(--line); border-radius:7px; background:var(--panel-2); color:var(--ink-soft);
  }
  .dirseg button svg{width:26px; height:26px; display:block}
  .dirseg .ic-bar{stroke:currentColor; stroke-width:1.6; opacity:.55}
  .dirseg .ic-arr{stroke:currentColor; stroke-width:1.9; stroke-linecap:round}
  .dirseg .ic-hd{fill:currentColor}
  .dirseg button:hover{border-color:var(--accent); color:var(--accent); background:var(--accent-soft)}
  .dirseg button.on{border-color:var(--accent); color:var(--accent); background:var(--accent-soft)}
  /* reloj de ángulo: la aguja apunta a donde empuja la carga y los cuatro
     cuadrantales son pulsables (touch-action:none, o arrastrar sobre el dial
     desplazaría la página en el móvil en vez de girar la aguja) */
  .angdial{display:flex; justify-content:center; padding:1px 0}
  .angsvg{width:152px; height:139px; touch-action:none; user-select:none; overflow:visible}
  .angsvg .aro{fill:var(--panel-2); stroke:var(--line); stroke-width:1.4}
  .angsvg .tk{stroke:var(--line); stroke-width:1}
  .angsvg .tk90{stroke:var(--ink-soft); stroke-width:1.6}
  .angsvg .cuad{fill:var(--panel); stroke:var(--line); stroke-width:1.4; cursor:pointer}
  .angsvg .cuad:hover{fill:var(--accent-soft); stroke:var(--accent)}
  .angsvg .cuad.on{fill:var(--accent); stroke:var(--accent)}
  .angsvg .cuadlbl{fill:var(--ink-soft); font-size:9.5px; text-anchor:middle; cursor:pointer}
  .angsvg .cuadlbl:hover{fill:var(--accent)}
  .angsvg .aguja{stroke:var(--load); stroke-width:2.6; stroke-linecap:round}
  .angsvg .punta{fill:var(--load)}
  .angsvg .eje{fill:var(--ink-soft)}
  /* sentido de un momento: dos botones grandes con el símbolo, sin signos */
  .senso{display:flex; gap:8px}
  .senso button{
    flex:1; font:inherit; font-size:12px; padding:9px 0 7px; border:1px solid var(--line);
    border-radius:8px; background:var(--panel-2); color:var(--ink-soft); cursor:pointer;
    transition:.12s; display:flex; flex-direction:column; align-items:center; gap:2px;
  }
  .senso button .gir{font-size:21px; line-height:1}
  .senso button:hover{border-color:var(--accent); color:var(--accent); background:var(--accent-soft)}
  .senso button.on{border-color:var(--accent); color:var(--accent); background:var(--accent-soft); font-weight:600}
  /* ============================================================
     IMPORTAR DESDE IMAGEN
     ============================================================ */
  .imp-back{
    position:fixed; inset:0; background:rgba(10,14,22,.62); z-index:96;
    display:none; align-items:center; justify-content:center;
  }
  .imp-back.open{display:flex}
  .imp-panel{
    width:min(1180px,95vw); height:92vh; height:92dvh; background:var(--panel);
    border:1px solid var(--line); border-radius:14px; overflow:hidden;
    display:flex; flex-direction:column; box-shadow:0 12px 60px rgba(0,0,0,.4);
  }
  .imp-bar,.imp-foot{
    display:flex; gap:8px; align-items:center; padding:8px 12px;
    font-size:12.5px; color:var(--ink-soft); flex:0 0 auto;
  }
  .imp-bar{border-bottom:1px solid var(--line)}
  .imp-foot{border-top:1px solid var(--line); background:var(--panel-2)}
  .imp-bar .t{font-family:var(--ui); font-weight:700; color:var(--ink); font-size:13.5px}
  .imp-spacer{flex:1 1 auto}
  .imp-note{font-size:11.5px; color:var(--ink-soft); line-height:1.45}
  .imp-body{flex:1 1 auto; display:none; min-height:0}
  .imp-preview{
    flex:1 1 auto; min-width:0; overflow:auto; padding:8px;
    background:var(--paper-2); display:flex; align-items:flex-start; justify-content:center;
  }
  /* La imagen y su superposición comparten caja: `.imp-shot` mide EXACTAMENTE
     lo que la imagen —no lo que el panel—, y el <svg> lleva el mismo viewBox
     que el mapa de bits. Anclando el SVG al panel se estiraba a todo el alto
     disponible y el dibujo reconocido aparecía desplazado. */
  .imp-shot{position:relative; width:100%; align-self:flex-start}
  .imp-shot img{display:block; width:100%; height:auto}
  .imp-ov{position:absolute; inset:0; width:100%; height:100%; pointer-events:none}
  .imp-ov .ov-bar{stroke:var(--accent); stroke-width:2.2; opacity:.85}
  .imp-ov .ov-bar.truss{stroke:var(--truss)}
  .imp-ov .ov-node{fill:var(--panel); stroke:var(--accent); stroke-width:1.8}
  .imp-ov .ov-node.sup{stroke:var(--ok); stroke-width:2.4}
  .imp-ov .ov-node.hinge{fill:var(--dload)}
  .imp-ov .ov-lbl{fill:var(--accent); font-size:11px; text-anchor:middle; font-family:var(--mono)}
  .imp-ov .ov-load{stroke:var(--load); stroke-width:2.2; opacity:.9}
  .imp-ov .ov-dim{fill:none; stroke:var(--dim); stroke-width:1.6; stroke-dasharray:3 2}
  .imp-side{
    flex:0 0 340px; width:340px; overflow-y:auto; padding:10px;
    border-left:1px solid var(--line); background:var(--panel);
    display:flex; flex-direction:column; gap:9px;
  }
  .imp-card{border:1px solid var(--line-soft); border-radius:10px; padding:8px 9px; background:var(--panel-2)}
  .imp-card.warn{border-color:var(--danger)}
  .imp-h{font-size:11px; font-weight:700; letter-spacing:.04em; text-transform:uppercase;
         color:var(--ink-soft); margin-bottom:6px}
  .imp-sum{display:flex; flex-wrap:wrap; gap:6px; align-items:center; font-size:12px; color:var(--ink)}
  .imp-chip{background:var(--accent-soft); color:var(--accent); border-radius:6px;
            padding:2px 7px; font-weight:700; font-size:11.5px}
  .imp-dims{display:flex; flex-direction:column; gap:5px}
  .imp-dim,.imp-row{
    display:flex; align-items:center; gap:6px; padding:3px 4px; border-radius:7px;
    border:1px solid transparent;
  }
  /* Marca de "esto lo he leído con poca confianza": es la señal que dirige la
     mirada del usuario a las dos o tres casillas que de verdad hay que mirar,
     en vez de obligarle a repasar todas. Se quita en cuanto se toca el campo. */
  .imp-dim.low,.imp-row.low{border-color:var(--danger); background:rgba(180,64,47,.07)}
  .imp-thumb{
    height:24px; width:64px; object-fit:contain; object-position:left center;
    background:var(--paper); border:1px solid var(--line-soft); border-radius:5px; padding:1px;
  }
  .imp-thumb.none{
    display:flex; align-items:center; justify-content:center;
    font-size:9.5px; color:var(--ink-soft); font-family:var(--mono);
  }
  .imp-val,.imp-load,.imp-dload{
    flex:1 1 0; min-width:0; width:100%; padding:5px 7px; font-size:12.5px;
    border:1px solid var(--line); border-radius:7px; background:var(--panel);
    color:var(--ink); font-family:var(--mono);
  }
  .imp-val.bad,.imp-load.bad,.imp-dload.bad{border-color:var(--danger)}
  .imp-u{font-size:11px; color:var(--ink-soft); min-width:30px}
  .imp-tag{font-size:11px; color:var(--ink-soft); font-family:var(--mono); min-width:52px}
  .imp-dir{font-size:14px; color:var(--load); min-width:24px; text-align:center}
  .imp-why{font-size:10.5px; color:var(--ink-soft); flex:1 1 100%; padding-left:52px}
  .imp-sup{
    flex:1 1 0; min-width:0; padding:4px 6px; font-size:12px; border-radius:7px;
    border:1px solid var(--line); background:var(--panel); color:var(--ink); font-family:var(--ui);
  }
  .imp-wait,.imp-err{
    flex:1 1 auto; display:none; flex-direction:column; gap:12px;
    align-items:center; justify-content:center; padding:24px; text-align:center;
    color:var(--ink-soft); font-size:13px;
  }
  .imp-err-msg{color:var(--danger); font-size:14px; font-weight:700}
  .imp-spin{
    width:34px; height:34px; border-radius:50%;
    border:3px solid var(--line); border-top-color:var(--accent);
    animation:impSpin .9s linear infinite;
  }
  @keyframes impSpin{to{transform:rotate(360deg)}}
  /* el lienzo acepta que le suelten encima la imagen del enunciado */
  .stage.dropping::after{
    content:"Suelta la imagen del ejercicio";
    position:absolute; inset:8px; z-index:60; pointer-events:none;
    border:2px dashed var(--accent); border-radius:12px;
    background:var(--accent-soft); opacity:.92;
    display:flex; align-items:center; justify-content:center;
    font-family:var(--ui); font-size:15px; color:var(--accent);
  }

  /* visor de PDF compilado */
  .pdf-back{
    position:fixed; inset:0; background:rgba(10,14,22,.62); z-index:95;
    display:none; align-items:center; justify-content:center;
  }
  .pdf-back.open{display:flex}
  .pdf-panel{
    /* 93dvh en vez de 93vh: en Safari de iOS, vh mide el viewport SIN la barra
       de direcciones, así que con la barra visible el panel se sale por abajo y
       recorta los botones. dvh sigue el alto realmente disponible; la línea vh
       queda antes como reserva para navegadores que no lo entiendan. */
    width:min(1100px,95vw); height:93vh; height:93dvh; background:var(--panel);
    border:1px solid var(--line); border-radius:14px; overflow:hidden;
    display:flex; flex-direction:column; box-shadow:0 12px 60px rgba(0,0,0,.4);
  }
  .pdf-bar{
    display:flex; gap:8px; align-items:center; padding:8px 12px;
    border-bottom:1px solid var(--line); font-size:12.5px; color:var(--ink-soft);
  }
  .pdf-bar .t{font-family:var(--ui); font-weight:700; color:var(--ink); font-size:13.5px}
  .pdf-body{flex:1; position:relative; background:#525659}
  #pdfFrame{position:absolute; inset:0; width:100%; height:100%; border:none}
  .pdf-status{
    position:absolute; inset:0; display:flex; flex-direction:column; gap:10px;
    align-items:center; justify-content:center; color:#eee; font-size:14px;
    font-family:var(--ui); pointer-events:none; transition:.3s;
  }
  .pdf-status.hide{opacity:0}
  /* ---- cargador del visor de PDF: el logo aguantando la carga ----
     El logo de DELX ya ES un pórtico con ojos, así que en vez de un reloj
     giratorio se le pone encima una carga (las mismas flechas que la app dibuja
     sobre las barras) y se le nota el esfuerzo: la estructura se acorta
     (scaleY), se ensancha un poco, tiembla justo en el pico y aprieta los ojos.
     Un único ciclo de 1.6 s sincroniza todas las capas. */
  .delx-loader{width:132px; height:auto; overflow:visible}
  .delx-loader .ld-arrows line{stroke:#e8a33d; stroke-width:2.1; stroke-linecap:round}
  .delx-loader .ld-arrows polygon{fill:#e8a33d}
  .delx-loader .ld-eye{fill:currentColor}
  .delx-loader .ld-drop{fill:#63c8ff; stroke:#0d5c8a; stroke-width:.9}
  .delx-loader .ld-shine{fill:#fff; opacity:.9}
  /* cada capa se anima sobre su propio origen */
  .delx-loader .ld-body,.delx-loader .ld-eye,.delx-loader .ld-sweat{transform-box:fill-box}
  .delx-loader .ld-body{transform-origin:50% 100%; animation:ldSquash 1.6s ease-in-out infinite}
  .delx-loader .ld-shake{animation:ldTremble 1.6s linear infinite}
  .delx-loader .ld-load{animation:ldPress 1.6s ease-in-out infinite}
  .delx-loader .ld-eye{transform-origin:center; animation:ldSquint 1.6s ease-in-out infinite}
  /* fill-mode backwards: durante el retardo se aplica ya el fotograma 0% (gota
     invisible). Sin esto, la gota retrasada se queda VISIBLE y quieta hasta que
     arranca su animación. */
  .delx-loader .ld-sweat{transform-origin:center top; animation:ldSweat 1.6s ease-in-out infinite backwards}
  .delx-loader .ld-sweat2{animation-delay:.22s}
  /* la estructura acusa la carga: se acorta y se abomba */
  @keyframes ldSquash{
    0%,100%{transform:scale(1,1)}
    20%{transform:scale(1.015,.975)}
    50%{transform:scale(1.032,.944)}
    70%{transform:scale(1.012,.982)}
    86%{transform:scale(.996,1.009)}     /* rebote al recuperarse */
  }
  /* tembleque concentrado en el momento de máximo esfuerzo */
  @keyframes ldTremble{
    0%,36%,68%,100%{transform:translateX(0)}
    40%{transform:translateX(-.5px)} 44%{transform:translateX(.55px)}
    48%{transform:translateX(-.55px)} 52%{transform:translateX(.5px)}
    56%{transform:translateX(-.4px)} 62%{transform:translateX(.28px)}
  }
  @keyframes ldPress{
    0%,100%{transform:translateY(0)}
    20%{transform:translateY(.9px)}
    50%{transform:translateY(2.3px)}
    70%{transform:translateY(.8px)}
    86%{transform:translateY(-.5px)}
  }
  @keyframes ldSquint{
    0%,14%{transform:scaleY(1)}
    34%{transform:scaleY(.55)}
    50%,62%{transform:scaleY(.16)}       /* ojos apretados: >_< */
    80%{transform:scaleY(.85)}
    100%{transform:scaleY(1)}
  }
  /* Movimiento reducido: un cargador CONGELADO es mala señal (no se distingue de
     la app colgada), así que en vez de pararlo todo se quitan los gestos bruscos
     —tembleque y apretar los ojos— y queda un esfuerzo lento y suave que sigue
     diciendo "estoy trabajando".
     OJO al depurar: Chrome headless activa esta preferencia POR DEFECTO, y
     Windows la activa con Configuración → Accesibilidad → Efectos visuales →
     "Efectos de animación" desactivado. */
  /* la gota: brota de golpe en el pico del esfuerzo, resbala y cae.
     Se ve durante ~1 s de cada 1.6 s para que dé tiempo a notarla. */
  @keyframes ldSweat{
    0%,24%{opacity:0; transform:translateY(-2px) scale(.35)}
    32%{opacity:1; transform:translateY(0) scale(1.2)}       /* brota */
    40%{opacity:1; transform:translateY(1px) scale(1)}
    68%{opacity:1; transform:translateY(13px) scale(1)}      /* resbala */
    86%{opacity:.55; transform:translateY(21px) scale(.9)}
    94%,100%{opacity:0; transform:translateY(26px) scale(.75)}
  }
  @media (prefers-reduced-motion: reduce){
    .delx-loader .ld-shake,.delx-loader .ld-eye,.delx-loader .ld-sweat{animation:none !important}
    .delx-loader .ld-sweat{opacity:0}
    .delx-loader .ld-body,.delx-loader .ld-load{animation-duration:3.4s}
  }
  /* menú desplegable (Ejemplo) */
  .dropdown{position:relative}
  .dropdown .menu{
    position:absolute; top:calc(100% + 6px); right:0; z-index:30; min-width:230px;
    background:var(--panel); border:1px solid var(--line); border-radius:10px;
    box-shadow:var(--shadow); padding:6px; display:none;
  }
  .dropdown .menu.open{display:block}
  .dropdown .menu button{
    display:flex; flex-direction:column; align-items:flex-start; gap:2px; width:100%;
    font:inherit; font-size:13px; text-align:left; padding:8px 10px; border:none;
    border-radius:7px; background:transparent; color:var(--ink); cursor:pointer;
    font-family:var(--ui); font-weight:600;
  }
  .dropdown .menu button span{font-size:11.5px; color:var(--ink-soft); font-family:system-ui; font-weight:400}
  .dropdown .menu button:hover{background:var(--accent-soft); color:var(--accent)}
  /* resultados */
  .restable{width:100%; border-collapse:collapse; font-size:12px; margin-top:6px}
  .restable th,.restable td{text-align:right; padding:4px 7px; border-bottom:1px solid var(--line-soft)}
  .restable th{color:var(--ink-soft); font-weight:600; font-family:var(--ui)}
  .restable td:first-child,.restable th:first-child{text-align:left; font-family:var(--ui); font-style:italic}
  .restable td{font-family:var(--mono)}
  .restable tr:hover td{background:var(--panel-2)}
  .pos{color:#1f9d55} .neg{color:#c0392b}
  .slider{width:100%; accent-color:var(--accent)}
  .segsm button{font-size:11.5px; padding:5px 3px}
  .analyze-note{font-size:11.5px; color:var(--ink-soft); line-height:1.5; margin-top:6px}
  #pane-code > .analyze-note{display:none}
  .donation-actions{display:flex; flex-direction:column; gap:8px; margin:14px 14px 0}
  .donation-actions .btn{width:100%; justify-content:center}
  .kofi-btn{background:#ff5e5b; border-color:#ff5e5b; color:#fff; text-decoration:none}
  .kofi-btn:hover{background:#e84f4c; border-color:#e84f4c; color:#fff}
  .paypal-btn{background:#0070ba; border-color:#0070ba; color:#fff; text-decoration:none}
  .paypal-btn:hover{background:#005ea6; border-color:#005ea6; color:#fff}
  .yape-btn{background:#6c2bd9; border-color:#6c2bd9; color:#fff}
  .yape-btn:hover{background:#5820b5; border-color:#5820b5; color:#fff}
  .donation-message{margin:0 14px 14px; color:var(--ink-soft); font-size:11.5px; line-height:1.5; text-align:center}
  .yape-dialog{width:min(92vw,390px); padding:18px; border:1px solid var(--line); border-radius:16px; background:var(--panel); color:var(--ink); box-shadow:0 18px 60px rgba(0,0,0,.35)}
  .yape-dialog::backdrop{background:rgba(13,20,32,.58); backdrop-filter:blur(2px)}
  .yape-dialog-head{display:flex; align-items:flex-start; gap:14px; margin-bottom:14px}
  .yape-dialog-head > div{flex:1}
  .yape-dialog h2{margin:0; font-family:var(--ui); font-size:20px}
  .yape-dialog p{margin:5px 0 0; color:var(--ink-soft); font-size:13px; line-height:1.45}
  .yape-dialog .btn.icon{flex:0 0 auto; font-size:23px; line-height:1}
  .yape-qr{display:block; width:min(100%,310px); margin:0 auto; border-radius:10px}
  .legend{display:flex; gap:12px; flex-wrap:wrap; font-size:11px; color:var(--ink-soft); margin-top:8px}
  .legend span{display:inline-flex; align-items:center; gap:5px}
  .legend i{width:14px; height:3px; border-radius:2px; display:inline-block}
  .okbadge{font-size:11px; padding:2px 8px; border-radius:999px; background:rgba(31,157,85,.14); color:#1f9d55; border:1px solid rgba(31,157,85,.3)}
  .errbadge{font-size:12px; padding:8px 11px; border-radius:8px; background:rgba(192,57,43,.12); color:#c0392b; border:1px solid rgba(192,57,43,.3)}
  @media (max-width:1080px){ .inspector{width:290px} .rail{width:184px} }

  /* ============================================================
     PANTALLA ANGOSTA (móvil)
     Los tres paneles en fila no caben: rail e inspector juntos ya suman más
     que el ancho de un teléfono y al lienzo no le quedaría sitio. Aquí los dos
     laterales dejan de robar ancho y pasan a FLOTAR sobre el dibujo, que así
     ocupa la pantalla entera. El asa de cada uno sigue en el flujo, pegada a
     su borde, para que nunca se pierda por dónde volver a abrirlos.
     ============================================================ */
  @media (max-width:820px){
    /* La cabecera es una sola fila sin envolver: en un teléfono la marca se
       come el ancho y los botones de la derecha —incluido «Analizar»— quedaban
       FUERA de la pantalla, sin ninguna otra forma de llegar a ellos. Se quita
       lo decorativo (el subtítulo y el badge, que repite lo que ya dice el
       panel de Resultados), se encoge el logotipo y, como red de seguridad, se
       deja que la fila se parta en dos antes que recortar nada. */
    header{flex-wrap:wrap; gap:10px; padding:10px 12px}
    /* «LaTeX» se va: hace lo mismo que la pestaña LaTeX del panel, y cada
       botón que sobra en la cabecera es una fila más robada al dibujo */
    /* El logotipo dibujado se va en móvil: su gracia es que los ojos siguen al
       cursor, y con el dedo eso no existe. Se queda la palabra DELx, que es lo
       que identifica. Cada cosa que sobra en la cabecera parte la fila en dos
       y le roba ~55 px de alto al dibujo. */
    .brand .tag,.struct-badge,#genBtn,.brand .facemark{display:none}
    .brand .logo{font-size:15px}
    header{gap:8px}
    .hbtns{gap:6px}
    header .spacer{flex:1 1 0; min-width:0}
    /* margin-left:auto (no solo el .spacer) para que, si la cabecera se
       parte en dos líneas, .hbtns se quede pegado al borde derecho de SU
       propia línea en vez de caer al borde izquierdo, justo debajo del
       logo — el spacer no ayuda ahí porque se queda en la primera línea. */
    .hbtns{flex-wrap:wrap; justify-content:flex-end; margin-left:auto}

    /* El rótulo del botón de analizar se va: queda solo el ▶, que es lo que
       de verdad se pulsa. La cabecera es el sitio más caro de la pantalla. */
    #analyzeBtn .btn-txt{display:none}

    .stage{position:relative}
    /* Los paneles FLOTAN sobre el lienzo en vez de robarle ancho: en 375-430 px
       no caben los tres en fila. Se abren y cierran desde los iconos de la
       cabecera, así que ya no hace falta reservar sitio para ninguna asa. */
    .rail,.inspector{
      position:absolute; top:0; bottom:0; z-index:30;
      width:min(84vw,300px); box-shadow:0 10px 40px rgba(0,0,0,.34);
    }
    .rail{left:0}
    .inspector{right:0}
    .rail.collapsed,.inspector.collapsed{width:0; box-shadow:none}
    /* Red de seguridad: si con el dedo (44 px por botón) las herramientas no
       entran, la barra se desliza en horizontal en vez de partirse en varias
       filas, que se comerían el alto del dibujo. */
    .toolbar{overflow-x:auto; overflow-y:visible; scrollbar-width:none}
    .toolbar::-webkit-scrollbar{display:none}
    .toolbar .tbtn,.toolbar .tsep{flex:0 0 auto}
    /* Menú de ejemplos: fuera las descripciones. En el móvil cada línea ocupaba
       tres renglones y con siete ejemplos el menú se salía de la pantalla; el
       nombre («Viga con rótulas», «Pórtico simple») ya distingue de sobra, y la
       descripción larga se conserva en pantalla ancha, donde sí cabe. */
    .dropdown .menu button span{display:none}
    .dropdown .menu button{flex-direction:row; align-items:center; padding:11px 10px}
    .dropdown .menu{max-height:70vh; max-height:70dvh; overflow-y:auto}

    /* la barra del visor de PDF tampoco envolvía: el título largo empujaba
       «Recompilar / Pestaña / Cerrar» fuera del panel */
    .pdf-bar{flex-wrap:wrap}
    .pdf-note{display:none}
    .pdf-panel{width:96vw; border-radius:10px}
    /* La revisión de la importación pasa a UNA columna: la imagen arriba y las
       cotas debajo. En dos columnas de 180 px no se lee ni el recorte del
       número ni la casilla en la que hay que escribirlo. */
    .imp-body{flex-direction:column}
    .imp-side{width:auto; border-left:none; border-top:1px solid var(--line)}
    .imp-preview{flex:0 0 auto; max-height:42vh}
    .imp-panel{width:96vw; border-radius:10px}
    .imp-foot .imp-note{display:none}
  }

  /* ============================================================
     PUNTERO GRUESO (dedo)
     Se consulta el TIPO DE PUNTERO, no el ancho: una tableta grande también se
     toca con el dedo y necesita lo mismo que un teléfono. La referencia de
     ~44 px es el mínimo cómodo para tocar sin fallar; con el ratón esos
     tamaños serían aparatosos, por eso van aquí y no en la regla general.
     ============================================================ */
  @media (pointer:coarse){
    /* min-height en vez de solo relleno: la altura de un .btn depende de su
       fuente (line-height:1), y ajustándola a mano se queda en 37 px. Así los
       44 px están garantizados pase lo que pase con la tipografía. */
    .btn{padding:11px 14px; min-height:44px}
    .btn.icon{width:44px; height:44px}
    .minibtn{padding:10px 12px; font-size:12.5px}
    .tbtn{width:44px; height:44px}
    .tbtn svg{width:22px; height:22px}
    .toolpop button{padding:13px 14px; font-size:14px}
    /* Las opciones del menú de ejemplos son una lista larga que se toca con el
       pulgar: sin `min-height` se quedaban en 38 px y era fácil pulsar la de
       al lado. Mismo criterio que en `.btn`. */
    .dropdown .menu button{min-height:44px}
    .seg button{padding:12px 6px}
    .segsm button{padding:10px 4px}
    .tabs button{padding:15px 11px}
    .tool{padding:12px 11px}
    .quickdirs button{padding:11px 0}
    .toggle{padding:11px 9px}
    .loadchip .rm,.loadedit .loadedit-head .rm{font-size:20px; padding:2px 10px}
    /* Un range de altura por defecto es una línea fina imposible de agarrar */
    .slider{height:34px}
    /* 16 px es el umbral por debajo del cual Safari en iOS AMPLÍA la página al
       enfocar un campo — y luego la deja desplazada y descuadrada. Subirlo es
       la forma de evitar ese salto, no una cuestión de estética. */
    input[type=text],input[type=number],select{padding:12px 10px; font-size:16px}
    .numtable input{padding:8px 5px; font-size:15px}
    /* Los campos del panel de importación llevan clase propia, que gana por
       especificidad a la regla de arriba: sin repetirlos aquí se quedaban en
       26 px de alto, la mitad de lo que necesita un dedo — y son justo los
       campos que el usuario TIENE que tocar para confirmar las cotas. */
    .imp-val,.imp-load,.imp-dload,.imp-sup{padding:11px 9px; font-size:16px}
    .imp-thumb{height:30px; width:74px}
  }

  /* Dedo Y pantalla estrecha a la vez: con 9 botones a 44 px la barra necesita
     396 px solo de botones y en un teléfono de 390 NO cabe, así que aparecía
     scroll horizontal y deshacer/rehacer quedaban escondidos fuera de vista.
     Se bajan a 40 px y se quitan los separadores (52 px de puro adorno): así
     entra todo de una vez y no hay nada que descubrir deslizando. */
  @media (pointer:coarse) and (max-width:820px){
    .tbtn{width:40px; height:40px}
    .tbtn svg{width:20px; height:20px}
    .toolbar{gap:2px; padding:5px 6px}
    .toolbar .tsep{display:none}
    /* Misma lógica que la barra de herramientas de arriba: a 44 px los botones
       de la cabecera (tema, Ejemplo, Analizar, los dos paneles) siguen
       robando ancho en un teléfono angosto. Los menús que abren estos botones
       (p. ej. las opciones de #demoMenu) NO se tocan aquí — viven en un popup
       aparte y no cuestan sitio en la fila de la cabecera. */
    header .btn{padding:6px 9px; min-height:36px}
    header .btn.icon{width:36px; height:36px}
  }

  /* ============================================================
     HAY TÁCTIL DISPONIBLE (aunque no sea el puntero principal)
     `any-pointer` en vez de `pointer`: un portátil con pantalla táctil apunta
     sobre todo con el trackpad (puntero FINO), así que no le vienen bien los
     botones de 44 px de arriba, pero sus dedos sufren exactamente los mismos
     problemas de menú nativo y de zoom por doble toque. Estas reglas no
     molestan con el ratón, así que se aplican en cuanto haya un dedo posible.
     ============================================================ */
  @media (any-pointer:coarse){
    /* Sin esto, mantener el dedo sobre un botón o sobre el lienzo abre el menú
       nativo de selección/copia del navegador en mitad del gesto. */
    .btn,.tbtn,.tool,.seg button,.tabs button,.quickdirs button,
    .toolpop button,.toggle,svg#board{
      -webkit-touch-callout:none; -webkit-user-select:none; user-select:none;
    }
    /* Quita el zoom por doble toque (y su retardo de 300 ms) SOLO en los
       controles: el pellizco para ampliar la página sigue disponible, que es
       una ayuda de accesibilidad y no se debe desactivar. */
    .btn,.tbtn,.tool,.seg button,.tabs button,.quickdirs button,
    .toolpop button,.toggle,.tabs,.dropdown .menu button{touch-action:manipulation}
  }

  /* ============================================================
     PORTADA / LANDING (primera página) — diseño minimalista
     ============================================================ */
  .landing{
    position:fixed; inset:0; z-index:200; overflow-y:auto;
    background:
      radial-gradient(1100px 560px at 50% -12%, var(--accent-soft), transparent 62%),
      linear-gradient(180deg, var(--paper), var(--paper-2));
    color:var(--ink); animation:lpIn .4s ease;
  }
  .landing.hidden{display:none}
  .landing.leaving{animation:lpOut .3s ease forwards}
  @keyframes lpIn{from{opacity:0}to{opacity:1}}
  @keyframes lpOut{to{opacity:0; transform:translateY(-10px)}}
  .lp-inner{max-width:1080px; margin:0 auto; padding:22px 26px 84px; display:flex; flex-direction:column; gap:56px}
  .lp-top{display:flex; align-items:center; justify-content:flex-end; gap:12px}
  .landing .facemark .delx-eye{transition:transform .12s ease-out}
  /* hero */
  .lp-hero{display:flex; flex-direction:column; align-items:center; text-align:center; gap:18px; padding:34px 0 6px}
  .lp-hero .facemark{width:118px; height:auto; color:var(--ink)}
  .lp-brandtitle{
    font-family:'LM Roman 8',var(--pixel); font-weight:400; color:var(--ink);
    font-size:22px; letter-spacing:.14em; line-height:1;
  }
  .lp-brandtitle .e{font-size:1.3em; color:var(--accent)}
  .lp-hero h1{margin:0; font-family:var(--pixel); font-weight:700; font-size:clamp(32px,6vw,52px); line-height:1.08; letter-spacing:-.5px}
  .lp-hero h1 em{font-style:italic; color:var(--accent)}
  .lp-sub{margin:0; max-width:640px; font-size:clamp(15px,2.4vw,18px); color:var(--ink-soft); line-height:1.6}
  .lp-cta{display:flex; gap:12px; flex-wrap:wrap; justify-content:center; margin-top:8px}
  .lp-cta .btn{font-size:15px; padding:12px 24px; border-radius:11px}
  /* secciones */
  .lp-section{display:flex; flex-direction:column; gap:18px}
  .lp-section > h2{
    margin:0; font-family:var(--ui); font-size:12.5px; font-weight:700;
    letter-spacing:.17em; text-transform:uppercase; color:var(--ink-soft);
  }
  .lp-grid{display:grid; gap:14px}
  .lp-grid.features{grid-template-columns:repeat(auto-fit,minmax(215px,1fr))}
  .lp-grid.work{grid-template-columns:repeat(auto-fit,minmax(248px,1fr))}
  .lp-feat{background:var(--panel); border:1px solid var(--line); border-radius:14px; padding:19px; display:flex; flex-direction:column; gap:9px}
  .lp-feat .ic{width:38px; height:38px; border-radius:10px; display:grid; place-items:center; background:var(--accent-soft); color:var(--accent); font-size:19px}
  .lp-feat h3{margin:0; font-size:14.5px; font-weight:700}
  .lp-feat p{margin:0; font-size:12.8px; color:var(--ink-soft); line-height:1.55}
  /* tarjetas de trabajo (ejemplos, clic → app) */
  .lp-card{
    text-align:left; font:inherit; color:var(--ink); cursor:pointer;
    background:var(--panel); border:1px solid var(--line); border-radius:14px;
    padding:0; overflow:hidden; display:flex; flex-direction:column; transition:.16s ease;
  }
  .lp-card:hover{border-color:var(--accent); transform:translateY(-3px); box-shadow:var(--shadow)}
  .lp-card:active{transform:translateY(-1px)}
  .lp-card .thumb{height:118px; background:var(--paper-2); border-bottom:1px solid var(--line); display:grid; place-items:center}
  .lp-card .thumb svg{width:80%; height:80%}
  .lp-card .meta{padding:13px 16px 15px; display:flex; flex-direction:column; gap:3px}
  .lp-card .meta .t{font-family:var(--ui); font-weight:700; font-size:15px}
  .lp-card .meta .d{font-size:12.3px; color:var(--ink-soft); line-height:1.5}
  .lp-card .meta .g{
    margin-top:8px; align-self:flex-start; font-size:10.5px; font-family:var(--mono);
    color:var(--accent); background:var(--accent-soft); border:1px solid var(--line);
    padding:2px 9px; border-radius:999px;
  }
  .lp-foot{text-align:center; color:var(--ink-soft); font-size:12px; font-family:var(--ui); font-style:italic; padding-top:6px}
  .lp-support-links{display:flex; justify-content:center; align-items:center; flex-wrap:wrap; gap:0 10px}
  .lp-support-links a,.lp-support-yape{color:var(--ink-soft); font:inherit; font-size:inherit; font-style:inherit; text-decoration:underline; text-underline-offset:3px}
  .lp-support-links a:hover,.lp-support-yape:hover{color:var(--accent)}
  .lp-support-yape{padding:0; border:0; background:none; cursor:pointer}
  @media (max-width:560px){ .lp-inner{padding:18px 16px 60px; gap:42px} }
