/* NIKOLA ZALE — layout, typography, layers, responsive.
   Layer order (back to front):
   body black -> .fallback -> .backdrop -> .vignette -> #stage (alpha canvas)
   -> .overlay -> .scroll-hint -> .preloader                                */

:root {
  --bone: #e8e2d6;
  --bone-dim: #9b948a;
  /* El tono de la LECTURA, distinto del tono de la ROTULACIÓN.
     `--bone-dim` nació para etiquetas de una palabra y filetes, y se acabó
     usando también para cuerpo de texto: contra `--bone` está a 2.33:1, que es
     el escalón con el que un navegador pinta un control desactivado. A cuerpo
     de 16 px y peso ligero eso no se lee como "secundario", se lee como
     "apagado", y ninguna subida de tamaño lo arregla porque el problema es el
     salto respecto a las voces que tiene al lado, no el valor absoluto.
     #cfc9bd deja el escalón en 1.28:1 —sigue siendo un peldaño por debajo del
     hueso lleno— y sube el contraste contra el fondo medido de 6.08:1 a
     11.07:1. `--bone-dim` se queda para lo que siempre fue suyo: bordes,
     filetes y el crédito de la casa. */
  --bone-body: #cfc9bd;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --pad: clamp(1.25rem, 4vw, 4.5rem);
  /* Lo que el parallax de ratón puede desplazar como mucho a una capa de
     texto: `layerAmount` 20 en escritorio por el `data-depth` más alto de un
     bloque de lectura, que es el 0.7 del taller. Cualquier caja que llegue a
     ocupar el ancho útil entero tiene que dejar al menos esto de margen, o al
     mover el ratón se sale del cuadro. Si cambia `layerAmount` en
     js/main.js, cambia aquí. */
  --parallax-reserve: 14px;
  /* Lo mismo, pero para la frase de la portada, que es la capa con el
     `data-depth` más alto de la pieza (1.6) y por tanto la que más se mueve:
     1.6 x 20 = 32 px en escritorio. En vertical la amplitud baja a 12, así que
     el acolchado normal ya la cubre y esta reserva se relaja para no comerle
     ancho a una pantalla estrecha. */
  --tagline-reserve: 32px;
  /* Dos voces, no una: ROTULACIÓN y LECTURA. La serif sólo firma —nombre,
     tagline, títulos de obra, rótulo del taller— y la grotesca lleva todo lo
     que se lee seguido. Es el reparto del cartel de danza, y el mismo que ya
     hacía la pieza con el color (`--bone` rotula, `--bone-body` se lee).
     `--font-display` se conserva apuntando a la de lectura: lo usa
     `.tunel__acceso` y cualquier regla que se escape de este inventario. */
  --font-rotulo: "Cormorant Garamond", Garamond, Georgia, serif;
  --font-lectura: "Jost", "Helvetica Neue", Helvetica, "Segoe UI", Arial, sans-serif;
  --font-display: var(--font-lectura);
}

/* ------------------------------------------------------------ webfonts */

/* Self-host, no fonts.googleapis: la pieza tapa la pantalla con el preloader
   mientras carga el modelo, y en esa ventana caben las fuentes sin que se vea
   un solo fotograma con la de respaldo. Una petición a un tercero metería una
   variable de red que no controlamos justo en la entrada.
   Un `@font-face` por subset con su `unicode-range`, igual que sirve Google:
   quien lee en castellano o inglés baja sólo `latin`; `latin-ext` cae únicamente
   si el texto trae los caracteres que lo piden. Con los cuatro idiomas
   pendientes (es · pt · fr · en) los dos subsets bastan.

   `size-adjust` es la compensación óptica, y va aquí a propósito. Cormorant y
   Jost tienen la altura de x bastante más baja que Helvetica: al mismo
   `font-size` se ven más pequeñas, y sobre negro con `antialiased` —que
   adelgaza el asta— eso no se lee como "fino" sino como "flojo". Escalando el
   glifo dentro del em desde el `@font-face` no hay que tocar ninguno de los
   cuarenta `clamp()` ni los ocho bloques de media queries que ya están
   calibrados contra el 3D: el `font-size` computado no cambia, así que el
   tracking en `em` y los `line-height` sin unidad siguen valiendo lo mismo. */

