/* ═══════════════════════════════════════════════════════════════════════════
   COSMERIUM ERA III · NCRT-3
   chrome.css — El chasis permanente de la terminal.

   Todo lo que está en pantalla SIEMPRE y no pertenece a ninguna pantalla:
   bandas de clasificación, raíl de 56px, cabecera, barra de comandos, rejilla
   milimetrada, capa de textura y las seis animaciones globales del catálogo
   §7.2 que viven en el marco (page-in, page-out, pulse, marquee, noise-shift,
   caret).

   Origen: DESIGN.md §2 (los dos registros), §7 (animaciones), §8 (textura),
   §11.4 (bifurcación móvil). Referencia de implementación validada:
   prototipo/ncrt3.html.

   Se carga DESPUÉS de tokens.css y reset.css, y ANTES de components.css.
   Este fichero no define ningún componente: si un selector de aquí aparece
   dentro de una pantalla concreta, está mal puesto.

   ───────────────────────────────────────────────────────────────────────────
   CONTRATO DE MARCADO — base.html debe emitir exactamente esta estructura:

   <html lang="es" data-zona="oficial" data-acceso="0">
   <body>
     <a class="skip" href="#expediente">[ SALTAR A CONTENIDO DEL EXPEDIENTE ]</a>

     <div id="gridbg" aria-hidden="true"></div>
     <div id="texture" aria-hidden="true"><div class="grain"></div></div>

     <div class="classif classif--top" aria-hidden="true"><span class="classif__t">…</span></div>
     <div class="classif classif--bot" aria-hidden="true"><span class="classif__t">…</span></div>

     <aside class="rail" aria-label="Estado del expediente">
       <div class="rail__id">NC-RG-2077-0447-K</div>
       <div class="rail__dots" aria-hidden="true"><i class="dot"></i><i class="dot"></i><i class="dot"></i></div>
       <div class="rail__jp" aria-hidden="true">夜の街 · 登録</div>
     </aside>

     <header class="appbar">
       <div class="netdir">NETdir://<b>ncity.registro.pub</b> · REV. 3</div>
       <div class="opline">OPERADOR: <em>…</em> · TERMINAL: <em>…</em> · NIVEL DE ACCESO: <em>0</em></div>
       <div class="appbar__tools"> …botones .term-tgl… </div>
     </header>

     <main class="stage stage--head stage--cmd" id="expediente">
       <section class="screen is-active">…</section>
     </main>

     <footer class="cmdbar">
       <div class="cmdbar__meta">…</div>
       <div class="cmdbar__acts">…</div>
     </footer>
   </body>

   `data-zona`   : "oficial" | "calle"
   `data-acceso` : "0" | "1" | "4"
   `data-medio`  : ausente (vista de terminal) | "papel" (COPIA IMPRESA)
   Las clases .stage--head y .stage--cmd se ponen solo si esa pantalla
   realmente monta cabecera y barra de comandos (el portal, por ejemplo, lleva
   su propia cabecera dentro del lienzo y no las usa).
   ═══════════════════════════════════════════════════════════════════════════ */


/* ═══════════════════════════════════════════════════════════════════════════
   MEDIDA INTERNA DEL CHASIS

   --rail-h NO es un token de diseño (no está en §5 y no significa nada fuera
   del marco): es la altura que ocupa el raíl cuando deja de ser columna y
   pasa a franja horizontal bajo el HUD en móvil (§11.4). Vale 0 en escritorio
   y es lo único que hay que cambiar para que cabecera, barra de comandos y
   lienzo se recoloquen solos. Por eso vive aquí y no en tokens.css.
   ═══════════════════════════════════════════════════════════════════════════ */
:root{ --rail-h: 0px; }


