/* Hallmark · genre: editorial · macrostructure: Stat-Led · theme: custom «El Corte»
 * tone: técnico · anchor hue: 90 (amarillo seguridad) sobre neutros teñidos en 248
 * nav: N9 edge-aligned · footer: Ft4 dense colophon · enrichment: Tier B SVG a mano
 * paper-band: dark (L 20.8%) · display-style: display-condensed (roman) · accent-hue: warm (90°)
 * display: Barlow Condensed 900/700/600 · body: Source Serif 4 (opsz) — dos familias, fundidas
 * pre-emit critique: P5 H5 E5 S5 R4 V4
 *
 * contrast: pass (40–41) — 179 elementos de texto auditados contra su fondo
 *   efectivo, con las dos pestañas del cajetín y a 375 y 1280 px: 0 fallos
 * pruebas: 4 761 comprobaciones en pruebas/bateria.js, 0 fallos
 * mobile: pass (34, 49, 50–57) — 0 px de desborde a 320/375/414/768/1280/1440/
 *   1920 px, medido recorriendo la página entera, no sólo arriba
 * honest: pass (46) · chrome: pass (47) · tokens: pass (48) · icons: pass (30)
 * slop: pass (42–45)
 *
 * Motor de scroll — verificado en los tres caminos, no supuesto:
 *   · declarativo (animation-timeline) · respaldo en JS (CSS.supports forzado
 *     a false) · sin JavaScript · y movimiento reducido.
 *   · el acto marca los mismos litros en los mismos puntos con los dos
 *     motores (82 L al 40 %, 208 L al 80 %).
 *   · 51 piezas se mueven y 0 quedan a medias al final del documento.
 *   · sin motor: la sección del corte vuelve a flujo normal (672 px en vez de
 *     2 240) y el dibujo se entrega lleno.
 *
 * Excepciones declaradas, y son dos:
 *   · gate 23 — la franja amarilla a sangre supera el 5 % de su pantalla. Es
 *     deliberada y única: el respiro de la página, previsto en la dirección.
 *     Fuera de ella el acento no pasa de 3 apariciones por pantalla.
 *   · motion.md prohíbe el parallax y el reveal al scroll generalizado. El
 *     cliente los pidió expresamente el 2026-09-11, y el 2026-09-12 pidió
 *     ampliarlos a todas las pantallas tomando como referencia el vocabulario
 *     de scroll-craft (MIT). Se ejecutan sobre el compositor para no cobrarlos
 *     en fluidez. Registrado en Documentacion/direccion-visual.md §6 y §9.
 *
 * Dirección cerrada en Documentacion/direccion-visual.md — se ejecuta, no se renegocia.
 */

/* ========================================================================
   BASE
   ======================================================================== */

*,
*::before,
*::after { box-sizing: border-box; }

html {
  overflow-x: clip;
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  overflow-x: clip;
  margin: 0;
  background: var(--color-grafito);
  color: var(--color-texto);
  font-family: var(--font-body);
  font-size: var(--text-body);
  font-weight: 400;
  font-optical-sizing: auto;
  font-variant-numeric: lining-nums;
  line-height: 1.6;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 900;
  font-style: normal;          /* los titulares son romanos, sin excepción */
  margin: 0;
  text-transform: uppercase;
  text-wrap: balance;
  overflow-wrap: anywhere;
  min-width: 0;
}

p { margin: 0; text-wrap: pretty; }

/* ---- La fusión --------------------------------------------------------
   Dentro de una frase en serif, el dato cambia de voz: pasa a la condensada
   del plano. «Sus 208 litros caen aquí dentro» se lee como argumento y la
   cifra se lee como medida, en la misma línea y sin romperla.

   1.16em no es un número estético: la condensada tiene el ojo medio más bajo
   que la serif, y a igual cuerpo el dato parecería más pequeño que la frase
   que lo contiene. Con este factor las alturas de mayúscula casan.        */
b, strong {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.16em;
  line-height: 1;
  letter-spacing: 0.01em;
  font-variant-numeric: tabular-nums;
}

img { display: block; max-width: 100%; height: auto; }

/* `hidden` tiene que ganar siempre. El del navegador pierde contra cualquier
   `display` de autor, y aquí media docena de controles declaran flex o grid:
   sin esta regla, las pestañas aparecerían sin JavaScript,
   muertos, justo cuando se supone que no están. */
[hidden] { display: none !important; }

a { color: inherit; }

:focus-visible {
  outline: var(--rule-gruesa) solid var(--color-amarillo);
  outline-offset: 2px;
  /* El anillo de foco aparece al instante: nunca se transiciona. */
}

::selection {
  background: var(--color-amarillo);
  color: var(--color-tinta);
}

/* ---- Utilidades tipográficas ---- */

/* El rótulo técnico. Antes era una monoespaciada; ahora es la misma
   condensada de los titulares, en seminegrita, tracked y en versalitas. Una
   familia menos y la misma función: las cotas, las normas y las etiquetas
   hablan la lengua del titular, no una tercera. */
.etiqueta {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--text-micro);
  letter-spacing: var(--tracking-micro);
  text-transform: uppercase;
  line-height: 1.3;
}

.tnum { font-variant-numeric: tabular-nums; }

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--color-amarillo);
  color: var(--color-tinta);
  font-family: var(--font-display);
  font-size: var(--text-sm);
  padding: var(--space-xs) var(--space-md);
  z-index: var(--z-modal);
}
.skip:focus { left: var(--space-sm); top: var(--space-sm); }

/* ---- Carriles ---- */

/* El ritmo lo produce la alternancia de ANCHOS, no la de alineaciones:
   todos los carriles comparten el mismo eje. */
.rail-text { max-width: var(--rail-text); margin-inline: auto; }
.rail-mid  { max-width: var(--rail-mid);  margin-inline: auto; }
.rail-wide { max-width: var(--rail-wide); margin-inline: auto; }

.pad { padding-inline: var(--gutter); }

/* ========================================================================
   N9 · NAV ANCLADA AL BORDE
   El vacío del centro es la decisión. No se rellena con enlaces.
   ======================================================================== */

.nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  padding-block: var(--space-md);
  border-bottom: var(--rule-width) solid var(--rule-claro);
}

.wordmark {
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
  text-decoration: none;
  line-height: 1;
}

.wordmark__mark {
  display: block;
  width: 26px;
  height: 26px;
  flex: none;
}

.wordmark__name {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: var(--text-lg);
  letter-spacing: 0.01em;
  text-transform: uppercase;
  white-space: nowrap;
}

.wordmark__kicker {
  display: block;
  color: var(--color-texto-apoyo);
  margin-top: var(--space-3xs);
}

/* ========================================================================
   BOTONES — ocho estados, etiqueta siempre en una línea
   ======================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);
  min-height: 48px;
  padding: var(--space-xs) var(--space-md);
  border: var(--rule-width) solid transparent;
  border-radius: var(--radius);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--text-md);
  letter-spacing: 0.02em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition:
    background-color var(--dur-micro) var(--ease-out),
    border-color var(--dur-micro) var(--ease-out),
    color var(--dur-micro) var(--ease-out),
    transform var(--dur-micro) var(--ease-out);
}

.btn--wa {
  background: var(--color-amarillo);
  color: var(--color-tinta);
}
.btn--wa:hover,
.btn--wa.is-hover { background: var(--color-amarillo-hondo); }
.btn--wa:active,
.btn--wa.is-active { background: var(--color-amarillo-hondo); transform: translateY(1px); }

.btn--ghost {
  background: transparent;
  color: var(--color-texto);
  border-color: var(--rule-claro);
}
.btn--ghost:hover,
.btn--ghost.is-hover { border-color: var(--color-amarillo); color: var(--color-amarillo); }
.btn--ghost:active { transform: translateY(1px); }

.btn--ink {
  background: var(--color-tinta);
  color: var(--color-papel);
}
.btn--ink:hover { background: var(--color-grafito-alto); }
.btn--ink:active { transform: translateY(1px); }

.btn[aria-disabled="true"],
.btn:disabled {
  opacity: 0.45;
  pointer-events: none;
}

.btn__glyph { flex: none; }

@media (pointer: coarse) {
  .btn { min-height: 52px; }
}

/* ========================================================================
   HERO — qué es, qué resuelve, por qué creerlo, cómo pedirlo
   Antes abría con «220» a 168 px: una cifra sin sujeto. Medido en el
   navegador, la palabra «pallet» no aparecía en nada de lo que se lee
   primero. Ahora el producto va delante y la promesa es lo más grande.
   ======================================================================== */

