/* =====================================================================
   RINAT IZHAK — hoja de estilos
   Paleta: negro tinta / papel crudo. Sin dependencias externas.
   Orden del archivo:
     1. Variables y reset
     2. Cabecera
     3. Portada (vídeo + nombre)
     4. Telón de carga
     5. Accesibilidad / responsive
     6. Sección «Off Screen»
     7. Ruleta de proyectos
     8. Obras (pasan en 3D)
     9. Sobre mí
     9b. Contacto
    10. Página de serie (/obra/<slug>)
    11. Página de proyecto (/proyecto/<slug>)
   ===================================================================== */

/* ---------------------------------------------------------------
   1. VARIABLES Y RESET
   --------------------------------------------------------------- */
:root {
  --bg:        #0a0a0a;
  --paper:     #f2efe8;   /* blanco cálido: el papel de la obra */
  --ink:       #f2efe8;
  --muted:     #8d8981;
  --line:      rgba(242, 239, 232, .14);
  --line-soft: rgba(242, 239, 232, .07);

  --sans:  "Helvetica Neue", Helvetica, "Segoe UI", Arial, sans-serif;
  --serif: "Didot", "Bodoni MT", "Playfair Display", Georgia, "Times New Roman", serif;

  --pad: clamp(20px, 5vw, 80px);
  --ease:     cubic-bezier(.22, .61, .36, 1);
  --ease-out: cubic-bezier(.16, 1, .3, 1);

  /* Oscurecido general del vídeo. Bajo a propósito: el peso se lo lleva la
     sombra concentrada detrás del nombre (ver .hero__scrim). */
  --scrim: .20;

  /* Hasta dónde crece "artista visual" en una vuelta del vídeo.
     2 = crece un 100% (dobla su tamaño). 1.4 = crece un 40%. */
  --sub-grow: 2;
}

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0 }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth }

/* Cuando obra.js enciende el scroll suave, el desplazamiento lo lleva él
   fotograma a fotograma; el `scroll-behavior` del navegador sobra (serían
   dos suavizados peleándose por la misma rueda). Por lo mismo se apaga el
   imán de aquí abajo: si algún día esa página tuviera secciones con anclaje,
   el navegador y el JS se estarían disputando la misma rueda. */
html.tiene-suave { scroll-behavior: auto; scroll-snap-type: none }

/* --- El imán: cada sección, siempre a pantalla completa ---------
   El scroll no se queda a medias entre dos secciones: al soltar, la
   página termina el viaje ella sola.

   Lo de aquí abajo es la RED DE SEGURIDAD, para quien no tenga
   JavaScript o lleve «reducir movimiento». Con JavaScript manda
   `iman.js`, que apaga esto (`html.iman-js`) y hace lo mismo pero con
   un viaje largo y con curva: el del navegador se despacha en unos
   200 ms y, con secciones de pantalla entera, eso se lee como un corte
   y no como scroll. El CSS no deja regular esa duración; de ahí el JS.

   Dos matices que valen para las dos versiones:

    · «Obras» y «Sobre mí» NO son de una pantalla: se fijan y el scroll
      las anima, así que miden varias (se lo pone su propio JS en
      píxeles). Ahí el imán solo agarra el borde de arriba. Mientras la
      sección tapa la pantalla entera, el navegador deja parar donde sea
      y la animación corre como siempre; al agotarse, el punto de
      anclaje siguiente es justo el principio de la sección de abajo.

    · En táctil el imán del CSS es flojo (`proximity`) a propósito: en
      el móvil la barra del navegador se esconde y se enseña, con ella
      cambia el alto de la pantalla, y un imán fuerte reajustaría a cada
      cambio. Con ratón o trackpad va fuerte (`mandatory`). */
html { scroll-snap-type: y proximity }

@media (hover: hover) and (pointer: fine) {
  html { scroll-snap-type: y mandatory }
}

.hero, .offscreen, .ruleta, .obras, .sobre, .contacto { scroll-snap-align: start }

/* Un gesto, una sección: un golpe de rueda fuerte no se puede saltar
   entera ninguna de las tres que caben justas en pantalla. */
.hero, .offscreen, .ruleta { scroll-snap-stop: always }

/* Y cuando `iman.js` toma el mando, el del navegador se aparta: si no,
   los dos irían a por lo mismo y el suyo llega antes. */
html.iman-js { scroll-snap-type: none }

body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  font-weight: 300;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  /* `clip` recorta igual que `hidden` pero NO crea un contenedor de
     scroll, que es lo que rompería el `position: sticky` de la tira de
     obras. `hidden` queda de repuesto para navegadores muy viejos. */
  overflow-x: hidden;
  overflow-x: clip;
}

/* Mientras carga no se puede hacer scroll: la portada es lo único que hay. */
body.is-loading { overflow: hidden; height: 100vh }

img, video { display: block; max-width: 100%; height: auto }
a { color: inherit; text-decoration: none }
em { font-family: var(--serif); font-style: italic; font-weight: 400 }

::selection { background: var(--paper); color: var(--bg) }

/* ---------------------------------------------------------------
   2. CABECERA
   --------------------------------------------------------------- */
.hdr {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 22px var(--pad);
  padding-top: max(22px, env(safe-area-inset-top));
  opacity: 0;
  transform: translateY(-12px);
  transition: opacity .9s var(--ease-out), transform .55s var(--ease-out);
  pointer-events: none;
}
body.is-ready .hdr { opacity: 1; transform: none; pointer-events: auto }

.hdr__brand {
  font-size: .74rem;
  letter-spacing: .32em;
  text-transform: uppercase;
}

.hdr__nav {
  display: flex;
  align-items: center;
  gap: clamp(14px, 2.4vw, 34px);
  font-size: .7rem;
  letter-spacing: .24em;
  text-transform: uppercase;
}
.hdr__nav a { position: relative; padding-bottom: 3px }
.hdr__nav a::after {
  content: "";
  position: absolute;
  left: 0; bottom: 0;
  width: 100%; height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: right;
  transition: transform .5s var(--ease-out);
}
.hdr__nav a:hover::after { transform: scaleX(1); transform-origin: left }

.hdr__lang { color: var(--muted); letter-spacing: .18em }
.hdr__lang b { color: var(--paper); font-weight: 400 }
.hdr__lang i { font-style: normal; margin: 0 .35em; opacity: .5 }

/* ---------------------------------------------------------------
   3. PORTADA
   --------------------------------------------------------------- */
.hero {
  position: relative;
  height: 100vh;
  height: 100svh;      /* en móvil no salta al esconderse la barra del navegador */
  width: 100%;
  overflow: hidden;
  background: var(--bg);
}

.hero__media {
  position: absolute;
  inset: 0;
  /* El fotograma fijo, siempre detrás del vídeo (la variable la pone el PHP). */
  background-image: var(--poster);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}

.hero__video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* El vídeo entra con un fundido lento en cuanto el telón se levanta. */
  opacity: 0;
  transform: scale(1.06);
  transition: opacity 1.6s var(--ease-out), transform 2.6s var(--ease-out);
}
body.is-ready .hero__video { opacity: 1; transform: none }

/* Modo estático: conexión mala o el vídeo se ha atascado. Se funde hacia el
   fotograma fijo en lugar de dejar una imagen congelada a tirones.
   Si más tarde hay vídeo suficiente, se quita la clase y entra solo. */
body.is-static .hero__video { opacity: 0; transform: none; transition: opacity .9s var(--ease) }

/* Oscurecido del vídeo. El vídeo va del blanco casi puro a planos muy oscuros,
   así que un velo uniforme no vale: o el nombre se pierde en los claros, o
   aplasta los oscuros. Se hace por capas, de arriba abajo:
     1. sombra CONCENTRADA justo detrás del nombre (la que hace el trabajo)
     2. degradados arriba y abajo, para la cabecera y el "desliza"
     3. viñeta suave en las esquinas
     4. un velo general mínimo
   Para ajustarlo: --scrim sube o baja el velo general; el primer radial,
   la sombra del nombre. */
.hero__scrim {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(58% 40% at 50% 47%,
      rgba(10, 10, 10, .66) 0%,
      rgba(10, 10, 10, .40) 45%,
      rgba(10, 10, 10, 0) 78%),
    linear-gradient(to bottom,
      rgba(10, 10, 10, .60) 0%,
      rgba(10, 10, 10, .10) 22%,
      rgba(10, 10, 10, .10) 68%,
      rgba(10, 10, 10, .66) 100%),
    radial-gradient(125% 95% at 50% 45%,
      rgba(10, 10, 10, 0) 45%,
      rgba(10, 10, 10, .42) 100%),
    rgba(10, 10, 10, var(--scrim));
}

/* --- el nombre ------------------------------------------------- */
.hero__lockup {
  position: absolute;
  inset: 0;
  z-index: 30;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: var(--pad);
  pointer-events: none;
}

.hero__name {
  font-size: clamp(2rem, 8.2vw, 7.5rem);
  font-weight: 200;
  letter-spacing: .14em;
  text-transform: uppercase;
  line-height: 1;
  white-space: nowrap;
  display: flex;
  justify-content: center;
  align-items: baseline;
  /* Al levantarse el telón el nombre no se va: solo se asienta un poco
     y coge una sombra suave para despegarse del vídeo. */
  transform: scale(1.04);
  transition: transform 1.8s var(--ease-out), text-shadow 1.2s var(--ease);
  text-shadow: 0 0 0 rgba(0, 0, 0, 0);
}
body.is-ready .hero__name {
  transform: none;
  text-shadow: 0 2px 40px rgba(0, 0, 0, .55), 0 1px 3px rgba(0, 0, 0, .35);
}

/* Cada letra la inyecta el JS para poder escribirlas de una en una. */
.hero__name .ltr {
  display: inline-block;
  opacity: 0;
  transform: translateY(.28em);
  filter: blur(6px);
  transition: opacity .5s var(--ease-out),
              transform .6s var(--ease-out),
              filter .5s var(--ease-out);
}
.hero__name .ltr.on { opacity: 1; transform: none; filter: none }
.hero__name .sp { display: inline-block; width: .42em }

/* El cursor que va escribiendo. */
.caret {
  display: inline-block;
  width: 1px;
  height: .92em;
  background: var(--paper);
  margin-left: .06em;
  transform: translateY(.04em);
  animation: blink .9s steps(1, end) infinite;
}
@keyframes blink { 0%, 49% { opacity: 1 } 50%, 100% { opacity: 0 } }
.caret.off { animation: none; opacity: 0; transition: opacity .35s }

.hero__sub {
  margin-top: 2.2em;
  font-size: .72rem;
  letter-spacing: .42em;
  text-transform: uppercase;
  color: var(--muted);
  opacity: 0;
  transform: translateY(8px);
  transition: opacity .8s var(--ease-out), transform .8s var(--ease-out);
}
.hero__sub.on { opacity: 1; transform: none }
body.is-ready .hero__sub { color: rgba(242, 239, 232, .78) }