@font-face {
  font-family: "Cormorant Garamond";
  font-style: normal;
  font-weight: 300 700;
  font-display: block;
  size-adjust: 112%;
  src: url("fonts/cormorant-garamond-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191,
    U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Cormorant Garamond";
  font-style: normal;
  font-weight: 300 700;
  font-display: block;
  size-adjust: 112%;
  src: url("fonts/cormorant-garamond-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Jost";
  font-style: normal;
  font-weight: 100 900;
  font-display: block;
  size-adjust: 106%;
  src: url("fonts/jost-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191,
    U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Jost";
  font-style: normal;
  font-weight: 100 900;
  font-display: block;
  size-adjust: 106%;
  src: url("fonts/jost-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* La cursiva de verdad, no una oblicua sintética. La pide `.taller__pregunta`,
   que es la voz más grande del nodo del taller: en una geométrica como Jost
   inclinar los glifos a la fuerza se nota a la primera —las bowls quedan
   torcidas en vez de redibujadas. */
@font-face {
  font-family: "Jost";
  font-style: italic;
  font-weight: 100 900;
  font-display: block;
  size-adjust: 106%;
  src: url("fonts/jost-italic-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191,
    U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Jost";
  font-style: italic;
  font-weight: 100 900;
  font-display: block;
  size-adjust: 106%;
  src: url("fonts/jost-italic-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

*, *::before, *::after { box-sizing: border-box; }

html {
  background: #000;
  /* Lenis drives scrolling; the native smooth behaviour would fight it. */
  scroll-behavior: auto;
}

body {
  margin: 0;
  background: #000;
  color: var(--bone);
  font-family: var(--font-lectura);
  /* 200 -> 300. El 200 de Helvetica Neue es la ExtraLight del sistema, que en
     realidad se resolvía a Light en casi todas partes; el 200 de Jost es un
     peso variable de verdad y es un hilo. Este valor sólo alcanza a lo que no
     declara peso propio, pero eso incluye la tagline y el contenedor del
     taller. */
  font-weight: 300;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

body.is-loading { overflow: hidden; }

/* ---------------------------------------------------------------- layers */

#stage {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  z-index: 3;
}

.backdrop,
.vignette,
.fallback {
  position: fixed;
  inset: 0;
  pointer-events: none;
}

.fallback { z-index: 0; display: none; }

.backdrop {
  z-index: 1;
  opacity: 0;
  will-change: opacity, transform;
}

.backdrop img,
.fallback::after {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 42%;
  display: block;
  /* The source is 1179x651 and gets blown up past 2x on most screens. A soft
     blur turns the upscaling artefacts into grain instead of mush, and the
     extra scale hides the transparent edge the blur would otherwise leave. */
  /* Held well down: the photograph is the room the skull ends up in, read as
     a silhouette behind it, never as a lit picture competing with the bone. */
  filter: grayscale(1) contrast(1.22) brightness(0.62) blur(3.5px);
  transform: scale(1.06);
}

/* Bottom gradient guarantees contrast for the contact line on any crop. */
.vignette {
  z-index: 2;
  opacity: 0;
  background:
    radial-gradient(120% 85% at 50% 45%, transparent 46%, rgba(0,0,0,.62) 100%),
    linear-gradient(to bottom, transparent 55%, rgba(0,0,0,.9) 100%);
}

/* ------------------------------------------------------------ text stages */

.overlay { position: fixed; inset: 0; z-index: 4; pointer-events: none; }

/* Below the canvas (z-index 3) and above the photograph. */
.underlay { position: fixed; inset: 0; z-index: 2; pointer-events: none; }

/* La capa de atrás caía 14vh por debajo de la de delante: con las dos palabras
   repartidas en los cantos del cuadro, a la altura de la primera el cráneo
   llenaba la derecha y se tragaba la segunda entera, así que había que bajarla
   para que la cruzara un cuerno en vez de taparla el hueso.
   Con la frase apilada ya no hace falta y además estorba: las dos palabras
   tienen que caer una encima de otra y alineadas, y para eso las dos capas
   deben ocupar exactamente el mismo sitio — cada una lleva una copia fantasma
   de la palabra de la otra justo para eso. Separarlas verticalmente rompía esa
   correspondencia. El cruce del cuerno lo sigue habiendo: la segunda línea baja
   hasta la altura de las astas por sí sola. */

/* Keeps the box the same in both layers so the two halves of the tagline
   land in exactly the same place. */
.tw--ghost { visibility: hidden; }

/* Lo que queda detrás del canvas se lee a través de la pared de tierra, que es
   un plano semitransparente cubriendo el encuadre entero, así que llega
   apagado. Más brillo y más cuerpo aquí para que salga al mismo peso que su
   copia de delante — el cuerno lo sigue tapando por completo donde cruza,
   porque el hueso es opaco. */
.underlay .name,
.underlay .credit {
  color: #fff;
  text-shadow:
    0 1px 3px rgba(0, 0, 0, 0.9),
    0 0 16px rgba(0, 0, 0, 0.75);
}

/* La palabra de atrás no lleva ni un gramo más de peso ni una sombra distinta
   que la de delante: a 500, y con su propia pila de sombras, el trazo engordaba
   por el borde y las dos palabras de la misma frase se leían en dos grosores.
   Comparten `.tw` entero; lo único suyo es el color —blanco puro, porque la
   pared de tierra se lo come— y el velo de debajo, que es contraste local:
   blanco atenuado sobre tierra iluminada no se lee. */
.underlay .tw { color: #fff; }

.underlay .tw::before {
  inset: -50% -12%;
  background: radial-gradient(closest-side,
    rgba(0, 0, 0, 0.78), rgba(0, 0, 0, 0.42) 56%, transparent 86%);
}

/* Las dos palabras de la frase comparten color, peso y sombra: cualquier
   diferencia entre ellas se lee como un error de composición, no como
   profundidad. */
.tw {
  position: relative;
  text-shadow:
    0 1px 3px rgba(0, 0, 0, 0.95),
    0 0 14px rgba(0, 0, 0, 0.85),
    0 0 38px rgba(0, 0, 0, 0.75);
}

.tw::before {
  content: "";
  position: absolute;
  inset: -45% -10%;
  background: radial-gradient(closest-side,
    rgba(0, 0, 0, 0.46), rgba(0, 0, 0, 0.2) 56%, transparent 84%);
  z-index: -1;
  pointer-events: none;
}

/* El hueco que cada capa reserva para la palabra de la otra no lleva velo: no
   hay nada que leer ahí y el círculo se vería flotando solo. */
.tw--ghost::before { display: none; }

/* Las dos palabras tienen que llegar iguales, y lo que las diferenciaba no era
   el tono sino la materia: la de atrás se ve a través del canvas, así que la
   tierra se le transparenta por dentro del trazo, mientras que la de delante
   era blanco plano encima de todo. Bajar su color no arregla eso — da un gris
   igual de opaco.
   Lo que se iguala es la operación: la pared de tierra cubre a la de atrás al
   34% mientras la frase está en pantalla, así que la de delante se compone al
   66% sobre lo que haya debajo. Aritméticamente es el mismo píxel.
   Va en el párrafo y no en la palabra a propósito: a nivel de elemento, el
   texto y su halo se aplanan primero y se atenúan juntos, que es lo que hace
   la pared con la otra. Puesto en el color, el halo se vería por dentro de las
   letras y saldrían sucias. Y el párrafo es el único nodo de los tres que no
   anima nadie: GSAP lleva la opacidad de la palabra y la transformación del
   envoltorio. */
.overlay .stage--2 .tagline { opacity: 0.66; }

/* Y va desnuda: sin halo y sin velo. Los dos oscurecen lo que rodea al trazo,
   y un blanco sobre un cerco oscuro se lee más blanco que el mismo blanco
   sobre la tierra — el ojo mide contraste, no luminancia. La de atrás sí los
   conserva: los necesita para no desaparecer bajo la pared. */
.overlay .stage--2 .tw {
  color: #fff;
  text-shadow: none;
}

.overlay .stage--2 .tw::before { display: none; }

.stage {
  position: absolute;
  inset: 0;
  display: flex;
  padding: var(--pad);
  padding-bottom: calc(var(--pad) + env(safe-area-inset-bottom));
}

/* La copia delantera del título sólo enseña su primera palabra: por ahí el
   cuerno pasa por detrás de las letras, y por la segunda —que sólo existe en
   la capa de atrás— pasa por delante. El recorte va en el contenedor, no en el
   texto, para que no toque las cajas que GSAP anima.
   El punto de corte lo mide main.js y cae en el hueco entre las dos palabras:
   partido a media letra, la diferencia de tono entre las dos capas se lee como
   un tajo en el glifo. El 50% es sólo el valor de arranque, antes de medir. */
.stage--1-front {
  clip-path: inset(0 calc(100% - var(--name-cut, 50%)) 0 0);
}

/* La palabra de delante se lee directa; la de atrás llega a través del canvas
   y de la pared de tierra, que se come casi un cuarto de su brillo, y ahí ya
   no hay color que subir: la de atrás es blanco puro. Medido sobre la portada
   iban a 221 y 174 de luminancia, y el título se leía en dos tonos partidos
   por el hueco entre las palabras. Se baja la de delante hasta el nivel al que
   sale la de atrás. */
.stage--1-front .name { color: #bcb7ae; }

/* Name and credit ride high: the skull needs the lower two thirds of the
   frame, and a title sitting on the crown is a title fighting the object. */
.stage--1 {
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  padding-top: clamp(2rem, 11vh, 9rem);
  gap: clamp(1.6rem, 4.6vw, 3.6rem);
}

/* La biografía respira más que el resto de escenas: es la única con texto
   corrido, y una columna de lectura que arranca a un dedo del canto de la
   pantalla se lee como si se saliera del cuadro. El resto de capas —display,
   una o dos palabras— sí quieren pegarse al borde. */
.stage--bio {
  align-items: center;
  justify-content: center;
  padding-left: clamp(1.5rem, 8vw, 9rem);
  padding-right: clamp(1.5rem, 8vw, 9rem);
}
.stage--video { align-items: center; justify-content: center; }

/* La tarjeta y su señal de scroll, en columna. El `.reveal` era un ítem flex
   de ancho automático con un solo hijo; con dos hay que decir cómo se apilan o
   la señal se pone al lado de la tarjeta. */
#reveal-video {
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* Las dos palabras van a los cantos, así que este acolchado es lo único que
   las separa del borde de la pantalla, y tiene que cubrir lo que puede
   empujarlas hacia fuera: el parallax de ratón, que sobre la frase escribe más
   que sobre ninguna otra capa —`data-depth` 1.6 por 20 de amplitud en
   escritorio, o sea 32 px—, y el recorte de un teléfono con muesca en
   apaisado. El valor va en `.stage--2`, la clase que comparten las DOS capas:
   si sólo se acolchara una, las copias fantasma dejarían de caer sobre las
   reales y el cuerno recortaría por donde no debe. */
.stage--2 {
  align-items: center;
  justify-content: flex-start;
  padding-left: max(var(--pad), var(--tagline-reserve), env(safe-area-inset-left));
  padding-right: max(var(--pad), var(--tagline-reserve), env(safe-area-inset-right));
}

/* El cierre reparte el alto: firma arriba (absoluta), la calavera en medio y
   el bloque de contacto pegado al borde inferior. El margen de abajo se
   recorta respecto al resto de escenas: cada píxel que gana el pie es un
   píxel que el bloque deja de robarle a la calavera. */
.stage--3 {
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  padding-bottom: calc(1.1rem + env(safe-area-inset-bottom));
}

/* La frase se lee en escalón: la primera palabra arranca en el canto
   izquierdo y la segunda termina en el derecho. El escalón no se inventa con
   ningún desplazamiento —sale solo de que una palabra es más corta que la
   otra— y por eso se mantiene igual de legible a cualquier ancho, que es lo
   que ningún margen fijo conseguía.
   Va en alineación y no en `transform`: GSAP escribe `transform` en cada
   palabra para traerlas, y dos escritores sobre la misma propiedad se pisan.
   El margen negativo de la segunda cancela el espacio que el tracking deja
   colgando detrás de la última letra: sin él la palabra se para un cuarto de
   letra antes del canto y el remate no llega. */
.tagline .tw { display: block; width: 100%; }
.tagline .tw:nth-child(1) { text-align: left; }
.tagline .tw:nth-child(2) {
  text-align: right;
  margin-right: calc(var(--tw-track) * -1);
}

/* GSAP owns this element's transform, opacity and filter. */
.reveal { will-change: transform, opacity, filter; }

.name,
.credit,
.tagline,
.contact,
.bio,
.bio__col {
  margin: 0;
  will-change: transform;   /* the parallax writes here, nothing else */
}

.name,
.credit,
.tagline,
.contact { text-transform: uppercase; }

/* The credit sits directly under the name, which is exactly where the lit
   crown of the skull is brightest. It carries its own soft scrim so it stays
   legible whatever the zenith light is doing behind it. */
/* Suelo de peso 400 en toda voz por debajo de 1rem — aquí y en `.contact`,
   `.contact__tag`, `.taller__sub`, `.taller__cta`, `.tunel__sinopsis`,
   `.tunel__pie` y `.tunel__acceso`. El 300 anterior era el Light de Helvetica,
   que es un peso ya bastante lleno; el 300 de Jost es un peso variable real y a
   11 px sobre la fotogrametría desaparece. El tamaño no cambia en ninguna. */
.credit {
  position: relative;
  font-size: clamp(0.68rem, 1.7vw, 1rem);
  font-weight: 400;
  letter-spacing: 0.62em;
  text-indent: 0.62em;
  color: var(--bone);
  text-align: center;
  text-shadow: 0 0 22px rgba(0, 0, 0, 0.9), 0 0 6px rgba(0, 0, 0, 0.8);
}

.credit::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  /* Ceñido a la palabra. Antes medía 560% x 1100% y con el rig más alto esa
     mancha se veía como un borrón sobre la tierra, lejos de cualquier letra:
     una sombra que no cae sobre nada delata que es un parche. */
  width: 240%;
  height: 420%;
  transform: translate(-50%, -50%);
  background: radial-gradient(closest-side,
    rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.2) 52%, transparent 82%);
  z-index: -1;
  pointer-events: none;
}

/* Two columns with the skull orbiting in the gap between them. The measure
   stays near 30 characters per column, which is what lets the type run this
   large without the eye losing the line. */
#reveal-bio { width: 100%; }

.bio {
  display: flex;
  width: 100%;
  align-items: center;
  justify-content: space-between;
  gap: clamp(1.5rem, 6vw, 6rem);
}

.bio__col {
  position: relative;
  max-width: 30ch;
  font-size: clamp(1.15rem, 1.6vw, 1.62rem);
  font-weight: 400;
  /* 1.62 y no 1.7. A 30ch de medida, 1.7 abre tanto la línea que un párrafo
     de cuatro se lee como cuatro frases sueltas en vez de como un bloque. El
     interlineado se cierra y el salto entre párrafos se abre: es ahí donde
     tiene que estar el aire, no dentro del párrafo. */
  line-height: 1.62;
  letter-spacing: 0.004em;
  color: #f2ede3;
  text-wrap: pretty;
  hyphens: none;
  /* Three stacked shadows: a tight one to separate the stroke from whatever
     is directly behind it, a mid one for the bone highlights, and a wide
     diffuse one that darkens the whole area the text sits on. Body copy over
     a moving lit object needs all three. */
  text-shadow:
    0 1px 2px rgba(0, 0, 0, 0.95),
    0 0 10px rgba(0, 0, 0, 0.9),
    0 0 30px rgba(0, 0, 0, 0.85);
}

/* Las dos columnas van centradas, no una a bandera izquierda y la otra a
   derecha. Enfrentadas por sus banderas se leían como dos textos empujándose
   contra el objeto del medio; centradas, las dos miran al mismo sitio que el
   resto de la pieza. Lo que sigue separándolas es el hueco, no la alineación. */
.bio__col { text-align: center; }
.bio__col--right { text-align: center; }

.bio p { margin: 0; }
/* El hueco entre párrafos tiene que ser claramente mayor que el hueco entre
   líneas o los dos párrafos se leen como uno. Con 1.62 de interlineado el
   blanco entre líneas es 0.62em; a 1.5em de separación el salto es 2.4 veces
   mayor y se ve dónde acaba una idea. Antes la proporción era 1.8 y el corte
   apenas se notaba. */
.bio p + p { margin-top: 1.5em; }

/* One span per word, injected by main.js so the copy can arrive at reading
   pace. inline-block is what makes them transformable; the text nodes between
   them keep the line breaking native. */
.bio .w { display: inline-block; will-change: transform, opacity; }

/* The skull orbits behind these columns, so each carries its own scrim. */
.bio__col::before {
  content: "";
  position: absolute;
  inset: -10% -12%;
  background: radial-gradient(closest-side,
    rgba(0, 0, 0, 0.66), rgba(0, 0, 0, 0.34) 66%, transparent 100%);
  z-index: -1;
  pointer-events: none;
}

.tagline { text-wrap: balance; }

.name {
  font-family: var(--font-rotulo);
  /* 10.5vw -> 9.6vw. Medido, no a ojo: con Helvetica el nombre medía 1380 px en
     una ventana de 1440 y con Cormorant al mismo cuerpo mide 1438 —la serif con
     su compensación óptica ocupa un 4% más de línea—, así que a 1280 y 1024 se
     salía del encuadre por 31 y 25 px. El término en `vw` es el que manda por
     debajo de 1500 px de ancho; bajarlo devuelve el nombre al ancho que tenía
     antes en 1440 y le deja margen donde antes rozaba. El techo se queda en
     9rem: por encima de 1500 px sobra sitio. */
  font-size: clamp(2.4rem, 9.6vw, 9rem);
  /* 200 -> 400. El peso ligero era la manera de que una grotesca no pesara
     demasiado a 9rem; una serif de alto contraste no tiene ese problema —lo
     resuelve el propio dibujo, con el asta gruesa y el remate fino— y a 300 el
     trazo delgado de Cormorant se parte sobre la tierra. */
  font-weight: 400;
  /* 0.16em -> 0.12em. El tracking abierto era lo que le daba aire a una
     Helvetica; entre astas con remate ya hay aire dibujado, y al mantenerlo el
     nombre se leía como letras sueltas en vez de como una palabra. */
  letter-spacing: 0.12em;
  line-height: 1;
  text-indent: 0.12em; /* compensates the trailing letter-space, keeps it optically centred */
  text-align: center;
  white-space: nowrap;
}

/* One span per glyph, injected by main.js for the staggered reveal. */
.name .char { display: inline-block; }
.name .word { display: inline-block; }
.name .word + .word { margin-left: 0.42em; }

.tagline {
  font-family: var(--font-rotulo);
  font-size: clamp(1.9rem, 6.6vw, 4.6rem);
  /* Un punto por encima del resto del display: la frase se lee sobre el objeto
     en movimiento y a peso 300 se deshacía contra el hueso.
     Con la serif sube otro punto por lo mismo: aquí el fondo no es negro sino
     el hueso iluminado, y es el trazo FINO de la serif el que desaparece
     contra él, no el grueso. Las dos copias de la frase —la de delante del
     canvas y la de detrás— tienen que llevar exactamente este peso: si se
     separan, las cajas dejan de maquetar igual y el cuerno recorta por donde
     no debe. */
  font-weight: 500;
  /* El tracking vive en una variable porque hay dos consumidores: la propia
     frase y el margen negativo con el que la segunda palabra recupera el
     hueco que el tracking deja detrás de su última letra. En vertical baja, y
     los dos tienen que bajar a la vez. */
  --tw-track: 0.26em;
  letter-spacing: var(--tw-track);
  line-height: 1.18;
  color: var(--bone);
  /* Ocupa el ancho útil entero: es lo que permite que una palabra se apoye en
     un canto y la otra en el opuesto. El `max-width` de 14ch de antes ceñía la
     caja al texto y dejaba los dos cantos fuera de alcance. */
  width: 100%;
}

/* Ocupa el sitio que tenía el nombre al cerrar: es lo que se lleva el visitante
   de la pieza, así que encabeza la última pantalla en vez de firmarla al pie. */
.contact {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
  font-size: clamp(1rem, 2.8vw, 1.35rem);
  font-weight: 400;
  letter-spacing: 0.34em;
  text-indent: 0.34em;
  color: var(--bone);
  text-decoration: none;
  padding: 0.75rem 1rem;
  transition: color 0.4s var(--ease), opacity 0.4s var(--ease);
}

/* El cierre es la última sección del DOM, o sea la que va por delante de todas
   las demás, y vive en un `.stage` de `inset: 0`: a opacidad cero sigue
   cubriendo la pantalla entera. Con `pointer-events: auto` fijo, sus tres
   enlaces eran destinos invisibles encima de toda la pieza — en un móvil
   tumbado el bloque lleva además `margin-top: 27vh`, que lo pone justo sobre
   la tarjeta del clip, así que tocar el vídeo abría Instagram.
   Se cierra con `body.is-end`, la misma llave que ya usaba `.to-top` y que
   main.js enciende en 0.9482, muy por detrás de la ventana del clip. */
body:not(.is-end) .contact,
body:not(.is-end) .powered a { pointer-events: none; }

body.is-end .contact,
body.is-end .powered a { pointer-events: auto; }

/* Etiqueta, no botón: dice qué pasa al tocar el handle sin montar un segundo
   elemento que compita con él. */
/* 0.55rem son 8.8 px, y el término en `vw` no alcanza al suelo hasta 677 px de
   ancho: en cualquier teléfono la etiqueta medía siempre 8.8 px con 0.4em de
   tracking, encima de la fotografía del cierre. Es lo único que dice adónde
   lleva cada uno de los tres destinos. Y el tono pasa al de lectura: la foto
   entra a 0.62 de opacidad y la viñeta no llega hasta arriba, así que el fondo
   no es negro — contra luminancia 0.10, `--bone-dim` da 2.33:1 y `--bone-body`
   4.25:1, y la sombra ceñida cubre el resto. */
.contact__tag {
  font-size: clamp(0.66rem, 1.3vw, 0.74rem);
  font-weight: 400;
  letter-spacing: 0.4em;
  text-indent: 0.4em;
  color: var(--bone-body);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.95), 0 0 16px rgba(0, 0, 0, 0.9);
  transition: color 0.4s var(--ease);
}

/* El hover señalaba con un cambio de color de hueso a blanco y nada más: sobre
   una fotografía en blanco y negro eso es un matiz, no una respuesta. Ahora
   contesta con un filete que se abre bajo el nombre —el mismo gesto que el
   índice de obras— y con el rótulo encendiéndose. Dos cosas a la vez, que es lo
   que hace que se lea como «esto se pulsa» y no como un reflejo de la pantalla.
   Sin `transform` en el hover: aquí escribe el parallax, y dos cosas moviendo
   el mismo `transform` se pisan. El filete se anima con `scaleX` sobre el
   pseudoelemento, que es suyo y de nadie más. */
.contact { cursor: pointer; }

.contact__handle {
  position: relative;
  padding-bottom: 0.35em;
}

.contact__handle::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: var(--bone);
  transform: scaleX(0);
  transform-origin: 50% 50%;
  transition: transform 0.45s var(--ease), opacity 0.45s var(--ease);
  opacity: 0.75;
}

.contact:hover,
.contact:focus-visible { color: #fff; }

.contact:hover .contact__handle::after,
.contact:focus-visible .contact__handle::after { transform: scaleX(1); opacity: 1; }

.contact:hover .contact__tag,
.contact:focus-visible .contact__tag { color: var(--bone); }
.contact:focus-visible { outline: 1px solid var(--bone-dim); outline-offset: 4px; }

/* Tres destinos hermanos. En apaisado van en fila; en vertical, apilados: en
   fila el correo se sale por los lados en cuanto la pantalla es estrecha. */
.outro-links {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: clamp(1.6rem, 5vw, 4rem);
  flex-wrap: wrap;
  margin: 0;
  /* Sin `will-change`: el parallax ya no escribe aquí. Escribe en cada `.contact`
     —cada uno con su profundidad y su propio seguimiento— para que los tres
     enlaces se muevan como tres cosas y no como una tablilla. */
}

/* El correo es el más largo de los tres y el único que no se puede acortar.
   El suelo sube de 0.6rem a 0.82rem. El término en `vw` no alcanza al techo
   hasta 917 px de ancho, así que en un teléfono la dirección se dibujaba
   siempre a 9.6 px — el cuerpo más pequeño de toda la pieza para el dato con
   el que se contrata al actor. A 13.1 px con este tracking mide unos 181 px,
   así que sigue cabiendo de sobra en los 350 útiles de un 390. */
.outro-links .contact[href^="mailto:"] .contact__handle {
  font-size: clamp(0.82rem, 1.5vw, 0.94rem);
  letter-spacing: 0.16em;
  text-indent: 0.16em;
  text-transform: none;
  word-break: break-word;
}

@media (orientation: portrait) {
  .outro-links {
    flex-direction: column;
    align-items: center;
    gap: clamp(1.1rem, 3.4vh, 2rem);
  }
}

/* --------------------------------------------------- frases que no se parten */

/* Hay dos frases en la pieza que son el remate de su bloque y sólo funcionan
   si caen enteras y solas en la última línea: «Es mi principal territorio de
   investigación.» cerrando el statement, y «Es el lugar donde la escena
   comienza.» cerrando el taller. Partidas a mitad por el borde de la caja
   dejan de ser un remate y pasan a ser la cola del párrafo anterior.
   `display: block` es lo que la fuerza a empezar línea propia —sin él, el
   equilibrado sólo puede repartir, no separar—, y `text-wrap: balance` es la
   red de seguridad: cuando la frase NO cabe en una línea, reparte las dos
   mitades en vez de dejar una palabra suelta abajo.
   Dónde no cabe, dicho: en un teléfono vertical la del statement mide unos 414
   px contra 359 de ancho útil, y no cabe en una línea a ningún cuerpo
   razonable. Ahí `balance` la parte en dos equilibradas, que es lo mejor
   posible; en la caja de 48rem de escritorio sí entra entera.
   Se envuelve con un `<span>` que no cambia ni un carácter del texto. */
.frase-final {
  display: block;
  text-wrap: balance;
}

/* La palabra que se señala dentro de un párrafo. Sube por dos vías a la vez y
   por eso funciona sin gritar: el peso pasa de 300/400 a 600 —Jost es variable,
   así que es un peso dibujado, no una negrita sintética— y el tono sube de
   `--bone-body` al hueso lleno, que es el mismo escalón que la pieza ya usa
   para separar lectura de rotulación.
   600 y no 700: sobre una fotogrametría con grano el 700 de una geométrica
   engorda el contorno y la palabra se lee como un botón. Con 600 se lee como
   énfasis.
   Máximo dos por párrafo. Tres marcas en un bloque de lectura dejan de señalar
   nada porque ya no hay contraste con el resto. */
.clave {
  font-weight: 600;
  color: var(--bone);
}

/* ----------------------------------------------------------- statement */

/* Los cuatro golpes ocupan el mismo sitio: la timeline los releva uno a uno,
   así que están apilados en la misma celda de rejilla en vez de en flujo. Un
   golpe entrando mientras el anterior sale no debe empujar nada. */
.stage--statement { align-items: center; justify-content: center; }

/* 48rem, y el número no lo elige la medida sino la última frase. El bloque iba
   a 44rem (704 px) porque a 46 el golpe más largo pasaba de 45 caracteres por
   línea; pero el remate del cuarto golpe —«Es mi principal territorio de
   investigación.», 44 caracteres— mide unos 707 px al cuerpo máximo de 32.8 px,
   o sea tres píxeles más que la caja, y se partía. 48rem son 768 y entra.
   El precio son unos 49 caracteres por línea en el golpe más largo, cuatro por
   encima de lo que buscaba el valor anterior: barato, porque estos cuatro
   golpes son display que se releva de uno en uno, no lectura corrida.
   Centrado se queda. */
/* El bloque no estaba centrado en pantalla, y no era cosa del texto: el texto
   siempre estuvo centrado DENTRO de su caja, y la caja estaba a la izquierda.
   Es el mismo eslabón que faltaba en el taller. `.reveal` es un ítem flex de
   ancho automático, así que su ancho sale de su contenido; el contenido más
   ancho de un párrafo largo sin cortar es enorme, o sea que `.reveal` se
   estiraba hasta ocupar el ancho útil entero. Dentro de esos 1816 px, el
   `min(100%, 44rem)` del bloque resolvía a 704 y, con `margin: 0`, se quedaba
   pegado al canto izquierdo. `justify-content: center` del stage centraba
   `.reveal`, que ya medía todo — o sea que no centraba nada.
   Se arregla por los dos lados, igual que allí: ancho definido en `.reveal`
   —para que el porcentaje tenga contra qué resolverse— y `margin-inline: auto`
   en el bloque, que es lo que lo centra de verdad. */
.stage--statement > .reveal {
  width: 100%;
  min-width: 0;
}

.statement {
  display: grid;
  width: min(100%, 48rem);
  margin-inline: auto;
  position: relative;
  will-change: transform;   /* aquí escribe el parallax, nada más */
}

/* Aquí vivía un velo radial en un `::before`, el mismo mecanismo que llevan la
   bio, el crédito y el taller. Se retira: sobre un fondo que se mueve, un
   radial de 0.74 de negro no se lee como oscuridad sino como un recuadro
   semitransparente pegado encima del texto, y este bloque es el único de los
   cuatro que no tiene ni filete ni caja que justifique un borde.
   Lo que queda en su sitio es la sombra del propio texto, reforzada abajo.
   El aviso, dicho y no escondido: contra el hueso iluminado del vuelo rasante
   (luminancia estimada 0.35) el texto en `--bone` vuelve a 2.04:1 por la
   fórmula WCAG, que es lo que medía antes del velo. La sombra es contraste
   LOCAL —vive en los dos o tres píxeles que rodean el asta— y esa fórmula no
   la ve, así que el número no captura lo que se juzga mirando. Si al verlo
   sigue sin leerse, la palanca siguiente es `state.dim` —el canal ya existe y
   ya se usa en la galería y en el taller— durante la ventana del statement:
   atenuar la FUENTE, que es lo que funcionó en el taller, y no volver a poner
   un velo delante. */

/* Peso 300 y no 200. Estos tres golpes caen sobre el vuelo rasante, con la
   cámara a 0.22 de la esfera de encuadre: el hueso llena el cuadro y a peso
   200 el trazo se deshacía contra su textura.
   La pila de sombras: tres, todas CEÑIDAS. Sin el velo detrás, el trabajo de
   separar el texto del hueso lo hace entero esta pila, así que las dos
   primeras se refuerzan —una pegada al asta a negro pleno y una media que le
   da cuerpo— y la tercera baja de 34px a 16: un halo ancho sobre un fondo que
   se mueve es exactamente la mancha rectangular que se acaba de quitar, sólo
   que con las esquinas redondeadas. */
.statement__golpe {
  grid-area: 1 / 1;
  margin: 0;
  align-self: center;
  text-align: center;
  text-wrap: balance;
  font-size: clamp(1.2rem, 2.8vw, 2.05rem);
  font-weight: 300;
  /* 1.34 -> 1.46. Es la tercera vuelta sobre «se ve junto» y las dos anteriores
     fueron a los saltos ENTRE bloques; el interlineado es lo que no se había
     tocado, y en un golpe de cuatro líneas es el que manda sobre la sensación
     de apretado. Estos golpes se relevan apilados en la misma celda, así que
     crecer de alto no empuja nada. */
  line-height: 1.46;
  letter-spacing: 0.008em;
  color: var(--bone);
  text-shadow:
    0 1px 2px rgba(0, 0, 0, 1),
    0 2px 7px rgba(0, 0, 0, 0.95),
    0 0 16px rgba(0, 0, 0, 0.9);
  will-change: transform, opacity, filter;
}

/* El primer golpe es la frase que abre el statement y va sola: en cursiva y
   un punto más grande, para que se lea como cita y no como párrafo.
   Peso 200 -> 300. El 200 se justificaba con que a este cuerpo la escala ya lo
   sostiene, y eso vale sobre negro, no sobre una fotogrametría de tierra
   agrietada a plena luz — que es exactamente el fondo que le toca. En cursiva
   el asta es además más fina que en redonda, así que este golpe era el más
   frágil de los cuatro justo cuando el fondo está más vivo. Lo que lo separa
   de los otros tres sigue siendo la cursiva y el cuerpo, que son mecanismos, y
   no un peldaño de peso que sólo funciona sobre fondo liso. */
.statement__golpe:first-child {
  font-size: clamp(1.35rem, 3.3vw, 2.5rem);
  font-weight: 300;
  font-style: italic;
  line-height: 1.28;
}

@media (orientation: portrait) {
  .statement { width: 100%; }
}

/* --------------------------------------------------------------- taller */

.stage--taller {
  align-items: center;
  justify-content: center;
  /* El acolchado lateral es lo único que separa el bloque del canto de la
     pantalla cuando la columna ocupa el ancho útil entero, así que tiene que
     cubrir las dos cosas que pueden empujarlo hacia fuera: el parallax de
     ratón, que escribe hasta `--parallax-reserve` de translación sobre
     `.taller`, y el recorte físico de un teléfono con muesca en apaisado. */
  padding-left: max(var(--pad), var(--parallax-reserve), env(safe-area-inset-left));
  padding-right: max(var(--pad), var(--parallax-reserve), env(safe-area-inset-right));
}

/* El eslabón que faltaba, y la causa del texto cortado por la izquierda.
   `.taller` pide `width: min(100%, 36rem)`, pero ese 100% no tenía contra qué
   resolverse: `.reveal` es un ítem flex de ancho automático, así que su ancho
   salía del ancho de su contenido — que es justamente ese porcentaje. Ante el
   bucle, el navegador se queda con el lado fijo del `min()`, y el bloque medía
   36rem (42rem antes) fuera cual fuera la pantalla. En un `.stage` con
   `justify-content: center` el sobrante se reparte entre los dos bordes: la
   mitad izquierda cae fuera del cuadro y se come el arranque de cada línea.
   Por eso `overflow: hidden` no valía — el texto no estaba de más, estaba
   fuera de la pantalla.
   Con un ancho definido aquí el porcentaje ya resuelve contra el ancho útil
   del stage, y `min-width: 0` retira además el suelo de tamaño mínimo
   automático del ítem flex, que es lo que impedía encogerlo. */
.stage--taller > .reveal {
  width: 100%;
  min-width: 0;
}

/* Centrado, como el resto de la pieza. La ronda anterior lo puso en bandera
   izquierda porque es el único nodo con lectura corrida de verdad —seis o siete
   líneas seguidas de cuerpo— y un párrafo centrado obliga al ojo a buscar el
   arranque de cada línea. Sigue siendo cierto y es el coste de esta decisión;
   se compensa con lo único que se puede: medida corta y más aire entre bloques,
   los dos ya puestos. La pieza entera va centrada y este nodo desentonaba.
   36rem y no 42rem: a 42 el cuerpo pasaba de 80 caracteres por línea.
   `max-width: 100%` es el cinturón sobre los tirantes: pase lo que pase con el
   `min()`, la columna no puede ser más ancha que el hueco que la contiene. */
/* La MEDIDA de las tres cajas de lectura, y en `rem` a propósito. Estaba en
   `em` —27 la pregunta, 29 el cuerpo, 26 el remate— y `1em` vale lo que valga
   la fuente de cada pieza, así que tres cifras parecidas daban tres columnas de
   anchos MUY distintos: en escritorio la pregunta salía a 648 px (recortada a
   576 por la caja), el cuerpo a 533 y el remate a 507. Con el bloque centrado
   eso son tres columnas concéntricas de tres anchos, y el filete del remate
   —que mide lo que su caja— quedaba de un ancho que no era el de ningún texto.
   Eso es lo que se lee como desorden: no las líneas, los bordes de las cajas.
   En `rem` las tres miden lo mismo en píxeles pase lo que pase con el cuerpo de
   cada una, así que comparten los dos márgenes y el filete cae exactamente
   sobre el ancho del párrafo que cierra. 32rem son 512 px: unos 62 caracteres
   por línea en el cuerpo de escritorio, dentro del tope de lectura. */
.taller {
  position: relative;
  --medida: 32rem;
  width: min(100%, 36rem);
  max-width: 100%;
  margin: 0 auto;
  text-align: center;
  /* `isolation` y no `will-change: transform`. El velo de abajo va a
     `z-index: -1` y necesita un contexto de apilado propio para quedarse detrás
     del texto sin irse detrás de todo; hasta ahora ese contexto lo abría de
     rebote el `will-change`, que estaba puesto porque el parallax escribía
     aquí. Ya no escribe aquí —escribe en cada pieza— así que reservar una capa
     de composición para el bloque entero es pagar por nada. `isolation` dice lo
     que de verdad se necesita y no cuesta capa. */
  isolation: isolate;
}

/* Y las piezas sí llevan capa: son ellas las que el parallax mueve ahora. */
.taller__rotulo,
.taller__pregunta,
.taller__cuerpo,
.taller__remate,
.taller__cta { will-change: transform; }

/* El velo. Mismo mecanismo que en `.bio__col` y `.credit` —un radial negro en
   un ::before a z-index -1, dentro del contexto de apilado que abre el propio
   `will-change`— y por encima del canvas, así que atenúa tierra, hueso y
   niebla a la vez.
   Lo que cambia es la GEOMETRÍA, que era el defecto. `closest-side` sobre una
   caja rectangular dibuja la elipse INSCRITA: toca el centro de cada lado y
   deja las cuatro esquinas fuera. Medido sobre esta caja, el velo anterior
   valía 0.740 en el centro del bloque, 0.215 en el margen izquierdo —que en
   bandera izquierda es donde arranca cada una de las siete líneas—, 0.153 en
   el canto superior y 0.000 en las esquinas. O sea: no estaba donde está el
   texto. El comentario del cuerpo daba por hecho que "el contraste lo da el
   velo de detrás" y el velo llegaba al rótulo, al margen y al botón a cero.
   `farthest-corner` estira la elipse hasta pasar por la esquina del ::before,
   y con el ensanche del `inset` la meseta cubre la caja entera y el degradado
   a transparente ocurre YA FUERA del texto. Los mismos cuatro puntos pasan a
   0.800 / 0.745 / 0.741 / 0.569.
   Tercera vuelta, y esta vez cambia lo que hay DETRÁS. Con el macro, el taller
   ya no cae sobre negro con una calavera pequeña al lado: cae sobre la cuenca
   del ojo a sangre, materia iluminada de borde a borde. Dos cambios, los dos
   por esa causa:
   — La meseta sube de 0.80 a 0.84. Contra un hueso de luminancia 0.35 ya
     atenuado al 58% por el filtro del canvas, el fondo bajo el texto queda en
     0.0325: el cuerpo (`--bone-body`) llega a 7.72:1 y el rótulo y el remate,
     en hueso lleno, a 9.87:1. Con el velo solo, sin atenuar el canvas, serían
     6.01:1 y 7.68:1; con el atenuado solo y el velo viejo, 7.03:1 y 8.98:1; y
     sin ninguno de los dos, 5.31:1 y 6.78:1. Hacen falta los dos — el velo
     porque el degradado tiene que morir fuera del texto, y el atenuado porque
     el velo solo no puede con una fuente cuatro veces más luminosa que la que
     había antes detrás del bloque.
   — El `inset` se ensancha mucho (-40% -22%). Con negro detrás daba igual
     dónde terminara el degradado; sobre un fondo vivo, un borde de velo que
     cae dentro del cuadro se ve como una mancha con forma. Llevado tan afuera,
     lo que queda en pantalla es la meseta y el degradado muere fuera del
     encuadre, así que se lee como que la luz cede alrededor del texto y no
     como que hay un parche debajo. */
.taller::before {
  content: "";
  position: absolute;
  inset: -40% -22%;
  /* Cuarta vuelta, y esta vez el velo BAJA. La meseta pasa de 0.84 a 0.78 y las
     dos paradas siguientes con ella: el encargo pide ver un poco la tierra
     detrás del texto, y a 0.84 sobre un canvas ya atenuado lo que quedaba
     debajo del bloque era negro liso — el macro no se leía como materia sino
     como fondo apagado.
     Va acompañado de la subida del canvas (`dim` 1 -> 0.95 en scroll.js): los
     dos suman. Fondo bajo el texto de 0.0308 a 0.0441 de luminancia, un 43%
     más de tierra visible, y el cuerpo (`--bone-body`) cae de 7.72:1 a 6.79:1
     — por encima del 4.5:1 de AA con margen, que es el suelo que este bloque
     no puede cruzar por mucha textura que se quiera enseñar. */
  background: radial-gradient(farthest-corner,
    rgba(0, 0, 0, 0.78) 0%,
    rgba(0, 0, 0, 0.72) 46%,
    rgba(0, 0, 0, 0.46) 76%,
    transparent 100%);
  z-index: -1;
  pointer-events: none;
}

/* Y esta regla es la causa de que el bloque se leyera apelmazado, no los
   números de más abajo. `.taller p` puntúa 0-1-1 y `.taller__pregunta` puntúa
   0-1-0: la del elemento GANA a la de la clase por especificidad, y el orden de
   aparición no arregla eso — ni siquiera dentro de una media query, que no
   suma. O sea que los cuatro `margin-top` cuidadosamente repartidos entre
   bloques, y los de las cuatro ramas responsive, no llegaron a aplicarse nunca:
   los cinco párrafos iban pegados uno a otro con el salto de una línea normal.
   Lo único que se veía era el `padding-top` del filete —que nadie pisa— y el
   margen del botón, que es un `<a>` y no un `<p>`.
   Aquí sólo se anula el margen INFERIOR, que es lo que la regla quería quitar,
   y el superior lo pone cada pieza. El rótulo lo declara a cero porque es la
   primera y no lleva salto propio. */
.taller p { margin-bottom: 0; }

/* Los saltos van en dos escalones y sólo dos, para que se note qué va junto y
   qué va aparte: MID entre partes que se leen seguidas, BIG entre bloques.
   El sub va pegado al rótulo con un tercer valor mucho menor: son una unidad.
   Los tres usan vh, así que en una pantalla baja el bloque se comprime solo
   en vez de salirse por debajo. */
.taller__rotulo,
.taller__sub,
.taller__pregunta,
.taller__cuerpo,
.taller__remate {
  /* Sin `text-indent`: los rótulos lo llevaban para compensar ópticamente el
     tracking, pero cada pieza tiene el suyo distinto y con el bloque centrado
     eso deja cada línea desplazada una cantidad diferente. Mejor ninguna. */
  text-indent: 0;
  /* Explícito en cada pieza y no sólo heredado del contenedor: basta con que
     una sola línea rompa el centrado para que se lea como un descuido. */
  text-align: center;
  /* Las tres cajas de lectura llevan `max-width` propio, y una caja más
     estrecha que su contenedor se queda pegada a la izquierda por mucho que su
     texto vaya centrado: lo que hay que centrar es la caja, no sólo la línea. */
  margin-inline: auto;
}

.taller__rotulo {
  margin-top: 0;
  font-family: var(--font-rotulo);
  font-size: clamp(1.05rem, 2.4vw, 1.7rem);
  /* 300 -> 500, por lo mismo que el título del túnel: versal, cuerpo pequeño y
     un velo de luz por detrás. */
  font-weight: 500;
  line-height: 1.16;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--bone);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.95), 0 0 14px rgba(0, 0, 0, 0.9);
}

/* El suelo del `clamp` sube de 0.62rem a 0.70rem: el término preferido va en
   `vw` y en un teléfono de 390 px vale 4.7 px, así que en móvil manda SIEMPRE
   el mínimo. 0.62rem son 9.9 px con 0.3em de tracking — el tamaño de una nota
   al pie, no el de la única línea que dice de qué va el taller. */
.taller__sub {
  display: block;
  margin-top: 1em;
  font-size: clamp(0.7rem, 1.2vw, 0.84rem);
  font-weight: 400;
  letter-spacing: 0.3em;
  color: var(--bone-body);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.95), 0 0 14px rgba(0, 0, 0, 0.9);
}

/* La pregunta manda dentro del nodo: es la más grande de las tres voces de
   lectura y la única en cursiva.
   La medida va en `em` y no en `ch`: `1ch` es el ancho de la cifra 0, que en
   esta familia son 0.556em, pero la media real de un texto en castellano es
   0.45em por carácter. O sea que `52ch` no son 52 caracteres sino 64, y `60ch`
   son 74. Toda la medida de este bloque estaba contada con esa vara. */
.taller__pregunta {
  /* Más aire entre bloques. Todo el nodo iba con dos escalones de salto
     —MID entre lo que se lee seguido, BIG entre bloques— pero los dos eran
     cortos para un bloque de cinco piezas y el conjunto se leía como un solo
     párrafo largo. Los BIG suben de 1.6/3.6vh/2.8 a 2.2/5vh/3.6 y el MID del
     cuerpo de 1.1/2.4vh/1.7 a 1.6/3.4vh/2.4. Medido, el bloque crece 74 px en
     un 390x844 y sigue sobrando alto de sobra; las ramas de pantalla corta
     mandan sobre esto y no se tocan. */
  /* Tercera vuelta sobre lo mismo, y esta vez por el sitio que faltaba. Las
     dos anteriores subieron sólo los SALTOS entre bloques; el interlineado
     —que es lo que decide si un párrafo se lee apretado— no se había tocado
     nunca. Sube en las cuatro voces a la vez (1.3 -> 1.4 aquí) y los saltos dan
     un empujón más. Las ramas de pantalla corta mandan sobre esto y NO se
     tocan: ahí el alto es el recurso escaso. */
  margin-top: clamp(3.2rem, 7.2vh, 5.2rem);
  max-width: var(--medida);
  font-size: clamp(1.05rem, 2.2vw, 1.5rem);
  font-style: italic;
  font-weight: 300;
  line-height: 1.4;
  letter-spacing: 0.004em;
  /* `balance` y no `pretty`: son dos o tres líneas centradas y cortas, que es
     justo el caso en el que `pretty` sólo evita la viuda y deja la primera
     línea larga y la última a un tercio. Igualadas, el bloque se lee como un
     cuerpo con forma y no como un párrafo mal partido. */
  text-wrap: balance;
  color: var(--bone);
  text-shadow:
    0 1px 2px rgba(0, 0, 0, 0.95),
    0 0 10px rgba(0, 0, 0, 0.9),
    0 0 30px rgba(0, 0, 0, 0.85);
}

/* El cuerpo. Segunda vuelta, y la primera no llegó al sitio donde se juzga.
   La ronda anterior subió el TECHO del `clamp` de 0.98rem a 1.12rem, y el
   techo de `clamp(0.98rem, 1.5vw, 1.12rem)` sólo entra a partir de 1195 px de
   ancho de ventana; por debajo de 1045 px manda siempre el SUELO. En un
   teléfono —que es donde se revisa esto— el cuerpo nunca midió 1.12rem: midió
   0.98rem, 15.7 px. Y la rama de apaisado bajo 560 px de alto lo dejaba en
   0.78rem, 12.5 px. Así que aquí se levanta el SUELO, que es el número que un
   móvil llega a ver, y el techo se queda casi donde estaba: no es la misma
   subida otra vez, es la subida en el extremo que faltaba.
   Peso 300 -> 400. En Helvetica Neue el 300 es la Light, y a 16 px sobre una
   fotogrametría de tierra agrietada el asta se deshace contra el grano. El
   remate sigue separado por mecanismo —filete, tono lleno y cuerpo mayor—, no
   por peso.
   `-webkit-font-smoothing: auto` sólo aquí. En `body` la pieza pide
   `antialiased`, que en macOS y iOS quita el engorde de asta y es justo lo que
   quieres en display grande; en el único párrafo de lectura de 16 px y claro
   sobre oscuro hace lo contrario de lo que hace falta. Es el ajuste que no se
   había tocado en ninguna de las dos rondas.
   El color pasa a `--bone-body`: ver la nota del token en `:root`. */
.taller__cuerpo {
  margin-top: clamp(2.6rem, 5.6vh, 4rem);
  max-width: var(--medida);
  font-size: clamp(1.05rem, 1.5vw, 1.15rem);
  font-weight: 400;
  /* 1.62 -> 1.75. Es el único párrafo corrido del nodo —seis o siete líneas— y
     el que más nota el interlineado; 1.75 es lo alto que pide un texto claro
     sobre una fotogrametría, donde el ojo tiene que volver a encontrar el
     arranque de la línea siguiente contra un fondo con grano. */
  line-height: 1.75;
  letter-spacing: 0.004em;
  text-wrap: pretty;
  color: var(--bone-body);
  -webkit-font-smoothing: auto;
  text-shadow:
    0 1px 2px rgba(0, 0, 0, 0.95),
    0 0 10px rgba(0, 0, 0, 0.9),
    0 0 28px rgba(0, 0, 0, 0.85);
}

/* El remate ya no compite con la pregunta: compiten cuando las dos son texto
   corrido de tamaño parecido. Aquí se separa por mecanismo, no por cuerpo —
   filete encima, peso 400 y el tono lleno del hueso — así que se lee como el
   cierre del bloque y no como otro párrafo. */
.taller__remate {
  margin-top: clamp(2.6rem, 5.8vh, 4rem);
  padding-top: clamp(1.5rem, 3.2vh, 2.2rem);
  border-top: 1px solid rgba(232, 226, 214, 0.22);
  max-width: var(--medida);
  font-size: clamp(1rem, 1.9vw, 1.22rem);
  font-weight: 400;
  line-height: 1.55;
  text-wrap: balance;
  color: var(--bone);
  text-shadow:
    0 1px 2px rgba(0, 0, 0, 0.95),
    0 0 10px rgba(0, 0, 0, 0.9),
    0 0 30px rgba(0, 0, 0, 0.85);
}

/* Único destino pulsable del nodo, y en un teléfono se pulsa con el pulgar:
   `min-height` de 2.75rem = 44 px, que es el objetivo de área táctil. Con
   `inline-block` la altura salía de la línea más el acolchado y en móvil se
   quedaba en 29 px. El suelo del cuerpo sube igual que el del sub, y por lo
   mismo: el término en `vw` no llega nunca en una pantalla estrecha. */
.taller__cta {
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 2.75rem;
  margin-top: clamp(2.6rem, 5.8vh, 4rem);
  padding: 0.85em 1.9em;
  font-size: clamp(0.72rem, 1.1vw, 0.8rem);
  font-weight: 400;
  letter-spacing: 0.28em;
  text-indent: 0.28em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--bone);
  border: 1px solid var(--bone-dim);
  border-radius: 1px;
  transition: color 0.4s var(--ease), border-color 0.4s var(--ease),
              background-color 0.4s var(--ease);
}

/* Y sólo dentro del tramo del taller. Las siete secciones son `.stage` de
   `inset: 0` apiladas en el mismo sitio: una sección a opacidad cero sigue
   ocupando la pantalla entera, así que un destino pulsable fuera de su turno
   es un botón invisible cubriendo el cuadro. `.stage--taller` va después de
   `.stage--video` en el DOM, o sea por delante, y en un móvil tumbado este
   botón caía justo sobre la tarjeta del clip.
   La marca la escribe main.js desde el progreso (`TALLER_WINDOW`), igual que
   `.tunel.is-live` con el índice de obras. */
#reveal-taller.is-live .taller__cta {
  pointer-events: auto;   /* .overlay las desactiva por defecto */
}

.taller__cta:hover,
.taller__cta:focus-visible {
  color: #000;
  background: var(--bone);
  border-color: var(--bone);
}

.taller__cta:focus-visible { outline: 1px solid var(--bone); outline-offset: 4px; }

/* ------------------------------------------------------------- video card */

/* Only clickable inside its own stretch of the timeline — an invisible card
   swallowing pointer events for the rest of the scroll is a trap. */
.videocard.is-near { pointer-events: auto; }

.videocard {
  width: min(86vw, 60rem);
  /* The tilt is written by video.js on the inner node; the perspective has to
     live on the parent or every corner would get its own vanishing point. */
  perspective: 1400px;
}

.videocard__tilt {
  position: relative;
  transform-style: preserve-3d;
  will-change: transform;
}

.videocard__frame {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-radius: clamp(0.9rem, 2vw, 1.6rem);
  background: #050504;
  box-shadow:
    0 2rem 5rem rgba(0, 0, 0, 0.75),
    0 0 0 1px rgba(232, 226, 214, 0.09) inset;
}

.videocard__poster {
  display: block;
  width: 100%;
  height: 100%;
  padding: 0;
  border: 0;
  margin: 0;
  background: none;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}

.videocard__poster img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  /* Desaturated at rest so the card belongs to the piece; it returns to
     colour as the pointer approaches. */
  filter: grayscale(0.85) contrast(1.06) brightness(0.74);
  transition: filter 0.6s var(--ease), transform 0.9s var(--ease);
  transform: scale(1.02);
}

.videocard:hover .videocard__poster img,
.videocard__poster:focus-visible img {
  filter: grayscale(0.15) contrast(1.04) brightness(0.94);
  transform: scale(1.05);
}

.videocard__poster:focus-visible { outline: 1px solid var(--bone-dim); outline-offset: 4px; }

.videocard__play {
  position: absolute;
  left: 50%;
  top: 50%;
  width: clamp(3.2rem, 6vw, 4.6rem);
  height: clamp(3.2rem, 6vw, 4.6rem);
  transform: translate(-50%, -50%);
  border: 1px solid rgba(232, 226, 214, 0.55);
  border-radius: 50%;
  backdrop-filter: blur(2px);
  transition: border-color 0.5s var(--ease), transform 0.6s var(--ease);
}

.videocard__play::after {
  content: "";
  position: absolute;
  left: 54%;
  top: 50%;
  width: 0;
  height: 0;
  transform: translate(-50%, -50%);
  border-left: 0.85rem solid var(--bone);
  border-top: 0.55rem solid transparent;
  border-bottom: 0.55rem solid transparent;
}

.videocard:hover .videocard__play {
  border-color: var(--bone);
  transform: translate(-50%, -50%) scale(1.08);
}

/* Only there once the clip is running: it is the way back to the scroll. */
.videocard__close {
  position: absolute;
  top: clamp(-3.2rem, -5vw, -2.6rem);
  right: 0;
  /* 44 px: es el único destino pulsable mientras el clip corre y en un móvil
     es la salida. A 2.6rem se quedaba en 41.6. */
  width: 2.75rem;
  height: 2.75rem;
  display: none;
  place-items: center;
  padding: 0;
  border: 1px solid rgba(232, 226, 214, 0.4);
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.55);
  cursor: pointer;
  transition: border-color 0.4s var(--ease), background 0.4s var(--ease);
}