.hero { padding-block: var(--space-lg) var(--space-3xl); }

.hero__grid {
  display: grid;
  gap: var(--space-lg);
  align-items: center;
}

/* Un solo h1 con dos voces: el producto, en amarillo y a tamaño de rótulo,
   dice QUÉ se vende; la promesa, a tamaño de titular, dice PARA QUÉ. Un
   lector de pantalla y un buscador leen las dos seguidas. */
.hero__titular { display: grid; gap: var(--space-sm); }
.hero__producto {
  font-weight: 700;
  font-size: var(--text-producto);
  line-height: 1.05;
  letter-spacing: 0.01em;
  color: var(--color-amarillo);
}
/* Suelo de 1.0 en display en versalitas: por debajo, las mayúsculas de la
   segunda línea chocan contra la base de la primera al partirse. */
.hero__promesa {
  font-size: var(--text-promesa);
  line-height: 1.0;
  letter-spacing: var(--tracking-display);
  /* Sin tope de ancho: con 14ch la promesa se partía en 3 líneas incluso en
     una columna de 1 000 px y dejaba medio metro vacío hasta la foto. El
     reparto lo decide `text-wrap: balance`. */
}

/* Tres pruebas, no una ficha. Cada una lleva el mismo visto que las normas
   del cajetín: la página usa una sola marca para «esto lo tiene». */
.hero__pruebas {
  list-style: none;
  margin: var(--space-lg) 0 0;
  padding: 0;
  display: grid;
  gap: var(--space-xs);
  font-size: var(--text-md);
  color: var(--color-texto);
}
.hero__pruebas li {
  position: relative;
  padding-inline-start: var(--space-lg);
  line-height: 1.35;
}
.hero__pruebas li::before {
  content: "";
  position: absolute;
  left: 4px;
  top: 0.18em;
  width: 8px;
  height: 15px;
  border-right: 3px solid var(--color-amarillo);
  border-bottom: 3px solid var(--color-amarillo);
  transform: rotate(45deg);
}
/* Filete inferior y no subrayado: el subrayado lo coloca cada tipografía a
   su altura, y con el dato en condensada dentro de la frase en serif salía
   escalonado. El borde sigue la línea, no la letra. */
.hero__pruebas a {
  text-decoration: none;
  border-bottom: var(--rule-width) solid var(--color-cota);
  transition: border-color var(--dur-micro) var(--ease-out), color var(--dur-micro) var(--ease-out);
}
.hero__pruebas a:hover { border-bottom-color: var(--color-amarillo); color: var(--color-amarillo); }

.hero__acciones {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs);
  margin-top: var(--space-lg);
}

/* En móvil la foto va PRIMERO: antes de leer una palabra, se ve qué es. */
.hero__lateral { order: -1; min-width: 0; }

/* La foto sangra por el borde derecho de la ventana. */
.hero__foto {
  margin: 0;
  margin-inline-end: calc(var(--gutter) * -1);
  border-block: var(--rule-width) solid var(--rule-claro);
}
.hero__foto img { width: 100%; }

@media (min-width: 60rem) {
  .hero__grid {
    /* minmax(0, …) y no 7fr/5fr a secas: un track «auto» toma como mínimo el
       ancho intrínseco de la foto (1024 px) y revienta el layout. */
    grid-template-columns: minmax(0, 6fr) minmax(0, 6fr);
    gap: var(--space-2xl);
  }
  /* 6/6 y no 7/5: la foto ES el producto, y con 7/5 quedaban ~300 px vacíos
     entre la promesa y la imagen a 1920. Más foto, menos hueco. */
  .hero__lateral { order: 0; }
  .hero { padding-block: var(--space-xl) var(--space-4xl); }
  /* Junto a un titular de 90 px, las pruebas a 19 px desaparecían. */
  .hero__pruebas { font-size: var(--text-lg); }
}

/* ========================================================================
   T4 · TIRA DE CIFRAS — densa, contra el aire del hero
   ======================================================================== */

.cifras {
  border-block: var(--rule-width) solid var(--rule-claro);
  background: var(--color-grafito-hondo);
}
.cifras__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.cifras__item {
  padding: var(--space-md) var(--space-sm);
  border-inline-end: var(--rule-width) solid var(--rule-claro);
  border-block-end: var(--rule-width) solid var(--rule-claro);
}
.cifras__item:nth-child(2n) { border-inline-end: 0; }
.cifras__item:nth-last-child(-n+2) { border-block-end: 0; }

.cifras__valor {
  display: block;
  font-family: var(--font-display);
  font-weight: 900;
  font-size: var(--text-xl);
  line-height: 1;
  letter-spacing: -0.01em;
}
.cifras__label {
  display: block;
  margin-top: var(--space-2xs);
  color: var(--color-texto-apoyo);
}

@media (min-width: 48rem) {
  .cifras__grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .cifras__item { border-block-end: 0; }
  .cifras__item:nth-child(2n) { border-inline-end: var(--rule-width) solid var(--rule-claro); }
  .cifras__item:last-child { border-inline-end: 0; }
}

/* ========================================================================
   EL CORTE — elemento firma. Un objeto único en mucho vacío.
   ======================================================================== */

.corte {
  background: var(--color-grafito-hondo);
  padding-block: var(--space-3xl);
  border-block-end: var(--rule-width) solid var(--rule-claro);
}

.corte__intro { max-width: 58ch; }

.corte__titulo {
  font-size: var(--text-h2);
  line-height: 1.02;
  letter-spacing: var(--tracking-h2);
}

.corte__texto {
  margin-top: var(--space-md);
  color: var(--color-texto-apoyo);
}

.corte__pista {
  margin-top: 0;
  margin-bottom: var(--space-2xs);
  color: var(--color-cota);
}

/* ---- El acto ------------------------------------------------------------
   Todo lo que sigue sólo vive cuando app.js pone `html.motor`. La intro
   queda siempre en flujo normal, delante del pin: fijarla también dejaría la
   escena móvil sin alto suficiente para el plano y la lectura a la vez.

   El llenado se deriva de --p, no lo escribe el script: así el nivel del
   líquido y la cifra de litros salen del mismo número y no pueden
   desincronizarse. El tramo útil va de 0,14 a 0,80 para que el acto tenga un
   respiro antes de empezar y, sobre todo, unos cientos de píxeles al final
   en los que la cuba ya está llena y hay tiempo de mirarla. Ese silencio del
   final es el acto, tanto como el llenado.                                  */

html.motor #corte { --fill: clamp(0, calc((var(--p) - 0.14) / 0.66), 1); }

/* Los rótulos de nivel llegan cuando el líquido llega a ellos, no antes. Un
   rótulo que espera a su línea delata que el dibujo es un decorado. */
html.motor .sec-marca {
  opacity: clamp(0, calc((var(--p) - var(--en, 0)) / 0.08), 1);
}
.sec-marca--meta { --en: 0.26; }   /* la meta, cuando la cuba va por un cuarto */
.sec-marca--tope { --en: 0.74; }   /* el tope, justo cuando el líquido lo toca */

/* La lectura es el remate, no el acompañamiento: entra cuando la cuba ya casi
   está llena. Primero la cifra, después el margen que sobra, y al final «0
   litros en el piso», que es la frase por la que existe la página. */
html.motor .corte__lectura {
  --casc-base: 0.62; --casc-paso: 0.07; --casc-dur: 0.11;
  /* Recorrido de entrada más corto que el del resto de la página. Dentro de
     un encuadre detenido, 34 px de alzada son un salto; y en una pantalla de
     568 px la última línea llegaba a rozar el borde de la escena mientras
     entraba, así que se habría recortado a media entrada. */
  --alza-entrada: clamp(8px, 1.4vw, 16px);
}

/* El filete de la lectura se DIBUJA, no se funde, y no es un capricho: el
   borde pertenece al contenedor, no a las líneas, así que mientras las tres
   cifras estaban invisibles quedaba una raya horizontal flotando en mitad de
   la escena sin nada debajo durante medio acto. Visto en captura.

   Se traza de izquierda a derecha justo antes de que aterrice la primera
   cifra. Una línea que se traza sola es además lo que hace un plano cuando se
   dibuja: el remedio habla el idioma del resto de la página. */