/* "artista visual" va creciendo despacio mientras corre el vídeo.
   Tarda una vuelta entera (--video-secs) en llegar a --sub-grow y la vuelta
   siguiente en volver, gracias a `alternate`. Así nunca da un salto seco al
   reiniciarse el bucle. `linear` a propósito: la idea es que no se note el
   movimiento, solo que al rato el texto es más grande que al principio.
   El transform vive en el <span> de dentro para no pisar la animación de
   entrada del <p>. */
.hero__sub-in {
  display: inline-block;
  transform-origin: center center;
  will-change: transform;
}
body.is-ready .hero__sub-in {
  animation: sub-grow var(--video-secs, 49s) linear infinite alternate;
}
@keyframes sub-grow {
  from { transform: scale(1) }
  to   { transform: scale(var(--sub-grow)) }
}

/* --- flecha de "sigue bajando" --------------------------------- */
.hero__cue {
  position: absolute;
  left: 50%;
  bottom: max(28px, env(safe-area-inset-bottom));
  z-index: 30;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  font-size: .62rem;
  letter-spacing: .34em;
  text-transform: uppercase;
  color: var(--muted);
  opacity: 0;
  transition: opacity 1s var(--ease-out) .6s;
}
body.is-ready .hero__cue { opacity: 1 }

.hero__cue-line {
  width: 1px;
  height: 46px;
  background: linear-gradient(to bottom, var(--line), transparent);
  position: relative;
  overflow: hidden;
}
.hero__cue-line::after {
  content: "";
  position: absolute;
  left: 0; top: -50%;
  width: 1px; height: 50%;
  background: var(--paper);
  animation: cue 2.4s var(--ease) infinite;
}
@keyframes cue {
  0%   { transform: translateY(0);    opacity: 0 }
  30%  { opacity: 1 }
  100% { transform: translateY(300%); opacity: 0 }
}

/* ---------------------------------------------------------------
   4. TELÓN DE CARGA
   Negro total encima del vídeo y debajo del nombre. Se levanta de
   abajo a arriba cuando hay vídeo suficiente en el buffer.
   --------------------------------------------------------------- */
.curtain {
  position: absolute;
  inset: 0;
  z-index: 20;
  background: var(--bg);
  clip-path: inset(0 0 0 0);
  transition: clip-path 1.25s var(--ease-out);
  will-change: clip-path;
}
body.is-ready .curtain { clip-path: inset(0 0 100% 0); pointer-events: none }

.loader {
  position: absolute;
  z-index: 30;
  left: var(--pad);
  right: var(--pad);
  bottom: max(calc(var(--pad) * .8), env(safe-area-inset-bottom));
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: baseline;
  gap: 14px;
  font-size: .66rem;
  letter-spacing: .3em;
  text-transform: uppercase;
  color: var(--muted);
  transition: opacity .45s var(--ease);
}
body.is-ready .loader { opacity: 0; pointer-events: none }

.loader__count {
  font-size: clamp(1.4rem, 4vw, 2.6rem);
  letter-spacing: .02em;
  color: var(--paper);
  font-weight: 200;
  font-variant-numeric: tabular-nums;
}
.loader__count i {
  font-style: normal;
  font-size: .4em;
  vertical-align: super;
  color: var(--muted);
  margin-left: .2em;
}

.loader__track {
  grid-column: 1 / -1;
  height: 1px;
  background: var(--line-soft);
  overflow: hidden;
}
.loader__fill {
  display: block;
  height: 100%;
  width: 0%;
  background: var(--paper);
  transition: width .35s var(--ease-out);
}

/* Aviso de conexión lenta: aparece si tarda demasiado. */
.loader__label::after {
  content: "";
  opacity: 0;
  transition: opacity .5s;
}
body.is-slow .loader__label::after {
  content: " · conexión lenta";
  opacity: 1;
}

/* ---------------------------------------------------------------
   5. ACCESIBILIDAD Y RESPONSIVE
   --------------------------------------------------------------- */

/* Si el sistema pide menos movimiento se apagan los ADORNOS: la escritura
   letra a letra, el zoom lento de la portada, los fundidos largos y el
   parpadeo del cursor. El vídeo se sigue viendo: es la obra, no un efecto.
   (La decisión está explicada en main.js, donde se calcula `noVideo`.) */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto }

  .hero__name, .hero__name .ltr, .hero__video, .curtain, .hero__sub, .hero__sub-in {
    transition: none !important;
    animation: none !important;
    transform: none !important;
    filter: none !important;
  }
  .caret { display: none }
  .hero__cue-line::after { animation: none }
}

@media (max-width: 720px) {
  :root { --scrim: .26 }

  /* En móvil el nombre no baja de 2rem, así que se queda relativamente
     pequeño; si el subtítulo creciera el doble acabaría siendo MÁS ancho que
     el nombre y se invertiría la jerarquía. Se recorta el crecimiento. */
  :root { --sub-grow: 1.45 }

  .hero__name { letter-spacing: .1em }
  /* Solo los enlaces sueltos del menú; el selector de idioma se queda entero
     (si se ocultara con `a:not(...)` desaparecería el "EN" y quedaría "ES /"). */
  .hdr__nav > a { display: none }
  .loader { font-size: .58rem; letter-spacing: .22em }
}

@media (max-width: 480px) {
  :root { --sub-grow: 1.3 }
}

/* En apaisado y pantalla baja (móvil tumbado) el nombre no debe comerse todo. */
@media (max-height: 480px) {
  :root { --sub-grow: 1.4 }
  .hero__name { font-size: clamp(1.5rem, 6vw, 3rem) }
  .hero__sub  { margin-top: 1em }
  .hero__cue  { display: none }
}

/* ---------------------------------------------------------------
   6. SECCIÓN «OFF SCREEN»
   Vídeo de fondo + mancha de tinta que crece (canvas) + título.
   Las clases de estado las pone offscreen.js en la <section>:
     .is-playing  el vídeo ya corre   → se funde sobre el fotograma fijo
     .is-titled   la mancha ya cubre el centro → entra el título
     .is-grown    la mancha ha terminado → se aclara y deja ver el vídeo
   --------------------------------------------------------------- */
.offscreen {
  position: relative;
  height: 100vh;
  height: 100svh;
  width: 100%;
  overflow: hidden;
  background: var(--bg);

  /* Diámetro de la mancha. Todo lo demás (canvas y título) cuelga de aquí,
     así el título siempre cabe dentro de la parte maciza de la tinta. */
  --blot-d: min(92vh, 94vw);
  --blot-d: min(92svh, 94vw);
}

.offscreen__media {
  position: absolute;
  inset: 0;
  background-image: var(--poster);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}

.offscreen__video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 1.4s var(--ease-out);
}
.offscreen.is-playing .offscreen__video { opacity: 1 }

/* El vídeo es casi todo papel blanco: sin esta sombra arriba, la cabecera
   (texto claro) desaparecería al pasar por encima. Abajo, lo justo para
   empalmar con lo que venga después. */
.offscreen__scrim {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    linear-gradient(to bottom,
      rgba(10, 10, 10, .55) 0%,
      rgba(10, 10, 10, 0) 18%,
      rgba(10, 10, 10, 0) 85%,
      rgba(10, 10, 10, .35) 100%);
}

/* El canvas es un cuadrado un 10 % mayor que la mancha, para que quepan las
   puntas de los dedos. El JS lee su tamaño para dibujar. */
.offscreen__blot {
  position: absolute;
  left: 50%;
  top: 50%;
  width: calc(var(--blot-d) * 1.1);
  height: calc(var(--blot-d) * 1.1);
  max-width: none;
  transform: translate(-50%, -50%);
  pointer-events: none;
  transition: opacity 3s var(--ease);
}

/* Cuando la mancha termina de crecer se vuelve algo transparente, y a través
   del negro se ve el vídeo. Más bajo = más vídeo; por debajo de ~.6 el
   título empieza a leerse peor en los planos blancos. */
.offscreen.is-grown .offscreen__blot { opacity: var(--blot-rest, .8) }

.offscreen__lockup {
  position: absolute;
  inset: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}

/* Bloque del título: "by Rinat Izhak" se alinea a la derecha, bajo el corchete. */
.offscreen__type {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  /* Un pelo por encima del centro, como en el diseño original. */
  transform: translateY(-4%);
}

.offscreen__title {
  font-size: calc(var(--blot-d) * .082);
  font-weight: 300;
  letter-spacing: .01em;
  line-height: 1;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--paper);

  /* Entrada: aparece desenfocado, como la tinta al asentarse en el papel. */
  opacity: 0;
  filter: blur(10px);
  transform: scale(.96);
  transition: opacity 1.6s var(--ease-out),
              filter 1.8s var(--ease-out),
              transform 2.4s var(--ease-out);
}
.offscreen__title b { font-weight: 700 }

/* Los corchetes, más finos que el texto y un poco más altos. */
.offscreen__br {
  display: inline-block;
  font-weight: 200;
  transform: scaleY(1.14);
}

.offscreen__by {
  margin-top: .55em;
  margin-right: .5em;
  font-size: calc(var(--blot-d) * .026);
  font-weight: 400;
  letter-spacing: .02em;
  line-height: 1;
  color: var(--paper);
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 1.2s var(--ease-out) .9s, transform 1.2s var(--ease-out) .9s;
}

.offscreen.is-titled .offscreen__title { opacity: 1; filter: none; transform: none }
.offscreen.is-titled .offscreen__by    { opacity: 1; transform: none }

/* En móvil "by Rinat Izhak" quedaría diminuto al ir en proporción a la mancha. */
@media (max-width: 720px) {
  .offscreen__by { font-size: max(.72rem, calc(var(--blot-d) * .03)) }
}

@media (prefers-reduced-motion: reduce) {
  .offscreen__video, .offscreen__title, .offscreen__by {
    transition: none !important;
    filter: none !important;
  }
}

/* ---------------------------------------------------------------
   7. RULETA DE PROYECTOS
   Anillo de miniaturas + imagen grande en el centro. Las posiciones
   de las miniaturas las calcula ruleta.js (transform en cada <li>);
   aquí solo van tamaños y aspecto. Todo cuelga de --ru-d (el lado
   del cuadrado que ocupa la ruleta):
     --ru-r  radio del anillo
     --ru-t  diámetro de las miniaturas
     --ru-c  diámetro de la imagen del centro
   Clases de estado (las pone el JS):
     .ruleta.is-in        la sección ya se ha visto → entra el marco
     .ruleta__stage.is-dragging  arrastrando
     .ruleta__img.is-on   la imagen nueva se descubre desde abajo
   --------------------------------------------------------------- */
.ruleta {
  position: relative;
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: clamp(14px, 2.6vh, 30px);
  padding-inline: var(--pad);
  padding-block: clamp(76px, 11vh, 110px) clamp(32px, 6vh, 64px);
  background: var(--bg);
  overflow: hidden;

  --ru-d: min(64vh, 100vw - 2 * var(--pad));
  --ru-d: min(64svh, 100vw - 2 * var(--pad));
  --ru-r: calc(var(--ru-d) * .44);
  --ru-t: calc(var(--ru-d) * .125);
  --ru-c: calc(var(--ru-d) * .66);
}

.ruleta__hint {
  font-size: .62rem;
  letter-spacing: .38em;
  text-transform: uppercase;
  color: var(--muted);
  opacity: 0;
  transition: opacity 1.2s var(--ease-out) .8s;
}
.ruleta.is-in .ruleta__hint { opacity: 1 }