.videocard.is-playing .videocard__close { display: grid; }

.videocard__close:hover,
.videocard__close:focus-visible { border-color: var(--bone); background: rgba(0, 0, 0, 0.8); }
.videocard__close:focus-visible { outline: 1px solid var(--bone-dim); outline-offset: 3px; }

/* Aspa de dos trazos cruzados sobre el mismo centro. La versión anterior
   rotaba un trazo y colgaba el otro de él en absoluto sin origen, así que las
   dos barras acababan descuadradas y el icono no leía como una X. */
.videocard__close span {
  position: relative;
  display: block;
  width: 0.95rem;
  height: 0.95rem;
}

.videocard__close span::before,
.videocard__close span::after {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  width: 100%;
  height: 1px;
  background: var(--bone);
}

.videocard__close span::before { transform: rotate(45deg); }
.videocard__close span::after { transform: rotate(-45deg); }

.videocard__frame iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

/* Corner brackets. They sit slightly outside the frame and pull further out
   on hover, so the card reads as something being framed rather than a box. */
.videocard__corner {
  position: absolute;
  width: clamp(1.4rem, 2.6vw, 2.2rem);
  height: clamp(1.4rem, 2.6vw, 2.2rem);
  border: 1px solid rgba(232, 226, 214, 0.45);
  opacity: 0;
  transition: transform 0.7s var(--ease), opacity 0.5s var(--ease),
              border-color 0.5s var(--ease);
  pointer-events: none;
}

