/* =========================================================================
   OFICINA DE CARTAS MUERTAS
   Paleta muestreada de los propios archivos. Tres familias tipográficas,
   una por cada cultura documental que choca en el fondo:
     EB Garamond    (humanística renacentista)  — la voz del archivo
     Courier Prime  (máquina de escribir)       — lo que la oficina estampó
     Archivo Narrow (grotesca condensada)       — solo los nombres cancelados

   Autoalojadas en fonts/, subconjunto latin de Google Fonts, 98 KB en total.
   Antes la pila era de fuentes del sistema encabezada por las TeX Gyre, que
   casi nadie tiene instaladas: --swiss caía hasta Arial y los nombres
   cancelados — el elemento más visible de la página — perdían la condensación
   que los define. Las cadenas de reserva se conservan tal cual, así que
   abrir el archivo con doble clic sigue siendo legible aunque CORS bloquee
   las fuentes sobre file://.
   ========================================================================= */

@font-face {
  font-family: "EB Garamond"; font-style: normal; font-weight: 400;
  font-display: swap; src: url("../fonts/eb-garamond-400.woff2") format("woff2");
}
@font-face {
  font-family: "EB Garamond"; font-style: italic; font-weight: 400;
  font-display: swap; src: url("../fonts/eb-garamond-400-italic.woff2") format("woff2");
}
@font-face {
  font-family: "Courier Prime"; font-style: normal; font-weight: 400;
  font-display: swap; src: url("../fonts/courier-prime-400.woff2") format("woff2");
}
@font-face {
  font-family: "Courier Prime"; font-style: normal; font-weight: 700;
  font-display: swap; src: url("../fonts/courier-prime-700.woff2") format("woff2");
}
@font-face {
  font-family: "Archivo Narrow"; font-style: normal; font-weight: 700;
  font-display: swap; src: url("../fonts/archivo-narrow-700.woff2") format("woff2");
}

:root {
  --red:         #EE0001;   /* tachones del cartel */
  --violet:      #600575;   /* tipografía del cartel */
  --violet-lt:   #C9A6E8;   /* mismo papel sobre fondo oscuro */
  --parchment:   #E0DAC8;   /* vitela del manuscrito */
  --parchment-2: #EDE8DA;
  --dark:        #14110E;
  --dark-2:      #1E1913;
  --ink:         #412C1A;
  --gold:        #D5A059;
  --lapis:       #2B6BA9;
  --leaf:        #43734E;
  --copper:      #CB7540;
  --poster:      #FEFEA4;

  --serif:  "EB Garamond", "TeX Gyre Pagella", "Palatino Linotype", Palatino, "Book Antiqua", Georgia, serif;
  --type:   "Courier Prime", "Courier 10 Pitch", "TeX Gyre Cursor", "Courier New", Courier, monospace;
  --swiss:  "Archivo Narrow", "TeX Gyre Heros Cn", "Helvetica Neue Condensed", "Arial Narrow", "Helvetica Neue", Helvetica, Arial, sans-serif;

  /* Topes de render. Ninguna imagen por encima de su ancho intrínseco. */
  --cap-polaroid: 340px;   /* intrínseco 368 */
  --cap-venice:   600px;   /* intrínseco 683 */
  --cap-fern:     620px;   /* intrínseco 706 */
  --cap-deniro:   720px;   /* intrínseco 800 */
  --cap-landing:  540px;   /* el recorte aterriza a 258px: límite del matte */

  --gut: clamp(20px, 5vw, 64px);
  --p: 0; --p1: 0; --p1b: 0; --p2: 0; --p3: 0;
}

* { box-sizing: border-box; }

/* El fondo va en html a proposito: si solo lo lleva body, el navegador lo
   propaga al lienzo raiz y mix-blend-mode se queda sin fondo contra el que
   mezclar. Las mariposas pintadas se verian como cajas opacas. */
html { scroll-behavior: smooth; background: var(--parchment); }

/* Los cuerpos de texto suben un 12% respecto a la versión con Palatino: medido
   en canvas, la altura de x de EB Garamond es 41 contra 46 a igual tamaño en
   píxeles. Sin el ajuste, el mismo rem se lee notablemente más pequeño.
   Casi todos los tamaños de la hoja están en rem, así que no basta con tocar
   body: hay que subir cada uno. Los de --type no se mueven (Courier Prime es
   métricamente compatible con Courier New) y los de --swiss suben solo un 10%,
   porque Archivo Narrow conserva la altura de x de Arial y solo gana sitio a lo
   ancho: compone un 18% más estrecho. */
