/* sitio.css — Gran Danés LM
   ─────────────────────────────────────────────────────────────────────────────
   El sitio vive en negro a propósito y no tiene tema claro: la pieza es una
   fotografía de las siete de la tarde disolviéndose en negro, y sobre blanco no
   existe. Por eso `color-scheme: dark` está declarado en el <head> y todos los
   colores se pintan explícitos — nada se hereda del navegador. */

:root{
  --negro:      #000;      /* el ground. La foto se disuelve EN él, no sobre él */
  --tinta:      #e8ddc8;   /* crema cálida: el mismo tono que la luz de la foto */
  /* DOS tokens, no uno: el texto y el filete pedían cosas opuestas. El tostado
     viejo (#5d5346) sobre negro da 2.79:1 — bajo el mínimo 4.5:1 de WCAG AA para
     texto normal, y aquí es el peor caso (cuerpo chico, versales, tracking alto).
     Subirlo a secas dejaba los filetes de 1 px como un subrayado grueso. Por eso
     se parten: --tenue pinta texto (5.11:1 contra negro, mismo matiz cálido);
     --filete conserva el susurro de 1 px. Medido, no a ojo. */
  --tenue:      #8a7b68;   /* texto: 5.11:1 contra --negro */
  --filete:     #5d5346;   /* trazo de 1 px: el tostado de siempre */
  --brasa:      #c8a06a;
}

*{ box-sizing:border-box; margin:0; padding:0 }

html,body{
  min-height:100%;
  background:var(--negro);
  color:var(--tinta);
  /* `clip`, NO `hidden`, Y ES LA DIFERENCIA ENTRE QUE LA SECCIÓN DE CONCURSOS
     FUNCIONE O NO. `overflow-x:hidden` obliga a que `overflow-y` compute a
     `auto`, y eso convierte al <body> en un contenedor de scroll propio: un
     `position:sticky` de dentro deja de pegarse a la ventana y se va hacia
     arriba con la página. Medido: el top de la vista bajaba a −540, −1620,
     −2700… o sea que el relevo cambiaba de pieza mientras el usuario ya estaba
     mirando negro vacío.

     `clip` recorta igual y NO crea contenedor de scroll, así que el sticky
     vuelve a pegarse a la ventana. */
  overflow-x:clip;
}
body{ -webkit-font-smoothing:antialiased; }

/* ── EL HERO ─────────────────────────────────────────────────────────────────
   El hero está CLAVADO siempre. En tríptico caben las tres escenas a lo ancho y
   el riel le da UNA pantalla de altura al documento: el hero se queda quieto esa
   pantalla y luego los concursos sube por encima de él. En relevo cabe una escena
   sola, el riel estira el documento a N pantallas y el scroll —el de verdad, el
   del sistema, sin secuestrar nada— sólo mueve un número que decide qué escena
   se ve. La página nunca baja: las escenas se reemplazan en su sitio. */
.hero{ position:fixed; inset:0; z-index:0; }
/* EL HERO SE GUARDA CUANDO SU RIEL SALE DE PANTALLA. El porqué entero está en el
   script del final del index (§ EL HERO SE GUARDA CUANDO NO LE TOCA): al ser
   `fixed` sigue pintado detrás de TODO el documento, así que cualquier píxel que
   no tape un elemento opaco lo enseña — y contra el rebote de iOS, que revela una
   zona que no pertenece al documento, no hay elemento que se pueda estirar.
   `visibility` y no `display`: no toca el layout ni el contexto de WebGL. */
body.hero-guardado .hero{ visibility:hidden; }
.hero__riel{ height:0; pointer-events:none; }
/* El documento SCROLLEA SIEMPRE desde que existe los concursos, así que el
   `overflow:hidden` y el `height:100%` que tenía el body se fueron: con ellos el
   riel no podía estirar nada y no había nada que scrollear —que es exactamente
   el bug que costó el relevo de móvil, sólo que ahora en las dos formas—. La
   clase `relevo` se sigue poniendo desde hero.js y queda como gancho. */

.hero__lienzo{
  position:absolute; inset:0;
  /* la pieza entra desde el negro: mientras la fuente carga y las texturas
     suben, no se ve un canvas a medio dibujar */
  opacity:0;
  transition:opacity 700ms ease-out 40ms;
}
body.listo .hero__lienzo{ opacity:1; }
.hero__lienzo canvas{ display:block; width:100%; height:100%; }

/* ── EL CANTO DE ARRIBA · LA FOTOGRAFÍA ACUSA EL EMPUJÓN ──────────────────────
   Arriba del todo no hay NADA en el DOM que pueda acusar nada: las palabras del
   hero —GRAN DANÉS, GUARDIÁN, COMPAÑERO— están horneadas en un atlas de
   tipografía y viven como píxeles de un canvas. Lo único agarrable es este
   `<div>`, que es el que lo envuelve. Así que arriba el empujón no lo acusa un
   rótulo: LO ACUSA LA FOTOGRAFÍA, creciendo un pelo contra el dedo, como una
   lámina que se aprieta y no cede. Es el gesto de las cabeceras de foto de las
   aplicaciones, que es literalmente como lo pidió Victor.

   Y AQUÍ ESTÁ LA REGLA DURA DE ESTA LANE, la que hace que los dos cantos NO sean
   el mismo efecto en espejo: EL MUELLE DE ARRIBA SÓLO PUEDE REBASAR HACIA
   ARRIBA. `--cima` está topado en 0 por el script, así que esta escala nunca baja
   de 1. Una escala de 0.99 sobre un elemento `position:fixed` a pantalla completa
   ABRE UN MARCO en los cuatro cantos, y por ese marco se ve lo que haya debajo
   del hero — que es exactamente el agujero de dceb633 y de 1431d9e por una puerta
   nueva. Un rebote elástico sin ese tope lo abriría en CADA gesto. Abajo el
   muelle sí rebasa a los dos lados, y puede porque allí el hero no se pinta.

   VA COLGADO DE UNA CLASE Y NO COMO `transform:scale(1)` PERMANENTE: en reposo
   este `<div>` no lleva transform ninguno, así que la capa del canvas se queda
   como estaba y esto no le cuesta nada al hero mientras nadie empuja. */
body.rebota-cima .hero__lienzo{ transform:scale(calc(1 + var(--cima, 0) * var(--lamina, .015))); }

/* La marca existe como TEXTO, no sólo como píxeles en un canvas. Se oculta a la
   vista, nunca a la accesibilidad: nada de display:none ni de visibility. */
.hero__marca{
  position:absolute; width:1px; height:1px;
  overflow:hidden; clip-path:inset(50%); white-space:nowrap;
  font:400 1rem/1 Georgia, serif;
}

/* Banco de pruebas. Sólo aparece con ?hud — no es parte de la página. */
.hero__medidas{
  position:absolute; right:12px; bottom:10px; display:none;
  font:10.5px/1.6 ui-monospace,"SF Mono",Menlo,monospace;
  letter-spacing:.14em; text-transform:uppercase; color:var(--tenue);
  font-variant-numeric:tabular-nums;
}
body.hud .hero__medidas{ display:block; }

/* Foco visible: el hero no tiene controles todavía, pero el sitio sí los tendrá
   y esto es el default del que van a heredar. */
:focus-visible{ outline:2px solid var(--brasa); outline-offset:3px; }

@media (prefers-reduced-motion: reduce){
  .hero__lienzo{ transition-duration:200ms; }
}

/* ══ LAS DOS PORTADAS ══════════════════════════════════════════════════════════
   Esto vivió un rato en un `CSS/portada.css` aparte y volvió aquí POR UNA MEDIDA:
   sobre HTTP/1.1 con 150 ms de ida y vuelta, un archivo más en la primera pantalla
   le quita al `three.js` de 143 KB —de quien cuelga que la pieza se destape— unos
   95 ms, medidos con un control (HEAD más un archivo cualquiera: 3 478 → 3 573 ms
   en Fast 3G con CPU ×4). Dos reglas de riel y un menú no valen eso.

   Y además es su sitio: el alto del riel es hermano de `.hero` y `.hero__riel`,
   que están veinte líneas más arriba, y el menú es cromo de la página entera. */