.videocard__corner--tl { top: -1px; left: -1px;   border-right: 0; border-bottom: 0; }
.videocard__corner--tr { top: -1px; right: -1px;  border-left: 0;  border-bottom: 0; }
.videocard__corner--bl { bottom: -1px; left: -1px;  border-right: 0; border-top: 0; }
.videocard__corner--br { bottom: -1px; right: -1px; border-left: 0;  border-top: 0; }

.videocard.is-near .videocard__corner { opacity: 0.5; }

.videocard:hover .videocard__corner,
.videocard:focus-within .videocard__corner {
  opacity: 1;
  border-color: var(--bone);
}

.videocard:hover .videocard__corner--tl { transform: translate3d(-0.75rem, -0.75rem, 40px); }
.videocard:hover .videocard__corner--tr { transform: translate3d( 0.75rem, -0.75rem, 40px); }
.videocard:hover .videocard__corner--bl { transform: translate3d(-0.75rem,  0.75rem, 40px); }
.videocard:hover .videocard__corner--br { transform: translate3d( 0.75rem,  0.75rem, 40px); }

/* ------------------------------------------------------------------ velo */

/* Negro puro por encima de todo. Sólo se usa para volver al principio: sube,
   la pieza salta a su primer fotograma detrás de él, y baja. */