.ruleta__stage {
  position: relative;
  flex: none;
  width: var(--ru-d);
  height: var(--ru-d);
  /* En móvil: el gesto vertical sigue haciendo scroll; el horizontal gira. */
  touch-action: pan-y;
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
  cursor: grab;
  outline: none;
}
.ruleta__stage.is-dragging { cursor: grabbing }
.ruleta__stage:focus-visible .ruleta__ring { border-color: rgba(242, 239, 232, .4) }

/* El anillo: una línea finísima por donde corren las miniaturas. */
.ruleta__ring {
  position: absolute;
  left: 50%;
  top: 50%;
  width: calc(var(--ru-r) * 2);
  height: calc(var(--ru-r) * 2);
  border: 1px solid var(--line);
  border-radius: 50%;
  transform: translate(-50%, -50%) scale(.6);
  opacity: 0;
  transition: transform 1.6s var(--ease-out), opacity 1.2s var(--ease-out), border-color .3s;
}
.ruleta.is-in .ruleta__ring { transform: translate(-50%, -50%); opacity: 1 }

/* --- el centro -------------------------------------------------- */
.ruleta__center {
  position: absolute;
  left: 50%;
  top: 50%;
  z-index: 1;
  width: var(--ru-c);
  height: var(--ru-c);
  border-radius: 50%;
  overflow: hidden;
  background: #141414;
  transform: translate(-50%, -50%);
  /* Evita que el borde redondo parpadee en Safari al animar lo de dentro. */
  isolation: isolate;

  /* Sin `href` (proyecto sin página todavía) es una foto y nada más: no
     recibe el ratón, así que arrastrar por encima gira la ruleta como
     antes de que el centro fuera un enlace. */
  pointer-events: none;
  display: block;
}

/* Con `href` sí es un enlace: se puede pinchar, y se nota. El arrastre
   sigue funcionando porque el evento sube igual hasta la ruleta. */
.ruleta__center[href] {
  pointer-events: auto;
  cursor: pointer;
}

/* Al pasar por encima, un aro fino y la foto un punto más cerca: lo justo
   para que se entienda que ahí se entra. Va en `box-shadow` y no en
   `border` para no mover ni un píxel lo de dentro. */
.ruleta__center[href]::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 6;
  border-radius: 50%;
  box-shadow: inset 0 0 0 1px rgba(242, 239, 232, 0);
  transition: box-shadow .45s var(--ease);
  pointer-events: none;
}
.ruleta__center[href]:hover::before,
.ruleta__center[href]:focus-visible::before {
  box-shadow: inset 0 0 0 1px rgba(242, 239, 232, .55);
}
.ruleta__center[href]:hover .ruleta__img.is-on {
  transform: scale(1.035);
  transition: transform 1.1s var(--ease-out);
}
/* Viñeta suave en el borde, como en la referencia: la imagen no acaba en seco. */
.ruleta__center::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 5;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(10, 10, 10, 0) 62%, rgba(10, 10, 10, .45) 100%);
}

/* Cada cambio mete una capa nueva encima, que se descubre como un círculo que
   crece desde abajo (desde la miniatura activa) mientras se asienta. */
.ruleta__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  object-fit: cover;
  clip-path: circle(0% at 50% 100%);
  transform: scale(1.14);
  transition: clip-path 1.1s var(--ease-out), transform 1.6s var(--ease-out);
}
.ruleta__img.is-on { clip-path: circle(120% at 50% 100%); transform: none }

/* --- las esquinas del marco ------------------------------------- */
.ruleta__corner {
  position: absolute;
  z-index: 2;
  width: calc(var(--ru-d) * .13);
  height: calc(var(--ru-d) * .13);
  border: 0 solid rgba(242, 239, 232, .34);
  pointer-events: none;
  opacity: 0;
  transition: opacity 1.4s var(--ease-out) .5s;
}
.ruleta.is-in .ruleta__corner { opacity: 1 }
.ruleta__corner--tl { left:  calc(50% - var(--ru-c) / 2); top:    calc(50% - var(--ru-c) / 2); border-top-width: 1px;    border-left-width: 1px }
.ruleta__corner--tr { right: calc(50% - var(--ru-c) / 2); top:    calc(50% - var(--ru-c) / 2); border-top-width: 1px;    border-right-width: 1px }
.ruleta__corner--bl { left:  calc(50% - var(--ru-c) / 2); bottom: calc(50% - var(--ru-c) / 2); border-bottom-width: 1px; border-left-width: 1px }
.ruleta__corner--br { right: calc(50% - var(--ru-c) / 2); bottom: calc(50% - var(--ru-c) / 2); border-bottom-width: 1px; border-right-width: 1px }

/* --- las miniaturas --------------------------------------------- */
.ruleta__list { list-style: none }

/* Todas parten del centro; el JS las lleva a su sitio con transform.
   --near (0 a 1) lo pone también el JS: 1 = abajo del todo (la activa). */
.ruleta__item {
  position: absolute;
  left: 50%;
  top: 50%;
  z-index: 3;
  width: var(--ru-t);
  height: var(--ru-t);
  margin: calc(var(--ru-t) / -2) 0 0 calc(var(--ru-t) / -2);
  opacity: 0;
  will-change: transform;
}

.ruleta__thumb {
  display: block;
  width: 100%;
  height: 100%;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 50%;
  overflow: hidden;
  background: #1a1a1a;
  color: inherit;
  font: inherit;
  cursor: pointer;
  opacity: calc(.42 + .58 * var(--near, 0));
  transition: opacity .3s, border-color .3s;
}
.ruleta__thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity .6s var(--ease);
  pointer-events: none;
}
.ruleta__thumb img.is-loaded { opacity: 1 }

.ruleta__thumb:hover,
.ruleta__thumb:focus-visible { opacity: 1; border-color: rgba(242, 239, 232, .55) }
.ruleta__thumb:focus-visible { outline: 1px solid var(--paper); outline-offset: 3px }
.ruleta__stage.is-dragging .ruleta__thumb { cursor: grabbing }
.ruleta__item.is-active .ruleta__thumb { border-color: rgba(242, 239, 232, .5) }

/* --- la flecha blanca ------------------------------------------- */
/* Fija abajo del anillo, un poco por encima de la miniatura activa, que
   asoma por debajo (como en la referencia). */
.ruleta__go {
  position: absolute;
  left: 50%;
  top: calc(50% + var(--ru-r) - var(--ru-t) * .32);
  z-index: 4;
  width: max(44px, calc(var(--ru-t) * .92));
  height: max(44px, calc(var(--ru-t) * .92));
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--paper);
  color: var(--bg);
  transform: translate(-50%, -50%) scale(0);
  transition: transform .7s var(--ease-out) .9s, background-color .3s;
}
.ruleta.is-in .ruleta__go { transform: translate(-50%, -50%) }
.ruleta.is-in .ruleta__go:hover { transform: translate(-50%, -50%) scale(1.08); transition-delay: 0s }
.ruleta__go:focus-visible { outline: 1px solid var(--paper); outline-offset: 4px }
.ruleta__go svg {
  width: 42%;
  height: 42%;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform .45s var(--ease-out);
}
.ruleta__go:hover svg { transform: translateX(2px) }

/* --- el pie: número, título y tipo ------------------------------ */
.ruleta__caption {
  text-align: center;
  max-width: 34rem;
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 1s var(--ease-out) .6s, transform 1s var(--ease-out) .6s;
}
.ruleta.is-in .ruleta__caption { opacity: 1; transform: none }

.ruleta__kicker {
  font-size: .62rem;
  letter-spacing: .38em;
  text-transform: uppercase;
  color: var(--muted);
}
.ruleta__num {
  margin-left: .9em;
  color: var(--paper);
  font-variant-numeric: tabular-nums;
}

.ruleta__title {
  margin-top: .55em;
  font-size: clamp(1.15rem, 1.9vw, 1.75rem);
  font-weight: 200;
  letter-spacing: .06em;
  line-height: 1.15;
  text-transform: uppercase;
  text-wrap: balance;
}
.ruleta__meta {
  margin-top: .7em;
  font-size: .68rem;
  letter-spacing: .28em;
  text-transform: uppercase;
  color: var(--muted);
}

/* Al cambiar de proyecto el texto entra desenfocado, igual que el título
   de «Off Screen». */
.ruleta__caption.is-swap .ruleta__title { animation: ru-in .8s var(--ease-out) both }
.ruleta__caption.is-swap .ruleta__meta  { animation: ru-in .8s var(--ease-out) .12s both }
@keyframes ru-in {
  from { opacity: 0; transform: translateY(8px); filter: blur(6px) }
  to   { opacity: 1; transform: none;            filter: none }
}

/* Pantalla ancha y apaisada: la ruleta crece y el texto se va a la
   esquina de abajo a la izquierda, fuera del círculo. */
@media (min-width: 1000px) and (min-aspect-ratio: 3/2) {
  .ruleta {
    --ru-d: min(78vh, 100vw - 2 * var(--pad));
    --ru-d: min(78svh, 100vw - 2 * var(--pad));
  }
  .ruleta__caption {
    position: absolute;
    left: var(--pad);
    bottom: clamp(40px, 9vh, 96px);
    max-width: calc((100vw - var(--ru-d)) / 2 - var(--pad) - 14px);
    text-align: left;
  }
}

/* Móvil: las miniaturas serían diminutas; se agrandan y el anillo se
   estrecha un poco para que sigan cabiendo. */
@media (max-width: 720px) {
  .ruleta {
    --ru-r: calc(var(--ru-d) * .42);
    --ru-t: calc(var(--ru-d) * .155);
    --ru-c: calc(var(--ru-d) * .6);
  }
}

@media (prefers-reduced-motion: reduce) {
  .ruleta__img, .ruleta__ring, .ruleta__corner, .ruleta__go, .ruleta__caption, .ruleta__hint {
    transition: none !important;
  }
  .ruleta__caption.is-swap .ruleta__title,
  .ruleta__caption.is-swap .ruleta__meta { animation: none }
}

/* ---------------------------------------------------------------
   8. OBRAS (pasan en 3D)

   La sección se fija en pantalla y las obras recorren un escenario en
   perspectiva: salen del fondo en oblicuo, crecen, pasan por delante y se
   van por la izquierda. No es un carrusel: cada obra ocupa un «hueco» en
   una fila que se aleja y el scroll mueve los huecos. La posición de cada
   una la calcula `obras.js` en cada fotograma; aquí solo está el decorado.

   Hay dos modos y el HTML es el mismo en los dos:
     - por defecto (sin JS, o con «reducir movimiento») las obras se apilan
       una debajo de otra, sin 3D y sin fijar nada;
     - con `.is-pin`, que pone el JS, la sección se estira (la altura se la
       da el JS en píxeles) y se monta el escenario.
   --------------------------------------------------------------- */
.obras {
  position: relative;
  background: var(--bg);

  --ob-r: clamp(10px, 1.1vw, 20px);    /* el redondeo de las esquinas */

  /* La proporción de la foto. OJO: tiene que ser la misma que el $AR de
     tools/14-obras.ps1, que es quien recorta las fotos. */
  --ob-ar: 4 / 3;
}