/* ═══════════════════════════════════════════════════════════════════════════
   REJILLA MILIMETRADA · §8.4

   POR QUÉ ES UNA CAPA APARTE Y NO EL FONDO DEL <body>:
   la rejilla necesita una máscara radial que la desvanezca hacia abajo, y
   `mask-image` recorta el elemento CON TODOS SUS DESCENDIENTES. Puesta en el
   body —como aparece literalmente en §8.4— la máscara se come el contenido de
   la mitad inferior de cada pantalla. Es el fallo que el prototipo encontró y
   resolvió: la máscara se aísla en su propia capa vacía detrás del contenido.

   PRESUPUESTO (§8.1): esta capa no cuenta contra el máximo de tres capas
   compuestas, porque no anima, no lleva mix-blend-mode y no se promociona:
   es un fondo estático pintado una vez. Las tres capas compuestas del
   presupuesto siguen siendo scanlines, grano y viñeta, y de ellas solo el
   grano anima.

   En data-zona="calle" los tokens --grid y --grid-major valen `transparent`:
   la rejilla desaparece sin tocar ni una regla de aquí.
   ═══════════════════════════════════════════════════════════════════════════ */
#gridbg{
  position: fixed;
  inset: 0;
  z-index: var(--z-base);
  pointer-events: none;
  background-image:
    linear-gradient(var(--grid) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid) 1px, transparent 1px),
    linear-gradient(var(--grid-major) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid-major) 1px, transparent 1px);
  background-size:
    var(--grid-minor) var(--grid-minor),
    var(--grid-minor) var(--grid-minor),
    var(--grid-major-sz) var(--grid-major-sz),
    var(--grid-major-sz) var(--grid-major-sz);
  -webkit-mask-image: radial-gradient(ellipse 120% 90% at 50% 0%, #000 20%, transparent 78%);
          mask-image: radial-gradient(ellipse 120% 90% at 50% 0%, #000 20%, transparent 78%);
}


/* ═══════════════════════════════════════════════════════════════════════════
   CAPA DE TEXTURA · §8.1–§8.5

   Un único contenedor para las tres capas de efecto, por encima de todo el
   contenido y por debajo de las bandas de clasificación. Inerte al puntero y
   al lector de pantalla.
   Todas las intensidades salen de tokens --fx-*: apagar la textura (COPIA
   IMPRESA, móvil, modo TEXTO PLANO) es cambiar números, nunca reglas.
   ═══════════════════════════════════════════════════════════════════════════ */
#texture{
  position: fixed;
  inset: 0;
  z-index: var(--z-texture);
  pointer-events: none;
}

/* SCANLINES — hueso, no cian. Esto es un tubo monocromo, no una consola de
   ciencia ficción de los 90. El repo antiguo las pintaba a rgba(0,229,255,.005)
   = 1,3 sobre 255: no se veían en ninguna pantalla del mundo (§8.2). */
#texture::before{
  content: '';
  position: fixed;
  inset: 0;
  background-image: repeating-linear-gradient(0deg,
    transparent 0 2px,
    rgba(232,228,220, var(--fx-scan-alpha)) 2px 3px);
  mix-blend-mode: soft-light;
}

/* VIÑETA CRT — sin curvatura. Curvar la página entera es caro y marea; la
   curvatura vive solo dentro del .mugshot y del visor de screamsheet (§8.5). */
#texture::after{
  content: '';
  position: fixed;
  inset: 0;
  background-image: radial-gradient(ellipse at center,
    transparent 52%,
    rgba(5,7,10, var(--fx-vignette)) 100%);
}

/* GRANO — ruido de feTurbulence serializado a data: URI y usado como IMAGEN
   DE FONDO, que se rasteriza una vez. Prohibido `filter: url(#turbulence)`
   sobre elementos vivos: recalcula el filtro cada fotograma (Anexo B.5).
   El inset negativo evita que el desplazamiento del tile descubra los bordes.
   Anima a 8 fps en saltos discretos, no a 60: es una de las cuatro únicas
   animaciones permanentes de toda la aplicación (§7.1). */
#texture .grain{
  position: fixed;
  inset: -64px;
  opacity: var(--fx-grain-alpha);
  mix-blend-mode: overlay;
  background-size: 180px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='128' height='128'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='128' height='128' filter='url(%23n)'/%3E%3C/svg%3E");
  animation: noise-shift 1000ms steps(8) infinite;
  /* La cadencia sale del token y no de un número escrito aquí: el keyframe
     tiene 8 paradas, así que 8 s / fps da la duración de un ciclo completo
     (8 fps → 1000 ms, que es el valor que estaba a mano). Con --fx-noise-fps
     a 0 la división es inválida en tiempo de cómputo, la duración cae a su
     valor inicial (0s) y el grano queda congelado — que es justo lo que el
     token promete en movimiento reducido, en papel y en TEXTO PLANO. */
  animation-duration: calc(8s / var(--fx-noise-fps));
}