/* ── EL ALTO DEL RIEL, QUE ES LA FRONTERA DE CADA PORTADA ─────────────────────
   `.hero__riel` es lo único que le da altura al documento —el hero es `fixed` y
   no ocupa flujo— así que este número decide DÓNDE empieza la sección de
   concursos y, con ella, si `loading="lazy"` sirve de algo.

   EL BUCLE MIDE UNA PANTALLA, en todas las ventanas. No cuelga del scroll: gira
   con su reloj y no tiene recorrido interno que agotar, así que está agotado
   desde el primer píxel y `HERO_FIN()` vale 0 igual en un escritorio que en un
   teléfono. El tríptico no lleva regla aquí: `medios.js` le pone la suya en el
   <head> —una pantalla en tríptico, N en relevo— y `hero.js` la reescribe inline
   con su propio encuadre. Por eso ésta cuelga de `html.portada-bucle`: gana por
   especificidad a la de `medios.js` venga en el orden que venga, y está decidida
   ANTES del primer layout porque `portada.js` marca el <html> desde el <head>.

   `svh` ANTES QUE `vh`, y no es un detalle: `100vh` en un teléfono es la ventana
   GRANDE —con la barra del navegador escondida— mientras `innerHeight` es la de
   ahora, más pequeña. La resta de `HERO_FIN()` daría sesenta u ochenta píxeles
   de recorrido muerto antes de que el salto a concursos se dispare. `100svh` es
   la ventana pequeña, que nunca es mayor que `innerHeight`: la frontera queda
   clavada en 0 y además no salta cuando la barra se esconde. El `100vh` de
   delante es el respaldo para quien no conozca `svh`. */
html.portada-bucle .hero__riel{ height:100vh; height:100svh; }

/* ── EL MENÚ DEL CLICK DERECHO ────────────────────────────────────────────────
   Tiene que parecer de esta página y no de un plugin: la didona del cintillo
   sobre negro, un filete de un píxel y nada más. Sin sombra de sistema
   operativo, sin esquinas redondas, sin degradado — la página entera está hecha
   de eso.

   `position:fixed` porque las coordenadas del click son de la ventana, y
   z-index por encima de la sección de concursos, que va en 1: el menú se abre
   con el click derecho EN CUALQUIER SITIO, también sobre una fotografía. */
.portada-menu{
  position:fixed; z-index:50;
  min-width:13.5rem;
  padding:.35rem 0;
  background:rgba(0,0,0,.92);
  border:1px solid var(--filete);
  /* El desenfoque de detrás es lo único que delata que hay algo vivo debajo. Es
     un adorno de verdad: si el navegador no lo sabe hacer, el menú sigue siendo
     negro opaco y legible. */
  backdrop-filter:blur(6px);
}
/* Un menú no se anima: aparece donde se pulsó. Cualquier transición aquí lo
   convierte en un widget, y esta página no tiene widgets. */

/* LOS DOS SELECTORES DE ABAJO LLEVAN `.cintillo` PEGADO, y no es adorno: la
   didona del cintillo se declara en `concursos.css`, que se enlaza DESPUÉS de
   esta hoja, así que con un selector de una sola clase su `font-size`, su
   `letter-spacing` y su `color` ganarían por orden y el menú saldría con el
   tracking de un cintillo —.34em— en vez del suyo. Con las dos clases este
   bloque tiene más peso y manda venga en el orden que venga. */
.portada-menu__titulo.cintillo{
  padding:.3rem 1rem .45rem;
  color:var(--filete);
  font-size:.58rem; letter-spacing:.30em;
  border-bottom:1px solid var(--filete);
  margin-bottom:.3rem;
}

.portada-menu__item.cintillo{
  display:block; width:100%;
  /* un <button> hereda cero del sitio: fondo, borde y tipografía se declaran */
  background:none; border:0; border-radius:0;
  padding:.5rem 1rem .5rem .55rem;
  text-align:left; cursor:pointer;
  font-size:.66rem; letter-spacing:.22em;
  color:var(--tenue);
  transition:color 120ms ease, background-color 120ms ease;
}
.portada-menu__item.cintillo:hover,
.portada-menu__item.cintillo:focus-visible{ color:var(--tinta); background:rgba(232,221,200,.06); }

/* LA MARCA DE LA PUESTA OCUPA ANCHO SIEMPRE, puesta o no: si apareciera y
   desapareciera, las dos entradas se moverían lateralmente al elegir y el menú
   daría un respingo justo en el instante en que se está mirando otra cosa. */
.portada-menu__marca{
  display:inline-block; width:1.1em; text-align:center;
  color:transparent;
}
.portada-menu__item.es-puesta{ color:var(--tinta); }
.portada-menu__item.es-puesta .portada-menu__marca{ color:var(--brasa); }

/* En un teléfono el menú llega por pulsación larga y los dedos no son punteros:
   las entradas crecen hasta el objetivo táctil de 44 px que pide la WCAG. */
@media (max-width:640px){
  .portada-menu{ min-width:min(18rem, calc(100vw - 2rem)); }
  .portada-menu__item.cintillo{ padding-top:.85rem; padding-bottom:.85rem; font-size:.72rem; }
}

/* ══ EL CONMUTADOR DE IDIOMA ══════════════════════════════════════════════════
   Arriba a la derecha y `fixed`, porque no hay un sitio del documento donde
   ponerlo que esté siempre a mano: el hero es `fixed`, los concursos son un riel
   de siete pantallas y el retrato mide dos.

   `--didona` se declara aquí por lo mismo de siempre —el token vive en
   `.concursos` y fuera de ahí no resuelve—, y los selectores llevan `.cintillo`
   PEGADO por la lección del commit 29ffcd0: `concursos.css` se enlaza después de
   esta hoja, así que con una sola clase su `font-size`, su `letter-spacing` y su
   `color` ganarían por orden.

   El puesto va en `--tinta` y el otro en `--filete`: la diferencia tiene que
   leerse de un vistazo sin recuadros, sin subrayados y sin fondo. Un conmutador
   de dos opciones no necesita más cromo que su propio contraste.

   `mix-blend-mode:difference` NO se usa aquí a propósito, aunque el rótulo cruce
   fotografía y negro: en la primera pantalla el fondo es un canvas de WebGL y la
   mezcla obliga al compositor a leer de vuelta esa capa en cada cuadro. Se paga
   con una sombra de un píxel, que cuesta cero. */
.idioma{
  --didona:"Bodoni Concursos", Didot, "Bodoni 72", "Times New Roman", serif;
  position:fixed; top:1.15rem; right:1.25rem; z-index:40;
  display:flex; align-items:baseline; gap:.5em;
  text-shadow:0 1px 3px rgba(0,0,0,.9);
  /* ── EL CONMUTADOR SE TRAE SU PROPIO SUELO, Y ESO ES LA CURA DE UNA CLASE ────
     TERCER CHOQUE DE ESTE WIDGET, Y LOS TRES SON EL MISMO PROBLEMA. Es
     `position:fixed` arriba a la derecha, así que POR DEBAJO LE PASA EL DOCUMENTO
     ENTERO: el cintillo del hero, el título de concursos, su filete, y mañana lo
     que alguien añada. Los dos primeros choques se taparon moviendo la caja unos
     píxeles —d043841 corrió el cintillo a 375— y por eso volvió: apartarse de UN
     vecino no dice nada del siguiente, y el siguiente todavía no existe cuando se
     escribe el arreglo.

     Y EL TERCERO ENSEÑÓ POR QUÉ MEDIR CAJAS DE TEXTO NO BASTA. A 1280 de ancho,
     con el scroll dentro de concursos, lo que cruza «ESPAÑOL» no es un texto: es
     el `border-top` de 1 px de `.concursos__cabecera`, que va de x=51.2 a
     x=1228.8 y cae en y=19.6, o sea 1.2 px por debajo del canto de arriba del
     conmutador, cruzándolo en 36 px de ancho. Un filete de un píxel no lo caza
     ninguna medida de cajas de texto, y no hay hueco al que apartarse porque la
     regla cruza la pantalla entera.

     LA CURA NO ES ESQUIVAR SINO OCULTAR: el conmutador PINTA SU PROPIO FONDO, así
     que deja de importar qué le pase por debajo. Esto no es un recurso nuevo —es
     literalmente el que esta hoja ya usaba en teléfono y el mismo del menú del
     click derecho: negro translúcido con un desenfoque de lo que hay detrás, sin
     borde, sin esquina redonda y sin sombra de sistema operativo—. Lo único que
     cambia hoy es que DEJA DE SER UNA REGLA DE TELÉFONO Y PASA A SER LA REGLA. El
     razonamiento que la justificaba en móvil («con el velo ya no importa que el
     cintillo pase por debajo») valía igual en escritorio; que viviera dentro de
     un `@media (max-width:640px)` es exactamente lo que dejó el escritorio a
     pelo, con un `text-shadow` que puede con un fondo pero no con una línea.

     EL VELO ES OPACO Y NO TRANSLÚCIDO, Y ESO SALIÓ DE QUE LA PRUEBA FALLARA.
     El primer intento fue negro al 82 % con `backdrop-filter:blur(6px)`, que es
     lo que llevaba el teléfono con más cuerpo. La prueba —fotografiar la caja del
     conmutador con el documento a la vista y con el documento ESCONDIDO, en cada
     paso de un barrido del scroll entero, y comparar los dos PNG— salió en rojo
     en 180 de 246 paradas, con diferencias de hasta 253 de 255.

     Y NO ERA UN FALLO DE LA PRUEBA: ES LA DEFINICIÓN DE `backdrop-filter`. Un
     velo translúcido con desenfoque CONSERVA A PROPÓSITO un fantasma de lo que
     hay detrás, así que el aspecto del conmutador depende, por diseño, de lo que
     le pase por debajo. Queda precioso sobre la fotografía y es exactamente lo
     que NO se puede afirmar de él: mientras haya fantasma, la respuesta a «¿es
     seguro para cualquier cosa que pase por debajo?» es «depende de qué cosa
     sea», y eso es lo que ha fallado ya tres veces.

     Así que el centro es NEGRO OPACO —el mismo `--negro` de la página, no un gris
     de interfaz— y lo que se disuelve son los CANTOS, con una elipse que apaga el
     velo antes de que se le acaben los bordes. Sobre el negro de la página no se
     ve nada en absoluto; sobre la fotografía del hero se lee como una viñeta, que
     es un recurso que esta hoja ya usa dos veces (§ `.escala::after` y el velo
     del raíl). El resultado es que las palabras se apoyan en negro puro y NADA de
     lo que pasa por debajo llega a ellas — y eso ya no es una lista de vecinos
     esquivados, es una propiedad del elemento. */
  padding:.42rem .62rem;
  /* NEGRO OPACO DEBAJO DE LAS PALABRAS Y UN HALO QUE DISUELVE EL CANTO. Son las
     dos mitades del mismo requisito y ninguna sobra: el `background` opaco es lo
     que hace la afirmación demostrable —debajo de las letras no llega nada, y no
     «casi nada»—, y la sombra negra sin desplazamiento y con extensión es lo que
     impide que eso se lea como una PLACA pegada encima de la fotografía. Sobre el
     negro de la página el halo no se ve; sobre el hero se lee como una viñeta,
     que es un recurso que esta hoja ya usa dos veces.

     Un intento anterior puso el velo en un `radial-gradient` de un
     pseudo-elemento, y la prueba lo tumbó: con `closest-side` sobre una caja
     mucho más ancha que alta, el núcleo opaco no llegaba a los EXTREMOS de la
     palabra y por ahí se colaban diferencias de 5 a 25 de 255. Una caja opaca no
     tiene esa duda. */
  background:var(--negro);
  box-shadow:0 0 20px 15px var(--negro);
}
.idioma__op.cintillo{
  background:none; border:0; padding:.2rem .1rem; cursor:pointer;
  font-size:.62rem; letter-spacing:.26em;
  color:var(--filete);
  transition:color 180ms ease;
}
.idioma__op.cintillo[aria-pressed="true"]{ color:var(--tinta); }
.idioma__op.cintillo:hover{ color:var(--tinta); }
.idioma__sep{ color:var(--filete); font-size:.62rem; line-height:1; }