.obras__sticky {
  position: relative;
  padding-block: clamp(74px, 11vh, 112px) clamp(56px, 9vh, 92px);
}
.obras.is-pin .obras__sticky {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  padding: 0;
  overflow: hidden;

  /* El arrastre horizontal es de `obras.js` (ver «4 ter»): con esto el
     navegador se queda el scroll de arriba abajo y el zoom de dos dedos, y
     nos deja a nosotros el de lado, que es el que trae las obras. Sin esta
     línea el navegador da el gesto por suyo y ya no se puede parar.
     La primera declaración es para los Safari viejos, que no entienden
     `pinch-zoom` y tirarían la regla entera. */
  touch-action: pan-y;
  touch-action: pan-y pinch-zoom;
}

/* Penumbra en la banda izquierda: sostiene el título y hace que las obras
   se disuelvan en sombra al salir. A toda altura, así no se ve ningún borde. */
.obras.is-pin .obras__sticky::before {
  content: "";
  position: absolute;
  left: 0; top: 0; bottom: 0;
  z-index: 3;
  width: min(46vw, 560px);
  pointer-events: none;
  background: linear-gradient(to right,
              rgba(10, 10, 10, .93) 0%,
              rgba(10, 10, 10, .68) 42%,
              rgba(10, 10, 10, 0)  100%);
}

/* --- el título, quieto a la izquierda --------------------------- */
.obras__head {
  margin: 0 var(--pad) clamp(34px, 6vh, 54px);
}
.obras.is-pin .obras__head {
  position: absolute;
  z-index: 4;
  left: var(--pad);
  top: clamp(88px, 13vh, 132px);
  margin: 0;
  max-width: min(34vw, 400px);
  pointer-events: none;
  text-shadow: 0 2px 24px rgba(10, 10, 10, .95);
  opacity: 0;
  transform: translateY(12px);
  transition: opacity 1.1s var(--ease-out), transform 1.1s var(--ease-out);
}
.obras.is-pin.is-in .obras__head { opacity: 1; transform: none }

/* Los rótulos van con la misma vara que la ruleta de arriba: sans, versalita
   y el mismo tracking (.62rem/.38em los avisos, .68rem/.28em los pies). */