html.motor .corte__lectura {
  position: relative;
  border-top-color: transparent;
}
html.motor .corte__lectura::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  height: var(--rule-width);
  background: var(--rule-claro);
  transform: scaleX(clamp(0, calc((var(--p) - 0.54) / 0.09), 1));
  transform-origin: 0 50%;
}

.corte__acto { --tramo: 2.2; }
@media (min-width: 60rem) { .corte__acto { --tramo: 2.8; } }

/* ---- El acto en pantallas bajas ----------------------------------------
   Esto se dispara por ALTO, no por ancho, porque el problema es de alto: en
   un teléfono de 568 px la escena dispone de 496 px —el resto se lo lleva la
   barra de cotización— y la escena pedía 574. El centrado recortaba «0
   litros en el piso», que es la frase por la que existe la página.

   Se recorta el espaciado, no el contenido: la lectura sigue diciendo lo
   mismo y el plano se dibuja igual de grande. De los 179 px que ocupaba la
   lectura, 56 eran aire. En una pantalla de este alto, ese aire es un lujo.  */
@media (max-height: 44rem) {
  html.motor .corte__pista { margin-bottom: var(--space-3xs); }
  html.motor .corte__lectura {
    margin-top: var(--space-xs);
    padding-top: var(--space-2xs);
    gap: var(--space-2xs) var(--space-md);
  }
  html.motor .corte__dato { line-height: 1.25; }
  html.motor .corte__dato b { font-size: var(--text-lg); }
  /* Con 14 px de holgura, la propia alzada de entrada se sale por un píxel. */
  html.motor .corte__lectura { --alza-entrada: 6px; }
}

/* El plano es el único elemento de la página autorizado a ser más ancho que
   la ventana: por debajo de cierto tamaño las cotas dejan de leerse, y una
   cota ilegible no es un dibujo técnico, es una textura. */
.corte__lienzo {
  margin-top: 0;
  position: relative;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
}
.corte__lienzo:focus-visible {
  outline: var(--rule-gruesa) solid var(--color-amarillo);
  outline-offset: 4px;
}

.corte__svg { width: 100%; height: auto; }

@media (max-width: 59.99rem) {
  .corte__svg { min-width: 640px; }
  /* A este tamaño los rótulos de 19 px caerían a 4,7 px reales. */
  .sec-cota-txt, .sec-nivel-txt { font-size: 34px; }
}

@media (min-width: 60rem) {
  .corte__pista { display: none; }
}

/* La escena del acto centra su contenido: el plano manda y la lectura y la
   pista se le acercan. Dentro de una ventana fijada los márgenes grandes de
   sección no sirven de nada — no hay travesía que ritmar, hay un encuadre. */
.corte__acto { margin-top: var(--space-2xl); }
html.motor .corte__acto { margin-top: 0; }

/* La lectura numérica que acompaña al llenado. */
.corte__lectura {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-md) var(--space-xl);
  margin-top: var(--space-lg);
  padding-top: var(--space-md);
  border-top: var(--rule-width) solid var(--rule-claro);
}
.corte__dato { display: flex; align-items: baseline; gap: var(--space-2xs); }
.corte__dato b {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: var(--text-xl);
  line-height: 1;
  letter-spacing: -0.01em;
}
.corte__dato--contenido b { color: var(--color-amarillo); }
.corte__dato--margen b    { color: var(--color-cota); }
.corte__dato span { color: var(--color-texto-apoyo); }

/* Piezas del dibujo.
   El amarillo es el EQUIPO — el pallet es amarillo de verdad.
   El óxido es el PRODUCTO derramado. Si ambos fueran del mismo color,
   la mitad inferior del corte sería una sola mancha y no se leería nada. */
.sec-acero   { fill: var(--color-amarillo); }
/* El tambor es acero desnudo: si fuera amarillo se confundiría con el pallet. */
.sec-acero-tambor { fill: var(--color-texto-apoyo); }
.sec-hueco   { fill: var(--color-grafito); }
.sec-rejilla { fill: var(--color-cota); }
.sec-tambor  { fill: none; stroke: var(--color-texto-apoyo); stroke-width: 2; }
.sec-liquido { fill: var(--color-oxido); }
.sec-rotura  { fill: none; stroke: var(--color-texto-apoyo); stroke-width: 2; }
.sec-cota    { stroke: var(--color-cota); stroke-width: 1.5; fill: none; }
.sec-cota-txt {
  fill: var(--color-cota);
  font-family: var(--font-display);
  font-size: 22px;           /* la condensada es más estrecha que la mono: sube de cuerpo sin ocupar más */
  font-weight: 600;
  letter-spacing: 1.6px;
}
.sec-nivel     { stroke: var(--color-texto); stroke-width: 1.5; stroke-dasharray: 7 5; }
.sec-nivel-txt { fill: var(--color-texto); font-family: var(--font-display); font-size: 24px; font-weight: 700; letter-spacing: 0.5px; }
.sec-margen    { fill: var(--color-cota); opacity: 0.22; }
.sec-hatch     { stroke: var(--color-amarillo-hondo); stroke-width: 1; opacity: 0.5; }
.sec-aire      { stroke: var(--color-cota); stroke-width: 1.4; }

/* El llenado: sólo transform. El origen es el fondo de la cuba (y=585 en el
   dibujo); el vaciado del tambor tiene el suyo en su propia base (y=344).

   Por defecto el corte va LLENO. Sin JavaScript, la página cuenta la historia
   correcta —la cuba contiene los 208 L— en lugar de quedarse vacía marcando
   cero. Es el script el que lo vacía al arrancar para poder llenarlo.

   Sin transición a propósito: el nivel lo gobierna el scroll fotograma a
   fotograma. Una transición encima lo dejaría por detrás del dedo. */
.sec-llenado {
  transform: scaleY(var(--fill, 1));
  transform-origin: 0 585px;
  transform-box: view-box;
}
.sec-vaciado {
  transform: scaleY(calc(1 - var(--fill, 1)));
  transform-origin: 0 344px;
  transform-box: view-box;
}

/* ========================================================================
   TRES DETALLES — pesos desiguales a propósito. No es una rejilla de tres iguales.
   ======================================================================== */

.detalles { padding-block: var(--space-3xl); }

.detalles__titulo {
  font-size: var(--text-h2);
  line-height: 1.02;
  letter-spacing: var(--tracking-h2);
  max-width: 20ch;
}

.detalles__grid {
  display: grid;
  gap: var(--space-xl);
  margin-top: var(--space-2xl);
}

/* Las imágenes nunca se muestran por encima de su tamaño real: las fotos de
   detalle son pequeñas de origen, así que van como recuadros, no como bandas. */
.detalle__foto,
.detalle__inset {
  margin: 0;
  border: var(--rule-width) solid var(--rule-claro);
  background: var(--color-grafito-hondo);
  overflow: hidden;
}
.detalle__foto img,
.detalle__inset img { width: 100%; }

.detalle__foto img { aspect-ratio: 5 / 2; object-fit: cover; }
.detalle__inset { width: 150px; flex: none; }
.detalle__inset img { aspect-ratio: 3 / 2; object-fit: cover; }

.detalle { display: grid; gap: var(--space-md); }
.detalle--menor { grid-template-columns: 150px minmax(0, 1fr); align-items: start; }

.detalle__nombre {
  font-size: var(--text-xl);
  line-height: 1.05;
  letter-spacing: -0.01em;
}
.detalle__texto {
  margin-top: var(--space-2xs);
  color: var(--color-texto-apoyo);
  max-width: 46ch;
}
.detalle__etiqueta { margin-top: var(--space-xs); color: var(--color-cota); }

@media (min-width: 60rem) {
  .detalles__grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--space-2xl);
  }
  .detalle--mayor {
    grid-column: 1 / -1;
    grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
    column-gap: var(--space-2xl);
    align-items: end;
  }
}

/* ========================================================================
   FICHA TÉCNICA — campo claro. El papel de plano rompe el monocromo oscuro.
   ======================================================================== */

/* Sin cuadrícula de fondo: competía con los filetes de la tabla y no era una
   referencia dimensional real, sólo textura. El campo claro ya rompe el
   monocromo por sí solo. */
.ficha {
  background: var(--color-papel);
  color: var(--color-tinta);
  padding-block: var(--space-3xl);
}

.ficha__cabecera {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-sm);
  padding-bottom: var(--space-md);
  border-bottom: var(--rule-gruesa) solid var(--color-tinta);
}
.ficha__titulo {
  font-size: var(--text-h2);
  line-height: 1;
  letter-spacing: var(--tracking-h2);
}
.ficha__sello { color: var(--color-cota-hondo); }