.veil {
  position: fixed;
  inset: 0;
  z-index: 8;
  background: #000;
  opacity: 0;
  pointer-events: none;
  will-change: opacity;
}

/* ------------------------------------------------------------- back to top */

/* El cierre se sostiene por sus dos extremos: el perfil arriba, donde antes
   volvía el nombre, y el pie abajo. La calavera se queda con todo el centro. */
#reveal-contact {
  position: relative;
  flex: 1;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  gap: clamp(0.8rem, 2.5vh, 1.4rem);
}

/* El pie sólo carga el crédito. Volver arriba se va a la esquina: en el centro
   se comía la altura que necesita el bloque para caber bajo la calavera. */
.outro-foot {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.1rem;
}

/* El crédito de la casa: se lee si se busca, nunca antes. */
.powered {
  margin: 0;
  font-size: clamp(0.48rem, 1vw, 0.58rem);
  font-weight: 400;
  letter-spacing: 0.26em;
  text-indent: 0.26em;
  text-transform: uppercase;
  color: rgba(155, 148, 138, 0.4);
  text-shadow: 0 0 18px rgba(0, 0, 0, 0.9);
}

/* Sin `pointer-events` propio: lo enciende la regla de `body.is-end` junto al
   resto del cierre. Dejarlo aquí también funcionaba por especificidad, pero
   deja dos sitios diciendo cosas distintas sobre lo mismo. */
.powered a {
  color: rgba(155, 148, 138, 0.55);
  text-decoration: none;
  transition: color 0.4s var(--ease);
}

.powered a { cursor: pointer; }
.powered a:hover,
.powered a:focus-visible { color: var(--bone); text-decoration: underline;
  text-underline-offset: 0.35em; text-decoration-thickness: 1px; }
.powered a:focus-visible { outline: 1px solid var(--bone-dim); outline-offset: 3px; }

.to-top {
  flex: none;
  width: 2.9rem;
  height: 2.9rem;
  display: grid;
  place-items: center;
  padding: 0;
  border: 1px solid rgba(232, 226, 214, 0.28);
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.35);
  color: var(--bone);
  cursor: pointer;
  pointer-events: auto;
  opacity: 0;
  transition: border-color 0.5s var(--ease), background 0.5s var(--ease),
              opacity 0.6s var(--ease);
}

/* Only reachable once the piece has run its course. */
body.is-end .to-top { opacity: 1; }
body:not(.is-end) .to-top { pointer-events: none; }

.to-top:hover, .to-top:focus-visible {
  border-color: var(--bone);
  background: rgba(0, 0, 0, 0.6);
}

.to-top:focus-visible { outline: 1px solid var(--bone-dim); outline-offset: 4px; }

.to-top__arrow {
  width: 0.62rem;
  height: 0.62rem;
  border-left: 1px solid currentColor;
  border-top: 1px solid currentColor;
  transform: rotate(45deg) translate(0.1rem, 0.1rem);
  transition: transform 0.5s var(--ease);
}

.to-top:hover,
.to-top:focus-visible {
  border-color: rgba(232, 226, 214, 0.75);
  background: rgba(0, 0, 0, 0.7);
}

.to-top:hover .to-top__arrow { transform: rotate(45deg) translate(0.28rem, 0.28rem); }

/* -------------------------------------------------------- scroll affordance */

.scroll-hint {
  position: fixed;
  z-index: 5;
  left: 50%;
  bottom: calc(1.6rem + env(safe-area-inset-bottom));
  width: 1px;
  height: 3.2rem;
  transform: translateX(-50%);
  overflow: hidden;
  opacity: 0;
  pointer-events: none;
  background: rgba(232, 226, 214, 0.14);
}

.scroll-hint span {
  position: absolute;
  inset: 0;
  background: var(--bone);
  animation: hint 2.4s var(--ease) infinite;
}

/* La señal sube lo que ocupa el selector, que se le pone debajo. Va por `id` y
   no por clase a propósito: `.scroll-hint--video` es esta misma pieza en flujo
   bajo la tarjeta del clip, y esa no tiene que moverse de sitio. */
/* Sube 2.15rem más de lo que le tocaría por defecto: es el hueco que deja
   libre el icono de compartir, que se le pone debajo del idioma. */
#scroll-hint { bottom: calc(4.9rem + env(safe-area-inset-bottom)); }

.lang {
  position: fixed;
  z-index: 5;
  left: 50%;
  bottom: calc(2.75rem + env(safe-area-inset-bottom));
  transform: translateX(-50%);
  display: flex;
  gap: 0.85rem;
  /* Las dos, y no sólo la opacidad: GSAP las escribe juntas con `autoAlpha`, y
     la visibilidad es lo que impide que un selector invisible siga recogiendo
     clics a mitad del recorrido. */
  opacity: 0;
  visibility: hidden;
}

.lang__op {
  margin: 0;
  padding: 0.3rem 0.1rem;
  border: 0;
  background: none;
  font-family: var(--font-lectura);
  font-size: 0.62rem;
  font-weight: 400;
  letter-spacing: 0.26em;
  text-indent: 0.26em;
  color: var(--bone-dim);
  cursor: pointer;
  /* El mismo velo que lleva el crédito: la portada es fotogrametría iluminada,
     y un gris de 10 px sin sombra desaparece sobre el hueso. */
  text-shadow: 0 0 18px rgba(0, 0, 0, 0.9), 0 0 6px rgba(0, 0, 0, 0.8);
  transition: color 0.4s var(--ease);
}

.lang__op:hover,
.lang__op:focus-visible { color: var(--bone-body); }

/* El activo se distingue por luz y no por color: la paleta de la pieza es un
   solo hueso, y un segundo tono aquí sería la única nota de color del cuadro. */
.lang__op[aria-current="true"] { color: var(--bone); }

/* Mismo lenguaje que `.lang`: ni borde ni fondo, sólo el trazo sobre la
   fotografía. Un botón con chapa propia (como `.to-top`) se leería como una
   pieza de interfaz ajena flotando sobre la portada; este es un signo más,
   igual de desnudo que ES/EN/PT/FR. */
.share {
  position: fixed;
  z-index: 5;
  left: 50%;
  bottom: calc(0.75rem + env(safe-area-inset-bottom));
  transform: translateX(-50%);
  display: grid;
  place-items: center;
  margin: 0;
  padding: 0.3rem;
  border: 0;
  background: none;
  color: var(--bone-dim);
  cursor: pointer;
  opacity: 0;
  visibility: hidden;
  transition: color 0.4s var(--ease);
}

.share svg {
  width: 0.95rem;
  height: 0.95rem;
  display: block;
  filter: drop-shadow(0 0 10px rgba(0, 0, 0, 0.9)) drop-shadow(0 0 4px rgba(0, 0, 0, 0.8));
}

.share:hover,
.share:focus-visible { color: var(--bone-body); }

.share:focus-visible { outline: 1px solid var(--bone-dim); outline-offset: 4px; }

.share__toast {
  position: absolute;
  bottom: 100%;
  left: 50%;
  margin-bottom: 0.5rem;
  padding: 0.28rem 0.6rem;
  font-family: var(--font-lectura);
  font-size: 0.58rem;
  font-weight: 400;
  letter-spacing: 0.2em;
  text-indent: 0.2em;
  white-space: nowrap;
  color: var(--bone-body);
  background: rgba(0, 0, 0, 0.72);
  border: 1px solid rgba(232, 226, 214, 0.22);
  border-radius: 999px;
  opacity: 0;
  pointer-events: none;
  transform: translate(-50%, 0.25rem);
  transition: opacity 0.35s var(--ease), transform 0.35s var(--ease);
}

.share__toast.is-visible { opacity: 1; transform: translate(-50%, 0); }

@keyframes hint {
  0%   { transform: translateY(-100%); }
  60%  { transform: translateY(100%); }
  100% { transform: translateY(100%); }
}

/* La segunda señal, la del clip. Misma pieza que la de la portada —el mismo
   filete de 1 px con la misma gota cayendo— pero en flujo bajo la tarjeta en
   vez de fija al borde inferior: fija se pondría encima del propio vídeo en
   una ventana baja, y lo que tiene que decir es «debajo de esto hay más», que
   es una relación con la tarjeta y no con la pantalla.
   Aparece con la tarjeta (`.is-near`, que la escribe video.js desde la ventana
   de la línea de tiempo) y se va en cuanto el clip está sonando: mientras se
   ve el vídeo, invitar a seguir bajando es pedir que se deje a medias. */