.obras__kicker {
  margin-bottom: 18px;
  font-size: .62rem;
  letter-spacing: .38em;
  text-transform: uppercase;
  color: var(--muted);
}
.obras__heading {
  font-size: clamp(1.35rem, 2.5vw, 2.1rem);
  font-weight: 200;
  line-height: 1.15;
  letter-spacing: .06em;
  text-transform: uppercase;
}
.obras__count {
  margin-top: 16px;
  font-size: .68rem;
  letter-spacing: .28em;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* --- el escenario ----------------------------------------------- */
.obras__stage {
  display: grid;
  gap: clamp(30px, 6vh, 64px);
  padding-inline: var(--pad);
}
.obras.is-pin .obras__stage {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: block;
  padding: 0;
  perspective: 1500px;
  perspective-origin: 44% 50%;
  transform-style: preserve-3d;
}

.obra { position: relative }
.obras.is-pin .obra {
  position: absolute;
  left: 44%;
  top: 50%;
  width: clamp(260px, 42vw, 640px);
  transform-origin: 50% 50%;
  transform-style: preserve-3d;
  backface-visibility: hidden;
  will-change: transform, opacity;
}

.obra__idx {
  display: block;
  margin-bottom: 12px;
  font-size: .62rem;
  letter-spacing: .38em;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.obras.is-pin .obra__idx {
  position: absolute;
  top: -26px;
  left: 0;
  margin: 0;
}

.obra__link { display: block; color: inherit }

/* La obra va a hueso, sin passe-partout: solo la sombra la despega del
   fondo. El envoltorio se queda porque es lo que oscurece `obras.js` al
   salir por la izquierda (el filtro no puede ir en la tarjeta: la aplana y
   rompe el apilado 3D). */
.obra__frame {
  display: block;
  border-radius: var(--ob-r);
  transition: box-shadow .7s var(--ease-out);
}
.obras.is-pin .obra__frame { box-shadow: 0 30px 80px rgba(0, 0, 0, .55) }

.obra__shot {
  position: relative;
  display: block;
  aspect-ratio: var(--ob-ar);
  border-radius: var(--ob-r);
  overflow: hidden;
  background: #141414;
  /* Safari, al animar lo de dentro, se olvida del borde redondeado. */
  isolation: isolate;
}

.obra__img {
  width: 100%;
  height: 100%;
  max-width: none;
  object-fit: cover;
  opacity: 0;
  transition: opacity .9s var(--ease), transform 1.2s var(--ease-out);
}
.obra__img.is-loaded { opacity: 1 }

/* El degradado que hace legible el nombre sin apagar la obra. */
.obra__veil {
  position: absolute;
  inset: 0;
  background: linear-gradient(to top,
              rgba(10, 10, 10, .86) 0%,
              rgba(10, 10, 10, .30) 34%,
              rgba(10, 10, 10, 0)   62%);
  pointer-events: none;
}

/* El nombre, incrustado abajo a la derecha de la obra. En el HTML va
   fuera del marco (es el figcaption) pero se coloca encima de la foto. */
.obra__cap {
  position: absolute;
  z-index: 2;
  pointer-events: none;
  left:   clamp(16px, 1.7vw, 26px);
  right:  clamp(16px, 1.7vw, 26px);
  bottom: clamp(14px, 1.5vw, 22px);
  text-align: right;
}
/* Mismo corte que el título de la ruleta: sans, versalita y tracking ancho.
   El Didot queda para los acentos (el nombre del fondo de «Sobre mí» y los
   <em>), no para los rótulos. */
.obra__name {
  font-weight: 200;
  font-size: clamp(1.15rem, 1.9vw, 1.75rem);
  line-height: 1.15;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--paper);
  text-shadow: 0 1px 18px rgba(0, 0, 0, .6);
}
.obra__meta {
  margin-top: .7em;
  font-size: .68rem;
  letter-spacing: .28em;
  text-transform: uppercase;
  color: rgba(242, 239, 232, .7);
  font-variant-numeric: tabular-nums;
}

/* Solo las que ya tienen página son enlaces: ahí sí hay reacción. */
a.obra__link { cursor: pointer }
a.obra__link:hover .obra__img { transform: scale(1.04) }
a.obra__link:focus-visible .obra__frame { outline: 1px solid var(--paper); outline-offset: 5px }

/* --- el pie: barra, contador y aviso ---------------------------- */
.obras__ui {
  display: flex;
  align-items: center;
  gap: 20px;
  margin: clamp(34px, 6vh, 56px) var(--pad) 0;
  font-size: .62rem;
  letter-spacing: .38em;
  text-transform: uppercase;
  color: var(--muted);
}
.obras.is-pin .obras__ui {
  position: absolute;
  left: var(--pad);
  right: var(--pad);
  bottom: clamp(30px, 5vh, 52px);
  z-index: 5;
  margin: 0;
  opacity: 0;
  transition: opacity 1.2s var(--ease-out) .4s;
}
.obras.is-pin.is-in .obras__ui { opacity: 1 }

.obras__bar {
  position: relative;
  flex: 1;
  height: 1px;
  overflow: hidden;
  background: var(--line);
}
.obras__bar i {
  position: absolute;
  inset: 0;
  background: var(--paper);
  transform: scaleX(0);
  transform-origin: left;
}
.obras__uitxt { white-space: nowrap }

/* El aviso se retira en cuanto la fila echa a andar: ya no hace falta y
   estorba. La clase la pone `obras.js` al primer movimiento. */
.obras.is-pin .obras__cue { transition: opacity .7s var(--ease-out) }
.obras.is-pin.is-moved .obras__cue { opacity: 0 }

.obras__uitxt b {
  color: var(--paper);
  font-weight: 400;
  font-variant-numeric: tabular-nums;
}

/* --- pantallas estrechas ---------------------------------------- */
/* El escenario se mantiene igual: lo mueve el scroll de la página. Solo
   cambia la escala (y el resto lo ajusta el JS, que junta más las obras y
   enseña menos al fondo). Lo que sí cambia aquí es el gesto: además del
   scroll de siempre se puede arrastrar de lado (ver «4 ter» en obras.js). */
@media (max-width: 900px) {
  .obras.is-pin .obras__head { top: clamp(80px, 12vh, 120px); max-width: 70vw }
  .obras.is-pin .obras__sticky::before {
    width: min(30vw, 190px);
    background: linear-gradient(to right, rgba(10, 10, 10, .86), rgba(10, 10, 10, 0));
  }
  .obras.is-pin .obras__stage { perspective: 1000px; perspective-origin: 38% 54% }
  .obras.is-pin .obra { left: 40%; width: min(78vw, 380px) }

  /* El aviso, que antes se escondía por falta de sitio, aquí es justo donde
     hace falta: el arrastre de lado no se adivina. Sale más corto y con la
     flecha, que es la que dice hacia dónde. */
  .obras.is-pin .obras__ui { gap: 12px }
  .obras__cue-larga { display: none }
  .obras__cue::after { content: " \2190" }
}

@media (prefers-reduced-motion: reduce) {
  .obra__frame, .obra__img, .obras__head, .obras__ui { transition: none !important }
}

/* ---------------------------------------------------------------
   9. SOBRE MÍ

   Foto a la izquierda, texto a la derecha, sobre negro. Al llegar, la
   sección se queda fija y el scroll ENCIENDE el texto palabra a palabra:
   arranca casi apagado y una «cabeza de luz» lo recorre al ritmo del
   dedo. Los dos párrafos no se ven a la vez: el segundo releva al
   primero en el mismo sitio, y al final queda sola la frase de cierre.

   Igual que en las obras, hay dos modos y el HTML es el mismo:
     - por defecto (sin JS, o con «reducir movimiento») la sección ocupa
       una pantalla, el texto sale entero y encendido y no se fija nada;
     - con `.is-pin`, que pone el JS, la sección se estira (la altura se
       la da el JS en píxeles) y `.sobre__sticky` se queda clavada.
   --------------------------------------------------------------- */
.sobre {
  position: relative;
  background: var(--bg);

  /* La luz de las palabras que aún no ha alcanzado el scroll. Por debajo
     de ~.10 el texto deja de intuirse y ya no invita a leer; por encima
     de ~.25 no se nota que se enciende. */
  --so-dim: .15;

  /* La luz del nombre que va detrás de la foto. Este es el valor de
     reposo, el de cuando ya está ella delante. Al llegar a la sección el
     JS lo sube (a .30, ver MARCA_SOLA en sobre.js), porque al principio el
     nombre se ve solo y tiene que leerse. Sin JS se queda siempre en este. */
  --so-marca: .05;

  /* El ancho de la foto. Contenido a propósito: el original mide 399 px
     de ancho y a sangre se vería blando. Si algún día aparece una copia
     grande, se sube este valor y ya está. */
  --so-foto: clamp(190px, 22vw, 330px);
}

.sobre__sticky {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 100vh;
  min-height: 100svh;
  padding-block: clamp(74px, 11vh, 112px) clamp(56px, 9vh, 92px);
  padding-inline: var(--pad);
  overflow: hidden;
  /* Este fondo no es decorativo: es contra lo que mezcla la foto.
     Ver el `mix-blend-mode` de .sobre__img. */
  background: var(--bg);
}
.sobre.is-pin .sobre__sticky {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  min-height: 0;
}

/* --- el rótulo de arriba ---------------------------------------- */
.sobre__head {
  position: absolute;
  top: clamp(74px, 11vh, 112px);
  left: var(--pad);
  /* Por encima de la foto: en móvil van en columna y, al subir la foto
     con el parallax, su borde de arriba llegaba a taparlo. */
  z-index: 3;
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 1s var(--ease-out), transform 1s var(--ease-out);
}
.sobre.is-in .sobre__head { opacity: 1; transform: none }

.sobre__kicker {
  font-size: .62rem;
  letter-spacing: .38em;
  text-transform: uppercase;
  color: var(--muted);
}
.sobre__desde {
  margin-left: .9em;
  color: var(--paper);
  font-variant-numeric: tabular-nums;
}

/* --- la rejilla -------------------------------------------------- */
.sobre__grid {
  display: grid;
  grid-template-columns: auto minmax(0, auto);
  align-items: center;
  /* Centradas las dos columnas como un bloque: el texto tiene su tope de
     medida (48ch) y sin esto se quedaba pegado a la izquierda dejando
     media pantalla vacía a la derecha. */
  justify-content: center;
  gap: clamp(34px, 6vw, 100px);
  width: 100%;
  max-width: 1400px;
  margin-inline: auto;
}

/* --- la foto ----------------------------------------------------- */
.sobre__foto {
  position: relative;
  width: var(--so-foto);
  justify-self: center;
  will-change: transform;

  /* Este negro no se ve (es el mismo de la página) pero hace falta: es
     contra lo que mezcla la foto. Va aquí y no solo en .sobre__sticky
     porque el parallax le pone un `transform` por JS y eso ya convierte
     a la figura en el contexto con el que mezcla lo de dentro. */
  background: var(--bg);
}

.sobre__img {
  /* Posicionada para que quede POR ENCIMA del «1998»: si no, al ser el
     número lo único posicionado de la figura, se pintaría sobre la cara. */
  position: relative;
  z-index: 1;
  width: 100%;
  height: auto;

  /* El fondo de la foto es negro PURO (0,0,0), más oscuro que el de la
     página (#0a0a0a): puesta a pelo se notaría como un rectángulo más
     negro. Con `lighten` cada píxel se queda con el más claro de los
     dos, así que ese fondo pasa a ser exactamente el de la página y el
     borde desaparece solo, sin máscaras ni degradados.
     De regalo, el «1998» que va detrás se transparenta a través de él.
     Para que mezcle con el negro y no con el vacío, quien tiene que
     llevar el fondo es .sobre__sticky (crea contexto por el sticky). */
  mix-blend-mode: lighten;

  opacity: 0;
  transform: scale(1.05);
  transition: opacity 1.7s var(--ease), transform 2s var(--ease-out);
}
/* Entra desde negro: como su fondo ya es negro, parece que se revela.
   Esto es solo para el modo sin fijar; con `.is-pin` quien manda es el
   scroll, y el JS le escribe la opacidad fotograma a fotograma (de ahí que
   ahí no pueda haber transición, o iría siempre a remolque). */
.sobre.is-in .sobre__img.is-loaded { opacity: 1; transform: none }
.sobre.is-pin .sobre__img { transition: none }

/* El nombre enorme del fondo, al borde de no verse. Cada línea va en su
   renglón: entero en uno solo sería mucho más ancho que la foto y se
   metería por debajo de la columna de texto. */
.sobre__marca {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  font-family: var(--serif);
  font-size: clamp(5rem, 8.6vw, 11.5rem);
  line-height: .86;
  letter-spacing: -.02em;
  text-align: center;
  color: rgba(242, 239, 232, var(--so-marca));
  z-index: 0;
  white-space: nowrap;
  pointer-events: none;
  user-select: none;
  will-change: transform;
}
.sobre__marca b { display: block; font-weight: 400 }

/* --- el texto ---------------------------------------------------- */
.sobre__texto {
  position: relative;
  max-width: 48ch;
}

.sobre__bloque {
  font-size: clamp(1rem, 1.25vw, 1.22rem);
  font-weight: 300;
  line-height: 1.78;
  color: var(--paper);
}
.sobre__bloque + .sobre__bloque { margin-top: 1.5em }

/* Fijada la sección, los tres textos comparten la misma casilla: el
   segundo párrafo entra donde estaba el primero y el cierre donde
   estaba el segundo. Así en pantalla nunca hay más de un bloque. */
.sobre.is-pin .sobre__texto { display: grid; align-items: center }
.sobre.is-pin .sobre__bloque,
.sobre.is-pin .sobre__cierre {
  grid-area: 1 / 1;
  margin: 0;
}
.sobre.is-pin .sobre__cierre { opacity: 0 }

/* Cada palabra. El JS las envuelve y les va subiendo la luz de una en
   una; esta regla es solo para que no haya un fogonazo de texto
   encendido entre que se pone `.is-pin` y llega el primer fotograma. */
.sobre.is-pin .w { opacity: var(--so-dim) }

/* La frase que cierra la sección (antes iba al final de «Obras»). Va en
   Didot y CLARAMENTE más grande que los párrafos: es lo último que queda en
   pantalla, ella sola. La última línea va aparte, en <em>, y un punto mayor. */
.sobre__cierre {
  margin-top: 1.7em;
  font-family: var(--serif);
  font-style: italic;
  font-size: clamp(1.3rem, 2.1vw, 2rem);
  line-height: 1.3;
  color: var(--paper);
  text-wrap: balance;
}
.sobre__cierre em {
  display: block;
  margin-top: .55em;
  font-size: 1.15em;
  line-height: 1.2;
  color: #fff;
}

/* --- la barra de lectura ----------------------------------------- */
.sobre__bar {
  position: absolute;
  left: var(--pad);
  right: var(--pad);
  bottom: clamp(30px, 5vh, 58px);
  height: 1px;
  background: var(--line-soft);
  opacity: 0;
  transition: opacity 1.2s var(--ease-out) .5s;
}
.sobre.is-in .sobre__bar { opacity: 1 }
.sobre__bar i {
  display: block;
  height: 100%;
  background: var(--paper);
  transform: scaleX(0);
  transform-origin: left;
}

/* --- pantallas estrechas ---------------------------------------- */
/* En columna: la foto arriba, más pequeña, y el texto debajo. */
@media (max-width: 900px) {
  .sobre { --so-foto: clamp(152px, 38vw, 240px) }
  .sobre__grid {
    grid-template-columns: minmax(0, 1fr);
    justify-items: center;
    gap: clamp(22px, 4vh, 44px);
  }
  .sobre__texto { max-width: 40ch }
  .sobre__marca { font-size: clamp(4.2rem, 19vw, 7rem) }
}
@media (max-width: 620px) {
  .sobre__bloque { font-size: 1rem; line-height: 1.7 }
  .sobre__cierre { font-size: 1.18rem }
}

@media (prefers-reduced-motion: reduce) {
  .sobre__head, .sobre__img, .sobre__bar { transition: none !important }
  .sobre__head { opacity: 1; transform: none }
  .sobre__img  { opacity: 1; transform: none }
  .sobre .w    { opacity: 1 !important }
}

/* ---------------------------------------------------------------
   9b. CONTACTO
   La última de la home. Negro, centrado y sobrio: un título en Didot,
   una línea de texto y el botón de WhatsApp.
   El botón NO lleva el verde de WhatsApp: borde e icono en el color del
   papel. Al pasar por encima lo cruza una pincelada blanca que invierte
   las letras (ver .contacto__trazo).
   La entrada la arma contacto.js con `.is-armed` → `.is-in`; sin JS todo
   se ve desde el principio.
   --------------------------------------------------------------- */
.contacto {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  min-height: 100svh;
  padding-block: clamp(74px, 11vh, 112px) clamp(56px, 9vh, 92px);
  padding-inline: var(--pad);
  background: var(--bg);
  text-align: center;
  /* Una línea fina arriba separa de «Sobre mí», que también es negra. */
  border-top: 1px solid var(--line-soft);
}

.contacto__head {
  position: absolute;
  top: clamp(74px, 11vh, 112px);
  left: var(--pad);
}
.contacto__kicker {
  font-size: .62rem;
  letter-spacing: .38em;
  text-transform: uppercase;
  color: var(--muted);
}

.contacto__cuerpo {
  display: flex;
  flex-direction: column;
  align-items: center;
  max-width: 100%;
}

.contacto__titulo {
  font-family: var(--serif);
  font-style: italic;
  font-weight: 400;
  font-size: clamp(2.6rem, 6.4vw, 6rem);
  line-height: 1.05;
  letter-spacing: -.01em;
  color: #fff;
}

.contacto__texto {
  max-width: 44ch;
  margin-top: clamp(18px, 3vh, 30px);
  font-size: clamp(1rem, 1.25vw, 1.22rem);
  line-height: 1.7;
  color: var(--paper);
  text-wrap: balance;
}

.contacto__wa {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: .9em;
  margin-top: clamp(30px, 5.5vh, 56px);
  padding: 1.05em 1.9em;
  /* Opaco a propósito: la pincelada lo invierte igual que a las letras, y
     solo con el color del papel entero queda NEGRO donde pisa la pintura
     (a media transparencia quedaba gris). */
  border: 1px solid var(--paper);
  border-radius: 999px;
  font-size: .7rem;
  letter-spacing: .24em;
  text-transform: uppercase;
  color: var(--paper);
  /* La pincelada se sale del botón a propósito. */
  overflow: visible;

  /* El recorrido de la pincelada: cuánto tarda en pintarse y en irse. */
  --trazo-in: .75s;
  --trazo-out: .45s;
}
.contacto__icono { flex: none; width: 22px; height: 22px }
.contacto__wa:focus-visible { outline: 1px solid var(--paper); outline-offset: 6px }

/* --- la pincelada ----------------------------------------------
   Más grande que el botón por los cuatro lados y un poco girada: así el
   trazo va de abajo a la izquierda hacia arriba a la derecha y cubre las
   esquinas redondeadas con holgura.

   Se descubre con una máscara: un degradado de negro a transparente tres
   veces más ancho que el trazo que se desliza de derecha a izquierda. El
   borde blando del degradado es el frente de la pintura avanzando. Como la
   máscara va DENTRO del giro, el avance sigue la diagonal del trazo.

   `difference` invierte lo que hay debajo: fondo negro → blanco, letras e
   icono blancos → negro. Para que funcione, el botón no puede crear su
   propio contexto de apilado (ni `transform`, ni `opacity` < 1, ni
   `isolation`) una vez ha entrado: si lo hiciera, la mezcla sería contra
   un fondo transparente y la pincelada taparía el texto. */
.contacto__trazo {
  position: absolute;
  left: -14%;
  top: -80%;
  width: 128%;
  height: 270%;
  color: var(--paper);
  mix-blend-mode: difference;
  pointer-events: none;
  overflow: visible;
  transform: rotate(-4deg);

  -webkit-mask-image: linear-gradient(90deg, #000 38%, transparent 58%);
          mask-image: linear-gradient(90deg, #000 38%, transparent 58%);
  -webkit-mask-size: 300% 100%;
          mask-size: 300% 100%;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-position: 100% 0;
          mask-position: 100% 0;
  transition: -webkit-mask-position var(--trazo-out) var(--ease),
                      mask-position var(--trazo-out) var(--ease);
}

.contacto__wa:hover .contacto__trazo,
.contacto__wa:focus-visible .contacto__trazo,
.contacto.is-pintado .contacto__trazo {
  -webkit-mask-position: 0 0;
          mask-position: 0 0;
  transition-duration: var(--trazo-in);
  transition-timing-function: cubic-bezier(.55, .1, .25, 1);
}

@media (prefers-reduced-motion: reduce) {
  .contacto__trazo { transition: none }
}

.contacto__pie {
  position: absolute;
  left: var(--pad);
  right: var(--pad);
  bottom: clamp(24px, 4vh, 44px);
  padding-bottom: env(safe-area-inset-bottom);
  font-size: .6rem;
  letter-spacing: .24em;
  text-transform: uppercase;
  color: var(--muted);
}

/* --- la entrada: escalonada ------------------------------------- */
.contacto.is-armed .contacto__head,
.contacto.is-armed .contacto__titulo,
.contacto.is-armed .contacto__texto,
.contacto.is-armed .contacto__wa {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 1.1s var(--ease-out), transform 1.1s var(--ease-out);
}
.contacto.is-armed .contacto__titulo { transition-delay: .1s }
.contacto.is-armed .contacto__texto  { transition-delay: .3s }
.contacto.is-armed .contacto__wa     { transition-delay: .5s }
.contacto.is-in .contacto__head,
.contacto.is-in .contacto__titulo,
.contacto.is-in .contacto__texto,
.contacto.is-in .contacto__wa { opacity: 1; transform: none }

@media (max-width: 620px) {
  .contacto__texto { font-size: 1rem }
  .contacto__wa { font-size: .64rem; letter-spacing: .18em; padding: 1em 1.5em }
}

/* ---------------------------------------------------------------
   10. PÁGINA DE SERIE (/obra/<slug>)

   Un tríptico: rejilla de TRES columnas de las que solo se llenan DOS
   en cada fila, el título pasando de derecha a izquierda arriba del
   todo y, detrás de todo, el título otra vez en letras huecas, en
   siete líneas que se cruzan (las impares hacia la izquierda, las
   pares hacia la derecha).

   Las tipografías son las mismas que el resto de la web: la sans
   (Helvetica Neue) en versalitas para rótulos y títulos, y el Didot
   solo en los <em> de los topónimos del texto. Las letras huecas NO
   necesitan otra tipografía: se vacían con `-webkit-text-stroke`.
   --------------------------------------------------------------- */
.serie {
  position: relative;
  min-height: 100vh;
  padding-bottom: clamp(70px, 12vh, 140px);
  overflow: hidden;   /* que la marquesina no ensanche la página */

  /* Lo que recorre de lado cada obra al entrar. Subirlo hace la entrada
     más aparatosa; bajarlo, más discreta. */
  --entra: clamp(44px, 7vw, 120px);
}

.serie__inner {
  position: relative;
  z-index: 1;   /* por encima del fondo hueco */
}

/* --- 1. el fondo de letras huecas -------------------------------- */
/* Va fijo a la ventana: las siete líneas ocupan siempre la pantalla
   entera, dure lo que dure la página, y la rejilla sube por encima.
   Cada línea mide 1/7 del alto, así que el número de líneas y el
   tamaño de letra cuadran solos en cualquier pantalla. */
.serie__bg {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  user-select: none;
}

.serie__bgrow {
  height: calc(100% / 7);
  display: flex;
  align-items: center;
  transform: translateX(var(--shift, 0));
}

/* La tira: dos copias idénticas del grupo, una detrás de otra. La
   animación la corre justo la mitad de su ancho, así que al acabar la
   segunda copia cae donde estaba la primera y el bucle es invisible. */
.serie__bgtrack {
  display: flex;
  width: max-content;
  will-change: transform;
  animation: serie-izq var(--dur, 54s) linear infinite;
}
.serie__bgrow--der .serie__bgtrack { animation-name: serie-der }

.serie__bgset { display: flex }

.serie__bg i {
  font-style: normal;
  font-weight: 600;
  font-size: 9.6vh;
  line-height: 1;
  letter-spacing: .01em;
  text-transform: uppercase;
  white-space: nowrap;
  padding-inline-end: .34em;

  /* El hueco: relleno transparente y solo el filo de la letra. */
  color: transparent;
  -webkit-text-stroke: 1px rgba(242, 239, 232, .11);
}

@keyframes serie-izq {
  from { transform: translate3d(0, 0, 0) }
  to   { transform: translate3d(-50%, 0, 0) }
}
@keyframes serie-der {
  from { transform: translate3d(-50%, 0, 0) }
  to   { transform: translate3d(0, 0, 0) }
}

/* Penumbra en la banda de arriba, para que la cabecera se lea siempre.
   Va FIJA y por encima de la rejilla (z-index 2, entre las obras y la
   cabecera): si fuera parte del fondo, una obra clara pasaría por delante
   y se comería el menú. Mismo recurso que la banda de «Obras». */
.serie::before {
  content: "";
  position: fixed;
  left: 0; right: 0; top: 0;
  z-index: 2;
  height: clamp(86px, 13vh, 142px);
  pointer-events: none;
  background: linear-gradient(to bottom,
              rgba(10, 10, 10, .94) 0%,
              rgba(10, 10, 10, .58) 52%,
              rgba(10, 10, 10, 0)  100%);
}

/* --- 2. el título pasando de derecha a izquierda ------------------ */
.serie__mq {
  margin-top: clamp(96px, 16vh, 160px);
  overflow: hidden;
  font-weight: 200;
  line-height: 1.06;
}
.serie__mqtrack {
  display: flex;
  width: max-content;
  will-change: transform;
  animation: serie-izq 58s linear infinite;
}
.serie__mqset { display: flex }

.serie__mq i {
  font-style: normal;
  font-size: clamp(2.4rem, 7.4vw, 6.6rem);
  letter-spacing: .06em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--paper);
}
/* El punto que separa una repetición de la siguiente. */
.serie__mq i::after {
  content: "·";
  margin-inline: .38em;
  color: var(--muted);
}

/* --- la ficha: dato a la izquierda, vuelta atrás a la derecha ----- */
.serie__meta {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 14px clamp(20px, 4vw, 48px);
  margin: clamp(22px, 3.4vh, 38px) var(--pad) 0;
  padding-top: clamp(16px, 2.4vh, 26px);
  border-top: 1px solid var(--line);
}

/* Misma vara que los rótulos del resto de la web: .62rem y .38em. */
.serie__data {
  display: flex;
  flex-wrap: wrap;
  gap: 6px clamp(14px, 2.2vw, 30px);
  font-size: .62rem;
  letter-spacing: .38em;
  text-transform: uppercase;
  color: var(--muted);
}
.serie__data span:not(:last-child)::after {
  content: "—";
  margin-inline-start: clamp(14px, 2.2vw, 30px);
  opacity: .45;
}

.serie__back {
  font-size: .62rem;
  letter-spacing: .38em;
  text-transform: uppercase;
  color: var(--muted);
  transition: color .5s var(--ease-out);
}
.serie__back i {
  font-style: normal;
  display: inline-block;
  margin-inline-end: .7em;
  transition: transform .5s var(--ease-out);
}
.serie__back:hover { color: var(--paper) }
.serie__back:hover i { transform: translateX(-5px) }

/* --- 3. la rejilla ----------------------------------------------- */
/* Tres columnas; cada fila llena solo dos y la que queda vacía va
   rotando. La columna y la fila de cada pieza las pone el PHP en
   --col y --fila (vienen de app/config.php). */
.serie__grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(46px, 9vh, 110px) clamp(20px, 3.2vw, 52px);
  align-items: start;          /* cada pieza al alto de su fila */
  margin: clamp(54px, 9vh, 110px) var(--pad) 0;
}
.serie__grid > * {
  grid-column: var(--col);
  grid-row: var(--fila);
}

/* CÓMO SE ALINEAN LAS DOS OBRAS DE UNA FILA
   -----------------------------------------
   Dos maneras, y la elige obra.js con `data-alineado` en el <main>:

   «arriba» (align-items: start, lo de arriba) las dos obras arrancan a la
   misma altura, apoyadas en la línea de su fila, y es el paralaje el que
   las va juntando por el centro según se baja.

   «centro» (esta regla) las dos nacen ya con sus centros a la misma
   altura —como en la web que sirve de referencia— y el paralaje solo las
   separa y las vuelve a juntar, suave, alrededor de esa alineación.

   Sin JS manda «centro», que es la que se sostiene sola: no depende de
   que nada se mueva. */
.serie__grid { align-items: center }
.serie[data-alineado="arriba"] .serie__grid { align-items: start }

/* Un instante, mientras obra.js toma medidas: la rejilla vuelve a la
   alineación de arriba y las obras sueltan el paralaje. Así las dos obras
   de una fila comparten el mismo `offsetTop` y saber quién va con quién es
   exacto, haya tres columnas, dos o una. No llega a pintarse. */
.serie.is-midiendo .serie__grid { align-items: start }
.serie.is-midiendo .serie__fig,
.serie.is-midiendo .serie__text { transform: none !important }

/* La capa de dentro. El <figure> lo mueve el paralaje en vertical; este
   <div> es el que entra de lado con fundido. Separados, porque una sola
   propiedad `transform` no puede llevar las dos cosas a la vez. */
.serie__cuerpo { display: block }

.serie__img {
  width: 100%;
  height: auto;
  background: #141414;   /* el hueco mientras la imagen cae */
}

/* El pie: el nombre de la pieza y, debajo, año · técnica · medidas.
   Misma vara que el resto de la web: el nombre con la del título de una
   obra (peso 200, .06em, versalita) y la ficha con la de los rótulos. */
.serie__cap {
  margin-top: 16px;
  display: flex;
  flex-direction: column;
  gap: 7px;
}
.serie__obra {
  font-size: .72rem;
  font-weight: 200;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--paper);
}
.serie__ficha {
  font-size: .62rem;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  text-wrap: balance;
}