.tabla {
  width: 100%;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
}
.tabla th,
.tabla td {
  text-align: start;
  vertical-align: baseline;
  padding: var(--space-sm) var(--space-sm) var(--space-sm) 0;
  border-bottom: var(--rule-width) solid var(--rule-oscuro);
}
.tabla th {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--text-micro);
  letter-spacing: var(--tracking-micro);
  text-transform: uppercase;
  color: var(--color-tinta-apoyo);
  width: 34%;
}
/* La ficha es donde la fusión se ve entera: el VALOR es medida y va en
   condensada; la NOTA que lo explica es argumento y va en serif. */
.tabla td {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--text-lg);
  line-height: 1.2;
  letter-spacing: 0.01em;
}
.tabla .nota {
  display: block;
  margin-top: var(--space-3xs);
  font-family: var(--font-body);
  font-weight: 400;
  font-size: var(--text-sm);
  line-height: 1.45;
  letter-spacing: 0;
  color: var(--color-tinta-apoyo);
}
.tabla tr:last-child th,
.tabla tr:last-child td { border-bottom: 0; }

.ficha__pie {
  margin-top: var(--space-lg);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-md);
}
.ficha__nota { color: var(--color-tinta-apoyo); max-width: 52ch; }

/* En móvil la tabla deja de ser tabla: cada fila es un bloque etiquetado. */
@media (max-width: 47.99rem) {
  .tabla, .tabla tbody, .tabla tr, .tabla th, .tabla td { display: block; width: auto; }
  .tabla tr {
    padding-block: var(--space-sm);
    border-bottom: var(--rule-width) solid var(--rule-oscuro);
  }
  .tabla th { border: 0; padding: 0 0 var(--space-3xs); }
  .tabla td { border: 0; padding: 0; }
  .tabla tr:last-child { border-bottom: 0; }
}

/* ========================================================================
   DÓNDE SE INSTALA — lista tipográfica sobre papel. Sin tarjetas, sin iconos.
   ======================================================================== */

.instala {
  background: var(--color-papel-alto);
  color: var(--color-tinta);
  padding-block: var(--space-3xl);
  border-top: var(--rule-width) solid var(--rule-oscuro);
}

.instala__titulo {
  font-size: var(--text-h2);
  line-height: 1.02;
  letter-spacing: var(--tracking-h2);
  max-width: 18ch;
}

.instala__lista {
  list-style: none;
  margin: var(--space-xl) 0 0;
  padding: 0;
  max-width: 62ch;
}
.instala__lista li {
  padding-block: var(--space-sm);
  border-top: var(--rule-width) solid var(--rule-oscuro);
  font-size: var(--text-md);
}
.instala__lista li:last-child { border-bottom: var(--rule-width) solid var(--rule-oscuro); }

/* ========================================================================
   FRANJA — el respiro. Una sola frase a sangre completa.
   ======================================================================== */

.franja {
  background: var(--color-amarillo);
  color: var(--color-tinta);
  padding-block: var(--space-3xl);
}
.franja__frase {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: var(--text-h2);
  line-height: 1.0;
  letter-spacing: var(--tracking-h2);
  text-transform: uppercase;
  text-wrap: balance;
  max-width: 24ch;
}
.franja__pie {
  margin-top: var(--space-lg);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-md);
}
.franja__nota { max-width: 48ch; }

/* ========================================================================
   F4 · CÓMO SE PIDE — la única numeración de la página, porque sí es secuencia
   ======================================================================== */

.pedido { padding-block: var(--space-3xl); }

.pedido__titulo {
  font-size: var(--text-h2);
  line-height: 1.02;
  letter-spacing: var(--tracking-h2);
}

.pasos {
  list-style: none;
  margin: var(--space-2xl) 0 0;
  padding: 0;
  display: grid;
  gap: 0;
}
.pasos li {
  display: grid;
  gap: var(--space-2xs);
  padding-block: var(--space-lg);
  border-top: var(--rule-width) solid var(--rule-claro);
}
.pasos li:last-child { border-bottom: var(--rule-width) solid var(--rule-claro); }

/* El número va DIRECTAMENTE encima del titular, nunca en una columna a su
   izquierda: la cabecera colgante es el tic editorial más reconocible. */
.paso__marca { display: block; }
.paso__num { display: block; color: var(--color-amarillo); }
.paso__nombre {
  margin-top: var(--space-3xs);
  font-size: var(--text-xl);
  line-height: 1.05;
  letter-spacing: -0.01em;
}
.paso__texto { color: var(--color-texto-apoyo); max-width: 54ch; }

@media (min-width: 60rem) {
  .pasos li {
    grid-template-columns: minmax(0, 20rem) minmax(0, 1fr);
    gap: var(--space-lg);
    align-items: baseline;
  }
}

/* ========================================================================
   PREGUNTAS — acordeón nativo. Sin animación de altura: el marcador gira y ya.
   ======================================================================== */

.faq {
  padding-block: var(--space-3xl);
  border-top: var(--rule-width) solid var(--rule-claro);
  background: var(--color-grafito-hondo);
}

/* Carril propio: más angosto que rail-mid, porque son preguntas que se leen
   seguidas y la medida corta ayuda. */
.faq__caja {
  max-width: 72ch;
  margin-inline: auto;
}

.faq__titulo {
  font-size: var(--text-h2);
  line-height: 1.02;
  letter-spacing: var(--tracking-h2);
}

.faq__lista { margin-top: var(--space-xl); }

.qa { border-top: var(--rule-width) solid var(--rule-claro); }
.qa:last-child { border-bottom: var(--rule-width) solid var(--rule-claro); }

.qa__q {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-md);
  padding-block: var(--space-md);
  cursor: pointer;
  list-style: none;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--text-lg);
  line-height: 1.15;
  text-transform: uppercase;
  letter-spacing: 0.005em;
  transition: color var(--dur-micro) var(--ease-out);
}
.qa__q::-webkit-details-marker { display: none; }
.qa__q:hover { color: var(--color-amarillo); }
.qa__q:active { color: var(--color-amarillo-hondo); }

.qa__signo {
  flex: none;
  width: 14px;
  height: 14px;
  position: relative;
  align-self: center;
}
.qa__signo::before,
.qa__signo::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  background: currentColor;
}
.qa__signo::before { width: 14px; height: 2px; }
.qa__signo::after  { width: 2px; height: 14px; transition: transform var(--dur-short) var(--ease-in-out); }
.qa[open] .qa__signo::after { transform: scaleY(0); }

.qa__a {
  padding-bottom: var(--space-md);
  color: var(--color-texto-apoyo);
  max-width: 64ch;
}
.qa__a b { color: var(--color-texto); font-weight: 600; }

/* ========================================================================
   EL CAJETÍN — respaldo del fabricante
   Todo plano termina en su cajetín. Va sobre papel porque es el documento
   firmado de la página: después del último argumento en oscuro, el papel
   dice «esto ya no es venta, es lo que consta por escrito».
   ======================================================================== */

.cajetin {
  background: var(--color-papel);
  color: var(--color-tinta);
  padding-block: var(--space-3xl);
  border-top: var(--rule-gruesa) solid var(--color-tinta);
}

.cajetin__cabecera { max-width: 62ch; }
.cajetin__kicker { color: var(--color-cota-hondo); }
.cajetin__titulo {
  margin-top: var(--space-2xs);
  font-size: var(--text-h2);
  line-height: 1.02;
  letter-spacing: var(--tracking-h2);
}
.cajetin__lede {
  margin-top: var(--space-md);
  font-size: var(--text-md);
  color: var(--color-tinta-apoyo);
}

/* El marco es el cajetín propiamente dicho: un filete grueso por fuera y
   filetes finos entre campos, como en un plano. */
.cajetin__marco {
  display: grid;
  margin-top: var(--space-xl);
  border: var(--rule-gruesa) solid var(--color-tinta);
  background: var(--color-papel-alto);
}
.cajetin__docs { min-width: 0; }

/* En escritorio la solicitud va PEGADA y compacta, no estirada. Estirada al
   alto de las normas dejaba ~500 px de papel vacío entre el botón y el sello
   del fabricante: visto en captura. Pegada, acompaña la lectura — se marca
   una norma y el contador cambia en el mismo vistazo. El filete que separa
   las columnas pasa a la columna de documentos, que es la que mide todo. */