@keyframes noise-shift{
  0%    { background-position:   0px    0px }
  12.5% { background-position: -22px   14px }
  25%   { background-position:  18px  -26px }
  37.5% { background-position: -34px   -8px }
  50%   { background-position:  28px   22px }
  62.5% { background-position: -12px   34px }
  75%   { background-position:  36px  -18px }
  87.5% { background-position: -28px  -30px }
  100%  { background-position:   0px    0px }
}

/* En COPIA IMPRESA el grano ya es invisible (--fx-grain-alpha: 0), pero seguir
   repintando ocho veces por segundo un tile de 180px que nadie ve es tirar
   batería. Se para la animación; el mecanismo de apagado sigue siendo el
   token, esto es solo higiene. */
:root[data-medio="papel"] #texture .grain{ animation: none; }


/* ═══════════════════════════════════════════════════════════════════════════
   BANDAS DE CLASIFICACIÓN · §1.5, §2.1

   El color lo pone --classif y cambia solo con el nivel de acceso:
   NIVEL 0 → RESTRINGIDO (rojo profundo) · NIVEL 1 → INTERNO (cian apagado)
   NIVEL 4 → ámbar de Oficial Verificador.
   Ausentes por completo en zona calle.
   ═══════════════════════════════════════════════════════════════════════════ */
.classif{
  position: fixed;
  left: 0;
  right: 0;
  z-index: var(--z-classif);
  height: var(--bar-top);
  display: flex;
  align-items: center;
  overflow: hidden;
  white-space: nowrap;
  pointer-events: none;

  background-color: var(--classif);

  /* Hueso, no --text. La banda es siempre un relleno oscuro y saturado (rojo
     profundo, cian apagado o rojo de imprenta), así que la tinta es SIEMPRE
     en knockout. Con --text, en COPIA IMPRESA la banda pintaría tinta casi
     negra sobre #7A0817 y quedaría ilegible. La única banda clara es la del
     nivel 4, y esa lleva su propia tinta unas líneas más abajo. */
  color: var(--k-bone);
  font-family: var(--ff-ui);
  font-weight: 700;
  font-size: var(--fs-3xs);
  letter-spacing: var(--tr-widest);
  text-transform: uppercase;

  /* 480ms: el mismo tiempo que el viraje de acento del sellado (§10.7), para
     que la banda y el resto de la interfaz cambien de estado como un bloque. */
  transition: background-color var(--d-heavy) var(--ez-term),
              color            var(--d-heavy) var(--ez-term);
}

.classif--top{ top: 0 }
.classif--bot{ bottom: 0; height: var(--bar-bottom) }

.classif__t{
  padding-inline: var(--sp-4);
  opacity: .92;   /* la banda es chrome, no titular: no compite con el contenido */
}

/* Sobre relleno ámbar la tinta obligatoria es --k-amber-ink (§3.4.5). El hueso
   sobre #B8AC00 daría 2.6:1 y la banda del Verificador es exactamente la que
   más se lee de las tres. */
:root[data-acceso="4"] .classif{ color: var(--k-amber-ink) }

/* Zona calle: sin bandas. Los tokens --bar-top / --bar-bottom ya valen 0 ahí,
   así que el lienzo recupera esos 44px sin ninguna regla de layout extra. */
:root[data-zona="calle"] .classif{ display: none }


/* ═══════════════════════════════════════════════════════════════════════════
   RAÍL IZQUIERDO · 56px · §10.4

   La firma estructural del sistema y la pieza de arquitectura de información
   más original que tiene: el mismo carril transporta los pasos del asistente,
   el histórico de sesiones, las anclas de las notas del Verificador y el
   último segmento cifrado con la clave de zona.
   Aquí solo está el chasis: número de expediente en vertical, los tres puntos
   de estado y el ornamento bilingüe. Lo que se pincha dentro es de otro
   fichero.
   ═══════════════════════════════════════════════════════════════════════════ */
