/* ============================================================================
   QC Propuesta · Hero + Los Seis Puntos

   Blanco y negro para el documento. Lo oscuro es el producto, y sólo vive
   dentro del marco del teléfono: ahí manda `theme/tokens.ts` de la app.

   EL HERO ES UN PIN, NO UNA SALIDA (Victor, 14-sep · patrón de apple.com/maps).
   El recorrido NO se va hacia arriba cuando scrolleas: se queda clavado y el
   nombre del producto CRECE hacia la cara hasta pasarse de la pantalla. Cuando
   esa ampliación termina, el pin suelta el scroll y entra el titular sobre
   blanco. Quien scrollea de golpe recorre la distancia del pin: la pieza no se
   puede brincar, que era el bug.

   EL TELÉFONO: enorme, ocupa el ancho de su sección y el viewport lo RECORTA por
   abajo. Bisel arriba y a los lados, nunca abajo, y el bisel es CSS —jamás un
   PNG: dos píxeles de marco oscuro son lo que hace que se lea como app y no como
   captura pegada en una web. El device no se mueve: las pantallas suben.
   ========================================================================== */

/* Las mismas caras que carga `theme/fonts.ts`, y en su formato original.
   Nada de derivados aquí: la compresión del sitio es una pasada aparte y final
   (Victor, 14-sep). Si alguien las convierte a woff2, que sea en esa pasada. */
@font-face { font-family: "QC Display"; src: url("assets/fonts/Fraunces_700Bold.68c9dd8d.woff2") format("woff2"); font-weight: 700; font-display: swap; }
@font-face { font-family: "QC Body"; src: url("assets/fonts/Poppins-Regular.b8bb0fb2.woff2") format("woff2"); font-weight: 400; font-display: swap; }
@font-face { font-family: "QC Body"; src: url("assets/fonts/Poppins-Medium.176203bd.woff2") format("woff2"); font-weight: 500; font-display: swap; }
@font-face { font-family: "QC Body"; src: url("assets/fonts/Poppins-Semibold.86661496.woff2") format("woff2"); font-weight: 600; font-display: swap; }

:root {
  /* LA MARCA, con los valores oficiales de Quierocasa
     (Quierocasa/Preparación/5. Brand Identity.md). El oscuro de la marca es
     #303431 y ése es el texto de esta propuesta: ni negro puro —que delata que
     no lo diseñó Apple— ni el gris de Apple, que no es de nadie.
     Regla de uso, textual de la marca: «Amarillo como acción/acento; oscuro
     para texto; blanco para lectura y planos». El amarillo NO es fondo de
     página ni relleno decorativo.
     Y un dato medido que no se discute: blanco sobre amarillo da 1.35:1.
     NUNCA texto blanco sobre amarillo. Oscuro sobre amarillo da 9.36:1. */
  --ink: #303431;             /* text.primary */
  --paper: #fff;              /* surface.primary */
  --soft: #f8f8f8;            /* surface.secondary */
  --hair: #e8e8e8;            /* border.subtle */
  --brand: #fedd00;           /* brand.primary · acción y acento, nada más */
  --brand-pressed: #e3c600;
  --muted: #6e6e73;
  --doc: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Helvetica Neue", Inter, Arial, sans-serif;

  /* Producto — tokens.ts de la app, sin reinterpretar.
     El UI de la app va a cambiar (Victor, 14-sep), así que las pantallas
     dibujadas se alimentan SÓLO de estas variables: cuando llegue el refresh,
     se cambian aquí y no repartidas a mano por el archivo. */
  --qc-bg: #000;
  --qc-surface: #1c1c1e;
  --qc-surface-muted: #141414;
  --qc-elevated: #2c2c2e;
  --qc-border: #38383a;
  --qc-text: #fff;
  --qc-text-muted: #aeaeb2;
  --qc-text-subtle: #8e8e93;
  --qc-primary: #fedd00;
  --qc-on-primary: #1a1a1a;

  /* Geometría que JS mantiene */
  --phone-w: 366px;
  --phone-h: 796px;
  --phone-y: 271px;
  --bezel: 6px;
  /* EL ANCHO DE LA PANTALLA, que es contra el que se mide el cromo. `--phone-w`
     es el aparato ENTERO, canto incluido; medir contra él deja el cromo un 3.4 %
     grande y desplazado. Las capturas miden la pantalla, no el aparato. */
  --screen-w: calc(var(--phone-w) - var(--bezel) * 2);
  --text-top: 76px;
  --text-h: 200px;
  --hero-size: 70px;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--doc);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

/* =============================== ① EL HERO =============================== */

/* 190vh de pin + 100vh de salida. Dentro del pin caben las dos cosas: el nombre
   creciendo sobre el video y, ya en blanco, el titular de la propuesta. */
.hero { height: 290vh; position: relative; }

/* `lvh` Y NO `dvh`, Y NO ES UN DETALLE: con `dvh` este pin CAMBIABA DE ALTO
   mientras el dedo scrollea, porque la barra de Safari entra y sale (medido en
   el Simulador: 714 → 752 → 754 en el primer gesto). Un pin que cambia de alto
   mueve su punto de suelta, y con él el titular que lleva encima. `lvh` es la
   ventana SIN barra y es constante, así que el hero cubre siempre y no se
   mueve; los 40 px que la barra tapa por abajo caen sobre el video, que va a
   sangre. Es la misma vara que usa `#hero { height: 290vh }`. Ver la nota de
   `unidad()` en main.js. */
.hero-pin {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100lvh;
  overflow: hidden;
  background: var(--paper);
}

/* El recorrido, a sangre. Cero margen blanco: el blanco de la propuesta
   empieza DESPUÉS del hero, no dentro. */
.media { position: absolute; inset: 0; overflow: hidden; will-change: transform; }

/* La copia desenfocada de atrás murió el 14-sep por la noche: el video del hero
   es 16:9 y cubre solo a los dos anchos. */

/* Encima, la copia nítida. JS le da su caja y NUNCA la amplía más allá de
   576×1024, que es lo que mide el fuente. */
.media-sharp {
  position: absolute;
  left: 50%;
  top: 50%;
  width: var(--sharp-w, 100vw);
  height: var(--sharp-h, 100vh);
  margin-left: calc(var(--sharp-w, 100vw) / -2);
  margin-top: calc(var(--sharp-h, 100vh) / -2);
  overflow: hidden;
}
.media-sharp video { width: 100%; height: 100%; object-fit: cover; display: block; }

/* EL VELO NEGRO MURIÓ AQUÍ, el 15-sep, por orden de Victor: «quita ese velo por
   completo, no entiendo qué tiene que hacer ahí».

   ERA `.media-veil { inset: 0; background: #000; opacity: .42 }` — negro al 42 %
   encima de TODO el hero desde el primer cuadro. Existía para que el titular se
   leyera sobre el video y se decidió cuando el hero era OTRO clip; nadie volvió
   a mirarlo con «06 Bienvenida 2K», que ABRE con la Q amarilla a pantalla
   completa. LO QUE DESLAVABA ERA ESTO Y NO LA COMPRESIÓN, medido por el 👑: el
   color dominante del cuadro 0 servido es #FEDD02 contra el #FEDD00 de marca, y
   la saturación media 116.08 contra 115.39 del original.

   El div ya salió también de index.html, por «✍️ Redacción». NO SE DEVUELVE
   NINGUNO DE LOS DOS: lo que el velo sostenía vive ahora en el halo de
   `.hero-name`, medido ahí mismo. El blanqueo del final (`--wash`) es otra cosa
   y se queda: es la entrega al documento. */

/* El pin se blanquea para entregarle la pantalla al titular. Sólo se justifica
   porque algo ocupa ese blanco enseguida; blanquear para nada es el defecto. */
.media::after { content: ""; position: absolute; inset: 0; background: var(--paper); opacity: var(--wash, 0); }

/* El nombre del producto. Una palabra, blanca, centrada, y crece hacia la cara. */
.hero-name {
  position: absolute;
  inset: 0;
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--hero-size);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -.045em;
  color: #fff;
  text-align: center;
  /* LO QUE SOSTENÍA EL VELO, RESUELTO DONDE VIVE EL TEXTO Y NO ENCIMA DE LA
     MARCA. Con el velo negro fuera, «Garantías» aparece en z 0.46 sobre el
     cuadro t≈5.65 del recorrido, que es LA FACHADA A PLENO DÍA: estuco crema
     claro, y blanco sobre crema no se lee. Medido en captura, a 390 y a 1440.
     Esto es un halo corto pegado a las letras —no un velo parejo—: sobre los
     cuadros oscuros, que son la mayoría, no se nota; sobre el crema, devuelve la
     palabra. El amarillo de marca de la portada no lo toca, porque ahí no hay
     letras. NO SE DEVUELVE EL VELO (Victor, 15-sep, tajante).
     El desenfoque se queda corto a propósito: este h1 se ESCALA hasta 16×, y un
     halo generoso a esa escala oscurecería media pantalla. */
  text-shadow: 0 0 2px rgba(0, 0, 0, .22), 0 2px 18px rgba(0, 0, 0, .46);
  will-change: transform, opacity;
  backface-visibility: hidden;
}

/* ==================== EL TITULAR DE LA PROPUESTA ==================== */

.hero-lede {
  position: absolute;
  inset: 0;
  margin: 0;
  display: grid;
  place-items: center;
  padding: 0 6vw;
  text-align: center;
  font-size: clamp(44px, 14.4vw, 72px);
  font-weight: 700;
  line-height: 1.07;
  letter-spacing: -.04em;
  color: var(--ink);
  opacity: 0;
  will-change: transform, opacity;
}

/* =========================== ② LOS CUATRO BEATS ===========================
   Estas secciones sólo aportan altura de scroll. */

/* Cada beat pide su propio tramo de scroll. Si un acto se acorta por debajo de
   ~90vh por beat, el titular no alcanza a entrar antes de que salga el siguiente. */
#act1  { height: 110vh; }  /* 1 beat · Sus Departamentos */
/* EL PLANO NO ES UN BEAT: ES LA DEMOSTRACIÓN. Su alto es el que necesitan dos
   recorridos completos —toque, entrada al cuarto y REGRESO— más la invitación
   del final, leídos sin prisa, porque el scroll ES la reproducción: cada paso
   cuesta su tramo de rueda. Con 150vh los dos recorridos cabían en un golpe de
   dedo y no se entendía ninguno. */
/* 430vh: los 360 de siempre MÁS los 70 del estirón, que va delante. La
   demostración guiada conserva su tramo EXACTO —358.65vh— porque su arranque se
   corrió los mismos 70vh; no se le quitó un píxel de rueda a nada aprobado. */
#plano { height: 430vh; }
/* 3 beats a 120vh: el de la evidencia necesita más rueda que los otros porque
   dentro de él pasan TRES cosas —las dos fotos y la nota de voz— y cada una
   tiene que leerse. A 90vh por beat se atropellaban. */
#act2 { height: 360vh; }
/* LA SALIDA. Sin contenido: es el tramo donde el aparato se encoge en su sitio
   y se va, ya con el ticket nacido. Aquí se acaba el recorrido. */
#salida { height: 70vh; }

/* SU ALTO ES EL MISMO QUE EL DEL PIN Y SE ESCRIBE, no se hereda del viewport:
   así la caja donde JS coloca el teléfono y los titulares mide exactamente lo
   que JS cree que mide (`G.vh`), y no lo que el navegador decida esta vez. */
.stage {
  position: fixed;
  inset: 0 0 auto;
  height: 100vh;
  height: 100lvh;
  overflow: hidden;
  pointer-events: none;
  z-index: 3;
  opacity: 0;
  contain: strict;
}