@media (min-width: 60rem) {
  .cajetin__marco { grid-template-columns: minmax(0, 8fr) minmax(0, 4fr); align-items: start; }
  .cajetin__docs { border-inline-end: var(--rule-width) solid var(--rule-oscuro); }
}

/* ---- Pestañas ---- */

.pestanas {
  position: relative;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  border-bottom: var(--rule-width) solid var(--rule-oscuro);
}
.pestana {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "num num" "nombre cuenta";
  align-items: baseline;
  gap: var(--space-3xs) var(--space-2xs);
  min-height: 56px;
  padding: var(--space-sm) var(--space-md);
  border: 0;
  background: transparent;
  color: var(--color-tinta-apoyo);
  font: inherit;
  text-align: start;
  cursor: pointer;
  transition: color var(--dur-micro) var(--ease-out), background-color var(--dur-micro) var(--ease-out);
}
.pestana + .pestana { border-inline-start: var(--rule-width) solid var(--rule-oscuro); }
.pestana:hover { color: var(--color-tinta); background: var(--color-papel); }
.pestana[aria-selected="true"] { color: var(--color-tinta); }
.pestana:focus-visible { outline-offset: -3px; outline-color: var(--color-tinta); }
.pestana__num    { grid-area: num; color: var(--color-cota-hondo); white-space: nowrap; }
.pestana__nombre { grid-area: nombre; }
.pestana__cuenta { grid-area: cuenta; }
/* A 390 px «Doc. 01 · Normas · 5» en una fila partía el «Doc. 01» en dos
   renglones. Por debajo de tablet el número sube encima del nombre. */
@media (min-width: 48rem) {
  .pestana { grid-template-columns: auto minmax(0, 1fr) auto; grid-template-areas: "num nombre cuenta"; }
}
.pestana__nombre {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--text-lg);
  line-height: 1.1;
  text-transform: uppercase;
  white-space: nowrap;
}
.pestana__cuenta { font-family: var(--font-display); font-weight: 600; }

/* La guía bajo la pestaña activa es una cota: filete grueso con sus dos
   remates. Se desliza con transform, no con left, para no tocar el layout. */
.pestanas__guia {
  position: absolute;
  inset-block-end: -2px;
  inset-inline-start: 0;
  width: 50%;
  height: var(--rule-gruesa);
  background: var(--color-tinta);
  transform: translateX(calc(var(--pestana, 0) * 100%));
  transition: transform var(--dur-short) var(--ease-out);
}
.pestanas__guia::before,
.pestanas__guia::after {
  content: "";
  position: absolute;
  bottom: 0;
  width: var(--rule-gruesa);
  height: 11px;
  background: var(--color-tinta);
}
.pestanas__guia::before { left: 0; }
.pestanas__guia::after  { right: 0; }

/* ---- Paneles ---- */

.panel { padding: var(--space-md); }
.panel:focus-visible { outline-offset: -3px; outline-color: var(--color-tinta); }
/* Sin JavaScript los dos documentos van seguidos: el segundo lleva su
   propio filete para que no se lean como uno solo. */
.panel + .panel { border-top: var(--rule-width) solid var(--rule-oscuro); }
.cajetin--vivo .panel + .panel { border-top: 0; }

.panel__cabeza {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-sm);
  padding-bottom: var(--space-sm);
}
.panel__titulo {
  font-size: var(--text-xl);
  line-height: 1.05;
  letter-spacing: -0.005em;
  max-width: 30ch;
}


/* ---- La marca de sello ---------------------------------------------------
   Cada norma y cada característica lleva un visto YA PUESTO, sobre fondo de
   tinta: no es una casilla que el cliente pueda marcar, es un sello. Por eso
   no tiene borde vacío ni estado de hover — nada invita a pulsarlo.        */

.sello-marca {
  position: relative;
  flex: none;
  width: 24px;
  height: 24px;
  margin-top: 4px;
  background: var(--color-tinta);
}
.sello-marca::after {
  content: "";
  position: absolute;
  left: 8px;
  top: 3px;
  width: 7px;
  height: 13px;
  border-right: 3px solid var(--color-papel);
  border-bottom: 3px solid var(--color-papel);
  transform: rotate(45deg);
}

/* ---- Normas ---- */

.normas { list-style: none; margin: 0; padding: 0; }
.norma {
  display: grid;
  grid-template-columns: 24px minmax(0, 1fr);
  gap: var(--space-sm);
  align-items: start;
  padding-block: var(--space-sm);
  border-top: var(--rule-width) solid var(--rule-oscuro);
}
.norma__cuerpo { display: grid; gap: var(--space-3xs); min-width: 0; }
/* El código de la norma es el dato de la fila: va en condensada y grande.
   Lo que la norma regula es argumento: va en serif. La fusión otra vez. */
.norma__codigo {
  font-family: var(--font-display);
  font-weight: 700;
  /* Escala con la ventana y no con --text-xl: a 320 px «OSHA 29 CFR 1910.106»
     a 24 px no cabía en su columna y se partía. Un código normativo se lee
     entero o no se lee. Desde ~480 px vuelve al tamaño de siempre. */
  font-size: clamp(1.2rem, 0.85rem + 1.7vw, 1.9rem);
  line-height: 1;
  letter-spacing: 0.005em;
  overflow-wrap: anywhere;
}
/* «Cumple» va pegado al código, en negativo sobre tinta: es la marca de
   inspección del plano, no una etiqueta de estado que pueda cambiar. */
.norma__cumple {
  display: inline-block;
  margin-inline-start: var(--space-2xs);
  padding: 0.15em 0.5em;
  vertical-align: 0.3em;
  background: var(--color-tinta);
  color: var(--color-papel);
  letter-spacing: 0.1em;
  line-height: 1.2;
}
.norma__org  { color: var(--color-cota-hondo); }
.norma__tema { color: var(--color-tinta); max-width: 60ch; }
.norma__aqui { color: var(--color-tinta-apoyo); }

/* ---- El acero ---- */