.rail{
  position: fixed;
  top: var(--bar-top);
  bottom: var(--bar-bottom);
  left: 0;
  width: var(--rail-w);
  z-index: var(--z-rail);

  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  padding-block: var(--sp-4);

  /* --bg-deep, no --k-void directamente (§3.2: las primitivas no se usan
     nunca en un componente). Resuelven al mismo negro en vista de terminal,
     pero en COPIA IMPRESA el raíl pasa a papel: una columna negra de 56px
     bajando por el margen izquierdo de un documento impreso delata que
     debajo hay una pantalla y rompe la única ficción del modo. */
  background-color: var(--bg-deep);
  border-right: var(--bw-hair) solid var(--line);
  transition: background-color var(--d-heavy) var(--ez-term),
              border-color     var(--d-heavy) var(--ez-term);
}

/* El identificador de expediente es lo único del raíl que NO es decorativo:
   es dato real emitido por el backend con checksum (§12.3), aparece aquí y en
   otros seis sitios del producto, y por eso se lee. */
.rail__id{
  writing-mode: vertical-rl;
  font-family: var(--ff-mono);
  font-size: var(--fs-3xs);
  letter-spacing: var(--tr-widest);
  color: var(--text-3);
  text-transform: uppercase;
}

.rail__dots{
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

/* pulse · §7.2 · 2200ms · desfases 0/800/1600ms.
   Segunda de las cuatro animaciones permanentes permitidas (§7.1). Tres
   puntos, no uno: leídos juntos dan sensación de sistema respirando; leído
   uno solo parece un indicador de carga. */
.dot{
  width: 6px;
  height: 6px;
  background-color: var(--accent);
  animation: pulse 2200ms ease infinite alternate;
}
.dot:nth-child(2){ animation-delay:  800ms; background-color: var(--alert) }
.dot:nth-child(3){ animation-delay: 1600ms; background-color: var(--text-off) }

@keyframes pulse{
  from{ opacity: 1 }
  to  { opacity: .25 }
}

/* Ornamento bilingüe corporativo: siempre --text-off o menor y siempre
   aria-hidden (§4.1). Nunca lleva información. */
.rail__jp{
  writing-mode: vertical-rl;
  font-family: var(--ff-jp);
  font-size: var(--fs-3xs);
  letter-spacing: .2em;
  color: var(--text-off);
}


/* ═══════════════════════════════════════════════════════════════════════════
   CABECERA · 64px · NETdir y línea de operador

   Las dos voces conviven aquí en horizontal: a la izquierda el dominio
   in-world, a la derecha la identidad numerada del operador. Ambas en
   monoespaciada, ambas voz del sistema; la voz de calle entra en el lienzo.
   ═══════════════════════════════════════════════════════════════════════════ */
.appbar{
  position: fixed;
  top: calc(var(--bar-top) + var(--rail-h));
  left: var(--rail-w);
  right: 0;
  height: var(--header-h);
  z-index: var(--z-header);

  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  padding-inline: var(--margin);

  /* nowrap es estructural, no estético: la cabecera es fija y el lienzo
     reserva su hueco con la constante --header-h. Si envolviera, crecería por
     encima de 64px y la primera línea del expediente quedaría debajo. Se
     sacrifica la línea de operador (que hace ellipsis) antes que el layout.
     Por debajo de 768px se resuelve al revés: ahí la cabecera entra en el
     flujo y puede crecer lo que necesite. */
  flex-wrap: nowrap;

  background-color: var(--bg-deep);
  border-bottom: var(--bw-hair) solid var(--line);
  transition: border-color var(--d-heavy) var(--ez-term);
}

.netdir{
  font-family: var(--ff-mono);
  font-size: var(--fs-2xs);
  letter-spacing: var(--tr-wider);
  color: var(--text-3);
  white-space: nowrap;
}
/* El dominio va en --text-2 y NO en acento: a 12px, --k-red sobre --bg-deep
   da 4,32:1, y la tabla de §11.1 es tajante con ese par —«≥14px peso 600,
   nunca microtexto, nunca prosa»—. El NETdir es microtexto. En --text-2 se
   lee a 9,77:1 y sigue destacando sobre el --text-3 del prefijo, que es todo
   lo que la jerarquía necesitaba. */
.netdir b{ color: var(--text-2); font-weight: 500 }

/* El hash de terminal es distinto en cada sesión: el sistema no te reconoce,
   te vuelve a numerar (§12.3). Es uno de los siete sitios de §12.3 y lleva
   dato (OPERADOR / TERMINAL / NIVEL DE ACCESO), así que va en --text-3, el
   suelo legal: --text-off da 2,48:1 aquí y el Anexo B.14.1.3 lo prohíbe para
   texto informativo. Los valores vivos siguen subiendo dentro de <em>. */
.opline{
  font-family: var(--ff-mono);
  font-size: var(--fs-3xs);
  letter-spacing: var(--tr-wider);
  color: var(--text-3);
  text-transform: uppercase;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;   /* sin esto un ítem flex no encoge y el ellipsis no llega a activarse */
}
.opline em{ color: var(--text-2); font-style: normal }

.appbar__tools{
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex: none;
}

/* Interruptores diegéticos de la cabecera: CANAL DE AUDIO (§9.3.2), MODO DE
   RENDERIZADO (§11.3) y COPIA IMPRESA. Mismo objeto físico, tres funciones.
   Se marcan con aria-pressed, no con una clase: el estado tiene que existir
   también para quien no ve el color. */
.term-tgl,
.audio-tgl{   /* alias conservado del prototipo validado */
  appearance: none;
  padding: 6px 10px;
  background-color: transparent;
  border: var(--bw-hair) solid var(--line-strong);
  color: var(--text-3);
  font-family: var(--ff-mono);
  font-size: var(--fs-3xs);
  letter-spacing: var(--tr-wider);
  text-transform: uppercase;
  cursor: pointer;
  transition: color var(--d-fast) var(--ez-term),
              border-color var(--d-fast) var(--ez-term);
}
.term-tgl:hover,
.audio-tgl:hover{ color: var(--text-2); border-color: var(--text-3) }

.term-tgl[aria-pressed="true"],
.audio-tgl[aria-pressed="true"]{ color: var(--alert); border-color: var(--alert) }


/* ═══════════════════════════════════════════════════════════════════════════
   BARRA DE COMANDOS INFERIOR · 52px · §10.4

   A la izquierda el rastro administrativo que acompaña al operador en todas
   las pantallas (expediente · copia · sello); a la derecha las acciones.
   overflow visible a propósito: el botón primario de esta barra mide 54px
   (§6.1) y sobresale 1px por arriba y por abajo. Es deliberado — la acción
   que cierra un trámite rompe la línea de la bandeja.
   ═══════════════════════════════════════════════════════════════════════════ */
.cmdbar{
  position: fixed;
  bottom: var(--bar-bottom);
  left: var(--rail-w);
  right: 0;
  height: var(--command-h);
  z-index: var(--z-command);

  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  padding-inline: var(--margin);
  overflow: visible;

  background-color: var(--surface);
  border-top: var(--bw-hair) solid var(--line-strong);
}

/* EXPEDIENTE · COPIA · sello. Es el rastro administrativo que acompaña al
   operador en todas las pantallas: dato, no ornamento. Va en --text-3 (6,55:1
   sobre --surface); en --text-off daba 2,31:1, que el Anexo B.14.1.3 prohíbe
   explícitamente para texto informativo. */
.cmdbar__meta{
  font-family: var(--ff-mono);
  font-size: var(--fs-3xs);
  letter-spacing: var(--tr-wider);
  color: var(--text-3);
  text-transform: uppercase;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cmdbar__meta b{ color: var(--text-2); font-weight: 500 }

.cmdbar__acts{
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex: none;
}


/* ═══════════════════════════════════════════════════════════════════════════
   LIENZO Y PANTALLAS

   .stage reserva el hueco de todo el chasis con una sola cuenta por eje. Como
   --rail-w, --rail-h, --bar-top y --bar-bottom cambian de valor en calle y en
   móvil, el marco entero se recoloca sin una sola regla de layout adicional.
   ═══════════════════════════════════════════════════════════════════════════ */
.stage{
  position: relative;
  z-index: var(--z-content);
  min-height: 100dvh;
  padding-left: var(--rail-w);
  padding-top: calc(var(--bar-top) + var(--rail-h));
  padding-bottom: var(--bar-bottom);
}

.stage--head{ padding-top: calc(var(--bar-top) + var(--rail-h) + var(--header-h)) }
.stage--cmd { padding-bottom: calc(var(--bar-bottom) + var(--command-h)) }

.screen{ display: none }
.screen.is-active{ display: block; animation: page-in 260ms var(--ez-term) both }
.screen.is-leaving{ display: block; animation: page-out 140ms var(--ez-in) both }

/* page-in · §7.2 · 260ms · la pantalla se REVELA de izquierda a derecha, como
   un cabezal que la imprime. No entra deslizándose ni escalando: eso sería una
   app; esto es una terminal escupiendo un documento.
   clip-path está en la lista blanca de propiedades animables de §8.1. */
@keyframes page-in{
  from{ clip-path: inset(0 100% 0 0) }
  to  { clip-path: inset(0) }
}

/* page-out · §7.2 · 140ms · sube 8px y se apaga. Corto a propósito: la salida
   nunca debe hacer esperar al siguiente trámite. */
@keyframes page-out{
  from{ transform: translateY(0);    opacity: 1 }
  to  { transform: translateY(-8px); opacity: 0 }
}


/* ═══════════════════════════════════════════════════════════════════════════
   CURSOR DE BLOQUE · caret · §7.2

   Tercera animación permanente permitida. Bloque de 8×1.1em, `step-end`: no
   se funde, salta. Un cursor con transición suave es un cursor de editor de
   texto moderno; este es el de una terminal.
   Lo usan el campo en foco (§6.2), el log de arranque y el teclado del OSD.
   ═══════════════════════════════════════════════════════════════════════════ */
.caret{
  display: inline-block;
  width: 8px;
  height: 1.1em;
  vertical-align: text-bottom;
  background-color: var(--accent);
  animation: caret 1000ms step-end infinite;
}

@keyframes caret{
  50%{ opacity: 0 }
}


/* ═══════════════════════════════════════════════════════════════════════════
   CINTA DE DATOS · marquee · §7.2 y §12.5

   Cuarta y última animación permanente. Vive en el chasis y no en el portal
   porque el suceso que la hace especial es global: cuando alguien falla la
   Salvación contra Muerte en el servidor, la cinta vira de ámbar a rojo
   durante 12 s EN TODAS LAS PANTALLAS A LA VEZ.

   El sangrado se implementa como cambio de clase con transición larga, no
   como keyframe: así el color de destino sigue saliendo de los tokens, la
   vuelta al ámbar es automática al quitar la clase, y no hay ningún bucle
   nuevo que pagar. El contenido es estado real del servidor, nunca copy
   inventado.

   El track se duplica en el marcado (dos copias del mismo contenido) para que
   el translateX(-50%) cierre el ciclo sin salto.
   ═══════════════════════════════════════════════════════════════════════════ */
.ribbon{
  position: relative;
  overflow: hidden;
  white-space: nowrap;

  /* Translúcida a propósito: en el portal la cinta cruza por encima del cielo
     y de los blobs, y un relleno opaco la convertiría en una pegatina. El 72%
     sale de --bg-deep en vez de estar escrito a mano, así que en COPIA IMPRESA
     deja de ser una franja negra sobre papel y pasa a ser una cinta de papel. */
  background-color: color-mix(in srgb, var(--bg-deep) 72%, transparent);
  border-block: var(--bw-2) solid var(--alert);
  transition: border-color 1200ms var(--ez-mech);
}

.ribbon__track{
  display: inline-flex;
  gap: var(--sp-8);
  padding-block: 10px;
  font-family: var(--ff-mono);
  font-size: var(--fs-2xs);
  letter-spacing: var(--tr-wider);
  text-transform: uppercase;
  color: var(--alert);
  animation: marquee 30s var(--ez-lin) infinite;
  will-change: transform;
  transition: color 1200ms var(--ez-mech);
}

@keyframes marquee{
  from{ transform: translateX(0) }
  to  { transform: translateX(-50%) }
}

/* Las dos únicas primitivas usadas a pelo en todo el chasis, y es deliberado:
   el sangrado NO sigue al tema. Da igual el nivel de acceso, la zona o el
   medio — cuando alguien muere en el servidor la cinta se pone del rojo
   Arasaka, y del mismo rojo en las pantallas de todos a la vez. Si esto
   resolviera a --accent, en nivel 1 una muerte se anunciaría en cian. */
.ribbon.is-bleeding{ border-color: var(--k-red) }
.ribbon.is-bleeding .ribbon__track{ color: var(--k-red-brt) }

/* Zona calle: la cinta se desalinea 1,2° como el resto de los decorativos
   (§2.1). Regla dura de §2.2: la desalineación es SOLO para decorativos.
   Ningún input, ningún select y ninguna celda de tabla se inclina jamás. */
:root[data-zona="calle"] .ribbon{
  transform: rotate(-1.2deg) scaleX(1.04);
}


/* ═══════════════════════════════════════════════════════════════════════════
   SALTO A CONTENIDO · §11.2

   Primer elemento del documento y diegético: no dice "saltar al contenido",
   dice lo que hace este sistema. Oculto por transform (no por display) para
   que siga siendo enfocable con teclado.
   ═══════════════════════════════════════════════════════════════════════════ */
.skip{
  position: fixed;
  top: var(--bar-top);
  left: var(--rail-w);
  z-index: var(--z-classif);
  padding: var(--sp-3) var(--sp-4);

  background-color: var(--alert);
  color: var(--alert-ink);
  font-family: var(--ff-ui);
  font-weight: 700;
  font-size: var(--fs-2xs);
  letter-spacing: var(--tr-wider);
  text-transform: uppercase;
  text-decoration: none;

  transform: translateY(-200%);
  transition: transform var(--d-fast) var(--ez-snap);
}
.skip:focus{ transform: translateY(0) }


/* ═══════════════════════════════════════════════════════════════════════════
   ≤1023px — BIFURCACIÓN, NO ADAPTACIÓN · §11.4

   A partir de aquí el chasis deja de ser tres columnas. La barra de comandos
   pasa a ser lo único fijo abajo y puede desbordar en horizontal dentro de sí
   misma: es la única excepción a "el body no hace scroll horizontal", y es
   dentro de un contenedor propio, que es justo lo que §6.5 permite.

   EL PUNTO DE CORTE ES 1023 Y NO 1024, y es un arreglo, no una preferencia:
   components.css bifurca en `max-width: 1023px` y pages/wizard.css en
   `min-width: 1024px`. Con 1024px aquí, en un viewport de exactamente 1024px
   el chasis ya estaba en modo móvil (padding reducido, cmdbar con scroll)
   mientras los componentes seguían en escritorio. Un solo píxel de solape es
   un modo entero que nadie diseñó.
   ═══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 1023px){
  .appbar{ padding-inline: var(--sp-4) }
  .cmdbar{
    padding-inline: var(--sp-4);
    gap: var(--sp-3);
    overflow-x: auto;
  }
  .cmdbar__meta{ display: none }   /* el expediente ya está en el raíl */
}


/* ═══════════════════════════════════════════════════════════════════════════
   ≤768px — EL RAÍL SE TUMBA · §11.4

   El raíl de 56px pasa a franja horizontal bajo el HUD con el número de
   expediente y los puntos de estado, rematada por la regla de 4px en acento.
   --rail-w vale ya 0 y --rail-h pasa a 32px: cabecera, barra de comandos y
   lienzo se recolocan solos con las cuentas que ya están escritas arriba.

   La textura de móvil la fija tokens.css (§8.9): solo scanlines a .020. Aquí
   se retiran además los dos pseudos que ya no pintan nada, para no pagar dos
   capas de composición a pantalla completa en una GPU integrada.
   ═══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 768px){
  :root{ --rail-h: 32px }

  .rail{
    top: var(--bar-top);
    bottom: auto;
    right: 0;
    width: auto;
    height: var(--rail-h);
    flex-direction: row;
    justify-content: space-between;
    align-items: center;
    padding-block: 0;
    padding-inline: var(--sp-3);
    border-right: 0;
    border-top: var(--bw-rule) solid var(--accent);
    border-bottom: var(--bw-hair) solid var(--line);
  }
  .rail__id{ writing-mode: horizontal-tb; letter-spacing: var(--tr-wider) }
  .rail__dots{ flex-direction: row }
  .rail__jp{ display: none }

  /* Solo la banda superior (--bar-bottom ya vale 0 en tokens.css). */
  .classif--bot{ display: none }

  #texture::after{ display: none }   /* viñeta */
  #texture .grain{ display: none }   /* grano */

  /* LA CABECERA DEJA DE SER FIJA Y ENTRA EN EL FLUJO.
     Por qué, medido: a 375px la línea de operador y los interruptores no caben
     en una fila, la cabecera envuelve y pasa de 64px a 81,5px, y el lienzo
     —que reservaba su hueco con la constante --header-h— dejaba el contenido
     1,5px POR DEBAJO del borde inferior de la cabecera. Con un interruptor más
     o un handle largo, el solape es de una línea entera.
     Un alto que depende del contenido no se reserva con un número: se le deja
     ocupar su sitio. Además devuelve ~80px de los 812 al expediente, que en
     móvil es un stepper a pantalla completa (§11.4).
     Las bandas y el raíl —la identidad del chasis y el número de expediente—
     siguen fijos: lo que se va con el scroll es el NETdir, que es rótulo. */
  .appbar{
    position: static;
    height: auto;
    margin-top: calc(var(--bar-top) + var(--rail-h));
    padding-block: var(--sp-3);
    flex-wrap: wrap;
    row-gap: var(--sp-2);
  }

  /* LOS TRES INTERRUPTORES BAJAN A SU PROPIA FILA.
     Medido a 375px: `.appbar__tools` es `flex: none; flex-wrap: nowrap` y sus
     tres interruptores (CANAL DE AUDIO, RENDER, COPIA IMPRESA) suman 489px
     dentro de una `.appbar` de 375. Como el <body> lleva `overflow-x: clip`,
     el tercero no se corta: DESAPARECE, y con él el ajuste de §11.3 y el de
     §9.3.2. Envolver la cabecera no bastaba porque el propio contenedor de
     los botones no envolvía. Va aquí, una sola vez y en el chasis, y no
     repetido en el bloque <style> de cada plantilla. */
  .appbar__tools{
    flex: 1 1 100%;
    flex-wrap: wrap;
    row-gap: var(--sp-2);
  }

  .stage{ min-height: auto }          /* la cabecera ya no se superpone al lienzo */
  .stage--head{ padding-top: 0 }      /* y por tanto no hay hueco que reservar */

  .skip{ left: 0 }
}