/* --- el texto de la serie, en el primer hueco --------------------- */
/* El texto cae justo encima del fondo hueco, así que se le pone debajo
   una penumbra que lo despega: se difumina hacia fuera y no se le ve
   ningún borde. */
.serie__text {
  position: relative;
  isolation: isolate;
  align-self: start;           /* a ras de las obras de su fila */

  /* Y a ras de verdad: una línea de texto lleva la mitad del interlineado
     por encima de la letra, así que la caja empieza unos píxeles antes que
     lo que se ve. Se sube esa mitad para que el filo de la primera línea
     case con el borde de arriba de las obras. */
  margin-top: -.34em;

  max-width: 42ch;
  font-size: clamp(.92rem, 1.05vw, 1.05rem);
  font-weight: 300;
  line-height: 1.78;
  color: var(--paper);
}
.serie__text::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset: -3.4em -3em;
  background: radial-gradient(ellipse at center,
              rgba(10, 10, 10, .96) 0%,
              rgba(10, 10, 10, .88) 45%,
              rgba(10, 10, 10, 0)  78%);
}
.serie__text p + p { margin-top: 1.35em }

/* --- el pie ------------------------------------------------------- */
.serie__pie {
  margin: clamp(60px, 11vh, 130px) var(--pad) 0;
  padding-top: clamp(18px, 2.6vh, 28px);
  border-top: 1px solid var(--line);
}