/* Lo único que queda arriba: seis rayas. No es texto, es saber dónde vas. */
.rail {
  position: absolute;
  left: 50%;
  top: max(16px, env(safe-area-inset-top));
  display: inline-flex;
  gap: 5px;
  z-index: 2;
  transform: translateX(-50%);
}
.rail i { display: block; width: 11px; height: 2px; border-radius: 2px; background: rgba(0,0,0,.16); transition: background .3s ease; }
.rail i.is-on { background: var(--ink); }

/* El titular manda solo: centrado, negro, gigante, y nada a su lado. */
.points-text {
  position: absolute;
  left: 50%;
  top: var(--text-top);
  width: min(1100px, 88vw);
  height: var(--text-h);
  margin-left: calc(min(1100px, 88vw) / -2);
  text-align: center;
  transform-origin: 50% 0;
}

.point {
  position: absolute;
  left: 0; right: 0; top: 0;
  margin: 0;
  opacity: 0;
  transform: translate3d(0, 18px, 0);
  will-change: transform, opacity;
}

/* 40/44/700 a 390 px, centrado. Es la medida real del titular de un punto en
   apple.com/maps. Los titulares grandes van APRETADOS —interlínea casi 1.0—;
   el cuerpo va suelto a 1.47. Ese contraste es media vibra. */

.point h2 {
  margin: 0;
  font-size: clamp(34px, 10.3vw, 44px);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -.035em;
}
/* Aquí NO va un párrafo. Ver la nota del principio de index.html. */

/* ============================== EL TELÉFONO ============================== */

.phone {
  position: absolute;
  left: 50%;
  top: var(--phone-y);
  transform-origin: 50% 46%;
  width: var(--phone-w);
  height: var(--phone-h);
  margin-left: calc(var(--phone-w) / -2);
  will-change: transform;
  backface-visibility: hidden;
}