/* ═══════════════════════════════════════════════════════════════════════════
   MOVIMIENTO REDUCIDO · §11.3

   El bloque global de tokens.css (duración .01ms, una iteración) ya deja
   quieto casi todo el chasis Y en su estado correcto, porque ninguna de estas
   animaciones lleva animation-fill-mode salvo las de pantalla, que terminan
   justamente en su fotograma final: pulse vuelve a opacidad 1, noise-shift a
   la posición 0, caret al bloque visible, page-in a inset(0).

   La única que acaba mal es la marquesina: sin animación, la mitad del
   contenido queda fuera del recorte. §11.3 pide que quede "estática con
   scroll manual", así que se le devuelve el desbordamiento y se hace
   enfocable por teclado para que sea recorrible de verdad, no solo con dedo.

   Es el único sitio del chasis donde se usa !important, y se usa porque tiene
   que ganarle a la propia declaración de .ribbon de más arriba.

   LAS DOS GUARDAS DE `data-render` SE REPITEN AQUÍ. Ver la nota larga de
   tokens.css: A = la preferencia del sistema salvo que el operador pida
   COMPLETO; B = REDUCIDO y TEXTO PLANO lo fuerzan sin media query. Sin la
   segunda, elegir REDUCIDO dejaba la marquesina corriendo.
   ═══════════════════════════════════════════════════════════════════════════ */

/* A · preferencia del sistema, anulable con RENDER: COMPLETO */
@media (prefers-reduced-motion: reduce){
  :root:not([data-render="completo"]) .ribbon{
    overflow-x: auto !important;
    overflow-y: hidden;
  }
  :root:not([data-render="completo"]) .ribbon__track{
    animation: none !important;
    transform: none !important;
    will-change: auto;
  }
}

/* B · RENDER: REDUCIDO y RENDER: TEXTO PLANO, forzado desde la cabecera */
:root[data-render]:not([data-render="completo"]) .ribbon{
  overflow-x: auto !important;
  overflow-y: hidden;
}
:root[data-render]:not([data-render="completo"]) .ribbon__track{
  animation: none !important;
  transform: none !important;
  will-change: auto;
}