body {
  margin: 0;
  background: var(--parchment);
  color: var(--ink);
  font: 400 1.19rem/1.6 var(--serif);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img { max-width: 100%; height: auto; display: block; }

.sr-only {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

::selection { background: var(--red); color: var(--poster); }

:focus-visible {
  outline: 3px solid var(--red);
  outline-offset: 3px;
}

/* --- grano de papel: una sola capa fija ------------------------------- */
.grain {
  position: fixed; inset: -180px; z-index: 90;
  pointer-events: none;
  background: url("../img/grain.png") repeat;
  background-size: 180px 180px;
  mix-blend-mode: multiply;
  opacity: .085;
  /* Doce saltos por segundo: la cadencia del grano de una proyeccion, no una
     deriva continua. Se anima transform y no background-position a proposito:
     esto es una capa fija a pantalla completa con mix-blend-mode, y repintarla
     doce veces por segundo cuesta; moverla por el compositor no. El inset
     negativo da margen para que el desplazamiento no descubra el borde. */
  animation: grain-drift 1s steps(1, end) infinite;
  will-change: transform;
}
@keyframes grain-drift {
  0%     { transform: translate(0, 0); }
  8.33%  { transform: translate(-11px, 6px); }
  16.66% { transform: translate(7px, -13px); }
  25%    { transform: translate(-16px, -4px); }
  33.33% { transform: translate(12px, 9px); }
  41.66% { transform: translate(-5px, -15px); }
  50%    { transform: translate(15px, 3px); }
  58.33% { transform: translate(-9px, 12px); }
  66.66% { transform: translate(4px, -8px); }
  75%    { transform: translate(-14px, -11px); }
  83.33% { transform: translate(10px, 14px); }
  91.66% { transform: translate(-3px, -6px); }
}

/* --- estructura ------------------------------------------------------- */
.act { position: relative; padding: clamp(64px, 12vh, 140px) 0; }
.act--door { padding: 0; background: var(--parchment); }

.wrap {
  width: min(1180px, 100% - var(--gut) * 2);
  margin-inline: auto;
}
.wrap + .wrap { margin-top: clamp(48px, 9vh, 110px); }

.wrap--split {
  display: grid; gap: clamp(28px, 4vw, 60px);
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: start;
}
.col > * + * { margin-top: 1.1em; }

/* =========================================================================
   Sistema de cancelación: anunciar mal, tachar, reemplazar por algo peor.
   ========================================================================= */

.cancel { margin: 0; line-height: 1.24; }

.cancel__num {
  display: block;
  font: 400 .78rem/1 var(--type);
  letter-spacing: .06em;
  color: color-mix(in srgb, var(--ink) 60%, transparent);
  margin-bottom: .55em;
}

.cancel__wrong {
  position: relative;
  display: inline-block;
  text-decoration: none;               /* el tachón lo dibuja ::after */
  font: 700 clamp(1.72rem, 3.95vw, 3.05rem)/1.06 var(--swiss);
  letter-spacing: -.012em;
  color: var(--violet);
}
.cancel__wrong::after {
  content: "";
  position: absolute; left: -2.5%; right: -2.5%; top: 52%;
  height: .11em; border-radius: 1px;
  background: var(--red);
  filter: blur(.2px);
  transform: rotate(var(--ang, -2.4deg)) scaleX(0);
  transform-origin: left center;
  transition: transform .42s cubic-bezier(.22,.61,.36,1) var(--d, 0s);
}
.in .cancel__wrong::after { transform: rotate(var(--ang, -2.4deg)) scaleX(1); }

.cancel__stamp {
  display: block;
  margin-top: .5em;
  font: 700 .82rem/1 var(--type);
  letter-spacing: .04em;
  color: var(--red);
  opacity: 0; transform: translateY(4px) rotate(-1deg);
  transition: opacity .3s ease calc(var(--d, 0s) + .42s),
              transform .3s ease calc(var(--d, 0s) + .42s);
}
.in .cancel__stamp { opacity: 1; transform: none; }

.cancel__right {
  display: block;
  margin-top: .34em;
  font-size: 1.25rem;
  opacity: 0; transform: translateY(3px);
  transition: opacity .3s ease calc(var(--d, 0s) + .62s),
              transform .3s ease calc(var(--d, 0s) + .62s);
}
.in .cancel__right { opacity: 1; transform: none; }
.cancel__right i { font-style: italic; color: var(--red); }

/* Variante atada al scroll. En los actos con scrub el bloque no se revela al
   entrar en pantalla y luego se queda quieto durante tres pantallas: se
   escribe a lo largo del recorrido, con --t1/--t2/--t3. Tiene que ir DESPUES
   de las reglas .in, que son de la misma especificidad. */
.cancel--scrub .cancel__wrong::after {
  transition: none;
  transform: rotate(var(--ang, -2.4deg)) scaleX(var(--t1, 0));
}
.cancel--scrub .cancel__stamp {
  transition: none;
  opacity: var(--t2, 0); transform: none;
}
.cancel--scrub .cancel__right {
  transition: none;
  opacity: var(--t3, 0); transform: none;
}

/* Variante para texto que puede partirse en dos líneas: dibuja por caja de
   línea, a cambio de no poder inclinarse. */
.cancel--wrap .cancel__wrong,
s.cancel__wrong--wrap {
  display: inline;
  background-image: linear-gradient(var(--red), var(--red));
  background-size: 0% .11em;
  background-position: 0 58%;
  background-repeat: no-repeat;
  transition: background-size .42s cubic-bezier(.22,.61,.36,1) var(--d, 0s);
}
.cancel--wrap .cancel__wrong::after { content: none; }
.in .cancel--wrap .cancel__wrong { background-size: 100% .11em; }

/* --- prosa y ficha ---------------------------------------------------- */
.body { max-width: 34em; margin: 1.15em 0 0; }
.body:first-child { margin-top: 0; }

.aside {
  max-width: 32em;
  font: italic 400 1.1rem/1.52 var(--serif);
  color: color-mix(in srgb, var(--ink) 66%, transparent);
  border-left: 2px solid var(--red);
  padding-left: .85em;
}

.docket {
  margin: 1.4em 0 0;
  display: grid;
  grid-template-columns: minmax(7.5em, auto) 1fr;
  font: 400 .84rem/1.5 var(--type);
  border-top: 1px solid color-mix(in srgb, var(--ink) 25%, transparent);
}
.docket dt {
  padding: .42em .9em .42em 0;
  color: color-mix(in srgb, var(--ink) 55%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--ink) 12%, transparent);
}
.docket dd {
  margin: 0; padding: .42em 0;
  border-bottom: 1px solid color-mix(in srgb, var(--ink) 12%, transparent);
}

.hand { font: italic 400 1.14rem/1 var(--serif); color: var(--red); }

figcaption {
  margin-top: .7em;
  font: italic 400 1.06rem/1.48 var(--serif);
  color: color-mix(in srgb, var(--ink) 65%, transparent);
  max-width: 30em;
}

/* --- fondos oscuros --------------------------------------------------- */
.on-dark { background: var(--dark); color: var(--parchment); }
.on-dark .cancel__wrong { color: var(--violet-lt); }
.on-dark .cancel__num,
.on-dark figcaption,
.on-dark .aside { color: color-mix(in srgb, var(--parchment) 62%, transparent); }
.on-dark .docket { border-top-color: color-mix(in srgb, var(--parchment) 28%, transparent); }
.on-dark .docket dt { color: color-mix(in srgb, var(--parchment) 52%, transparent); }
.on-dark .docket dt,
.on-dark .docket dd { border-bottom-color: color-mix(in srgb, var(--parchment) 14%, transparent); }
.on-dark .hand { color: var(--copper); }

/* =========================================================================
   ACTO 0 · la puerta
   ========================================================================= */
.act--door { min-height: 100svh; }

.frame {
  position: relative;
  /* El marco es el borde de la pantalla, no una tarjeta centrada: las cuatro
     tiras crecen desde los bordes reales del viewport. */
  width: 100%;
  min-height: 100svh;
  display: grid; place-items: center;
  /* Las bandas del marco ocupan los bordes: el contenido vive dentro. */
  padding: clamp(66px, 10vh, 104px) clamp(54px, 8vw, 130px);
}
.frame__t, .frame__b, .frame__l, .frame__r {
  position: absolute; z-index: 2;
  object-fit: cover;
  transition: clip-path 1.5s cubic-bezier(.16,.84,.3,1);
}
.frame__t, .frame__b { left: 0; right: 0; width: 100%; height: clamp(46px, 7vh, 82px); }
.frame__t { top: 0;    clip-path: inset(0 0 100% 0); }
.frame__b { bottom: 0; clip-path: inset(100% 0 0 0); }
.frame__l, .frame__r { top: 0; bottom: 0; height: 100%; width: clamp(38px, 6vw, 96px); }
.frame__l { left: 0;  clip-path: inset(0 100% 0 0); transition-delay: .18s; }
.frame__r { right: 0; clip-path: inset(0 0 0 100%); transition-delay: .36s; }

.ready .frame__t, .ready .frame__b,
.ready .frame__l, .ready .frame__r { clip-path: inset(0 0 0 0); }

.frame__med {
  position: absolute; z-index: 3;
  width: clamp(54px, 7vw, 96px);
  border-radius: 50%;
  opacity: 0; transform: scale(.82);
  transition: opacity .6s ease .9s, transform .6s cubic-bezier(.2,.9,.3,1) .9s;
}
.ready .frame__med { opacity: 1; transform: none; }
.frame__med--tl { top: clamp(6px, 1.2vh, 14px); left: clamp(6px, 1vw, 16px); }
.frame__med--tr { top: clamp(6px, 1.2vh, 14px); right: clamp(6px, 1vw, 16px); }

/* El presagio. Marginalia de 1476 — salvo que se recortó de un fotograma
   de 1976. El lector lo descubre en el Acto III. */
.omen {
  position: absolute; z-index: 4;
  display: block;
  top: clamp(78px, 13vh, 160px);
  right: clamp(80px, 15vw, 320px);
  width: 90px;
  opacity: 0;
  transition: opacity 1.2s ease 1.6s;
  /* Deriva minima, muy larga. Lo unico que se mueve en la puerta. */
  animation: omen-drift 16s ease-in-out infinite alternate;
}
.ready .omen { opacity: .75; }

/* El aleteo va en pasos, no interpolado: la cadencia es de proyector y no de
   CSS. Es la unica pista de que este recorte no es marginalia de 1476 sino un
   fotograma de 1976, y el lector no lo entiende hasta el Acto III. Dos
   transformadas distintas piden dos elementos, igual que .flier/.flier__wing
   en el diner. */
.omen__wing { width: 100%; animation: omen-flap 2.4s steps(1, end) infinite; }

@keyframes omen-flap {
  0%   { transform: scaleX(1); }
  38%  { transform: scaleX(.93); }
  62%  { transform: scaleX(.86); }
  100% { transform: scaleX(1); }
}
@keyframes omen-drift {
  from { transform: translate(0, 0) rotate(0deg); }
  to   { transform: translate(-13px, 8px) rotate(-3deg); }
}

/* La puerta se abre al bajar. --d0 lo escribe js/site.js: 0 arriba del todo,
   1 tras una pantalla de scroll. Las cuatro tiras se apartan hacia sus bordes
   y el titulo se queda atras, asi que salir del Acto 0 es cruzar un umbral y
   no pasar de pagina. El ambito .act--door es obligatorio: .stage--return usa
   estas mismas clases con su propia transformada, gobernada por --p1. */
.act--door .frame__t { transform: translateY(calc(var(--d0, 0) * -46%)); }
.act--door .frame__b { transform: translateY(calc(var(--d0, 0) *  46%)); }
.act--door .frame__l { transform: translateX(calc(var(--d0, 0) * -52%)); }
.act--door .frame__r { transform: translateX(calc(var(--d0, 0) *  52%)); }

.door {
  position: relative; z-index: 5; text-align: center; max-width: 44rem;
  transform: translateY(calc(var(--d0, 0) * 12vh));
  opacity: calc(1 - var(--d0, 0) * .85);
}
.door__title {
  position: relative;
  margin: 0;
  font: 400 min(clamp(2.6rem, 8.3vw, 5.85rem), 15vh)/1.0 var(--serif);
  letter-spacing: -.018em;
  color: var(--ink);
}

/* El titulo de polvo. El <h1> sigue siendo el texto real —accesible, y lo
   unico que se ve si falla el canvas o no hay JS— y solo se vuelve
   transparente cuando js/door.js confirma que hay particulas de verdad.
   La caja del <h1> es la que manda: el canvas se limita a cubrirla, asi que
   el polvo hereda el clamp() responsivo y el tracking negativo de arriba. */
.door__dust {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  display: block;
}
.door__title--dust { color: transparent; }
/* El fantasma lleva el texto completo en transparente: reserva la altura
   exacta a cualquier ancho y sigue leyendose en un lector de pantalla, cosa
   que visibility:hidden no permitiria. Sin el, .door esta centrado con
   place-items y la portada entera saltaria mientras se teclea. */
.door__sub--typing { position: relative; }
.door__sub--typing .door__sub__ghost { color: transparent; }
.door__sub__out { position: absolute; inset: 0; }

@keyframes door-caret { 0%, 49% { opacity: 1; } 50%, 100% { opacity: 0; } }
.door__cursor {
  margin-left: .15em;
  animation: door-caret 1.06s steps(1, end) infinite;
}
.door__cursor--done { animation: none; opacity: 0; }

.door__sub {
  margin: 1.1em auto 0;
  font: 400 .92rem/1.5 var(--type);
  letter-spacing: .04em;
  color: color-mix(in srgb, var(--ink) 62%, transparent);
}
.door__note { margin: 1.4em auto 0; max-width: 26em; }
.door__go {
  display: inline-block; margin-top: 1.8em;
  font: 400 .88rem/1 var(--type);
  letter-spacing: .05em;
  color: var(--red);
  text-decoration: none;
  border-bottom: 1px solid currentColor;
  padding-bottom: .3em;
}
.door__go:hover { color: var(--violet); }

/* =========================================================================
   La tira que crece. El Acto I dice que «los márgenes de esta página siguen
   creciendo mientras usted baja»: esto lo cumple. No es una entrada, es un
   mecanismo continuo — --g lo escribe js/site.js desde el rect de la sección.
   ========================================================================= */
.grow {
  position: absolute; z-index: 1;
  left: 0; top: 0;
  width: var(--strip);
  height: 100%;
  object-fit: cover; object-position: top center;
  pointer-events: none;
  opacity: .9;
  /* La máscara hace las dos cosas: oculta lo que aún no ha crecido y difumina
     el filo. Un corte duro se lee como imagen truncada; así se lee como tinta
     que se acaba. */
  -webkit-mask-image: linear-gradient(to bottom,
    #000 calc(var(--g, 0) * 100% - 90px), rgba(0,0,0,0) calc(var(--g, 0) * 100%));
          mask-image: linear-gradient(to bottom,
    #000 calc(var(--g, 0) * 100% - 90px), rgba(0,0,0,0) calc(var(--g, 0) * 100%));
}
.on-dark .grow { mix-blend-mode: screen; opacity: .24; }

/* El contenido se aparta para dejarle sitio, como en un manuscrito real.
   Por debajo de 1024px no hay hueco: la tira no se descarga siquiera. */
.act--ms, .act--post { --strip: 0px; }
@media (min-width: 1024px) {
  .act--ms, .act--post {
    --strip: clamp(34px, 5vw, 86px);
    padding-left: calc(var(--strip) + clamp(10px, 1.6vw, 30px));
  }
}
@media (max-width: 1023px) { .grow { display: none; } }

/* =========================================================================
   ACTO I · la baraja

   Tres laminas en un solo mazo. Todas comparten la misma celda de rejilla,
   asi que el mazo mide lo que la mayor y cambiar de carta no mueve nada
   alrededor. --pos lo escribe js/baraja.js: 0 es la de arriba. La que se va
   sale girando y desvaneciendose, y reaparece al fondo sin transicion para
   no cruzar el mazo por delante.
   ========================================================================= */
.baraja { display: grid; gap: 1.4em; justify-items: center; --drop: -2.2deg; }
/* El texto de la pieza 001 se centra en alto contra el mazo, no arriba. */
.act--ms .wrap--split { align-items: center; }

.baraja__mazo {
  display: grid;
  padding: 0; border: 0; background: none;
  cursor: pointer;
}
/* --vis topa el abanico en tres: con mas laminas, las del fondo se quedan
   apiladas detras de la tercera en vez de desplegarse fuera del acto. */
.baraja__carta {
  grid-area: 1 / 1; place-self: center;
  display: block;
  --pos: 0;
  --vis: min(var(--pos), 3);
  z-index: calc(50 - var(--pos));
  transform: translate(calc(var(--vis) * 14px), calc(var(--vis) * -11px))
             rotate(calc(var(--rot, 0deg) + var(--vis) * 1.6deg));
  opacity: calc(1 - var(--vis) * .22);
  transition: transform .42s cubic-bezier(.2,.8,.3,1), opacity .32s ease;
}
.baraja__carta img {
  width: auto;
  max-height: min(70svh, 640px);
  box-shadow: 0 26px 60px rgba(0,0,0,.6);
}
.baraja__carta--ms    { --rot: -1.4deg; }
.baraja__carta--carta { --rot:  1.7deg; }
.baraja__carta--jorge { --rot:  -.7deg; }

.baraja__carta.saliendo { transform: translate(58%, -5%) rotate(13deg); opacity: 0; }
.baraja__carta.volviendo { transition: none; opacity: 0; }

/* Los pies se apilan igual que las cartas: el alto lo marca el mas largo y
   el texto de debajo no salta al cambiar de lamina. */
.baraja__pies { display: grid; width: min(30em, 100%); }
.baraja__pie {
  grid-area: 1 / 1; margin: 0;
  font: italic 400 1.06rem/1.48 var(--serif);
  color: color-mix(in srgb, var(--parchment) 62%, transparent);
  opacity: 0;
  transition: opacity .32s ease;
}
.baraja__pie.activo { opacity: 1; }


/* =========================================================================
   Las piezas se posan. Cada documento llega como si lo dejaran sobre la mesa:
   entra con rotación de más y se asienta en su ángulo de reposo. --rest es
   ese ángulo final, para que las piezas que ya venían torcidas lo conserven.
   ========================================================================= */
.settle {
  opacity: 0;
  transform: rotate(calc(var(--rest, 0deg) + var(--drop, 2.6deg))) scale(1.018);
  transition: opacity .40s ease var(--d, 0s),
              transform .58s cubic-bezier(.16,.84,.3,1) var(--d, 0s);
}
.in .settle { opacity: 1; transform: rotate(var(--rest, 0deg)); }

.card          { --drop:  1.6deg; }

/* =========================================================================
   ACTO II · el matasellos
   ========================================================================= */
.act--post { background: var(--parchment-2); }
/* Texto a la izquierda, la postal entera a la derecha. */
.wrap--postal { align-items: center; }
.card { margin: 0; }
.card img {
  width: 100%;
  box-shadow: 0 16px 44px rgba(65,44,26,.26);
}

/* =========================================================================
   Mecanismo de scrub. Un contenedor alto, una escena pegada.
   El progreso --p lo escribe js/site.js desde getBoundingClientRect().
   ========================================================================= */
.act[data-scrub] { padding: 0; }
.scrub { height: var(--scrub-h, 250vh); }
.stage {
  position: sticky; top: 0;
  height: 100svh;
  overflow: hidden;
  display: grid; place-items: center;
}

/* --- ACTO III · el diner --------------------------------------------- */
.act--diner { background: var(--dark); }
.shot { position: relative; width: 100%; height: 100%; }

/* object-fit: cover recorta la imagen, asi que los rectangulos en porcentaje
   dejarian de apuntar al contenido. Esta caja reproduce el encuadre de cover
   con dimensiones reales, y todo lo que va encima se posiciona en porcentaje
   de ELLA: la geometria vuelve a ser exacta a cualquier viewport. */
.shot__inner {
  position: absolute; top: 0; left: 50%;
  width: max(100%, calc(100svh * 1600 / 1119));
  aspect-ratio: 1600 / 1119;
  transform: translateX(-50%);
}
.shot__img { width: 100%; height: 100%; object-fit: fill; }

/* El texto va sobre la foto: sin este velo, «dafür:» cae sobre una camiseta
   roja y deja de leerse. */
.scrim {
  position: absolute; z-index: 5; inset: auto 0 0 0; height: 62%;
  background: linear-gradient(to top,
    rgba(10,7,4,.88) 0%, rgba(10,7,4,.72) 26%, rgba(10,7,4,.34) 58%, rgba(10,7,4,0) 100%);
  pointer-events: none;
}

/* Rectángulos emitidos por tools/make_assets.py desde las cajas de recorte.
   No editar a mano: el script los recalcula. */
.spot     { left: 23.69%; top: 0.00%;  width: 39.83%; height: 36.17%; }
.patch--a { left: 30.90%; top: 9.48%;  width: 10.00%; height: 19.04%; }
.patch--b { left: 41.57%; top: 0.83%;  width:  4.80%; height:  9.56%; }
.flier--a { left: 30.90%; top: 9.48%;  width: 10.00%; height: 19.04%; }
.flier--b { left: 41.57%; top: 0.83%;  width:  4.80%; height:  9.56%; }

.spot, .patch, .flier { position: absolute; }

.spot {
  border-radius: 50%;
  box-shadow: 0 0 90px 100vmax rgba(10,7,4,.82);
  opacity: var(--p1);
}
/* Borde difuminado: un rectangulo de canto duro sobre oleo pintado se lee
   como pegatina por bien igualado que este el color. */
.patch {
  object-fit: fill;
  opacity: var(--p3);
  -webkit-mask-image: radial-gradient(ellipse at 50% 50%, #000 38%, rgba(0,0,0,0) 78%);
          mask-image: radial-gradient(ellipse at 50% 50%, #000 38%, rgba(0,0,0,0) 78%);
}

/* La fuga. La posición base es el sitio que ocupan dentro del cuadro; la
   transformada las saca de él. mix-blend-mode va en el elemento transformado
   para que siga mezclándose con el fotograma, no consigo mismo. */
/* Se va del encuadre en vez de crecer: ampliada mas alla de 1.2 la pintura
   se deshace, porque el original es oleo suave a baja resolucion. Y una
   mariposa que sale del cuadro se lee mejor que una que se queda flotando.
   Se desvanece justo cuando los parches cierran el ovalo. */
.flier { will-change: transform; opacity: calc(1 - var(--p3)); }
.flier--a {
  transform:
    translate(calc(var(--p2) * 41vw), calc(var(--p2) * -34vh))
    scale(calc(1 + var(--p2) * .16))
    rotate(calc(var(--p2) * 14deg));
}
.flier--b {
  transform:
    translate(calc(var(--p2) * 27vw), calc(var(--p2) * -22vh))
    scale(calc(1 + var(--p2) * .10))
    rotate(calc(var(--p2) * -11deg));
}
.flier__wing {
  width: 100%; height: 100%; object-fit: fill;
  animation: flap 380ms infinite ease-in-out;
}
.flier--b .flier__wing { animation-duration: 440ms; animation-delay: -160ms; }

@keyframes flap {
  0%, 100% { transform: scaleX(1); }
  50%      { transform: scaleX(.82); }
}

.shot__text {
  position: absolute; z-index: 6;
  left: var(--gut); bottom: clamp(28px, 7vh, 72px);
  width: min(30em, calc(100% - var(--gut) * 2));
}
.body--light { color: var(--parchment); text-shadow: 0 1px 14px rgba(0,0,0,.75); }
.cancel--light .cancel__wrong { color: var(--violet-lt); text-shadow: 0 2px 18px rgba(0,0,0,.8); }
.cancel--light .cancel__num { color: color-mix(in srgb, var(--parchment) 62%, transparent); }
.cancel--light .cancel__right { color: var(--parchment); text-shadow: 0 1px 14px rgba(0,0,0,.8); }
.cancel--light .cancel__right i { color: #FF6A6B; }
.cancel--light .cancel__stamp { color: #FF3B3C; text-shadow: 0 1px 12px rgba(0,0,0,.9); }

.late  { opacity: var(--p1); }
.later { opacity: var(--p3); }

/* --- ACTO V · los Dolomitas ------------------------------------------ */
.shot--alps { position: relative; width: 100%; height: 100%; }
.alps-a, .alps-b { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }

/* Movimiento de camara, no escalado. Antes solo habia un 5% de zoom en 200vh
   de scroll: un acercamiento puro no se lee como movimiento, se lee como una
   imagen que crece. Con la deriva vertical la pared de los Dolomitas pasa por
   delante y el acto tiene direccion. */
.alps-a, .alps-b {
  transform: scale(calc(1.08 + var(--p, 0) * .09))
             translateY(calc(var(--p, 0) * -3.2%));
}
.alps-b { opacity: var(--p2); }

/* El velo se cierra al final y entrega el negro al acto siguiente. */
.shot--alps .scrim { height: calc(62% + var(--p3, 0) * 46%); }

.shot__text--alps { max-width: min(26em, calc(100% - var(--gut) * 2)); }

/* =========================================================================
   ACTO IV · devuelto al remitente
   ========================================================================= */
.act--desk { overflow: visible; }

/* El sistema de clasificación medieval anotando 1976. La tira empieza y acaba
   fuera de esta sección: cruzar la frontera cose los actos entre sí. */
.rail {
  position: absolute; z-index: 5;
  left: 0; top: -34vh; bottom: -34vh;
  width: clamp(44px, 6.5vw, 104px);
  height: auto;
  object-fit: cover;
  mix-blend-mode: screen;
  opacity: .2;
  pointer-events: none;
}

/* Fundido encadenado. Las dos fotos son el mismo hombre: una entre
   bastidores, otra de estudio. Fotos y textos van apilados cada uno en su
   celda y --p2 (PHASES["acto-4"] en js/site.js) los cruza. Antes y despues
   del fundido hay tramos quietos para poder leer cada pieza.

   Las rotaciones van en transform y el fundido en opacity y en las
   propiedades sueltas scale/translate: asi no se pisan entre si. */
.desk {
  width: min(1180px, 100% - var(--gut) * 2);
  display: grid; gap: clamp(30px, 5vw, 72px);
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
  align-items: center;
  position: relative; z-index: 6;
}
.desk__fotos, .desk__textos { display: grid; align-items: center; }
.desk__fotos { justify-items: center; }
.desk__fotos > *, .desk__textos > * { grid-area: 1 / 1; }

.desk__a { opacity: calc(1 - var(--p2, 0)); }
.desk__b { opacity: var(--p2, 0); }
.desk__fotos .desk__a { scale: calc(1 - var(--p2, 0) * .05); }
.desk__fotos .desk__b { scale: calc(1.05 - var(--p2, 0) * .05); }
.desk__textos .desk__a { translate: 0 calc(var(--p2, 0) * -18px); }
.desk__textos .desk__b { translate: 0 calc((1 - var(--p2, 0)) * 18px); }

.object { margin: 0; }
.object--polaroid { position: relative; width: var(--cap-polaroid); transform: rotate(2deg); }
.object--polaroid img { box-shadow: 0 26px 48px rgba(0,0,0,.6); }
.object--signed { width: min(560px, 100%); transform: rotate(-1.5deg); }
.object--signed img { box-shadow: 0 26px 48px rgba(0,0,0,.6); }

/* La sombra de algo que no está en el encuadre. */
.ghost {
  position: absolute; z-index: 2;
  right: 5%; top: 11%;
  width: 40%;
  filter: brightness(0) blur(1.2px);
  opacity: .26;
  transform: rotate(6deg);
}

/* =========================================================================
   ACTO V · lugares que nunca se alcanzaron
   ========================================================================= */
.act--places { background: var(--parchment); }

/* Las dos postales verticales del fondo, en zigzag: postal y ficha lado a
   lado, centradas en alto, y la segunda fila invertida. Puestas en columna,
   con la ficha debajo, cada una media casi dos pantallas.
   El ancho de la postal sale del alto de la pantalla: con su marco, la
   postal entera nunca pasa del 80svh. */
.postales { display: grid; gap: clamp(72px, 14vh, 160px); }
.postal {
  display: grid; align-items: center;
  grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
  gap: clamp(32px, 5vw, 80px);
}
.postal--inv { grid-template-columns: minmax(0, 6fr) minmax(0, 5fr); }
.postal--inv .postcard { order: 2; }
.postal .postcard {
  justify-self: center;
  width: min(440px, 94%, calc((80svh - 82px) * .66 + 44px));
}
.postal .col { max-width: 34em; }
.postal--inv .col { justify-self: end; }

.postcard {
  position: relative; margin: 0;
  width: min(var(--cap-venice), 100%);
  background: #fff;
  padding: 22px 22px 60px;
  box-shadow: 0 20px 48px rgba(65,44,26,.3);
  --rest: -1.2deg; --drop: -3.4deg;
}
.postcard--fern {
  width: min(var(--cap-fern), 100%);
  background: #f2ecdd;
  --rest: 1deg; --drop: 3.2deg;
  padding: 20px 20px 54px;
}
.postcard__stamp {
  position: absolute; top: 12px; right: 12px;
  width: 70px; transform: rotate(4deg);
  box-shadow: 0 3px 8px rgba(0,0,0,.25);
}

/* =========================================================================
   ACTO VII · el regreso
   ========================================================================= */
.act--return { background: var(--parchment); }
.stage--return { background: var(--parchment); }

.stage--return .close {
  position: absolute; z-index: 2; object-fit: cover;
}
.stage--return .frame__t,
.stage--return .frame__b { left: 0; width: 100%; height: clamp(46px, 7vh, 82px); clip-path: none; }
.stage--return .frame__l,
.stage--return .frame__r { top: 0; height: 100%; width: clamp(38px, 6vw, 96px); clip-path: none; }
.stage--return .frame__t { top: 0;    transform: translateY(calc((1 - var(--p1)) * -100%)); }
.stage--return .frame__b { bottom: 0; transform: translateY(calc((1 - var(--p1)) * 100%)); }
.stage--return .frame__l { left: 0;   transform: translateX(calc((1 - var(--p1)) * -100%)); }
.stage--return .frame__r { right: 0;  transform: translateX(calc((1 - var(--p1)) * 100%)); }

/* El recorte y su propia fotografía salen del mismo archivo, así que el
   aterrizaje es exacto a cualquier tamaño: los porcentajes los emite
   make_assets.py desde la caja de recorte. */
.landing {
  position: relative; z-index: 3;
  width: min(var(--cap-landing), 86vw);
  aspect-ratio: 1400 / 996;
}
.landing__macro {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover;
  opacity: var(--p2);
  box-shadow: 0 24px 60px rgba(65,44,26,.34);
}
.landing__cut {
  position: absolute;
  left: 16.85%; top: 14.14%; width: 47.83%; height: 62.67%;
  display: block;
  opacity: calc(1 - var(--p3) * .92);
  transform:
    translate(calc((1 - var(--p1b)) * 52vw), calc((1 - var(--p1b)) * -46vh))
    scale(calc(.24 + var(--p1b) * .76))
    rotate(calc((1 - var(--p1b)) * 16deg));
  will-change: transform;
}
.landing__cut img { width: 100%; height: 100%; object-fit: fill; }
.landing__paint {
  position: absolute; z-index: 4;
  left: 104%; top: 26%;
  width: 34%;
  opacity: var(--p3);
}
.shot__text--return {
  left: 50%; transform: translateX(-50%);
  text-align: center; width: min(28em, calc(100% - var(--gut) * 2));
}
.shot__text--return .cancel__right,
.shot__text--return .body { margin-inline: auto; }

/* =========================================================================
   ACTO VIII · el medio

   El 1/2 de "Fondo 131 1/2", que la pagina lleva prometiendo desde su primera
   linea sin explicarlo nunca. Estas cinco son las unicas piezas sin
   cancelacion: no hay tachon, ni sello, ni "dafuer". En su sitio va s/n, sin
   numero, que es como se anota en un archivo lo que no se ha llegado a
   catalogar.
   ========================================================================= */
.act--medio { background: var(--dark-2); }

.medio__head {
  margin: 0 0 .5em;
  font: 400 .84rem/1 var(--type);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--parchment) 55%, transparent);
}
.medio__frac { font-feature-settings: "frac" 1; }

/* La pelicula. La .stage se queda fija y la tira desfila de lado con
   --p1 (PHASES["acto-8"]). Todas las piezas a la misma altura y ancho libre:
   las proporciones dispares -0.69, 1.52, 1.55, 0.81, 1.5- dejan de ser
   desorden.

   El desplazamiento no se mide en JS. En translate el 100% es el ancho de la
   propia tira -justo la propiedad que rompio la tanda de recto/verso, aqui a
   favor- y 100cqw el de la escena, que es contenedor. Con p1 = 1 el borde
   derecho de la tira queda en el borde de la pantalla. min() con 0px impide
   que se mueva hacia la derecha si un dia la tira cupiera entera. */
.stage--medio {
  container-type: inline-size;
  display: grid;
  /* minmax(0, ...) y no auto: una columna auto crece hasta el ancho de la
     tira entera, y la introduccion se centraria fuera de la pantalla. */
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto 1fr;
  place-items: initial;
  align-content: center;
  gap: clamp(24px, 4vh, 48px);
  padding-block: clamp(32px, 6vh, 72px);
}
.medio__intro { margin-inline: auto; }
.medio__intro .body { max-width: 46em; }
.medio__visor { align-self: center; }

.medio__tira {
  display: flex; align-items: flex-start;
  gap: clamp(32px, 4.5vw, 72px);
  width: max-content;
  padding-inline: var(--gut);
  translate: calc(var(--p1, 0) * min(0px, 100cqw - 100%)) 0;
  will-change: translate;
}
/* min-content: el pie se ajusta al ancho de su imagen en vez de estirarla. */
.medio__pieza { margin: 0; flex: none; width: min-content; }

/* El boton no debe parecer un boton: es la pieza. */
.medio__abrir {
  display: block;
  padding: 0; border: 0; background: none;
  cursor: zoom-in;
}
.medio__abrir img {
  height: min(50svh, 540px); width: auto; max-width: none;
  box-shadow: 0 22px 48px rgba(0,0,0,.55);
  transition: transform .45s cubic-bezier(.2,.8,.3,1);
}
.medio__abrir:hover img,
.medio__abrir:focus-visible img { transform: scale(1.02); }
.medio__pieza figcaption { margin-top: .9em; }

.medio__sn {
  font: 400 .78rem/1 var(--type);
  letter-spacing: .08em;
  color: var(--copper);
  margin-right: .6em;
}

/* =========================================================================
   La mesa de consulta

   <dialog> nativo: el atrapado de foco, el cierre con Escape y el velo vienen
   de serie. Reimplementarlo a mano habria sido mas codigo y peor.
   ========================================================================= */
.lupa {
  width: min(92vw, 1100px);
  max-height: 92vh;
  padding: clamp(14px, 2vw, 26px);
  border: 0;
  background: var(--parchment);
  color: var(--ink);
  box-shadow: 0 40px 90px rgba(0,0,0,.6);
}
.lupa::backdrop { background: rgba(10, 7, 4, .86); }

/* Nunca ampliada y nunca recortada: con width:auto la imagen se queda en su
   tamano real si cabe, y solo se reduce cuando no cabe. object-fit sobra en
   cuanto se deja de forzar el ancho. */
.lupa__img {
  width: auto; height: auto;
  max-width: 100%;
  max-height: calc(92vh - 8rem);
  margin-inline: auto;
}
.lupa__pie {
  margin: .9em 0 0;
  font: 400 .84rem/1.5 var(--type);
  color: color-mix(in srgb, var(--ink) 66%, transparent);
  max-width: 46em;
}
.lupa__cerrar {
  float: right;
  font: 400 .74rem/1 var(--type);
  letter-spacing: .04em;
  color: color-mix(in srgb, var(--ink) 70%, transparent);
  background: none;
  border: 1px solid color-mix(in srgb, var(--ink) 28%, transparent);
  padding: .5em .8em; cursor: pointer;
}
.lupa__cerrar:hover { color: var(--red); border-color: var(--red); }

/* =========================================================================
   Coda
   ========================================================================= */
.act--coda {
  position: relative; overflow: hidden;
  background: var(--parchment-2);
  padding-block: clamp(80px, 16vh, 180px);
  text-align: center;
}
.coda__seal {
  position: absolute; left: 50%; top: 50%;
  width: min(120vmin, 1100px);
  transform: translate(-50%, -50%) scale(calc(.6 + var(--p) * .4));
  opacity: calc(var(--p) * .12);
  pointer-events: none;
  /* sin la mascara, el canto del recorte se lee como una linea recta
     atravesando la pagina en vez de como una mancha de tinta */
  -webkit-mask-image: radial-gradient(ellipse at 50% 50%, #000 42%, rgba(0,0,0,0) 76%);
          mask-image: radial-gradient(ellipse at 50% 50%, #000 42%, rgba(0,0,0,0) 76%);
}
.act--coda .wrap { position: relative; z-index: 2; }
.coda__line {
  margin: 0 auto;
  font: 400 clamp(1.45rem, 3.8vw, 2.35rem)/1.3 var(--serif);
  max-width: 20em;
}
.coda__line .cancel__wrong {
  font: inherit; color: var(--ink);
}
.coda__fix { display: block; margin-top: .3em; }
.coda__fix i { font-style: italic; color: var(--red); }
.colophon {
  margin: 2.4em auto 0; max-width: 42em;
  font: 400 .84rem/1.75 var(--type);
  color: color-mix(in srgb, var(--ink) 58%, transparent);
}

/* =========================================================================
   PIEZA 000 · el archivero

   Toda oficina tiene un funcionario, y el matasellos que lleva ocho actos
   fechando mal cada pieza es de alguien. Esta seccion nace con [hidden] y solo
   existe para quien lo pulse. No lleva cancelacion: es la unica pieza del
   fondo que esta donde tiene que estar, y por eso el tachon se desactiva.
   ========================================================================= */
.act--cero { background: var(--dark-2); }

/* Mismo titular que el resto del fondo, pero sin la linea roja encima. */
.cancel__wrong--limpio::after { content: none; }

.cero__cabeza { margin-top: clamp(32px, 5vh, 64px); align-items: start; }
/* Los certificados suben a la columna derecha, bajo la ficha; el retrato
   acompana mientras se leen. */
.cero__retrato { margin: 0; --rest: -1.6deg; --drop: -3.4deg; position: sticky; top: 12vh; }
.cero__retrato img {
  width: min(320px, 100%);
  box-shadow: 0 24px 52px rgba(0,0,0,.6);
}

.cero__head {
  margin: clamp(40px, 6vh, 80px) 0 1.2em;
  font: 400 .84rem/1 var(--type);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--parchment) 55%, transparent);
}

.cero__lista { margin: 0; padding: 0; list-style: none; }
.cero__lista li + li { margin-top: .9em; }
.cero__lista a {
  display: block;
  text-decoration: none;
  color: inherit;
  padding: .7em .9em;
  border-left: 2px solid color-mix(in srgb, var(--parchment) 22%, transparent);
  transition: border-color .3s ease, background-color .3s ease;
}
.cero__lista a:hover,
.cero__lista a:focus-visible {
  border-left-color: var(--red);
  background: color-mix(in srgb, var(--parchment) 5%, transparent);
}
.cero__t {
  display: block;
  font: 400 1.22rem/1.3 var(--serif);
  color: var(--parchment);
}
.cero__e {
  display: block; margin-top: .2em;
  font: 400 .84rem/1.4 var(--type);
  color: var(--copper);
}
.cero__n {
  display: block; margin-top: .15em;
  font: italic 400 .98rem/1.4 var(--serif);
  color: color-mix(in srgb, var(--parchment) 58%, transparent);
}

.cero__mosaico { margin: clamp(32px, 5vh, 64px) 0 0; --rest: 1.2deg; --drop: 2.8deg; }

.act--cero .docket a {
  color: var(--parchment);
  text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, var(--parchment) 40%, transparent);
}
.act--cero .docket a:hover { color: var(--red); border-color: var(--red); }

/* =========================================================================
   Sello de progreso. Entra en el Acto II y fecha mal cada pieza.
   ========================================================================= */
.seal {
  position: fixed; z-index: 80;
  /* Es un boton desde que abre el expediente, pero no debe parecerlo: lo
     unico que lo delata es el cursor. Si se anuncia, deja de ser un hallazgo. */
  appearance: none; border: 0; background: none; padding: 0;
  font: inherit; color: inherit; text-align: left;
  cursor: pointer;
  right: clamp(12px, 2vw, 26px); bottom: clamp(12px, 2vh, 26px);
  display: flex; align-items: center; gap: .6em;
  /* visibility, y no solo opacity, porque desde que es un <button> hace falta
     sacarlo tambien del orden de tabulacion: un control invisible al que se
     llega con Tab desorienta a quien navega con teclado. Se retrasa hasta el
     final del fundido al ocultarse, y es inmediata al aparecer. */
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity .5s ease, visibility 0s linear .5s;
}
.seal.on {
  opacity: 1; visibility: visible; pointer-events: auto;
  transition: opacity .5s ease, visibility 0s;
}
.seal:focus-visible { outline: 3px solid var(--red); outline-offset: 6px; }

/* El golpe: el sello registra cada pieza en vez de solo girar despacio. */
@keyframes seal-hit {
  0%   { transform: rotate(var(--seal-rot, 0deg)) scale(1.13); }
  55%  { transform: rotate(var(--seal-rot, 0deg)) scale(.975); }
  100% { transform: rotate(var(--seal-rot, 0deg)) scale(1); }
}
.seal.hit img { animation: seal-hit 340ms cubic-bezier(.2,.9,.3,1); }
.seal img {
  width: 96px;
  transform: rotate(var(--seal-rot, 0deg));
  transition: transform .6s cubic-bezier(.2,.8,.3,1);
  transform-origin: 62% 38%;
  filter: drop-shadow(0 1px 2px rgba(255,255,255,.35));
}
.seal__date {
  font: 400 .74rem/1 var(--type);
  letter-spacing: .04em;
  color: color-mix(in srgb, var(--ink) 74%, transparent);
  white-space: nowrap;
  text-shadow: 0 0 6px var(--parchment), 0 0 14px var(--parchment);
}
.on-dark-seal .seal img { filter: invert(1) drop-shadow(0 1px 3px rgba(0,0,0,.6)); }
.on-dark-seal .seal__date {
  color: color-mix(in srgb, var(--parchment) 78%, transparent);
  text-shadow: 0 0 6px rgba(0,0,0,.9), 0 0 16px rgba(0,0,0,.8);
}

/* =========================================================================
   LA VENTANILLA

   Un buzon de laton atornillado al borde derecho, encima del matasellos:
   asoma del filo y sale un poco al pasar por encima. Abre una ficha de
   mostrador sobre vitela, tras una persiana metalica que sube. El archivero
   escribe a maquina (Courier, con el cursor de la portada); el visitante, a
   mano (Garamond italica). Cada respuesta recibe su sello.
   ========================================================================= */
.buzon {
  --laton: linear-gradient(168deg, #f0d79a 0%, #c99d52 34%, #9a7333 62%, #6b4d1f 100%);
  position: fixed; z-index: 81;
  right: 0; bottom: clamp(142px, 19vh, 176px);
  display: grid; justify-items: center; gap: 7px;
  padding: 11px 26px 10px 18px;
  border: 0; border-radius: 7px 0 0 7px;
  background: var(--laton);
  box-shadow:
    inset 0 1px 0 rgba(255,244,210,.75),
    inset 0 -2px 3px rgba(60,38,10,.45),
    -3px 6px 16px rgba(20,12,4,.45);
  cursor: pointer;
  translate: 12px 0;
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: translate .35s cubic-bezier(.2,.8,.3,1), opacity .5s ease, visibility 0s .5s;
}
.buzon.on {
  opacity: 1; visibility: visible; pointer-events: auto;
  transition: translate .35s cubic-bezier(.2,.8,.3,1), opacity .5s ease, visibility 0s;
}
.buzon:hover, .buzon:focus-visible { translate: 0 0; }
/* Con la ficha abierta, el buzon asomaria por detras de su marco. */
.ventanilla-abierta .buzon { opacity: 0; visibility: hidden; }
.buzon:focus-visible { outline: 3px solid var(--red); outline-offset: 4px; }

/* Los dos tornillos: un punto con brillo y una ranura en diagonal. */
.buzon::before, .buzon::after {
  content: ""; position: absolute; top: 50%;
  width: 7px; height: 7px; margin-top: -3.5px; border-radius: 50%;
  background:
    linear-gradient(45deg, transparent 42%, rgba(50,30,8,.75) 44% 56%, transparent 58%),
    radial-gradient(circle at 35% 30%, #fff1c8, #a07a3a 60%, #5c4119);
  box-shadow: 0 1px 1px rgba(0,0,0,.35);
}
.buzon::before { left: 6px; }
.buzon::after  { right: 15px; }

.buzon__ranura {
  width: 92px; height: 9px; border-radius: 5px;
  background: linear-gradient(#0d0905, #2a1d0e);
  box-shadow: inset 0 2px 3px rgba(0,0,0,.9), 0 1px 0 rgba(255,240,200,.55);
}
.buzon__placa {
  font: 700 .6rem/1 var(--type);
  letter-spacing: .24em; text-transform: uppercase;
  color: #4a3210;
  text-shadow: 0 1px 0 rgba(255,240,200,.6), 0 -1px 0 rgba(60,36,8,.35);
}

/* --- la ficha --------------------------------------------------------- */
.ventanilla {
  position: fixed; z-index: 90;
  right: clamp(12px, 2vw, 26px); bottom: clamp(12px, 2vh, 26px);
  width: min(440px, 100vw - 24px);
  height: min(640px, 100svh - 32px);
  display: grid; grid-template-rows: auto 1fr auto auto;
  overflow: hidden;
  color: var(--ink);
  /* Vitela pautada: la ficha de un libro de registro. */
  background:
    repeating-linear-gradient(to bottom, transparent 0 31px, rgba(65,44,26,.09) 31px 32px),
    var(--parchment-2);
  border: 1px solid rgba(65,44,26,.35);
  box-shadow: 0 30px 70px rgba(10,6,2,.55), 0 0 0 6px rgba(201,157,82,.9), 0 0 0 7px rgba(80,56,20,.7);
  border-radius: 3px;
  opacity: 0; translate: 0 18px;
  transition: opacity .3s ease, translate .38s cubic-bezier(.2,.8,.3,1);
}
.ventanilla.abierta { opacity: 1; translate: 0 0; }

/* La persiana: lamas de metal que suben al abrir y dejan la ficha a la vista. */
.ventanilla__persiana {
  position: absolute; inset: 0; z-index: 5; pointer-events: none;
  background:
    repeating-linear-gradient(to bottom,
      #8d8576 0 3px, #c9c2b2 3px 9px, #a69e8e 9px 15px, #5d564b 15px 17px);
  box-shadow: inset 0 -6px 10px rgba(0,0,0,.45);
  transition: translate .55s cubic-bezier(.6,.05,.3,1) .12s;
}
.ventanilla.abierta .ventanilla__persiana { translate: 0 -101%; }

.ventanilla__cabeza {
  position: relative;
  padding: 16px 20px 12px;
  border-bottom: 2px solid rgba(65,44,26,.55);
  background: linear-gradient(rgba(224,218,200,.9), rgba(224,218,200,.6));
}
.ventanilla__num {
  margin: 0;
  font: 400 .7rem/1 var(--type); letter-spacing: .08em;
  color: color-mix(in srgb, var(--ink) 65%, transparent);
}
.ventanilla__titulo {
  margin: .25em 0 0;
  font: 700 1.7rem/1 var(--swiss); letter-spacing: -.01em;
  color: var(--violet);
}
.ventanilla__cerrar {
  position: absolute; top: 14px; right: 16px;
  font: 400 .72rem/1 var(--type); letter-spacing: .05em;
  color: var(--red); background: none;
  border: 0; border-bottom: 1px solid currentColor;
  padding: 0 0 .2em; cursor: pointer;
}

.ventanilla__registro {
  list-style: none; margin: 0; padding: 16px 20px 8px;
  overflow-y: auto; overscroll-behavior: contain;
  display: grid; align-content: start; gap: 18px;
}
.ventanilla__asiento { position: relative; }
.ventanilla__quien {
  display: block; margin-bottom: .3em;
  font: 400 .64rem/1 var(--type); letter-spacing: .14em; text-transform: uppercase;
  color: color-mix(in srgb, var(--ink) 55%, transparent);
}
.ventanilla__texto { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; }
.ventanilla__asiento--archivero .ventanilla__texto {
  font: 400 .9rem/1.55 var(--type); color: var(--ink);
}
.ventanilla__asiento--visitante { justify-self: end; max-width: 86%; text-align: right; }
.ventanilla__asiento--visitante .ventanilla__texto {
  font: italic 400 1.12rem/1.45 var(--serif); color: var(--violet);
}
.ventanilla__texto a { color: var(--red); text-underline-offset: .18em; }

/* El sello de cada respuesta: tinta roja, torcido, con su golpe. */
@keyframes ventanilla-golpe {
  0%   { scale: 1.6; opacity: 0; }
  60%  { scale: .96; opacity: .9; }
  100% { scale: 1;   opacity: .82; }
}
.ventanilla__sello {
  display: inline-block; margin-top: .55em;
  padding: .28em .5em .22em;
  font: 700 .62rem/1 var(--type); letter-spacing: .12em; text-transform: uppercase;
  color: var(--red); border: 1.5px solid currentColor; border-radius: 2px;
  rotate: -3.5deg; opacity: .82;
  mix-blend-mode: multiply;
}
.ventanilla__sello.golpe { animation: ventanilla-golpe .34s cubic-bezier(.2,.9,.3,1); }

.ventanilla__form {
  display: grid; grid-template-columns: 1fr auto; align-items: end; gap: 10px;
  padding: 12px 16px;
  border-top: 2px solid rgba(65,44,26,.55);
  background: rgba(224,218,200,.75);
}
.ventanilla__form textarea {
  resize: none; min-height: 2.8em; max-height: 7em;
  padding: .5em .6em;
  font: italic 400 1.05rem/1.35 var(--serif); color: var(--ink);
  background: rgba(255,252,242,.7);
  border: 1px solid rgba(65,44,26,.35); border-radius: 2px;
}
.ventanilla__form textarea:focus { outline: 2px solid var(--violet); outline-offset: 1px; }
.ventanilla__form button {
  font: 700 .74rem/1 var(--type); letter-spacing: .12em; text-transform: uppercase;
  color: var(--parchment-2); background: var(--red);
  border: 0; border-radius: 2px; padding: .9em 1em; cursor: pointer;
}
.ventanilla__form button:disabled { background: color-mix(in srgb, var(--ink) 45%, transparent); cursor: wait; }
.ventanilla__nota {
  margin: 0; padding: 0 16px 12px;
  background: rgba(224,218,200,.75);
  font: italic 400 .8rem/1.35 var(--serif);
  color: color-mix(in srgb, var(--ink) 62%, transparent);
}

/* --- modo quieto ------------------------------------------------------ */
.quiet {
  position: fixed; z-index: 85;
  left: clamp(12px, 2vw, 26px); bottom: clamp(12px, 2vh, 26px);
  font: 400 .74rem/1 var(--type);
  letter-spacing: .04em;
  color: color-mix(in srgb, var(--ink) 70%, transparent);
  background: color-mix(in srgb, var(--parchment) 86%, transparent);
  border: 1px solid color-mix(in srgb, var(--ink) 28%, transparent);
  padding: .55em .8em; cursor: pointer;
}
.quiet:hover { color: var(--red); border-color: var(--red); }
.on-dark-seal .quiet {
  color: color-mix(in srgb, var(--parchment) 75%, transparent);
  background: color-mix(in srgb, var(--dark) 78%, transparent);
  border-color: color-mix(in srgb, var(--parchment) 30%, transparent);
}

/* =========================================================================
   Responsive
   ========================================================================= */
@media (max-width: 900px) {
  .wrap--split { grid-template-columns: minmax(0, 1fr); }
  .postal, .postal--inv { grid-template-columns: minmax(0, 1fr); }
  .postal--inv .postcard { order: 0; }
  .postal--inv .col { justify-self: start; }

  /* Acto IV en una columna: foto arriba y texto debajo tienen que caber en
     una sola pantalla, porque la escena es fija. La foto cede alto. Va en 900,
     que es donde colapsa todo el layout, no en 700. */
  .desk { grid-template-columns: minmax(0, 1fr); gap: 18px; }
  .desk__fotos img:not(.ghost) { max-height: 34svh; width: auto; }
  .object--polaroid, .object--signed { width: auto; }
  .desk figcaption { display: none; }

  /* La pelicula, mas baja: el texto de arriba ocupa mas lineas. */
  .medio__abrir img { height: min(40svh, 420px); }

  /* El retrato deja de acompanar: en una columna no hay columna de al lado. */
  .cero__retrato { position: static; }
  .seal img { width: 68px; }
}

@media (max-width: 700px) {
  body { font-size: 1.12rem; }
  /* Una imagen con display:none y loading=lazy no llega a descargarse. */
  .frame__l, .frame__r, .rail, .stage--return .frame__l, .stage--return .frame__r { display: none; }
  .frame__med { width: 52px; }
  .omen { width: 62px; right: 18px; top: 74px; }
  .shot__text { left: 18px; right: 18px; width: auto; }
  .grain { display: none; }        /* capa fija con blend: lo primero que se corta */
  .lupa { width: 96vw; padding: 12px; }
  .lupa__img { max-height: calc(92vh - 9rem); }
  /* La caja de una postal rotada es mas ancha que la postal: margen para que
     no roce el borde en pantallas estrechas. Vale para todo lo que se posa
     torcido, no solo para las postales: la Polaroid entra a 5.4 grados y su
     caja envolvente mide 388px donde la figura mide 335. */
  .postcard { width: min(var(--cap-venice), 94%); }
  .postcard--fern { width: min(var(--cap-fern), 94%); }
  .quiet, .seal { bottom: 8px; }
  .buzon { bottom: 104px; padding: 9px 22px 8px 16px; }
  .buzon__ranura { width: 70px; }
  /* En el movil la ventanilla es la pantalla entera. */
  .ventanilla {
    inset: 0; width: 100%; height: 100%;
    border: 0; border-radius: 0; box-shadow: none;
  }

}

/* Viewports bajos: los actos con scrub no pueden volverse interminables. */
@media (max-height: 560px) {
  .scrub { height: min(var(--scrub-h, 250vh), 1400px); }
}

/* =========================================================================
   Movimiento reducido y modo quieto: todo aparece resuelto, nada se mueve.
   ========================================================================= */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  /* El presagio aparecía por transición con 1.6s de retardo, y anular
     duraciones no anula retardos: sin esto tarda en aparecer aunque ya no
     haya nada que animar. El título ya no necesita rescate aquí — es texto
     real y js/door.js solo lo vuelve transparente si el polvo llegó a
     formarse. */
  .omen { transition-delay: 0s !important; }
  .door__cursor { animation: none !important; opacity: 0; }
}

.still * , .still *::before, .still *::after {
  animation: none !important;
  transition: none !important;
}
.still .frame__t, .still .frame__b, .still .frame__l, .still .frame__r { clip-path: inset(0 0 0 0); }
.still .frame__med, .still .reveal { opacity: 1; transform: none; }
.still .omen { opacity: .75; }
.still .cancel__wrong::after { transform: rotate(var(--ang, -2.4deg)) scaleX(1); }
.still .cancel__stamp, .still .cancel__right { opacity: 1; transform: none; }
.still .cancel--wrap .cancel__wrong { background-size: 100% .11em; }
.still .settle { opacity: 1; transform: rotate(var(--rest, 0deg)); }
.still .grow { -webkit-mask-image: none; mask-image: none; }

.still .door__cursor { opacity: 0; }
.still .ventanilla__persiana { display: none; }
.still .cancel--scrub .cancel__wrong::after { transform: rotate(var(--ang, -2.4deg)) scaleX(1); }

/* Congelados, el fundido y la pelicula se quedarian en su ultimo fotograma:
   la Polaroid invisible y las primeras piezas fuera de pantalla por la
   izquierda. La pagina tiene que seguir teniendo sentido, asi que en modo
   quieto los dos dejan de ser escenas fijas y lo enseñan todo. */
.still .act--desk .scrub,
.still .act--medio .scrub { height: auto; }
.still .stage--desk,
.still .stage--medio { position: static; height: auto; overflow: visible; }
.still .stage--desk { padding-block: clamp(64px, 12vh, 140px); }
.still .desk { grid-template-areas: "fa ta" "fb tb"; row-gap: clamp(48px, 8vh, 96px); }
.still .desk__fotos, .still .desk__textos { display: contents; }
.still .desk__a, .still .desk__b { opacity: 1; scale: none; translate: none; }
.still .desk__fotos .desk__a  { grid-area: fa; }
.still .desk__fotos .desk__b  { grid-area: fb; }
.still .desk__textos .desk__a { grid-area: ta; }
.still .desk__textos .desk__b { grid-area: tb; }
.still .medio__tira { translate: none; width: auto; flex-wrap: wrap; justify-content: center; }
.still .cancel--scrub .cancel__stamp,
.still .cancel--scrub .cancel__right { opacity: 1; }