/* --- la entrada de cada obra (la enciende obra.js) ---------------- */
/* Sin JS no hay clase `is-obs` y no se esconde nada: se ve tal cual.

   Las obras no solo se funden: **entran de lado, desde el hueco de su
   fila**. La fila que se apoya a la izquierda (1-1-0) viene de la
   izquierda y la que se apoya a la derecha (0-1-1) viene de la derecha,
   así que las obras llegan desde el vacío que tienen al lado en vez de
   atravesar la página. El lado lo pone el PHP en `data-lado`.

   Un fundido a secas casi no se veía; el desplazamiento lateral sí, y
   sigue siendo blando porque va largo, con desenfoque de salida y con la
   segunda obra de cada fila un pelo retrasada (`--retraso`). */
.serie.is-obs [data-lado] {
  opacity: 0;
  filter: blur(6px);
  transition: opacity 1.4s var(--ease-out) var(--retraso, 0s),
              transform 1.7s var(--ease-out) var(--retraso, 0s),
              filter 1.2s var(--ease-out) var(--retraso, 0s);
}
.serie.is-obs [data-lado="izq"] { transform: translate3d(calc(-1 * var(--entra)), 0, 0) }
.serie.is-obs [data-lado="der"] { transform: translate3d(var(--entra), 0, 0) }

.serie.is-obs [data-lado].is-in { opacity: 1; transform: none; filter: none }

/* --- pantallas estrechas ----------------------------------------- */
/* Dos columnas: se mantiene el aire, pero ya no caben tres. El texto
   pasa a ocupar el ancho entero, arriba del todo. */
@media (max-width: 900px) {
  .serie__grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: clamp(34px, 6vh, 70px) clamp(16px, 3vw, 30px);
  }
  .serie__grid > * { grid-column: auto; grid-row: auto }
  .serie__text {
    grid-column: 1 / -1;
    align-self: auto;
    max-width: 46ch;
    margin-top: 0;               /* en columna no hay nada con lo que alinear */
    margin-bottom: clamp(10px, 3vh, 34px);
  }
  .serie__bg i { font-size: 7.4vh }
}

@media (max-width: 620px) {
  /* Una sola columna: las obras a lo ancho, que es como mejor se ven. */
  .serie__grid { grid-template-columns: minmax(0, 1fr) }
  .serie__mq { margin-top: clamp(84px, 14vh, 120px) }
  .serie__meta { gap: 12px }
  .serie__text { font-size: 1rem; line-height: 1.72 }
}

/* --- reducir movimiento ------------------------------------------ */
/* Se paran las dos marquesinas. El título de arriba se queda quieto y
   pegado a la izquierda, y el fondo deja de cruzarse. */
@media (prefers-reduced-motion: reduce) {
  .serie__bgtrack,
  .serie__mqtrack { animation: none !important; transform: none !important }
  /* Parada, la marquesina ya no es una tira que cruza la pantalla: es un
     título normal, así que se mete en el margen del resto de la página. */
  .serie__mq { padding-inline: var(--pad) }
  .serie.is-obs [data-lado] {
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
    transition: none !important;
  }
  /* Y el paralaje ni se enciende (lo decide obra.js), así que las obras y el
     texto se quedan quietos en la alineación que traiga la rejilla. */
  .serie__fig,
  .serie__text { transform: none !important }
}

/* ---------------------------------------------------------------
   11. PÁGINA DE PROYECTO (/proyecto/<slug>)

   Un tendedero. Las fotos cuelgan de un hilo que se comba, y el hilo
   recorre la página entera: en «La Plaza de las Palomas» el hilo es el
   tiempo, ocho décadas de una plaza; en «Seres Invisibles» será el hilo
   rojo del que Rinat colgó las obras en la sala. El color lo pone el
   PHP en `--hilo`, así que cambiar de proyecto es cambiar una línea.

   NO se parece a las páginas de serie, y es a propósito: allí hay una
   rejilla de tríptico porque una serie es un catálogo; aquí hay tramos,
   porque un proyecto es un relato. Lo único que comparten es la
   tipografía: la sans en versalitas para rótulos y el Didot solo en los
   <em> de los topónimos.

   Dónde se coloca cada foto lo decide proyecto.js, no el CSS: para
   saber por dónde pasa la cuerda hay que saber lo ancha que es cada
   foto, y eso cambia con el alto al que cuelgan. Sin JavaScript queda
   una fila que se parte sola, en orden y con su pie.
   --------------------------------------------------------------- */
.pro {
  position: relative;
  overflow: hidden;   /* que nada de lo que se mece ensanche la página */

  /* El repuesto, por si un proyecto no trae el suyo. Ver más abajo, en
     `.pro__hilofondo`, por qué ese .46 no se puede bajar. */
  --hilo: rgba(242, 239, 232, .46);

  /* La columna de texto: nunca más ancha que esto, lea uno en un móvil
     o en un monitor de 27". */
  --col: 62ch;
}

/* Penumbra en la banda de arriba para que la cabecera se lea siempre,
   igual que en «Obras» y en las páginas de serie. Fija y por encima de
   todo menos de la propia cabecera: si fuera parte del fondo, una foto
   clara pasaría por delante y se comería el menú. */
.pro::before {
  content: "";
  position: fixed;
  left: 0; right: 0; top: 0;
  z-index: 3;
  height: clamp(86px, 13vh, 142px);
  pointer-events: none;
  background: linear-gradient(to bottom,
              rgba(10, 10, 10, .92) 0%,
              rgba(10, 10, 10, .54) 52%,
              rgba(10, 10, 10, 0)  100%);
}

/* --- el rótulo pequeño, que se repite por toda la página ---------- */
.pro__kicker {
  font-size: .72rem;
  font-weight: 400;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--muted);
}
.pro__kicker i { font-style: normal; margin-inline: .5em; opacity: .6 }

/* --- 1. EL RIEL -------------------------------------------------- */
/* El nombre del proyecto, de canto y fijo al borde izquierdo, con una
   barrita que dice cuánto llevas leído. Hace de título permanente sin
   copiar la marquesina de las páginas de serie, que es suya. Se
   esconde en pantallas estrechas: ahí ese margen no existe. */
.pro__riel {
  position: fixed;
  left: 0;
  top: 50%;
  z-index: 2;
  width: var(--pad);
  transform: translateY(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
  pointer-events: none;
}

.pro__rieltxt {
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  font-size: .66rem;
  letter-spacing: .28em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}

.pro__rielbar {
  position: relative;
  width: 1px;
  height: clamp(60px, 14vh, 120px);
  background: var(--line);
}
.pro__rielbar i {
  position: absolute;
  inset: 0;
  background: var(--paper);
  transform-origin: top;
  transform: scaleY(0);
}

@media (max-width: 900px) {
  .pro__riel { display: none }
}

/* --- 2. LA PORTADA ----------------------------------------------- */
.pro__hero {
  position: relative;
  height: 100svh;
  min-height: 520px;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: 0 var(--pad) clamp(34px, 7vh, 70px);
  overflow: hidden;
}

.pro__heroimg {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  /* La portada de «Palomas» es un cuadro casi negro: se aclara un pelo
     para que no se confunda con el fondo de la página. */
  filter: grayscale(.2) contrast(1.04);
}

/* Dos sombras, no una: la de abajo sostiene el título y la de arriba
   despega la cabecera. Concentradas, para no apagar el cuadro entero. */
.pro__heroscrim {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to top, rgba(10, 10, 10, .92) 0%, rgba(10, 10, 10, .5) 34%, rgba(10, 10, 10, 0) 66%),
    linear-gradient(to bottom, rgba(10, 10, 10, .5) 0%, rgba(10, 10, 10, 0) 26%);
}

.pro__heroinfo { position: relative; max-width: min(760px, 62%) }

.pro__titulo {
  margin-top: .34em;
  font-size: clamp(2.3rem, 7vw, 6rem);
  font-weight: 200;
  line-height: 1.02;
  letter-spacing: .02em;
  text-transform: uppercase;
}

.pro__rol {
  margin-top: 1.1em;
  font-size: .82rem;
  font-weight: 300;
  letter-spacing: .04em;
  color: var(--muted);
}

/* El pie del cuadro de portada, abajo a la derecha: es una obra y tiene
   su nombre, aunque aquí esté haciendo de fondo. */
.pro__heropie {
  position: absolute;
  right: var(--pad);
  bottom: clamp(34px, 7vh, 70px);
  max-width: 16ch;
  text-align: right;
  font-size: .72rem;
  letter-spacing: .1em;
  color: var(--muted);
}

@media (max-width: 760px) {
  /* En vertical no caben las dos cosas en la misma línea: el pie del
     cuadro se va debajo del título. */
  .pro__heropie {
    position: static;
    max-width: none;
    text-align: left;
    margin-top: 1.6em;
  }
  .pro__hero { justify-content: flex-end }
}

/* --- 3. EL TEXTO DE PRESENTACIÓN --------------------------------- */
/* Aquí empieza el hilo: baja de la portada y se mete en el texto. */
.pro__intro {
  position: relative;
  padding: 0 var(--pad) clamp(90px, 16vh, 190px);
}

.pro__baja {
  display: block;
  width: 1px;
  height: clamp(70px, 13vh, 150px);
  margin: 0 auto clamp(46px, 8vh, 96px);
  background: linear-gradient(to bottom, transparent, var(--hilo));
}

.pro__texto {
  max-width: var(--col);
  margin: 0 auto;
  font-size: clamp(1rem, 1.28vw, 1.18rem);
  line-height: 1.78;
  color: rgba(242, 239, 232, .9);
}
.pro__texto p + p { margin-top: 1.5em }

/* El primer párrafo va un punto más grande: es el que resume el
   proyecto y es lo único que mucha gente va a leer. */
.pro__texto p:first-child {
  font-size: 1.12em;
  line-height: 1.66;
  color: var(--paper);
}

/* --- 4. LA CABECERA DE CADA TRAMO -------------------------------- */
.pro__tramo {
  position: relative;
  padding: 0 var(--pad) clamp(90px, 15vh, 180px);
}

.pro__cab {
  max-width: var(--col);
  margin: 0 auto clamp(40px, 7vh, 84px);
  text-align: center;
}

.pro__h2 {
  margin-top: .5em;
  font-size: clamp(1.5rem, 3.2vw, 2.5rem);
  font-weight: 200;
  line-height: 1.12;
  letter-spacing: .03em;
  text-transform: uppercase;
}