.scroll-hint--video {
  position: relative;
  left: auto;
  bottom: auto;
  transform: none;
  height: 2.4rem;
  margin-top: clamp(1.1rem, 3.2vh, 2.2rem);
  transition: opacity 0.6s var(--ease);
}

.videocard.is-near + .scroll-hint--video { opacity: 1; }
.videocard.is-playing + .scroll-hint--video { opacity: 0; }

/* ---------------------------------------------------------------- preloader */

.preloader {
  position: fixed;
  inset: 0;
  z-index: 9;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1.4rem;
  background: #000;
  transition: opacity 0.9s var(--ease);
}

.preloader__count {
  font-size: clamp(0.72rem, 1.8vw, 0.86rem);
  font-weight: 400;
  letter-spacing: 0.4em;
  text-indent: 0.4em;
  color: var(--bone-dim);
  font-variant-numeric: tabular-nums;
}

.preloader__count::after { content: "%"; }

.preloader__bar {
  width: min(38vw, 240px);
  height: 1px;
  background: rgba(232, 226, 214, 0.16);
  overflow: hidden;
}

.preloader__bar i {
  display: block;
  width: 100%;
  height: 100%;
  background: var(--bone);
  transform: scaleX(0);
  transform-origin: left center;
}

body.is-ready .preloader { opacity: 0; pointer-events: none; }

/* --------------------------------------------------------------- scroll track */

/* 26 pantallas. La pieza pasó de tres nodos de texto a ocho, con 47 fotos en
   medio: con los 1500vh de antes, cada foto del túnel duraba dos décimas de
   pantalla y no daba tiempo a mirarla. */
.scroll-track { height: 2260vh; pointer-events: none; }

/* Un dedo recorre mucho menos track que una rueda, así que la misma longitud
   que en escritorio se lee como inercia, en un móvil se lee como una página
   atascada. Track más corto, misma coreografía. */
@media (orientation: portrait) {
  .scroll-track { height: 1530vh; }
}

/* ------------------------------------------------------------------ fallback */

body.no-webgl #stage,
body.no-webgl .overlay .stage--2,
body.no-webgl .overlay .stage--video,
body.no-webgl .scroll-hint,
body.no-webgl .scroll-track { display: none; }

body.no-webgl { overflow: hidden; }

body.no-webgl .fallback {
  display: block;
  background: #000 url("../img/fondo.webp") center 42% / cover no-repeat;
  filter: grayscale(1) contrast(1.16) brightness(0.6) blur(3px);
}

body.no-webgl .vignette { opacity: 1; }

body.no-webgl .stage--1 .reveal,
body.no-webgl .stage--3 .reveal {
  opacity: 1 !important;
  transform: none !important;
  filter: none !important;
}

body.no-webgl .name .char { opacity: 1 !important; transform: none !important; filter: none !important; }

/* Sin WebGL no hay línea de tiempo —`.scroll-track` va a `display: none` y
   `bailToFallback` sale antes de montar el scroll—, así que `body.is-end` no
   se enciende nunca y los tres enlaces del cierre se quedarían muertos. Aquí
   el cierre es lo único que hay en pantalla: se encienden de partida.
   `.to-top` no, y a propósito: su manejador se registra después del arranque
   que este camino no llega a hacer, así que sería un botón que no hace nada. */
body.no-webgl .contact,
body.no-webgl .powered a,
body.no-webgl .taller__cta { pointer-events: auto; }

/* ------------------------------------------------------------- orientation */

/* Landscape splits the tagline around the skull: one word hard left, the
   other hard right, with the 3D object holding the gap between them. */
@media (orientation: landscape) {
  /* El bloque sube: con las dos palabras repartidas en los cantos, la línea
     única caía por encima del cráneo y no había nada que esquivar. Apiladas, la
     segunda aterrizaba a la altura del arranque de los cuernos —donde son más
     gruesos— y el hueso se comía sus primeras letras. Ahí arriba las astas ya
     son puntas y el hueco entre las dos es ancho, así que INTERVENTION se lee
     entera y el cruce con el cuerno sigue existiendo, que es el efecto.
     Va en `.stage--2`, la clase que comparten las DOS capas: si sólo subiera
     una, las copias fantasma dejarían de caer una sobre otra. */
  .stage--2 { align-items: center; justify-content: center; padding-bottom: 34vh; }
  .js-tagline { width: 100%; }
  /* Las dos palabras van apiladas y cada una apoyada en un canto: la primera
     arranca en el izquierdo, la segunda termina en el derecho. El escalón es
     la diferencia de largo entre las dos —INTERVENCIÓN tiene doce letras y
     DRAMÁTICA nueve—, así que existe solo, sin ningún desplazamiento que
     calibrar, y se conserva igual en cualquier ventana. Antes el escalón salía
     de una caja ceñida a la palabra larga más un margen negativo de 2.2em en
     la primera: a ventanas anchas ese margen sacaba las primeras letras fuera
     del cuadro por la izquierda, y la segunda palabra se quedaba a media
     pantalla en vez de llegar al canto. */
  .tagline {
    display: flex;
    flex-direction: column;
    /* Un 11% menos de cuerpo que en el resto de la pieza (6.6vw/4.6rem ->
       5.9vw/4.1rem). INTERVENCIÓN mide 11.7 em con este tracking, o sea que en
       una ventana de 1024 ocupa tres cuartos del ancho: al cuerpo pleno la
       palabra larga tocaría los dos cantos a la vez y no quedaría escalón que
       ver. Los dos px de cuerpo no se ven; el escalón sí. */
    font-size: clamp(1.9rem, 5.9vw, 4.1rem);
    /* Mismo criterio que en vertical: sin descendentes en versalita las dos
       cajas se tocan, y a 1.05 con este pellizco de aire se leen como dos
       líneas de una frase y no como una palabra partida. */
    gap: 0.08em;
    line-height: 1.05;
  }

  .tagline .tw { display: block; }
}

/* Portrait: the tagline keeps the same step, at a smaller body and with the
   name shrunk so the horns stay clear of it. */
@media (orientation: portrait) {
  /* En vertical la frase es una frase de dos líneas, no dos palabras en los
     cantos del cuadro. Repartidas de borde a borde —que es lo que hacía
     `space-between`— la segunda caía a 10vh del pie, justo donde la calavera
     llena el encuadre, y de INTERVENTION sólo salían las últimas letras y en
     penumbra. Una palabra tapada no es profundidad, es una palabra tapada: en
     vertical manda que las dos se lean enteras, aunque el cuerno deje de
     cruzarlas.
     El cambio vive en `.stage--2`, que es la clase que comparten las DOS
     capas —`.overlay .stage--2` y `.underlay .stage--2-back`—, así que las dos
     siguen midiendo y colocándose igual. La regla que las separa
     (`align-items: flex-end` + `padding-bottom: 14vh` en la de atrás) es sólo
     de apaisado y aquí no entra: en vertical caen exactamente una sobre otra.
     El truco de las dos capas no se toca, sólo su sitio. */
  /* Y la frase baja. A 10vh arrancaba pegada al sitio que ocupa el nombre, y
     como la portada todavía se está yendo cuando la frase ya está entrando, las
     dos se leían encima la una de la otra. A 30vh cae en la franja de tierra
     libre que hay entre el nombre y los cuernos: la frase deja de disputarle el
     sitio a nada.
     El valor va en `.stage--2`, que es la clase que comparten las dos capas
     —la de delante y la de detrás del canvas—, así que las dos bajan lo mismo y
     el recorte del cuerno sobre las letras se conserva. */
  .stage--2 {
    justify-content: center;
    align-items: stretch;
    padding-top: 30vh;
    padding-bottom: calc(10vh + env(safe-area-inset-bottom));
    /* En vertical la amplitud del parallax baja a 12, o sea 19 px sobre la
       frase, y el acolchado normal ya los cubre. Reservar los 32 px de
       escritorio aquí sería quitarle a una pantalla de 390 px un ancho que
       necesita para la palabra larga. */
    --tagline-reserve: 0px;
  }

  .js-tagline { flex: 1 1 auto; width: 100%; display: flex; }

  .tagline {
    flex: 1;
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
    /* Interlineado propio entre las dos palabras: a 1.05 de `line-height` y
       sin descendentes en versalita, las dos cajas se tocan y la frase se lee
       como una sola palabra partida. */
    gap: 0.08em;
    max-width: 100%;
    /* La medida la fija la palabra larga: INTERVENCIÓN son doce letras y con
       este tracking mide unos 10em. A 9.2vw daba 359 px en una pantalla de
       390, que es más de los 350 que quedan libres una vez descontado el
       acolchado lateral: apoyada en el canto izquierdo, lo que antes
       desbordaba medio píxel por cada lado ahora desborda entero por la
       derecha. A 8.6vw mide 335 y entra con holgura. */
    font-size: clamp(1.85rem, 8.6vw, 3.9rem);
    /* Tracking más cerrado que en apaisado: la pantalla estrecha no da para
       abrir la letra sin partir la palabra. El margen negativo con el que la
       segunda alcanza el canto derecho lo lee de aquí. */
    --tw-track: 0.12em;
    /* Sin sangría: la que había compensaba el tracking colgante para centrar
       ópticamente, y aquí ya no se centra nada — cada palabra se apoya en su
       canto, y la sangría solo separaría la primera del suyo. */
    text-indent: 0;
    line-height: 1.05;
  }

  .tagline .tw { display: block; }

  .stage--bio { justify-content: center; align-items: center; }
  .name       { font-size: clamp(2.4rem, 11vw, 4.4rem); letter-spacing: 0.12em; text-indent: 0.12em; }
  .credit     { font-size: clamp(0.78rem, 3.2vw, 1rem); }
  .contact    { font-size: clamp(0.95rem, 4vw, 1.15rem); }

  /* No width to put the columns either side of the skull: they stack, one
     above it and one below, and the skull keeps the gap between them. The
     explicit height is what lets space-between push them apart — without it
     the two blocks meet in the middle and bury the object. */
  #reveal-bio { height: 100%; }

  .bio {
    flex-direction: column;
    align-items: center;
    justify-content: space-between;
    height: 100%;
    padding: 3vh 0 5vh;
    gap: 0;
  }

  .bio__col {
    max-width: 28ch;
    font-size: clamp(1.05rem, 4.5vw, 1.35rem);
    /* Algo más abierto que en apaisado: aquí las columnas van centradas, y un
       texto centrado necesita más blanco entre líneas para que el ojo
       encuentre el arranque de la siguiente. */
    line-height: 1.56;
    text-align: center;
  }

  .bio__col--right { text-align: center; }
  .videocard { width: 92vw; }

  /* El taller en vertical. La columna ocupa el ancho disponible, así que la
     medida la marca la pantalla y no el `min()`: a 1rem de cuerpo en un móvil
     de 390px salen unos 44 caracteres por línea, dentro del margen. Lo que sí
     cambia es que el bloque se hace largo —nueve líneas de cuerpo— y los
     saltos tienen que ceder para que quepa entero sin scroll interno. */
  /* La medida NO se suelta. `max-width: none` daba por hecho que en vertical
     manda siempre la pantalla, y eso sólo es cierto en un teléfono: en un iPad
     de 768 la línea del cuerpo se iba a 98 caracteres y en una ventana de
     escritorio estrechada a 900, a 114. El tope de lectura cómoda son 65. Con
     la medida en `em` puesta, la caja se queda en 63 en las dos y el teléfono
     no se entera, porque ahí quien recorta sigue siendo el ancho útil. */
  .taller { width: 100%; }

  /* Los saltos de vertical suben con los de apaisado, o en un teléfono —que es
     donde esto se mira— no se habría notado nada: esta rama pisa a la general.
     Siguen siendo más cortos que los de escritorio, porque aquí el bloque cae
     en el doble de líneas y el alto útil es el mismo. */
  /* Segundo empujón, y va aquí a la vez que en la general por el mismo motivo
     que el primero: esta rama PISA a la general, así que tocar sólo allí no se
     nota en un teléfono, que es donde se revisa. El interlineado sí lo heredan
     de la general —no hay `line-height` en esta rama— y por eso no se repite.
     Las dos ramas de vertical corto —≤820 y ≤660, más abajo— mandan sobre
     estos cuatro valores, así que esto sólo alcanza a los teléfonos altos y a
     las tabletas, que es donde hay alto para gastar. */
  .taller__pregunta { margin-top: clamp(2.8rem, 6.4vh, 4.4rem); }
  .taller__cuerpo   { margin-top: clamp(2.2rem, 4.9vh, 3.3rem); }
  .taller__remate   { margin-top: clamp(2.1rem, 4.9vh, 3.3rem); }
  .taller__cta      { margin-top: clamp(2.1rem, 4.9vh, 3.3rem); }

  /* Portrait stacks the block whole at the top — name, surname and credit
     as one unit — and deja el resto del alto para el cráneo. Un nombre
     partido entre los bordes leía como dos títulos distintos. */
  .stage--1 {
    justify-content: flex-start;
    padding-top: 7vh;
    padding-bottom: calc(7vh + env(safe-area-inset-bottom));
    gap: clamp(0.9rem, 3vw, 1.8rem);
  }

  #reveal-name { width: 100%; display: flex; }

  .name {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.04em;
    white-space: normal;
    font-size: clamp(2.2rem, 13vw, 5rem);
    letter-spacing: 0.14em;
    text-indent: 0.14em;
  }

  .name .word + .word { margin-left: 0; }

  /* Retrato apila las dos palabras, así que no queda hueco horizontal por
     donde cortar: cualquier recorte vertical parte las dos por la mitad. Aquí
     el título va entero detrás del canvas y la copia delantera sobra. */
  .stage--1-front { display: none; }
}

