/* ═══════════════════════════════════════════════════════════════════════════
     LA HOJA DE ESTILOS DE TETRA RAD — una sola verdad para la puerta y para el
     cascarón de trabajo. Este archivo SIGUE SIENDO ESA ÚNICA VERDAD; lo que
     cambió el 12-ago-2026 es por dónde llega al navegador.

     YA NO VIAJA DENTRO DE CADA PÁGINA. La sirve `tetra_plataforma/hoja.py` como
     un archivo `.css` con la huella de su contenido en el nombre y un año de
     caché; las dos plantillas madre la enlazan con `<link>` en `<head>`. Aquí no
     se editó ni una regla: se editó el transporte.

     LO QUE DECÍA ANTES ESTA CABECERA, y por qué ya no rige: «Va EN LÍNEA a
     propósito: es una hoja sola, la sirve comprimida la puerta y así no hay una
     segunda petición que bloquee el primer pintado ni un parpadeo sin estilo».
     Ese razonamiento es correcto para una página que se visita UNA VEZ, y le
     faltaba un número que nadie había medido: la página pesaba 126 KB y 122 eran
     esta hoja —el 96 %—, y no se pagaban una vez sino en cada navegación; unos
     4 MB diarios para un radiólogo que abre cien pantallas. Y la premisa técnica
     tampoco se sostenía: un `<link>` en la cabeza TAMBIÉN bloquea el primer
     pintado, así que no hay parpadeo sin estilo — ese riesgo es de las hojas que
     carga un guion. El desglose medido está en
     `docs/specs/2026-08-07-lo-que-tarda-la-plataforma.md`.

     DOS CONSECUENCIAS QUE HAY QUE RESPETAR AL EDITAR ESTE ARCHIVO:
       · NADA DE ETIQUETAS DE PLANTILLA aquí dentro. Un `{{ … }}` o un `{% … %}`
         no lo renderiza nadie en un `.css`: viaja literal al navegador. Si algo
         tiene que cambiar con el cliente, va en `plantillas/pie_impreso.html`,
         que sí viaja en línea. `hoja.py` se niega a arrancar si aparece una, y
         `tools/interfaz_test.py` lo caza antes, en el integrador.
       · EL ENVOLTORIO `<style>…</style>` SE QUEDA. Es lo que recorta `hoja.py`,
         y es lo que mantiene este archivo legible y con las mismas coordenadas
         de texto que usan las baterías para romperlo a propósito.

     CÓMO ESTÁ ORDENADA
       1. Fichas (tokens): color claro y oscuro, tipografía, espaciado, radios,
          relieve, y las fichas del MOVIMIENTO.
       2. Cimientos: cuerpo, foco visible, tipografía base.
       3. El cascarón de trabajo: riel de módulos, lienzo, pie.
       4. La puerta: entrada, sala de espera, tropiezos, biblioteca de acceso.
       5. Piezas: botones, campos, cifras, filas, chips, tablas, dictador.
       6. Movimiento: el sistema completo, y su apagado por «reduced-motion».
       7. Impresión: el expediente en papel.

     LOS JAMÁS DEL MANUAL §08, que esta hoja respeta y la batería vigila: sin
     degradados y sin sombras. El relieve se hace con SUPERFICIE y con LÍNEA FINA
     —como en el papel impreso—, nunca con una sombra difusa: es lo que separa
     una herramienta clínica de una plantilla de moda.

     EL CRITERIO DEL MOVIMIENTO (revisado el 8-ago-2026, orden del operador):
     movimiento con oficio, no efectos. Corto (120–240 ms), con propósito
     —cambio de estado, aparición de contenido, foco, confirmación—, continuo
     (lo que aparece crece desde donde estaba), y SOLO sobre «transform» y
     «opacity», que el compositor mueve sin recalcular el diseño: cero saltos de
     contenido. Lo pequeño se mueve rápido, lo grande un poco más; entrar es más
     lento que salir. Se retiró la ceremonia anterior (la palabra letra por letra
     y el emblema dibujándose, casi tres segundos antes de poder leer los
     botones): una animación que no explica nada sobra, y menos delante de la
     puerta.
     ═══════════════════════════════════════════════════════════════════════════ */

  /* ─── 0 · LA TIPOGRAFÍA, SERVIDA POR ESTA CASA ──────────────────────────── */
  /* QUÉ CAMBIA Y POR QUÉ (decisión del operador, 16-ago-2026; la evidencia, en
     `docs/specs/2026-08-16-la-tipografia-de-la-medicina.md`). Hasta hoy la
     plataforma NO tenía tipografía: tenía la del sistema operativo de cada
     puesto —Segoe UI en Windows, otra en Mac, otra en Linux—, o sea que el mismo
     informe se componía con una letra distinta en cada estación y ninguna la
     había elegido nadie. Atkinson Hyperlegible es la única candidata cuyo
     ENCARGO DE DISEÑO fue distinguir los caracteres que se confunden: el 1 de la
     l y de la I, el 0 de la O, el 8 de la B. En un informe donde conviven «8 mm»,
     «0,5 mg», la l de litro y un número de accesión alfanumérico, equivocar un
     carácter no produce un error visible: produce un informe que se aprueba mal.

     LO QUE ESTA HOJA NO AFIRMA, porque la evidencia no lo sostiene: que esto
     REDUZCA errores clínicos. Nadie lo ha medido en informes radiológicos. Lo
     sostenido es el reconocimiento de caracteres, y con eso basta para elegir.

     SE SIRVE DESDE ESTE SERVIDOR Y JAMÁS DESDE UNA RED AJENA. Los archivos están
     versionados en `activos/tipografia/` con su OFL.txt al lado (SIL OFL 1.1, que
     autoriza expresamente incrustar y redistribuir), y `settings.py` ya publica
     `activos/` como `/static/`. Un `@import` a un CDN de fuentes contradiría la
     premisa entera de esta plataforma —no sale a internet— y añadiría una
     petición a un tercero desde el puesto del radiólogo. `tools/interfaz_test.py`
     lo vigila desde hoy.

     RUTA ABSOLUTA Y NUNCA `{% static %}`: esta hoja se sirve como archivo .css y
     ahí no la renderiza nadie; una etiqueta de plantilla viajaría literal al
     navegador. `tetra_plataforma/hoja.py` se niega a arrancar si aparece una.

     `font-display: optional` Y NO `swap`. Con `swap` el texto se pinta con la
     letra de reserva y CAMBIA a media carga: la caja de cada palabra se mueve y
     el contenido salta, que es exactamente lo que la cabecera de esta hoja
     prohíbe («cero saltos de contenido»). Con `optional` el navegador usa la
     fuente propia si ya la tiene y, si no, se queda con la de reserva hasta la
     visita siguiente — nada parpadea y nada salta. El precio, declarado: la
     PRIMERA visita del día puede verse con la letra del sistema, y por eso la
     cadena de reserva se conserva ENTERA y la geometría tiene que valer en los
     dos estados.

     `format("woff2")` Y NO `format("woff2-variations")`, aunque los archivos SEAN
     variables — y esto lo cazó el guardián nuevo el mismo día que se escribió.
     `woff2-variations` es una cadena de un borrador antiguo: el navegador no la
     reconoce, DESCARTA esa fuente por no saber leerla y se queda con la de
     reserva. No hay error, no hay 404, el archivo se sirve con sus 47 KB — y la
     pantalla sale con la letra del sistema. El rasgo variable no se declara en el
     formato: se declara en `font-weight: 200 800`, que es lo que hay abajo.

     LOS DOS ARCHIVOS SON VARIABLES (eje `wght` 200–800) y pesan 47,1 KB la
     proporcional y 25,0 KB la de ancho fijo: 72,1 KB las dos. Tres pesos
     estáticos de la proporcional costarían más (74,7 KB) y darían seis pesos
     menos. NO se empaqueta la itálica: son 51,5 KB —más que las dos redondas
     juntas— y esta interfaz apenas usa cursiva. */
  @font-face {
    font-family: "Atkinson Hyperlegible Next";
    src: url("/static/tipografia/AtkinsonHyperlegibleNext-wght.woff2")
         format("woff2");
    font-weight: 200 800;
    font-style: normal;
    font-display: optional;
  }
  @font-face {
    font-family: "Atkinson Hyperlegible Mono";
    src: url("/static/tipografia/AtkinsonHyperlegibleMono-wght.woff2")
         format("woff2");
    font-weight: 200 800;
    font-style: normal;
    font-display: optional;
  }

  /* ─── 1 · FICHAS ────────────────────────────────────────────────────────── */
  :root {
    /* Color — modo oscuro, el de la sala de lectura y el de la marca.
       Las razones de contraste están CALCULADAS, no supuestas, y
       tools/interfaz_test.py las vuelve a calcular en cada cambio (AA = 4,5:1
       para texto; 3:1 para bordes de control y foco). */
    --fondo: #0B1420;            /* lienzo de la aplicación */
    --superficie: #111E2C;       /* riel, tarjetas, cabecera de tabla */
    --superficie-alta: #18293A;  /* fila señalada, control pulsado */
    --linea: #22303c;            /* filete de separación */
    --linea-fuerte: #35485C;     /* filete que sí divide bloques */
    --borde-control: #6B7C8C;    /* borde de campo y de botón: 3:1 contra el fondo */
    --texto: #FFFFFF;            /* 18,50:1 */
    --texto-2: #C8D2DC;          /* 12,08:1 — texto secundario */
    --texto-3: #93A3B2;          /*  7,16:1 — rótulos y detalle */
    --coral: #FF6B4A;            /*  6,57:1 — la señal, se gana */
    --coral-tinta: #0B1420;      /* lo que va ENCIMA del coral macizo */
    --tinta: #0C1826;            /* la tinta clínica del manual */

    /* LA TIPOGRAFÍA DE LA CASA, con la pila del sistema ENTERA detrás. La
       reserva no es adorno: si el archivo no llega —despliegue a medias, caché
       envenenada, primera visita del día con `font-display: optional`— la
       plataforma se ve exactamente como se veía antes de hoy. Un cambio
       tipográfico no puede tener como modo de falla una pantalla en blanco.
       Y medido, no supuesto (16-ago-2026): Atkinson Next compone la misma prosa
       clínica con un 0,0 % de diferencia de ancho frente a Segoe UI, y su altura
       de x es un 0,8 % menor. La creencia de que «Atkinson es muy ancha» es
       falsa para la versión Next 2.001. */
    --letra: "Atkinson Hyperlegible Next", system-ui, -apple-system, "Segoe UI",
             Roboto, "Helvetica Neue", Arial, sans-serif;
    /* La de ancho fijo se reserva para lo que se lee CARÁCTER A CARÁCTER —una
       cédula, un número de accesión, una tecla—: es la única de las dos que trae
       el cero ranurado (comprobado abriendo el archivo, no leyendo una ficha), y
       su ancho fijo hace imposible el clásico «rn» que se lee «m». Es un 40 % más
       ancha, así que JAMÁS un párrafo: cifras sueltas y columnas cortas. */
    --letra-mono: "Atkinson Hyperlegible Mono", ui-monospace, "SF Mono", Menlo,
                  Consolas, monospace;

    /* LOS CINCO ESCALONES DE CUERPO, en fichas y no repartidos por la hoja.
       Hasta hoy había diecinueve combinaciones de cuerpo y peso en una sola
       pantalla, que no es una jerarquía: es ruido con forma de jerarquía. Y la
       jerarquía iba AL REVÉS de lo que importa — el andamiaje de la aplicación a
       16 px y el cuerpo del informe que se firma a 14,08 px. Nadie tomó esa
       decisión: salió de encadenar .88rem sobre .95rem sobre 1rem. */
    --t-titulo: 1.5rem;    /* el h1 del módulo */
    --t-seccion: 1.15rem;  /* todo h2 — sin regla valía 24 px, igual que el h1 */
    --t-panel: .95rem;     /* h3 y títulos de panel */
    --t-clinico: 1rem;     /* EL INFORME: el texto que se aprueba y se firma */
    --t-detalle: .82rem;   /* rótulos y notas */
    /* EL PISO, declarado: nada por debajo de 12 px en la plataforma. Un tamaño
       mínimo escrito es lo que permite discutirlo; uno implícito solo se
       descubre mirando una captura. */
    --t-piso: 12px;

    /* Escala de espacio (4 px de base) y radios. */
    --e1: .25rem; --e2: .5rem; --e3: .75rem; --e4: 1rem;
    --e5: 1.5rem; --e6: 2rem;  --e7: 3rem;
    --radio: 6px; --radio-chico: 4px; --radio-grande: 10px;

    /* Movimiento: duración por tamaño del gesto, y curvas con oficio. */
    --mov-rapido: 120ms;   /* lo pequeño: pulsar, marcar, resaltar */
    --mov-medio: 180ms;    /* lo normal: aparecer, cambiar de estado */
    --mov-lento: 240ms;    /* lo grande: un panel, una vista */
    --entrar: cubic-bezier(0, 0, .2, 1);      /* desacelera al llegar */
    --salir: cubic-bezier(.4, 0, 1, 1);       /* acelera al irse */
    --estandar: cubic-bezier(.2, 0, 0, 1);    /* dentro de la pantalla */

    /* LA EXCEPCIÓN DECLARADA A LA BANDA (9-ago-2026), y se escribe aquí porque
       una excepción sin argumento es una regla rota. La banda de 120–240 ms
       gobierna los GESTOS —pulsar, aparecer, cambiar de estado—: son respuestas
       a una acción, y una respuesta lenta se siente lenta. Un contador no es un
       gesto: es una cifra CONTÁNDOSE, y contar es lo único que hace legible el
       recorrido. Los contadores estaban puestos a 180 ms —dentro de la banda, y
       por eso nadie los había mirado— y a 180 ms un número no cuenta: aparece.
       Era exactamente la queja del operador («el movimiento existe pero no se
       percibe»), medida en vez de supuesta. 560 ms alcanza a leerse subir y no
       retrasa la lectura de la pantalla, que sigue completa desde el primer
       fotograma. El guardián de la interfaz le pone techo (600 ms) y exige que
       el guion la lea DE AQUÍ: una duración escrita dos veces se separa. */
    --cuenta: 560ms;
  }

  /* El modo claro: mismas fichas, otros valores. La razón de contraste de cada
     par se calcula igual, y el coral se oscurece porque el coral de marca sobre
     blanco da 2,6:1 y sería ilegible — el color de marca no se negocia en el
     emblema, pero un texto que no se lee no es marca, es un defecto. */
  @media (prefers-color-scheme: light) {
    :root {
      --fondo: #FFFFFF;
      --superficie: #F4F6F8;
      --superficie-alta: #E9EDF1;
      --linea: #D9E0E7;
      --linea-fuerte: #B4C0CC;
      --borde-control: #5D6B78;
      --texto: #0C1826;          /* 17,88:1 */
      --texto-2: #33455A;        /*  9,81:1 */
      --texto-3: #5B6C7D;        /*  5,40:1 */
      --coral: #C2360F;          /*  5,49:1 — el coral de marca, entintado */
      --coral-tinta: #FFFFFF;
    }
  }

  /* ─── 2 · CIMIENTOS ─────────────────────────────────────────────────────── */
  html, body { margin: 0; min-height: 100%; }
  html { color-scheme: dark light; }
  body {
    background: var(--fondo); color: var(--texto);
    font-family: var(--letra);
    font-size: 16px; line-height: 1.55;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
    /* UNA PALABRA QUE NO CABE SE PARTE; NO SE LLEVA LA PÁGINA POR DELANTE
       (10-ago-2026, barrido geométrico). Con la letra al 200 % en un teléfono
       —WCAG 1.4.4, y lo que hace a diario quien no ve de cerca— una ruta de
       archivo dentro de una nota de procedencia («tools/distribucion_cumplimiento.py»,
       que para el navegador es UNA sola palabra) empujaba la página hasta 127 px
       a lo ancho, y con ella todo el contenido. Va en el cuerpo y no en cada
       clase porque «overflow-wrap» se hereda: así lo cumple también la nota que
       alguien escriba mañana. «break-word» y no «anywhere»: solo parte cuando la
       palabra NO cabe de ninguna manera, y no cambia el ancho mínimo de las
       cajas — que es lo que rompería las tablas. */
    overflow-wrap: break-word;
  }
  * { -webkit-tap-highlight-color: transparent; }

  /* EL FOCO SE VE SIEMPRE, en todo control y en toda la plataforma. Es la
     diferencia entre poder trabajar con el teclado y no poder. El anillo no
     ocupa sitio (outline), así que aparecer no mueve nada de la página. */
  a:focus-visible, button:focus-visible, input:focus-visible,
  textarea:focus-visible, select:focus-visible, summary:focus-visible,
  [tabindex]:focus-visible {
    outline: 2px solid var(--coral); outline-offset: 3px;
    border-radius: var(--radio-chico);
  }
  .saltar {
    position: absolute; left: var(--e3); top: var(--e3); z-index: 60;
    background: var(--superficie); color: var(--texto);
    border: 1px solid var(--borde-control); border-radius: var(--radio);
    padding: var(--e2) var(--e3); font-size: .9rem; text-decoration: none;
    transform: translateY(-160%);
    transition: transform var(--mov-medio) var(--entrar);
  }
  .saltar:focus-visible { transform: none; }

  /* «text-wrap: balance» EN LOS TÍTULOS (10-ago-2026, barrido geométrico). Un
     título que envuelve dejando UNA sola palabra en el último renglón
     —«Auditoría de tiempos de / lectura», «A quién hay que acompañar ·
     Distribución por / nombres»— se lee como si sobrara: el ojo cierra el título
     en el renglón anterior y la palabra suelta queda de huérfana. El barrido lo
     cazó en 14 títulos, en cinco pantallas, entre 320 y 900 px y con la letra
     agrandada. Se reparte el texto entre los renglones en vez de llenar el
     primero. En los párrafos va «pretty», que es la misma idea con otro
     presupuesto: no reequilibra el bloque entero —sería caro en un texto largo—
     pero sí impide el renglón final de una sola palabra. Ninguna de las dos
     MUEVE nada: son reglas de composición, no de movimiento, y donde el
     navegador no las entienda el texto sale exactamente como salía. */
  h1, h2, h3, h4 { font-weight: 650; letter-spacing: -.01em; margin: 0;
                   text-wrap: balance; }
  /* EL h2 GANA CUERPO PROPIO (16-ago-2026). Un `<h2>` sin clase se quedaba con
     el 1.5em que le da el navegador, o sea EL MISMO CUERPO QUE EL h1: dos niveles
     de título con el mismo tamaño no son dos niveles. Se le da la ficha de
     sección, que es lo que ya usaban los h2 que sí llevaban clase. */
  h2 { font-size: var(--t-seccion); }
  p { margin: var(--e2) 0; color: var(--texto-2); max-width: 68ch;
      text-wrap: pretty; overflow-wrap: break-word; }
  /* «overflow-wrap: anywhere» y no solo «break-word»: una ruta como
     `tools/distribucion_cumplimiento.py` es UNA palabra para el navegador, y sin
     esto empujaba la página a lo ancho en pantalla estrecha (medido con
     navegador el 9-ago-2026, el último desborde que quedaba en la escalera). Un
     nombre de archivo partido en dos renglones se lee igual de bien; una página
     que hay que rodar en horizontal para leer una nota al pie, no. */
  code { font-family: var(--letra-mono); font-size: .84em; color: var(--texto-2);
         overflow-wrap: anywhere; }
  img { max-width: 100%; height: auto; }
  /* Cifras y horas se leen en columna: los numerales de ancho fijo son la
     diferencia entre una tabla clínica y una lista que baila. */
  .cifra b, .tabla td, .chip, time, .num, .medida, .identificador, kbd {
    font-variant-numeric: tabular-nums;
  }

  /* ── LAS DOS PIEZAS DE CIFRA, y por qué son dos y no una ────────────────────
     El informe se lee en dos modos: se lee la redacción y se BUSCAN las medidas.

     `.medida` es la MAGNITUD DENTRO DE LA PROSA («8 mm», «0,5 mg»): misma
     familia, mismo cuerpo y MISMO COLOR que el texto que la rodea. Sin coral, y
     esto es una decisión, no un olvido: el coral de esta casa significa alerta
     —«no se puede aprobar», «grabando»— y una medida normal no es una alerta.
     Dos significados en una ficha y el que pierde es la alerta. Lo que sí lleva
     es el CERO RANURADO y el ancho tabular, que es lo único que hace falta para
     que el 0 no se confunda con la O y para que dos cifras se comparen en
     columna.

     `.identificador` es lo que se lee CARÁCTER A CARÁCTER —número de accesión,
     cédula, código, tecla—, y ahí manda el ancho fijo: es la única forma de ver
     que dos códigos difieren en un dígito.

     LO QUE NO SE HACE, y va escrito para que nadie lo reintente: marcar las
     magnitudes con una expresión regular sobre el texto libre. Marcaría de más y
     de menos —el «5,1» de un SUV sí, el «2026» de una fecha no— y una medida
     marcada donde no hay medida enseña a desconfiar del marcado entero. Solo se
     envuelve lo que el motor YA reconoce como magnitud con unidad.

     Y TAMPOCO se pone lo tabular en el selector universal: en prosa, un «2026»
     con espaciado tabular se lee separado. Va por piezas declaradas, que es
     además lo que permite apagarlo en el papel. */
  .medida { font-feature-settings: "tnum", "zero"; }
  .identificador { font-family: var(--letra-mono); font-weight: 500;
                   font-feature-settings: "zero"; }
  /* El peso explícito de arriba no es adorno: la Mono variable trae `wght` por
     omisión en 200 (ExtraLight). Quien la enlace sin declarar peso obtiene las
     cifras clínicas en el peso más delgado de la familia — y no da error, se ve
     fino y ya. */

  /* ─── 3 · EL CASCARÓN DE TRABAJO ────────────────────────────────────────── */
  /* En el PC el riel de módulos va a la izquierda y fijo —es una aplicación de
     trabajo, no una página— y el lienzo se queda con todo el ancho útil. En el
     teléfono el mismo marcado se convierte en barra superior con la navegación
     deslizable. Un solo HTML, dos comportamientos, sin duplicar nada. */
  body.app .marco { min-height: 100svh; }
  .columna { display: flex; flex-direction: column; min-width: 0; }
  .columna .lienzo { flex: 1; }
  /* El salto al contenido enfoca el lienzo: el anillo va por dentro para no
     dibujar un marco alrededor de media pantalla. */
  main[tabindex]:focus-visible { outline-offset: -2px; }
  /* En el teléfono el riel es una REJILLA de dos filas y no un montón de cajas
     que se acomodan como pueden: arriba la marca y la identidad, abajo la
     navegación a todo el ancho. Con flujo flexible, la identidad se metía en la
     fila de los módulos y se montaba encima (visto en 390 px el 8-ago-2026). */
  .rail {
    display: grid; align-items: center;
    grid-template-columns: minmax(0, auto) minmax(0, 1fr);
    grid-template-areas: "marca quien" "modulos modulos";
    column-gap: var(--e3);
    padding: var(--e3) var(--e4) 0;
    background: var(--superficie);
    border-bottom: 1px solid var(--linea);
    position: sticky; top: 0; z-index: 20;
    /* El riel de módulos se sale a propósito del acolchado de la cabecera para
       poder rodar de borde a borde (ver «.modulos»). Sin este recorte, esos dos
       centímetros negativos empujarían la PÁGINA a lo ancho, que es justo el
       defecto que se venía a quitar. «clip» y no «hidden»: no crea contenedor de
       rodadura, así que la cabecera pegajosa sigue pegada. */
    overflow-x: clip;
  }
  .marca {
    grid-area: marca;
    display: flex; align-items: center; gap: var(--e2);
    color: var(--texto); text-decoration: none; padding-bottom: var(--e3);
  }
  /* El glifo del PRODUCTO (8-ago-2026, regla dura 7): la cabecera de trabajo la
     preside Tetra Rad. Es vector y trae su propio fondo, así que no lleva placa
     blanca — la placa era del logotipo del cliente, que viene sobre blanco y por
     respeto al original no se recorta ni se recolorea (regla dura 8). */
  .marca .glifo-app { width: 28px; height: 28px; display: block; flex: none; }
  .marca-texto { display: flex; flex-direction: column; min-width: 0; }
  .marca-nombre {
    font-size: .98rem; font-weight: 650; letter-spacing: .08em;
    text-transform: uppercase; white-space: nowrap;
  }
  /* NO ES CORAL (8-ago-2026, auditoría con navegador): el coral aquí competía
     con el mismo coral de «por debajo del acuerdo» en las pantallas de al
     lado, y una marca que usa la ficha de la alerta clínica se lee como alerta
     cuando no lo es. La firma queda en tinta neutra —de mayor contraste
     todavía— y el coral se reserva para lo crítico (regla 8 de la auditoría). */
  .marca-nombre b { font-weight: 650; color: var(--texto); }
  /* De quién es la licencia, dicho en la cabecera y no adivinado. En el teléfono
     no cabe una segunda línea al lado de la identidad, y ahí ya lo dice el pie. */
  .marca-licencia { display: none; }
  .modulos {
    grid-area: modulos;
    display: flex; gap: 0; min-width: 0;
    overflow-x: auto;
    /* DE BORDE A BORDE (9-ago-2026). El riel vivía dentro del acolchado de la
       cabecera, así que el último módulo se cortaba contra un margen en blanco
       y la pantalla decía «aquí se acabó» cuando no se había acabado. Ahora la
       banda rueda hasta el filo del teléfono —donde el recorte se lee como lo
       que es, algo que sigue— y el acolchado se devuelve por dentro para que el
       primer módulo no quede pegado al borde. La cabecera recorta el desborde
       («.rail { overflow-x: clip }»), así que la página no se mueve. */
    margin-inline: calc(var(--e4) * -1); padding-inline: var(--e4);
    scroll-padding-inline: var(--e4);
    /* LA PISTA DE QUE HAY MÁS (8-ago-2026, auditoría con navegador): con más
       módulos que ancho de pantalla, el riel rueda sin avisar y el borde recorta
       un enlace a la mitad sin que se note que sigue algo.
       CÓMO SE RESUELVE, Y POR QUÉ ASÍ: el primer arreglo fue un desvanecido con
       cuatro degradados en cada borde. Se retiró — el degradado es un «jamás»
       del manual §08 y `tools/aplicacion_test.py` lo caza, con razón: esta casa
       hace relieve con superficie y filete, no con humo. La pista honesta ya
       existía y estaba apagada a mano con «scrollbar-width: none»: LA BARRA DE
       DESPLAZAMIENTO. Se enciende fina y con la tinta de los filetes, y hace
       exactamente lo que se quería —aparece solo cuando de verdad hay desborde,
       dice de qué lado y cuánto queda— sin una línea de JavaScript y sin
       inventar una pieza nueva. Esconder el indicador nativo y luego dibujar
       otro peor era el defecto de fondo. */
    scrollbar-width: thin;
    scrollbar-color: var(--linea-fuerte) transparent;
  }
  /* El mismo hilo para los navegadores que aún piden el prefijo. */
  .modulos::-webkit-scrollbar { height: 4px; }
  .modulos::-webkit-scrollbar-track { background: transparent; }
  .modulos::-webkit-scrollbar-thumb { background: var(--linea-fuerte);
                                      border-radius: 2px; }
  /* ── EL GRUPO Y SUS MÓDULOS, QUE NO SE CONFUNDEN (9-ago-2026) ──────────────
     LA CRÍTICA, literal y con la pantalla del teléfono delante: «no sé si unos
     módulos van adentro de otros». Se leía «MI TRABAJO · Resumen de dirección ·
     Dictado · Progr…» en una sola fila, y el rótulo del grupo era UN ELEMENTO
     MÁS de esa fila: mismo renglón, misma tinta (`--texto-3` en los dos) y solo
     un cuerpo de letra más pequeño para distinguirlos. Con eso, «Mi trabajo» se
     leía como un módulo que quizá contenía a los siguientes — que es
     exactamente lo que el operador preguntó.

     LO QUE LO ARREGLA SON TRES COSAS A LA VEZ, y ninguna sola bastaba:
       1 · ESTRUCTURA. Cada grupo es una CAJA con su rótulo ENCIMA de sus
           módulos, no al lado. Arriba el nombre del grupo, debajo la fila (o la
           columna, en el PC) de lo que contiene: la jerarquía se ve antes de
           leerse. En el marcado son dos cajas de verdad —«.grupo-modulos» con
           su «.grupo-enlaces»— y el lector de pantalla las anuncia igual,
           porque el grupo lleva su rótulo asociado.
       2 · SEPARADOR. Un filete entre grupos —vertical en el teléfono, horizontal
           en el PC— dice dónde termina uno y empieza el otro.
       3 · TINTA. El módulo sube a `--texto-2` y el rótulo del grupo se queda en
           `--texto-3`. Dos tintas distintas para dos cosas distintas; antes
           compartían la misma y la diferencia quedaba fiada al tamaño. */
  .grupo-modulos { display: flex; flex-direction: column; flex: none; min-width: 0; }
  .grupo-modulos + .grupo-modulos {
    margin-left: var(--e3); padding-left: var(--e3);
    border-left: 1px solid var(--linea-fuerte);
  }
  .grupo-enlaces { display: flex; min-width: 0; }
  .modulos a {
    position: relative; color: var(--texto-2); text-decoration: none;
    padding: var(--e2) var(--e3) var(--e3); font-size: .93rem;
    white-space: nowrap;
    transition: color var(--mov-rapido) var(--estandar),
                background-color var(--mov-rapido) var(--estandar);
  }
  .modulos a:hover { color: var(--texto); }
  /* El toque responde SIN depender del movimiento: quien pidió «reducir
     movimiento» sigue viendo que su dedo llegó, porque esto es un cambio de
     superficie y no un desplazamiento. */
  .modulos a:active { background: var(--superficie-alta); }
  /* El indicador del módulo activo CRECE desde su propio sitio (continuidad:
     nada aparece de la nada) y no ocupa espacio, así que no empuja el texto. */
  .modulos a::after {
    content: ""; position: absolute; left: var(--e3); right: var(--e3);
    bottom: 0; height: 2px;
    /* Tinta neutra y no coral (8-ago-2026): «dónde estoy» es navegación, no una
       alerta, y en esta misma barra hay pantallas con chips coral de verdad
       (vencidos, por debajo del acuerdo). Confundir las dos señales es el
       defecto que la auditoría cazó — el coral se gana, no se reparte. */
    background: var(--texto);
    transform: scaleX(0); transform-origin: center;
    transition: transform var(--mov-medio) var(--entrar);
  }
  .modulos a.activa { color: var(--texto); }
  .modulos a.activa::after { transform: scaleX(1); }
  .modulos .grupo {
    display: flex; align-items: center; gap: var(--e1);
    color: var(--texto-3); font-size: .66rem;
    letter-spacing: .14em; text-transform: uppercase; font-weight: 650;
    padding: 0 var(--e3) var(--e1); white-space: nowrap;
  }
  /* EL GRUPO SE PLIEGA (24-ago-2026): `.grupo-modulos` es un `<details>` y
     `.grupo` es su `<summary>` — el navegador ya sabe abrir, cerrar y llevar
     el foco; aquí solo se le quita la flecha por omisión (fea y siempre a la
     izquierda) y se dibuja una propia, del mismo tamaño de letra que el
     rótulo y que gira al abrir, para que quede claro que el rótulo se toca. */
  .modulos .grupo { cursor: pointer; list-style: none; user-select: none; }
  .modulos .grupo::-webkit-details-marker { display: none; }
  .modulos .grupo::marker { content: ""; }
  .modulos .grupo::before {
    content: ""; flex: none; width: .4em; height: .4em;
    border-right: 1.5px solid var(--texto-3);
    border-bottom: 1.5px solid var(--texto-3);
    transform: rotate(-45deg); transition: transform var(--mov-rapido) var(--estandar);
  }
  .grupo-modulos[open] > .grupo::before { transform: rotate(45deg); }
  /* SIN «:not([open])» (24-ago-2026, revisión adversarial de Gemini): la
     primera versión solo daba realce al pasar el mouse o el foco cuando el
     grupo estaba CERRADO — quien quería CERRAR el grupo abierto tocando su
     rótulo no recibía ninguna señal de que el rótulo respondía. El realce es
     de la interacción, no de un estado del grupo. */
  .modulos .grupo:hover, .modulos .grupo:focus-visible { color: var(--texto-2); }
  .quien {
    grid-area: quien; justify-self: end;
    display: flex; gap: var(--e2); align-items: center;
    font-size: .8rem; color: var(--texto-3); padding-bottom: var(--e3);
    min-width: 0;
  }
  /* La identidad: un disco con la inicial y el nombre al lado. Lo que había
     —el correo cortado con puntos suspensivos sobre el papel en versalitas—
     ocupaba el ancho de un titular en un teléfono para decir dos veces lo
     mismo. El correo entero vive en «title» y en /cuenta/. */
  .quien .yo { display: flex; align-items: center; gap: var(--e2);
               min-width: 0; text-decoration: none; }
  .quien .yo .inicial {
    flex: none; width: 1.95rem; height: 1.95rem; border-radius: 50%;
    display: grid; place-items: center;
    font-size: .7rem; font-weight: 600; letter-spacing: .02em;
    background: var(--superficie-alta); color: var(--texto-2);
    border: 1px solid var(--borde-control);
    transition: border-color var(--mov-rapido) var(--estandar);
  }
  .quien .yo .yo-texto { display: flex; flex-direction: column;
                         line-height: 1.2; min-width: 0; text-align: left; }
  .quien .yo .nombre {
    color: var(--texto-2); overflow: hidden; text-overflow: ellipsis;
    white-space: nowrap; max-width: 14ch;
  }
  .quien .yo .papel {
    font-size: .66rem; letter-spacing: .08em; text-transform: uppercase;
    color: var(--texto-3);
  }
  /* En pantalla estrecha queda el disco solo: en un teléfono el nombre y el
     papel le quitan el sitio al riel de módulos, que es para lo que se entra. */
  @media (max-width: 34rem) {
    .quien .yo .yo-texto { display: none; }
  }
  .quien .salir { margin: 0; }
  .quien .salir button {
    color: var(--texto); background: transparent; font: inherit;
    font-size: .8rem; cursor: pointer;
    border: 1px solid var(--borde-control); border-radius: var(--radio);
    padding: var(--e1) var(--e3);
    transition: background-color var(--mov-rapido) var(--estandar),
                border-color var(--mov-rapido) var(--estandar),
                transform var(--mov-rapido) var(--salir);
  }
  /* Tinta neutra (8-ago-2026): «esta es mi cuenta» es navegación, no una
     alerta clínica — ver la nota de «.modulos a::after» arriba. */
  .quien a.yo.activa { border-color: var(--texto); }
  .quien .salir button:hover { background: var(--superficie-alta);
                               border-color: var(--coral); }
  .quien .salir button:active { transform: scale(.97); }

  @media (min-width: 1040px) {
    /* ── EL RIEL DEL PC NO PUEDE EMPUJAR AL CONTENIDO (9-ago-2026) ────────────
       Se reportó desde una ventana de escritorio ANGOSTA: riel a la izquierda y
       una franja muerta a su derecha, con el contenido fuera de sitio. Se midió
       con navegador —19 anchos × 3 altos × 10 pantallas, en esta versión y en la
       publicada— y el solape NO se reprodujo: el corte se evalúa contra el
       viewport y a 1039 px ya es barra. Pero la medición dejó ver que la
       geometría ERA frágil, y eso sí se arregla: dos columnas rígidas («248px
       1fr») confían en que el contenido quepa, y en una rejilla la pista «1fr»
       tiene mínimo automático — le basta con un hijo que no sepa encogerse para
       empujar la página entera. Ahora:
         · Las dos pistas van con «minmax(0, …)»: ninguna puede crecer más allá
           de lo que hay, pase lo que pase adentro.
         · La del riel se ENCOGE PRIMERO cuando el sitio aprieta —«clamp» entre
           13 y 15,5 rem según el ancho—, porque si algo tiene que ceder es la
           navegación, nunca lo que se vino a leer.
       Es un arreglo de geometría, no de síntoma: la forma de fallar que se
       describió deja de ser posible aunque la causa fuera otra. */
    body.app .marco {
      display: grid;
      grid-template-columns: minmax(0, clamp(13rem, 20vw, 15.5rem)) minmax(0, 1fr);
    }
    .rail {
      display: flex; flex-direction: column; align-items: stretch;
      /* «box-sizing: border-box» NO SOBRA, y esto se midió (9-ago-2026). El riel
         declaraba «height: 100svh» con acolchado, y sin caja de borde el alto
         REAL era 100svh + 40 px: cuarenta píxeles más que la ventana, siempre.
         Consecuencias, las tres medidas con navegador: la cabecera pegajosa no
         podía quedarse quieta —se iba 40 px hacia arriba al rodar—; la rodadura
         propia del riel no entraba hasta 40 px tarde; y el bloque de la cuenta
         con el botón de «Salir», que vive al final del riel, quedaba por debajo
         del borde de la ventana. En una ventana baja —o con el navegador al
         150 %, que es lo mismo visto por dentro— eso es el riel comiéndose la
         pantalla y el módulo pareciendo que no cabe. Es la causa más probable de
         lo que el operador reportó, y llevaba ahí desde que el riel existe. */
      box-sizing: border-box;
      gap: 0; height: 100svh; padding: var(--e5) var(--e4) var(--e4);
      border-bottom: 0; border-right: 1px solid var(--linea);
      overflow-y: auto;
    }
    .columna { min-height: 100svh; }
    .marca { padding-bottom: var(--e5); align-items: flex-start; }
    .marca .glifo-app { width: 32px; height: 32px; }
    .marca-licencia {
      display: block; margin-top: 2px; font-size: .68rem; color: var(--texto-3);
      letter-spacing: .02em; line-height: 1.3;
      overflow: hidden; text-overflow: ellipsis;
    }
    .modulos { flex-direction: column; gap: 0; overflow: visible;
               margin-inline: 0; padding-inline: 0; }
    /* En el PC el grupo ya se lee de arriba abajo, así que el filete que lo
       separa del anterior es HORIZONTAL y el rótulo se queda encima igual. */
    .grupo-modulos + .grupo-modulos {
      margin: var(--e4) 0 0; padding: var(--e4) 0 0;
      border-left: 0; border-top: 1px solid var(--linea);
    }
    .grupo-enlaces { flex-direction: column; gap: 1px; }
    .modulos a {
      padding: var(--e2) var(--e3); border-radius: var(--radio);
      font-size: .95rem;
      transition: color var(--mov-rapido) var(--estandar),
                  background-color var(--mov-rapido) var(--estandar);
    }
    .modulos a:hover { background: var(--superficie-alta); }
    .modulos a.activa { background: var(--superficie-alta); }
    .modulos a::after {
      left: 0; right: auto; top: var(--e2); bottom: var(--e2);
      width: 2px; height: auto;
      transform: scaleY(0); transform-origin: center;
      transition: transform var(--mov-medio) var(--entrar);
    }
    .modulos a.activa::after { transform: scaleY(1); }
    .modulos .grupo { padding: 0 var(--e3) var(--e2); }
    .quien {
      justify-self: stretch; margin-top: auto; padding: var(--e4) 0 0;
      border-top: 1px solid var(--linea);
      flex-wrap: wrap; gap: var(--e2);
    }
    .quien .yo { text-align: left; }
    .quien .yo .correo { max-width: 100%; }
    .quien .salir { width: 100%; }
    .quien .salir button { width: 100%; }
  }

  /* ── LA CABECERA SE APILA ANTES DE MONTARSE (10-ago-2026) ──────────────────
     EL DEFECTO, medido con navegador y visto en captura: en un teléfono de
     390 px con la letra agrandada —la que pone quien no ve de cerca, y la que
     deja el navegador al 150 % o al 200 %— la marca del producto se dibujaba
     ENCIMA del correo y del papel de quien entró. «TETRA RAD» y
     «dir@ejemplo.com / DIRECTOR» ocupando los mismos píxeles: las dos cosas
     ilegibles, y el enlace de la marca inalcanzable porque el rótulo del papel
     quedaba delante. Se reprodujo en las 12 pantallas y en los dos modos.

     LA CAUSA es geométrica y estaba a la vista: la cabecera del teléfono es una
     rejilla de dos pistas, «minmax(0, auto)» para la marca y «minmax(0, 1fr)»
     para la identidad. Con mínimo 0 la primera pista SE ENCOGE por debajo de su
     contenido, y el contenido —que lleva «white-space: nowrap», y con razón:
     una marca partida en dos renglones no es una marca— se desborda hacia la
     derecha, que es donde vive la identidad. Una rejilla no envuelve.

     LO QUE LO ARREGLA: en el teléfono y la tableta la cabecera vuelve a ser una
     caja flexible QUE SÍ ENVUELVE, con una diferencia respecto al armazón que
     tuvo antes —y que es exactamente lo que falló el 8-ago-2026, cuando la
     identidad se le metía en el renglón de los módulos—: el riel de módulos
     declara «flex: 1 0 100 %», así que SIEMPRE estrena renglón, quepa lo que
     quepa arriba. Con eso, cuando marca e identidad no caben juntas, la
     identidad baja un renglón en vez de montarse; y cuando caben —que es todo
     lo que se ve hoy en un teléfono con la letra normal— no cambia ni un píxel.
     Va DESPUÉS del bloque del PC y acotado a menos de 1040 px para no tocar el
     riel en columna, que es de otra geometría y ya está resuelto. */
  @media (max-width: 1039.98px) {
    .rail { display: flex; flex-wrap: wrap; align-items: center; }
    .marca { flex: 0 1 auto; min-width: 0; order: 1; }
    /* «margin-left: auto» hace en la caja flexible lo que hacía
       «justify-self: end» en la rejilla: la identidad pegada a la derecha. Y
       «order» conserva el ORDEN QUE SE VE: en el marcado la identidad va después
       del riel de módulos —así la lee un lector de pantalla, y así estaba bien—,
       pero en la pantalla siempre ha ido arriba, al lado de la marca. La rejilla
       lo conseguía con áreas; aquí, con el orden visual. */
    .quien { flex: 0 1 auto; margin-left: auto; order: 2; }
    .modulos { flex: 1 0 100%; order: 3; }
  }

  /* «width: 100%» no sobra, y costó una medición averiguarlo (8-ago-2026): dentro
     de una columna flexible, un margen automático en el eje transversal ANULA el
     estirado, y el lienzo se quedaba en 713 px fijos —una columna estrecha
     centrada en un monitor de 1920, justo lo que este rediseño venía a quitar—.
     Con el ancho declarado, el margen automático solo centra. */
  .lienzo {
    /* «min-width: 0» además del ancho declarado (9-ago-2026): dentro de una caja
       flexible o de una rejilla, el mínimo automático de un hijo es su ancho
       mínimo de contenido — una tabla ancha, una palabra larga o un dibujo sin
       tope bastan para que el lienzo se niegue a encoger y empuje la página a lo
       ancho. Es la causa clásica del desborde en este tipo de armazón, y se
       declara aunque hoy no haya nada que la dispare. */
    width: 100%; min-width: 0; box-sizing: border-box;
    max-width: 1180px; margin-inline: auto;
    padding: var(--e5) clamp(var(--e4), 3vw, var(--e6)) var(--e6);
  }
  .pie-app {
    width: 100%; box-sizing: border-box; max-width: 1180px; margin-inline: auto;
    padding: var(--e4) clamp(var(--e4), 3vw, var(--e6)) var(--e5);
    font-size: .78rem; color: var(--texto-3);
    border-top: 1px solid var(--linea);
  }
  /* LA MIGAJA DE PAN (24-ago-2026): «no salen lo de volver atrás», la otra
     mitad del mismo encargo que plegó el riel. Tinta discreta a propósito
     —es orientación, no el contenido de la pantalla— salvo el tramo activo,
     que sube a `--texto` porque es la respuesta a «¿dónde estoy». */
  .migas {
    display: flex; align-items: center; flex-wrap: wrap; gap: var(--e1);
    font-size: .82rem; color: var(--texto-3); margin-bottom: var(--e4);
  }
  .migas a { color: var(--texto-2); text-decoration: none; }
  .migas a:hover, .migas a:focus-visible { color: var(--texto);
                                           text-decoration: underline; }
  .migas [aria-current] { color: var(--texto); font-weight: 600; }
  /* La cabecera editorial de cada módulo: nombre, filete, y una línea que dice
     de dónde salen las cifras. EL FILETE PASÓ A TINTA NEUTRA (8-ago-2026,
     auditoría con navegador): coral aquí era firma de marca en la MISMA
     pantalla que usa coral para «vencido» y «por debajo del acuerdo» — dos
     significados en una ficha, y el que pierde es la alerta. El coral se
     reserva para lo crítico; la firma editorial se lee igual en tinta neutra,
     y con más contraste todavía. */
  /* EL TÍTULO SE ENCOGIÓ Y PERDIÓ SU RAYA (8-ago-2026). En un teléfono, el
     titular a 1,9rem sobre una raya decorativa y tres renglones de bajada se
     comía la primera pantalla entera: el operador abrió /programacion/ y tuvo
     que desplazarse dos veces para ver un dato. La raya no decía nada —era
     firma editorial en una herramienta de trabajo, que no es lo mismo que una
     portada— y el titular no necesita gritar el nombre de la pantalla a la que
     uno acaba de entrar a propósito. */
  .titulo-modulo {
    font-size: clamp(1.2rem, 1.05rem + .55vw, 1.5rem); font-weight: 650;
    letter-spacing: -.02em; color: var(--texto); line-height: 1.25;
    margin: 0;
  }
  .bajada-modulo { color: var(--texto-3); font-size: .88rem;
                   margin: var(--e2) 0 var(--e4); max-width: 68ch; }

  /* ─── 4 · LA PUERTA ─────────────────────────────────────────────────────── */
  /* Entrar, esperar y tropezar: una sola tarjeta, centrada, sin riel — porque
     todavía no hay a dónde navegar. Es lo único de la plataforma que se centra.

     RECOMPUESTA EL 8-AGO-2026 (crítica del operador con la pantalla del teléfono
     delante: «muy simple y feo y desordenado… no se entiende la plataforma Tetra
     Rad y que Imagenología la licenció»). Tres decisiones, y cada una arregla
     algo que se veía:
       · La puerta es UNA COLUMNA CENTRADA, también en el monitor. No se abre en
         dos con una ilustración de relleno: en una herramienta clínica la
         entrada es aburrida a propósito. Lo que cambia en el PC es la escala y
         el aire, no la estructura — así no es «un móvil estirado» ni un tríptico.
       · Lo que se ve es una TARJETA con superficie y filete (relieve del manual
         §08: superficie y línea, jamás sombra). Antes eran piezas sueltas sobre
         el fondo, y por eso se leía como un borrador.
       · El PRODUCTO preside en un lockup —glifo y wordmark en la misma línea— y
         el CLIENTE es la línea de licencia del pie. La jerarquía es el mensaje:
         Tetra Rad es la plataforma; Imagenología la licenció. */
  /* «box-sizing: border-box» lo destapó el guardián nuevo del alto de ventana
     (9-ago-2026), y era el mismo defecto que el riel del PC: con el acolchado
     por fuera, la puerta medía 100svh + 48 px y SIEMPRE traía barra de rodadura
     vertical — en la pantalla de entrar, donde no hay nada que rodar. */
  body.puerta { box-sizing: border-box;
                display: flex; align-items: center; justify-content: center;
                min-height: 100svh; padding: var(--e5) var(--e4); }
  /* «margin: auto» además de centrar: en una caja flexible, centrar por
     alineación RECORTA por arriba cuando el contenido no cabe —y la sala de
     espera con un aviso largo no cabe en un teléfono apaisado—. Con el margen
     automático el contenido se centra si sobra sitio y se puede rodar si falta. */
  .portico { width: 100%; max-width: 33rem; margin: auto; }
  .tarjeta-puerta {
    background: var(--superficie); border: 1px solid var(--linea);
    border-radius: var(--radio-grande);
    padding: clamp(var(--e5), 5vw, var(--e6));
  }

  /* EL LOCKUP DEL PRODUCTO: el EMBLEMA y el nombre a la misma altura, y debajo
     el filete que arranca en coral — la misma firma editorial que encabeza cada
     módulo. Antes el nombre colgaba debajo de la placa blanca del cliente, sin
     relación visual con nada, y eso era justo lo ilegible de la pantalla.

     UN SOLO SÍMBOLO DE PRODUCTO (9-ago-2026). Aquí presidía el PEZ y en la
     cabecera de trabajo el EMBLEMA: dos símbolos distintos para el mismo
     producto en dos pantallas contiguas — «no se entiende cuál es el logo y
     cuál la mascota», y tenía razón. Ahora la entrada lleva el MISMO emblema
     que la cabecera, la pestaña, el atajo de iOS y la aplicación instalable, y
     con la misma construcción: emblema a la izquierda, wordmark a la derecha,
     en la misma línea. Lo único que cambia entre pantallas es la escala.
     Y NO LO DECIDIÓ EL GUSTO: el manual §07 ya tenía escrito que la mascota vive
     en los momentos humanos, y CLAUDE.md que el pez no sale a material público
     mientras el abogado no despeje «TETRA®» —marca de alimento para peces desde
     1951—. Con esas dos reglas, el símbolo que puede ser identidad comercial es
     uno solo: el emblema. */
  .sello-producto {
    position: relative;
    display: flex; align-items: center; gap: var(--e3);
    margin: 0 0 var(--e5); padding: 0 0 var(--e4);
    border-bottom: 1px solid var(--linea);
  }
  .sello-producto::after {
    content: ""; position: absolute; left: 0; bottom: -1px;
    width: 34px; height: 1px; background: var(--coral);
  }
  /* El emblema de la puerta: el MISMO archivo que sirve la pestaña y el ícono
     de la aplicación, a otra escala. Es vector y trae su propio fondo, así que
     no lleva placa —la placa blanca es del logotipo del cliente, que viene
     sobre blanco y no se recorta ni se recolorea (regla dura 8)—. */
  .emblema { flex: none; width: 54px; line-height: 0; }
  .emblema img { display: block; width: 100%; height: auto; }
  /* La pecera: el sitio de la MASCOTA, que ya no es este. Sigue siendo un ancho
     fijo sin alto propio —el dibujo trae su relación— para no reservar espacio
     que después sobra; lo que cambió es dónde vive (ver «.mascota»). */
  .pecera {
    flex: none; width: 82px; color: var(--texto); line-height: 0;
    transition: transform var(--mov-medio) var(--entrar);
  }
  /* EL SITIO DE LA MASCOTA (9-ago-2026), y es el que el manual §07 ya le tenía
     escrito: los MOMENTOS HUMANOS —la sala de espera, el tropiezo, el estado
     vacío—. Nunca el lockup, nunca junto al wordmark, nunca junto a la marca
     del cliente. Ahí no compite con el emblema porque no hay identidad que
     asertar en esas pantallas: hay una persona esperando o equivocada, que es
     exactamente cuando un gesto humano vale. */
  .mascota { display: flex; justify-content: center; margin: 0 0 var(--e4); }
  .mascota .pecera { width: 116px; }
  .vacio-util .mascota { justify-content: flex-start; margin: 0 0 var(--e3); }
  .vacio-util .mascota .pecera { width: 72px; color: var(--texto-3); }
  .sello-texto { min-width: 0; }
  .titulo-puerta {
    font-size: clamp(1.35rem, 1.15rem + .9vw, 1.7rem); font-weight: 650;
    letter-spacing: .09em; text-transform: uppercase;
    margin: 0; color: var(--texto); line-height: 1.15;
  }
  /* El wordmark de la puerta, IGUAL que el de la cabecera (9-ago-2026): tinta
     neutra y no coral. La misma familia visual en todas partes era media crítica
     del operador, y además el coral se gana — aquí no había nada que ganarlo. */
  .titulo-puerta b { font-weight: 650; color: var(--texto); }
  .bajada { color: var(--texto-3); font-size: .88rem; margin: var(--e2) 0 0;
            letter-spacing: .01em; }
  .invita { margin: 0 0 var(--e3); font-size: .93rem; color: var(--texto-2); }
  .estado { margin-top: var(--e4); font-size: .8rem; color: var(--texto-3);
            line-height: 1.55; letter-spacing: .005em; }

  /* Las puertas de entrada, parejas y a todo el ancho. Google y Microsoft son la
     misma acción con dos proveedores: mismo alto, mismo peso, mismo dibujo — lo
     único distinto es el glifo. A todo el ancho y no en dos columnas porque
     «Entrar con Microsoft» partido en dos líneas es exactamente lo que hace que
     una pantalla se vea barata. */
  .puertas { display: grid; gap: var(--e2); margin: 0 0 var(--e3); }
  .puertas .boton { margin: 0; }
  .boton.proveedor {
    display: flex; align-items: center; justify-content: center;
    gap: var(--e3); min-height: 3.3rem; padding: var(--e3) var(--e4);
    background: var(--superficie-alta); color: var(--texto);
    border: 1px solid var(--borde-control); font-size: 1rem;
  }
  /* El coral se GANA (manual §08): aquí marca el borde de la puerta que se va a
     abrir, no rellena el botón entero de color. Y el botón sube UN píxel: la
     respuesta más corta que se puede dar, y la que hace que se sienta un
     control y no una figura pintada. */
  .boton.proveedor:hover { background: var(--superficie-alta);
                           border-color: var(--coral); color: var(--texto);
                           transform: translateY(-1px); }
  .boton.proveedor:active { transform: scale(.985); }
  .glifo-prov { width: 20px; height: 20px; flex: none; display: block; }
  /* «Entrar con» es el verbo; lo que el ojo busca es el nombre del proveedor. */
  .boton .acc { font-weight: 400; }
  /* El tercer camino es el tercero también en el dibujo: más bajo, más pequeño y
     con la tinta secundaria. Antes pesaba igual que las dos puertas de arriba y
     la pantalla no decía por dónde entrar primero. */
  .puertas.menores { gap: var(--e1); }
  .puertas.menores .boton {
    font-size: .86rem; font-weight: 500; min-height: 2.5rem;
    padding: var(--e2) var(--e3); color: var(--texto-2);
  }
  .puertas.menores .boton:hover { color: var(--texto); }

  /* LA REACCIÓN DE LA MASCOTA — movimiento con propósito, no adorno. El pez se
     impulsa cuando la mano (o el teclado) llega a un botón, y recula un instante
     cuando la pantalla es un tropiezo. Sin una línea de JavaScript: lo dice el
     estado real del documento, que es la única manera de que la animación no
     mienta. Solo transform, así que no mueve ni un píxel del diseño.
     EL SELECTOR SE AMPLIÓ (9-ago-2026): apuntaba a «.puertas a», que son las
     puertas de la ENTRADA — donde la mascota ya no vive. Ahora apunta a
     cualquier botón de la tarjeta, que es lo que hay en las pantallas que sí
     son suyas: «Salir» en la sala de espera y «Volver al inicio» en el
     tropiezo. Un selector que apunta a donde la pieza ya no está es una
     animación muerta que nadie nota. */
  .tarjeta-puerta:has(.boton:hover) .pecera { transform: translateX(6px); }
  .tarjeta-puerta:has(.boton:focus-visible) .pecera { transform: translateX(6px); }
  .tarjeta-puerta:has(.boton:active) .pecera { transform: translateX(11px); }
  .tarjeta-puerta:has(.aviso.error) .pecera,
  .mascota.tropiezo .pecera {
    animation: recular var(--mov-lento) var(--estandar) both;
  }
  @keyframes recular {
    0%   { transform: none; }
    40%  { transform: translateX(-7px) rotate(-5deg); }
    100% { transform: none; }
  }

  /* EL PIE DE LA PUERTA: el sello de licencia y la línea de garantías. Fuera de
     la tarjeta a propósito — no es parte de la acción, es de quién es esto. */
  .pie-puerta { margin: var(--e5) 0 0; padding: 0 var(--e2); }
  /* EL SELLO DE LICENCIA — el sitio del cliente en la puerta (regla dura 7).
     Su emblema sale del original versionado y va sobre la placa blanca que ese
     original necesita (regla dura 8: no se redibuja, no se recolorea). Deja de
     ser cabecera flotante y pasa a ser lo que es: un sello. */
  .licencia {
    display: flex; align-items: center; gap: var(--e3); flex-wrap: wrap;
  }
  .licencia-placa {
    flex: none; background: #FFFFFF; border-radius: var(--radio-chico);
    padding: 5px var(--e2); line-height: 0;
  }
  .licencia-placa img { display: block; width: 88px; height: auto; }
  /* Un colofón, no un párrafo: rótulo pequeño en versalitas y el nombre debajo.
     Dicho en prosa corrida —«TETRA RAD es la plataforma, licenciada a…»— la
     razón social en negrita partía en dos líneas y el pie pesaba más que la
     puerta. Dos líneas cortas dicen lo mismo y se leen de un vistazo. */
  .licencia-texto { margin: 0; flex: 1 1 12rem; min-width: 0; }
  .licencia-rotulo {
    display: block; font-size: .66rem; letter-spacing: .14em;
    text-transform: uppercase; color: var(--texto-3);
  }
  .licencia-nombre {
    display: block; margin-top: 2px; font-size: .8rem; font-weight: 650;
    line-height: 1.35; color: var(--texto-2); letter-spacing: .005em;
  }
  .pie-puerta .estado { margin-top: var(--e3); font-size: .75rem; }

  /* En el monitor la puerta no se estira: se compone. Más aire, más escala y la
     mascota un punto mayor — la misma pieza, pensada para el sitio donde se está
     mirando, en vez de un teléfono ensanchado. */
  @media (min-width: 1040px) {
    .portico { max-width: 35rem; }
    .emblema { width: 60px; }
    .mascota .pecera { width: 132px; }
    .licencia-placa img { width: 104px; }
    .boton.proveedor { min-height: 3.35rem; }
  }
  .exito { color: var(--texto); font-size: 1.05rem; }
  .exito b { font-weight: 650; }
  .alerta { color: var(--texto-2); }
  .alerta b { color: var(--coral); }

  /* ─── 5 · PIEZAS ────────────────────────────────────────────────────────── */
  .boton {
    display: block; box-sizing: border-box; width: 100%;
    margin: var(--e2) 0; padding: var(--e3) var(--e4);
    background: var(--texto); color: var(--fondo); text-decoration: none;
    font-weight: 600; font-size: .97rem; border-radius: var(--radio);
    text-align: center; border: 1px solid var(--texto);
    transition: background-color var(--mov-rapido) var(--estandar),
                border-color var(--mov-rapido) var(--estandar),
                color var(--mov-rapido) var(--estandar),
                transform var(--mov-rapido) var(--salir);
  }
  .boton:hover { background: var(--coral); border-color: var(--coral);
                 color: var(--coral-tinta); }
  /* La respuesta al toque es inmediata y discreta: se hunde, no rebota. */
  .boton:active { transform: scale(.985); transition-duration: 80ms; }
  .boton.secundario {
    background: transparent; color: var(--texto);
    border: 1px solid var(--borde-control); font-weight: 500;
  }
  .boton.secundario:hover { background: var(--superficie-alta);
                            border-color: var(--coral); color: var(--texto); }
  button.boton { font: inherit; font-weight: 600; cursor: pointer; }
  button.boton.secundario { font-weight: 500; }
  body.app .boton { max-width: 24rem; }
  /* LAS DESCARGAS, EN FILA (8-ago-2026). «.boton» nace en bloque al 100 % por
     la puerta de entrada, donde es una acción sola y grande. En un tablero son
     tres —Excel, PDF, Negocio— y apiladas ocupaban tres renglones de 24 rem en
     una pantalla de 1440 px, con el resto vacío al lado. Son acciones
     hermanas: van juntas, del ancho de su texto, y se envuelven solas cuando
     no caben. */
  .acciones { display: flex; flex-wrap: wrap; gap: var(--e3);
              margin: var(--e5) 0 var(--e4); }
  .acciones .boton { width: auto; max-width: none; margin: 0;
                     padding: var(--e2) var(--e5); }

  .campo { margin: var(--e4) 0; }
  .campo label { display: block; font-size: .78rem; color: var(--texto-3);
                 letter-spacing: .06em; text-transform: uppercase;
                 margin-bottom: var(--e1); }
  .campo input, .dictador textarea,
  main input[type="email"], main input[type="text"],
  main input[type="password"], main input[type="number"] {
    width: 100%; box-sizing: border-box; background: var(--superficie);
    border: 1px solid var(--borde-control); border-radius: var(--radio);
    color: var(--texto); font: inherit; font-size: 1rem;
    padding: var(--e3);
    transition: border-color var(--mov-rapido) var(--estandar);
  }
  .campo input:hover, main input:hover { border-color: var(--texto-3); }
  .campo .ayuda { display: block; font-size: .78rem; color: var(--texto-3);
                  margin-top: var(--e1); }
  .aviso { margin: var(--e3) 0; padding: var(--e3); border-radius: var(--radio);
           border: 1px solid var(--linea-fuerte); background: var(--superficie);
           color: var(--texto-2); font-size: .93rem; }
  .aviso.error { border-color: var(--coral); }
  ul.errorlist { margin: var(--e2) 0; padding-left: var(--e4);
                 color: var(--coral); font-size: .88rem; }
  /* EL ENLACE NO ES UNA ALERTA (8-ago-2026, puerta final). La auditoría de esa
     misma jornada sacó el coral del riel, del filete editorial y del filtro
     activo con un argumento que aquí no se había aplicado hasta el final: dos
     significados en una ficha y el que pierde es la alerta. Un enlace corriente
     dentro del texto iba en coral —el MISMO coral de «por debajo del acuerdo» y
     de «vencido»— y en la portada de dirección quedaban, en el mismo renglón,
     una cifra crítica en coral y el nombre del módulo en coral, sin ninguna
     relación entre las dos. Ahora el enlace se marca como se marca un enlace
     desde que existe la tipografía: SUBRAYADO, en tinta de texto y con el filete
     apagado, que además pasa de 5,5:1 a 17,9:1 de contraste. El coral queda para
     lo que de verdad incumple. */
  main a:not(.boton):not(.marca):not(.saltar):not(.tarjeta):not(.accion-mayor) {
    color: var(--texto); text-decoration: underline;
    text-decoration-color: var(--linea-fuerte); text-underline-offset: 3px;
    transition: text-decoration-color var(--mov-rapido) var(--estandar);
  }
  main a:not(.boton):not(.marca):not(.saltar):hover {
    text-decoration-color: var(--texto);
  }

  /* Cifras del período: grandes, de ancho fijo, con su rótulo debajo. */
  /* QUE LAS TRES QUEPAN EN UNA FILA EN UN TELÉFONO (8-ago-2026). Con
     `minmax(9rem…)` cabían dos y la tercera bajaba sola, así que la banda de
     cifras ocupaba media pantalla para decir tres números. A 5,5rem entran tres
     en 360 px, que es el ancho que hay que servir. El numeral también baja: a
     2,5rem un número de cinco dígitos desbordaba su casilla. */
  .cifras {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(5.5rem, 1fr));
    gap: var(--e3) var(--e4); margin: 0 0 var(--e4);
    border: 1px solid var(--linea); border-radius: var(--radio-grande);
    background: var(--superficie); padding: var(--e3) var(--e4);
  }
  .cifra b { display: block; font-size: clamp(1.35rem, 1.05rem + .95vw, 2rem);
             font-weight: 650; line-height: 1.1; letter-spacing: -.02em; }
  .cifra span { display: block; margin-top: var(--e1); font-size: .72rem;
                color: var(--texto-3); letter-spacing: .02em; }
  .cifra.atencion b { color: var(--coral); }
  .cifra .nota { font-size: .72rem; }
  .solicitud { margin: var(--e1) 0; color: var(--texto); }
  .solicitud span { color: var(--texto-3); font-size: .85rem; }
  .pulso { margin-top: var(--e6); padding-top: var(--e4);
           border-top: 1px solid var(--linea); }
  /* CUANDO EL PULSO YA LO ANUNCIA UN ENCABEZADO (8-ago-2026, puerta final), su
     filete y su aire propios sobran: quedaba «título — línea — hueco de 60 px —
     texto», que se lee como una sección que no cargó. El encabezado ya separa. */
  h2 + .pulso { margin-top: 0; padding-top: 0; border-top: 0; }
  .pulso p { font-size: .82rem; color: var(--texto-3); margin: var(--e1) 0; }

  /* Filas densas del expediente y de los hitos: una línea por hecho. */
  .lista { border-top: 1px solid var(--linea); }
  /* LA FILA ENVUELVE, y no es un detalle: medido con navegador el 8-ago-2026, el
     expediente desbordaba a lo ancho en 390 y en 360 px —el chip de estado no se
     encoge y el nombre del profesional no cabía al lado—. Una fila que no envuelve
     empuja la página entera y obliga a rodar en horizontal para leer una lista. */
  .doc {
    margin: 0; padding: var(--e2) var(--e2) var(--e2) 0;
    color: var(--texto); display: flex; align-items: baseline;
    flex-wrap: wrap; gap: var(--e1) var(--e3);
    font-size: .95rem; max-width: none; min-width: 0;
    overflow-wrap: anywhere;
    border-bottom: 1px solid var(--linea);
    transition: background-color var(--mov-rapido) var(--estandar);
  }
  .doc:hover { background: var(--superficie); }
  /* EL TOPE DE MEDIDA DE LÍNEA, que esta hoja ya tenía escrito y se le escapaba
     justo donde más texto hay. La regla `p { max-width: 68ch }` está desde el
     principio y está bien argumentada, pero el texto largo de estas filas NO es
     un `<p>`: es un `span.det`, y `.doc` no le ponía tope. Medido el 16-ago-2026:
     seis renglones a 94 caracteres en la portada, dos a 99 y 102. Un ancho máximo
     solo puede reducir el desborde, nunca producirlo. */
  .doc .det { color: var(--texto-3); font-size: .85rem; max-width: 68ch; }
  /* DEJA DE PARECER UN CAMPO DE FORMULARIO (8-ago-2026). Tenía ancho mínimo
     fijo, texto centrado, borde de un solo píxel y esquinas redondeadas: la
     receta exacta de un «input». En /programacion/ se veían «13:30», «24 min»,
     «3» y «2» dentro de esas cajas y parecían editables — se ve al abrir la
     pantalla en un portátil, no en ninguna batería. Ahora es lo que es: un
     valor destacado sobre fondo, sin borde. El BORDE se reserva para cuando
     comunica algo (vencido, por vencer), que es cuando gana el derecho a
     llamar la atención. */
  .chip {
    flex: none; min-width: 3.4rem; text-align: center;
    font-size: .74rem; font-weight: 650; letter-spacing: .03em;
    padding: var(--e1) var(--e2); border-radius: var(--radio-chico);
    background: var(--superficie-alta); border: 1px solid transparent;
    color: var(--texto);
  }
  .chip.vencido { background: var(--coral); border-color: var(--coral);
                  color: var(--coral-tinta); }
  .chip.por_vencer, .chip.siguiente { border-color: var(--coral);
                                      color: var(--coral); }
  /* «Sin fecha de vigencia» (24-sep-2026): un papel que consta y del que
     ninguna fuente trae fecha. Ni rojo ni aviso —no es un incumplimiento, es
     un dato que falta—: borde discreto, para que se vea que hay algo que
     precisar sin que nadie corra detrás de un papel que quizá está bien. */
  .chip.sin_fecha { border-color: var(--texto-3); color: var(--texto-2); }
  /* La procedencia del dato, a simple vista: lo real se gana el peso. */
  .marca-real {
    flex: none; font-size: .68rem; font-weight: 650; letter-spacing: .08em;
    text-transform: uppercase; padding: var(--e1) var(--e2);
    border-radius: var(--radio-chico); border: 1px solid var(--texto);
    color: var(--texto);
  }
  /* «EJEMPLO» ES UNA ETIQUETA, no una palabra más del rótulo (8-ago-2026,
     puerta final): en «SU BOLETÍN DE JUNIO DE 2026 EJEMPLO» se leía como si el
     mes se llamara así. Con su filete se ve lo que es —una marca puesta encima
     del dato— y es justo la advertencia que no se puede perder. */
  .marca-demo { flex: none; font-size: .64rem; letter-spacing: .1em;
                text-transform: uppercase; color: var(--texto-3);
                border: 1px solid var(--linea-fuerte); border-radius: var(--radio-chico);
                padding: .15em .45em; line-height: 1.4; }

  /* Tablas de gestión: densas, alineadas, sin adornos. En pantalla estrecha
     ruedan dentro de su propia caja, jamás desbordan la página. */
  /* La misma pista de «hay más», aplicada a la tabla (8-ago-2026): la caja rueda
     en pantalla estrecha y sin marca de que se corta una columna. Y con el mismo
     remedio que el riel: la barra de desplazamiento nativa, fina y con la tinta
     de los filetes — no un desvanecido pintado, que aquí es un «jamás» del
     manual §08 y además miente cuando no hay nada que rodar. */
  .tabla-caja {
    overflow-x: auto; margin: 0 0 var(--e5);
    scrollbar-width: thin;
    scrollbar-color: var(--linea-fuerte) transparent;
  }
  .tabla-caja::-webkit-scrollbar { height: 6px; }
  .tabla-caja::-webkit-scrollbar-track { background: transparent; }
  .tabla-caja::-webkit-scrollbar-thumb { background: var(--linea-fuerte);
                                         border-radius: 3px; }
  .tabla { width: 100%; border-collapse: collapse; }
  .tabla th {
    text-align: left; font-size: .7rem; color: var(--texto-3);
    font-weight: 500; letter-spacing: .1em; text-transform: uppercase;
    padding: var(--e2) var(--e3) var(--e2) 0;
    border-bottom: 1px solid var(--linea-fuerte); white-space: nowrap;
  }
  .tabla td { padding: var(--e2) var(--e3) var(--e2) 0; font-size: .93rem;
              color: var(--texto); border-bottom: 1px solid var(--linea); }
  .tabla tbody tr { transition: background-color var(--mov-rapido) var(--estandar); }
  .tabla tbody tr:hover { background: var(--superficie); }
  .tabla td.num, .tabla th.num { text-align: right; }
  .tabla td .det { color: var(--texto-3); font-size: .82rem; }

  /* ── La rejilla que RESPONDE (7-ago-2026) ────────────────────────────────
     Añade sobre la tabla de arriba lo que hace falta para barrer ~1.600 filas
     sin perder el hilo: encabezado que se queda, columnas que ordenan,
     contadores que filtran y búsqueda. Se apoya en las fichas de este sistema
     —nada de valores sueltos— y no trae sombras: el encabezado se separa con
     el filete fuerte que ya usa la tabla, como en el papel. */
  .rejilla .tabla thead th { position: sticky; top: 0; background: var(--fondo);
                             z-index: 1; }
  .rejilla .tabla th[data-ordenable] { cursor: pointer; user-select: none; }
  .rejilla .tabla th[data-ordenable]:hover { color: var(--texto); }
  .rejilla .tabla th:focus-visible { outline: 2px solid var(--coral);
                                     outline-offset: -2px; }
  /* La flecha es un símbolo tipográfico de ESTADO (manual §07.2), y aparece
     solo en la columna que de verdad está ordenando. */
  .rejilla .tabla th[aria-sort]::after { content: " \2191"; color: var(--coral); }
  .rejilla .tabla th[aria-sort="descending"]::after { content: " \2193"; }

  /* Los contadores son botones sin disfraz de botón: siguen leyéndose como
     cifras y se marcan con el filete coral cuando están filtrando. */
  .filtros .cifra { background: none; border: 0; padding: 0 0 var(--e1);
                    margin: 0; font: inherit; color: var(--texto);
                    text-align: left; cursor: pointer;
                    border-bottom: 2px solid transparent;
                    transition: border-color var(--mov-rapido) var(--estandar); }
  .filtros .cifra:hover { border-bottom-color: var(--linea-fuerte); }
  /* Tinta neutra y no coral (8-ago-2026): el contador «vigentes» también se
     puede filtrar, y marcarlo activo en coral se leía como si «vigente» —lo
     que NO es una alerta— también estuviera incumpliendo algo. El coral se
     reserva para lo que de verdad está vencido o por vencer. */
  .filtros .cifra[aria-pressed="true"] { border-bottom-color: var(--texto); }
  .filtros .cifra:focus-visible { outline: 2px solid var(--coral);
                                  outline-offset: 3px; }

  .barra-tabla { display: flex; align-items: baseline; gap: var(--e4);
                 flex-wrap: wrap; margin: 0 0 var(--e2); }
  .buscador { flex: 1 1 18rem; max-width: 26rem; display: block; }
  .buscador input { width: 100%; box-sizing: border-box; background: none;
                    color: var(--texto); border: 1px solid var(--borde-control);
                    border-radius: var(--radio); padding: var(--e2) var(--e3);
                    font: inherit; font-size: .9rem;
                    transition: border-color var(--mov-rapido) var(--estandar); }
  .buscador input::placeholder { color: var(--texto-3); }
  /* El foco NO se apaga: se hace más visible. Un «outline: none» deja afuera a
     quien navega con teclado, y la batería de interfaz lo caza por eso. */
  .buscador input:focus-visible { outline: 2px solid var(--coral);
                                  outline-offset: 1px; }
  .buscador input:focus { border-color: var(--coral); }
  .conteo { margin: 0; font-size: .78rem; color: var(--texto-3); }
  .vacio { margin: var(--e4) 0; color: var(--texto-3); font-size: .9rem; }
  /* Existe para los lectores de pantalla y no ocupa lugar en la página. */
  .visualmente-oculto { position: absolute; width: 1px; height: 1px;
                        overflow: hidden; clip-path: inset(50%);
                        white-space: nowrap; }

  /* La curva horaria: las barras crecen desde su base y el pico se gana el coral. */
  .curva { width: 100%; height: 104px; display: block; margin: var(--e2) 0 0; }
  .curva rect.b { fill: var(--texto-3); transform-origin: center bottom;
                  transform-box: fill-box; }
  .curva rect.b.pico { fill: var(--coral); }

  /* ─── LOS GRÁFICOS DE LOS TABLEROS ──────────────────────────────────────
     Dibujados a mano en SVG, como la curva: cero librerías, cero chartjunk, y
     cada gráfico responde UNA pregunta — si no responde ninguna, sobra. Tres
     niveles de color y no cinco, porque el ojo distingue tres de un vistazo y
     porque la única frontera que significa algo es el acuerdo con el cliente.
     El coral se GANA: lo lleva lo que incumple, nunca la decoración. */
  /* EL DIBUJO TIENE UN TAMAÑO MÁXIMO, y esto no es gusto: es aritmética.
     La geometría vive en un lienzo de 320 unidades (`dibujo.py`, ANCHO) con los
     rótulos a 8,5 px. Un SVG con `width: 100%` y sin tope ESCALA TODO — al
     sacar el gráfico de la rejilla de dos columnas (8-ago-2026), el de gestión
     y el de la auditoría pasaron a ocupar los 1.150 px del lienzo, o sea ×3,6:
     los rótulos salían a 30 px, más grandes que el título de la pantalla, y un
     tablero clínico con letra de afiche se lee como una maqueta. Con el tope,
     el mismo dibujo mantiene su letra en la banda del resto de la interfaz —y
     sigue siendo vector, así que en el teléfono se encoge sin perder nitidez.
     Medido con navegador; el guardián de la interfaz lo vigila. */
  .grafico { width: 100%; max-width: 36rem; height: auto; display: block;
             margin: var(--e2) 0 var(--e4); overflow: visible; }
  .grafico text { font-family: var(--letra); font-variant-numeric: tabular-nums; }
  .grafico rect.g-barra { fill: var(--texto-3); transform-origin: left center;
                          transform-box: fill-box; }
  .grafico rect.g-barra.atencion { fill: var(--texto-2); }
  .grafico rect.g-barra.critico  { fill: var(--coral); }
  .grafico .g-rotulo { fill: var(--texto-3); font-size: 8.5px; text-anchor: end; }
  .grafico .g-rotulo.marcada { fill: var(--texto); font-weight: 650; }
  .grafico .g-cifra { fill: var(--texto-2); font-size: 8.5px; }
  .grafico .g-cifra.critico { fill: var(--coral); font-weight: 650; }
  .grafico .g-grupo { fill: var(--texto); font-size: 8.5px; font-weight: 650;
                      letter-spacing: .04em; }
  .grafico .g-eje { stroke: var(--linea-fuerte); stroke-width: .6; }
  /* El umbral se DIBUJA, no se describe: con el acuerdo en una nota al pie hay
     que comparar doce números a mano. */
  .grafico .g-umbral { stroke: var(--coral); stroke-width: .8; stroke-dasharray: 3 2; }
  .grafico .g-umbral-rotulo { fill: var(--coral); font-size: 7px; text-anchor: middle;
                              letter-spacing: .06em; }
  .grafico polyline.g-serie { fill: none; stroke: var(--coral); stroke-width: 1.6;
                              stroke-linejoin: round; stroke-linecap: round; }
  .grafico line.g-referencia { stroke: var(--texto-3); stroke-width: .8;
                               stroke-dasharray: 3 2; }
  .grafico circle.g-punto { fill: var(--fondo); stroke: var(--coral); stroke-width: 1.2; }
  .grafico circle.g-punto.ultimo { fill: var(--coral); }
  /* La cifra de cada punto de la línea (8-ago-2026): sin ella, la serie solo
     decía cuándo, nunca cuánto. */
  .grafico .g-valor { fill: var(--texto-2); font-size: 7.5px; }
  .grafico .g-valor.ultimo { fill: var(--coral); font-weight: 650; }

  /* La ficha de procedencia: de dónde salió cada cifra y con qué fecha de corte.
     Un tablero sin fecha de corte envejece sin avisar. */
  .fuente { font-size: .78rem; color: var(--texto-3); line-height: 1.5;
            border-top: 1px solid var(--linea); padding-top: var(--e2);
            margin: 0 0 var(--e5); }
  /* Y el aviso de que lo que se ve es un EJEMPLO. Se gana el coral porque es la
     información más importante de esas pantallas: una pantalla que enseña un
     ejemplo con aire de dato real es peor que una pantalla vacía. */
  .aviso-fuente { border: 1px solid var(--linea-fuerte);
                  border-left: 3px solid var(--coral); border-radius: var(--radio);
                  padding: var(--e3) var(--e4); margin: 0 0 var(--e5); }
  .aviso-fuente b { color: var(--coral); font-weight: 650; }
  .aviso-fuente p { margin: var(--e2) 0 0; font-size: .85rem; color: var(--texto-2); }
  /* La desviación frente al propio historial, dicha con el signo y con el sentido
     (en la mediana de lectura, bajar es mejorar). */
  .delta { display: block; margin-top: var(--e1); font-size: .78rem;
           color: var(--texto-3); }
  .delta.empeora { color: var(--coral); }

  /* En el monitor, dos columnas: el gráfico al lado de su tabla, no debajo. Es
     lo que convierte el tablero en una herramienta —se lee de una vez— y por eso
     el lienzo se estira. En el teléfono, una sola columna y el mismo marcado. */
  .rejilla { display: grid; gap: var(--e5); align-items: start; }
  .rejilla > * { min-width: 0; }
  @media (min-width: 1040px) {
    .rejilla { grid-template-columns: 1fr 1fr; gap: var(--e6); }
    .rejilla.ancha-izquierda { grid-template-columns: 3fr 2fr; }
  }

  /* ─── 5b · EL MÓDULO DE DICTADO ─────────────────────────────────────────────
     El motor es compartido con la demostración (`activos/dictado/motor.js`) y
     busca sus elementos por identificador; las CLASES son contrato con él y por
     eso los nombres son los suyos. Lo que NO se hereda es el aspecto: allí la
     hoja tiene degradados, sombras y radios de 20 px; aquí manda el manual §08
     —relieve por superficie y línea, cero sombras, cero degradados— y la batería
     de la aplicación lo vigila. Dos aspectos, un solo motor.

     En el PC, el dictado a la izquierda y el informe a la derecha, ambos a la
     vista: dictar mirando el informe es el gesto del oficio. En el teléfono, una
     columna y el mismo marcado. */
  .dic { display: grid; gap: var(--e5); align-items: start; margin: 0 0 var(--e5); }
  .dic > * { min-width: 0; }
  @media (min-width: 1040px) {
    .dic { grid-template-columns: 1fr 1fr; gap: var(--e6); }
  }

  /* De dónde vienen los datos: se dice arriba y se gana el coral, porque es lo
     primero que un radiólogo va a suponer mal. */
  .procedencia { border: 1px solid var(--linea-fuerte);
                 border-left: 3px solid var(--coral); border-radius: var(--radio);
                 padding: var(--e3) var(--e4); margin: 0 0 var(--e5);
                 font-size: .88rem; color: var(--texto-2); line-height: 1.55; }
  .procedencia b { color: var(--texto); font-weight: 650; }

  /* Los datos del paciente, en una sola fila que envuelve. */
  .pctxro { display: flex; align-items: center; gap: var(--e2); flex-wrap: wrap;
            margin: 0 0 var(--e4); padding: var(--e3);
            background: var(--superficie); border: 1px solid var(--linea);
            border-radius: var(--radio); font-size: .82rem; color: var(--texto-3); }
  .pctxro .src { font-size: .76rem; color: var(--texto-3); }
  .oin { border: 1px solid var(--borde-control); background: var(--fondo);
         color: var(--texto); font: inherit; font-size: .84rem;
         border-radius: var(--radio-chico); padding: var(--e1) var(--e2); }

  .rotulo-campo { display: block; font-size: .78rem; letter-spacing: .06em;
                  text-transform: uppercase; color: var(--texto-3);
                  margin: 0 0 var(--e1); }
  /* Lo que oyó el motor se lee tan bien como lo que quedará escrito: el mismo
     cuerpo clínico que el informe (16-ago-2026). Antes iba a .95rem, un escalón
     por debajo, sin ninguna razón que nadie pudiera decir. */
  .dtarea { width: 100%; box-sizing: border-box; min-height: 5.5rem;
            border: 1px solid var(--borde-control); background: var(--superficie);
            color: var(--texto); font: inherit; font-size: var(--t-clinico);
            border-radius: var(--radio); padding: var(--e3);
            resize: vertical; line-height: 1.55; margin: 0 0 var(--e3); }
  .dtarea::placeholder { color: var(--texto-3); }

  /* Los controles del dictado: generar, grabar, la onda y el estado. */
  .dctl { display: flex; align-items: center; gap: var(--e3); flex-wrap: wrap;
          margin: 0 0 var(--e3); }
  .playb { border: 1px solid var(--texto); background: var(--texto);
           color: var(--fondo); font: inherit; font-weight: 650; font-size: .9rem;
           padding: var(--e2) var(--e4); border-radius: var(--radio);
           cursor: pointer;
           transition: transform var(--mov-rapido) var(--salir); }
  .playb:active { transform: scale(.985); }
  .micbtn { border: 1px solid var(--borde-control); background: transparent;
            color: var(--texto); font: inherit; font-weight: 650; font-size: .9rem;
            padding: var(--e2) var(--e4); border-radius: var(--radio);
            cursor: pointer;
            transition: background-color var(--mov-medio) var(--estandar),
                        color var(--mov-medio) var(--estandar),
                        border-color var(--mov-medio) var(--estandar); }
  /* GRABANDO: coral macizo. Es el único estado que se ve de reojo desde el
     negatoscopio, y por eso se gana el color de la señal. */
  .micbtn.rec { background: var(--coral); border-color: var(--coral);
                color: var(--coral-tinta); }
  .micbtn.err { background: var(--superficie-alta); color: var(--texto-3);
                border-color: var(--linea-fuerte); }
  .micbtn.wait { border-style: dashed; color: var(--coral);
                 border-color: var(--coral); cursor: progress; }
  .micbtn:disabled { opacity: .62; cursor: not-allowed; }
  /* La onda: cinco barras que solo se mueven cuando el motor está vivo. Un
     indicador que se mueve con el motor muerto es la mentira que costó el
     defecto del botón rojo (docs/specs/2026-07-30-modos-de-falla-del-microfono). */
  .mic { display: flex; align-items: center; gap: 3px; height: 22px;
         opacity: .22; transition: opacity var(--mov-medio) var(--estandar); }
  .mic.on { opacity: 1; }
  .mic span { width: 3px; height: 6px; background: var(--coral);
              border-radius: 2px; }
  .mic.on span { animation: onda .9s ease-in-out infinite; }
  .mic.on span:nth-child(2) { animation-delay: .10s; }
  .mic.on span:nth-child(3) { animation-delay: .20s; }
  .mic.on span:nth-child(4) { animation-delay: .30s; }
  .mic.on span:nth-child(5) { animation-delay: .15s; }
  @keyframes onda { 0%, 100% { transform: scaleY(1); }
                    50% { transform: scaleY(2.6); } }
  .dstatus { font-size: .82rem; font-weight: 650; color: var(--coral); }
  .michint { font-size: .78rem; color: var(--texto-3); margin: 0 0 var(--e3);
             line-height: 1.5; }
  .michint b { color: var(--texto-2); font-weight: 650; }

  /* Los avisos de verificación. El crítico DETIENE la aprobación y por eso se
     gana el coral; los de rutina se agrupan solos a partir del segundo, porque
     un informe con trece avisos enseña a ignorarlos. */
  .vchips { display: flex; flex-wrap: wrap; gap: var(--e2);
            margin: 0 0 var(--e4); min-height: 2px; }
  .vchip { display: inline-flex; align-items: center; gap: var(--e1);
           font-size: .74rem; font-weight: 650; color: var(--texto-2);
           background: var(--superficie); border: 1px solid var(--linea);
           border-radius: 999px; padding: var(--e1) var(--e3);
           opacity: 0; transform: translateY(4px);
           transition: opacity var(--mov-medio) var(--entrar),
                       transform var(--mov-medio) var(--entrar); }
  .vchip.show { opacity: 1; transform: none; }
  .vchip.corr { color: var(--texto); border-color: var(--linea-fuerte); }
  .vchip.crit { color: var(--coral); border-color: var(--coral); }
  .vchip.resuelto { color: var(--texto-3); border-color: var(--linea); }
  .vchip.mas { color: var(--texto-3); cursor: pointer; font-weight: 500; }
  .vchip.mas:hover, .vchip.mas:focus-visible { color: var(--texto);
                                               border-color: var(--linea-fuerte); }

  /* El informe: un documento, no un formulario. Rótulo a la izquierda, texto a
     la derecha, filete fino entre campos — como se lee en el papel. */
  .report { border: 1px solid var(--linea-fuerte); border-radius: var(--radio);
            overflow: hidden; }
  .rlab { display: flex; align-items: center; gap: var(--e2); flex-wrap: wrap;
          background: var(--superficie); border-bottom: 1px solid var(--linea);
          padding: var(--e3) var(--e4); font-size: .85rem; font-weight: 650; }
  .rlab .norm { font-weight: 400; color: var(--texto-3); font-size: .76rem; }
  /* La marca del CLIENTE preside el informe que se firma (regla dura 7): es su
     documento. La del PRODUCTO va en el riel, que es donde va la aplicación. */
  .marca-informe { flex: none; width: 24px; height: 24px; background: #FFFFFF;
                   border-radius: var(--radio-chico); padding: 1px;
                   object-fit: contain; }
  /* EL INFORME DEJA DE SER EL TEXTO MÁS PEQUEÑO DE LA PANTALLA (16-ago-2026).
     Estaba a .88rem —14,08 px— mientras el andamiaje de la aplicación se leía a
     16: el documento que se aprueba y se firma, un 12 % más pequeño que el menú
     que lleva hasta él. Nadie decidió eso. Con 79 radiólogos de jornada larga y
     fatiga visual documentada (Krupinski 2009, Stec 2018), 14 px de prosa a
     distancia de monitor clínico obliga a subir el zoom para el documento y a
     bajarlo para el resto. La interlínea sube a 1,6 por lo mismo. */
  .rf { display: grid; grid-template-columns: 150px 1fr; gap: var(--e3);
        padding: var(--e3) var(--e4); border-top: 1px solid var(--linea);
        font-size: var(--t-clinico); line-height: 1.6; }
  .rf:first-of-type { border-top: 0; }
  .rk { color: var(--texto-3); font-size: var(--t-detalle); }
  /* Y el VALOR del campo lleva su propia medida de línea. Es texto corrido de
     verdad —hay campos de informe de varios renglones— y a partir de unos 75
     caracteres el ojo pierde el salto de renglón. 72ch y no 68 porque esta
     columna ya nace estrecha por la rejilla de dos columnas. */
  .rvl { color: var(--texto); max-width: 72ch; }
  .rvl .norm { color: var(--texto-3); font-size: .76rem; }
  /* La EXCEPCIÓN dictada, marcada: lo que el radiólogo dijo se distingue de lo
     que puso su plantilla. Sin fondo de color —el manual no lo permite— y con
     filete lateral, que en papel es como se marca una enmienda. */
  .exc { border-left: 2px solid var(--coral);
         padding-left: var(--e2); font-weight: 650;
         box-decoration-break: clone; -webkit-box-decoration-break: clone; }
  .rvl.revisado { border-left: 2px solid var(--texto); padding-left: var(--e2); }
  /* Revisión por partes: el lápiz y el visto de cada campo. Pequeños y sin peso
     —el informe manda, no sus controles— pero con área de toque suficiente. */
  .fctl { display: inline-flex; gap: var(--e1); margin-left: var(--e2);
          vertical-align: middle; }
  /* DONDE DE VERDAD CAEN (8-ago-2026, puerta final). El motor los añade al
     PADRE del valor, y en la plataforma ese padre es la rejilla `.rf` de dos
     columnas: los dos botones se convertían en un elemento más de la rejilla y
     aterrizaban en la fila siguiente, DEBAJO DEL RÓTULO, sin relación visible
     con el texto que cambian. Se les manda a la columna del valor, que es lo
     que el motor quiso decir con «inline». En el teléfono la rejilla es de una
     sola columna y no hay nada que corregir. */
  .rf > .fctl { grid-column: 2; justify-self: start;
                margin-left: 0; margin-top: var(--e1); }
  @media (max-width: 700px) { .rf > .fctl { grid-column: 1; } }
  .fbtn { border: 1px solid var(--linea-fuerte); background: transparent;
          color: var(--texto-3); font: inherit; font-size: .74rem;
          min-width: 1.9rem; padding: 2px var(--e2);
          border-radius: var(--radio-chico); cursor: pointer;
          transition: color var(--mov-rapido) var(--estandar),
                      border-color var(--mov-rapido) var(--estandar); }
  .fbtn:hover { color: var(--texto); border-color: var(--coral); }
  .rvl.editando1 { outline: 2px solid var(--coral); outline-offset: 3px;
                   border-radius: var(--radio-chico); background: var(--superficie); }

  /* Lo que impide aprobar, dicho entero y sin adorno. */
  .bloqueos { display: none; border: 1px solid var(--coral);
              border-left: 3px solid var(--coral); border-radius: var(--radio);
              padding: var(--e3) var(--e4); margin: var(--e3) var(--e4);
              font-size: .84rem; color: var(--coral); line-height: 1.55; }
  .bloqueos.on { display: block; }
  .bloqueos b { display: block; margin-bottom: var(--e1); }

  .dic-acciones { display: flex; gap: var(--e2); flex-wrap: wrap;
                  padding: var(--e4); }

  /* CADA PAPEL DE UNA FILA DE LA MATRIZ DEL CONTROL (15-sep-2026): el botón
     que lo abre y, al lado, de qué archivo se trata y por qué puerta entró.
     Un requerimiento puede tener varios papeles, así que van uno bajo otro y
     no en una línea corrida donde no se sabría qué nombre es de qué botón. */
  .papel-de-la-fila { display: flex; gap: var(--e1); align-items: baseline;
                      flex-wrap: wrap; margin-bottom: var(--e1); }
  .papel-de-la-fila:last-child { margin-bottom: 0; }

  /* LAS FICHAS DE LOS PENDIENTES DEL DRIVE (2-sep-2026). Dos columnas: los
     datos, que pueden ser largos (una ruta del Drive, una evidencia), y las
     acciones, que necesitan un ancho fijo para que un selector del censo y
     tres botones nunca se corten. En pantalla estrecha, una columna. Todo
     «min-width: 0» a propósito: es lo que impide que un nombre de archivo sin
     espacios empuje la ficha fuera de la página (ver la nota del 9-ago). */
  .casos { list-style: none; margin: 0 0 var(--e5); padding: 0; }
  .caso { display: grid; grid-template-columns: minmax(0, 1fr) minmax(17rem, 21rem);
          gap: var(--e3) var(--e5); padding: var(--e4) 0;
          border-bottom: 1px solid var(--linea); }
  .caso-datos, .caso-acciones { min-width: 0; }
  .caso-cabeza { display: flex; flex-wrap: wrap; align-items: center;
                 gap: var(--e2); margin: 0 0 var(--e1); max-width: none; }
  .caso-archivo { overflow-wrap: anywhere; color: var(--texto); }
  .caso-veredicto { white-space: nowrap; }
  .caso-origen, .caso-evidencia-titulo { margin: var(--e1) 0; max-width: none; }
  .caso-carpeta { overflow-wrap: anywhere; }
  .caso-evidencia-titulo { text-transform: uppercase; letter-spacing: .08em;
                           font-size: .7rem; margin-top: var(--e3); }
  .caso-evidencia { margin: 0; padding-left: 1.2em; }
  .caso-evidencia li { overflow-wrap: anywhere; }
  .caso-acciones { display: flex; flex-direction: column; gap: var(--e2); }
  .caso-acciones .dic-acciones { padding: 0; flex-direction: column;
                                 align-items: stretch; gap: var(--e2); }
  .caso-acciones form { display: flex; flex-direction: column; gap: var(--e1);
                        min-width: 0; }
  .caso-acciones form .det { margin: 0; }
  .caso-acciones select, .caso-acciones input[type="text"] {
    width: 100%; min-width: 0; box-sizing: border-box; }
  .caso-acciones .boton { width: 100%; box-sizing: border-box;
                          white-space: normal; text-align: center; }
  .caso-acciones .ver-drive { font-size: .82rem; }
  @media (max-width: 56rem) {
    .caso { grid-template-columns: minmax(0, 1fr); }
  }
  /* SIRVE PARA UN <a> Y NO SOLO PARA UN <button> (20-ago-2026). Hasta hoy
     esta clase solo se ponía en botones, así que le bastaba con el color; el
     día que el censo estrenó su «Dar de alta» —que es un ENLACE, porque lleva
     a otra pantalla y no envía nada— salía subrayado y con el relleno
     aplastado, o sea sin parecer un botón. Un enlace no respeta el relleno
     vertical mientras siga siendo «inline», y el subrayado lo hereda del
     documento: las dos líneas de abajo son eso, y no un retoque de gusto. */
  .apprb { border: 1px solid var(--coral); background: var(--coral);
           color: var(--coral-tinta); font: inherit; font-weight: 650;
           font-size: .88rem; padding: var(--e2) var(--e4);
           border-radius: var(--radio); cursor: pointer;
           display: inline-block; text-decoration: none;
           transition: transform var(--mov-rapido) var(--salir); }
  a.apprb:hover { text-decoration: none; }
  .apprb:active { transform: scale(.985); }
  .pdfb { border: 1px solid var(--borde-control); background: transparent;
          color: var(--texto); font: inherit; font-weight: 650; font-size: .88rem;
          padding: var(--e2) var(--e4); border-radius: var(--radio);
          cursor: pointer; display: inline-flex; align-items: center;
          gap: var(--e1);
          transition: border-color var(--mov-medio) var(--estandar); }
  .pdfb:hover { border-color: var(--coral); }

  /* El mapa del informe: cuánto lo dictó el radiólogo y cuánto es su plantilla.
     Lo dibuja el motor en SVG, sin librería. */
  #dicmapa { border-top: 1px solid var(--linea); padding-top: var(--e3);
             margin: 0 0 var(--e4); }
  .mapa-t { font-size: .78rem; letter-spacing: .06em; text-transform: uppercase;
            color: var(--texto-3); margin: 0 0 var(--e2); }
  .mapa-svg { display: block; width: 100%; height: 22px; color: var(--coral); }
  .mapa-p { font-size: .84rem; color: var(--texto-2); margin: var(--e2) 0 0; }
  .mapa-f { font-size: .76rem; color: var(--texto-3); margin: var(--e1) 0 0; }
  .mapa-alto { color: var(--coral); }

  /* Los plegables de ayuda: cerrados por defecto, porque el radiólogo entra a
     dictar y no a leer. */
  .dtoggle { margin: 0 0 var(--e4); }
  .dtoggle > summary { cursor: pointer; font-size: .84rem; color: var(--texto-2);
                       padding: var(--e2) 0; }
  .dnote { font-size: .82rem; color: var(--texto-3); line-height: 1.6;
           margin: var(--e2) 0 0; }
  .hnota { font-size: .8rem; line-height: 1.5; color: var(--texto-2);
           margin: var(--e2) 0 0; }
  .interruptor { display: flex; align-items: center; gap: var(--e2);
                 font-size: .85rem; cursor: pointer; }
  .note { font-size: .8rem; color: var(--texto-3); line-height: 1.5;
          margin: 0 var(--e4) var(--e3); }

  /* En pantalla estrecha el informe se lee en UNA columna: el rótulo encima y el
     texto debajo. Con la rejilla de 150 px, la redacción quedaba en una tira de
     seis palabras por línea — un informe clínico ilegible en el teléfono. */
  @media (max-width: 700px) {
    .rf { grid-template-columns: 1fr; gap: var(--e1); }
    .rk { font-size: .72rem; letter-spacing: .05em; text-transform: uppercase; }
  }

  /* ─── 5c · LAS PORTADAS POR PAPEL Y LOS ATAJOS ──────────────────────────────
     El resumen de dirección y «Mi día» comparten piezas, y una regla que esta
     sección respeta sin excepción: EL CORAL SE GANA. Aquí solo lo lleva lo que
     de verdad está por debajo del acuerdo o vencido — el titular, la jerarquía y
     el estado activo van en tinta neutra, que en esta paleta tiene MÁS contraste
     que el coral. Es la misma corrección que la auditoría del 8-ago-2026 aplicó
     al riel y a los filetes: dos significados en una ficha y el que pierde es la
     alerta.
     Relieve por superficie y filete, jamás sombra (manual de la casa). */
  .tnum { font-variant-numeric: tabular-nums; }

  /* La tecla, dibujada como tecla: es lo que hace que un atajo se lea como algo
     que se pulsa y no como una letra suelta en mitad de una frase. */
  kbd {
    font-family: var(--letra-mono); font-size: .74rem; line-height: 1;
    padding: .22em .45em; border: 1px solid var(--borde-control);
    border-bottom-width: 2px; border-radius: var(--radio-chico);
    background: var(--superficie); color: var(--texto-2);
    white-space: nowrap;
  }

  /* EL VEREDICTO: lo único de la portada de dirección escrito en tamaño de
     titular, porque es lo que se lee en diez segundos. */
  .veredicto {
    border: 1px solid var(--linea-fuerte); border-left-width: 3px;
    border-radius: var(--radio); background: var(--superficie);
    padding: var(--e4) var(--e5); margin: 0 0 var(--e5);
  }
  .veredicto.urgente { border-left-color: var(--coral); }
  .veredicto-linea {
    margin: 0; font-size: clamp(1.15rem, .95rem + .7vw, 1.5rem);
    font-weight: 650; letter-spacing: -.01em; color: var(--texto);
    line-height: 1.3;
  }
  .veredicto.urgente .veredicto-linea b { color: var(--coral); }
  .veredicto-nota { margin: var(--e2) 0 0; font-size: .84rem;
                    color: var(--texto-3); max-width: 62ch; }

  /* LOS ASUNTOS: una lista ordenada de verdad (es una prioridad, y el marcado lo
     dice), con la cifra a la izquierda en columna propia para que los números se
     puedan comparar de un vistazo sin leer los titulares. */
  .asuntos { list-style: none; margin: 0 0 var(--e6); padding: 0;
             border-top: 1px solid var(--linea); }
  .asunto {
    display: grid; gap: var(--e2) var(--e5); align-items: start;
    grid-template-columns: 1fr;
    padding: var(--e4) 0; border-bottom: 1px solid var(--linea);
  }
  /* LAS ÁREAS SE NOMBRAN DESDE LAS DOS PISTAS, y no es ceremonia: el pie del
     asunto es un HERMANO de la cifra y del cuerpo (tiene que serlo, porque lleva
     dentro un plegable y un párrafo no puede contener uno). Sin áreas, ese
     tercer hijo caía en la fila siguiente, COLUMNA UNO — la de 7,5 rem de la
     cifra—, y la línea de procedencia del último asunto salía partida en tres
     renglones dentro de una tira estrecha. Se vio en captura propia a 1440 px,
     en la única fila que no lleva medidor. */
  @media (min-width: 720px) {
    .asunto {
      grid-template-columns: 7.5rem minmax(0, 1fr);
      grid-template-areas: "cifra cuerpo" "cifra pie";
    }
    .asunto > .asunto-cifra { grid-area: cifra; }
    .asunto > .asunto-cuerpo { grid-area: cuerpo; }
    .asunto > .asunto-pie { grid-area: pie; }
  }
  /* LA TERCERA COLUMNA (9-ago-2026, dirección de arte), y es la que le devuelve
     el sitio muerto al dibujo. En el monitor el lienzo son 1.150 px y la fila
     gastaba 7,5 rem en la cifra, unos 66 ch en el texto y DEJABA EL RESTO EN
     BLANCO: cuatrocientos píxeles de nada, siete veces, en la primera pantalla
     que ve quien paga. Ahí va ahora el medidor, que es exactamente la
     información que el texto estaba contando con palabras.
     La cifra abarca las dos filas: es el ancla de la fila, y la vista la usa
     para comparar en vertical sin leer un solo titular. */
  /* «:has(.medida)» y no «.asunto» a secas, y no es refinamiento: la lista de
     asuntos la comparten DOS pantallas —el resumen de dirección y la
     inteligencia de negocio—, y los hallazgos de la segunda no traen medidor.
     Con la tercera pista incondicional, esa pantalla quedaba con una columna
     vacía de 15 rem en cada fila: exactamente el hueco muerto que esta pasada
     vino a quitar, reintroducido por el arreglo. La rejilla de tres pistas la
     pide la fila que tiene qué poner en ellas. */
  @media (min-width: 980px) {
    .asunto:has(.medida) {
      grid-template-columns: 7.5rem minmax(0, 1fr) minmax(0, 15rem);
      grid-template-areas: "cifra cuerpo medida" "cifra pie medida";
      column-gap: var(--e5); row-gap: var(--e2);
    }
    .asunto:has(.medida) .asunto-cifra { grid-area: cifra; }
    .asunto:has(.medida) .asunto-cuerpo { grid-area: cuerpo; }
    .asunto:has(.medida) .medida { grid-area: medida; margin: 0; }
    .asunto:has(.medida) .asunto-pie { grid-area: pie; align-self: end; }
  }
  /* LA CIFRA NO SE PARTE, PERO SU UNIDAD SÍ BAJA DE RENGLÓN (11-ago-2026,
     barrido geométrico en el integrador). Aquí había un «white-space: nowrap»
     sobre TODO el párrafo, y el párrafo no es solo el número: lleva detrás su
     unidad («1.378 informes»). Con la letra al 200 % en una ventana de 390 px
     eso son 426 px de texto que no puede partirse por ningún sitio, así que
     empujaba la página 36 px a lo ancho — hay que rodar la pantalla en
     horizontal para leer un titular. El «nowrap» baja al número, que es lo
     único que de verdad no se puede partir; la unidad cae al renglón siguiente
     cuando no cabe, y donde cabe no se mueve nada. */
  .asunto-cifra {
    margin: 0; font-size: clamp(1.5rem, 1.2rem + 1vw, 2.1rem); font-weight: 650;
    letter-spacing: -.02em; color: var(--texto-2); line-height: 1;
    font-variant-numeric: tabular-nums;
  }
  .asunto-cifra b { font-weight: 650; white-space: nowrap; }
  .asunto.critico .asunto-cifra { color: var(--coral); }
  .asunto-cuerpo { min-width: 0; }
  .asunto-titular { margin: 0; font-size: 1rem; font-weight: 650;
                    color: var(--texto); line-height: 1.35; }
  /* El grado, como rótulo y no como color a secas: quien no distingue el coral
     lee «Crítico» igual de rápido. Un semáforo que solo es color deja fuera a
     una de cada doce personas. */
  .grado {
    display: inline-block; margin-right: var(--e2); vertical-align: 1px;
    font-size: .62rem; letter-spacing: .1em; text-transform: uppercase;
    font-weight: 650; padding: .2em .5em; border-radius: var(--radio-chico);
    border: 1px solid var(--linea-fuerte); color: var(--texto-3);
  }
  .grado.critico { background: var(--coral); border-color: var(--coral);
                   color: var(--coral-tinta); }
  .grado.atencion { border-color: var(--coral); color: var(--coral); }
  /* MEDIDA DE LÍNEA, no ancho disponible: 66 ch es donde la vista encuentra sola
     el principio del renglón siguiente. A 74 ch, en el lienzo del monitor, la
     explicación se leía como un párrafo de documento y no como la nota de una
     fila de tablero — que es exactamente la queja que abrió esta pasada. */
  .asunto-que { margin: var(--e2) 0 var(--e1); font-size: .86rem;
                color: var(--texto-2); line-height: 1.6; max-width: 66ch; }
  .asunto-pie { margin: var(--e2) 0 0; font-size: .78rem; color: var(--texto-3);
                display: flex; flex-wrap: wrap; align-items: center;
                gap: var(--e2) var(--e4); }
  /* «min-width: 0» POR TERCERA VEZ EN ESTA HOJA, y por la misma razón de siempre:
     dentro de una caja flexible el mínimo de un hijo es su ancho MÍNIMO DE
     CONTENIDO, y con una ruta de archivo dentro («tools/distribucion_cumplimiento.py»)
     ese mínimo es la ruta entera. Con la letra al 200 % en un teléfono, la ficha
     de procedencia empujaba la página 30 px a lo ancho. «overflow-wrap» del
     cuerpo no bastaba: partir una palabra no reduce el mínimo de contenido, solo
     permite partirla una vez que la caja ya cede. Las dos cosas juntas, sí. */
  .asunto-pie > * { min-width: 0; }
  .enlace-modulo {
    color: var(--texto); font-weight: 650; text-decoration: none;
    border-bottom: 1px solid var(--linea-fuerte);
    transition: border-color var(--mov-rapido) var(--estandar);
  }
  .enlace-modulo::after { content: " →"; color: var(--texto-3); }
  .enlace-modulo:hover { border-bottom-color: var(--texto); }

  /* LA ACCIÓN MAYOR de «Mi día»: dictar está a un clic, y se ve que lo está.
     Es un enlace, no un botón, porque lleva a otra pantalla — y así el clic
     central, el «abrir en pestaña nueva» y el teclado funcionan de fábrica. */
  .accion-mayor {
    display: flex; align-items: center; gap: var(--e4);
    padding: var(--e4) var(--e5); margin: 0 0 var(--e5);
    border: 1px solid var(--borde-control); border-radius: var(--radio-grande);
    background: var(--superficie); color: var(--texto); text-decoration: none;
    transition: background-color var(--mov-rapido) var(--estandar),
                border-color var(--mov-rapido) var(--estandar),
                transform var(--mov-rapido) var(--salir);
  }
  .accion-mayor:hover { background: var(--superficie-alta);
                        border-color: var(--texto); }
  .accion-mayor:active { transform: scale(.995); }
  .accion-glifo {
    flex: none; display: grid; place-items: center;
    width: 46px; height: 46px; border-radius: 50%;
    border: 1px solid var(--linea-fuerte); color: var(--texto);
  }
  .accion-texto { display: flex; flex-direction: column; gap: var(--e1);
                  min-width: 0; flex: 1; }
  .accion-titulo { font-size: 1.15rem; font-weight: 650; letter-spacing: -.01em; }
  .accion-detalle { font-size: .82rem; color: var(--texto-3); }
  .accion-detalle b { color: var(--texto-2); font-weight: 650; }
  .accion-atajo { flex: none; }
  @media (max-width: 560px) {
    /* En el teléfono no hay teclado físico que valga: el atajo se retira en vez
       de ocupar sitio prometiendo algo que ahí no se usa. */
    .accion-atajo { display: none; }
  }

  /* LAS TARJETAS de «Mi día». Cada una es un enlace entero: el objetivo de toque
     es la tarjeta, no un renglón de ocho píxeles. */
  .rejilla-tarjetas {
    display: grid; gap: var(--e4); margin: 0 0 var(--e6);
    /* «min(15rem, 100%)» y no «15rem» a secas (9-ago-2026, medido con navegador
       en la escalera de anchos con zoom): en una pista de rejilla, un mínimo
       mayor que el sitio disponible NO se encoge — empuja la página entera. Las
       tres tarjetas de «Mi día» se salían cincuenta píxeles por debajo de unos
       240 px de ancho útil, que es un teléfono al 150 % de zoom: justo la
       condición de quien amplía la letra porque le cuesta leer. */
    grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
  }
  .tarjeta {
    display: flex; flex-direction: column; gap: var(--e2); min-width: 0;
    padding: var(--e4); border: 1px solid var(--linea);
    border-radius: var(--radio); background: var(--superficie);
    color: var(--texto); text-decoration: none;
    transition: background-color var(--mov-rapido) var(--estandar),
                border-color var(--mov-rapido) var(--estandar);
  }
  .tarjeta:hover { background: var(--superficie-alta);
                   border-color: var(--linea-fuerte); }
  .tarjeta.pendiente { border-left: 3px solid var(--coral); }
  .tarjeta-rotulo {
    margin: 0; font-size: .72rem; letter-spacing: .08em; text-transform: uppercase;
    color: var(--texto-3); display: flex; align-items: center; gap: var(--e2);
    flex-wrap: wrap;
  }
  .tarjeta-cifras { margin: 0; display: flex; gap: var(--e5); flex-wrap: wrap; }
  .turno { display: flex; flex-direction: column; }
  .turno b { font-size: 1.5rem; font-weight: 650; letter-spacing: -.02em;
             line-height: 1.1; font-variant-numeric: tabular-nums; }
  .turno span { font-size: .74rem; color: var(--texto-3); margin-top: var(--e1); }
  .tarjeta-nota { margin: 0; font-size: .8rem; color: var(--texto-3);
                  line-height: 1.55; }
  .tarjeta-nota b { color: var(--texto-2); font-weight: 650; }

  /* LOS LÍMITES DECLARADOS: término y explicación, alineados en columna. Es lo
     que un radiólogo con veinte años de dictado pregunta en el primer minuto, y
     merece leerse de un golpe — no en tres renglones que empiezan cada uno en un
     sitio distinto. */
  .limites { margin: 0 0 var(--e6); border-top: 1px solid var(--linea); }
  .limites dt {
    font-size: .68rem; letter-spacing: .1em; text-transform: uppercase;
    color: var(--texto-3); padding-top: var(--e3);
  }
  .limites dd { margin: var(--e1) 0 0; padding-bottom: var(--e3);
                border-bottom: 1px solid var(--linea);
                font-size: .88rem; color: var(--texto-2); line-height: 1.6;
                max-width: 76ch; }
  @media (min-width: 720px) {
    /* En el monitor, término y explicación en dos columnas: la vista baja por el
       canal de la izquierda y encuentra los tres términos alineados. */
    .limites { display: grid; grid-template-columns: 7rem 1fr;
               column-gap: var(--e5); }
    .limites dt { padding-top: var(--e3); }
    .limites dd { margin: 0; padding-top: var(--e3); }
  }

  /* EL ESTADO VACÍO CON PROPÓSITO. No dice «no hay nada»: dice qué va a aparecer
     aquí, quién lo pone y qué pasa mientras tanto. Un vacío mudo y una pantalla
     que no cargó se ven exactamente igual. */
  .vacio-util {
    border: 1px dashed var(--linea-fuerte); border-radius: var(--radio);
    padding: var(--e4) var(--e5); margin: 0 0 var(--e5);
    background: var(--superficie);
  }
  .vacio-titulo { margin: 0 0 var(--e2); font-weight: 650; color: var(--texto); }
  .vacio-util p { margin: 0; font-size: .85rem; color: var(--texto-3);
                  line-height: 1.6; max-width: 68ch; }
  .vacio-util p b { color: var(--texto-2); }

  /* EL PANEL DE ATAJOS. `<dialog>` de verdad: el foco entra, el tabulador queda
     atrapado y Escape cierra — tres cosas que una caja hecha a mano hace mal. */
  /* ALTO MÍNIMO DE OBJETIVO TÁCTIL (16-ago-2026, WCAG 2.5.8). Este botón heredaba
     su alto del renglón, y con la tipografía propia el renglón mide 21 px: por
     debajo de los 24 que pide la norma, y con un `<summary>` pegado al lado, o sea
     sin la excepción de separación que la propia norma admite. El barrido lo cazó
     en siete casillas. Se le pone el piso y no se le toca el aspecto: el botón
     sigue siendo un enlace subrayado, con dos píxeles más de área que acertar. */
  .enlace-pie {
    background: none; border: 0; padding: 0; font: inherit; cursor: pointer;
    color: var(--texto-3); text-decoration: underline;
    text-underline-offset: 3px; display: inline-flex; align-items: center;
    gap: var(--e1); min-height: 24px;
    transition: color var(--mov-rapido) var(--estandar);
  }
  .enlace-pie:hover { color: var(--texto); }
  .atajos {
    border: 1px solid var(--linea-fuerte); border-radius: var(--radio-grande);
    background: var(--superficie); color: var(--texto);
    padding: var(--e5); width: min(30rem, calc(100vw - 2rem));
    max-height: calc(100vh - 4rem);
  }
  .atajos::backdrop { background: rgba(0, 0, 0, .55); }
  .atajos h2 { margin: 0; font-size: 1.1rem; font-weight: 650; }
  .atajos-nota { margin: var(--e2) 0 var(--e4); font-size: .82rem;
                 color: var(--texto-3); line-height: 1.55; }
  .atajos-cierre { float: right; margin-left: var(--e3); }
  .atajos-cierre button {
    border: 1px solid var(--borde-control); background: transparent;
    color: var(--texto-2); font: inherit; font-size: .78rem;
    padding: var(--e1) var(--e3); border-radius: var(--radio); cursor: pointer;
    transition: background-color var(--mov-rapido) var(--estandar),
                color var(--mov-rapido) var(--estandar);
  }
  .atajos-cierre button:hover { background: var(--superficie-alta);
                                color: var(--texto); }
  .atajos-lista { list-style: none; margin: 0; padding: 0; }
  .atajos-lista li {
    display: flex; align-items: baseline; gap: var(--e3);
    padding: var(--e2) 0; border-top: 1px solid var(--linea);
    font-size: .88rem; color: var(--texto-2);
  }
  .atajos-lista li > span { flex: none; width: 8.5rem; display: flex;
                            gap: var(--e1); flex-wrap: wrap; }
  .atajos-sub { margin: var(--e5) 0 var(--e2); font-size: .72rem;
                letter-spacing: .1em; text-transform: uppercase;
                color: var(--texto-3); font-weight: 650; }

  /* ═══ 5d · LA COMPOSICIÓN — el panel, el par, el sello y el medidor ════════
     EL DEFECTO QUE ESTA SECCIÓN VIENE A CERRAR (dirección de arte, 9-ago-2026),
     medido con capturas propias a 1440, 1024, 760 y 390 px en los dos modos: la
     plataforma estaba ESCRITA como un informe y no COMPUESTA como un producto.
     La prueba estaba en el ancho. El lienzo del PC son 1.150 px; los gráficos y
     las tablas los usaban enteros, pero TODA la prosa vivía en una columna de
     62–74 ch y dejaba media pantalla en blanco a su derecha — en cada bloque, en
     cada pantalla. Y ningún bloque tenía contenedor: título, párrafo y dibujo
     flotaban sobre el mismo fondo, con el mismo peso y el mismo aire. Eso es lo
     que se lee como «documento largo» en vez de «tablero»: no faltaba contenido,
     faltaba COMPOSICIÓN.

     LA PIEZA QUE LO RESUELVE ES UNA SOLA, y por eso esta sección es corta: EL
     PANEL. Una sección con su caja, su cabecera y su cuerpo. Hace tres cosas de
     un golpe, y ninguna es decoración:
       1 · El margen derecho deja de ser vacío y pasa a ser superficie: el mismo
           párrafo, dentro de una caja, se lee como una decisión de composición.
       2 · El título de la sección gana un sitio propio en vez de ser un `<h2>`
           suelto a la misma distancia de lo de arriba que de lo de abajo.
       3 · LA PROCEDENCIA ENCUENTRA SU LUGAR. Iba en un ensayo al pie, con los
           nombres de los archivos de Python en la cara del cliente. Ahora va en
           la cabecera del panel, en el idioma del negocio («Auditoría de tiempos
           · corte marzo de 2026»), y el detalle técnico se pliega. No se pierde
           —es valor y la casa lo exige—: se muestra como corresponde.

     RELIEVE POR SUPERFICIE Y FILETE, jamás sombra ni degradado (manual §08). El
     panel no es una tarjeta de moda: es el recuadro del papel impreso. */
  .panel {
    border: 1px solid var(--linea); border-radius: var(--radio-grande);
    background: var(--superficie); margin: 0 0 var(--e5); min-width: 0;
  }
  /* La cabecera del panel: a la izquierda QUÉ es, a la derecha DE DÓNDE SALE.
     Se separa con filete porque es la misma división del papel impreso: el
     encabezado de una tabla y su cuerpo. */
  .panel-cabeza {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: var(--e2) var(--e4); flex-wrap: wrap;
    padding: var(--e4) var(--e5); border-bottom: 1px solid var(--linea);
  }
  .panel-titulo {
    margin: 0; font-size: .95rem; font-weight: 650; color: var(--texto);
    letter-spacing: -.005em; line-height: 1.35; min-width: 0; max-width: 52ch;
  }
  /* El rótulo pequeño que dice de qué habla el panel antes de leer el título.
     Versalitas y tinta de detalle: se ve sin leerse, que es lo que se le pide. */
  .panel-ojo {
    display: block; font-size: .66rem; letter-spacing: .14em; font-weight: 650;
    text-transform: uppercase; color: var(--texto-3); margin-bottom: var(--e1);
  }
  .panel-cuerpo { padding: var(--e4) var(--e5) var(--e5); min-width: 0; }
  .panel-cuerpo > :first-child { margin-top: 0; }
  .panel-cuerpo > :last-child { margin-bottom: 0; }
  /* Un panel puede llevar una tabla a sangre: la tabla ya trae sus filetes y un
     acolchado doble la separaría de su propio marco. */
  .panel-cuerpo.a-sangre { padding: 0; }
  .panel-cuerpo.a-sangre .tabla-caja { margin: 0; }
  .panel-cuerpo.a-sangre .tabla th:first-child,
  .panel-cuerpo.a-sangre .tabla td:first-child { padding-left: var(--e5); }
  .panel-cuerpo.a-sangre .tabla th:last-child,
  .panel-cuerpo.a-sangre .tabla td:last-child { padding-right: var(--e5); }
  .panel-cuerpo.a-sangre .tabla tr:last-child td { border-bottom: 0; }
  /* Dentro de un panel, la banda de cifras pierde su propia caja: dos marcos
     concéntricos son el defecto clásico de este armazón. */
  .panel .cifras { border: 0; background: none; padding: 0; margin: 0; }
  .panel .lista { border-top: 0; }
  .panel .lista .doc:last-child { border-bottom: 0; }
  /* Los dibujos traen su propio aire de cuando vivían sueltos en la página. Aquí
     el aire ya lo pone el panel, y dos acolchados sumados dejan el hueco que
     hace que un bloque parezca no haber cargado. */
  .panel .grafico, .panel .curva { margin-top: 0; margin-bottom: 0; }
  .panel .fuente { border-top: 0; padding-top: 0; margin-bottom: 0; }

  /* SE LLAMA «SELLO» Y NO «PROCEDENCIA», y la razón queda escrita porque es una
     reconciliación entre dos sesiones que trabajaron el mismo árbol el mismo
     día: `.procedencia` ya existía y es de la pantalla de dictado, donde marca
     con coral de dónde salen los datos del paciente —una advertencia, y por eso
     se gana el coral—. Esto es otra cosa: una acotación callada en la cabecera
     de un panel. Dos significados en una ficha y el que pierde es el que grita
     —la misma regla que esta hoja ya aplicó al coral—, así que el nombre se
     queda con quien llegó primero y el recién llegado se busca el suyo. */
  /* «flex: 0 1 auto» y «min-width: 0», y no «flex: none»: medido con navegador en
     la escalera de anchos con zoom (9-ago-2026), un sello que se niega a encoger
     empujaba la cabecera del panel fuera de la página a partir de unos 240 px de
     ancho útil — que es lo que queda de un teléfono al 150 % de zoom. Un rótulo
     de acotación es lo PRIMERO que debe ceder sitio, nunca lo último. */
  .sello {
    flex: 0 1 auto; min-width: 0; overflow-wrap: anywhere;
    font-size: .72rem; color: var(--texto-3); line-height: 1.45;
    text-align: right; letter-spacing: .01em; margin: 0; max-width: 26ch;
  }
  .sello b { color: var(--texto-2); font-weight: 650;
             font-variant-numeric: tabular-nums; }
  @media (max-width: 720px) {
    /* Cuando la cabecera se apila, un texto alineado a la derecha debajo de un
       título alineado a la izquierda se lee como un descuido. */
    .sello { text-align: left; max-width: none; }
  }

  /* El plegable de la procedencia técnica. Cerrado por defecto: quien dirige no
     entra a leer rutas de archivos, y quien audita sabe dónde pulsar. */
  .detalle-fuente { margin: 0 0 var(--e6); border-top: 1px solid var(--linea); }
  .detalle-fuente > summary {
    cursor: pointer; list-style: none; padding: var(--e3) 0;
    font-size: .74rem; letter-spacing: .1em; text-transform: uppercase;
    font-weight: 650; color: var(--texto-3);
    display: flex; align-items: center; gap: var(--e2);
    transition: color var(--mov-rapido) var(--estandar);
  }
  .detalle-fuente > summary::-webkit-details-marker { display: none; }
  /* El signo dice el estado, y GIRA para decir que cambió: continuidad, no un
     glifo que se sustituye por otro (§6, movimiento con propósito). */
  .detalle-fuente > summary::before {
    content: "+"; flex: none; display: inline-grid; place-items: center;
    width: 1.15rem; height: 1.15rem; font-size: .95rem; line-height: 1;
    border: 1px solid var(--linea-fuerte); border-radius: var(--radio-chico);
    color: var(--texto-3);
    transition: transform var(--mov-medio) var(--estandar),
                color var(--mov-rapido) var(--estandar);
  }
  .detalle-fuente[open] > summary::before { transform: rotate(45deg); }
  .detalle-fuente > summary:hover { color: var(--texto-2); }
  .detalle-fuente > summary:hover::before { color: var(--texto-2); }
  .detalle-fuente .fuente { border-top: 0; padding-top: 0;
                            margin: 0 0 var(--e4); max-width: 78ch; }

  /* EL «POR QUÉ» PLEGADO. Un asunto de dirección se lee por su cifra y por su
     forma; la explicación es para cuando se decide actuar. Estaba siempre
     abierta y gastaba tres o cuatro renglones por asunto — siete asuntos,
     veinticinco renglones de prosa antes de llegar al pie.

     VIVE DENTRO DE LA FILA DE PROCEDENCIA, no en un renglón propio: cerrado es
     una palabra más al final de esa línea y abierto se lleva la fila entera. Con
     renglón propio, cada asunto pasaba de dos líneas a tres y los siete juntos
     volvían a ser una pantalla de prosa —con la prosa escondida, que es peor,
     porque ocupa igual y no dice nada—. */
  .porque { margin: 0; min-width: 0; }
  .porque > summary {
    /* DE BLOQUE POR OMISIÓN, Y EN LÍNEA SOLO DONDE COMPARTE RENGLÓN
       (4-sep-2026). Este summary era «inline-flex» siempre, y una caja en
       línea no manda sobre la altura de su renglón: la fija el «line-height»
       del contenedor. Cuando el rótulo envuelve a dos líneas —y envuelve en
       cuanto lleva un contador, «… — 4 cosas, cada una con su condición»— la
       caja crece, el renglón no, y lo que sobra se mete en el plegable de
       abajo. El barrido lo cazó en «Mi día» a 1366 px: 1054 × 8 px pisados
       sobre «Procedencia y atajos».
       El 2-sep se intentó con «vertical-align: top», que alinea la caja arriba
       del renglón y por tanto la hace sobresalir por ABAJO: movió el defecto,
       no lo quitó. La forma de bloque no tiene renglón que la contenga, así
       que el «details» crece con su rótulo. La variante en línea sobrevive
       donde de verdad hace falta —dentro del pie de un asunto, compartiendo
       renglón con el módulo y el corte—, y ahí el contenedor es flex, que sí
       reserva la altura de sus hijos. */
    cursor: pointer; list-style: none; display: flex; align-items: center;
    flex-wrap: wrap; box-sizing: border-box; width: 100%;
    gap: var(--e2); font-size: .74rem; letter-spacing: .08em; font-weight: 650;
    text-transform: uppercase; color: var(--texto-3);
    transition: color var(--mov-rapido) var(--estandar);
  }
  .asunto-pie .porque > summary {
    display: inline-flex; flex-wrap: nowrap; width: auto; vertical-align: top;
  }
  .porque > summary::-webkit-details-marker { display: none; }
  /* EL MISMO SIGNO QUE EL PLEGABLE DEL PIE, y a propósito: dos plegables en la
     misma pantalla con dos marcas distintas se leen como dos cosas distintas. */
  .porque > summary::before {
    content: "+"; flex: none; display: inline-grid; place-items: center;
    width: .95rem; height: .95rem; font-size: .8rem; line-height: 1;
    border: 1px solid var(--linea-fuerte); border-radius: var(--radio-chico);
    transition: transform var(--mov-medio) var(--estandar);
  }
  .porque[open] > summary::before { transform: rotate(45deg); }
  .porque > summary:hover { color: var(--texto-2); }
  /* Abierta, la explicación se lleva toda la fila: dentro de la caja flexible
     del pie se quedaría en la anchura de su propia palabra. */
  .porque[open] { flex: 1 1 100%; }

  /* AIRE ENTRE DOS PLEGABLES SEGUIDOS (2-sep-2026, barrido visual). Su rótulo
     es «inline-flex»: cuando envuelve a dos renglones en el teléfono, su caja
     crece hacia abajo y se mete en la del plegable siguiente — dos rótulos
     pisados, y el de abajo cuesta pulsarlo. Con «margin: 0» no había nada que
     lo impidiera. */
  .porque + .porque { margin-top: var(--e2); }

  /* Y EN EL TELÉFONO, CADA RÓTULO EN SU RENGLÓN. «inline-flex» deja que la caja
     del rótulo comparta línea con lo que venga después; cuando el texto envuelve
     a dos renglones —que en 390 px es lo normal— crece hacia abajo y se mete en
     el plegable siguiente. Con «flex» ocupa su propia línea y no pisa a nadie.
     En el monitor sigue «inline-flex», que es lo que permite ponerlos en fila. */
  /* EL PLEGABLE, SIEMPRE BLOQUE Y CON SU RÓTULO EN SU RENGLÓN (2-sep-2026).
     Esto empezó acotado al teléfono y volvió a aparecer en otra pantalla el
     mismo día: mientras el rótulo sea «inline-flex» dentro de algo en línea,
     se apoya en la línea base y al envolver sobresale hacia el plegable
     siguiente. El ancho al que eso ocurre depende del largo del rótulo, no de
     la pantalla, así que acotarlo por ancho era perseguir el síntoma. Los
     plegables que de verdad van en fila —los del pie— lo piden con su propia
     clase, y no al revés. */
  .porque { display: block; }
  .porque > summary {
    /* «line-height» EXPLÍCITO, y es la causa raíz de un roce que se persiguió
       tres veces el 2-sep-2026 antes de encontrarla: este rótulo hereda un
       interlineado pensado para prosa de 1 rem y lo aplica a su propia letra
       de 0,74 rem, así que cuando envuelve a dos renglones su contenido mide
       más que la caja que lo contiene y se derrama sobre el plegable de
       abajo. Se persiguió por ancho de pantalla y por tipo de caja, que eran
       síntomas: el defecto era este número que nadie había escrito. */
    display: flex; width: 100%; box-sizing: border-box;
    line-height: 1.35; flex-wrap: wrap;
    /* Y RELLENO PROPIO ARRIBA Y ABAJO: que la caja contenga su texto sin
       depender del margen del vecino. Un margen entre hermanos solo separa a
       los que son hermanos directos, y estos plegables a veces llevan algo en
       medio; el relleno viaja con el elemento y funciona en los dos casos. */
    padding-block: var(--e1);
  }
  /* Y AIRE PROPIO ARRIBA, EN TODOS Y NO SOLO ENTRE HERMANOS DIRECTOS. El
     selector «.porque + .porque» solo alcanza a dos plegables pegados, y en la
     portada del día hay un párrafo entre medias: el margen no se aplicaba y
     quedaban seis píxeles de roce. Un margen propio viaja con el elemento y
     funciona en los dos casos; de paso los plegables se leen como cosas
     distintas y no como una lista apretada. */
  .porque { margin-top: var(--e4); }

  /* EL CUPO, FUERA DEL «DETAILS» (25-sep-2026, encargo CODE punto 3). Vivía
     colapsado junto al detalle por recurso; el director quiere el total del
     día contra el cupo A LA VISTA, no un clic más adentro. Caja neutra —igual
     que `.aviso`— porque el cupo en sí no es un hallazgo; el exceso, si lo
     hay, ya se resalta en coral dentro del propio texto. */
  .cupo-visible { margin: var(--e3) 0; padding: var(--e3); border-radius: var(--radio);
                  border: 1px solid var(--linea-fuerte); background: var(--superficie); }
  .cupo-visible h2 { margin: 0 0 var(--e2); font-size: 1rem; }

  /* UN HALLAZGO SE VE, NO SE LEE ENTRE LÍNEAS (25-sep-2026, encargo CODE). El
     mismo coral que ya usan `.alerta b` y `.cifra.atencion b` para lo que
     merece atención — no un color nuevo por pantalla nueva. */
  .hallazgo-extra { color: var(--coral); }
  .hallazgo-extra b { color: inherit; }
  td.hallazgo-extra { font-weight: 600; }

  /* EL PAR: un dibujo y su lectura, lado a lado en el monitor y uno debajo del
     otro en el teléfono. Existía suelto en dos pantallas; aquí se declara una
     vez para las nueve, que es lo que hace que se sientan el mismo producto. La
     pista del texto es la MENOR: el dibujo responde la pregunta y el texto la
     matiza — al revés, el gráfico se lee como ilustración. */
  .par {
    display: grid; gap: var(--e4) var(--e6); align-items: start;
    grid-template-columns: 1fr;
  }
  .par > * { min-width: 0; }
  @media (min-width: 900px) {
    .par { grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); }
    /* Dentro de un par el dibujo ya está acotado por su columna: el tope de
       36 rem lo dejaría flotando con un hueco a su derecha. */
    .par .grafico { max-width: 100%; }
  }
  .par-lectura > :first-child { margin-top: 0; }
  /* 46 ch en la columna estrecha: la medida de línea manda sobre el ancho
     disponible, y en una columna de apoyo se lee mejor corta. */
  .par-lectura p { max-width: 46ch; }

  /* LA REJILLA DE PANELES: dos secciones del mismo rango, una al lado de la
     otra. Se rompe sola cuando el sitio no da, y sus paneles se estiran a la
     misma altura para que la vista tenga un renglón donde apoyarse. */
  .paneles {
    display: grid; gap: var(--e4); margin: 0 0 var(--e5);
    /* «min(19rem, 100%)» y no «19rem» a secas: en una pista de rejilla, un
       mínimo mayor que el sitio disponible NO se encoge — empuja la página. A
       320 px de ancho (el más estrecho que mide la batería con navegador) el
       lienzo deja 288 px útiles y una pista de 304 px desbordaba. Con el mínimo
       acotado al 100 %, la pista cede antes de empujar. */
    grid-template-columns: repeat(auto-fit, minmax(min(19rem, 100%), 1fr));
    align-items: stretch;
  }
  .paneles > .panel { margin: 0; min-width: 0; display: flex;
                      flex-direction: column; }
  .paneles > .panel > .panel-cuerpo { flex: 1; }

  /* ── EL MEDIDOR: la forma de la cifra, al lado de la cifra ─────────────────
     POR QUÉ ENTRA (dirección de arte, 9-ago-2026). La portada de quien dirige no
     tenía UN SOLO GRÁFICO: números grandes y párrafos. Un asunto decía «68,9 %»
     y luego explicaba en tres renglones que estaba por debajo del acuerdo — la
     comparación que el ojo hace en una décima de segundo, contada con palabras.
     El medidor la dibuja: la barra es la cifra, la marca es el acuerdo, y el
     coral se gana igual que en el resto de la casa.

     ES SVG A MANO, como todo dibujo de esta plataforma: cero librerías, lienzo
     de 100 unidades, y la geometría con PUNTO decimal —una coma la descarta el
     navegador y la barra sale de alto cero, defecto ya visto aquí el 8-ago-2026
     con la curva horaria—. La geometría la calcula la vista, no la plantilla.
     Y sin una letra dentro del dibujo: los rótulos los pone el HTML, porque un
     texto dentro de un lienzo de 100 unidades se multiplica por la escala del
     contenedor (así salieron unos rótulos a 30 px el 8-ago-2026). */
  /* «min(22rem, 100%)»: un tope en rem no encoge cuando el sitio es menor que el
     tope, y a 195 px de ancho útil —un teléfono al 200 % de zoom— el medidor
     empujaba la página cien píxeles. El tope acota; el 100 % obliga a ceder. */
  .medida { margin: var(--e3) 0 0; max-width: min(22rem, 100%); }
  /* Alto declarado y «preserveAspectRatio: none» en el marcado: la barra se
     estira al ancho de su columna sin engordar, que es lo que se le pide a un
     medidor —lo que significa algo es el LARGO, nunca el grosor—. Sin alto fijo,
     el mismo lienzo saldría de 14 px en una pantalla y de 20 en otra. */
  .medidor { display: block; width: 100%; height: 12px; }
  .medidor rect.m-pista { fill: var(--linea); }
  .medidor rect.m-valor { fill: var(--texto-3); transform-origin: left center;
                          transform-box: fill-box; }
  .asunto.critico .medidor rect.m-valor,
  .asunto.atencion .medidor rect.m-valor { fill: var(--coral); }
  /* La marca del acuerdo va en tinta de texto y no en coral: el coral ya lo
     lleva lo que incumple, y el acuerdo no incumple nada — es la referencia. */
  .medidor rect.m-umbral { fill: var(--texto); }
  .medidor-pie { margin: var(--e2) 0 0; font-size: .7rem; color: var(--texto-3);
                 display: flex; justify-content: space-between;
                 gap: var(--e3); flex-wrap: wrap; line-height: 1.4;
                 letter-spacing: .01em; }
  .medidor-pie b { color: var(--texto-2); font-weight: 650;
                   font-variant-numeric: tabular-nums; }
  /* La referencia va a la derecha y sin envolver: es la que se compara con la
     marca del dibujo, y una referencia partida en dos renglones desalinea la
     única pareja de esta pieza que tiene que quedar alineada. */
  .medidor-pie > :last-child { flex: none; white-space: nowrap; }

  /* ── EL ESTADO VACÍO, COMPUESTO ────────────────────────────────────────────
     El defecto que cierra la pasada: a quien no dicta, la pantalla de dictado le
     contestaba con tres párrafos y dos botones en un lienzo de 1.440 px — media
     pantalla muerta, con aire de carta de rechazo. Un producto serio contesta
     con el estado de lo que se preguntó y con lo que esa persona SÍ puede hacer.
     La pieza es una rejilla de dos pistas, y sirve para las tres pantallas de
     este tipo, que hasta hoy eran tres párrafos distintos con la misma forma. */
  .sin-permiso { display: grid; gap: var(--e4); grid-template-columns: 1fr;
                 margin: 0 0 var(--e5); align-items: start; }
  .sin-permiso > * { min-width: 0; }
  @media (min-width: 900px) {
    .sin-permiso { grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr); }
  }
  /* Lo que SÍ está abierto: filas de acción, no un botón suelto debajo de un
     párrafo. Cada una es un enlace entero, y es una TARJETA — la misma pieza de
     «Mi día» en su variante estrecha. No se inventa un primitivo nuevo para lo
     que ya existe: menos piezas es lo que hace que nueve pantallas se sientan un
     solo producto, y además hereda de `.tarjeta` el no llevar subrayado (la
     regla de enlaces del §5 ya la exceptúa por su nombre).
     Se llama «caminos» y no «puertas» porque `.puertas` es de la entrada, donde
     son los proveedores de acceso: dos sentidos en un nombre envejecen mal. */
  .caminos { display: flex; flex-direction: column; gap: var(--e2); }
  .tarjeta.camino {
    flex-direction: row; align-items: baseline; gap: var(--e3);
    padding: var(--e3) var(--e4); background: var(--fondo);
  }
  .camino-texto { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
  /* El camino puede llevar el glifo de su módulo — el MISMO del riel, para que
     «a dónde lleva» se reconozca antes de leerse. Alineado con la primera línea
     del título y no centrado en la caja: en una fila de dos renglones, un glifo
     centrado flota entre los dos y no señala a ninguno. */
  .camino-glifo { flex: none; display: grid; place-items: center;
                  width: 34px; height: 34px; border-radius: var(--radio);
                  border: 1px solid var(--linea-fuerte); color: var(--texto-3);
                  align-self: start; }
  .camino-glifo .ico { width: 19px; height: 19px; }
  .tarjeta.camino:hover .camino-glifo { color: var(--texto);
                                        border-color: var(--texto-3); }
  .camino-titulo { font-size: .92rem; font-weight: 650; }
  .camino-titulo::after { content: " →"; color: var(--texto-3); font-weight: 400; }
  /* Mismo tope, y aquí era el caso extremo: un `span.camino-nota` de 166
     caracteres en un solo renglón. A 166 caracteres no hay salto de renglón que
     buscar — el ojo vuelve al principio de la línea equivocada. */
  .camino-nota { font-size: .78rem; color: var(--texto-3); line-height: 1.5;
                 max-width: 68ch; }

  /* ── EL TABLERO DE ENTRADA (21-ago-2026, encargo del director) ────────────
     «En el inicio yo quisiera que se viera como más gráfico… que aparezcan los
     cuatro íconos.» Es la MISMA tarjeta-enlace de `.caminos` en su variante
     grande, y no una pieza nueva: la fila del riel, el camino estrecho de
     gestión y el ícono grande de la entrada son tres tamaños de un solo objeto.
     Inventar aquí un cuarto primitivo es lo que hace que una pantalla se sienta
     de otro producto.

     LA REJILLA ES `auto-fit` Y NO «dos columnas»: con cuatro íconos da 2×2 en un
     portátil, 4×1 en pantalla ancha y 1×4 en el teléfono, sin una sola consulta
     de medios. Y el mínimo va con `min(…, 100%)` porque en 320 px una columna de
     17rem desborda la página — es el defecto que esta hoja ya pagó una vez.

     SE MANTIENE «superficie y línea, jamás sombra»: lo que cambia al pasar por
     encima es el fondo, el filete y el color del glifo. Nada se mueve de sitio:
     una tarjeta que se levanta al pasar el ratón desplaza a las de al lado y
     obliga a perseguirla. */
  .caminos-tablero {
    display: grid; gap: var(--e3); max-width: 62rem;
    grid-template-columns: repeat(auto-fit, minmax(min(17rem, 100%), 1fr));
  }
  /* LOS TRES BLOQUES, con el rótulo de a qué pregunta contesta cada par. El
     encabezado usa la misma tinta y el mismo tratamiento que el rótulo de grupo
     del riel —versalita pequeña, `--texto-3`— porque son la misma idea en dos
     sitios: «esto de aquí abajo va junto». */
  .bloque-tablero { margin: 0 0 var(--e5); }
  .grupo-tablero {
    margin: 0 0 var(--e2); font-size: .72rem; font-weight: 650;
    letter-spacing: .1em; text-transform: uppercase; color: var(--texto-3);
  }
  .tarjeta.camino-tablero {
    flex-direction: row; align-items: flex-start; gap: var(--e3);
    padding: var(--e4); background: var(--superficie);
  }
  .tarjeta.camino-tablero .camino-titulo { font-size: 1.02rem; }
  .tarjeta.camino-tablero:hover { background: var(--superficie-alta);
                                  border-color: var(--linea-fuerte); }
  /* El glifo grande: 48 px de caja y 26 de trazo. El mismo `<symbol>` del riel,
     al doble — un juego de íconos aparte para esta pantalla enseñaría dos
     símbolos distintos para la misma pantalla, y el riel dejaría de reconocerse. */
  .camino-glifo.grande { width: 48px; height: 48px;
                         border-radius: var(--radio-grande); }
  .camino-glifo.grande .ico { width: 26px; height: 26px; }
  .tarjeta.camino-tablero:hover .camino-glifo { color: var(--texto);
                                                border-color: var(--texto-3); }
  /* En el teléfono el glifo baja a 40 px: a 48 px se comía la mitad del ancho
     útil y la nota quedaba en columnas de cuatro palabras. */
  @media (max-width: 30rem) {
    .caminos-tablero { gap: var(--e2); }
    .tarjeta.camino-tablero { padding: var(--e3); }
    .camino-glifo.grande { width: 40px; height: 40px; }
    .camino-glifo.grande .ico { width: 22px; height: 22px; }
  }

  /* ── EL RIEL: ICONOGRAFÍA Y EL HUECO DEL PIE ──────────────────────────────
     DOS DEFECTOS DE LA MISMA PIEZA, vistos en captura propia a 1440 px: el riel
     era texto plano sin ningún ancla para el ojo —ocho renglones idénticos— y
     entre el último módulo y la identidad quedaba un vacío de casi cuatrocientos
     píxeles, que se lee como una pantalla que no terminó de cargar.
       · EL ÍCONO no es adorno: en una lista de ocho, la forma se reconoce antes
         que la palabra, y quien vuelve mil veces al mismo módulo deja de leer.
         Trazo de 1,5 y tinta de detalle, para que NO compita con el nombre.
       · EL HUECO se cierra con INFORMACIÓN, jamás con relleno: al pie del riel
         va el corte de los datos y el día hábil — lo que ya se decía suelto en
         cada pantalla, puesto donde acompaña sin estorbar.

     «.modulos .grupo-enlaces a» y NO «.modulos a» a secas, a propósito: el
     guardián de la interfaz busca el PRIMER bloque «.modulos a {» de la hoja
     para comprobar que la tinta del módulo y la del rótulo de grupo son
     distintas, y una hoja con dos definiciones del mismo selector en dos sitios
     envejece mal. Además este selector dice dónde vive el ícono: en el grupo. */
  /* EL ALMACÉN DE FORMAS, apagado con una regla y no con el atributo «hidden»:
     la regla del navegador que apaga lo oculto vive en el espacio de nombres de
     HTML y un `<svg>` no está en ese espacio, así que el atributo no lo apagaba
     — el almacén ocupaba sus 300 × 150 px por defecto EN EL FLUJO, empujaba el
     riel 157 px hacia abajo y desbordaba la página en cualquier teléfono (medido
     con navegador el 9-ago-2026; ver `iconos_riel.html`). Las referencias «use»
     siguen resolviendo dentro de un subárbol apagado. */
  .iconos-fuente { display: none; }
  .modulos .grupo-enlaces a { display: flex; align-items: center; gap: var(--e2); }
  .ico { flex: none; width: 17px; height: 17px; display: block;
         color: var(--texto-3);
         transition: color var(--mov-rapido) var(--estandar); }
  .modulos a:hover .ico, .modulos a.activa .ico { color: var(--texto); }
  .rail-pulso {
    margin-top: auto; padding: var(--e4) var(--e3) 0;
    font-size: .7rem; line-height: 1.5; color: var(--texto-3);
    display: none;
  }
  .rail-pulso dt {
    font-size: .6rem; letter-spacing: .14em; text-transform: uppercase;
    color: var(--texto-3);
  }
  .rail-pulso dd { margin: 0 0 var(--e2); color: var(--texto-2); }
  @media (min-width: 1040px) {
    /* Solo en el PC: es el único sitio donde sobra alto. En el teléfono el riel
       es una barra que rueda, y ahí esto sería un estorbo. */
    .rail-pulso { display: block; }
    .rail-pulso + .quien { margin-top: var(--e4); }
  }

  /* ─── 6 · EL SISTEMA DE MOVIMIENTO ──────────────────────────────────────── */
  /* Lo que aparece se asienta. EL RECORRIDO SUBIÓ DE 6 A 10 px (9-ago-2026), y
     el motivo es una medición y no un gusto: el operador dijo «no veo animación
     ni nada chévere», y al mirarlo con el número delante tenía razón — 6 px en
     180 ms, en una pantalla de teléfono sostenida a medio metro, está por
     debajo de lo que el ojo registra como movimiento. La duración NO se tocó:
     el defecto era el recorrido, no el tiempo. 10 px sigue siendo corto —lo que
     separa «con oficio» de «con efectos» es que el contenido esté completo y
     legible desde el primer fotograma, y lo está. */
  .aparece {
    opacity: 0; animation: asentarse var(--mov-medio) var(--entrar) forwards;
    animation-delay: var(--d, 0ms);
  }
  @keyframes asentarse {
    from { opacity: 0; transform: translateY(10px); }
    to   { opacity: 1; transform: none; }
  }
  /* Una lista llega en orden, no de golpe: el escalonado dice «esto vino junto
     y en este orden». El techo de 120 ms es la regla — con cincuenta filas, un
     escalonado sin tope se convierte en una espera, que es justo lo contrario
     de lo que la animación quería decir. */
  .lista > .doc {
    opacity: 0; animation: asentarse var(--mov-rapido) var(--entrar) forwards;
    animation-delay: min(calc(var(--i, 0) * 15ms), 120ms);
  }

  /* La barra de progreso de la navegación: 2 px que dicen «voy». Sin ella, un
     clic que tarda parece un clic que no pasó. Solo transform, así que no
     empuja ni un píxel de la página. */
  .progreso {
    position: fixed; inset: 0 0 auto 0; height: 2px; z-index: 50;
    background: var(--coral); transform: scaleX(0); transform-origin: left;
    opacity: 0; pointer-events: none;
  }
  .progreso.andando { opacity: 1; animation: avanzar 8s var(--entrar) forwards; }
  @keyframes avanzar {
    0%   { transform: scaleX(0); }
    40%  { transform: scaleX(.7); }
    100% { transform: scaleX(.96); }
  }

  /* La curva y el pez: movimiento que dice algo. La curva DIBUJA el dato al
     llegar (por eso crece desde la base); el pez respira en la única pantalla
     donde alguien espera, y esa respiración es la señal de que el sistema
     sigue vivo mientras la página se recarga sola. */
  .curva rect.b { transform: scaleY(0);
                  animation: crecer var(--mov-lento) var(--entrar) forwards;
                  animation-delay: min(calc(var(--i, 0) * 12ms), 180ms); }
  @keyframes crecer { to { transform: scaleY(1); } }
  /* Las barras horizontales dibujan el dato desde el eje, en el sentido en que se
     leen. Mismo escalonado y mismo techo que la curva. */
  .grafico rect.g-barra { transform: scaleX(0);
                          animation: crecer-x var(--mov-lento) var(--entrar) forwards;
                          animation-delay: min(calc(var(--i, 0) * 12ms), 180ms); }
  @keyframes crecer-x { to { transform: scaleX(1); } }
  /* El dibujo llena su pecera y conserva su relación: quien fija el tamaño es el
     sitio donde vive, no la mascota. Así el mismo SVG preside la puerta y puede
     aparecer mañana en otra escala sin tocar el dibujo. */
  .pez { width: 100%; height: auto; display: block; overflow: visible; }
  /* La espina toma la ficha coral desde AQUÍ y no desde el dibujo: el color de
     marca vive en un solo sitio, y en el modo claro está entintado para que se
     lea sobre blanco (el coral puro sobre blanco da 2,6:1). */
  .pez .p-espina { stroke: var(--coral); }
  .pez .p-nada { transform-origin: 33px 24px;
                 animation: nadar 6s ease-in-out infinite; }
  .pez .p-cola { transform-origin: 13.5px 24px;
                 animation: mecer 2.4s ease-in-out infinite; }
  @keyframes nadar {
    0%, 100% { transform: translateY(0); }
    50%      { transform: translateY(-2px); }
  }
  @keyframes mecer {
    0%, 100% { transform: rotate(0deg); }
    25%      { transform: rotate(4deg); }
    75%      { transform: rotate(-4deg); }
  }

  /* ─── EL REVELADO: el pez se dibuja a sí mismo ──────────────────────────────
     QUÉ ES Y POR QUÉ ESTE MOVIMIENTO Y NO OTRO (7-ago-2026, encargo del
     operador: «animaciones chéveres… que se vea muy pro en inteligencia,
     ingeniería y medicina»). El pez ya estaba dibujado con `pathLength="1"` en
     CADA trazo: quien lo dibujó dejó el gancho puesto para esto, y no hizo falta
     redibujar nada.

     El pez es su propia radiografía —así se decidió el 6-ago— y esta animación es
     lo que le faltaba para decirlo: **los trazos aparecen en el orden en que un
     radiólogo lee una placa.** Primero el contorno del cuerpo (la silueta), luego
     la branquia y la cola (los bordes), después LA ESPINA EN CORAL (la
     estructura, que es lo que se busca), luego las costillas de dentro hacia
     fuera, y al final el ojo. No es una decoración girando: es el gesto del
     oficio, y esa es la única clase de movimiento que esta casa se permite.

     POR QUÉ `stroke-dashoffset` ES BARATA aunque no sea `transform`: no mueve el
     diseño ni desplaza un píxel de la página — repinta el trazo de un vector de
     64×48 y nada más. La lista de propiedades baratas existe para prohibir el
     SALTO DE CONTENIDO, y esto no salta; está declarada en
     `tools/interfaz_test.py` con esta misma razón escrita.

     UN SOLO MODO, `.esperando`, Y EN BUCLE — y no hay una versión «una sola vez»
     porque no hay dónde ponerla sin mentir. El bucle vive SOLO en la sala de
     espera, que es la única pantalla de la plataforma donde alguien aguarda algo
     que decide otra persona y que se recarga sola cada 30 s: ahí el dibujo
     repitiéndose ES el mensaje («el sistema sigue vivo»). En los estados vacíos y
     en la pantalla de error el pez se queda quieto y entero, porque a nadie le
     ayuda una mascota haciendo gracias cuando no hay nada o algo se atravesó.

     EL CICLO ACABA DONDE EMPEZÓ, y esto costó verlo (7-ago-2026): la primera
     escritura usaba los mismos fotogramas para el bucle y para una versión de una
     sola pasada, y como el último fotograma esconde el trazo —hace falta para que
     el bucle pueda volver a dibujar—, la versión de una pasada **terminaba con el
     pez desvanecido**. Se vio pintando los cuadros y mirándolos, no leyendo el
     CSS: a los 900 ms el contorno del cuerpo ya no estaba. Con un solo modo, el
     fotograma final es correcto por construcción. */
  .pez.esperando .p-t { stroke-dasharray: 1 1; stroke-dashoffset: 1;
                        animation: revelar 2600ms var(--entrar) infinite; }
  .pez.esperando .p-ojo { opacity: 0;
                          animation: asomar 2600ms var(--entrar) infinite; }
  /* EL ORDEN DE LECTURA, escrito como retrasos: silueta, borde, cola, LA ESPINA,
     las costillas de dentro hacia fuera, y el ojo al final. El escalonado es lo
     que hace que se lea como un revelado y no como ocho cosas a la vez. */
  .pez.esperando .p-cuerpo    { animation-delay: 0ms; }
  .pez.esperando .p-branquia  { animation-delay: 180ms; }
  .pez.esperando .p-cola .p-t { animation-delay: 260ms; }
  .pez.esperando .p-espina    { animation-delay: 400ms; }
  .pez.esperando .p-e2        { animation-delay: 540ms; }
  .pez.esperando .p-e1        { animation-delay: 620ms; }
  .pez.esperando .p-e3        { animation-delay: 620ms; }
  .pez.esperando .p-ojo       { animation-delay: 720ms; }
  @keyframes revelar {
    /* El trazo se completa en el primer tercio y SE QUEDA hasta el 92 %: esos dos
       tercios son la pausa en la que el dibujo está entero y quieto, que es
       cuando de verdad se lee. Sin la pausa el pez parpadearía sin descanso, que
       es lo contrario de «el sistema sigue vivo». */
    0%       { stroke-dashoffset: 1; }
    34%, 92% { stroke-dashoffset: 0; }
    100%     { stroke-dashoffset: 1; }
  }
  @keyframes asomar {
    0%       { opacity: 0; }
    34%, 92% { opacity: 1; }
    100%     { opacity: 0; }
  }
  .mic.grabando { animation: latir 1.8s ease-in-out infinite; }
  @keyframes latir { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.05); } }

  /* Cambiar de módulo no debe parpadear en blanco: el navegador que sabe
     hacerlo funde las dos vistas; el que no, sigue igual que siempre. La vista
     que llega ENTRA, no solo aparece: el mismo asentamiento de 10 px que usa
     todo lo demás, para que pasar de un módulo a otro se lea como un
     movimiento y no como un corte. La que se va solo se desvanece — salir es
     más rápido que entrar (§ el criterio del movimiento, arriba). */
  @view-transition { navigation: auto; }
  ::view-transition-old(root) { animation: irse var(--mov-rapido) var(--salir); }
  ::view-transition-new(root) { animation: llegar var(--mov-medio) var(--entrar); }
  @keyframes irse   { to   { opacity: 0; } }
  @keyframes llegar { from { opacity: 0; transform: translateY(10px); } }

  /* EL QUE PIDIÓ MENOS MOVIMIENTO, MANDA. Todo lo de arriba se apaga: no se
     atenúa, se apaga — y el contenido queda completo y quieto, nunca invisible.
     tools/interfaz_test.py comprueba que cada animación declarada arriba tenga
     su apagado aquí: una promesa de accesibilidad sin guardián es una creencia.

     Y LA INTERFAZ SIGUE VIVA SIN MOVERSE (9-ago-2026). Esta es la mitad que
     faltaba: apagar el movimiento no puede dejar una lámina muerta. Lo que
     queda vivo son los CAMBIOS DE ESTADO, que no son movimiento y que aquí
     siguen enteros — el módulo pulsado cambia de superficie, el botón cambia de
     borde y de fondo, el foco dibuja su anillo, el activo lleva su barra, y el
     contador entrega la cifra final de una vez en vez de esconderla. La
     información no se pagó con la animación: se paga con la jerarquía. */
  @media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
      animation-duration: 1ms !important; animation-delay: 0ms !important;
      animation-iteration-count: 1 !important;
      transition-duration: 1ms !important; transition-delay: 0ms !important;
    }
    .aparece, .lista > .doc { opacity: 1; transform: none; }
    .modulos a.activa::after { transform: scaleX(1); }
    .curva rect.b { transform: scaleY(1); }
    .grafico rect.g-barra { transform: scaleX(1); }
    .pez .p-nada, .pez .p-cola { transform: none; }
    /* EL REVELADO SE APAGA DEJANDO EL DIBUJO ENTERO, no a medias y no invisible:
       es el defecto clásico de este apagado. Con el trazo a `dashoffset: 0` y el
       ojo a opacidad 1, quien pide menos movimiento ve el pez completo y quieto
       —la misma pose digna que ya tenía— en vez de un contorno cortado. */
    .pez.esperando .p-t {
      stroke-dashoffset: 0 !important; stroke-dasharray: none !important;
    }
    .pez.esperando .p-ojo { opacity: 1 !important; }
    /* La mascota se queda en una POSE DIGNA, no se esconde: quieta, entera y en
       su sitio. Y la reacción al pasar por una puerta tampoco se dispara — quien
       pidió menos movimiento no pidió menos información, y la información aquí
       la da el foco, que sigue viéndose. */
    .pecera { transform: none; animation: none; }
    .progreso.andando { transform: scaleX(.96); }
    .saltar:focus-visible { transform: none; }
    ::view-transition-old(root), ::view-transition-new(root) { animation: none; }
  }
  @media (min-width: 1040px) and (prefers-reduced-motion: reduce) {
    .modulos a.activa::after { transform: scaleY(1); }
  }

  /* ─── 7 · EL EXPEDIENTE EN PAPEL ────────────────────────────────────────── */
  /* Una lista de vencimientos se imprime y se firma: si el papel sale con el
     riel de navegación y el fondo oscuro, no sirve para nada. */
  @media print {
    :root {
      --fondo: #FFFFFF; --superficie: #FFFFFF; --superficie-alta: #FFFFFF;
      --linea: #B0B0B0; --linea-fuerte: #000000; --borde-control: #000000;
      --texto: #000000; --texto-2: #000000; --texto-3: #333333;
      --coral: #000000; --coral-tinta: #FFFFFF;

      /* ── LA COSTURA DEL PAPEL (16-ago-2026) ────────────────────────────────
         HASTA HOY ESTABA ABIERTA, y al revés de como se creía: este bloque
         bajaba el cuerpo a 10,5 pt y NO tocaba la familia, así que un expediente
         impreso desde el navegador salía con la letra de la barra de tareas de
         Windows mientras el PDF que emite el servidor salía en Times. Dos
         papeles distintos del MISMO dato, y ninguno de los dos elegido.

         Ahora los dos papeles llevan la misma letra, y se declara aquí en dos
         fichas —no en veinte reglas— con la misma mecánica con la que este
         bloque ya vuelve todo negro. La familia es la que incrusta el generador
         de PDF (`tools/tipografia_documental.py`): un expediente impreso desde
         el navegador y el mismo expediente descargado del servidor tienen que
         ser el mismo documento.

         Y SE ESCRIBE EL ARGUMENTO AL LADO porque dentro de seis meses esta línea
         parecerá un descuido sin él: **en pantalla manda la lectura clínica; en
         papel manda el formato institucional**, y desde hoy los dos coinciden
         en la familia. La reserva se conserva entera por lo mismo de siempre: la
         estación que imprime puede no tener el archivo. */
      --letra: "Atkinson Hyperlegible Next", system-ui, -apple-system, "Segoe UI",
               Roboto, Arial, sans-serif;
      --letra-mono: "Atkinson Hyperlegible Mono", ui-monospace, Consolas,
                    monospace;
    }
    @page { margin: 16mm 14mm; }
    body { background: #FFFFFF; color: #000000; font-size: 10.5pt;
           font-family: var(--letra); }
    /* EL CERO RANURADO SE APAGA EN EL PAPEL. Es un rasgo de INSTRUMENTO —sirve
       para leer una cifra en pantalla, de reojo, entre otras cifras— y no de
       documento firmado: en un papel que se archiva, una barra cruzando el cero
       se lee como una tachadura, y la Resolución 1995 de 1999 pide la historia
       clínica «sin tachones, enmendaduras». Lo tabular sí se queda: alinear una
       columna de cifras sigue valiendo lo mismo impreso. */
    .medida { font-feature-settings: "tnum"; }
    .identificador { font-feature-settings: normal; }
    .rail, .pie-app, .saltar, .progreso, .boton, .quien, .modulos,
    .dictador, .mic, .pez { display: none !important; }
    body.app .marco { display: block; }
    .lienzo { max-width: none; margin: 0; padding: 0; }
    .aparece { opacity: 1; animation: none; }
    .cifras { border: 1px solid #000000; page-break-inside: avoid; }
    .doc, .tabla tr { page-break-inside: avoid; }
    .tabla th { border-bottom: 1px solid #000000; }
    .chip { border-color: #000000; color: #000000; }
    /* En papel el vencido no puede depender de que la impresora acepte fondos:
       se distingue por GROSOR de filete, que siempre sale. */
    .chip.vencido { background: #FFFFFF; color: #000000; font-weight: 700;
                    border-width: 2px; }
    .licencia-placa { padding: 0; }
    /* EL PIE DEL PAPEL —el que nombra a la institución— YA NO ESTÁ AQUÍ
       (12-ago-2026). Se mudó a `plantillas/pie_impreso.html` y sigue viajando en
       línea con cada página, porque era la ÚNICA regla de esta hoja que llevaba
       una etiqueta de plantilla (`cliente.corto`, arquitectura de cliente del
       9-ago-2026) y esta hoja pasó a servirse como archivo `.css`, donde nadie
       renderiza nada: el `{{ … }}` habría viajado literal al navegador. El
       criterio del reparto es ese y no otro — lo que es igual para todas las
       instalaciones se cachea un año; lo que cambia con el cliente se pinta en
       la página. */
  }

/* ───────────────────────────────────────────────────────────────────────────
   EL ENTREGABLE DEL DIRECTOR (9-ago-2026). No es un panel más de la pantalla:
   es una HOJA, y se ve como una hoja — fondo claro fijo, tipografía con serifa,
   márgenes de documento. La razón es que se copia y se manda: tiene que parecer
   lo que va a ser cuando salga de aquí, no una tarjeta de la aplicación.

   FONDO CLARO EN LOS DOS MODOS, a propósito y en contra de la regla general de
   la casa. En modo oscuro una hoja oscura se copia como texto blanco y llega
   ilegible a un correo de fondo blanco. Un documento se ve como un documento.
   ─────────────────────────────────────────────────────────────────────────── */
.informe-director { margin: var(--e4) 0; }
.informe-director .hoja {
  background: #FFFFFF; color: #14202B;
  border: 1px solid #D9E0E7; border-radius: var(--radio);
  padding: var(--e5) var(--e5) var(--e4);
  font-family: Georgia, "Times New Roman", serif;
}
.informe-director .titulo-informe {
  margin: 0; color: #14202B; font-weight: 650; letter-spacing: .02em;
  font-size: 1rem; line-height: 1.35; max-width: none;
}
.informe-director .fecha-informe {
  margin: 0 0 var(--e4); color: #48606F; font-size: .95rem; max-width: none;
}
.tabla-informe { border-collapse: collapse; min-width: 18rem; }
.tabla-informe th, .tabla-informe td {
  padding: var(--e2) var(--e5) var(--e2) 0; text-align: right;
  font-variant-numeric: tabular-nums; font-size: 1.15rem; color: #14202B;
}
.tabla-informe thead th { font-size: .85rem; color: #48606F; font-weight: 650;
  border-bottom: 1px solid #C8D3DC; letter-spacing: .06em; }
.tabla-informe tbody th { text-align: left; font-weight: 400; padding-right: var(--e6); }
.tabla-informe tbody tr + tr th, .tabla-informe tbody tr + tr td {
  border-top: 1px solid #EDF1F4; }
.informe-director .nota-informe {
  margin: var(--e4) 0 0; font-size: .8rem; line-height: 1.5; color: #5B6C79;
  font-family: var(--letra); max-width: 64ch;
}
/* Sin `.acciones-informe`: el botón de copiar la tabla se retiró el 13-ago-2026
   por instrucción del director («no está haciendo nada, y no lo necesito»), y
   una regla que no viste a nada es la que sobrevive a tres rediseños. */
@media print {
  /* Al imprimir, la hoja es lo único que importa. */
  .informe-director .hoja { border: 0; padding: 0; }
}

/* LA MINIVENTANA DEL PAPEL (28-ago-2026). Ocupa casi toda la pantalla a
   propósito: un documento de identidad o una póliza escaneada no se lee en un
   recuadro pequeño, y el encargo era «que él VEA». */
.miniventana {
  position: fixed; inset: 0; z-index: 60;
  background: rgba(15, 23, 42, .55);
  display: flex; align-items: center; justify-content: center;
  padding: 2vh 2vw;
}
.miniventana[hidden] { display: none; }
.miniventana-caja {
  background: #fff; border-radius: 10px; overflow: hidden;
  width: min(1100px, 96vw); height: min(88vh, 900px);
  display: flex; flex-direction: column;
  /* Sin sombra: el manual §08 la tiene entre sus «jamás». La caja se separa
     del fondo por el velo oscuro de atrás y por su propio borde, que es como
     esta casa levanta una superficie en todas las demás pantallas. */
  border: 1px solid var(--linea-fuerte, #cbd5e1);
}
.miniventana-cabeza {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; padding: .7rem 1rem; border-bottom: 1px solid var(--linea, #e2e8f0);
}
.miniventana-cabeza h2 {
  margin: 0; font-size: 1rem; font-weight: 600;
  /* El nombre original puede ser largo: se recorta antes que empujar los
     botones fuera de la caja. */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.miniventana-acciones { display: flex; align-items: center; gap: .75rem; flex: none; }
.miniventana-caja iframe { flex: 1; width: 100%; border: 0; }
/* Con la ventana abierta, el fondo no se desplaza: si se moviera, al cerrar
   la lista aparecería en otro sitio y se perdería dónde estaba uno. */
body.con-miniventana { overflow: hidden; }