.pro__txt {
  margin-top: 1.3em;
  font-size: clamp(.95rem, 1.1vw, 1.04rem);
  line-height: 1.75;
  color: var(--muted);
}

/* --- 5. EL TENDEDERO --------------------------------------------- */
/* SIN JavaScript esto es una fila que se parte sola: todas las fotos al
   mismo alto, que es lo que hace que se lea como ropa tendida aunque no
   haya cuerda. Con JavaScript (`.tiene-js`) cada foto se coloca en su
   sitio exacto y aparece el hilo. */
.pro__tendedero {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: flex-start;
  gap: clamp(16px, 2.8vw, 46px);
  --alto: 200px;
}

.pro__pieza {
  position: relative;
  /* Se mece colgada de la pinza, así que el giro es desde arriba y por
     el centro: cualquier otro origen la haría patinar de lado. */
  transform-origin: 50% 0;
  will-change: transform;
}

.pro__pieza img {
  width: 100%;
  height: auto;
  background: rgba(242, 239, 232, .04);
}

/* Sin JS el ancho lo manda el alto, como en la cuerda. */
.pro:not(.tiene-js) .pro__pieza img { height: var(--alto); width: auto }

.pro.tiene-js .pro__tendedero {
  display: block;
  gap: 0;
}
.pro.tiene-js .pro__pieza {
  position: absolute;
  top: 0; left: 0;
}

/* La pinza: se pone a caballo del hilo, mitad por encima y mitad por
   debajo, que es como sujeta una pinza de verdad. */
.pro__pinza {
  position: absolute;
  top: -7px;
  left: 50%;
  width: 7px; height: 15px;
  margin-left: -3.5px;
  border-radius: 1.5px;
  background: var(--paper);
  opacity: .82;
}
.pro__pinza::after {
  content: "";
  position: absolute;
  left: 50%; top: 3px; bottom: 3px;
  width: 1px;
  margin-left: -.5px;
  background: rgba(10, 10, 10, .45);
}

.pro__pieza figcaption {
  margin-top: .9em;
  text-align: center;
  font-size: .66rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--muted);
}

/* El hilo. Lo dibuja proyecto.js: una curva por cuerda, con un remate en
   cada punta. `overflow: visible` porque la comba se sale del alto del
   <svg> cuando la cuerda se descuelga del todo. */
.pro__hilo {
  position: absolute;
  left: 0;
  overflow: visible;
  pointer-events: none;
}
.pro__hilo path {
  fill: none;
  stroke: var(--hilo);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}
.pro__hilo circle { fill: var(--hilo) }

/* --- 6. LO QUE NO SE CUELGA (cuadros y retratos) ------------------ */
.pro__libre {
  position: relative;
  max-width: 1280px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: clamp(58px, 9vh, 112px);
}

/* El hilo sigue bajando por detrás, para que el tendedero no se pierda
   entre un tramo y el siguiente. Se sale por arriba y por abajo: con el
   paralaje se mueve, y si acabara justo en el borde se vería el corte. */
.pro__hilofondo {
  position: absolute;
  left: 50%;
  top: -160px; bottom: -160px;
  width: 1px;
  /* Se desvanece por las dos puntas: el hilo entra y sale del tramo en vez
     de cortarse en seco, y así tampoco atraviesa el título de arriba. */
  background: linear-gradient(to bottom,
              transparent 0,
              var(--hilo) 210px,
              var(--hilo) calc(100% - 210px),
              transparent 100%);
  /* CUIDADO CON LA TRANSPARENCIA DEL HILO. Una raya de UN píxel sobre un
     fondo casi negro se ve o no se ve por los pelos, y hay dos maneras de
     apagarla sin darse cuenta:

     1. Poniéndole un `opacity` encima. El color ya trae su transparencia
        desde `--hilo`; un .55 por fuera lo dejaba en .21 real y la raya
        desaparecía en muchas pantallas. Por eso aquí NO hay `opacity`.
     2. Eligiendo un color oscuro. Pasó al montar las seis páginas nuevas:
        el rojo de «Terra Condere» y el de «Seres Invisibles» se veían en
        un monitor y en otro no, porque pintados sobre el fondo daban la
        MITAD de contraste que el blanco de «Palomas», que ya iba justo.

     La regla, entonces: el `'hilo'` de cada proyecto puede ser del color
     que quiera, pero pintado sobre `--bg` tiene que quedar al menos tan
     claro como el de «Palomas». Si el color del proyecto es oscuro, se
     sube el tono y se sube el alfa hasta que llegue: un hilo rojo se ve
     rojo igual siendo claro, y si no se ve no es un hilo. */
  pointer-events: none;
  --p: .5;
}

.pro__grande {
  position: relative;
  /* Ni a media página ni a pantalla completa: seis retratos verticales a
     todo lo ancho serían seis pantallas de scroll seguidas. */
  width: min(42%, 540px);
  will-change: transform;
}
.pro__grande[data-lado="der"] { align-self: flex-end }
/* Los panorámicos (el díptico de «Seres Invisibles») a 540 px serían una
   tira de dos dedos: van centrados y casi a todo lo ancho. */
.pro__grande--ancha,
.pro__grande--ancha[data-lado="der"] { width: min(100%, 1100px); align-self: center }

.pro__grande img { width: 100%; height: auto }

.pro__grande figcaption {
  margin-top: 1.1em;
  display: flex;
  flex-direction: column;
  gap: .3em;
}
.pro__obra {
  font-size: .78rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--paper);
}
.pro__credito { font-size: .7rem; letter-spacing: .06em; color: var(--muted) }

@media (max-width: 760px) {
  .pro__grande { width: 100% }
  .pro__grande[data-lado="der"] { align-self: auto }
  .pro__hilofondo { left: 12px }
}

/* --- 7. LA FICHA -------------------------------------------------- */
/* Los créditos enteros y legibles. En un proyecto que hicieron cien
   personas, la lista de nombres no es letra pequeña: es parte de la
   obra. Por eso va en columnas anchas y no escondida tras un
   desplegable. */
.pro__ficha {
  padding: clamp(60px, 10vh, 120px) var(--pad) clamp(70px, 12vh, 140px);
  border-top: 1px solid var(--line-soft);
}
.pro__ficha .pro__h2 {
  max-width: 1280px;
  margin: 0 auto clamp(36px, 6vh, 70px);
  text-align: center;
}

/* Rótulo a la izquierda y nombres a la derecha, en línea y separados por
   un punto. NO en columnas: hay bloques de un nombre y bloques de
   cuarenta, y el reparto en columnas dejaba media pantalla en blanco
   debajo de los cortos. Así cada bloque ocupa exactamente lo que
   necesita, y un listado largo se lee como lo que es: una lista de
   gente, no una tabla. */
.pro__creditos {
  max-width: 1180px;
  margin: 0 auto;
}

.pro__credito-bloque {
  display: grid;
  grid-template-columns: minmax(150px, 21%) 1fr;
  gap: 0 clamp(20px, 3vw, 48px);
  padding: 1.15em 0;
  border-top: 1px solid var(--line-soft);
}
.pro__credito-bloque:last-child { border-bottom: 1px solid var(--line-soft) }

.pro__creditos dt {
  font-size: .64rem;
  line-height: 1.9;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--muted);
}
.pro__creditos dd { margin: 0 }
.pro__creditos dd span {
  font-size: .92rem;
  line-height: 1.72;
  color: rgba(242, 239, 232, .84);
}
/* El punto que separa un nombre del siguiente. Va en el hueco entre los
   dos, así que el último no lleva. */
.pro__creditos dd span + span::before {
  content: "·";
  margin-inline: .62em;
  color: var(--muted);
  opacity: .7;
}

@media (max-width: 620px) {
  /* En vertical el rótulo se pone encima: a dos columnas, con 150 px para
     el rótulo, los nombres se quedarían en una tira de nada. */
  .pro__credito-bloque { grid-template-columns: 1fr; gap: .5em }
}

/* --- 8. LA SALIDA -------------------------------------------------- */
.pro__pie {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 28px;
  padding: clamp(50px, 8vh, 90px) var(--pad) clamp(70px, 12vh, 130px);
  border-top: 1px solid var(--line-soft);
}

.pro__volver {
  font-size: .74rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--muted);
  transition: color .3s var(--ease);
}
.pro__volver i { font-style: normal; margin-inline-end: .7em }
.pro__volver:hover { color: var(--paper) }

.pro__siguiente {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: .6em;
  text-align: right;
}
.pro__sigtit {
  font-size: clamp(1.1rem, 2.4vw, 1.9rem);
  font-weight: 200;
  letter-spacing: .03em;
  text-transform: uppercase;
  transition: opacity .3s var(--ease);
}
.pro__sigtit i {
  font-style: normal;
  display: inline-block;
  margin-inline-start: .3em;
  transition: transform .4s var(--ease-out);
}
.pro__siguiente:hover .pro__sigtit i { transform: translateX(10px) }

@media (max-width: 620px) {
  .pro__pie { flex-direction: column; align-items: flex-start }
  .pro__siguiente { align-items: flex-start; text-align: left }
}

/* --- 9. LA ENTRADA ------------------------------------------------- */
/* Un fundido corto al asomar. Lo enciende proyecto.js poniendo `is-obs`,
   así que si el JavaScript no llega nunca, nada se queda escondido. */
.pro.is-obs .pro__pieza,
.pro.is-obs .pro__grande,
.pro.is-obs .pro__cab,
.pro.is-obs .pro__texto,
.pro.is-obs .pro__credito-bloque {
  opacity: 0;
  transition: opacity .8s var(--ease-out);
}
/* Las piezas colgadas NO pueden entrar con `transform`: ese hueco lo usa
   proyecto.js para la comba y el balanceo, y se pisarían. Entran solo
   con el fundido. */
.pro.is-obs .pro__grande,
.pro.is-obs .pro__cab,
.pro.is-obs .pro__texto,
.pro.is-obs .pro__credito-bloque {
  transform: translateY(26px);
  transition: opacity .8s var(--ease-out), transform .9s var(--ease-out);
}
.pro.is-obs .is-in { opacity: 1 !important; transform: none }

/* Y las sueltas, en cuanto están dentro, las manda el paralaje: se les
   quita la transición o cada fotograma pelearía con ella. */
.pro.is-obs .pro__grande.is-in { transition: opacity .8s var(--ease-out) }

/* --- 10. SIN MOVIMIENTO -------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  /* La cuerda se monta igual —es el diseño, no un adorno— pero se queda
     quieta: proyecto.js pinta un solo fotograma y no vuelve. Lo que sí
     se quita es todo lo que se mueva solo. */
  .pro.is-obs .pro__pieza,
  .pro.is-obs .pro__grande,
  .pro.is-obs .pro__cab,
  .pro.is-obs .pro__texto,
  .pro.is-obs .pro__credito-bloque {
    opacity: 1 !important;
    transition: none !important;
  }
  .pro.is-obs .pro__grande,
  .pro.is-obs .pro__cab,
  .pro.is-obs .pro__texto,
  .pro.is-obs .pro__credito-bloque { transform: none !important }
  .pro__sigtit i { transition: none }
}