.phone-shell {
  position: absolute;
  inset: 0;
  border-radius: calc(var(--phone-w) * .152);
  background: linear-gradient(158deg, #45454a 0%, #17171a 30%, #0b0b0d 62%, #2a2a2e 100%);
  padding: var(--bezel);
  box-shadow:
    0 0 0 1px rgba(0, 0, 0, .55),
    0 -2px 3px rgba(255, 255, 255, .12) inset,
    0 30px 70px -24px rgba(0, 0, 0, .42),
    0 90px 140px -60px rgba(0, 0, 0, .3);
}

.phone-screen {
  position: relative;
  height: 100%;
  border-radius: calc(var(--phone-w) * .152 - var(--bezel));
  overflow: hidden;
  background: var(--qc-bg);
  color: var(--qc-text);
  font-family: "QC Body", var(--doc);
  /* Todo lo de adentro se mide en em de esta base: el teléfono escala solo */
  font-size: calc(var(--phone-w) / 31);
  isolation: isolate;
}

/* LA ISLA, MEDIDA EN «09 · Zona Elegida» sobre 1206 px de ancho de pantalla:
   376 de ancho (0.3118), 110 de alto (0.0912) y su canto superior a 42 (0.0348).
   Antes iba a ojo —0.31 · 0.088 · 0.030— y colgada del ancho del APARATO, que
   incluye el canto. El radio 99px no es una medida: es «completamente redonda». */
.island {
  position: absolute;
  left: 50%; top: calc(var(--screen-w) * .0348);
  width: calc(var(--screen-w) * .3118); height: calc(var(--screen-w) * .0912);
  margin-left: calc(var(--screen-w) * -.1559);
  border-radius: 99px;
  background: #000;
  z-index: 40;
}

/* LA BARRA DE ESTADO NO SE DIBUJA: ES LA FRANJA DE LA CAPTURA, recortada con
   alfa. Va a TODO el ancho de la pantalla y su alto lo pone la propia imagen
   (1206x170), así que la hora, la cobertura, el wifi y la pila caen en su sitio
   y con su cuerpo por construcción, a cualquier tamaño de aparato. Dibujada se
   quedó chica tres vueltas seguidas, y la causa siempre fue la misma: un valor
   colgado del `em` de la pantalla en vez del ancho del aparato. */
.sb {
  position: absolute;
  left: 0; top: 0;
  width: 100%; height: auto;
  z-index: 39;
  display: block;
  pointer-events: none;
}

/* ------------------------------ Las pantallas -----------------------------
   Suben desde abajo y se acomodan como una hoja sobre la anterior. */

.screen {
  position: absolute;
  inset: 0;
  background: var(--qc-bg);
  overflow: hidden;
  transform: translate3d(0, 100%, 0);
  will-change: transform;
  backface-visibility: hidden;
  /* El canto de la hoja: sin él, una pantalla subiendo sobre otra negra se lee
     como un error de pintado y no como una hoja que entra. */
  /* Proporcional al aparato, como todo lo de dentro: una sombra de píxeles
     fijos se vuelve una raya cuando el teléfono crece. */
  box-shadow: 0 calc(var(--phone-w) * -.04) calc(var(--phone-w) * .1) rgba(0, 0, 0, .8);
}

/* ============================ LOS GESTOS ============================
   Cada punto tiene que DEMOSTRAR lo que su titular promete (Victor, 14-sep:
   «esta madre no está haciendo lo que dice»). El scroll escribe `--g` (0→1) en
   la pantalla activa y cada gesto se expresa aquí, en CSS, como función de esa
   variable. Sin `--g` todo queda en su estado final, que es lo correcto para
   una pantalla ya vista.

   Se usa clamp() sobre números crudos para recortar los tramos: así un gesto
   puede empezar a mitad de otro sin que JS tenga que saberlo. */

/* DE PÍXEL DE CAPTURA A PÍXEL DE PANTALLA. Lo usan todos los beats que
   recortan una captura en franjas, así que vive aquí y no repetido.
   Las capturas miden 1206x2622 y la pantalla del aparato es un pelo más
   alargada, así que `object-fit: cover` escala por el ALTO y recorta ANCHO,
   centrado. Una Y de la captura cae directa en `Y * --evk`; una X cae en
   `X * --evk - --evx`. Todo cuelga de --phone-h y --bezel, que los escribe JS
   en fit(): ni un píxel fijo, que es la regla dura de lo que va dentro del
   teléfono. */
.screen {
  --g: 1;
  --screen-h: calc(var(--phone-h) - var(--bezel) * 2);
  --evk: calc(var(--screen-h) / 2622);
  --evx: calc((1206 * var(--evk) - var(--screen-w)) / 2);
}

/* El gesto de un beat es PASAR DE UN ESTADO REAL A OTRO, con píxeles de la app
   y no con un dibujo. CÓMO se pasa depende de si las dos capturas casan, y eso
   se MIDE, no se supone: si casan, se cruza; si una es la otra desplazada, se
   desplaza (.is-folio); si no casan de ninguna manera, se cambia de golpe
   (.is-corte). Cruzar dos capturas que no casan es el defecto que Victor
   señaló el 14-sep: «un JPG encima de otro». */
.screen-shot { background: var(--qc-bg); }
.shot, .ev { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: top center; display: block; }
.shot-b { opacity: clamp(0, calc((var(--g) - .18) * 2.6), 1); }

/* ============ EL BEAT DE LA EVIDENCIA · UN LIENZO QUE SE LLENA ============
   El porqué y las capturas de las que sale cada franja están en index.html,
   donde vive el beat. Esto es la mecánica.

   NADA SE CRUZA. Una foto se AÑADE al lado de la otra, y el paso a «grabando»
   es un CORTE de un cuadro —que es lo que hace la app cuando sueltas el botón—,
   no una disolvencia. El defecto que Victor señaló no puede volver por
   construcción: no hay dos capturas superpuestas a media opacidad en ningún
   cuadro.

   LAS CAPAS, de abajo a arriba:
     .ev-pie    la fila de botones, la regla y Enviar, de la captura 13. BAJA
                216 px al entrar las fotos, que es lo que la tarjeta crece
     .ev-foto1  la primera foto, de la 14, entrando desde su propio centro
     .ev-foto2  la segunda, al lado y después
     .ev-head   la cabecera y la descripción, de la 14. Va ENCIMA del pie para
                que el pie se meta debajo de ella al subir, como una hoja
     .ev-dedo   el dedo que toca Audio: llega, presiona, suelta y se va
     .ev-rec    lo que ya está grabando: barra, y la onda que se dibuja

   EL REPARTO EN `--g`, y las ventanas NO se tocan entre sí: una sola cosa se
   mueve a la vez.
     .05-.25  entra la foto 1 y el pie baja
     .30-.48  entra la foto 2
     .54-.74  el dedo: llega, presiona, suelta y se va
     .66      EL CORTE, en el cuadro en que el dedo suelta
     .76-.99  la onda de la grabación se dibuja de izquierda a derecha */
.is-evidencia {
  --pF1: clamp(0, calc((var(--g) - .05) * 5), 1);
  --pF2: clamp(0, calc((var(--g) - .30) * 5.5), 1);
  --dIn: clamp(0, calc((var(--g) - .54) * 16.6), 1);
  --dPr: clamp(0, calc((var(--g) - .60) * 22), 1);
  --dRe: clamp(0, calc((var(--g) - .645) * 33), 1);
  --dOut: clamp(0, calc((var(--g) - .68) * 16.6), 1);
  --cut: clamp(0, calc((var(--g) - .658) * 500), 1);
  --w:   clamp(0, calc((var(--g) - .76) * 4.3), 1);
}

.ev-head { z-index: 5; clip-path: inset(0 0 calc(var(--screen-h) - 1840 * var(--evk)) 0); }

/* EL PIE, DE LA CAPTURA 13. Los 36 px fijos no son un capricho: es lo que la
   app desplaza la pantalla entre las dos capturas, medido. Los 216 son lo que
   crece la tarjeta al entrar las fotos, medido entre estados de la misma toma. */
.ev-pie {
  z-index: 1;
  clip-path: inset(calc(1700 * var(--evk)) 0 0 0);
  transform: translateY(calc((36 + 216 * var(--pF1)) * var(--evk)));
}

.ev-foto1, .ev-foto2 {
  z-index: 3;
  clip-path: inset(calc(1840 * var(--evk)) var(--ev-r) calc(var(--screen-h) - 2100 * var(--evk)) var(--ev-l));
  transform-origin: var(--ev-cx) calc(1976 * var(--evk));
}
.ev-foto1 {
  --ev-l: calc(62 * var(--evk) - var(--evx));
  --ev-r: calc(var(--screen-w) - (319 * var(--evk) - var(--evx)));
  --ev-cx: calc(193 * var(--evk) - var(--evx));
  opacity: var(--pF1);
  transform: scale(calc(.82 + var(--pF1) * .18));
}
.ev-foto2 {
  --ev-l: calc(319 * var(--evk) - var(--evx));
  --ev-r: calc(var(--screen-w) - (600 * var(--evk) - var(--evx)));
  --ev-cx: calc(445 * var(--evk) - var(--evx));
  opacity: var(--pF2);
  transform: scale(calc(.82 + var(--pF2) * .18));
}

/* EL DEDO, el mismo de la sección del plano y por la misma razón: un círculo
   que llega, se contrae y suelta dice TOCA. Aquí lo mueve `--g` desde el CSS
   —allá lo mueve paint() porque la demostración guiada tiene más estado—, pero
   los dos salen del scroll y ninguno se anima solo.
   Cae sobre el botón Audio, x 262 de la captura 13, y baja con el pie. */
/* La especificidad importa y costó un cuadro en blanco: `.tap-hint` declara
   `opacity: 0` y vive MÁS ABAJO en este archivo, así que una regla de una sola
   clase la perdía. Por eso va calificada con la sección. */
.is-evidencia .ev-dedo {
  z-index: 9;
  left: calc(262 * var(--evk) - var(--evx));
  top: calc((1965 + 36 + 216 * var(--pF1)) * var(--evk));
  --press: calc(var(--dPr) * (1 - var(--dRe)));
  opacity: calc(var(--dIn) * (1 - var(--dOut)));
  transform: scale(calc(1.6 - var(--dIn) * .6 - var(--dPr) * .32 + var(--dRe) * .4 + var(--dOut) * .5));
}

/* Y GRABANDO. Entra de golpe —`--cut` sube de 0 a 1 en dos milésimas de la
   ventana— porque en la app la barra aparece en el cuadro en que sueltas.
   Va partida en tres para poder dibujar la onda aparte: lo que queda a la
   izquierda de la onda, lo que queda a la derecha, y la onda. */
.ev-rec { z-index: 6; opacity: var(--cut); }
.ev-rec-bajo { clip-path: inset(calc(2280 * var(--evk)) 0 0 0); }
/* EL FONDO DE LA ONDA, y es píxel de la barra, no un color pintado: la misma
   captura recortada en x 600-804 —interior liso de la barra, entre la onda y el
   botón de enviar— y corrida 241 px a la izquierda para que caiga justo donde
   va la onda. Sin esto, mientras la onda se dibuja se veía por el hueco la
   fila de botones del compositor, que está debajo. */
.ev-onda-fondo {
  clip-path: inset(calc(2100 * var(--evk)) calc(var(--screen-w) - (804 * var(--evk) - var(--evx))) calc(var(--screen-h) - 2280 * var(--evk)) calc(600 * var(--evk) - var(--evx)));
  transform: translateX(calc(-241 * var(--evk)));
}
.ev-voz-l { clip-path: inset(calc(2100 * var(--evk)) calc(var(--screen-w) - (359 * var(--evk) - var(--evx))) calc(var(--screen-h) - 2280 * var(--evk)) 0); }
.ev-voz-r { clip-path: inset(calc(2100 * var(--evk)) 0 calc(var(--screen-h) - 2280 * var(--evk)) calc(563 * var(--evk) - var(--evx))); }
.ev-onda  { clip-path: inset(calc(2100 * var(--evk)) calc(var(--screen-w) - ((359 + 204 * var(--w)) * var(--evk) - var(--evx))) calc(var(--screen-h) - 2280 * var(--evk)) calc(359 * var(--evk) - var(--evx))); }

@media (prefers-reduced-motion: reduce) {
  .ev-foto1, .ev-foto2, .ev-pie, .fo-tit, .fo-cuerpo { transform: none !important; }
  .ev-onda-fondo { transform: translateX(calc(-241 * var(--evk))) !important; }
  .ev-dedo { display: none; }
}

/* ===================== EL FOLIO · NO SE CRUZA, SE DESPLAZA =====================
   MISMO DEFECTO QUE EL DE LA EVIDENCIA, Y MISMA CURA. Lo medí: «22 · Folio Y
   Seguimiento · Movimientos» ES «21 · Folio Y Seguimiento» DESPLAZADA 336 px,
   exacta al píxel entre las filas 366 y 1985. O sea que cruzarlas por opacidad
   escribía cada línea dos veces, separadas 336 px. Aquí no se cruza nada: el
   cuerpo se DESPLAZA, que es lo que hace la pantalla cuando la recorres.

   TRES CAPAS Y NINGUNA SE PISA:
     · la cabecera —barra de estado y «‹ Ágreda 229 · Departamento 101»— está
       QUIETA en las dos capturas, filas 0-366, y va encima de todo
     · el titular del folio, filas 366-702 de la 21, sube y se mete debajo de la
       cabecera; la 22 no lo tiene porque allí ya está arriba
     · el cuerpo, filas 366-2622 de la 22, sube detrás
   Con --g a 0 el resultado es la 21 y con --g a 1 es la 22, las dos exactas.
   ÚNICA SALVEDAD, y la digo porque se ve: en la 22 la lista de movimientos
   está DESPLEGADA y en la 21 no, así que con --g a 0 el último 11 % de la
   pantalla enseña los movimientos abiertos. Es un estado real de esa pantalla
   —no es un dibujo— y cae en la parte que el recorte del aparato ya se come. */
.fo-cab { z-index: 3; clip-path: inset(0 0 calc(var(--screen-h) - 366 * var(--evk)) 0); }
.fo-tit {
  z-index: 2;
  clip-path: inset(calc(366 * var(--evk)) 0 calc(var(--screen-h) - 702 * var(--evk)) 0);
  transform: translateY(calc(-336 * var(--fo) * var(--evk)));
}
.fo-cuerpo {
  z-index: 1;
  clip-path: inset(calc(366 * var(--evk)) 0 0 0);
  transform: translateY(calc(336 * (1 - var(--fo)) * var(--evk)));
}
.is-folio { --fo: clamp(0, calc((var(--g) - .22) * 1.9), 1); }

/* ================== LAS DOS QUE CAMBIAN DE GOLPE, Y ES LO HONESTO ==================
   En «Sus Departamentos.» las dos capturas son DOS TARJETAS DISTINTAS, y en el
   catálogo la hoja crece hacia arriba mientras los síntomas se insertan. Medí
   las dos y NINGUNA casa para cruzarse: la de departamentos no comparte un
   píxel —dos fotos distintas— y la del catálogo sólo se traslada en su bloque
   de arriba, no entera. Cruzarlas por opacidad es exactamente el defecto que
   Victor señaló: un JPG encima de otro.

   Así que no se cruzan: CAMBIAN. Y no es una renuncia, es lo que hace la app —
   tocas la categoría y se abre en el mismo cuadro, no hay disolvencia en
   ningún sitio del producto. El cambio lo sigue mandando el scroll, en el
   punto medio del gesto; lo único que se va es el tramo de mezcla.
   SI ALGUIEN QUIERE MOVIMIENTO AQUÍ, no es cosa de ajustar este número: hacen
   falta capturas que casen —la tarjeta siguiente entrando desde abajo con su
   barra de estado aparte, o la hoja del catálogo con los síntomas en su
   propia franja— y eso es una captura nueva, no un cambio de CSS. */
.is-corte .shot-b { opacity: clamp(0, calc((var(--g) - .5) * 400), 1); }

/* ===================== 5 · LOCALIZA EL PROBLEMA =====================
   Ya no tiene pin ni aparato propio: es un tramo más del recorrido del ÚNICO
   teléfono del primer movimiento, y su titular vive con los demás en la capa
   fija. Lo único suyo es que SE TOCA. */


/* UN DEDO QUE LLEGA, PRESIONA Y SUELTA, no un destello. Un destello dice
   «mírame»; un círculo que se contrae y suelta dice TOCA, y eso es una
   instrucción, no decoración.
   Y NO SE ANIMA SOLO: ni `animation` ni `transition` ni `setInterval`. La
   posición, la escala y la opacidad las escribe `paint()` en cada cuadro a
   partir del scroll, que es la regla ① de la casa aplicada también aquí. El
   anillo es el dedo que llega; el punto de dentro es la yema que aprieta. */
.tap-hint {
  position: absolute; z-index: 4;
  width: 17%; aspect-ratio: 1;
  margin: -8.5% 0 0 -8.5%;
  border-radius: 50%;
  border: calc(var(--screen-w) * .008) solid var(--qc-primary);
  background: rgba(254, 221, 0, .14);
  box-shadow: 0 0 0 calc(var(--screen-w) * .004) rgba(0, 0, 0, .28);
  opacity: 0;
  pointer-events: none;
  will-change: transform, opacity;
}
.tap-hint i {
  position: absolute; inset: 26%;
  border-radius: 50%;
  background: var(--qc-primary);
  opacity: calc(var(--press, 0) * .72);
}
/* Y la palabra la pone el producto: mientras la seña vive, su placa se hace
   presente. No hay rótulo inventado. */
.is-hint .plan-ask { color: #fff; font-weight: 600; }

/* LA INVITACIÓN, y no retiene a nadie: aparece cuando la demostración terminó
   sus dos recorridos y el visitante puede seguir scrolleando sin tocar nada. */
.plan-invite {
  position: absolute; z-index: 4;
  left: 50%; bottom: 19%;
  margin: 0;
  transform: translateX(-50%);
  padding: calc(var(--screen-w) * .028) calc(var(--screen-w) * .052);
  border-radius: 999px;
  background: rgba(20, 20, 22, .72);
  -webkit-backdrop-filter: blur(calc(var(--screen-w) * .03)); backdrop-filter: blur(calc(var(--screen-w) * .03));
  color: #fff;
  font-size: calc(var(--screen-w) * .040);
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  will-change: opacity, transform;
}

/* EL ESTIRÓN, encima de todo lo de esta pantalla porque ES la pantalla: la
   grabación trae su barra de estado, su galón, su placa y su píldora. Entra y
   sale de golpe, y puede hacerlo sin que se note porque su primer cuadro y su
   último son el mismo, y los dos son el plano en reposo. */
/* `.elastico` murió el 14-sep por la noche: el estirón lo hace `.plan-lienzo` con una transformación (main.js, estiron). */

/* LAS SEIS FLECHAS. Comparten caja y viewBox con los polígonos —el
   `.plan-lienzo`— así que no se pueden descolocar respecto de las zonas.
   El trazo NO escala con la caja: sin `non-scaling-stroke`, el estirón
   vertical del viewBox saldría como una punta gorda. Y el puntero no las toca:
   ésta era la capa que bloqueaba el plano. */
.plan-flechas {
  position: absolute; inset: 0; z-index: 4;
  width: 100%; height: 100%;
  opacity: 0;
  pointer-events: none;
  overflow: visible;
  will-change: opacity;
}
.plan-flechas path {
  fill: none;
  stroke: var(--qc-primary);
  stroke-width: calc(var(--screen-w) * .0062);
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
  filter: drop-shadow(0 0 calc(var(--screen-w) * .008) rgba(0, 0, 0, .5));
}

/* ============ QUÉ SE TOCA DENTRO DEL PLANO, Y SÓLO ESO ============
   EL BUG QUE ESTO CURA, y era grave: aquí vivía
       `.is-plan, .is-plan *, .zscene, .zscene * { pointer-events: auto }`
   —una escoba— y como `.stage` ignora el puntero para que la página siga
   scrolleando por debajo, hacía falta volver a encenderlo dentro del plano. El
   problema es que lo encendía en TODO: la flecha de la invitación, la propia
   invitación, la seña del dedo, la placa y el degradado del pie quedaban
   tocables y, al estar POR ENCIMA de los polígonos, se comían el clic. La caja
   de un SVG es rectangular aunque su trazo sea un pelo, así que la flecha
   tapaba medio plano y la sección que Victor llama el oro dejó de funcionar.

   REGLA: el puntero se enciende UNA POR UNA en las piezas que de verdad son
   controles, nunca con `*`. Un hijo con `pointer-events: auto` es tocable
   aunque su padre lo tenga en `none`, así que no hace falta encender el
   contenedor. Si añades una capa nueva encima de las zonas, NO la enciendas.

   Y LA MISMA REGLA TIENE UNA SEGUNDA MITAD, que costó la sección entera: LA
   ESCENA QUE PONE LA GUÍA NO ES UN CONTROL DEL VISITANTE. La demostración
   guiada abre `.zscene` sola, y `.zscene` está a z-index 30 ENCIMA de los seis
   polígonos: mientras corría, un clic sobre una zona caía en el `.zvid` y
   `back()` salía de largo porque no había nada abierto. Medido el 15-sep a 390
   en WebKit, a dp .22: nueve muestras con el reloj clavado en t = 0.385 y la
   zona sin cambiar. El clic no abría el cuarto, no cerraba la escena, no hacía
   NADA — y la sección que Victor llama el oro se sentía muerta.

   Por eso el puntero de la escena cuelga de `.is-suya`, que JS pone SÓLO en
   `open()`: mientras manda la guía el clic pertenece al plano, y en cuanto el
   visitante toca, manda él.

   Y está PROBADO, no supuesto: `Site/.build/toca.py` hace clic en el centro de
   las seis zonas a 390 y a 1440, con la invitación puesta, después de apagarla
   Y CON LA GUÍA CORRIENDO Y LA ESCENA ABIERTA —dp .22, la muestra que destapó
   esto—, y falla si alguna no responde. */
.is-plan .zone,
.zscene.is-on.is-suya .zback,
.zscene.is-on.is-suya .zstill,
.zscene.is-on.is-suya .zvid { pointer-events: auto; }

/* Sube como las demás: es una pantalla más de la pila. */
.is-plan { position: absolute; inset: 0; display: flex; flex-direction: column; background: var(--qc-bg); }

/* El plano A SANGRE, como en la app: llega al borde de arriba y a todo el
   ancho, y abajo se funde a negro. Ni caja, ni borde, ni barra, ni título. */
.plan-bleed { position: relative; flex: 1 1 auto; overflow: hidden; background: #ededee; }
/* LA PLANTA Y SUS ZONAS, EN LA MISMA CAJA. A ancho completo y pegada arriba,
   que es exactamente como la coloca la app —medido contra «Capturas/08»: la
   escala del ancho, 1206/941 = 1.2816, y el borde superior a cero—. Debajo
   queda el fondo del plano, igual que en la app. */
.plan-lienzo { position: absolute; left: 0; top: 0; width: 100%; aspect-ratio: 941 / 1672; }
.plan-bleed img { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
/* El degradado va DEBAJO de la placa. Pintado encima —que es donde lo deja el
   orden natural de un ::after— apagaba el único texto de la pantalla. */
.plan-bleed::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 26%; z-index: 1;
  background: linear-gradient(180deg, rgba(0,0,0,0), var(--qc-bg) 88%);
}
.plan-zones { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }

/* LOS SEIS CUARTOS, TOCABLES ANTES DE ELEGIR NINGUNO. Punteado amarillo y
   lavado al 15 % sobre una subcapa negra, que es lo que deja leer el amarillo
   en las zonas claras y en las oscuras del dibujo. Elegir uno se distingue por
   contorno SÓLIDO y más pesado — nunca apagando los demás. */
.zone {
  fill: rgba(0, 0, 0, .18);
  stroke: var(--qc-primary);
  stroke-width: 2;
  stroke-dasharray: 6 5;
  vector-effect: non-scaling-stroke;
  cursor: pointer;
  pointer-events: auto;
}
.zone.is-picked { fill: rgba(254, 221, 0, .24); stroke-dasharray: none; stroke-width: 3.5; }
.zone:hover { fill: rgba(254, 221, 0, .18); }

/* EL CHEVRON DE VOLVER, MEDIDO EN LA CAPTURA. En «07 · Plano» y en «09 · Zona
   Elegida», sobre 1206 px de pantalla: asta blanca de 24x44 con traza de 5 y
   perfil oscuro que la lleva a 12, esquina superior izquierda del asta en
   (59, 230). El viewBox está en esos mismos píxeles, así que 28 unidades de
   ancho son 28/1206 del ancho de la pantalla y todo cuadra solo.
   Era «‹», una comilla tipográfica: cada tipografía la dibuja de otro tamaño y
   por eso nunca coincidía con nada. */
.chev { display: block; width: 100%; height: auto; overflow: visible; }
.chev path { fill: none; stroke-linecap: round; stroke-linejoin: round; }
.chev-halo { stroke: rgba(0, 0, 0, .5); stroke-width: 12; }
.chev-ink  { stroke: #fff; stroke-width: 5; }
.plan-chev, .zback {
  position: absolute; z-index: 3;
  left: calc(var(--screen-w) * .0463);
  top: calc(var(--screen-w) * .1882);
  width: calc(var(--screen-w) * .0232);
}
.plan-chev { pointer-events: none; }
.plan-plate {
  position: absolute; z-index: 2; left: .8em; right: .8em; bottom: .9em;
  padding: .75em .9em .85em;
  border-radius: 1em;
  background: rgba(40, 40, 42, .64);
  -webkit-backdrop-filter: blur(calc(var(--phone-w) * .034)); backdrop-filter: blur(calc(var(--phone-w) * .034));
}
.plan-ask { margin: 0; font-size: .82em; color: rgba(255,255,255,.92); }
.plan-where { margin: .35em 0 0; font-size: 1em; font-weight: 600; letter-spacing: -.02em; color: #fff; }
.plan-pill {
  flex: 0 0 auto;
  margin: .9em 1.1em 1.4em;
  padding: .7em 1.2em;
  align-self: flex-start;
  border-radius: 99px;
  background: var(--qc-surface);
  font-size: .86em; font-weight: 500;
}

/* La zona abierta: el recorrido corriendo a pantalla completa dentro del
   teléfono. En la app la zona se dice DENTRO de la foto, en serif, sobre una
   placa negra translúcida, con la dirección debajo. */
/* LA ESCENA DE LA ZONA. APAGADA ES APAGADA, Y ESTO NO ES UN DETALLE DE ESTILO:
   es la otra mitad del bug que dejó el plano sin responder. Estaba oculta con
   `opacity: 0` + `visibility: hidden`, pero su video conserva la clase `is-on`
   de la zona que se enseñó por última vez, y `.zvid.is-on` volvía a declarar
   `visibility: visible` — una declaración en un descendiente DESHACE el hidden
   del padre—. Resultado: un video a pantalla completa, invisible porque el
   padre tiene opacidad 0, pero PRESENTE para el puntero y por encima de los
   seis polígonos, comiéndose todos los clics.
   Dos cierres, cada uno bastaría y los dos están: la escena apagada no recibe
   puntero, y el `visible` de sus hijos sólo vale cuando la escena está puesta. */
.zscene {
  position: absolute; inset: 0; z-index: 30;
  background: var(--qc-bg);
  display: flex; flex-direction: column;
  opacity: 0; visibility: hidden;
  pointer-events: none;
  /* SIN DISOLVENCIA, Y AHORA SE PUEDE. La tarjeta de esta pantalla y la del
     plano son la MISMA caja, así que pasar de una a otra es un corte limpio —que
     es lo que hace la app: en «Capturas/C» el cuadro 02 es el plano con sus
     zonas y el 03 ya es el recorrido, sin nada intermedio. Con los 0.18 s de
     disolvencia que había aquí se veían las dos placas y las dos barras de
     estado a la vez durante un tercio de segundo. */
}
.zscene.is-on { opacity: 1; visibility: visible; }
/* LA TARJETA ES LA MISMA QUE LA DEL PLANO, y por eso usa SUS clases —
   `.plan-bleed`, `.plan-lienzo`, `.plan-plate`— en vez de copias parecidas.
   En la app las dos pantallas son la misma caja: pie a 0.8162 del alto en
   «Capturas/08 · Plano» y a 0.8154 en «Capturas/09 · Zona Elegida».
   Lo único que cambia es lo que hay DETRÁS de la tarjeta: en el plano, el gris
   del propio dibujo; aquí, el fondo de la app, porque la foto del cuarto es
   opaca y termina en negro. */
.zscene .plan-bleed { background: var(--qc-bg); }
/* El gris del plano, detrás de la tarjeta y sólo mientras la tarjeta ES el
   plano. Va DEBAJO del lienzo y debajo del degradado del pie, que es la razón
   de que no lleve z-index: cualquier número lo pondría por encima. */
.zfondo {
  position: absolute; inset: 0;
  background: #ededee;
  opacity: clamp(0, calc((.30 - var(--zg)) * 6.67), 1);
  pointer-events: none;
}
/* EL RENGLÓN DE LA PÍLDORA SE RESERVA AUNQUE NO SE VEA. En la app ese renglón
   existe en las dos pantallas —en el plano dice «Mis Garantías», en la zona
   «¿Qué está pasando? · Seleccionar problema»— y sin él la placa bajaba 72 px
   al abrir la zona. Es el MISMO elemento que el del plano, así que su altura no
   puede discrepar; lo que aquí no se enseña es su texto. */
.zscene .plan-pill { visibility: hidden; }
/* EL CROMO DE ARRIBA CAMBIA CUANDO CAMBIA LO QUE HAY DEBAJO, que es lo que hace
   la app: mientras el recorrido enseña el PLANO, la hora va en oscuro y no hay
   velo; cuando entra la fotografía, la hora pasa a blanca y un velo corto la
   deja leer sobre un cuarto clarísimo. Medido en «Capturas/C · Del Plano A La
   Foto.mp4»: oscura en los cuadros 01-04, blanca del 05 en adelante.
   `--zg` es el avance del recorrido (0 = el plano, 1 = la foto) y lo escribe JS
   en el mismo sitio donde mueve el reloj del video. Por defecto vale 1 —el
   estado en reposo es la foto— así que si alguien olvida escribirlo, lo que se
   ve es lo correcto y no un plano con la hora en blanco. */
.zscene { --zg: 1; }
.zscene .sb-foto,
.zscene::before { opacity: clamp(0, calc((var(--zg) - .14) * 5.56), 1); }
.zscene::before {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 16%; z-index: 2;
  background: linear-gradient(180deg, rgba(0,0,0,.42), rgba(0,0,0,0));
  pointer-events: none;
}
/* Tres elementos de video EN EL LIENZO, que es la caja del plano: los dos
   recorridos precargados de la demostración y uno perezoso para las otras cuatro
   zonas. Sólo se ve el de la zona abierta; los demás no ocupan ni pintan.
   `cover` aquí ya no recorta nada —la caja mide 941/1672 = 0.5628 y el mp4
   432/768 = 0.5625, 0.05 % de diferencia— y se queda por si un recorte futuro
   cambia una de las dos proporciones. */
.zscene .zvid {
  position: absolute; inset: 0; z-index: 1;
  width: 100%; height: 100%; object-fit: cover; display: block; cursor: pointer;
  opacity: 0; visibility: hidden;
}
.zscene.is-on .zvid.is-on { opacity: 1; visibility: visible; }
/* El estado quieto, en alta. Entra cuando el recorrido termina y se va en
   cuanto vuelve a correr: así nunca se ve un cuadro de 432 px ampliado. */
.zstill {
  position: absolute; inset: 0; z-index: 1;
  width: 100%; height: 100%; object-fit: cover; display: block; cursor: pointer;
  opacity: 0; transition: opacity .12s ease;
}
.zstill.is-on { opacity: 1; }
.zback {
  appearance: none; border: 0; background: none; cursor: pointer;
  padding: 0; line-height: 0;
}
/* El asta es fina; el área que responde al dedo no tiene por qué serlo. */
.zback::before { content: ""; position: absolute; inset: calc(var(--screen-w) * -.05); }

/* EL ANILLO DEL GALÓN MURIÓ AQUÍ el 15-sep, y con él `@keyframes sena-vuelta`.
   Era un aro amarillo dibujado DENTRO de la pantalla, encima del píxel de la
   app, latiendo en bucle: a 390 lo recortaba el canto redondeado, a 1440 se
   desbordaba sobre el bisel y partía el aparato. Victor: «esa animación era un
   overlay, no tenía que estar en el teléfono».
   La seña vive ahora FUERA del aparato y es del sitio: ver `.sena-vuelta`, al
   final de este bloque. Con ella no hace falta la excepción a la regla ① que
   este anillo necesitaba: no se anima, aparece porque el visitante abrió una
   zona y se va cuando usa el galón. */

/* La zona se dice DENTRO de la foto y sobre la MISMA placa que el plano: el
   estilo de la caja es `.plan-plate` y aquí sólo viven sus dos renglones.
   `.zscene-plate` era una segunda placa con las mismas reglas y otro anclaje
   —al pie de la PANTALLA en vez de al pie de la tarjeta—, y por eso saltaba 72
   px al abrir la zona. */
.zname { margin: 0; font-family: "QC Display", Georgia, serif; font-weight: 700; font-size: 1.45em; line-height: 1.05; letter-spacing: -.03em; color: #fff; }
.zwhere { margin: .35em 0 0; font-size: .8em; color: rgba(255,255,255,.82); }

/* ============ LA SEÑA DE VOLVER · EL SITIO HABLANDO, FUERA DEL APARATO ============

   Victor, 15-sep: «esa animación era un overlay, NO TENÍA QUE ESTAR EN EL
   TELÉFONO. Y lo único que debía de hacer era… una vez que picó un lugar,
   pudiera regresar, supiera que tiene que picar para regresar.»

   VIVE EN `.phone` Y NO EN `.phone-shell`, que es toda la diferencia: no puede
   pintarse encima del clip, no la recorta el canto de la pantalla y no le roba
   el clic a nada. Negro sobre blanco y con la tipografía del documento, porque
   esto NO lo dice la app: lo dice la propuesta.

   EL GALÓN QUE LLEVA ES EL MISMO SVG que el de la pantalla, sin el perfil
   oscuro —sobre papel blanco no hace falta— para que el ojo sepa a cuál de los
   dos se refiere la frase.

   Y NO SE ANIMA. Aparece porque el visitante abrió una zona con su dedo y se va
   cuando usa el galón una vez; el resto del tiempo no existe. Regla ① intacta,
   sin la excepción que necesitaba el anillo que sustituye. */
.sena-vuelta {
  position: absolute;
  z-index: 5;
  left: 0;
  /* Encima del aparato: por debajo de 900 px la página no tiene costados. */
  bottom: calc(100% + var(--phone-w) * .022);
  display: flex; align-items: center; gap: .42em;
  margin: 0;
  font-size: clamp(11px, calc(var(--phone-w) * .036), 15px);
  line-height: 1;
  letter-spacing: -.01em;
  color: var(--ink);
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity .22s ease;
}
.sena-vuelta.is-on { opacity: 1; }

/* ============ LA SEÑA DE BAJAR · LO MISMO, EN LA OTRA PUNTA ============

   HERMANA DE `.sena-vuelta` A PROPÓSITO: misma tinta negra, misma tipografía del
   documento, el mismo galón —girado— y una frase que dice qué hacer. La casa ya
   tenía este idioma; no se inventa otro. Tinta negra y no blanca porque el
   recorrido abre con el amarillo de marca a pantalla completa, con la Q en
   oscuro encima; desde que murió el velo, ese amarillo es el de verdad.

   NO SE ANIMA: ni `transition` ni `animation`. Su opacidad la escribe `paint()`
   en cada cuadro a partir del scroll y el nodo se quita al llegar a cero. El
   porqué y su medida, en main.js y en `hero.py`. */
.sena-bajar {
  position: absolute;
  z-index: 4;
  left: 50%;
  /* SE ANCLA A LO QUE SE VE, no al pie del pin: el pin mide `100lvh` y su pie
     queda 40 px POR DEBAJO del canto de la barra de Safari. `--vh-seguro` es
     `100svh` en píxeles, lo publica main.js, y con `translateY(-100%)` esta
     línea apoya su base ahí. Medido en el Simulador: con `bottom` sobre el pin
     la seña salía pegada al canto de la barra. */
  top: calc(var(--vh-seguro, 100vh) - 4.6vh - env(safe-area-inset-bottom, 0px));
  transform: translate(-50%, -100%);
  display: flex; flex-direction: column; align-items: center; gap: .55em;
  margin: 0;
  font-family: var(--doc);
  font-size: clamp(12px, 3.4vw, 15px);
  line-height: 1;
  letter-spacing: -.01em;
  color: var(--ink);
  white-space: nowrap;
  opacity: 0;              /* la escribe el scroll, no una animación */
  pointer-events: none;
}
.sena-bajar .chev { width: 1.35em; flex: 0 0 auto; }
.sena-bajar .chev-ink { stroke: var(--ink); stroke-width: 7; }

@media (min-width: 900px) {
  .sena-bajar {
    font-size: clamp(13px, 1.05vw, 16px);
    top: calc(var(--vh-seguro, 100vh) - 6vh);
    gap: .7em;
  }
  .sena-bajar .chev { width: 1.5em; }
}
.sena-vuelta .chev { width: .52em; flex: 0 0 auto; }
.sena-vuelta .chev-ink { stroke: var(--ink); stroke-width: 7; }

/* ================================ DESKTOP ================================ */

@media (min-width: 900px) {
  .hero-lede { font-size: clamp(72px, 7.2vw, 118px); }
  /* El titular cede el espacio: el sujeto de la página es el teléfono, y la
     pantalla de la app tiene que caber entera. Autorizado por Victor. */
  .point h2 { font-size: clamp(38px, 3.4vw, 52px); line-height: 1.06; }

  /* LA SEÑA SE PONE AL LADO Y AL RENGLÓN DEL GALÓN. Aquí sí hay página a los
     costados —500 px a 1440— así que la frase se coloca donde apunta y un
     filete la une con el canto del aparato. La altura sale de la misma cuenta
     que el galón: canto + 0.1882 del ancho de pantalla hasta su borde de
     arriba, más medio galón (su viewBox es 28x48, así que alto = ancho x 48/28
     y medio alto = ancho x .0199 del ancho de pantalla). */
  .sena-vuelta {
    left: auto;
    right: calc(100% + var(--phone-w) * .085);
    bottom: auto;
    top: calc(var(--bezel) + var(--screen-w) * .1882 + var(--screen-w) * .0199);
    transform: translateY(-50%);
    font-size: clamp(12px, 1.05vw, 16px);
  }
  .sena-vuelta::after {
    content: "";
    position: absolute;
    left: calc(100% + .7em);
    top: 50%;
    width: calc(var(--phone-w) * .085 - .7em);
    height: 1px;
    background: rgba(0, 0, 0, .32);   /* no `--hair`: #e8e8e8 no se ve, y un filete que apunta tiene que verse */
  }
}

/* ============================== MENOS MOVIMIENTO ============================== */

@media (prefers-reduced-motion: reduce) {
}

/* ============================================================================
   SEGUNDO MOVIMIENTO · La Propuesta Comercial

   LA MISMA PÁGINA, MÁS SERIA. No es otro sitio web: mismo negro sobre blanco,
   misma familia, misma escala. Si el titular de un punto mide 88 px, el de una
   sección comercial no mide 24.

   LO QUE AQUÍ ESTÁ PROHIBIDO, y se llama por su nombre porque ya se coló una
   vez (Victor, 14-sep: «AI slop»):
     · filetes verticales a la izquierda de citas, datos o notas
     · tarjetas con borde y esquina redondeada
     · box-shadow inset simulando borde
     · emoji como contenido de CSS
     · píldoras de radio 99px
     · fondos de color suave detrás de bloques de texto para «destacarlos»
     · rótulos en mayúsculas con tracking — la única excepción es NO SE PUBLICA,
       que es andamio y desaparece al imprimir
   La jerarquía se hace con TAMAÑO, PESO Y AIRE. Para separar, reglas
   horizontales finas. Nunca marcos.

   LOS HUECOS SIGUEN GRITANDO. Son trece decisiones de Victor y es lo que él
   viene a buscar. Amarillos, sí, pero como MARCA DE TEXTO: el amarillo vive
   sobre las letras, no en una caja alrededor. Se ven también impresos: un hueco
   invisible en papel es una decisión que se firma sin tomarla.
   ========================================================================== */

:root {
  --rule: #e8e8e8;            /* border.subtle de la marca */
  --rule-soft: #f2f2f2;
  /* EL ANDAMIO NO PUEDE SER AMARILLO. El amarillo ahora es la marca del
     cliente, y un pendiente pintado del color de la acción se lee como un
     acento de marca. El magenta no aparece en ningún otro lugar del sitio:
     si ves magenta, falta una decisión. Desaparece con ?limpio y al imprimir
     NO: un hueco invisible en papel es una decisión que se firma sin tomarla. */
  --gap-ink: #8e0060;
  --gap-bg: #ffdff1;
  --stop-ink: #8a1616;
  --stop-bg: #ffd2d2;
  --measure: 34em;
  --gutter: 24px;
}

/* ============================== LA FRONTERA ============================== */

/* LA FRONTERA VIVE EN LA MISMA COLUMNA QUE TODO LO DEMÁS. Estaba pegada al
   filo izquierdo con un titular de 108 px mientras el documento de abajo era
   una columna del ancho del teléfono, centrada: se leían como dos páginas. */
.numeros {
  /* `svh` Y NO `dvh`: con `dvh` esta pantalla CRECÍA 40 px en cuanto la barra
     de Safari se retiraba, y como debajo de ella cuelga TODO el documento
     comercial, el texto que se estaba leyendo saltaba esos 40 px bajo el dedo.
     `svh` es la ventana con la barra puesta —la que se ve seguro— y no cambia:
     la frontera cabe entera, y cuando la barra se va sobran 40 px de blanco,
     que en una pantalla de silencio no los nota nadie. */
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  padding: 16vh var(--gutter) 12vh;
  background: var(--paper);
}
.numeros > * { width: min(100%, var(--phone-w)); }
/* EL TITULAR DE LA FRONTERA NO CABE EN LA COLUMNA DEL TELÉFONO, y tampoco
   tiene por qué: es lo único de esta pantalla y la pantalla es suya. Con el
   ancho del aparato, «La Cotización del Módulo de Garantías.» se rompía en
   seis renglones de una palabra cada uno. Su medida es la del titular, no la
   del documento. */
.numeros-title { width: min(100%, 11em); }
/* EL TITULAR DE LA FRONTERA, al tamaño de los del primer movimiento y centrado
   como ellos: es la misma página, y el cambio de registro no puede empezar con
   una caída de energía (Victor, 14-sep). En desktop crece como crece todo lo
   demás del segundo movimiento. */
.numeros-title {
  margin: 0;
  text-align: center;
  font-size: clamp(34px, 9.6vw, 50px);
  font-weight: 700;
  line-height: 1.07;
  letter-spacing: -.042em;
}

/* ================================ LA BARRA ================================
   Sólo existe dentro del segundo movimiento. Arriba mandan el pin y el
   teléfono, y una barra flotando sobre el hero sería otra página.

   YA NO LLEVA BOTÓN DE ÍNDICE NI CAJÓN. El cajón era una lista de saltos, y
   saltar es justo lo que Victor retiró: «cuando pique uno no quiero que me
   haga scroll hacia un lugar». El índice es el documento, así que lo único
   que queda arriba es dónde estás, cuánto llevas y cómo volver. */

.bar { opacity: 0; pointer-events: none; visibility: hidden; }
body.is-comercial .bar { opacity: 1; pointer-events: auto; visibility: visible; }

.bar {
  position: fixed; top: 0; left: 0; right: 0; z-index: 40;
  display: flex; align-items: center; gap: 12px;
  padding: 10px var(--gutter);
  /* Opaca, no translúcida: un documento largo pasa texto por debajo todo el
     rato, y medio renglón fantasma se lee como un error de impresión. */
  background: var(--paper);
  border-bottom: 1px solid var(--rule);
  transition: opacity .25s ease;
}
.bar-home { display: flex; align-items: center; gap: .55em; color: var(--muted); text-decoration: none; font-size: .8rem; font-weight: 500; white-space: nowrap; }
.bar-logo { display: block; height: 15px; width: auto; }
.bar-where {
  flex: 1; min-width: 0;
  font-size: .82rem; font-weight: 600; letter-spacing: -.01em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Cuánto llevas leído del documento. Dos píxeles: saber, no decorar. */
/* La barra de progreso es el único amarillo del documento, y la marca lo pide
   por su nombre: «una línea amarilla breve en progreso o cambio de sección». */
.bar-progress { position: absolute; left: 0; bottom: -1px; height: 2px; background: var(--brand); width: 0; }

/* ============================== EL DOCUMENTO ==============================

   EL SEGUNDO MOVIMIENTO TIENE LA FORMA DEL PRIMERO (Victor, 14-sep: «en la
   propuesta le estás quitando el formato de iPhone que veníamos siguiendo,
   todo me lo pones como de iPhone»).

   Y no es una metáfora: la columna del documento MIDE LO QUE MIDE EL TELÉFONO,
   `--phone-w`, la misma variable que JS escribe en fit() para el aparato del
   primer movimiento, y va centrada en el mismo sitio. Así la página no cambia
   de anchura ni de encuadre al pasar la frontera: es el mismo documento, más
   serio. Antes el texto se abría a 46rem con un índice fijo a la izquierda y
   se leía como OTRO sitio web.

   Efecto lateral bueno y buscado: a esa anchura la medida de lectura queda en
   unos 52 caracteres en los dos extremos, y las tablas se leen como fichas
   —igual en un teléfono que en un monitor—, así que hay UNA maqueta y no dos.

   17/25/400 con letter-spacing -0.374px, medido en apple.com/maps. */
.page { padding: 0 var(--gutter); font-size: 17px; line-height: 1.47; letter-spacing: -.022em; }
.wrap { width: min(100%, var(--phone-w)); margin: 0 auto; }

/* EL ENCABEZADO DE DOS LÍNEAS, que es el que Apple usa en toda su página:
   dos líneas del MISMO tamaño y peso, una encima de la otra —la categoría en
   gris, la afirmación en negro—. El gris hace el trabajo de un rótulo sin
   serlo, y por eso aquí no hay etiqueta en mayúsculas, ni regla, ni caja.
   32/36/700 a 390 px, alineado a la izquierda. */
/* CADA SECCIÓN ES UNA FILA, separada por una regla fina de un píxel — el
   patrón de la sección de detalle de apple.com/maps, que es el que Victor
   mandó traerse. Cero cajas y cero tarjetas: una regla no es una caja. */
.sec { padding: 4.5vh 0; border-bottom: 1px solid var(--rule); scroll-margin-top: 58px; }
.sec:first-of-type { border-top: 1px solid var(--rule); }
.sec-h { margin: 0 0 .8em; }
/* LA LÍNEA DE ÍNDICE: el número a la izquierda y el signo de abrir al filo
   derecho de la columna, como el número de página de un índice impreso. El
   nombre va debajo, en la segunda línea, que es el encabezado de dos líneas de
   siempre. Sin caja, sin filete y sin fondo: es un <button> desnudo. */
.sec-btn {
  display: grid; width: 100%;
  grid-template-columns: 1fr auto;
  align-items: center;
  margin: 0; padding: 0; border: 0; background: none;
  color: inherit; font: inherit; text-align: left; cursor: pointer;
  -webkit-appearance: none; appearance: none;
}
.sec-titulo { grid-area: 1 / 1; max-width: 15em; }
.sec-mas { grid-area: 1 / 2; justify-self: end; align-self: start; }
.sec-btn i { grid-area: 2 / 2; justify-self: end; align-self: start; margin-top: .4em; }
/* El número y el nombre son UNA línea, no dos: el salto lo decide el ancho. */
.sec-n, .sec-name {
  display: inline;
  font-size: clamp(28px, 8.2vw, 34px);
  font-weight: 700;
  line-height: 1.125;
  letter-spacing: -.032em;
}
.sec-n { color: var(--muted); font-variant-numeric: tabular-nums; }
.sec-titulo { display: block; }

/* EL SIGNO DE QUE SE ABRE. Una cruz que al abrirse se vuelve una raya: dos
   trazos de una línea, sin caja, sin filete y sin círculo. Es lo más pequeño
   que puede decir «esto se abre» sin añadir un objeto a la página. */
.sec-mas {
  display: block; position: relative;
  width: .62em; height: .62em;
  font-size: clamp(28px, 8.2vw, 34px);
  color: var(--muted);
}
.sec-mas::before, .sec-mas::after {
  content: ""; position: absolute; left: 0; top: calc(50% - .5px);
  width: 100%; height: 1.5px; background: currentColor;
}
.sec-mas::after { transform: rotate(90deg); }
.sec.is-open .sec-mas::after { transform: none; }
.sec.is-open .sec-mas { color: var(--ink); }
/* El hueco de Victor se ve DESDE EL ÍNDICE, sin abrir la sección. */
.sec-btn i { width: 1.1em; height: .62em; background: var(--gap-ink); }

/* LO QUE SE PLIEGA, Y LO QUE NO. Fuera del pliegue quedan el número, el
   nombre y la primera frase: el índice cerrado se lee entero y dice de qué va
   cada sección. Nada de esto se anima — editorial no es animado (Victor,
   14-sep); abre y ya está. */
.sec-body { display: none; }
.sec.is-open > .sec-body { display: block; }

/* LA PRIMERA FRASE PESA MÁS: se recorre la propuesta leyendo sólo éstas. */
/* 19/23/600 — la medida del subtítulo de apple.com/maps. Sigue siendo la
   primera frase con más peso: se recorre la propuesta leyendo sólo éstas. */
.lede {
  margin: 0 0 1.4em; max-width: 25em;
  font-size: clamp(19px, 4.9vw, 21px); font-weight: 600;
  line-height: 1.21; letter-spacing: -.024em;
}

/* Subsección. Mucho más chica que la sección: la distancia entre niveles es
   lo que los hace legibles. */
/* EL PELDAÑO DE EN MEDIO. Estaba a la misma altura que la primera frase —los
   dos daban 30 px en desktop— y una escalera con dos escalones iguales no es
   una escalera: entre el nombre de la sección y el cuerpo no había nada que
   pisar. Ahora la subsección manda sobre la primera frase, como debe. */
.sub { margin: 3.2em 0 .7em; font-size: clamp(24px, 6.2vw, 28px); font-weight: 700; line-height: 1.18; letter-spacing: -.028em; }
.sub-n { color: var(--muted); font-variant-numeric: tabular-nums; }

.sec p, .sec li { max-width: var(--measure); }
.sec p { margin: 0 0 1.15em; }
.sec strong { font-weight: 600; }
.sec ul { margin: 0 0 1.4em; padding-left: 1.1em; }
.sec li { margin-bottom: .5em; }

/* LAS CITAS SE FUERON, Y CON ELLAS EL PAR CITA + RESPUESTA (Victor, 14-sep).
   Ya no hay `.said`, ni `blockquote`, ni `cite`: la respuesta de Casaporte, que
   es alcance y el contrato la necesita, quedó como cuerpo. Si alguien vuelve a
   meter una cita, que no la meta en una caja. */

/* ========================== EL DATO ES DATO ==========================
   Cifras, plazos, límites y precios no se entierran en un párrafo ni se meten
   en una caja: ocupan su propio espacio, a tamaño grande. */

.fact { display: block; margin: 3em 0; break-inside: avoid; }
.fact b {
  display: block;
  font-size: clamp(40px, 12vw, 68px); font-weight: 700;
  line-height: .98; letter-spacing: -.042em;
  font-variant-numeric: tabular-nums;
}
.fact span { display: block; margin-top: .5em; max-width: 24em; font-size: 1em; line-height: 1.45; color: var(--muted); }


/* ===================== LOS TRES PLANES · EL ÚNICO SITIO CON CAJAS =====================
   Victor, 14-sep: «en la inversión ponle más amor. Ponme como tres planes, ya
   sabes, que te sale como un tríptico, y que diga qué incluye cada uno y qué
   no, como cualquier servicio que compras».

   Y AQUÍ SÍ HAY CAJAS, autorizado por escrito y es la ÚNICA excepción en todo
   el sitio a la regla de cero tarjetas: una página de precios sin columnas
   separadas no se lee. Son sobrias a propósito —una regla fina, nada de
   sombras, nada de esquinas gordas, nada de degradados— y el que separa de
   verdad es el aire de dentro.

   NO HAY COLUMNA DESTACADA, y es una decisión: destacar una es recomendarla, y
   esta propuesta no recomienda ninguna. Si Victor quiere recomendar, se marca
   la que él diga.

   El dato manda la maqueta: lo que no entra en un plan se DICE, apagado y
   tachado, en vez de faltar. Un renglón apagado informa; un hueco, no. */
.planes {
  display: grid;
  gap: 1.4em;
  margin: 2.5em 0 3em;
}
.plan {
  padding: 1.6em 1.4em 1.8em;
  border: 1px solid var(--rule);
  border-radius: 6px;
  break-inside: avoid;
}
/* EL PRECIO ES EL DATO, Y DESDE EL 15-SEP ES TAMBIÉN EL NOMBRE DE LA COLUMNA
   (Victor: «lo que sale hasta arriba lo tienes que poner abajo»). Encima del
   precio había un rótulo —«Producido en 3D», «Con Sus Fotografías»— que repetía
   el renglón de las fotografías y, al repetirlo, aplanaba la diferencia entre
   la opción de 497 y la de 297. Murió el rótulo, y con él `.plan-nombre`. */
.plan-precio {
  margin: 0 0 1.2em;
  font-size: clamp(34px, 9vw, 46px); font-weight: 700;
  line-height: 1; letter-spacing: -.04em;
  font-variant-numeric: tabular-nums;
}
.plan-precio span {
  display: block; margin-top: .5em;
  /* `em` aquí es del PRECIO, que es enorme; hay que bajar mucho más para que
     el «más IVA» sea el escalón de nota y no un segundo precio. */
  font-size: .34em; font-weight: 400; letter-spacing: -.01em;
  color: var(--muted);
}
/* Lo que dice la palomita, en palabras, para quien no la ve. */
.solo-voz {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.plan-lista { margin: 0; padding: 0; list-style: none; }
.plan-lista li {
  margin: 0 0 .85em;
  padding-left: 1.15em;
  position: relative;
  font-size: 1em; line-height: 1.38;
  max-width: none;
}
/* La palomita y la raya son un carácter de texto, no un icono: escalan con la
   letra y no hay un archivo más que bajar. */
.plan-lista li::before {
  content: "✓";
  position: absolute; left: 0; top: 0;
  font-weight: 700;
}
.plan-lista li span {
  display: block; margin-top: .2em;
  font-size: .85em; line-height: 1.3; color: var(--muted);
}
.plan-no { color: var(--muted); text-decoration: line-through; text-decoration-thickness: 1px; }
.plan-lista .plan-no::before { content: "—"; text-decoration: none; font-weight: 400; }

@media (min-width: 900px) {
  .planes { grid-template-columns: repeat(3, 1fr); gap: 1.6em; }
  .plan { padding: 2em 1.7em 2.2em; }
  .plan-precio { font-size: clamp(32px, 3vw, 44px); }
  .plan-lista li { font-size: 1.02em; }
}

@media print {
  .planes { grid-template-columns: repeat(3, 1fr); gap: 4mm; }
  .plan { padding: 4mm; border: .5pt solid #b9b9bd; }
  .plan-precio { font-size: 16pt; }
}

/* ============================== EL HUECO ==============================
   TRECE, Y TIENEN QUE GRITAR. El amarillo va sobre las letras. */

.gap {
  background: var(--gap-bg);
  box-decoration-break: clone; -webkit-box-decoration-break: clone;
  padding: .06em .3em;
  color: var(--gap-ink); font-weight: 700; letter-spacing: -.01em;
  -webkit-print-color-adjust: exact; print-color-adjust: exact;
}

/* El hueco que ocupa un bloque entero: una decisión, no una palabra. El
   amarillo marca su rótulo; el cuerpo va en su tinta, sin caja. */
.gap-block { margin: 3.4em 0; break-inside: avoid; }
.gap-block h4 {
  display: inline-block; margin: 0 0 .7em; padding: .06em .35em;
  background: var(--gap-bg); color: var(--gap-ink);
  font-size: 1.1em; font-weight: 700; letter-spacing: -.024em;
  -webkit-print-color-adjust: exact; print-color-adjust: exact;
}
.gap-block p { margin: 0; max-width: 30em; color: var(--gap-ink); font-weight: 600; }
.gap-block p + p { margin-top: .7em; font-weight: 400; }

/* ============================== LAS TABLAS ==============================
   Diecisiete, y son el corazón. Objetos diseñados: reglas finas, cifra
   tabular, y en móvil FICHAS separadas por una regla — nunca marcos, y jamás
   scroll horizontal de la página. */

.tw { margin: 3em 0; max-width: 46rem; }
.tw > figcaption { margin: 0 0 1em; font-size: 1.15em; font-weight: 600; letter-spacing: -.024em; color: var(--muted); }

/* ============ LA JERARQUÍA DE LAS TABLAS, Y EL BUG QUE LA ROMPÍA ============
   Victor, 14-sep: «dale muy bien jerarquía de texto: el cambio de font es
   enorme de títulos a estas tablitas, donde el texto es como un 10 % de un
   título».

   LA CAUSA ERA UNA UNIDAD, no un criterio. Todo esto estaba en `rem`, que mide
   contra la RAÍZ —16 px— y no contra el documento, que va a 17 en móvil y a 20
   en desktop. Resultado: las tablas se pintaban a 15.2 px mientras el cuerpo
   iba a 20, un 24 % MÁS CHICAS que el texto que las rodea, y el título de cada
   ficha a 18.4 px, o sea más chico que el cuerpo. El precio se veía más
   pequeño que su encabezado, que es justo lo que Victor señaló.

   AHORA TODO VA EN `em`, que mide contra `.page`: la tabla crece cuando crece
   el documento y la escala no se puede descolgar por cambiar un tamaño en otro
   sitio. Y es contenido, no pie de página: la tabla ES el cuerpo —1em—, el
   título de cada ficha sube un peldaño —1.25em— y el rótulo de cada celda baja
   al escalón de nota —0.8em—. La vara sigue siendo la de apple.com/maps:
   titular, encabezado, cuerpo, nota. */
.page table { width: 100%; border-collapse: collapse; font-size: 1em; line-height: 1.45; }
.page thead th {
  padding: 0 .9em .6em 0; text-align: left; vertical-align: bottom;
  border-bottom: 1.5px solid var(--ink);
  font-size: .8em; font-weight: 600; color: var(--muted);
}
.page tbody td, .page tbody th {
  padding: 1em .9em 1em 0; text-align: left; vertical-align: top;
  border-bottom: 1px solid var(--rule-soft);
}
.page tbody th { font-weight: 600; }
.page tbody tr:last-child td, .page tbody tr:last-child th { border-bottom: 1px solid var(--rule); }
.page th:last-child, .page td:last-child { padding-right: 0; }
.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.ix { width: 2.4em; color: var(--muted); font-variant-numeric: tabular-nums; font-weight: 600; }
.page tr.tot td, .page tr.tot th { border-top: 1.5px solid var(--ink); border-bottom: none; font-weight: 700; }

/* --------------------------- LAS TABLAS SON FICHAS ---------------------------
   Cada fila se lee entera sin mover el dedo de lado. Requiere `data-l`.
   YA NO ES UN CASO DE MÓVIL: la columna del documento mide lo que el teléfono
   en todas las anchuras, así que ésta es LA maqueta de tabla del sitio, y no
   una versión reducida de otra. Se acabaron las dos tablas por tabla. */
.tw table, .tw thead, .tw tbody, .tw tr, .tw th, .tw td { display: block; width: auto; }
.tw thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.tw tbody tr { padding: 1.4em 0; border-bottom: 1px solid var(--rule); }
.tw tbody tr:last-child { border-bottom: 1.5px solid var(--ink); }
.tw tbody td, .tw tbody th { padding: .22em 0; border: none; text-align: left; max-width: none; }
.tw tbody tr > *:first-child { font-size: 1.25em; font-weight: 700; letter-spacing: -.025em; line-height: 1.22; }
.tw tbody td[data-l]::before, .tw tbody th[data-l]::before {
  content: attr(data-l); display: block; margin-bottom: .05em;
  font-size: .8rem; font-weight: 500; color: var(--muted);
}
.tw tbody tr > *:first-child[data-l]::before { margin-bottom: .2em; }
.tw .num { text-align: left; }
/* Cuando la primera columna es el contador —#, Pago, Hito—, el número NO es
   el título de la ficha: el título es la celda que sigue. */
.tw .ix { width: auto; }
.tw tbody tr > .ix:first-child { font-size: .85em; font-weight: 600; color: var(--muted); }
.tw tbody tr > .ix:first-child::before { display: inline; margin: 0 .4em 0 0; }
.tw tbody tr > .ix:first-child + * { font-size: 1.25em; font-weight: 700; letter-spacing: -.025em; line-height: 1.22; }
.tw tr.tot { border-top: 1.5px solid var(--ink); }

/* ================= EL RESPIRO ENTRE BLOQUES =================
   Victor, 14-sep: «no hay animaciones, no hay nada, nada más bloques de texto…
   mira qué podemos poner entre los bloques para hacerlo más light y bonito».
   Dos recursos y ninguno cuesta un byte nuevo; quién va dónde lo decide
   render.py, que es donde vive el guion del documento.

   LA PALABRA, enorme y sola sobre el fondo. Ni bajada, ni línea de apoyo, ni
   caja: es un respiro que además parte el documento en capítulos. Es lo más
   grande de esta mitad a propósito —más que los titulares de sección—, porque
   su trabajo es dejar respirar, no competir. */
.respiro {
  margin: 14vh 0 10vh;
  font-size: clamp(56px, 17vw, 96px);
  font-weight: 700;
  line-height: .95;
  letter-spacing: -.045em;
}

/* LA LÁMINA. Se llama así y no `media` porque el hero ya tiene un `.media`, y
   dos cosas distintas con el mismo nombre es un bug esperando su turno.
   A SANGRE para una fotografía —es lo que hace apple.com/maps y es lo que le
   da aire al documento—, y CONTENIDA para el plano, porque un plano recortado
   por los cuatro lados deja de ser un plano. */
.lamina { margin: 0 0 12vh; }
.lamina img { display: block; width: 100%; }
.lamina-sangre {
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
  width: 100vw;
}
.lamina-sangre img { height: clamp(260px, 52vh, 560px); object-fit: cover; }
.lamina-planta img { height: clamp(320px, 62vh, 620px); object-fit: contain; }

/* ===================== EN DESKTOP SE ESCRIBE BIEN =====================
   Victor, 14-sep, y es la corrección más dura que recibió esta mitad: «en
   iPhone me lo vas a hacer tamaño de iPhone, pero en desktop sí me lo escribes
   bien… lo hiciste muy conscripted».

   El defecto era de diseño, no de texto: la columna medía lo que el teléfono en
   TODAS las anchuras, así que en un monitor el documento se leía apretado justo
   después de un primer movimiento enorme. A 390 px la medida de teléfono es la
   correcta; a 1440 no lo es.

   Así que la columna y la escala CRECEN con la pantalla. Lo que NO cambia es el
   encuadre —centrada, sin índice lateral, sin cajas— ni el comportamiento: las
   filas se abren en sitio en las dos. */
@media (min-width: 900px) {
  .wrap { width: min(100%, 52rem); }
  .page { font-size: 20px; }
  .sec { padding: 6vh 0; }
  .sec-n, .sec-name, .sec-mas { font-size: clamp(44px, 4.2vw, 68px); line-height: 1.06; }
  .respiro { font-size: clamp(96px, 9.5vw, 148px); margin: 18vh 0 12vh; }
  .lede { font-size: clamp(24px, 2.1vw, 30px); max-width: 24em; margin-bottom: 1em; }
  .sub { font-size: clamp(30px, 2.7vw, 38px); }
  .fact b { font-size: clamp(52px, 5vw, 84px); }
  .numeros-title { font-size: clamp(56px, 5.4vw, 92px); letter-spacing: -.045em; }
  .bar { padding-left: 4vw; padding-right: 4vw; }
}

/* ================================ IMPRESIÓN ================================
   Hoja carta. Va del director de tecnología a su jefe, así que: sin fondos que
   se coman el tóner, sin tablas partidas, sin andamio, sin el primer
   movimiento —que es pantalla, no papel— y los huecos SIGUEN VIÉNDOSE. */

@media print {
  @page { size: letter; margin: 18mm 16mm; }
  html { scroll-behavior: auto; }
  body { font-size: 10.5pt; line-height: 1.45; background: #fff; }
  .hero, .plano, #act1, #act2, #salida, .stage, .bar, .lamina { display: none !important; }
  /* EN PAPEL NADA ESTÁ PLEGADO. El índice que se abre es de pantalla; una hoja
     con veintitrés secciones cerradas no es una propuesta, es una portada. */
  .sec-body { display: block !important; }
  .sec-mas { display: none !important; }
  .sec-btn { cursor: auto; }
  .numeros { min-height: 0; padding: 0 0 8mm; border-bottom: 1.5pt solid #000; display: block; }
  .numeros-title { font-size: 30pt; }
  .page { padding: 0; font-size: 10.5pt; line-height: 1.45; }
  .wrap { width: auto; }
  :root { --measure: 40em; }

  .sec { padding: 8mm 0 0; break-inside: auto; border: none; }
  /* La palabra de capítulo abre página en papel: es lo que hace en pantalla. */
  .respiro { margin: 0 0 6mm; font-size: 26pt; break-before: page; break-after: avoid; }
  .sec-h, .sec-n, .sec-name, .sub { break-after: avoid; break-inside: avoid; }
  /* La línea de tiempo se imprime COMPLETA: en papel no hay scroll que la
     recorra, así que sus tres variables se quedan en su estado final. */
  .cronos-paradas li { --a: 1 !important; --t: 1 !important; --s: 1 !important; break-inside: avoid; }
  .sec-n, .sec-name { font-size: 17pt; max-width: none; }
  .lede { font-size: 12pt; break-after: avoid; }
  .sub { font-size: 12.5pt; }
  .fact { margin: 5mm 0; break-inside: avoid; }
  .fact b { font-size: 18pt; }
  .gap-block { margin: 5mm 0; padding-top: 2mm; }
  p, li { orphans: 3; widows: 3; }

  /* EN PAPEL SON TABLAS, NO FICHAS. El ancho útil de una carta cae dentro del
     umbral de móvil; sin esto, diecisiete tablas salen como doscientas fichas. */
  .tw table { display: table; width: 100%; }
  .tw thead { display: table-header-group; position: static; width: auto; height: auto; clip: auto; }
  .tw tbody { display: table-row-group; }
  .tw tr { display: table-row; }
  .tw th, .tw td { display: table-cell; }
  .tw tbody tr { margin: 0; padding: 0; border: none; }
  .tw tbody td, .tw tbody th { padding: .5em .7em .5em 0; border-bottom: .5pt solid #d8d8dc; }
  .tw tbody td[data-l]::before, .tw tbody th[data-l]::before { content: none; }
  .tw tbody tr > *:first-child { font-size: inherit; font-weight: 600; letter-spacing: 0; }
  .tw .num { text-align: right; }
  .tw .ix { width: 2.2em; }
  .tw tbody tr > .ix:first-child { font-size: inherit; font-weight: 600; color: #000; }
  .tw tbody tr > .ix:first-child + * { font-size: inherit; font-weight: 400; letter-spacing: 0; }

  .tw { break-inside: avoid; }
  .page table { font-size: 9.5pt; }
  thead { display: table-header-group; }
  tr { break-inside: avoid; }
  .gap, .gap-block h4 { -webkit-print-color-adjust: exact; print-color-adjust: exact; }

  a { color: inherit; text-decoration: none; }
}

/* ================= EL SEGUNDO MOVIMIENTO, ESTILO APPLE MAPS =================
   Victor, 14-sep (noche): «si te inspiras en apple.com/maps, ¿qué metemos
   después de los iPhones?». Cuatro piezas, ninguna es una caja, todas sobre
   blanco: LA CIFRA gigante (20 desarrollos / 92 tipologías, y los `.fact` que
   ya existían), LO QUE NECESITAMOS como seis iconos con tres palabras, LOS
   HITOS como una línea de tiempo con seis puntos, y EL CIERRE como un enlace
   grande al correo. Quién genera cada una es render.py, por papel de sección. */

/* La cifra: el número enorme y su palabra debajo, en gris. Dos seguidas se
   apilan sin aire entre ellas, como una sola cifra de dos renglones. */
.cifra { margin: .4em 0 0; line-height: 1; max-width: none; }
.cifra + .cifra { margin-top: .35em; }
.cifra b {
  display: block; font-size: clamp(72px, 21vw, 150px); font-weight: 700;
  letter-spacing: -.05em; line-height: .95; font-variant-numeric: tabular-nums;
}
.cifra span { display: block; margin-top: .18em; font-size: clamp(19px, 4.9vw, 24px); font-weight: 600; color: var(--muted); letter-spacing: -.02em; }
.cifra + p:not(.cifra) { margin-top: 1.8em; }

/* Lo que necesitamos: seis renglones con icono, sin caja. En desktop, tres
   columnas; en teléfono, uno debajo del otro. El número sigue ahí, en gris,
   porque el fuente lo tiene y comparar.py lo busca. */
.necesita { list-style: none; margin: 2.2em 0 2.6em; padding: 0; display: grid; gap: 2.2em 2.6em; grid-template-columns: 1fr; max-width: none; }
.necesita li { margin: 0; max-width: none; }
.nec-ico { display: block; width: 2.3em; height: 2.3em; margin: 0 0 .75em; }
.nec-ico svg { display: block; width: 100%; height: 100%; fill: none; stroke: var(--ink); stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; }
.nec-n { display: block; margin: 0 0 .25em; font-size: .78em; font-weight: 600; color: var(--muted); font-variant-numeric: tabular-nums; }
.nec-q { margin: 0 0 .45em; font-size: 1.06em; font-weight: 600; letter-spacing: -.016em; line-height: 1.35; }
.nec-c { margin: 0; font-size: .9em; line-height: 1.4; color: var(--muted); }
@media (min-width: 900px) { .necesita { grid-template-columns: repeat(3, 1fr); } }

/* ============ LA LÍNEA DE TIEMPO DE §3 · EL TIEMPO, RECORRIDO ============
   Victor, 15-sep: «hay que poner un inciso de timeline, y hay que hacer una
   animación chingona de timeline».

   QUÉ ENSEÑA, que es lo que la justifica: que las revisiones son constantes
   —la línea son semanas ticadas, no un trazo liso—, que la semana 3 cae pronto
   y que lo que queda después es corto. Si se pudiera quitar sin perder eso,
   sobraría.

   LAS DOS REGLAS DE LA CASA, aquí: todo el movimiento lo manda el scroll —esta
   hoja no tiene una sola `animation` ni `transition`, sólo lee las tres
   variables que escribe main.js— y se mueve UNA COSA A LA VEZ: llega la parada
   (`--a` el punto, `--t` sus palabras) y sólo cuando terminó avanza el tiempo
   hacia la siguiente (`--s`). Las ventanas están en `cronosPaint()`.

   LOS VALORES POR DEFECTO SON 1, Y NO ES UN DESCUIDO: sin JS —o en papel, o con
   `prefers-reduced-motion`— la línea se ve entera y el texto se lee completo. Lo
   que la animación añade es el orden en que llega, nunca la información. */
.cronos { margin: 1.6em 0 .4em; padding: 0; }
.cronos-paradas { list-style: none; margin: 0; padding: 0; max-width: none; }
.cronos-paradas li {
  --a: 1; --t: 1; --s: 1;
  position: relative; margin: 0; padding: 0 0 2.1em 2.5em; max-width: none;
}
.cronos-punto {
  position: absolute; left: .22em; top: .3em;
  width: .8em; height: .8em; border-radius: 50%;
  background: var(--ink);
  transform: scale(var(--a));
}
/* EL TIEMPO SE DIBUJA TICADO, y ahí está el dato: son semanas, no un trazo. Una
   línea lisa diría «pasa el tiempo»; ésta dice «pasan semanas, y en cada una
   hay revisión». Es un degradado repetido — cero bytes de red. */
.cronos-paradas li::before {
  content: ""; position: absolute; left: .58em; top: 1.35em; bottom: .15em; width: 1.5px;
  background: repeating-linear-gradient(180deg, var(--ink) 0 3px, transparent 3px 8px);
  opacity: .55;
  transform: scaleY(var(--s)); transform-origin: 50% 0;
}
.cronos-paradas li:last-child::before { display: none; }
.cronos-cuando {
  display: block;
  font-size: 1.2em; font-weight: 700; letter-spacing: -.022em; line-height: 1.2;
  opacity: var(--t); transform: translateY(calc((1 - var(--t)) * .5em));
}
.cronos-que {
  display: block; margin-top: .35em; line-height: 1.42; color: var(--muted);
  opacity: var(--t); transform: translateY(calc((1 - var(--t)) * .5em));
}
/* En desktop la línea es horizontal, como la de los hitos. Las columnas se
   cuentan solas: si el fuente añade una parada, entra sin tocar esta hoja. */
@media (min-width: 900px) {
  .cronos { margin: 1.8em 0 .6em; }
  .cronos-paradas { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 0 1.6em; }
  .cronos-paradas li { padding: 2.1em 0 0; }
  .cronos-punto { left: 0; top: .5em; }
  .cronos-paradas li::before {
    left: 1.1em; right: -1.6em; top: .87em; bottom: auto; width: auto; height: 1.5px;
    background: repeating-linear-gradient(90deg, var(--ink) 0 3px, transparent 3px 8px);
    transform: scaleX(var(--s)); transform-origin: 0 50%;
  }
  .cronos-cuando { font-size: 1.14em; }
  .cronos-que { font-size: .92em; }
}

/* Los hitos: una línea con seis puntos. Vertical en teléfono, horizontal en
   desktop. El punto negro con el número es un punto, no una caja. */
.hitos { list-style: none; margin: 2.4em 0 2.6em; padding: 0; position: relative; max-width: none; }
.hitos li { position: relative; margin: 0; padding: 0 0 2em 2.5em; max-width: none; }
.hitos li::before { content: ""; position: absolute; left: .62em; top: .5em; bottom: -.5em; width: 1.5px; background: var(--rule); }
.hitos li:last-child::before { display: none; }
.hito-n {
  position: absolute; left: 0; top: .05em; width: 1.3em; height: 1.3em; border-radius: 50%;
  background: var(--ink); color: #fff; font-size: .95em; font-weight: 700; line-height: 1.3em;
  text-align: center; font-variant-numeric: tabular-nums;
}
.hito-t { display: block; font-size: 1.2em; font-weight: 700; letter-spacing: -.022em; line-height: 1.2; }
.hito-d { display: block; margin-top: .35em; line-height: 1.42; color: var(--muted); }
@media (min-width: 900px) {
  .hitos { display: grid; grid-template-columns: repeat(6, 1fr); gap: 0 1.2em; }
  .hitos li { padding: 2.3em 0 0; }
  .hitos li::before { left: 0; right: -1.2em; top: .6em; bottom: auto; width: auto; height: 1.5px; }
  .hitos li:last-child::before { display: block; right: 0; }
  .hito-n { top: 0; }
  .hito-t { font-size: 1.02em; }
  .hito-d { font-size: .88em; }
}

/* El cierre: el correo como un enlace grande, subrayado a mano, sin botón. */
.cta { margin: .4em 0 0; font-size: clamp(22px, 5.8vw, 34px); font-weight: 700; letter-spacing: -.026em; line-height: 1.2; max-width: none; }
.cta a { color: var(--ink); text-decoration: none; border-bottom: 2px solid var(--ink); }
.cta a:hover { border-bottom-color: var(--muted); }

/* Los anexos son letra chica: un peldaño abajo en la cabecera. */
.sec[id^="a"] .sec-n, .sec[id^="a"] .sec-name, .sec[id^="a"] .sec-mas { font-size: clamp(22px, 6.2vw, 26px); }

/* El dato largo, un peldaño abajo, y las cifras con % no se parten. */
.fact-larga b { font-size: clamp(34px, 9.5vw, 52px); }
.fact b { text-wrap: balance; }

/* EL CORTE ENTRE CAPÍTULOS ES LA PANTALLA DE LA APP, ENTERA Y PEQUEÑA (Victor,
   14-sep noche: las láminas sangradas «no van, y cortadas así están súper
   pendejas»). La captura completa, sobre blanco, centrada, del tamaño de una
   tarjeta y con la esquina de una pantalla. No lleva marco de aparato: el sitio
   tiene UN teléfono y está arriba. Medidas en em contra .page, como todo aquí. */
.lamina-captura { display: flex; justify-content: center; margin: 9vh 0 7vh; }
/* `aspect-ratio` NO ES COSMÉTICA: ES LA CAJA RESERVADA, y sin ella esta imagen
   ERA el texto que se meneaba.

   MEDIDO EL 15-sep EN EL SIMULADOR (iPhone 17 Pro, iOS 26.5), bajando el
   documento de una vez: las tres capturas del segundo movimiento llevan
   `loading="lazy"` y no traen `width`/`height`, así que hasta que la imagen
   aterriza el `<img>` MIDE CERO DE ALTO. Al aterrizar, el documento crece de
   golpe y todo lo que va debajo baja bajo el dedo de quien lee: la sección 1 se
   desplazó 581.8 px, la 4 llevaba 1,123.7 acumulados y el Anexo B 1,642.6. Eso
   —más que la barra del navegador— es lo que Victor vio moverse.

   Las tres son pantallazos de iPhone 17 Pro, 1206×2622: la proporción es un
   hecho del archivo, no un número a ojo. `lamina-sangre` y `lamina-planta` no lo
   necesitan, las dos declaran su alto. Si entra una captura con otra
   proporción, esta regla se parte por imagen: una caja reservada con la
   proporción equivocada vuelve a mover el texto, sólo que menos. */
.lamina-captura img {
  width: min(62vw, 15.5em);
  aspect-ratio: 1206 / 2622;
  height: auto;
  border-radius: clamp(20px, 6.4vw, 30px);
}
@media (min-width: 900px) { .lamina-captura { margin: 12vh 0 9vh; } }

/* PRUEBA A/B · el logotipo como firma encima del nombre (14-sep noche). Oculto
   salvo con body.has-logo (?logo=1). Un tercio escaso del cuerpo: si al mirarlo
   se duda cuál es el titular, hay que bajarlo más, no subirlo. */
.hero-name { flex-direction: column; }
.hero-logo { display: none; height: .30em; width: auto; margin: 0 0 .22em; }
body.has-logo .hero-logo { display: block; }