/* En un teléfono se aparta del borde lo justo para no caer bajo el pulgar que
   sujeta el aparato, y crece hasta el objetivo táctil de 44 px que pide la WCAG
   — el mismo criterio que el menú del click derecho veinte líneas más arriba. */
/* EN TELÉFONO NO HAY ZONA LIBRE, Y ESO ES UN HECHO DE LA COMPOSICIÓN, no una
   pega que se pueda esquivar moviendo la caja. El cabezal del hero es un lockup
   a sangre: cintillo y palabra grande ARRIBA, palabra de la escena ABAJO, y el
   sujeto en medio. Se probaron las tres colocaciones y las tres chocan, medido:
   arriba a 11…47 se monta sobre el cintillo (empieza en ~30); a 70…106 sobre la
   T de GREAT; abajo a 792…828 sobre COMPANION, que es la más larga de las seis
   palabras y llega al 80 % del ancho; y en el medio del canto derecho cae sobre
   el vestido plateado de la escena 1 y desaparece por contraste.

   ASÍ QUE LO QUE CAMBIA NO ES EL SITIO SINO LA LEGIBILIDAD: un velo oscuro
   pequeño detrás, que es exactamente el recurso que esta hoja ya usa para el menú
   del click derecho —negro translúcido y un desenfoque de lo que hay detrás, sin
   borde, sin esquina redonda y sin sombra de sistema operativo—. No es cromo
   añadido: es el mismo idioma, y es lo único que hace que dos palabras de 11 px
   sobrevivan encima de una fotografía que cambia cada tres segundos.

   Se queda ARRIBA A LA DERECHA, que es donde se busca un conmutador de idioma, y
   con el velo ya no importa que el cintillo pase por debajo. */
@media (max-width:640px){
  .idioma{
    /* PEGADO AL CANTO Y LO MÁS PEQUEÑO QUE AGUANTA. La banda de arriba la pelean
       DOS cintillos —el del hero en la portada y el del título de concursos, que
       en español cae en dos renglones— y a 375 la caja del conmutador quedaba a
       UN píxel del segundo. Un píxel no es holgura: es que todavía no ha chocado.
       Con el velo detrás ya no tapa nada que no se lea, pero seguía apretando, así
       que se le recorta el relleno y se pega más a la esquina. */
    top:calc(.3rem + env(safe-area-inset-top, 0px)); bottom:auto;
    right:.4rem; transform:none;
    /* el velo ya lo trae la regla de arriba, y con más cuerpo: aquí sólo queda
       la GEOMETRÍA, que en un teléfono se aprieta contra la esquina */
    padding:.34rem .5rem;
  }
  .idioma__op.cintillo{ padding:0; font-size:.6rem; letter-spacing:.2em; color:var(--tinta); }
}

/* ══ EL RETRATO DE ESCALA ══════════════════════════════════════════════════════
   El tamaño del perro no se cuenta con una cifra: se cuenta CON EL SCROLL. Se
   entra y sólo se le ve la cara, se baja, y al salir sólo se ve el cachorro entre
   sus patas. SIN UNA LÍNEA DE JAVASCRIPT — ni `scroll`, ni `sticky`, ni
   `IntersectionObserver`: es un cajón más alto que la ventana con una imagen más
   alta que la ventana dentro, y la página lo recorre sola.

   TODO CUELGA DEL ALTO DEL CAJÓN, y ese alto no lleva UNA sola cifra tecleada.
   Sale de seis variables: cuatro que MIDE `Scripts/retrato.py` sobre el PNG y
   reimprime cada vez que se corre, y dos que son decisiones.

     --aspecto    ancho ÷ alto del PNG. LA PROPORCIÓN DEL ARCHIVO SE ESCRIBE AQUÍ
                  Y EN NINGÚN OTRO SITIO: la guarda, el tope y la caja de la
                  lámina salen de ella con una división.
     --perro      cuánto del lienzo ocupa EL PERRO, de las orejas a las patas del
                  cachorro. Hace que «dos pantallas» sea dos pantallas DE PERRO y
                  no de archivo. Sin él la pieza se rompe en silencio el día que
                  el PNG traiga negro añadido: medido con un lienzo de prueba 9:39
                  (862×3735), «dos pantallas» dejaban al perro en 0.83 pantallas
                  —cabe entero de una vez, el scroll ya no lo recorre— y con
                  `--perro` los dos lienzos rinden IDÉNTICO: 695 px de ancho
                  dibujado y 1.48 pantallas de perro a 390×844 con los dos.
     --holgura    medio lienzo ÷ media banda del perro con su aire. Es COMPOSICIÓN,
                  no archivo: no cambia con la resolución ni con el relleno negro.
     --nativo     el ancho en píxeles del PNG. Sólo alimenta el tope de nitidez.

     --pantallas  CUÁNTO MIDE LA PIEZA, en pantallas de perro. Es la única perilla
                  que se va a querer mover: a 200vh el perro ocupa el 32 % del
                  ancho de un 1440. En TELÉFONO subirla no hace nada y es correcto
                  —ahí manda la guarda, que es un tope—: medido a 2, 3 y 4, la
                  sección mide 1471 px y la banda vista 189…673 en las tres.
     --invento    cuánto puede el navegador estirar la lámina, en múltiplos del
                  ancho nativo. HOY ESTA PERILLA MUERDE A LA OTRA: con 862 px topa
                  la sección en 1825, que a 1440×900 son justo 2 pantallas de
                  perro, así que subir `--pantallas` no mueve un píxel mientras
                  `--invento` sea 1. La receta de las dos está en el mensaje del
                  commit. No es pega de la regla, es el archivo: el sitio ya sirve
                  esta lámina con 2.42× de invención en un teléfono a dpr 3, y con
                  el retrato a 2400 px el tope se va a 5081 y la perilla del alto
                  queda libre hasta 4 pantallas sin pagar nada.

   LA FÓRMULA. La imagen va a `height:100%`, o sea `alto × aspecto` de ancho, y de
   ella se ven los `100vw` centrales. Que el perro entre entero pide

       100vw ÷ (alto × aspecto) × (lienzo÷2)  ≥  media banda + aire

   y despejando sale `100vw × holgura ÷ aspecto`: LA GUARDA DE LAS PATAS, la única
   razón por la que la pieza no mide siempre dos pantallas. Lo primero que se
   pierde por los lados son las patas delanteras, que son lo que enmarca al
   cachorro. Hoy da 377vw; a 420vw —el número con el que llegó el encargo— se ven
   los 50.4 % centrales y las zarpas se cortan 18 px por lado. Mirado en
   `Capturas/Escala/`, no razonado. Los 6 px de aire que lleva dentro la holgura
   son también lo que absorbe que `100vw` incluya la barra de scroll y el redondeo
   del navegador al escalar.

   Y LO QUE NINGÚN ARCHIVO ARREGLA: el recorte no lo decide el lienzo, lo decide
   EL PERRO. Mide 470×1825 px —3.88 veces más alto que ancho— contra 2.16 de una
   pantalla de 390×844. Dibujar N pantallas de perro pide N×844×0.2575 px de
   ancho: N=1.79 pide 389 y N=2 pide 435. DOS PANTALLAS DE PERRO SIN CORTAR NO
   EXISTEN en un teléfono de 390, con este lienzo ni con ninguno. La guarda
   entrega 1.74 pantallas de lienzo, el 97 % de ese techo geométrico.

   `vh` Y NO `svh`, al revés que el riel del hero: aquel necesita no pasarse de la
   ventana de ahora, y éste necesita NO MOVERSE. `svh` cambia cuando la barra del
   navegador se esconde, y eso reescribiría el alto del documento con el dedo
   dentro de la sección.

   `background` explícito: a 1440×900 la imagen se dibuja a 862 px centrados y a
   los lados hay negro de verdad, que tiene que ser el mismo de la fotografía.
   `z-index:1` como los concursos, o el hero `fixed` en z-index 0 la taparía. */