/* El taller en un teléfono VERTICAL, en dos escalones — como el apaisado, y por
   la misma razón: es el bloque más alto de la pieza y vive en un `.stage` de
   `inset: 0`, que no scrollea. Lo que no cabe sale del cuadro.
   Antes había un solo corte, en 720 px de alto, pensado para el 320x568. Con el
   interlineado subido y con la frase de cierre ocupando línea propia el bloque
   crece lo bastante como para que un teléfono de 740 u 800 —que quedaba fuera
   de aquel corte— se salga por abajo: medido, un 360x740 pedía 738 px contra
   700 de hueco. De ahí el escalón nuevo.
   El PRIMER escalón (≤820) recoge 667, 740, 800 y 812. Baja el cuerpo un 5%
   —de 16.8 a 16 px, no a 15.2— y afloja interlineado y saltos, que siguen
   siendo mucho más generosos que los del escalón corto porque aquí hay alto.
   Los saltos van en `vh` a propósito: dentro de la banda, un 812 recibe casi el
   doble de aire que un 667 sin necesitar un tercer corte.
   Los teléfonos de hoy —844, 852, 882, 915, 932— quedan fuera de las dos ramas
   y se llevan la escala general entera, que es lo que pedía el encargo. */
@media (orientation: portrait) and (max-height: 820px) {
  .stage--taller {
    padding-top: clamp(0.45rem, 1.6vh, 1.6rem);
    padding-bottom: calc(clamp(0.45rem, 1.6vh, 1.6rem) + env(safe-area-inset-bottom));
  }

  /* Los saltos de esta banda estaban por DEBAJO del interlineado del propio
     cuerpo: 17-22 px de separación entre bloques contra 25.3 px de línea a
     línea. Un salto más corto que el interlineado no separa nada — las cinco
     piezas se leían como un único párrafo largo, y eso es la mitad de por qué
     el bloque parecía desordenado. Suben a 28-35 px, claramente por encima de
     la línea. Medido en la proporción más justa de la banda (375x667, con la
     pregunta en tres líneas y el cuerpo en siete) el bloque pide 533 px contra
     645 de hueco: sobran 112, así que no se sale por ningún lado de la banda. */
  .taller__rotulo   { font-size: 1rem; }
  .taller__pregunta { font-size: 1rem; line-height: 1.34;
                      margin-top: clamp(1.7rem, 5.2vh, 3.4rem); }
  .taller__cuerpo   { font-size: 1rem; line-height: 1.58;
                      margin-top: clamp(1.4rem, 4.2vh, 2.8rem); }
  .taller__remate   { font-size: 1rem; line-height: 1.44;
                      margin-top: clamp(1.4rem, 4.2vh, 2.8rem);
                      padding-top: clamp(0.9rem, 2.6vh, 1.8rem); }
  .taller__cta      { margin-top: clamp(1.4rem, 4.2vh, 2.8rem); }
}

/* El SEGUNDO escalón, y el más apretado de la pieza en vertical: 568 y 640. Un
   320x568 es la proporción con menos margen de toda la lista —doce líneas de
   cuerpo en 280 px de caja— y aquí no hay `vh` que reparta: los saltos son
   fijos y cortos porque el alto que hay ya está todo repartido.
   Medido, el bloque queda en 532 px contra 554 de hueco. Sobran 22, que es el
   margen que hace falta para que la diferencia entre el avance real de la
   fuente y el estimado no se lo coma: a doce líneas, cada centésima de
   interlineado son 1.8 px.
   Lo único que no cede es el área táctil del botón: en vertical se pulsa con el
   pulgar. */
@media (orientation: portrait) and (max-height: 660px) {
  .stage--taller {
    padding-top: 0.45rem;
    padding-bottom: calc(0.45rem + env(safe-area-inset-bottom));
  }

  .taller__rotulo   { font-size: 0.95rem; }
  .taller__pregunta { font-size: 0.95rem; line-height: 1.3; margin-top: 0.65rem; }
  .taller__cuerpo   { font-size: 0.95rem; line-height: 1.42; margin-top: 0.45rem; }
  .taller__remate   { font-size: 0.95rem; line-height: 1.38; margin-top: 0.6rem;
                      padding-top: 0.35rem; }
  .taller__cta      { margin-top: 0.6rem; }
}

/* TERCER escalón, y existe por una razón concreta: los saltos entre bloques no
   se aplicaban (ver la nota de `.taller p`), así que el bloque medía menos de
   lo que su propia hoja de estilos pedía y las dos ramas de arriba estaban
   calibradas contra esa medida falsa. Con los saltos puestos, la única
   proporción real de la lista que se sale es un 320x480 —iPhone 4—: pedía 488
   px contra 466 de hueco. Aquí cede todo a la vez, tipografía incluida, que es
   lo único que queda por ceder cuando el alto útil es de 466 px.
   El área táctil del botón baja a 2rem por lo mismo que en apaisado corto: en
   480 px de alto, 44 px son un décimo del cuadro. */
@media (orientation: portrait) and (max-height: 500px) {
  .taller__rotulo   { font-size: 0.9rem; }
  .taller__sub      { font-size: 0.6rem; margin-top: 0.6em; letter-spacing: 0.26em; }
  .taller__pregunta { font-size: 0.9rem; line-height: 1.26; margin-top: 0.5rem; }
  .taller__cuerpo   { font-size: 0.9rem; line-height: 1.34; margin-top: 0.4rem; }
  .taller__remate   { font-size: 0.9rem; line-height: 1.3; margin-top: 0.45rem;
                      padding-top: 0.3rem; }
  .taller__cta      { font-size: 0.62rem; min-height: 2rem; margin-top: 0.45rem;
                      padding: 0.55em 1.3em; }
}

/* El acolchado del stage en CUALQUIER ventana apaisada que no sea alta. El
   `--pad` general se calcula con `vw`, así que en una pantalla ancha y baja
   reserva arriba y abajo un margen dimensionado por un ancho que aquí sobra: en
   un 1440x810 son 115 px de alto tirados justo donde no hay alto, y con la
   escala nueva el bloque pedía 715 contra los 695 que quedaban.
   El corte va en 900 y no en 680 —donde estaba— porque el problema no es de
   teléfonos tumbados sino de portátiles: 1366x768 y 1440x810 son ventanas de
   escritorio corrientes y las dos se salían. Va antes de las dos ramas de
   abajo para que ellas puedan seguir recortando más. */
@media (orientation: landscape) and (max-height: 900px) {
  .stage--taller {
    padding-top: 1.25rem;
    padding-bottom: calc(1.25rem + env(safe-area-inset-bottom));
  }

  /* Los saltos ceden aquí, y sólo aquí, porque esta banda es la que se quedó
     sin holgura cuando los `margin-top` del bloque empezaron a aplicarse de
     verdad (ver la nota de `.taller p`): un 1366x768 pedía 704 px contra 728 de
     hueco, o sea 24 de margen para toda la variación de métrica de la fuente.
     Con estos valores pide 657 y sobran 71. Siguen estando muy por encima del
     interlineado, que es lo que tienen que estar para que los cinco bloques se
     lean como cinco y no como un párrafo. Por encima de 900 px de alto no se
     toca nada: allí sobran 146. */
  .taller__pregunta { margin-top: clamp(2.2rem, 5.2vh, 3.6rem); }
  .taller__cuerpo   { margin-top: clamp(1.8rem, 4.2vh, 3rem); }
  .taller__remate   { margin-top: clamp(1.8rem, 4.2vh, 3rem); }
  .taller__cta      { margin-top: clamp(1.8rem, 4.2vh, 3rem); }
}

/* El taller contra ventanas apaisadas bajas. Es el bloque más alto de la pieza
   y vive en un `.stage` de `inset: 0`: si no cabe no scrollea, se sale del
   cuadro por arriba y por abajo. Aquí ya sólo cede la TIPOGRAFÍA: el recorte
   del acolchado, que era el primer ajuste de esta rama, se ha subido entero a
   la de ≤900 —el mismo valor y para toda la banda—, así que repetirlo aquí sólo
   dejaría un número que contradice al de arriba. */
@media (orientation: landscape) and (max-height: 680px) {
  .taller__rotulo   { font-size: clamp(1rem, 2.2vw, 1.45rem); }
  .taller__pregunta { font-size: clamp(1rem, 2vw, 1.25rem); margin-top: clamp(1.1rem, 3vh, 1.9rem); }
  .taller__cuerpo   { font-size: clamp(1rem, 1.4vw, 1.06rem); line-height: 1.55;
                      margin-top: clamp(0.8rem, 2vh, 1.3rem); }
  .taller__remate   { font-size: clamp(0.95rem, 1.7vw, 1.12rem); margin-top: clamp(1.1rem, 3vh, 1.9rem);
                      padding-top: clamp(0.7rem, 1.8vh, 1.1rem); }
  .taller__cta      { margin-top: clamp(1.1rem, 3vh, 1.9rem); }
}

@media (orientation: landscape) and (max-height: 560px) {
  .name    { font-size: clamp(1.8rem, 6vw, 3.4rem); }
  .tagline { font-size: clamp(1.3rem, 3.8vw, 2.5rem); }
  /* 0.9rem son 14.4 px para el único texto corrido de la biografía. Sube a
     15.7 y la medida pasa de `ch` a `em` por la vara de siempre: `32ch` no
     eran 32 caracteres sino 40. Medido en un 568x320, las dos columnas caen en
     siete líneas y el bloque ocupa 170 px de los 275 útiles. */
  .bio__col { font-size: 0.98rem; line-height: 1.55; max-width: 18em; }
  .videocard { width: min(70vw, 34rem); }

  /* Un móvil tumbado no tiene alto que repartir y el taller es el bloque más
     largo de la pieza: aquí se comprime entero —cuerpo, filete y saltos— o se
     sale por debajo del cuadro. La medida se ensancha a la vez, que es lo que
     le quita líneas al párrafo. */
  /* 0.4rem y no 0.6rem. Calculado con el bloque entero —siete líneas de cuerpo
     a 12.8 px, los tres saltos, el filete y el botón— un 568x320 pedía 312 px
     contra 301 de hueco útil: se salía por abajo por 11. Aquí cada píxel de
     acolchado es un píxel que el párrafo no tiene. */
  .stage--taller {
    padding-top: 0.4rem;
    padding-bottom: calc(0.4rem + env(safe-area-inset-bottom));
  }

  /* La caja se ensancha a 40rem porque aquí sobra ancho y falta alto: cada
     carácter que entra en una línea es una línea menos de párrafo. Lo que no
     se ensancha es la medida — se queda en 65 caracteres, que es donde a este
     cuerpo el párrafo ya cae en seis líneas: pasar de ahí no ahorra ni un
     píxel de alto y sólo empeora la lectura. */
  /* La medida también aquí en `rem` y una sola para las tres, por lo mismo que
     en la regla general: 29rem son los 36em a los que el cuerpo de 0.8rem cae
     en seis líneas —el número que esta rama tenía medido— y de paso la pregunta
     y el remate dejan de ir cada uno a un ancho distinto. */
  .taller { width: min(100%, 40rem); --medida: 29rem; }
  .taller__rotulo   { font-size: 0.92rem; letter-spacing: 0.14em; line-height: 1.14; }
  .taller__sub      { font-size: 0.55rem; margin-top: 0.5em; letter-spacing: 0.26em; }
  .taller__pregunta { font-size: 0.94rem; margin-top: 0.6rem; line-height: 1.24; }
  /* Aquí, y sólo aquí, la medida se suelta —a 29rem, ver arriba. Es la única
     proporción de la lista en la que el alto es el recurso escaso y el ancho
     sobra: cada carácter que entra en la línea es una línea menos de párrafo. A
     33em el cuerpo cae en siete líneas y el bloque se sale por abajo; a 36em
     —que es lo que valen 29rem a este cuerpo— cae en seis y entra con 20 px de
     sobra. Son unos 80 caracteres por línea, quince por encima del tope de
     lectura cómoda: es un intercambio consciente entre dos males —una medida
     larga se lee mal, una línea cortada por el borde de la pantalla no se lee—
     y no un descuido. El interlineado cede a la vez, de 1.42 a 1.38, que es lo
     que compra el margen para que la estimación no dependa de que la línea
     caiga justo. */
  .taller__cuerpo   { font-size: 0.8rem; margin-top: 0.5rem; line-height: 1.38; }
  .taller__remate   { font-size: 0.86rem; margin-top: 0.5rem; padding-top: 0.4rem;
                      line-height: 1.36; }
  /* El área táctil cede a 2rem: en 320 px de alto, 44 px son un séptimo del
     cuadro y el bloque no cabe. Un teléfono tumbado es la postura en la que
     menos se pulsa y la única en la que el texto se corta si no cede algo. */
  .taller__cta      { font-size: 0.62rem; min-height: 2rem; margin-top: 0.5rem; padding: 0.6em 1.4em; }

  /* El cierre en un móvil tumbado no tiene alto que repartir: la calavera se
     come el centro entero y el pie, apilado en mitad del borde inferior, caía
     justo encima de ella. Aquí el pie se abre en fila y se va a las dos
     esquinas, que es el único sitio del cuadro que la figura no ocupa. */
  #reveal-contact { justify-content: flex-start; }

  .outro-foot {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    /* Invertido respecto al DOM: el crédito a la izquierda y el botón a la
       derecha, que es la mano con la que se sostiene el teléfono tumbado. */
    flex-direction: row-reverse;
    align-items: center;
    justify-content: space-between;
    gap: 0;
  }

  /* El handle baja del rostro al pecho: sobre la cara tapaba lo único que la
     fotografía tiene que enseñar. */
  .contact { margin-top: 27vh; padding-top: 0; }
}

/* -------------------------------------------------------- reduced motion */

@media (prefers-reduced-motion: reduce) {
  .scroll-hint span { animation: none; }
  .preloader { transition-duration: 0.01ms; }
}

/* ---------------------------------------------------------------- obras */

/* El túnel. Las fotos viajan por el eje Z hacia la cámara y la atraviesan; la
   geometría la escribe js/tunnel.js y aquí solo está el escenario, la caja de
   cada foto y lo que tiene que seguir vivo con la pieza quieta. */