.acero {
  display: grid;
  gap: var(--space-md) var(--space-xl);
  border-top: var(--rule-width) solid var(--rule-oscuro);
  padding-top: var(--space-md);
}
@media (min-width: 48rem) { .acero { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* Titular de grupo con la familia explícita: la regla general de titulares
   sólo alcanza a h1–h4 y aquí conviene no depender de la etiqueta. */
.declarado__titulo {
  font-family: var(--font-display);
  font-weight: 900;
  text-transform: uppercase;
  display: flex;
  justify-content: space-between;
  gap: var(--space-sm);
  font-size: var(--text-lg);
  line-height: 1.1;
  letter-spacing: 0;
  padding-bottom: var(--space-2xs);
  border-bottom: var(--rule-gruesa) solid var(--color-tinta);
}
.declarado__lista { list-style: none; margin: 0; padding: 0; }
.declarado__item {
  position: relative;
  padding: var(--space-2xs) 0 var(--space-2xs) var(--space-lg);
  border-bottom: var(--rule-width) solid var(--rule-oscuro);
}
/* El mismo visto que el sello, en pequeño y sin fondo: se lee como la misma
   marca a otra escala. */
.declarado__item::before {
  content: "";
  position: absolute;
  left: 6px;
  top: 0.62em;
  width: 6px;
  height: 11px;
  border-right: 2.5px solid var(--color-tinta);
  border-bottom: 2.5px solid var(--color-tinta);
  transform: rotate(45deg);
}

/* ---- El respaldo, en cifras -----------------------------------------------
   Donde antes el cliente armaba una solicitud, ahora lee el recuento: cuántas
   normas, de dónde, cuántas características. Cifras en condensada, rótulos
   en versalita: es la tira de cifras del principio de la página, otra vez,
   aplicada al respaldo.                                                     */

.resumen {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  padding: var(--space-md);
  border-top: var(--rule-width) solid var(--rule-oscuro);
  background: var(--color-papel);
  min-width: 0;
}
@media (min-width: 60rem) {
  /* Pegado y compacto: acompaña la lectura de las normas. Estirado al alto
     del panel dejaba un vacío de medio metro de papel. */
  .resumen { position: sticky; top: var(--space-md); border-top: 0; background: transparent; }
}
.resumen__kicker { color: var(--color-cota-hondo); }

.resumen__cifras {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  margin: 0;
  border-top: var(--rule-gruesa) solid var(--color-tinta);
}
.resumen__cifra {
  display: flex;
  flex-direction: column-reverse;   /* la cifra manda; el rótulo la explica debajo */
  justify-content: flex-end;
  gap: var(--space-3xs);
  padding: var(--space-sm) var(--space-sm) var(--space-sm) 0;
  border-bottom: var(--rule-width) solid var(--rule-oscuro);
}
.resumen__cifra + .resumen__cifra:not(.resumen__cifra--ancha):nth-child(3) {
  padding-inline-start: var(--space-sm);
  border-inline-start: var(--rule-width) solid var(--rule-oscuro);
}
.resumen__cifra--mayor,
.resumen__cifra--ancha { grid-column: 1 / -1; }
.resumen__cifra dt { color: var(--color-tinta-apoyo); }
.resumen__cifra dd {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 900;
  font-size: var(--text-h2);
  line-height: 0.9;
}
.resumen__cifra--mayor dd { font-size: var(--text-display); line-height: 0.8; }

.resumen__cta { align-self: stretch; }

.sello { margin: 0; }
.sello__campo {
  display: grid;
  gap: var(--space-3xs);
  padding-block: var(--space-2xs);
  border-top: var(--rule-width) solid var(--rule-oscuro);
}
.sello dt { color: var(--color-cota-hondo); }
.sello dd { margin: 0; font-size: var(--text-sm); line-height: 1.4; }

.cajetin__aviso {
  margin-top: var(--space-lg);
  max-width: 72ch;
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--color-tinta-apoyo);
}
.cajetin__aviso-rotulo {
  display: inline-block;
  margin-inline-end: var(--space-2xs);
  padding: 0 var(--space-2xs);
  background: var(--color-tinta);
  color: var(--color-papel);
}

.normas  { --casc-paso: 0.16; --casc-dur: 0.26; }
.acero   { --casc-paso: 0.20; --casc-dur: 0.30; }


/* ========================================================================
   Ft4 · COLOFÓN DENSO
   ======================================================================== */

/* El colofón NO repite la ficha técnica: eso ya está en la tabla, y repetirlo
   convertía el pie en once líneas de monoespaciada del mismo tamaño — un muro,
   no una lectura. Aquí van tres registros distintos (display para la marca,
   texto para la frase, mono para los datos) y sólo lo que cierra la página. */
.colofon {
  background: var(--color-grafito-hondo);
  border-top: var(--rule-width) solid var(--rule-claro);
  padding-block: var(--space-2xl) var(--space-lg);
  color: var(--color-texto-apoyo);
}

.colofon__marca {
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
}

.colofon__nota {
  margin-top: var(--space-md);
  max-width: 58ch;           /* la frase respira; no es un párrafo de specs */
  color: var(--color-texto-apoyo);
}

.colofon__pie {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-xs) var(--space-xl);
  margin-top: var(--space-xl);
  padding-top: var(--space-md);
  border-top: var(--rule-width) solid var(--rule-claro);
}

.colofon__tel,
.colofon__vinculo {
  text-decoration: none;
  white-space: nowrap;
  border-bottom: 1px solid transparent;
  transition: color var(--dur-micro) var(--ease-out),
              border-color var(--dur-micro) var(--ease-out);
}
.colofon__tel { color: var(--color-amarillo); }
.colofon__tel:hover,
.colofon__vinculo:hover { color: var(--color-amarillo); border-bottom-color: currentColor; }
.colofon__tel:active,
.colofon__vinculo:active { color: var(--color-amarillo-hondo); }

/* ========================================================================
   C4 · BARRA INFERIOR FIJA — sólo donde hace falta: móvil
   ======================================================================== */

.barra {
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  z-index: var(--z-sticky);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  padding: var(--space-2xs) var(--space-sm);
  padding-bottom: max(var(--space-2xs), env(safe-area-inset-bottom));
  background: var(--color-grafito-alto);
  border-top: var(--rule-width) solid var(--rule-claro);
}
.barra__texto {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--text-micro);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--color-texto-apoyo);
  line-height: 1.25;
}
.barra .btn { padding-inline: var(--space-sm); font-size: var(--text-body); }

@media (min-width: 60rem) {
  .barra { display: none; }
}
@media (max-width: 59.99rem) {
  body { padding-bottom: 4.5rem; }
  /* En móvil el CTA vive en la barra fija, que está siempre a la vista.
     Repetirlo en la cabecera es pedir dos veces lo mismo en la misma pantalla. */
  .nav > .btn { display: none; }
}

@media (max-width: 30rem) {
  /* El descriptor parte en dos líneas y ensucia la CABECERA. En el pie hay
     sitio de sobra, así que allí se queda. */
  .nav .wordmark__kicker { display: none; }
}

/* ========================================================================
   MOTOR DE SCROLL
   ------------------------------------------------------------------------
   Una sola variable de progreso, --p (0 → 1), alimenta todas las fórmulas.
   Quién la mueve depende del navegador:

     · Chrome/Edge/Firefox → animation-timeline: view(), que corre FUERA del
       hilo principal. Es la razón de que no haya tirones al hacer scroll.
     · Safari antiguo y demás → el motor de respaldo en app.js escribe --p.

   Las fórmulas son las mismas para los dos motores, así que no hay dos
   implementaciones que se desincronicen.

   --motor es el interruptor: vale 0 mientras no haya nadie alimentando --p,
   y con 0 todas las fórmulas se anulan solas. Sin JavaScript y sin soporte,
   la página se queda exactamente como estaba: quieta y correcta.

   La profundidad no es decorativa: el sujeto es un objeto por capas —rejilla
   sobre cuba sobre base— y el scroll las separa.
   ======================================================================== */

@property --p {
  syntax: "<number>";
  inherits: true;
  initial-value: 0.5;
}

[data-mv] { --motor: 0; }

/* Los rangos viven DENTRO de este bloque y con el mismo prefijo de selector.
   Dos motivos, ambos comprobados en el navegador y no supuestos:

     · el atajo `animation` resetea `animation-range` a `normal`, así que una
       regla de rango suelta con menos especificidad se pierde y el elemento
       se queda a media opacidad en mitad de la pantalla;
     · `animation-range` no acepta calc() con variables — descarta la
       declaración entera. Por eso el escalonado va escrito posición a
       posición en vez de calcularse.

   Un tramo de `cover` y no `entry`: el rango `entry` dura lo que mide el
   propio elemento, así que en una fila de 60 px el reveal se consume en 35 px
   de scroll y se percibe como un parpadeo. Sobre `cover` ocupa ~230 px.      */

@supports (animation-timeline: view()) {
  html:not(.mv-js) [data-mv] {
    --motor: 1;
    animation: avance-mv linear both;
    animation-timeline: view();
    animation-range: cover;                /* toda la travesía por la ventana */
  }

  /* `entry` y no `cover`, y esto importa: `cover` sólo termina cuando el
     elemento sale por arriba de la ventana, cosa que el último bloque de la
     página NO puede hacer — se quedaría a media opacidad para siempre. Con
     `entry`, la revelación acaba cuando el elemento entra del todo, que sí
     ocurre en cualquier posición del documento, pie incluido. */
  html:not(.mv-js) [data-mv="sube"] { animation-range: entry 0% entry 78%; }

  /* La cortina se retira mientras la franja ENTRA y termina cuando acaba de
     entrar: la banda crece desde abajo mientras el amarillo la barre, y para
     cuando está entera en pantalla ya está entera pintada. Con `cover`
     seguiría medio tapada al quedar centrada, que es justo cuando no puede
     estarlo. */
  html:not(.mv-js) [data-mv="cortina"] { animation-range: entry 10% entry 100%; }

  /* El reloj de la cascada: de «el bloque asoma por abajo» a «su borde
     superior toca el techo». Ese tramo mide SIEMPRE una ventana de alto, mida
     lo que mida el bloque, y ahí está todo el asunto.

     Con `entry` a secas el recorrido lo fija el contenedor, y eso rompe los
     bloques bajos: la tira de cifras mide 102 px, así que su cascada entera
     se consumía en unos 100 px de scroll —un golpe de rueda se la saltaba— y
     además arrancaba ya revelada a medias, porque al cargar la página su
     borde superior queda 7 px por encima del pliegue. Medido, no supuesto.

     Con una ventana de reloj, una tira de 102 px y una ficha de 750 px
     reparten sus hijos sobre la misma distancia y entran al mismo ritmo.    */
  html:not(.mv-js) [data-mv="cascada"] { animation-range: entry 0% exit 0%; }

  /* Salvo los dos últimos bloques, que no pueden llegar a `exit`: para que su
     borde superior toque el techo haría falta una ventana de contenido por
     debajo, y debajo del pie no hay nada. Ahí vuelve `entry`, que es el único
     rango que termina en cualquier posición del documento. El precio es una
     entrada más corta, y en el cierre es justo lo que se quiere: resolver, no
     hacer un número. */
  html:not(.mv-js) [data-mv="cascada-fin"] { animation-range: entry 0% entry 92%; }

  /* La retirada ocurre cuando el hero SE VA. `exit` empieza cuando su borde
     superior toca el techo de la ventana y termina cuando lo toca el
     inferior: exactamente el tramo en que abandona la pantalla. */
  html:not(.mv-js) [data-mv="retirada"] { animation-range: exit 0% exit 100%; }

  /* Ya no hay escalonado por curvas. Repartir hermanos entre sí lo hace ahora
     la cascada, que cuelga de UN solo recorrido en vez de dar a cada elemento
     su propia línea de tiempo: es más barato, es visible en filas pequeñas y
     no depende de tener una curva escrita para cada posición. `sube` queda
     para los elementos que van solos —los titulares de sección. */
}
html.mv-js [data-mv] { --motor: 1; }