.escala{
  /* lo que mide Scripts/retrato.py — cambiar el PNG = cambiar estas cuatro */
  --aspecto:0.4723;      /* 862 ÷ 1825 */
  --perro:0.850;         /* de las orejas (0.030) a las patas del cachorro (0.880) */
  --holgura:1.781;       /* 431 ÷ 242 — medio lienzo ÷ (media banda 236 + aire 6) */
  --nativo:862px;        /* el ancho nativo del PNG */
  /* y las dos perillas de la decisión, una cifra cada una */
  --pantallas:2;
  --invento:1;

  position:relative; z-index:1;
  /* el alto va a una variable propia porque no lo usa sólo `height`: el radio del
     halo del cachorro se calcula a partir de él, y repetir un `min()` de tres
     términos en dos sitios es exactamente como se separan */
  --alto:min(calc(var(--pantallas) * 100vh / var(--perro)),
             calc(100vw * var(--holgura) / var(--aspecto)),
             calc(var(--nativo) * var(--invento) / var(--aspecto)));
  height:var(--alto);
  overflow:hidden;
  background:var(--negro);
}

/* AIRE DESPUÉS DE LA PIEZA, PARA PODER CENTRAR AL PERRO. Sin esto la sección es
   lo último del documento: al llegar al final del scroll el canto de abajo de la
   fotografía coincide con el canto de la ventana y el perro se queda anclado
   ABAJO, sin margen para subirlo. Con 12vh de aire el scroll llega un poco más
   lejos y la pieza sube: medido a 1440x900, el centro del cachorro pasa de y=497
   a y=389 en el último cuadro del recorrido, o sea de pegado al borde inferior a
   centrado alto. Sólo en escritorio: en un teléfono la pieza ya llena la ventana
   y el aire sólo añadiría negro que empujar. */
/* EL AIRE DE DESPUÉS lo da el bloque del rótulo, que vive fuera de la sección:
   ver `.escala__cta`. Su alto es lo único que decide DÓNDE queda el cachorro en
   el último cuadro, porque el scroll acaba en el canto de abajo del documento:
       y del cachorro = alto de ventana − alto del bloque − 403 px
   (los 403 son lo que el cachorro está por encima del canto de la fotografía).
   Con el bloque en ~180 px y una ventana de 900, el cachorro cae en y≈316: un
   pelo por encima del centro, que es exactamente lo pedido. */

/* EL ALTO DE ESCRITORIO, APARTE Y SIN DUPLICAR LA REGLA: aquí no se reescribe el
   `height`, sólo la variable de la que sale. 821 px es la MISMA frontera que ya
   usa el `sizes` de las láminas de concursos, así que no se inventa un punto de
   ruptura nuevo. Queda en 2 porque hoy subirlo no haría nada — ver `--invento`. */
@media (min-width:821px){
  .escala{ --pantallas:2; }
}

/* ── TRES CAPAS, UN SOLO MARCO ────────────────────────────────────────────────
   La fotografía llega separada: `fondo` (el estudio y el piso, en un lienzo 16:9
   de 3244×1825), `grande` (el adulto, con alfa) y `cachorro` (el cachorro, con
   alfa y SIN su reflejo — el reflejo se queda en el fondo, que es donde estaba).
   Se apilan en ese orden: el cachorro va ENCIMA del adulto porque se sienta
   delante de sus patas. Comprobado componiendo las tres contra la foto plana:
   RMSE 5.14 sobre 255 y el 99.1 % de los píxeles sin moverse ni 8.

   EL MARCO ES EL LIENZO DE LOS SUJETOS, 862×1825, y ocupa EXACTAMENTE el sitio
   que ocupaba la lámina plana: mismo alto, mismo `--aspecto`, mismo centro. Por
   eso la guarda de las patas, el aire de 8/6 px y las capturas siguen valiendo
   sin volver a medir nada — el reparto en capas no mueve la geometría, sólo la
   reparte.

   CADA CAPA TRAE SU CAJA MEDIDA por `Scripts/retrato.py`, en fracciones de ese
   marco, y se publica RECORTADA a ella: el cachorro ocupaba el 3.3 % de su lienzo
   y ahora es un archivo de 211×383 en vez de uno de 862×1825. No es sólo peso —
   es que la zona sensible del puntero tiene que ser el cachorro y no el
   rectángulo que lo contiene, o el halo se encendería con el ratón a media
   pantalla. El fondo sale con `x` NEGATIVA y ancho 3.76: así el piso se derrama
   por los dos lados y en escritorio cubre de lado a lado, que es justo para lo
   que la Fábrica lo separó, sin que los perros se muevan un píxel. */
.escala__marco{
  position:absolute; top:0; left:50%; transform:translateX(-50%);
  height:100%; aspect-ratio:var(--aspecto);
  /* PROMOVIDO A SU PROPIA CAPA, Y EN ESTE ORDEN: primero se encogió el fondo (ver
     el bloque de abajo) y sólo DESPUÉS se promueve. Al revés habría sido peor —
     promover una capa de 2 614 px de ancho a dpr 3 es pedirle al compositor que
     sostenga 34 Mpx en memoria de textura—. Con el fondo ceñido al marco, la capa
     mide lo que la pieza y el scroll la TRASLADA en vez de re-rasterizar mosaicos
     según entran en la ventana, que es lo que se veía al bajar rápido.
     `translateZ(0)` y no sólo `will-change`: aquí ya hay un `translateX`, así que
     la promoción sale del mismo transform y no se paga dos veces. */
  transform:translateX(-50%) translateZ(0);
  will-change:transform;
}
.escala__capa{
  position:absolute;
  left:calc(var(--x) * 100%);  top:calc(var(--y) * 100%);
  width:calc(var(--w) * 100%); height:calc(var(--h) * 100%);
  max-width:none; display:block;
}
.escala__fondo   { --x:-1.38167; --y:0;       --w:3.76334; --h:1;       }

/* ── EL FONDO NO SE DERRAMA EN UN TELÉFONO, Y ESTO ES UN ARREGLO DE RASTER ────
   Victor: bajando rápido por el perro «se ven los bloques de la foto cargando,
   it makes it look cheap», y —lo que lo delata— PASA TAMBIÉN DESPUÉS DE CARGADA:
   subes, vuelves a bajar y los trozos se vuelven a pintar.

   LA CIFRA LO EXPLICA ENTERO. El derrame 16:9 hace que el fondo se dibuje a 3.76
   marcos de ancho. En un teléfono de 390 eso son 2 614 px CSS, y a dpr 3 la
   superficie que el navegador tiene que rasterizar y SOSTENER es de 34.6 Mpx
   (42.1 en un 430). No la sostiene: tira los mosaicos cuando sales y los vuelve a
   pintar cuando reapareces. Eso no es «cargando», es re-rasterizando — por eso
   pasa con la lámina ya descargada. En escritorio la misma regla son 5.9 Mpx y
   por eso ahí nunca se vio.

   Y LO QUE SE TIRA NO SE VE NUNCA: el derrame existe para que la madera llegue a
   los cantos en ESCRITORIO; en teléfono ya está medido que sobran de 1 069 a
   1 226 px por lado. Se está pagando raster por fotografía que cae fuera de la
   pantalla en todas las ventanas de teléfono.

   Debajo de 820 px el fondo se ciñe al marco y se recorta con `object-fit:cover`,
   que a este aspecto conserva EXACTAMENTE el encuadre vertical de siempre y
   recorta por los lados justo el ala que sobraba: la composición no se mueve un
   píxel y la superficie baja de 34.6 a 9.2 Mpx. El negro de los cantos, si
   sobrara, ya lo pone la sección — no hace falta una fotografía para pintarlo. */