.stage--obras {
  align-items: center;
  justify-content: center;
  /* Sin padding: el túnel es a sangre por los cuatro lados, que es lo que hace
     que una foto pueda salirse del cuadro al pasar de largo. */
  padding: 0;
}

/* El wrapper del reveal no puede encogerse a su contenido: las fotos son
   absolutas y no le dan altura, y el escenario tiene que medir la pantalla
   entera para que la perspectiva sea la del viewport. */
.stage--obras > .reveal {
  position: absolute;
  inset: 0;
}

.tunel {
  position: relative;
  width: 100%;
  height: 100%;
  /* Aquí escribe el parallax de ratón, y solo él. */
  will-change: transform;
}

/* La perspectiva va en el contenedor y no en cada foto: una sola cámara para
   las 47, que es lo que hace que compartan punto de fuga. `overflow: hidden`
   recorta lo que ya ha pasado de largo — con el translateZ de salida de +410
   sobre esta perspectiva la foto sale del cuadro a 1.6 aumentos. Este número y
   la tabla `Z` de tunnel.js son el mismo ajuste visto desde dos sitios: tocar
   uno sin el otro cambia cuánto crece la foto al pasar. */
.tunel__escena {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  perspective: 1100px;
  perspective-origin: 50% 46%;
  transform-style: preserve-3d;
  overflow: hidden;
}

.tunel__foto {
  position: absolute;
  margin: 0;
  transform-style: preserve-3d;
  backface-visibility: hidden;
  /* Fuera de su recorrido no se dibuja y no se le escribe nada. */
  visibility: hidden;
}

/* Solo mientras está en el tramo visible. Puesto en las 47 son 47 capas de
   composición reservadas encima de un canvas WebGL a pantalla completa. */
.tunel__foto--cerca { will-change: transform, opacity, filter; }

/* Dos opacidades anidadas que se multiplican al componer: la del recorrido va
   en la figura y la escribe el JS por posición; la atenuación de las que no
   son la activa va aquí y la lleva una transición. Separadas a propósito —
   `update` solo corre cuando el scroll se mueve, así que un fundido calculado
   en JS se quedaría a medias en cuanto la rueda se para. */
.tunel__flota {
  width: fit-content;
  transform-style: preserve-3d;
  opacity: 0.14;
  transition: opacity 0.42s var(--ease), transform 0.5s var(--ease);
}

/* Flotación leve, solo la activa. Va en CSS y no en el bucle de scroll porque
   tiene que seguir respirando con la pieza parada, que es justo cuando se mira
   la foto. Deliberadamente al margen de `prefers-reduced-motion`. */
.tunel__foto--activa .tunel__flota {
  opacity: 1;
  animation: tunel-flota 6.5s ease-in-out infinite;
}

@keyframes tunel-flota {
  0%, 100% { transform: translate3d(0, 0, 0) rotateZ(0deg); }
  50%      { transform: translate3d(0, -7px, 0) rotateZ(0.4deg); }
}

/* La caja de la foto no depende del archivo que haya cargado: el `src` de
   arranque es el placeholder de 24 px, así que el tamaño sale de `--ratio` y
   de la ventana. Sin esto la foto mediría 24 px hasta que llegara la buena. */
.tunel__foto img {
  display: block;
  height: min(66vh, calc(80vw / var(--ratio)));
  height: min(66svh, calc(80vw / var(--ratio)));
  width: auto;
  aspect-ratio: var(--ratio);
  /* Despega la foto del negro: contacto cercano y sombra proyectada. */
  box-shadow:
    0 2px 12px rgba(0, 0, 0, 0.55),
    0 26px 70px -20px rgba(0, 0, 0, 0.8);
}

/* El rótulo del capítulo: entra con su primera foto, informa y se va antes de
   que llegue la segunda. Por encima de la activa, que va a z-index 9999. */
.tunel__rotulo {
  position: absolute;
  left: var(--pad);
  right: var(--pad);
  top: clamp(1.5rem, 7vh, 6rem);
  z-index: 10000;
  text-align: center;
  opacity: 0;
  pointer-events: none;
}

/* Misma familia y misma escala que el resto de la pieza: versalitas con el
   `letter-spacing` abierto, exactamente el tratamiento que ya tenían los
   rótulos de obra. */
.tunel__titulo {
  margin: 0;
  font-family: var(--font-rotulo);
  font-size: clamp(1.1rem, 2.5vw, 1.85rem);
  /* 200 -> 500. Este rótulo aparece a 1.1rem sobre las fotos del túnel, que es
     el sitio con el fondo más sucio de la pieza; en versal y con este cuerpo,
     una serif por debajo de 500 se deshace contra la imagen. */
  font-weight: 500;
  letter-spacing: 0.14em;
  text-indent: 0.14em;
  text-transform: uppercase;
  line-height: 1.24;
  color: var(--bone);
  text-shadow: 0 0 26px rgba(0, 0, 0, 0.9);
}

/* La sinopsis es lectura, no rotulación, y estaba tratada como rotulación:
   `--bone-dim` a peso 200 y con una sola sombra difusa. Sobre negro puro ese
   tono da 7.00:1 y parece de sobra, pero el túnel no es negro: la pared de
   tierra se queda al 50% durante todo el recorrido y la niebla pasa por
   delante. Contra un fondo de luminancia 0.10 —la tierra cedida, que es lo que
   hay detrás del rótulo— `--bone-dim` se desploma a 2.33:1 y `--bone-body`
   sube a 4.25:1; sobre negro, a 12.74:1. El tramo que le falta a 4.5 lo cubre
   la pila de sombras, que es contraste local y no global.
   El peso sube a 300 por lo mismo que subió el del taller: a 200 sobre una
   imagen con detalle el asta se deshace. Y la pila de sombras pasa a ser la de
   tres que usan la bio y el taller — la difusa sola oscurece la zona pero no
   separa el trazo de lo que tiene justo debajo.
   El suelo del `clamp` sube de 0.74rem a 0.82rem: el término preferido va en
   `vw` y no alcanza al suelo hasta 1147 px de ancho, así que en tableta y
   móvil manda siempre el mínimo.
   La medida pasa de `52ch` a `29em` por la misma vara que el taller: `1ch` es
   el ancho de la cifra 0 y en esta familia sale un 23% más ancho que el
   carácter medio del castellano, así que `52ch` no eran 52 caracteres sino
   64. */
.tunel__sinopsis {
  margin: clamp(0.7rem, 1.8vh, 1.2rem) auto 0;
  max-width: 29em;
  font-size: clamp(0.82rem, 1.15vw, 0.92rem);
  font-weight: 400;
  line-height: 1.58;
  text-wrap: pretty;
  color: var(--bone-body);
  text-shadow:
    0 1px 2px rgba(0, 0, 0, 0.95),
    0 0 10px rgba(0, 0, 0, 0.9),
    0 0 26px rgba(0, 0, 0, 0.85);
}

/* Contador: abajo, discreto, en la línea del capítulo. */
.tunel__pie {
  position: absolute;
  left: var(--pad);
  right: var(--pad);
  bottom: calc(clamp(1rem, 4vh, 3rem) + env(safe-area-inset-bottom));
  z-index: 10000;
  display: flex;
  justify-content: center;
  align-items: baseline;
  gap: clamp(0.8rem, 2vw, 1.6rem);
  /* 0.6rem son 9.6 px, y el término en `vw` no llega al suelo hasta 1010 px de
     ancho: en tableta y móvil el pie medía siempre 9.6 px con 0.26em de
     tracking sobre las fotos. Es la única cosa que dice por dónde va el
     recorrido. */
  font-size: clamp(0.72rem, 0.95vw, 0.8rem);
  font-weight: 400;
  letter-spacing: 0.26em;
  text-indent: 0.26em;
  text-transform: uppercase;
  color: var(--bone-body);
  pointer-events: none;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.95), 0 0 18px rgba(0, 0, 0, 0.9);
}

/* Cifras de ancho fijo: sin esto el contador se mueve al pasar de 09 a 10. */
.tunel__cuenta { font-variant-numeric: tabular-nums; }

/* Índice de obras. Siete accesos, uno por obra, en el orden del manifiesto.
   Índice de catálogo y no barra de navegación: la misma familia y la misma
   versalita abierta que el pie, sin cajas, sin fondos y sin más peso que el
   contador que tiene debajo.
   Vive dentro de `#reveal-obras`, así que se funde con la sección; lo que
   decide si además existe —clics y tabulación— es `.tunel.is-live`, que
   main.js enciende sólo dentro del recorrido del túnel. `visibility` y no sólo
   `opacity`: una fila de botones invisibles que se pueden tabular es una
   trampa igual que una que se puede pulsar. */
.tunel__indice {
  position: absolute;
  left: var(--pad);
  right: var(--pad);
  bottom: calc(clamp(2.6rem, 8vh, 5rem) + env(safe-area-inset-bottom));
  z-index: 10000;
  display: flex;
  /* Se parte en dos filas cuando los nombres no caben. Nunca una barra que se
     arrastra de lado: eso es un gesto más que aprender dentro de una pieza que
     va de un solo gesto. */
  flex-wrap: wrap;
  justify-content: center;
  align-items: baseline;
  gap: 0 clamp(0.5rem, 1.6vw, 1.4rem);
  visibility: hidden;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.5s var(--ease), visibility 0.5s;
}

.tunel.is-live .tunel__indice {
  visibility: visible;
  opacity: 1;
}

/* Un índice es texto que se lee para decidir, así que va en el tono de lectura
   y no en el de los filetes: contra el fondo del túnel (luminancia 0.10)
   `--bone-dim` se queda en 2.33:1 y `--bone-body` llega a 4.25:1.
   La obra activa sigue distinguiéndose —hueso lleno más el filete de
   debajo—, que es un mecanismo y no un peldaño de tono.
   Y `min-height` de 2.75rem = 44 px, el objetivo de área táctil: con
   `padding` sobre una caja de línea el alto se quedaba en 28 px, y esto es una
   fila de siete destinos que en un móvil se pulsan con el pulgar. `inline-flex`
   y no `block` para que el `align-items: baseline` de la fila siga teniendo una
   línea base con la que alinear. */
.tunel__acceso {
  margin: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 2.75rem;
  padding: 0.45em 0.2em;
  background: none;
  border: 0;
  border-bottom: 1px solid transparent;
  font-family: var(--font-display);
  font-size: clamp(0.66rem, 0.92vw, 0.74rem);
  font-weight: 400;
  letter-spacing: 0.22em;
  text-indent: 0.22em;
  text-transform: uppercase;
  color: var(--bone-body);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.95), 0 0 18px rgba(0, 0, 0, 0.9);
  cursor: pointer;
  pointer-events: auto;   /* .overlay los desactiva por defecto */
  transition: color 0.4s var(--ease), border-color 0.4s var(--ease);
}

/* El hover subía el color a hueso, que es EXACTAMENTE la marca de la obra
   activa: pasar por encima de un acceso lo hacía parecer el que estás viendo.
   Ahora el hover trae su propio filete a media luz —la activa lo lleva lleno—
   así que las dos señales conviven y ninguna miente. */
.tunel__acceso:hover,
.tunel__acceso:focus-visible {
  color: var(--bone);
  border-bottom-color: rgba(232, 226, 214, 0.30);
}

/* La obra que se está viendo. La marca la escribe tunnel.js derivándola del
   progreso, no de un estado propio que pueda desincronizarse. */
.tunel__acceso.is-activa {
  color: var(--bone);
  border-bottom-color: rgba(232, 226, 214, 0.55);
}

.tunel__acceso:focus-visible {
  outline: 1px solid var(--bone);
  outline-offset: 3px;
  color: var(--bone);
}

/* La cifra sólo se enseña donde el nombre no cabe. */
.tunel__acceso-num { display: none; font-variant-numeric: tabular-nums; }

@media (orientation: portrait) {
  .tunel__foto img {
    height: min(52vh, calc(86vw / var(--ratio)));
    height: min(52svh, calc(86vw / var(--ratio)));
  }
  .tunel__titulo { font-size: clamp(1rem, 5vw, 1.4rem); }
  /* El suelo sube de 0.72rem a 0.84rem. Aquí el término en `vw` sí entra —a
     partir de 263 px de ancho—, así que en un teléfono manda el preferido y el
     suelo sólo protege la ventana estrechísima; aun así, 11.5 px es tamaño de
     nota al pie para la única línea que cuenta de qué va la obra. */
  .tunel__sinopsis { font-size: clamp(0.84rem, 3.2vw, 0.94rem); }
  /* Sin regla propia, el pie heredaba el suelo de 0.72rem: en vertical es la
     línea que dice por qué foto de 47 va el recorrido. */
  .tunel__pie { font-size: 0.76rem; }

  /* El índice en vertical. Siete nombres largos no caben en fila —"Le poison,
     c'est moi" solo ya se come media pantalla— y partirlos en tres filas
     convierte un índice discreto en un bloque de texto encima de las fotos.
     Se queda la cifra: el índice de catálogo sin el título, que ya lo dicen el
     rótulo al entrar en la obra y el pie mientras se recorre. La fila cabe
     entera y sigue siendo un solo gesto. */
  .tunel__acceso-nombre { display: none; }
  .tunel__acceso-num { display: inline; }

  .tunel__indice { gap: 0 0.1rem; }

  .tunel__acceso {
    font-size: 0.8rem;
    letter-spacing: 0.08em;
    text-indent: 0.08em;
    /* Área de toque: la cifra son dos caracteres y sin esto el blanco de
       alrededor decide el acierto en vez del dedo. El alto ya lo pone el
       `min-height` de 44 px de la regla base; aquí se reparte el ancho.
       0.75em y no más: siete accesos tienen que caber en una sola fila, y el
       caso estrecho es un 320 px —20 px de acolchado a cada lado, 6 huecos de
       1.6 px—, donde a este cuerpo cada acceso mide 36.5 px y la fila entera
       265 de los 280 disponibles. Con 0.85em se pasaba a 283 y el índice se
       partía en dos filas justo encima del contador. */
    padding: 0.6em 0.75em;
  }
}