/* Instante de medición del motor de respaldo: con los transforms activos, cada
   elemento se leería a sí mismo ya desplazado y el parallax se realimentaría. */
html.mv-midiendo [data-mv],
html.mv-midiendo [data-mv="ventana"] > * { transform: none !important; }

@keyframes avance-mv { from { --p: 0; } to { --p: 1; } }

/* ---- Fórmulas -----------------------------------------------------------
   Sólo transform y opacity: son las dos propiedades que el compositor puede
   resolver sin volver a calcular el layout en cada fotograma.              */

/* Plano de fondo: se queda atrás respecto al contenido. */
[data-mv="lejos"] {
  transform: translate3d(0, calc(var(--motor) * (var(--p) - 0.5) * 2 * var(--deriva-media)), 0);
}

/* Plano delantero: se adelanta. La diferencia entre ambos es la profundidad. */
[data-mv="cerca"] {
  transform: translate3d(0, calc(var(--motor) * (var(--p) - 0.5) * -1.2 * var(--deriva-sutil)), 0);
}

/* Deriva horizontal: reservada a la franja, donde la frase cruza como un
   letrero de planta al pasar por delante.

   Va en un solo sentido —de 0 a +deriva— y no de −deriva a +deriva: con el
   recorrido simétrico, en anchos medios la frase se salía por el margen
   izquierdo, porque la deriva llegaba a ser mayor que el propio margen.
   Moviéndose sólo hacia dentro, no hay ancho en el que pueda invadirlo.     */
[data-mv="derivax"] {
  transform: translate3d(calc(var(--motor) * var(--p) * var(--deriva-media)), 0, 0);
}

/* Ventana: el marco queda fijo y la foto se mueve dentro. Es el parallax que
   mejor funciona con fotografía de producto — el encuadre actúa de mirilla.

   El recorrido va en PORCENTAJE del alto de la propia foto, no en píxeles: con
   una deriva absoluta, un marco bajo se quedaría sin holgura y asomaría el
   fondo por arriba o por abajo. Con zoom 1.14 sobra un 7 % por cada lado y el
   recorrido es del 5 %, así que nunca se despega. Y es correcto a cualquier
   tamaño de pantalla sin una sola media query.                              */
[data-mv="ventana"] { overflow: clip; }
[data-mv="ventana"] > * {
  display: block;   /* <picture> es inline, y los inline ignoran transform */
  transform:
    translate3d(0, calc(var(--motor) * (var(--p) - 0.5) * 2 * var(--deriva-ventana)), 0)
    scale(calc(1 + var(--motor) * (var(--zoom-ventana) - 1)));
  transform-origin: 50% 50%;
}

/* Reveal de entrada. Con --motor en 0 la opacidad se resuelve en 1: nada
   queda invisible si el motor no llega a arrancar. */
[data-mv="sube"] {
  opacity: calc(1 - var(--motor) * (1 - var(--p)));
  transform: translate3d(0, calc(var(--motor) * (1 - var(--p)) * var(--alza-entrada)), 0);
}

/* ---- Cascada -----------------------------------------------------------
   Un contenedor publica --p y sus hijos se reparten ese mismo recorrido: el
   hermano --n arranca --n·paso más tarde y todos tardan --casc-dur.

   Por qué no se hace con un `sube` por hijo, que sería más corto de escribir:
   cada elemento tendría su propia línea de tiempo `view()`, y una fila de
   tabla de 60 px consume su rango `entry` en 47 px de scroll — se percibe
   como un parpadeo, no como una entrada. Colgando de UN solo recorrido, el
   de la tabla entera, cada fila dispone de cientos de píxeles.

   La misma fórmula sirve a los dos motores y también a los actos fijados,
   donde --p no lo escribe el timeline sino app.js. El reloj cambia; el
   dispositivo no.                                                          */

[data-mv="cascada"],
[data-mv="cascada-fin"] { /* sólo publican --p; no se mueven por sí mismos */ }

[data-casc] {
  --sn: clamp(0, calc((var(--p) - var(--casc-base, 0) - var(--n, 0) * var(--casc-paso)) / var(--casc-dur)), 1);
  opacity: calc(1 - var(--motor) * (1 - var(--sn)));
  transform: translate3d(0, calc(var(--motor) * (1 - var(--sn)) * var(--alza-entrada)), 0);
}

/* Variante lateral: entra desde la derecha, nunca desde la izquierda. Hacia
   la izquierda el recorrido invadiría el margen en anchos pequeños, donde el
   gutter baja a 16 px y la alza vale 14. Hacia dentro no hay ancho en el que
   pueda morder el texto. */
[data-casc="x"] {
  transform: translate3d(calc(var(--motor) * (1 - var(--sn)) * var(--alza-entrada) * 0.6), 0, 0);
}

/* Índice de hermano. Va en CSS y no en el HTML porque es posición, no
   contenido: el marcado no tiene por qué saber que es el cuarto de una fila. */
[data-casc]:nth-child(1)  { --n: 0; }
[data-casc]:nth-child(2)  { --n: 1; }
[data-casc]:nth-child(3)  { --n: 2; }
[data-casc]:nth-child(4)  { --n: 3; }
[data-casc]:nth-child(5)  { --n: 4; }
[data-casc]:nth-child(6)  { --n: 5; }
[data-casc]:nth-child(7)  { --n: 6; }
[data-casc]:nth-child(8)  { --n: 7; }
[data-casc]:nth-child(9)  { --n: 8; }
[data-casc]:nth-child(10) { --n: 9; }

/* ---- El ritmo de la página, en un sitio -------------------------------
   Cada cascada tiene su paso y su duración, y son distintos a propósito. Si
   todas las secciones entraran igual, el lector deja de notarlas a la
   tercera: el movimiento se vuelve fondo en cuanto se vuelve previsible.

   El paso de partida es 1/nº de hermanos, que es lo que hace que cada uno
   llegue justo al cruzar el borde inferior. A partir de ahí se ajusta por
   carácter: una tira de instrumentos se enciende casi a la vez, una ficha se
   rellena línea a línea, una secuencia numerada se sella paso a paso con el
   desfase más ancho de la página, y unas preguntas frecuentes —que son
   trámite y no experiencia— pasan de largo casi sin efecto.                */

.cifras__grid      { --casc-paso: 0.16; --casc-dur: 0.30; }  /* 4 · enciende */
.tabla             { --casc-paso: 0.085; --casc-dur: 0.20; } /* 10 · rellena */
.instala__lista    { --casc-paso: 0.17; --casc-dur: 0.26; }  /* 5 · recita   */
.detalles__grid    { --casc-paso: 0.26; --casc-dur: 0.34; }  /* 3 · presenta */
.pasos             { --casc-paso: 0.29; --casc-dur: 0.34; }  /* 3 · sella    */
.faq__lista        { --casc-paso: 0.11; --casc-dur: 0.14; }  /* 6 · trámite  */
.colofon__bloque   { --casc-paso: 0.22; --casc-dur: 0.30; }  /* 3            */