@media (max-width:820px){
  .escala__fondo{
    --x:0; --w:1;
    object-fit:cover; object-position:50% 50%;
  }
}
.escala__grande  { --x: 0.21810; --y:0.02082; --w:0.56613; --h:0.87342; }
.escala__cachorro{ --x: 0.38399; --y:0.67452; --w:0.22854; --h:0.20932; }

/* EL FONDO YA NO NECESITA VELO EN LOS CANTOS. La lámina plana tenía uno porque el
   piso de madera cortaba contra el negro en dos líneas verticales duras a los
   lados; ahora el piso mide 3.76 marcos de ancho y a 1440×900 se dibuja a 3243 px
   sobre una ventana de 1440, o sea que el canto cae 900 px fuera de la pantalla
   por cada lado. El velo se queda SÓLO como red para una ventana absurdamente
   ancha, y a 10 % del ancho de una lámina que ya se sale, no se ve nunca. */
.escala__fondo{
  -webkit-mask-image:linear-gradient(90deg, transparent 0, #000 6%,
                                     #000 94%, transparent 100%);
          mask-image:linear-gradient(90deg, transparent 0, #000 6%,
                                     #000 94%, transparent 100%);
}

/* ── EL CACHORRO SE ENCIENDE CUANDO TE ACERCAS ────────────────────────────────
   Es la única parte de la pieza que pide JavaScript, y está separada a propósito:
   el script sólo escribe UN número, `--brillo` de 0 a 1, en la sección. Si no
   llega, no se ejecuta o falla, la pieza sigue entera y sólo se pierde el halo —
   el recorrido con scroll no depende de él ni de nada más que de estas reglas.

   EL HALO ES UNA COPIA DEL PROPIO CACHORRO PUESTA DEBAJO, no un resplandor
   pintado a mano. `drop-shadow` sigue el ALFA, así que el halo tiene la forma
   exacta de su silueta —orejas, patas, la cola— y no la de una caja; y como la
   copia va debajo, del halo sólo se ve lo que asoma por fuera de su contorno. Es
   la misma URL que la lámina de arriba: el navegador la decodifica una vez.

   TRES SOMBRAS Y NO UNA: una pegada y clara que dibuja el filo, una media que le
   da cuerpo, y una ancha y tenue que es la que se nota desde lejos. Con una sola
   el halo se lee como un borde de selección; con tres se lee como luz. Los tonos
   son los de la propia fotografía —la crema del sitio y la brasa— porque un halo
   azul o blanco puro sobre esta luz de las siete de la tarde canta a widget.

   EL RADIO ESCALA CON LA PIEZA, no con la ventana: sale del alto de la sección
   por la altura del cachorro dentro de ella, así que el halo se ve igual de
   grueso en un teléfono que en un monitor. */
/* EL ACUSE VISUAL DEL PICADO. Crece desde SUS PATAS —`transform-origin` abajo—
   porque está sentado en el piso: escalar desde el centro lo despegaría del suelo
   y se vería flotar. Es un 3.5 %, que a 200 px de ancho son siete píxeles: lo
   justo para que el ojo lo registre como respuesta y no como un salto. La curva
   de vuelta es un rebote corto; el halo se va a tope a la vez, así que el gesto
   entero es «se enciende y empuja». */
.escala__cachorro{ transition:transform 160ms cubic-bezier(.2,.9,.3,1.35); transform-origin:50% 96%; }
.escala.es-picado .escala__cachorro{ transform:scale(1.035); }

/* ── TRES ESTADOS EN ESCALERA, Y CADA UNO DICE UNA COSA DISTINTA ──────────────
   CERCA se entera · ENCIMA se ofrece · PICADO acusa. Si los tres fueran el mismo
   halo con más fuerza, serían un estado con tres intensidades y el visitante no
   distinguiría nada.

     cerca   el halo crece con la distancia y se topa en .78. Es ATMÓSFERA: algo
             está vivo por aquí.
     encima  `--realce` lo lleva a 1 Y ADEMÁS le ciñe el filo — una cuarta sombra
             pegada y clara que dibuja el contorno en vez de difundirlo. El
             cachorro pasa de brillar a estar RECORTADO, que es lo que hace que
             se lea como un objeto que se puede tomar. Y sube un 1.2 %, la mitad
             de lo que sube al picarlo: se ofrece, no responde todavía.
     picado  3.5 % desde las patas, vibración y toque. Eso ya es respuesta.

   En táctil no existe «encima» —no hay puntero que se acerque ni que se pose— y
   por eso la afordancia de ahí es el LATIDO, que no depende de ninguno de los
   tres. */
.escala__cachorro:hover{ transform:scale(1.012); }
.escala__cachorro:hover .escala__halo{
  --realce:1.35;
  filter:drop-shadow(0 0 calc(var(--r) * .16) rgba(255,255,255,.9))
         drop-shadow(0 0 calc(var(--r) * .35) rgba(255,242,214,.95))
         drop-shadow(0 0 var(--r)             rgba(255,206,128,.72))
         drop-shadow(0 0 calc(var(--r) * 2.4) rgba(255,170, 70,.45));
}
.escala.es-picado .escala__cta__flecha{ transform:translateY(-.7em); }
@media (prefers-reduced-motion: reduce){
  .escala__cachorro{ transition:none; }
  .escala.es-picado .escala__cachorro{ transform:none; }
}

/* la capa del cachorro lleva DOS láminas dentro —el halo y la nítida— así que a
   diferencia de las otras dos es una caja con hijos, y ellos la llenan */
.escala__cachorro{ cursor:pointer; }
.escala__cachorro img{ position:absolute; inset:0; width:100%; height:100%; display:block; }
.escala__halo{
  --r:calc(var(--alto) * var(--h) * .075);
  filter:drop-shadow(0 0 calc(var(--r) * .35) rgba(255,242,214,.95))
         drop-shadow(0 0 var(--r)             rgba(255,206,128,.72))
         drop-shadow(0 0 calc(var(--r) * 2.4) rgba(255,170, 70,.45));
  /* `--brillo` lo escribe el puntero por proximidad y se topa en .78; `--realce`
     es del hover y es lo que separa «cerca» de «encima». Multiplicados, el estado
     de encima llega a 1 y ninguno pisa al otro. */
  opacity:calc(var(--brillo, 0) * var(--realce, 1));
  /* el brillo lo escribe el puntero cuadro a cuadro, así que NO lleva transición:
     una transición aquí haría que el halo persiguiera al ratón con retraso. La
     única que hace falta es la de apagarse cuando el puntero se va de la ventana,
     y ésa la da el propio salto a 0, que desde un valor bajo no se ve. */
  will-change:opacity;
  pointer-events:none;
}

/* SIN PUNTERO FINO NO HAY «ACERCARSE», así que en un teléfono el halo se queda
   encendido bajito y quieto: la afordancia tiene que existir igual en el sitio
   donde está casi todo el tráfico, y ahí no hay hover que la revele. El script
   pone este mismo valor; la regla está aquí para que el halo se vea aunque el
   JavaScript no llegue. */
@media (hover:none), (pointer:coarse){
  /* EN UN TELÉFONO EL CACHORRO RESPIRA. No hay hover que descubra nada, así que
     la afordancia tiene que existir sola: el halo late despacio entre .22 y .82
     en vez de quedarse en un valor fijo. Un brillo quieto se lee como parte de la
     fotografía —de hecho ES una luz de estudio plausible— y no dice que se pueda
     picar; uno que respira sí.

     LOS 2.6 s SON LOS MISMOS QUE LOS DE LA FLECHA, y eso es lo que hace que el
     gesto se lea como UNO: el cachorro y la flecha que lo señala laten en el
     mismo compás, así que el ojo los une en vez de ver dos adornos sueltos.

     `ease-in-out` y no lineal: un latido lineal es un parpadeo de aviso, y esto
     no es una alarma. Y no es intermitente de verdad —nunca llega a apagarse—,
     porque apagarse del todo hace que la silueta se recorte contra el negro y
     salte a la vista como un error de carga.

     Cuesta cero en el hilo principal: es una animación de OPACIDAD sobre una capa
     que ya está promovida (`will-change:opacity`), así que la resuelve el
     compositor sin tocar layout ni pintura. */
  .escala__halo{
    opacity:var(--brillo, .34);
    animation:escala-late 1.3s ease-in-out infinite;
  }
}
/* 1.3 s ES LA MITAD EXACTA DE LOS 2.6 s DE LA FLECHA, y ésa es toda la razón del
   número. Victor pidió el latido «un poco más rápido»; cualquier valor cercano
   pero no múltiplo —2.0, 2.2— hace que los dos se crucen y se separen sin repetir
   nunca, y una pieza con dos ritmos que baten entre sí se siente nerviosa. A la
   mitad exacta el cachorro late DOS veces por cada movimiento de la flecha y los
   dos coinciden en cada ciclo: se lee como un pulso y su eco, no como dos
   adornos. Y el recorrido se abre un poco (.18 a .86) porque al ir más rápido el
   ojo integra el brillo medio y un rango corto se vuelve invisible. */
@keyframes escala-late{
  0%, 100% { opacity:.18; }
  50%      { opacity:.86; }
}
@media (prefers-reduced-motion: reduce){
  .escala__halo{ animation:none; opacity:var(--brillo, .34); }
}

/* EL CANTO DE ABAJO SE DISUELVE, y hasta ahora no hacía falta: la sección era lo
   último del documento, así que el corte de la fotografía coincidía con el fin de
   la página y no se veía. Con la línea de disponibilidad debajo hay negro después,
   y entonces el piso acaba en una raya horizontal dura — la misma fotografía
   pegada sobre la página que esta casa lleva tres secciones evitando, sólo que
   por el otro canto. Un velo del 14 % del alto la cierra: se come reflejo del
   suelo, que es lo más oscuro de la lámina, y ni un píxel de perro (el cachorro
   acaba en el 88.4 % y el velo empieza en el 86). Va en un pseudo-elemento y no
   en la máscara del fondo porque tiene que tapar TAMBIÉN a los sujetos si algún
   día uno llega más abajo. */
.escala::after{
  content:""; position:absolute; left:0; right:0; bottom:0;
  /* Z-INDEX EXPLÍCITO, Y COSTÓ ENCONTRARLO: el rótulo se declaraba blanco puro
     —el computado decía rgb(255,255,255)— y en la captura su píxel más claro era
     191. No era la tipografía ni la sombra: era ESTE velo pintándose encima. Un
     pseudo-elemento posicionado sin `z-index` no está por debajo de un hermano
     con `z-index:2` por arte de magia, y aquí el orden acababa al revés. Con el
     velo en 1 y el rótulo en 2 el orden queda dicho y no supuesto: medido otra
     vez, el blanco vuelve a 255. */
  z-index:1;
  /* LARGO Y SUAVE: la fotografía tiene que MORIR en el negro de la página, no
     acabar en un canto. Con el rótulo fuera de la sección hay negro de verdad
     debajo, así que el corte se vería como una raya horizontal — el mismo defecto
     que ya se arregló dos veces en los cantos verticales, ahora en el de abajo.
     26 % del alto y una curva de tres paradas: imperceptible al empezar, negro
     entero al final. El cachorro acaba en el 88.4 % y el velo llega al 74 %, así
     que se come reflejo del piso y ni un píxel de perro. */
  height:26%; pointer-events:none;
  background:linear-gradient(to bottom,
    rgba(0,0,0,0) 0%, rgba(0,0,0,.28) 38%, rgba(0,0,0,.78) 74%, var(--negro) 100%);
}

/* ── EL CACHORRO SE ENCIENDE CUANDO TE ACERCAS ────────────────────────────────
   El halo es una COPIA del propio cachorro puesta debajo: `drop-shadow` sigue el
   ALFA, así que tiene la forma exacta de su silueta —orejas, patas, cola— y no la
   de una caja. Misma URL que la lámina de arriba, así que se decodifica una vez.
   Tres sombras y no una: una pegada que dibuja el filo, una media que da cuerpo y
   una ancha que es la que se nota de lejos. Con una sola se lee como un borde de
   selección; con tres, como luz. Los tonos son los de la propia fotografía. */
.escala__cachorro{
  cursor:pointer;
  /* EL ACUSE DEL PICADO crece desde SUS PATAS: está sentado en el piso y escalar
     desde el centro lo despegaría del suelo. 3.5 % son siete píxeles a 200 de
     ancho — lo justo para que el ojo lo registre como respuesta. */
  transition:transform 160ms cubic-bezier(.2,.9,.3,1.35);
  transform-origin:50% 96%;
}
.escala.es-picado .escala__cachorro{ transform:scale(1.035); }

/* ── TRES ESTADOS EN ESCALERA, Y CADA UNO DICE UNA COSA DISTINTA ──────────────
   CERCA se entera · ENCIMA se ofrece · PICADO acusa. Si los tres fueran el mismo
   halo con más fuerza, serían un estado con tres intensidades y el visitante no
   distinguiría nada.

     cerca   el halo crece con la distancia y se topa en .78. Es ATMÓSFERA: algo
             está vivo por aquí.
     encima  `--realce` lo lleva a 1 Y ADEMÁS le ciñe el filo — una cuarta sombra
             pegada y clara que dibuja el contorno en vez de difundirlo. El
             cachorro pasa de brillar a estar RECORTADO, que es lo que hace que
             se lea como un objeto que se puede tomar. Y sube un 1.2 %, la mitad
             de lo que sube al picarlo: se ofrece, no responde todavía.
     picado  3.5 % desde las patas, vibración y toque. Eso ya es respuesta.

   En táctil no existe «encima» —no hay puntero que se acerque ni que se pose— y
   por eso la afordancia de ahí es el LATIDO, que no depende de ninguno de los
   tres. */
.escala__cachorro:hover{ transform:scale(1.012); }
.escala__cachorro:hover .escala__halo{
  --realce:1.35;
  filter:drop-shadow(0 0 calc(var(--r) * .16) rgba(255,255,255,.9))
         drop-shadow(0 0 calc(var(--r) * .35) rgba(255,242,214,.95))
         drop-shadow(0 0 var(--r)             rgba(255,206,128,.72))
         drop-shadow(0 0 calc(var(--r) * 2.4) rgba(255,170, 70,.45));
}
.escala__cachorro img{ position:absolute; inset:0; width:100%; height:100%; display:block; }
.escala__halo{
  --r:calc(var(--alto) * var(--h) * .075);
  filter:drop-shadow(0 0 calc(var(--r) * .35) rgba(255,242,214,.95))
         drop-shadow(0 0 var(--r)             rgba(255,206,128,.72))
         drop-shadow(0 0 calc(var(--r) * 2.4) rgba(255,170, 70,.45));
  /* `--brillo` lo escribe el puntero por proximidad y se topa en .78; `--realce`
     es del hover y es lo que separa «cerca» de «encima». Multiplicados, el estado
     de encima llega a 1 y ninguno pisa al otro. */
  opacity:calc(var(--brillo, 0) * var(--realce, 1));
  /* el brillo lo escribe el puntero cuadro a cuadro: SIN transición, o el halo
     perseguiría al ratón con retraso */
  will-change:opacity;
  pointer-events:none;
}
/* SIN PUNTERO FINO NO HAY «ACERCARSE», y ahí está casi todo el tráfico: el halo
   se queda encendido bajito y quieto. La afordancia tiene que existir donde no
   hay hover que la revele, y la regla vive en el CSS para que se vea aunque el
   JavaScript no llegue. */
@media (hover:none), (pointer:coarse){
  /* EN UN TELÉFONO EL CACHORRO RESPIRA. No hay hover que descubra nada, así que
     la afordancia tiene que existir sola: el halo late despacio entre .22 y .82
     en vez de quedarse en un valor fijo. Un brillo quieto se lee como parte de la
     fotografía —de hecho ES una luz de estudio plausible— y no dice que se pueda
     picar; uno que respira sí.

     LOS 2.6 s SON LOS MISMOS QUE LOS DE LA FLECHA, y eso es lo que hace que el
     gesto se lea como UNO: el cachorro y la flecha que lo señala laten en el
     mismo compás, así que el ojo los une en vez de ver dos adornos sueltos.

     `ease-in-out` y no lineal: un latido lineal es un parpadeo de aviso, y esto
     no es una alarma. Y no es intermitente de verdad —nunca llega a apagarse—,
     porque apagarse del todo hace que la silueta se recorte contra el negro y
     salte a la vista como un error de carga.

     Cuesta cero en el hilo principal: es una animación de OPACIDAD sobre una capa
     que ya está promovida (`will-change:opacity`), así que la resuelve el
     compositor sin tocar layout ni pintura. */
  .escala__halo{
    opacity:var(--brillo, .34);
    animation:escala-late 1.3s ease-in-out infinite;
  }
}
/* 1.3 s ES LA MITAD EXACTA DE LOS 2.6 s DE LA FLECHA, y ésa es toda la razón del
   número. Victor pidió el latido «un poco más rápido»; cualquier valor cercano
   pero no múltiplo —2.0, 2.2— hace que los dos se crucen y se separen sin repetir
   nunca, y una pieza con dos ritmos que baten entre sí se siente nerviosa. A la
   mitad exacta el cachorro late DOS veces por cada movimiento de la flecha y los
   dos coinciden en cada ciclo: se lee como un pulso y su eco, no como dos
   adornos. Y el recorrido se abre un poco (.18 a .86) porque al ir más rápido el
   ojo integra el brillo medio y un rango corto se vuelve invisible. */
@keyframes escala-late{
  0%, 100% { opacity:.18; }
  50%      { opacity:.86; }
}
@media (prefers-reduced-motion: reduce){
  .escala__halo{ animation:none; opacity:var(--brillo, .34); }
}

/* ── LA LÍNEA DE DISPONIBILIDAD ───────────────────────────────────────────────
   El cierre del sitio y lo único que esta casa le pide al visitante. Va DEBAJO de
   la fotografía, sobre el negro de la página, y sube una flecha desde ella hasta
   el cachorro: el halo dice que algo está vivo, la flecha dice CUÁL y el rótulo
   dice para qué. Los tres juntos son la instrucción; ninguno solo lo es.

   EL AIRE ES EL DISEÑO. Apretado contra la foto se leía como un pie de imagen —y
   así se veía en la captura de Victor, todo junto—. Con 14vh por arriba y 20vh
   por abajo el bloque mide un tercio de pantalla en escritorio, y ese tercio es
   NEGRO: es también lo que le da a la fotografía sitio donde morir.

   `--didona` se declara aquí porque el token vive en `.concursos`, no en `:root`.
   El cuerpo es el de la PÁGINA y no el de la fotografía —la pieza se dibuja a 694
   px de ancho en un teléfono y a 862 en un monitor—, así que un rótulo que
   escalara con ella saldría diminuto donde más se necesita. */
.escala__cta{
  --didona:"Bodoni Concursos", Didot, "Bodoni 72", "Times New Roman", serif;
  position:relative; z-index:1;
  display:flex; flex-direction:column; align-items:center;
  /* EL ÚLTIMO CUADRO ES UNA COMPOSICIÓN, NO UNA COLA. Victor lo pidió como el
     logo de Apple al encender un teléfono: el perro centrado o un pelo por
     encima, y la palabra justo debajo — LOS DOS EN LA MISMA PANTALLA. Con
     relleno normal el bloque medía 477 px y empujaba al cachorro hasta el borde
     de arriba; el cachorro vive 403 px por encima del canto de la fotografía, o
     sea que cada píxel de este bloque se lo come.

     Por eso el relleno de arriba es CERO y la flecha sube con un margen negativo
     hasta solaparse con el piso: así el bloque sólo aporta la palabra y su aire,
     la flecha se dibuja SOBRE la fotografía apuntando al cachorro, y el conjunto
     cabe. Los números salen de la medida, no del gusto: ver el aire de después,
     que es el otro término de la misma ecuación. */
  /* LAS MISMAS CUATRO VARIABLES DE LA SECCIÓN, porque este bloque tiene que
     resolver la MISMA ecuación desde fuera: `--alto` se declara en `.escala` y
     aquí no se hereda. Repetirlas es feo; adivinar el alto de la fotografía lo
     sería más. */
  --aspecto:0.4723; --perro:0.850; --holgura:1.781; --nativo:862px;
  --pantallas:2; --invento:1;
  --alto:min(calc(var(--pantallas) * 100vh / var(--perro)),
             calc(100vw * var(--holgura) / var(--aspecto)),
             calc(var(--nativo) * var(--invento) / var(--aspecto)));
  /* EL CACHORRO VIVE 0.2211 DEL ALTO DE LA FOTOGRAFÍA POR ENCIMA DE SU CANTO
     —su centro está en el píxel 1421.5 de 1825, medido— y el scroll acaba en el
     canto de abajo del documento, así que:
         y del cachorro = alto de ventana − alto de ESTE bloque − 0.2211·alto
     Despejando para que caiga en el 40 % de la ventana —un pelo por encima del
     centro, el encuadre del logo al encender un teléfono— sale el relleno de
     abajo. El `max(2vh, …)` es el suelo para ventanas bajas, donde la ecuación
     pediría un número negativo. */
  padding:0 1.2rem max(2vh, calc(60vh - 0.2211 * var(--alto) - 1.2em));
  gap:.2em;
  background:var(--negro);
  text-decoration:none;
  color:#fff;
  font-size:clamp(2rem, 7.6vw, 6.4rem);
}

/* APARECE AL LLEGAR. Es lo último de la página y lo único que pide algo: que se
   revele cuando el visitante termina de bajar le da el peso de un cierre en vez
   del de un letrero que ya estaba. La clase que esconde se la pone el SCRIPT, no
   el HTML: esconder desde el HTML y revelar desde el script es la receta clásica
   para dejar un texto invisible para siempre en cuanto algo falle. */
/* LA OPACIDAD DEL REVELADO VA EN EL CONTENIDO, NUNCA EN EL BLOQUE, y esto fue un
   bug que se vio en pantalla: el bloque pinta el NEGRO que tapa al hero —que es
   `position:fixed` y sigue vivo debajo de todo el documento—, así que bajarle la
   opacidad para la entrada volvía translúcido también ese negro y durante los 900
   ms del revelado se leía GUARDIÁN del hero por detrás de AVAILABILITY. Victor lo
   cazó en una captura a media animación. El fondo del bloque es opaco SIEMPRE; lo
   que aparece es la palabra y la flecha. */
.escala__cta.es-oculto .escala__cta__texto,
.escala__cta.es-oculto .escala__cta__flecha{ opacity:0; }
.escala__cta.es-oculto .escala__cta__texto{ transform:translateY(.7em); }
/* EL EFECTO ES LA TIPOGRAFÍA ABRIÉNDOSE, no una caja deslizándose. La palabra
   entra con el tracking cerrado y se ABRE hasta el suyo mientras sube: en una
   didona de versales eso se lee como un rótulo asentándose en su sitio, que es
   el gesto de una portada impresa. Y la flecha llega DESPUÉS —200 ms de retraso—
   para que primero se lea QUÉ y luego se señale DÓNDE; las dos a la vez es una
   animación, escalonadas son una frase. */
.escala__cta.es-oculto .escala__cta__texto{ --pista:.14em; }

.escala__cta__texto{ transition:letter-spacing 1100ms cubic-bezier(.2,.7,.2,1),
                     text-indent 1100ms cubic-bezier(.2,.7,.2,1),
                     opacity 900ms ease,
                     transform 900ms cubic-bezier(.2,.7,.2,1); }
.escala__cta__flecha{ transition:transform 260ms ease, opacity 700ms ease 200ms; }

/* EL REBOTE APAGA LA TRANSICIÓN DEL TRACKING MIENTRAS DURA, y esto se vio
   MIDIENDO, no razonando: el revelado de arriba pone 1100 ms de transición sobre
   `letter-spacing`, y el rebote escribe esa misma propiedad SESENTA VECES POR
   SEGUNDO. Con la transición viva, cada valor que escribe el muelle arranca su
   propia interpolación de 1100 ms y lo que llega a la pantalla es el promedio de
   sesenta animaciones solapadas — o sea, papilla con un segundo de retraso: justo
   lo contrario de un gesto que va con el dedo. En el barrido salía en el número,
   con el tracking leyendo 21.47 px donde tocaban 30.70.

   Se apaga sólo mientras hay tensión, así que la ENTRADA conserva su transición
   entera: el revelado ocurre al llegar al cierre y el rebote sólo puede ocurrir
   en el canto del documento, que está más abajo. Nunca coinciden. Y al soltar,
   la clase se quita cuando el muelle YA está en reposo, así que no hay salto. */
body.rebota-pie .escala__cta__texto{ transition:none; }

/* ── QUÉ ACUSA EL EMPUJÓN ABAJO · SE DECIDE MIDIENDO, NO POR VENTANA ──────────
   LO NATURAL AQUÍ SERÍA ABRIR EL TRACKING, que es el gesto de la casa: el rótulo
   entra abriéndose de .14em a .30em (6af9c06) porque en una didona de versales
   eso es un rótulo asentándose. PERO ABRIR EL TRACKING CUESTA ANCHO, y en esta
   página casi nunca lo hay. Medido en español —«Disponibilidad», un 17 % más
   ancha que «Availability»— la palabra ocupa en reposo:

       320 px → 287.6 de 284.8 de hueco     821 px →  804.3 de  782.6
       375 px → 337.0 de 339.8              1024 px → 1003.2 de  985.6
       390 px → 350.5 de 354.8              1280 px → 1254.1 de 1241.6
       430 px → 386.3 de 394.8              1440 px → 1320.1 de 1401.6

   O sea que EN LA MITAD DE LAS VENTANAS YA SE SALE ELLA SOLA, sin que nadie la
   toque —eso es un bug de la página y no de esta lane, y va apuntado—, y en el
   resto le sobran cuatro píxeles. Abrirla un 4.7 % encima la sacaría entre 10 y
   62 px. Sólo a 1440 y más arriba, donde el cuerpo topa en 6.4rem y deja de
   crecer con la ventana, hay sitio de verdad.

   POR ESO EL CANAL NO LO ELIGE UNA MEDIA QUERY SINO UNA MEDIDA. Una media query
   no sabe qué idioma se está leyendo, y la diferencia entre las dos palabras es
   mayor que la que hay entre dos ventanas. El script mide el hueco que le sobra a
   la palabra ANTES de cada gesto y pone `rebote-abre` sólo si el tracking abierto
   cabe entero. Si no cabe —que es lo normal en un teléfono y en un portátil
   estrecho— el empujón se acusa en el eje que SÍ tiene sitio: la palabra CEDE
   HACIA ABAJO contra el dedo, dentro de los 11vh de aire que el bloque ya lleva
   debajo. Misma física, misma amplitud, cero píxeles de ancho.

   LAS DOS SON SEGURAS por lo mismo, y es lo que hace libre a este canto: con
   `hero-guardado` puesto el hero no se pinta, y el bloque es opaco. Mover la
   palabra aquí no puede destapar nada. */
body.rebota-pie .escala__cta__texto{ transform:translateY(calc(var(--pie, 0) * .10em)); }
body.rebota-pie.rebote-abre .escala__cta__texto{ transform:none; --tira:.034em; }

/* ── LA FLECHA ────────────────────────────────────────────────────────────────
   ES UN VECTOR SÓLIDO, no un filete: un `<path>` de 1 px sobre la veta de la
   madera y a media pantalla de distancia sencillamente no existe. Y se dibuja con
   DOS CAJAS y no con un `<svg>` porque tiene que cumplir dos cosas que no escalan
   igual — LLEGAR hasta las patas del cachorro (un largo en fracción de la
   SECCIÓN) y tener el grosor de un trazo tipográfico (un ancho en `em`, atado a
   la palabra). Un `viewBox` fijo tiene que elegir: o estira la punta al estirar el
   asta, o la punta se queda enana en un monitor.

   OJO: el `clip-path` va en el `::after` y NO en el elemento. En el elemento
   recorta también a sus hijos, y así fue como una versión anterior se comió su
   propia punta. */
.escala__cta__flecha{
  position:relative; flex:none;
  width:.9em;
  /* EL LARGO VA EN `em` Y NO EN FRACCIÓN DE LA SECCIÓN, y el cambio es
     obligatorio desde que el rótulo vive FUERA de `.escala`: `--alto` se declara
     en la sección y aquí ya no se hereda, así que el `calc` quedaba inválido y la
     flecha se quedaba sin asta — sólo la punta, flotando. En `em` escala con la
     palabra, que es a lo que pertenece ahora. */
  height:1.55em;
  /* SUBE Y SE SOLAPA CON EL PISO: no ocupa alto en el bloque, así que no empuja
     al perro. Es además donde tiene que estar — dentro de la fotografía,
     señalando al cachorro, no en el margen de debajo. */
  margin-top:-1.55em; margin-bottom:0;
  filter:drop-shadow(0 0 .1em rgba(0,0,0,.96)) drop-shadow(0 0 .34em rgba(0,0,0,.88));
  transition:transform 260ms ease;
  /* SE MECE SOLA. Una flecha quieta apuntando a un perro es una anotación; una
     que respira es una invitación, y en un teléfono —donde no hay hover que
     descubra nada— es lo único que dice que ahí hay algo vivo. */
  animation:escala-mece 2.6s ease-in-out infinite;
}
@keyframes escala-mece{
  0%, 100% { transform:translateY(.14em); }
  50%      { transform:translateY(-.14em); }
}
.escala__cta__flecha::after{          /* el ASTA, afilada hacia la punta */
  content:""; position:absolute; left:50%; bottom:0;
  transform:translateX(-50%);
  width:.18em; height:calc(100% - .52em);
  background:currentColor;
  clip-path:polygon(0 100%, 100% 100%, 81% 0, 19% 0);
}
.escala__cta__flecha::before{         /* la PUNTA: aguja, no triángulo de icono */
  content:""; position:absolute; left:50%; top:0;
  transform:translateX(-50%);
  border-left:.24em solid transparent;
  border-right:.24em solid transparent;
  border-bottom:.58em solid currentColor;
}
.escala__cta:hover .escala__cta__flecha,
.escala__cta:focus-visible .escala__cta__flecha{ animation:none; transform:translateY(-.45em); }
.escala.es-picado .escala__cta__flecha{ animation:none; transform:translateY(-.7em); }

.escala__cta__texto{
  font-family:var(--didona);
  /* ── EL CANTO DE ABAJO · EL TRACKING ACUSA EL EMPUJÓN ───────────────────────
     EL TRACKING SE ESCRIBE COMO UNA SUMA, y ésa es toda la razón por la que el
     rebote y el revelado de 6af9c06 pueden compartir la misma propiedad sin
     pisarse: `--pista` es el tracking de REPOSO —el que cambia entre escritorio,
     teléfono y la entrada—, y `--pie` es la TENSIÓN, de -1 a 1, que escribe el
     script. Ninguno de los dos sabe del otro.

     ES EL MISMO GESTO DE LA CASA Y NO UN `scale` DE INTERFAZ: en una didona de
     versales, abrir el tracking es lo que hace que un rótulo se lea como que se
     asienta. El revelado lo usa para entrar; el rebote lo usa para acusar el
     empujón. Y `--pie` LLEGA EN NEGATIVO al soltar, porque el muelle rebasa: la
     palabra se pasa un pelo POR DEBAJO de su tracking de reposo antes de
     asentarse. Eso es, con estas letras, lo que pidió Victor — «que crezca
     tantito y se haga más chiquito y te empuje de vuelta». Aquí el rebasamiento
     sale gratis y arriba no: esto es texto blanco sobre un bloque OPACO, y con
     `hero-guardado` puesto el hero ni siquiera se pinta.

     `white-space:nowrap` NO ES ADORNO, ES EL SEGURO: si el tracking abierto
     partiera la palabra en dos renglones, el bloque crecería de alto, el
     documento entero cambiaría de altura A MEDIO GESTO y el scroll saltaría bajo
     el dedo. Una línea, siempre. */
  --pista:.30em;
  /* EL TRACKING NO SE ABRE POR DEFECTO: SE GANA MIDIENDO. Ver `.rebote-abre`
     ochenta líneas más abajo — en esta página la palabra suele llenar su caja. */
  --tira:0em;
  letter-spacing:calc(var(--pista) + var(--pie, 0) * var(--tira));
  text-indent:calc(var(--pista) + var(--pie, 0) * var(--tira));
  white-space:nowrap;
  /* wght 500 y no 400 aunque sea display: a 102 px la Bodoni de peso normal tiene
     finos de un par de píxeles y el ojo promedia asta + fino y lo lee PLATEADO
     aunque el color sea #fff — pasaba literalmente, con el computado en 255. */
  font-variation-settings:"opsz" 30, "wght" 500;
  font-size:1em;
  line-height:1;
  text-transform:uppercase;
  color:inherit;
  margin:0;
  /* la viñeta va en PÍXELES y no en `em`: en `em` el desenfoque crecía con la
     letra, se metía dentro de las astas y el blanco salía gris */
  text-shadow:0 0 8px rgba(0,0,0,.95), 0 0 26px rgba(0,0,0,.85), 0 0 70px rgba(0,0,0,.7);
}

/* EN UN TELÉFONO EL TRACKING SE CIERRA PARA QUE EL CUERPO PUEDA CRECER: con .30em
   la palabra medía 423 px en una ventana de 390 y se salía. Encogerla habría sido
   lo contrario de lo pedido; cerrar el tracking a cuerpos grandes es además lo
   tipográficamente correcto. Y el eje óptico baja al corte de TEXTO — ver el
   bloque del final de concursos.css, que es donde está el porqué entero. */
@media (max-width:820px){
  .escala__cta{ padding:8vh 1.1rem 11vh; }
  .escala__cta__texto{
    font-variation-settings:"opsz" 16, "wght" 600;
    font-size:clamp(1.5rem, 8.2vw, 6.4rem);
    --pista:.15em;
  }
}

@media (prefers-reduced-motion: reduce){
  .escala__cta, .escala__cta__flecha, .escala__cachorro{ transition:none; animation:none; }
  /* EL REBOTE SE APAGA EN LA HOJA Y NO SÓLO EN EL SCRIPT. El script no se
     instala con reduced-motion, pero alguien puede cambiar la preferencia con la
     página ya abierta y el oyente ya puesto: poniendo a cero las dos amplitudes,
     los dos `calc` caen en su valor de reposo pase lo que pase. */
  .escala__cta__texto{ --tira:0em; }
  body.rebota-pie .escala__cta__texto{ transform:none; }
  body.rebota-cima .hero__lienzo{ transform:none; }
  .escala.es-picado .escala__cachorro{ transform:none; }
  .escala__cta.es-oculto{ opacity:1; transform:none; }
}