/* ---- Cortina -----------------------------------------------------------
   El amarillo no aparece de golpe: lo barre un telón que se retira hacia la
   derecha, como se pasa un rodillo. Es la única pantalla de la página que
   puede permitirse ser ruidosa, y es donde se gasta.

   El barrido es HORIZONTAL, y lo fue después de mirarlo. Con un telón que se
   retiraba hacia abajo, la parte tapada era siempre la que todavía no había
   entrado en pantalla: el efecto se medía perfectamente y no se veía nunca.
   Un barrido lateral cambia todo el ancho visible, esté la banda donde esté.

   Se hace escalando una capa y no con clip-path: escalar lo resuelve el
   compositor, recortar obliga a repintar la banda entera cada fotograma.   */
[data-mv="cortina"] { position: relative; isolation: isolate; }
[data-mv="cortina"] > * { position: relative; z-index: 0; }
[data-mv="cortina"]::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: var(--color-grafito);
  transform-origin: 100% 50%;
  transform: scaleX(calc(var(--motor) * (1 - var(--p))));
  pointer-events: none;
}

/* ---- Retirada ----------------------------------------------------------
   La cámara se aleja del hero al abandonarlo. No es un fundido de salida
   por decorar: separa el primer acto del resto y hace que la tira de cifras
   llegue como un plano nuevo y no como más página.                         */
[data-mv="retirada"] {
  transform: translate3d(0, calc(var(--motor) * var(--p) * var(--deriva-sutil) * -1), 0)
             scale(calc(1 - var(--motor) * var(--p) * var(--retirada-escala)));
  transform-origin: 50% 0;
  opacity: calc(1 - var(--motor) * var(--p) * var(--retirada-velo));
}

/* ========================================================================
   ACTOS FIJADOS
   ------------------------------------------------------------------------
   Un acto detiene la página y gasta scroll en contar UNA cosa. La página
   tiene exactamente uno —el corte— porque tres actos son ninguno: el lector
   sale acordándose de que el sitio se movía mucho y no de qué vio.

   El pin es un AÑADIDO, nunca la base. El alto extra y el `sticky` sólo
   existen bajo `html.motor`, que pone app.js. Si el script no corre, o si el
   lector pidió menos movimiento, la sección vuelve a flujo normal con el
   dibujo entregado lleno. Sin esa precaución, un fallo de JavaScript dejaría
   dos mil píxeles de scroll muerto con un plano congelado dentro — que es
   peor que no tener el efecto.

   Aquí --p no lo escribe `view()`: lo escribe app.js. La razón es mecánica,
   no de gusto. Dentro de un elemento pegado, la línea de tiempo `view()` se
   congela, porque el elemento deja de avanzar respecto a la ventana. El
   progreso de un acto se mide contra el recorrido del PIN, no contra el del
   elemento, y eso hoy no lo expresa ninguna línea de tiempo declarativa.
   ======================================================================== */

[data-acto] { --motor: 0; }
html.motor [data-acto] { --motor: 1; }

html.motor [data-acto="fijo"] {
  position: relative;
  height: calc(var(--tramo, var(--tramo-acto)) * 100vh);
  height: calc(var(--tramo, var(--tramo-acto)) * 100svh);
}

html.motor .escena {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  display: grid;
  align-content: center;
  /* `safe` cambia el modo de fallo, y eso importa más que el centrado. Si
     algún día el contenido no cupiera, un centrado normal recorta por ARRIBA
     y por ABAJO a la vez: se perdería el plano por un lado y el remate por el
     otro. Con `safe`, al desbordar se alinea al principio y sólo se pierde la
     cola. Se declara después para que los navegadores que no lo entiendan se
     queden con el centrado a secas. */
  align-content: safe center;
  overflow: clip;
}

/* Estas cuatro declaraciones son el precio de centrar la escena con una
   rejilla, y ninguna es decorativa. Sin ellas, a 375 px de ancho el acto se
   comía 296 px de dibujo —el tapón, la cota de 450 mm y medio segundo
   tambor— sin manera de llegar a ellos. Medido en el navegador.

   La cadena es ésta. El corte lleva `min-width: 640px` en móvil, porque por
   debajo de eso las cotas dejan de leerse. Ese mínimo sube por el lienzo
   hasta el carril, y el carril lleva `margin-inline: auto` para centrarse en
   flujo normal. Un margen automático en un ítem de rejilla DESACTIVA el
   estirado y lo pasa a «ajustar al contenido»: el carril dejaba de medir lo
   que mide su pista (360 px) y pasaba a medir lo que mide el plano (672).
   Con el carril más ancho que el lienzo, el lienzo dejaba de ser contenedor
   desplazable — y `overflow-x: auto` sin desbordamiento no desplaza nada.

   Así que se le quita el margen automático, se le fija el ancho a su pista y
   se centra con la alineación de la rejilla, que es la herramienta correcta
   dentro de una rejilla. El `min-width: 0` se queda como segunda red. */
html.motor .escena > * {
  min-width: 0;
  margin-inline: 0;
  width: 100%;
  justify-self: center;
}

/* En móvil la barra fija de cotización se come el fondo de la ventana. Si la
   escena midiera 100svh, su último tercio quedaría debajo de la barra y la
   lectura de litros —que es el remate del acto— no se vería nunca. */
@media (max-width: 59.99rem) {
  html.motor .escena {
    height: calc(100vh - 4.5rem);
    height: calc(100svh - 4.5rem);
  }
}

/* ---- Cota de avance ----------------------------------------------------
   La página mide 7500 px. Una línea de cota que crece con el recorrido es
   el indicador de progreso dicho en el idioma del resto del sitio.         */

/* La cota es además lo único de la página que responde a la PERSONA y no al
   recorrido: se enciende cuando el lector corre y se apaga cuando se para.
   Va en opacidad y no en grosor porque el grosor es alto, y cambiar el alto
   sesenta veces por segundo obliga a rehacer el layout de la página entera.

   Registrada sin herencia: así escribirla en cada fotograma no invalida el
   estilo de ningún otro elemento del documento. */
@property --vel { syntax: "<number>"; inherits: false; initial-value: 0; }

.avance {
  position: fixed;
  inset-block-start: 0;
  inset-inline: 0;
  height: 2px;
  z-index: var(--z-sticky);
  background: var(--color-amarillo);
  transform: scaleX(var(--avance, 0));
  transform-origin: 0 50%;
  opacity: calc(0.5 + var(--vel) * 0.5);
  pointer-events: none;
}
@supports (animation-timeline: scroll()) {
  html:not(.mv-js) .avance {
    animation: crece-avance linear both;
    animation-timeline: scroll(root block);
  }
  @keyframes crece-avance { from { transform: scaleX(0); } to { transform: scaleX(1); } }
}

/* ---- Contención --------------------------------------------------------
   Nada de lo que se mueve puede empujar el ancho del documento.            */

.franja,
.cifras,
.hero { overflow: clip; }

/* ---- El corte ----------------------------------------------------------
   El llenado lo sigue gobernando app.js, no el timeline de CSS: la cifra que
   sube hasta 208 L es texto, y tiene que ir clavada al dibujo. Un solo
   elemento medido por fotograma no cuesta nada; desincronizarlo, sí.       */

@media (prefers-reduced-motion: reduce) {
  /* El interruptor a 0 desactiva todas las fórmulas de golpe, sin tener que
     enumerarlas. La página queda legible y completamente quieta. */
  [data-mv],
  [data-acto] {
    --motor: 0 !important;
    animation: none !important;
  }
  [data-casc] { transform: none !important; }
  /* El telón de la franja desaparece en vez de quedarse a media altura. */
  [data-mv="cortina"]::before { display: none; }
  .avance { display: none; }
}

/* ========================================================================
   ENTRADA ORQUESTADA — una vez, sólo en el hero, 5 elementos, ≤ 900 ms
   ======================================================================== */

.entra {
  opacity: 0;
  transform: translateY(10px);
  animation: entra var(--dur-long) var(--ease-out) forwards;
  animation-delay: calc(var(--i, 0) * 70ms);
}
@keyframes entra { to { opacity: 1; transform: none; } }

/* El titular es candidato a LCP: entra con desplazamiento pero nunca desde
   opacity 0 — un elemento invisible no cuenta como pintado. */
.entra--solido {
  opacity: 1;
  animation-name: entra-solido;
}
@keyframes entra-solido { to { transform: none; } }


@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  .entra { opacity: 1; transform: none; animation: none; }
  /* El corte se entrega lleno: el mecanismo se lee igual, sin movimiento. */
  .sec-llenado { transform: scaleY(1); }
  .sec-vaciado { transform: scaleY(0); }
}
