/* ══════════════════════════════════════════════════════════════
   FOTOFEST COLOMBIA 2026 — Galería virtual 3D
   Capa DOM sobre el render. Construida sobre el sistema del sitio.
   Al integrar en fotofestcolombia.com se puede borrar el bloque
   :root de abajo: los tokens ya existen en el styles.css principal.
   ══════════════════════════════════════════════════════════════ */

:root {
  --ink:     #090909;
  --ink2:    #0f0f0f;
  --panel:   #151515;
  --border:  #252525;
  --mid:     #444;
  --muted:   #787878;
  --body:    #b8b8b8;
  --hi:      #dedede;
  --white:   #fff;
  --off:     #f0ede6;
  --teal:    #1a4a3a;
  --teal-m:  #2a6650;
  --teal-lt: #3db88a;
  --pad:     clamp(1.2rem, 5vw, 5rem);
  /* Sistema de capas: HEADER · CONTENIDO · INTERACCIÓN.
     Ningún elemento se posiciona sin respetar estas dos medidas. */
  --header-h: clamp(58px, 8vh, 82px);
  --gut:      clamp(1.2rem, 3.2vw, 3rem);
  --z-scene: 0; --z-content: 10; --z-hud: 20; --z-header: 30;
  --z-panel: 42; --z-lightbox: 50; --z-loader: 60;
  --fd: 'Bebas Neue', Impact, sans-serif;
  --fs: 'Instrument Serif', Georgia, serif;
  --fb: 'DM Sans', system-ui, sans-serif;
}

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

/* ── EL ATRIBUTO hidden GANA SIEMPRE ─────────────────────────
   Parece de más y no lo es. El atributo hidden oculta porque la
   hoja DEL NAVEGADOR le pone display:none, y esa hoja tiene la
   prioridad más baja de todas: en cuanto una regla nuestra le da
   un display, el hidden deja de hacer nada.

   Esta hoja lo venía parcheando caso por caso —.mast-btn[hidden],
   #room-hud[hidden], #lightbox[hidden]…— y se le escapaba alguno.
   El que se veía: #logo-img tiene display:block, así que cuando el
   archivo del logo no carga y applySite lo oculta con hidden, el
   icono de imagen rota se quedaba encima del logotipo tipográfico.

   Una regla al principio cubre todos los casos, incluidos los que
   se añadan después. */
[hidden] { display: none !important; }

html, body { background: var(--ink); color: var(--white); }
html { -webkit-text-size-adjust: 100%; }
body {
  font-family: var(--fb);
  font-weight: 300;
  font-size: 16px;
  line-height: 1.5;
  overflow-x: hidden;
  overscroll-behavior-y: none;
  -webkit-font-smoothing: antialiased;
}
body.has-cursor { cursor: none; }

button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
body.has-cursor button { cursor: none; }
:focus-visible { outline: 1px solid var(--teal-lt); outline-offset: 4px; }

/* ── CURSOR ─────────────────────────────────────────────────── */
#cur { position: fixed; pointer-events: none; z-index: 10000; top: 0; left: 0; }
#cring {
  position: absolute; width: 34px; height: 34px;
  border: 1px solid rgba(255,255,255,.25); border-radius: 50%;
  transition: width .5s cubic-bezier(.23,1,.32,1), height .5s, border-color .3s;
}
#cdot { position: absolute; width: 6px; height: 6px; background: #fff; border-radius: 50%;
  transition: width .2s, height .2s, opacity .2s; }
body.hov #cdot  { width: 0; height: 0; opacity: 0; }
body.hov #cring { width: 50px; height: 50px; border-color: var(--teal-lt); }

/* ── LIENZO ─────────────────────────────────────────────────── */
#gl { position: fixed; inset: 0; width: 100vw; height: 100vh; height: 100dvh; display: block; z-index: 0; }
#scroll-track { position: relative; width: 100%; height: 700vh; pointer-events: none; }

#grain {
  position: fixed; inset: -60px; z-index: 6; pointer-events: none;
  opacity: .14; mix-blend-mode: overlay;
  background-image: var(--grain-src); background-size: 180px 180px;
  animation: grainShift 1.1s steps(3) infinite;
}
@keyframes grainShift {
  0%{transform:translate(0,0)} 33%{transform:translate(-14px,9px)}
  66%{transform:translate(11px,-7px)} 100%{transform:translate(0,0)}
}

#curtain { position: fixed; inset: 0; z-index: 40; background: var(--ink); opacity: 0; pointer-events: none; }

/* ── CARGADOR ───────────────────────────────────────────────── */
#loader {
  position: fixed; inset: 0; z-index: 60; background: var(--ink);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 18px;
}
.loader-bar { display: block; width: min(200px, 40vw); height: 1px; background: var(--border); overflow: hidden; }
.loader-bar i { display: block; height: 100%; width: 0; background: var(--teal-lt); transition: width .4s ease; }
.is-error .loader-text { max-width: 34rem; text-align: center; line-height: 1.7; color: var(--body); letter-spacing: .06em; text-transform: none; }
.loader-text { font-size: .55rem; letter-spacing: .28em; text-transform: uppercase; color: var(--muted); }

/* ── MICRO-ETIQUETAS ──────────────────────────────────────────
   OJO: esta regla la comparten DIEZ selectores en dos líneas. El
   9 de octubre una limpieza automática borró la segunda línea
   porque dos de sus nombres —.mast-name y .mast-edition— no
   aparecían en el HTML. Pero no eran reglas suyas: eran dos
   nombres dentro de una lista compartida. Al llevarse esa línea
   se llevó también el bloque, la primera línea quedó colgando con
   su coma, y el navegador la unió con la regla siguiente: el
   contador de la sala y las cartelas del visor pasaron a pintarse
   como botones blancos.

   Quitar un nombre de una lista compartida no ahorra nada y
   rompe a los demás. Esta regla se toca a mano o no se toca. */
.meta-item, .intro-eyebrow, .room-count, .lb-index, .lb-tech,
.mast-name, .mast-edition, .station-origin, #room-hint, .river-tag {
  font-size: .58rem; letter-spacing: .22em; text-transform: uppercase;
}

/* ── BOTONES ────────────────────────────────────────────────── */
.btn-primary {
  display: inline-block; padding: .88rem 2.2rem;
  background: var(--white); color: var(--ink);
  font-size: .62rem; font-weight: 500; letter-spacing: .16em; text-transform: uppercase;
  transition: background .25s, color .25s;
}
.btn-primary:hover { background: var(--teal-lt); color: var(--white); }
.btn-outline {
  display: inline-block; padding: .72rem 1.6rem;
  border: 1px solid rgba(255,255,255,.3); color: var(--white);
  font-size: .58rem; letter-spacing: .18em; text-transform: uppercase;
  transition: border-color .25s, background .25s, color .25s;
}
.btn-outline:hover { border-color: var(--teal-lt); color: var(--teal-lt); background: rgba(61,184,138,.06); }

/* ── INTRO + TEXTO CURATORIAL ───────────────────────────────── */
#intro {
  position: fixed; inset: 0; z-index: var(--z-content);
  display: grid; align-content: end;
  padding: calc(var(--header-h) + var(--gut)) var(--gut) clamp(1.6rem, 5vh, 3.4rem);
  background: linear-gradient(to top, rgba(6,11,16,.95) 0%, rgba(6,11,16,.62) 52%, rgba(6,11,16,0) 100%);
  overflow: hidden;
}
.intro-grid {
  display: grid; gap: clamp(1.6rem, 4vw, 4rem);
  grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr);
  align-items: end; width: 100%; max-width: 96rem; margin: 0 auto;
}
.intro-titles { min-width: 0; }
.intro-eyebrow {
  display: inline-flex; align-items: center; gap: .9rem;
  color: var(--teal-lt); margin-bottom: 1.4rem;
}
.intro-eyebrow::before { content: ''; width: 24px; height: 1px; background: var(--teal-lt); }
.intro-title { font-family: var(--fd); font-weight: 400; line-height: .87; letter-spacing: .01em; }
.intro-title .line-a,
.intro-title .line-b { display: block; font-size: clamp(3.4rem, 8.5vw, 9rem); }
.intro-title .line-b { color: rgba(255,255,255,.95); }
.intro-title .line-sub {
  display: block; margin-top: .5rem;
  font-family: var(--fs); font-style: italic; font-weight: 400; letter-spacing: 0;
  font-size: clamp(1.2rem, 2.4vw, 2.2rem); line-height: 1.15; color: #37a16c;
}
.intro-quote {
  margin-top: 1.5rem; max-width: 32ch;
  font-family: var(--fs); font-size: clamp(1rem, 1.4vw, 1.3rem); line-height: 1.55;
  color: rgba(255,255,255,.62);
}
.intro-meta { display: flex; align-items: center; gap: 1rem; margin-top: 1.4rem; flex-wrap: wrap; }
.meta-item { color: rgba(255,255,255,.55); }
.meta-sep { width: 1px; height: 14px; background: rgba(255,255,255,.18); }

/* Bloque editorial: ancho de lectura limitado y desplazamiento propio
   si el texto curatorial crece. Nunca rompe la composición. */
.intro-curatorial { min-width: 0; max-width: 34rem; padding-bottom: .3rem; }
.curatorial-title {
  font-family: var(--fd); font-weight: 400; font-size: clamp(1.1rem, 1.8vw, 1.5rem);
  letter-spacing: .08em; color: rgba(255,255,255,.75);
  padding-bottom: .8rem; margin-bottom: 1.1rem;
  border-bottom: 1px solid rgba(255,255,255,.16);
}
.curatorial-body {
  max-height: min(34svh, 22rem); overflow-y: auto; overscroll-behavior: contain;
  padding-right: 1rem; mask-image: linear-gradient(to bottom, #000 84%, transparent 100%);
  -webkit-mask-image: linear-gradient(to bottom, #000 84%, transparent 100%);
}
.curatorial-body p {
  font-size: clamp(.82rem, 1.05vw, .92rem); line-height: 1.75; color: var(--body);
  max-width: 62ch;
}
.curatorial-body p + p { margin-top: .9rem; }
.curatorial-body::-webkit-scrollbar { width: 2px; }
.curatorial-body::-webkit-scrollbar-thumb { background: rgba(255,255,255,.2); }
.curatorial-actions {
  display: flex; align-items: center; gap: 1.4rem; flex-wrap: wrap;
  margin-top: 1.6rem;
}
.intro-hint { display: inline-flex; align-items: center; gap: .8rem; }
.intro-hint span { font-size: .5rem; letter-spacing: .26em; text-transform: uppercase; color: rgba(255,255,255,.3); }
.scroll-line { display: block; width: 34px; height: 1px; background: rgba(255,255,255,.1); position: relative; overflow: hidden; }
.scroll-line::after {
  content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
  background: var(--teal-lt); animation: scrollAnim 2.2s ease-in-out infinite;
}
@keyframes scrollAnim { 0%{left:-100%} 50%{left:0} 100%{left:100%} }

/* ── CABECERA ───────────────────────────────────────────────── */
#masthead {
  position: fixed; z-index: var(--z-header); top: 0; left: 0; right: 0;
  height: var(--header-h);
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; padding: 0 var(--gut);
  pointer-events: none;
}
#masthead > * { pointer-events: auto; }
.mast-left { display: flex; align-items: center; gap: .8rem; text-decoration: none; min-width: 0; }
#logo-img { height: clamp(22px, 3.4vh, 32px); width: auto; display: block; }
#logo-word { display: flex; align-items: baseline; gap: .7rem; min-width: 0; }
#logo-word b {
  font-family: var(--fd); font-weight: 400; font-size: clamp(.95rem, 2vw, 1.3rem);
  letter-spacing: .06em; color: var(--white);
}
#logo-word i {
  font-family: var(--fs); font-style: italic; font-size: .95rem; color: var(--teal-lt);
  white-space: nowrap;
}
.mast-right { display: flex; align-items: center; gap: .4rem; }
.mast-btn {
  padding: .55rem .9rem; border: 1px solid transparent;
  font-size: .56rem; letter-spacing: .2em; text-transform: uppercase;
  color: rgba(255,255,255,.62); white-space: nowrap;
  transition: color .25s, border-color .25s;
}
.mast-btn:hover { color: var(--white); border-color: rgba(255,255,255,.22); }
.mast-btn[hidden] { display: none; }

/* ── LA GUÍA ES UN RÍO ──────────────────────────────────────── */
/* La caja es ANCHA aunque el cauce mida 64 px: los nombres de los
   artistas cuelgan a su izquierda y necesitan ese sitio. El <svg>
   va pegado a la derecha. */
/* La guía sale del todo dentro de las salas: ver UI.showRiver.
   visibility (y no sólo opacity) la quita también del tabulador y
   de los lectores de pantalla. El retardo de 0.45 s en visibility
   deja que el desvanecido termine antes de desaparecer; al volver,
   sin retardo, para que reaparezca a la vez que el cauce. */
/* La guía tiene TRES estados, y hay que decirlos los tres:

     sin clase    apagada  — la portada y la pedagogía, antes de
                             que empiece el recorrido
     .is-live     visible  — navegando el cauce
     .is-hidden   fuera    — dentro de una sala

   Antes sólo existía el primero: la regla base dejaba #river en
   opacity 0 y lo único que lo encendía era un style="opacity:1"
   escrito desde JavaScript. Al pasar a clases y limpiar ese estilo
   en línea, la guía ya no volvía al salir de una sala, porque caía
   en el 0 del CSS. De ahí que el encendido también sea una clase:
   los dos sentidos explícitos, ninguno dependiendo de un resto.

   .is-hidden va después y gana: dentro de una sala manda ocultar
   aunque se esté navegando. visibility, además de opacity, la saca
   del tabulador y de los lectores de pantalla; su retardo de
   0.45 s deja que el desvanecido termine antes de desaparecer. */
#river { transition: opacity .45s ease, visibility 0s linear 0s; }
#river.is-live { opacity: 1; pointer-events: auto; }
#river.is-hidden {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity .45s ease, visibility 0s linear .45s;
}

#river {
  position: fixed; z-index: var(--z-hud);
  right: calc(var(--gut) * .5);
  top: calc(var(--header-h) + var(--gut));
  bottom: calc(var(--gut) * 2);
  width: 272px; opacity: 0; pointer-events: none;
}
#river .river-svg {
  position: absolute; right: 0; top: 0;
  width: 64px; height: 100%;
}
.river-svg { width: 100%; height: 100%; overflow: visible; display: block; }
.river-bed  { fill: none; stroke: rgba(255,255,255,.10); stroke-width: 1; vector-effect: non-scaling-stroke; }
.river-flow {
  fill: none; stroke: var(--teal-lt); stroke-width: 1.5; stroke-linecap: round;
  vector-effect: non-scaling-stroke; filter: drop-shadow(0 0 4px rgba(61,184,138,.45));
}
.river-node {
  fill: var(--ink); stroke: rgba(255,255,255,.30); stroke-width: 1;
  vector-effect: non-scaling-stroke; transition: fill .35s, stroke .35s;
}
.river-node.is-active { fill: var(--teal-lt); stroke: var(--teal-lt); }
.river-hit { fill: transparent; pointer-events: auto; }
.river-glow { fill: var(--teal-lt); opacity: .16; }
.river-mark { fill: var(--white); }
/* ── LOS NOMBRES DEL CAUCE ───────────────────────────────────
   El PUNTO sigue el meandro; el TEXTO va en columna recta, con el
   borde derecho siempre en la misma x. Cuando el texto acompañaba
   al punto por la curva, su borde bailaba de fila en fila y los
   dieciséis nombres se leían como una escalera rota, aunque el
   orden fuese correcto.

   Y van siempre visibles: en opacidad 0 hasta el hover, la guía
   era una columna de puntos sin identificar. */
.river-tag {
  position: absolute;
  right: 72px;                 /* despejado del punto más a la izquierda */
  transform: translateY(-50%);
  display: flex; align-items: baseline; justify-content: flex-end; gap: .45rem;
  white-space: nowrap; pointer-events: none;
  color: rgba(255,255,255,.58);
  transition: color .3s;
}
.river-tag i {
  font-family: var(--fd); font-style: normal;
  font-size: .72rem; letter-spacing: .06em;
  color: var(--mid);
  transition: color .3s;
}
.river-tag b {
  font-weight: 400;
  /* 168 px era un tope puesto a ojo, y cortaba cuatro de los
     dieciséis nombres —«Neil Ebeth Meled Morales Rodríguez» pide
     260— teniendo 1209 px libres a su izquierda. Ahora el tope
     crece con la pantalla y los dieciséis caben enteros desde
     820 px en adelante. Los puntos suspensivos se quedan como red
     de seguridad para un nombre aún más largo, no como norma. */
  max-width: clamp(168px, 30vw, 300px);
  overflow: hidden; text-overflow: ellipsis;
}
/* Por donde ya se pasó. */
.river-tag.is-past { color: rgba(255,255,255,.3); }
/* Dónde estás ahora. */
.river-tag.is-active { color: var(--white); }
.river-tag.is-active i { color: var(--teal-lt); }
/* Al pasar el cursor, el nombre completo, por si se cortó. */
.river-tag.is-shown {
  color: var(--white); z-index: 2;
}
.river-tag.is-shown b {
  max-width: none; overflow: visible;
  background: rgba(9,9,9,.94); box-shadow: 0 0 0 5px rgba(9,9,9,.94);
}
.river-tag.is-shown i { color: var(--teal-lt); }

/* ── ESTACIONES DE ARTISTA ──────────────────────────────────── */
#stations { position: fixed; inset: 0; z-index: 10; pointer-events: none; }
.station { position: absolute; top: 0; left: 0; text-align: center; width: min(340px, 70vw); transform-origin: 0 0; opacity: 0; pointer-events: none; }
.station.is-near { pointer-events: auto; }
.station-rule { width: 100%; height: 1px; background: rgba(255,255,255,.16); margin-bottom: 1rem; }
.station-name {
  font-family: var(--fd); font-weight: 400; line-height: .95;
  font-size: clamp(1.7rem, 2.8vw, 2.9rem); letter-spacing: .015em;
}
.station-origin { margin-top: .5rem; color: var(--muted); }
.station-project {
  margin-top: .7rem; font-family: var(--fs); font-style: italic;
  font-size: clamp(1rem, 1.5vw, 1.35rem); color: var(--teal-lt);
}
.station-enter { margin-top: 1.2rem; }
.station-rule { margin-left: auto; margin-right: auto; }

/* ── HUD DE SALA ────────────────────────────────────────────── */
#room-hud { position: fixed; inset: 0; z-index: var(--z-hud); pointer-events: none; }
#room-hud[hidden] { display: none; }
/* Un solo anclaje inferior izquierdo: la ficha crece desde la identidad
   del artista, no aparece como un módulo suelto en otro lugar. */
#room-stack {
  position: absolute; left: var(--gut); bottom: var(--gut);
  width: min(34rem, 42vw);
}
#room-meta { transition: opacity .4s, transform .4s; }
#room-meta.is-tucked { opacity: 0; transform: translateY(8px); pointer-events: none; }
.room-artist { font-family: var(--fd); font-weight: 400; font-size: clamp(1.6rem, 2.6vw, 2.7rem); line-height: .95; }
.room-project { margin-top: .4rem; font-family: var(--fs); font-style: italic; font-size: 1.2rem; color: var(--teal-lt); }
.room-count { margin-top: .7rem; color: var(--muted); }
#room-hint {
  position: absolute; right: var(--gut); bottom: var(--gut);
  color: rgba(255,255,255,.28); transition: opacity .6s;
}

/* Ficha editorial del proyecto — centrada en pantalla.
   El contenedor ocupa toda la ventana pero no captura el puntero: la
   sala se sigue pudiendo mirar y arrastrar con la ficha abierta. Sólo
   la tarjeta es interactiva. */
#artist-panel {
  position: fixed; inset: 0; z-index: var(--z-hud);
  display: grid; place-items: center;
  padding: calc(var(--header-h) + 1rem) var(--gut) calc(var(--gut) + 3rem);
  pointer-events: none;
  background: radial-gradient(ellipse at center, rgba(6,9,12,.62) 0%, rgba(6,9,12,.18) 62%, rgba(6,9,12,0) 100%);
}
#artist-panel[hidden] { display: none; }
.ap-card {
  pointer-events: auto; position: relative;
  width: min(38rem, 100%);
  max-height: min(72svh, calc(100svh - var(--header-h) - 6rem));
  padding: 2rem 2.2rem 1.8rem;
  background: rgba(9,9,9,.88); border: 1px solid var(--border);
  backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
  overflow-y: auto; overscroll-behavior: contain;
  text-align: center;
}
.ap-label { font-size: .52rem; letter-spacing: .26em; text-transform: uppercase; color: var(--teal-lt); }
.ap-name {
  margin-top: .7rem; font-family: var(--fd); font-weight: 400;
  font-size: clamp(1.7rem, 3.2vw, 2.8rem); line-height: .98;
}
.ap-project { margin-top: .35rem; font-family: var(--fs); font-style: italic; font-size: 1.2rem; color: var(--teal-lt); }
.ap-text { margin-top: 1.3rem; }
.ap-text p { font-size: .86rem; line-height: 1.78; color: var(--body); max-width: 54ch; margin-inline: auto; text-align: left; }
.ap-text p + p { margin-top: .85rem; }
.ap-meta {
  margin-top: 1.4rem; padding-top: 1rem; border-top: 1px solid var(--border);
  font-size: .56rem; letter-spacing: .22em; text-transform: uppercase; color: var(--muted);
}
.panel-close {
  position: absolute; top: .5rem; right: .6rem;
  width: 34px; height: 34px; font-size: 1.15rem; line-height: 1;
  color: var(--muted); transition: color .25s;
}
.panel-close:hover { color: var(--white); }

/* ── ETIQUETA SOBRE LA OBRA ─────────────────────────────────── */
#hover-tag {
  position: fixed; z-index: 14; pointer-events: none;
  display: flex; align-items: baseline; gap: .7rem;
  padding: .45rem .85rem; background: rgba(9,9,9,.72);
  border: 1px solid var(--border); backdrop-filter: blur(8px);
  white-space: nowrap; transform: translate(22px, -50%);
}
#hover-tag[hidden] { display: none; }
#hover-tag b { font-family: var(--fd); font-weight: 400; font-size: .95rem; letter-spacing: .06em; color: var(--teal-lt); }
#hover-tag em { font-family: var(--fs); font-style: italic; font-size: .95rem; color: rgba(255,255,255,.82); }

/* ── LIGHTBOX ───────────────────────────────────────────────── */
#lightbox {
  position: fixed; inset: 0; z-index: 50; background: rgba(6,6,6,.97);
  display: grid; place-items: center;
  padding: clamp(1.4rem, 5vh, 4rem) clamp(1.4rem, 8vw, 7rem); opacity: 0;
}
#lightbox[hidden] { display: none; }
#lb-figure {
  display: grid; gap: clamp(1.2rem, 3vw, 3rem);
  grid-template-columns: minmax(0, 1fr) minmax(220px, 300px);
  align-items: center; width: 100%; max-width: 1400px;
}
#lb-media { display: grid; place-items: center; min-width: 0; }
#lb-media img, #lb-media canvas {
  display: block; max-width: 100%; max-height: 78svh; width: auto; height: auto;
  box-shadow: 0 30px 90px rgba(0,0,0,.7);
}
#lb-caption { align-self: end; }
.lb-index { color: var(--teal-lt); margin-bottom: 1rem; }
.lb-title { font-family: var(--fs); font-weight: 400; font-size: clamp(1.4rem, 2vw, 2rem); line-height: 1.2; }
.lb-tech { margin-top: .7rem; color: var(--muted); }
.lb-desc { margin-top: 1.1rem; font-size: .88rem; line-height: 1.7; color: var(--body); max-width: 42ch; }
.lb-artist {
  margin-top: 1.4rem; padding-top: 1rem; border-top: 1px solid var(--border);
  font-family: var(--fd); font-size: 1.05rem; letter-spacing: .06em;
}
.lb-close {
  position: absolute; top: 1.4rem; right: var(--pad);
  font-size: .58rem; letter-spacing: .22em; text-transform: uppercase; color: var(--muted);
  transition: color .25s;
}
.lb-close:hover { color: var(--white); }
.lb-arrow {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 54px; height: 54px; font-size: 1.2rem; color: rgba(255,255,255,.4); transition: color .25s;
}
.lb-arrow:hover { color: var(--teal-lt); }
.lb-prev { left: clamp(.3rem, 2vw, 1.6rem); }
.lb-next { right: clamp(.3rem, 2vw, 1.6rem); }

/* ── RESPALDO ───────────────────────────────────────────────── */
#fallback { position: relative; z-index: 30; padding: var(--pad); background: var(--ink); }
#fallback[hidden] { display: none; }
.fallback-artist { margin-bottom: 4rem; }
.fallback-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 12px; margin-top: 1.4rem; }
.fallback-grid img, .fallback-grid canvas { width: 100%; height: auto; display: block; }
.fallback-note { padding: var(--pad); font-family: var(--fs); color: var(--body); }

/* ── PEDAGOGÍA DE ENTRADA ───────────────────────────────────
   Se abre sobre la portada. El recorrido no arranca hasta que se
   pulsa «Interactuar». */
#onboarding {
  position: fixed; inset: 0; z-index: var(--z-panel);
  display: grid; place-items: center;
  padding: calc(var(--header-h) + 1rem) var(--gut) var(--gut);
  background: rgba(6,9,12,.86);
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
}
#onboarding[hidden] { display: none; }
/* El panel no se desplaza: se desplaza su cuerpo. El botón queda
   fuera de esa caja, siempre a la vista y sin que el texto le pase
   por detrás. El tope va en svh porque «100vh» en el móvil mide la
   ventana sin la barra del navegador, y por eso el final del panel
   —el botón— quedaba por debajo de lo que se ve. */
.ob-inner {
  width: min(34rem, 100%);
  max-height: calc(100svh - var(--header-h) - 4rem);
  display: flex; flex-direction: column;
  overflow: hidden;
  padding: 2.2rem 2.2rem 2rem;
  background: var(--ink); border: 1px solid var(--border);
}
.ob-scroll {
  overflow-y: auto; overscroll-behavior: contain;
  min-height: 0;        /* sin esto, un hijo flexible no encoge */
  -webkit-overflow-scrolling: touch;
}
.ob-title {
  margin-top: .7rem; font-family: var(--fd); font-weight: 400;
  font-size: clamp(1.9rem, 4vw, 2.9rem); line-height: .96;
}
.ob-lead {
  margin-top: .5rem; font-family: var(--fs); font-style: italic;
  font-size: 1.15rem; color: var(--teal-lt);
}
.ob-steps { list-style: none; margin: 1.8rem 0 0; padding: 0; }
.ob-step {
  display: grid; grid-template-columns: 2.6rem minmax(0, 1fr); gap: 1rem;
  padding: 1rem 0; border-top: 1px solid var(--border);
}
.ob-num { font-family: var(--fd); font-size: 1.15rem; color: var(--teal-lt); letter-spacing: .06em; }
.ob-body { font-size: .86rem; line-height: 1.7; color: var(--body); }
.ob-body b { display: block; font-weight: 400; color: var(--white); margin-bottom: .15rem; }
/* El botón se queda pegado al fondo del panel mientras se lee.
   «Cómo recorrer la galería» mide 687 px de contenido en una
   pantalla de 568: hay que desplazarse sí o sí, y con el botón al
   final del todo había que llegar hasta abajo para empezar. Ahora
   está siempre a la vista, y el texto pasa por detrás.

   El relleno y el fondo son suyos, no del panel: sin ellos las
   últimas líneas se leerían pegadas al borde del botón. */
#ob-start { width: 100%; text-align: center; flex: none; margin-top: 1.4rem; }

/* ── TÍTULOS DE APERTURA ────────────────────────────────────
   Dos hitos tipográficos sobre el cauce, antes del primer artista. */
#milestones { position: fixed; inset: 0; z-index: var(--z-hud); pointer-events: none; }
.milestone {
  position: absolute; top: 0; left: 0;
  width: min(46rem, 84vw); text-align: center;
  transform-origin: 0 0; opacity: 0;
}
.milestone-kicker {
  font-size: .56rem; letter-spacing: .28em; text-transform: uppercase;
  color: var(--teal-lt); margin-bottom: .9rem;
}
.milestone-title {
  font-family: var(--fd); font-weight: 400; line-height: .92;
  font-size: clamp(2.4rem, 7vw, 6rem); letter-spacing: .02em;
  text-shadow: 0 2px 40px rgba(0,0,0,.75);
}
.milestone.is-serif .milestone-title {
  font-family: var(--fs); font-style: italic; letter-spacing: 0;
  font-size: clamp(2.2rem, 6vw, 5rem);
}
/* Tercera línea, opcional. La usa el texto de cierre para llevar
   «Ríos y memorias 2026» debajo del titular. La sombra es la misma
   del titular: estos textos flotan sobre el agua, que tiene brillos
   claros, y sin ella se pierden. */
.milestone-sub {
  margin-top: .9rem;
  font-family: var(--fs); font-style: italic;
  font-size: clamp(1.1rem, 2.4vw, 1.9rem);
  color: var(--teal-lt);
  text-shadow: 0 2px 30px rgba(0,0,0,.8);
}

/* ── PANEL INSTITUCIONAL ────────────────────────────────────
   Cajón lateral, no pantalla completa: la experiencia sigue viva
   detrás. La información institucional nunca compite con la obra. */
#about-panel {
  position: fixed; inset: 0; z-index: var(--z-panel);
  background: rgba(6,9,12,.72);
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  display: flex; justify-content: flex-end;
}
#about-panel[hidden] { display: none; }
.panel-inner {
  position: relative;
  width: min(38rem, 100%); height: 100%;
  padding: calc(var(--header-h) + 1.2rem) clamp(1.4rem, 3vw, 3rem) clamp(2rem, 6vh, 4rem);
  background: var(--ink); border-left: 1px solid var(--border);
  overflow-y: auto; overscroll-behavior: contain;
}
#about-close { position: fixed; top: calc(var(--header-h) * .28); right: var(--gut); z-index: 1; }
.panel-kicker {
  font-size: .56rem; letter-spacing: .24em; text-transform: uppercase; color: var(--teal-lt);
}
.panel-title {
  margin-top: .8rem; font-family: var(--fd); font-weight: 400;
  font-size: clamp(2rem, 4.5vw, 3.4rem); line-height: .95; letter-spacing: .01em;
}
.panel-lead {
  margin-top: .6rem; font-family: var(--fs); font-style: italic;
  font-size: clamp(1.05rem, 1.8vw, 1.4rem); color: rgba(255,255,255,.7);
}
.panel-text { margin-top: 1.6rem; }
.panel-text p { font-size: .88rem; line-height: 1.8; color: var(--body); max-width: 60ch; }
.panel-text p + p { margin-top: 1rem; }
/* El acceso a la programación completa. Discreto a propósito: es
   una salida, no el asunto del panel. */
.panel-link { margin-top: 1.4rem; }
.panel-link a {
  display: inline-flex; align-items: center; min-height: 44px;
  font-size: .58rem; letter-spacing: .18em; text-transform: uppercase;
  color: var(--teal-lt); text-decoration: none;
  border-bottom: 1px solid rgba(61,184,138,.4);
  transition: color .25s, border-color .25s;
}
.panel-link a:hover { color: var(--white); border-color: var(--white); }
.panel-sub {
  margin-top: 2.2rem; padding-bottom: .7rem; margin-bottom: 1.1rem;
  border-bottom: 1px solid var(--border);
  font-family: var(--fb); font-weight: 400;
  font-size: .56rem; letter-spacing: .24em; text-transform: uppercase; color: var(--teal-lt);
}

/* Contacto: ficha editorial, nunca un formulario. */
.contact-org { font-family: var(--fd); font-size: 1.3rem; letter-spacing: .05em; margin-bottom: .9rem; }
.field {
  display: grid; grid-template-columns: 8.5rem minmax(0, 1fr); gap: 1rem;
  padding: .55rem 0; border-bottom: 1px solid rgba(255,255,255,.07);
  font-size: .84rem; color: var(--hi);
}
.field-label {
  font-size: .54rem; letter-spacing: .22em; text-transform: uppercase;
  color: var(--muted); padding-top: .22rem;
}
.field a { color: var(--teal-lt); text-decoration: none; border-bottom: 1px solid rgba(61,184,138,.35); }
.field a:hover { color: var(--white); border-color: var(--white); }
/* Un valor entre corchetes es un dato pendiente, no un contenido. */
.field .ph { color: var(--mid); font-style: italic; }

/* ── CIERRE DEL RECORRIDO ───────────────────────────────────
   La imagen final está en la escena 3D, al final del cauce. Esto
   son los créditos que aparecen sobre ella. */
#ending {
  position: fixed; inset: 0; z-index: var(--z-hud);
  display: grid; align-items: end; justify-items: start;
  padding: calc(var(--header-h) + 1rem) var(--gut) clamp(1.6rem, 6vh, 4rem);
  background: linear-gradient(to top, rgba(6,10,14,.94) 0%, rgba(6,10,14,.6) 46%, rgba(6,10,14,0) 100%);
  opacity: 0; pointer-events: none;
}
#ending[hidden] { display: none; }
#ending .ending-inner { pointer-events: auto; }
.ending-inner {
  width: min(62rem, 100%);
  max-height: calc(100svh - var(--header-h) - 4rem);
  overflow-y: auto; overscroll-behavior: contain;
}
.ending-actions { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; margin-top: 2rem; }
.ending-credit { margin-top: 1.2rem; font-size: .54rem; letter-spacing: .2em; text-transform: uppercase; color: var(--mid); }

/* ── El cierre ───────────────────────────────────────────────
   Ya no hay dos tiempos ni dos bloques: el texto de cierre flota
   en el cauce (es un hito, ver Journey.buildMilestones) y aquí
   sólo queda el botón con los créditos.

   El velo se recoge abajo, lo justo para que el botón blanco tenga
   fondo: la fotografía ocupa el centro del cuadro y apagarla sería
   contradecir el momento. */
#ending {
  background: linear-gradient(to top,
    rgba(6,10,14,.88) 0%,
    rgba(6,10,14,.42) 18%,
    rgba(6,10,14,0) 38%);
}
.ending-foot { opacity: 0; pointer-events: none; }

/* ── RESPONSIVE ─────────────────────────────────────────────── */
@media (max-width: 992px) {
  #river { width: 228px; height: 58svh; }
  #river .river-svg { width: 48px; }
  .river-tag { font-size: .5rem; letter-spacing: .18em; right: 56px; }
  /* Entre 769 y 992 px la guía sigue a la vista y aquí el tope
     volvía a ser fijo —140 px— con 636 px libres al lado. Mismo
     criterio que arriba: crece con la pantalla. */
  .river-tag b { max-width: clamp(140px, 30vw, 240px); }
}
@media (max-width: 768px) {
  #cur { display: none !important; }
  body.has-cursor { cursor: auto; }
  #river { display: none; }
  .station { width: min(290px, 80vw); }
  #room-stack { left: var(--gut); right: var(--gut); width: auto; bottom: 3.4rem;
    max-height: calc(100svh - var(--header-h) - 5rem); }
  #room-hint { left: var(--gut); right: auto; bottom: var(--gut); font-size: .5rem; }
  .ap-card { padding: 1.5rem 1.3rem 1.3rem; max-height: min(76svh, calc(100svh - var(--header-h) - 4rem)); }
  #artist-panel { padding: calc(var(--header-h) + .6rem) var(--gut) calc(var(--gut) + 2.4rem); }
  .ending-inner { max-height: calc(100svh - var(--header-h) - 3rem); }
  .field { grid-template-columns: 1fr; gap: .2rem; }
  .ob-inner { padding: 1.5rem 1.3rem 1.4rem; }
  .ob-step { grid-template-columns: 2rem minmax(0, 1fr); gap: .7rem; padding: .8rem 0; }
  .milestone { width: 88vw; }
  #lb-figure { grid-template-columns: 1fr; gap: 1.1rem; align-content: center; }
  #lb-media img, #lb-media canvas { max-height: 52svh; }
  .lb-desc { display: none; }
  .lb-arrow { top: auto; bottom: .4rem; transform: none; }
  #grain { opacity: .09; }
}
@media (max-width: 992px) {
  .intro-grid { grid-template-columns: 1fr; gap: 1.6rem; }
  .curatorial-body { max-height: min(26svh, 15rem); }
  .panel-inner { width: 100%; border-left: 0; }
}
@media (max-width: 576px) {
  .intro-title .line-a, .intro-title .line-b { font-size: clamp(3rem, 15vw, 5rem); }
  .intro-quote { font-size: .95rem; }
  .intro-meta { gap: .7rem; }
  .curatorial-body { max-height: 22svh; }
  .curatorial-actions { flex-direction: column; align-items: flex-start; gap: .9rem; }
  .mast-btn { padding: .5rem .5rem; letter-spacing: .14em; }
  #logo-word i { display: none; }
  .ending-actions .btn-outline, .ending-actions .btn-primary { flex: 1 1 auto; text-align: center; }
}
@media (max-width: 420px) {
  .intro-meta .meta-item:nth-child(n+4) { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  #grain, .scroll-line::after { animation: none; }
}

/* ══════════════════════════════════════════════════════════════
   PORTADA AMPLIADA · añadido a la hoja original
   ──────────────────────────────────────────────────────────────
   El #intro original era una sola pantalla: el titular abajo, el
   texto curatorial al lado, y nada más. Por eso estaba declarado
   fijo, sin desplazamiento y alineado al fondo (align-content:end).

   El index.html actual mete DENTRO de ese mismo #intro tres cosas
   más —el índice de sala, los aliados y el pie institucional— y
   lleva data-scroll, es decir, está pensado para desplazarse por
   dentro. Con las reglas de antes ese contenido no cabía: como el
   bloque se alinea al fondo y recorta lo que sobra, el botón
   «Entrar a la galería virtual» quedaba EMPUJADO FUERA de la
   pantalla por arriba. La prueba de navegador no conseguía
   pulsarlo, y una visitante tampoco habría podido.

   Lo que sigue hace que #intro se desplace por dentro, deja la
   primera pantalla igual que estaba, y da estilo a las secciones
   nuevas, que la hoja original no conocía.
   ══════════════════════════════════════════════════════════════ */

#intro {
  display: block;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 0;
  /* El degradado del original se traslada a la primera sección,
     que es donde cumple su función: separar el titular del río. */
  background: var(--ink);
}

/* La primera pantalla conserva la composición de siempre, ahora con
   una fotografía de fondo.

   El degradado ya estaba en el original y existía justo para esto:
   oscurece la parte baja, donde va el titular, y deja la de arriba
   limpia. Por eso va DELANTE de la imagen en la lista de capas: en
   CSS la primera capa es la de encima.

   La ruta de la imagen entra por la variable --intro-img, que pone
   ui.js desde site.json (assets.introImage). Si no hay imagen la
   variable vale «none» y el hero vuelve a ser negro, sin romperse. */
.intro-exhibition {
  min-height: 100svh;
  display: grid;
  align-content: end;
  padding: calc(var(--header-h) + var(--gut)) var(--gut) clamp(1.6rem, 5vh, 3.4rem);
  /* El degradado está medido contra foto1.jpg, no supuesto.

     La fotografía reparte así sus tres franjas (de arriba abajo):

       0–20 %   cielo encapotado, casi blanco (luminancia 228–235)
       20–55 %  los dos pescadores, la atarraya y el pez
       55–100 % el río y la orilla de piedra (luminancia 95 y 65)

     Es decir: lo que hay que ver está EN MEDIO, y las dos franjas
     que estorban están en los extremos. Justo al revés de lo que
     uno supondría mirando sólo los promedios de luz, porque abajo
     la foto ya es oscura y arriba ya es clarísima.

     De ahí las paradas. Ojo: en «to top» el 0 % es el BORDE DE
     ABAJO.

       0–18 %   velo fuerte. Ahí va el titular y ahí la foto sólo
                tiene agua y grava: oscurecerla no cuesta nada y
                el texto blanco queda limpio.
       46–78 %  velo mínimo. Es la banda de las caras, las manos y
                el pez. Si se vela aquí, daba igual poner la foto.
       90–100 % velo fuerte otra vez. El cielo llega a 235 y la
                cabecera va escrita en blanco encima; sin velo no
                se leería. */
  /* Hace falta una segunda capa, en diagonal, y no es capricho: el
     bloque de texto vive abajo a la IZQUIERDA, y justo ahí la foto
     tiene la camiseta blanca del pescador y la grava clara. Un
     degradado vertical no distingue izquierda de derecha, así que
     para salvar «TERCERA EDICIÓN» y «16 ARTISTAS» habría que
     oscurecer toda la franja y apagar también al pescador de la
     derecha y la canoa, que están a la misma altura y se ven bien.

     Anclada en la esquina inferior izquierda, la diagonal pesa donde
     está el texto y se disuelve antes de llegar al pez y al chico de
     la derecha, que quedan intactos. */
  background-image:
    linear-gradient(to top right,
      rgba(6,11,16,.72) 0%,
      rgba(6,11,16,.44) 22%,
      rgba(6,11,16,.12) 42%,
      rgba(6,11,16,0) 58%),
    linear-gradient(to top,
      rgba(6,11,16,.80) 0%,
      rgba(6,11,16,.68) 18%,
      rgba(6,11,16,.50) 32%,
      rgba(6,11,16,.22) 46%,
      rgba(6,11,16,.14) 64%,
      rgba(6,11,16,.26) 78%,
      rgba(6,11,16,.52) 90%,
      rgba(6,11,16,.68) 100%),
    var(--intro-img, none);
  background-size: cover, cover, cover;
  background-position: center, center, center;
  background-repeat: no-repeat, no-repeat, no-repeat;
  background-color: var(--ink);
}

/* Los dos renglones pequeños de la portada —«TERCERA EDICIÓN» y
   «16 ARTISTAS»— están pensados para un fondo negro plano: son texto
   diminuto, muy espaciado y a media opacidad. Sobre la fotografía,
   que justo ahí tiene la camiseta blanca del pescador, se borran.

   La tentación es subir el velo hasta que se lean, pero eso apaga la
   foto entera por culpa de dos líneas. Una sombra hace el mismo
   trabajo sin tocar la imagen: donde el fondo ya es oscuro no se
   nota, y donde es claro despega el texto. Va limitada a la portada
   —no a .eyebrow en general— porque en el resto de la galería el
   fondo sí es plano y ahí la sombra sólo ensuciaría la letra.

   El titular no la necesita: es tan grande que se defiende solo. */
.intro-exhibition .eyebrow,
.intro-exhibition .meta-item {
  text-shadow: 0 1px 10px rgba(6,11,16,.92), 0 0 2px rgba(6,11,16,.85);
}
.intro-exhibition .meta-item { color: rgba(255,255,255,.78); }

/* El ojo lleva doble ración de sombra. No es por capricho: es texto
   de 9 px en verde agua claro, y le toca justo la camiseta blanca.
   Un verde claro sobre un gris claro no separa por mucho que se
   suba la opacidad —el problema es el contraste de color, no el de
   cantidad—, así que lo que lo rescata es el borde oscuro pegado a
   la letra. Se mantiene el verde porque es el color del festival;
   cambiarlo a blanco lo arreglaría y rompería la identidad. */
.intro-exhibition .eyebrow {
  text-shadow:
    0 0 1px rgba(6,11,16,.95), 0 1px 2px rgba(6,11,16,.95),
    0 0 6px rgba(6,11,16,.9), 0 0 14px rgba(6,11,16,.75);
}
.intro-exhibition .eyebrow::before {
  box-shadow: 0 0 6px rgba(6,11,16,.9);
}

/* La clase del ojo de la portada cambió de .intro-eyebrow a .eyebrow. */
.eyebrow {
  display: inline-flex; align-items: center; gap: .9rem;
  font-size: .58rem; letter-spacing: .22em; text-transform: uppercase;
  color: var(--teal-lt); margin-bottom: 1.4rem;
}
.eyebrow::before { content: ''; width: 24px; height: 1px; background: var(--teal-lt); }
.sec-h[data-n] .eyebrow::before { content: none; }

/* ── Cabeceras de las secciones nuevas ─────────────────────── */
.sec-h { margin-bottom: 2rem; }
.sec-h .big {
  font-family: var(--fd); font-weight: 400; line-height: .92;
  font-size: clamp(2.2rem, 6.5vw, 5rem);
  display: flex; flex-direction: column;
}
.sec-h .big span:nth-child(2) { color: var(--teal-lt); }

.intro-index, .intro-partners, .intro-contact {
  max-width: 96rem; margin: 0 auto;
  padding: clamp(2.4rem, 6vh, 5rem) var(--gut);
  border-top: 1px solid var(--border);
}

/* ── El índice de sala ─────────────────────────────────────── */
.ix-note {
  font-family: var(--fs); font-style: italic; color: var(--body);
  max-width: 54ch; margin-bottom: 2rem;
}
.ix-list { list-style: none; margin: 0; padding: 0; }
.ix-row {
  display: grid; grid-template-columns: 3.2rem 1fr auto auto;
  gap: 1.2rem; align-items: baseline;
  padding: 1.1rem 0; border-bottom: 1px solid rgba(255,255,255,.07);
  transition: background .2s;
}
.ix-row:hover { background: rgba(255,255,255,.03); }
.ix-num { font-family: var(--fd); font-size: 1.1rem; color: var(--mid); }
.ix-main { display: flex; flex-direction: column; gap: .18rem; min-width: 0; }
.ix-name {
  font-family: var(--fd); font-weight: 400; letter-spacing: .035em;
  font-size: clamp(1.1rem, 2.4vw, 1.6rem);
}
.ix-project {
  font-family: var(--fs); font-style: italic; font-size: .94rem; color: var(--teal-lt);
}
.ix-country {
  font-size: .56rem; letter-spacing: .2em; text-transform: uppercase; color: var(--muted);
}
.ix-open {
  font-size: .56rem; letter-spacing: .18em; text-transform: uppercase;
  color: var(--teal-lt); border: 1px solid rgba(255,255,255,.2);
  padding: .55rem .9rem; white-space: nowrap;
  transition: background .2s, color .2s, border-color .2s;
}
.ix-open:hover { background: var(--teal-lt); color: var(--ink); border-color: var(--teal-lt); }

/* ── Aliados ───────────────────────────────────────────────── */
.aliados-banda { overflow: hidden; }
/* La animación va en la CINTA, no en las pistas: así el bucle no
   da un salto al reiniciar. */
.aliados-cinta { display: flex; width: max-content; animation: desfile 42s linear infinite; }
.aliados-track { display: flex; align-items: center; }
@keyframes desfile { from { transform: translateX(0); } to { transform: translateX(-50%); } }
.aliados-banda:hover .aliados-cinta { animation-play-state: paused; }
.aliado-logo { flex: 0 0 auto; height: 54px; margin-right: 3.2rem; display: flex; align-items: center; }
.aliado-logo img {
  height: 100%; width: auto; object-fit: contain;
  filter: brightness(0) invert(1); opacity: .72; transition: opacity .2s;
}
.aliado-logo:hover img { opacity: 1; }
/* Un aliado SIN logo aparece como nombre, con borde punteado, para
   que se vea que falta. No se sustituye por otro logo. */
.aliado-logo.is-pending { padding: 0 1.1rem; border: 1px dashed rgba(255,255,255,.26); }
.aliado-nombre {
  font-size: .6rem; letter-spacing: .14em; text-transform: uppercase; color: var(--muted);
}

/* ── Contacto ──────────────────────────────────────────────── */
.contact-grid {
  display: grid; grid-template-columns: 1.2fr .9fr .9fr;
  gap: clamp(1.6rem, 4vw, 3.4rem);
}
.contact-col { display: flex; flex-direction: column; gap: 1.2rem; min-width: 0; }
.contact-big {
  font-family: var(--fd); font-size: clamp(1.6rem, 4vw, 3rem); line-height: 1;
  display: flex; flex-direction: column;
}
.contact-big span:last-child { color: var(--teal-lt); }
.contact-city {
  font-size: .6rem; letter-spacing: .18em; text-transform: uppercase;
  color: var(--muted); margin-top: 1rem;
}
.contact-item { display: flex; flex-direction: column; gap: .3rem; }
.contact-label {
  font-size: .54rem; letter-spacing: .22em; text-transform: uppercase; color: var(--muted);
}
.contact-item a { color: var(--teal-lt); text-decoration: none; }
.contact-item a:hover { color: var(--white); }
.instagram-link { display: inline-flex; align-items: center; gap: .5rem; }

/* ── Pie institucional ─────────────────────────────────────── */
.footer-bar {
  max-width: 96rem; margin: 0 auto;
  padding: clamp(2.2rem, 6vh, 4rem) var(--gut);
  border-top: 1px solid var(--border);
  display: flex; flex-direction: column; align-items: center; gap: 1.3rem;
  text-align: center;
}
.footer-logo { height: 40px; width: auto; opacity: .6; filter: brightness(0) invert(1); }
.footer-copy { font-size: .68rem; color: var(--muted); max-width: 60ch; }
.footer-site {
  font-size: .58rem; letter-spacing: .2em; text-transform: uppercase;
  color: var(--white); text-decoration: none;
  border-bottom: 1px solid rgba(255,255,255,.22); padding-bottom: .35rem;
}
.footer-site:hover { color: var(--teal-lt); border-color: var(--teal-lt); }

/* ── Hero de banners · solo si la galería se publica sola ───── */
#hero { position: relative; }
.hero-track {
  position: relative; aspect-ratio: 16 / 7; min-height: 42svh;
  overflow: hidden; background: var(--ink2);
}
.hero-slide { position: absolute; inset: 0; opacity: 0; transition: opacity .9s ease; }
.hero-slide.is-active { opacity: 1; }
.hero-slide img { width: 100%; height: 100%; object-fit: cover; }
.hero-controls { display: flex; align-items: center; gap: .6rem; padding: .9rem var(--gut); }
.hero-arrow, .hero-toggle {
  font-size: .58rem; letter-spacing: .16em; text-transform: uppercase;
  color: var(--muted); padding: .5rem .7rem; border: 1px solid var(--border);
}
.hero-arrow:hover, .hero-toggle:hover { color: var(--white); border-color: var(--teal-lt); }
.hero-dots { display: flex; gap: .4rem; }
.hero-dot { width: 22px; height: 2px; background: rgba(255,255,255,.26); transition: background .2s; }
.hero-dot.is-on { background: var(--teal-lt); }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

@media (max-width: 992px) {
  .contact-grid { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 768px) {
  .contact-grid { grid-template-columns: 1fr; }
  .ix-row {
    grid-template-columns: 2.4rem 1fr;
    grid-template-areas: 'num main' '. country' '. open';
    row-gap: .5rem;
  }
  .ix-num { grid-area: num; } .ix-main { grid-area: main; }
  .ix-country { grid-area: country; } .ix-open { grid-area: open; justify-self: start; }
}

/* Portada a una sola columna: cuando se apaga el bloque curatorial
   (deploy.showCuratorial: false), ui.js traslada el botón de entrada
   a la columna del titular y marca la rejilla con .is-solo. Sin esto
   quedaría una columna vacía a la derecha. */
.intro-grid.is-solo { grid-template-columns: minmax(0, 1fr); }
.intro-grid.is-solo .intro-actions { margin-top: 2.2rem; }

/* ══════════════════════════════════════════════════════════════
   LA SALA EN EL TELÉFONO
   ──────────────────────────────────────────────────────────────
   Galería vertical en vez de sala tridimensional. Ver
   UI.abrirSalaMovil: en el teléfono la sala 3D no se construye,
   así que esto no se superpone a nada — lo sustituye.

   Vive fuera de las medias queries a propósito: quien decide si
   se usa es U.esMovil() en JavaScript, no el ancho de la ventana.
   Así no hay dos criterios que puedan discrepar.
   ══════════════════════════════════════════════════════════════ */
.room-movil {
  position: fixed; inset: 0; z-index: var(--z-panel);
  background: var(--ink);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  /* «proximity» y no «mandatory»: con mandatory, un texto curatorial
     más alto que la pantalla queda atrapado entre dos imanes y no se
     puede leer entero. Con proximity cada obra se centra sola cuando
     el dedo la deja cerca, y los textos largos se recorren libres. */
  scroll-snap-type: y proximity;
  scroll-behavior: smooth;
}
.room-movil[hidden] { display: none; }

/* Mientras está abierta, el lienzo 3D no pinta nada que se vea y
   el cuerpo no se desplaza por detrás. */
body.en-sala-movil { overflow: hidden; }

/* La barra de volver va pegada arriba y siempre a la vista: en una
   galería de veinte fotografías, tener que subir hasta el principio
   para salir es justo lo que hace incómoda una sala en el móvil. */
.rm-barra {
  position: sticky; top: 0; z-index: 2;
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; padding: .7rem var(--gut);
  background: rgba(9,9,9,.92);
  backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--border);
}
.rm-volver {
  font-size: .58rem; letter-spacing: .18em; text-transform: uppercase;
  color: var(--white); padding: .5rem 0;
}
.rm-cuenta {
  font-size: .54rem; letter-spacing: .2em; text-transform: uppercase;
  color: var(--muted); white-space: nowrap;
}

/* Sin márgenes aquí: cada escena pone los suyos. Si el contenedor
   los pusiera, las fotografías no podrían ocupar el ancho entero. */
.rm-inner { padding: 0; }

/* ── La identidad del proyecto, antes de las fotografías ─────── */
/* Es la portada de la sala y ocupa su propia pantalla: el nombre
   del artista y su texto, y nada más. Así la secuencia fotográfica
   empieza limpia, sin texto compitiendo con la primera obra. */
.rm-cab {
  min-height: 100svh;
  display: flex; flex-direction: column; justify-content: center;
  padding: 4.5rem var(--gut) 5rem;
  scroll-snap-align: center;
}
.rm-label {
  font-size: .52rem; letter-spacing: .26em; text-transform: uppercase;
  color: var(--teal-lt);
}
.rm-name {
  margin-top: .7rem;
  font-family: var(--fd); font-weight: 400; line-height: .98;
  font-size: clamp(2rem, 9vw, 3rem); letter-spacing: .015em;
}
.rm-project {
  margin-top: .35rem;
  font-family: var(--fs); font-style: italic;
  font-size: clamp(1.1rem, 5vw, 1.4rem); color: var(--teal-lt);
}
.rm-meta {
  margin-top: .8rem;
  font-size: .54rem; letter-spacing: .22em; text-transform: uppercase; color: var(--muted);
}
/* Medida de lectura cómoda y aire entre párrafos: el texto
   curatorial se lee de verdad aquí, no es un pie de foto. */
.rm-text { margin-top: 1.4rem; }
.rm-text p { font-size: .93rem; line-height: 1.8; color: var(--body); max-width: 40ch; }
.rm-text p + p { margin-top: .9rem; }

/* ══ Las fotografías: una obra por pantalla, suspendida ══
   ─────────────────────────────────────────────────────────────
   La sala del teléfono no es una tira de fotos pegadas: cada obra
   tiene su propia escena de una pantalla de alto, y el dedo pasa
   de una a la siguiente. La profundidad la da el navegador, no
   three.js: «perspective» en la escena y «translateZ» en la obra
   son transformaciones que van por GPU, así que la sensación
   espacial no cuesta ni un vértice ni una textura.

   Quien mueve las obras es ui.js, escribiendo --z, --rx, --s y --o
   según lo lejos que esté cada una del centro de la pantalla. Aquí
   sólo se declara cómo se usan esos cuatro números: así el gesto
   manda sobre la animación y nada se mueve por su cuenta.

   svh y no vh: en el móvil la barra del navegador aparece y
   desaparece, y con vh la escena cambiaba de alto al desplazarse.
   ───────────────────────────────────────────────────────────── */
.rm-figura {
  margin: 0;
  min-height: 100svh;
  display: grid; place-items: center;
  /* Arriba despeja la barra de volver, que va pegada y por encima;
     abajo, los botones de pasar de obra. A los lados, el aire que
     hace que la fotografía se lea como una obra colgada y no como
     un fondo de pantalla: sin ese margen, una horizontal tocaba los
     dos bordes y el resplandor de detrás no se veía. */
  padding: 3.2rem clamp(1rem, 4.5vw, 1.8rem) 4.4rem;
  scroll-snap-align: center;
  perspective: 1100px;
  perspective-origin: 50% 44%;
  /* El navegador no pinta las escenas que no se ven. Con veinte
     fotografías, esto es la diferencia entre una sala fluida y una
     que se arrastra.

     Sin contain-intrinsic-size a propósito: con él, una escena cuya
     imagen aún no había llegado medía 741 px en una pantalla de 664
     y encogía al cargar la foto. Eso mueve el contenido debajo del
     dedo justo mientras se desplaza. Sin él, el min-height manda
     siempre y toda escena mide exactamente una pantalla, cargada o
     no: medido en iPhone 13, las cuatro primeras dan 664 = 664. */
  content-visibility: auto;
}
.rm-obra {
  transform-style: preserve-3d;
  will-change: transform, opacity;
  transform:
    translate3d(0, calc(var(--ty, 0) * 1px), calc(var(--z, 0) * 1px))
    rotateX(calc(var(--rx, 0) * 1deg))
    scale(var(--s, 1));
  opacity: var(--o, 1);
}

.rm-marco { position: relative; display: grid; place-items: center; }
/* Ni recorte ni deformación: la fotografía crece hasta donde le
   permita el ancho o el alto, y manda el que se agote primero.
   Así una vertical y una horizontal tienen la misma presencia. */
.rm-foto {
  display: block; position: relative; z-index: 1;
  /* El alto que de verdad sobra. La cartela, los dos rellenos y la
     barra de abajo ocupan unos 8 rem; el resto es para la obra. Con
     un tope fijo del 62 % una fotografía vertical se quedaba en 412
     px cuando caben 500, y perdía presencia sin motivo. Manda el
     menor de los dos: así ni se desborda en pantallas bajas ni se
     queda pequeña en las altas. */
  max-width: 100%; max-height: min(72svh, calc(100svh - 11rem));
  width: auto; height: auto;
  box-shadow:
    0 1px 0 rgba(255,255,255,.07),
    0 18px 30px -12px rgba(0,0,0,.75),
    0 50px 90px -30px rgba(0,0,0,.9);
}
/* El resplandor que separa la obra del fondo: la misma imagen,
   desenfocada y detrás. Es lo que hace que no parezca pegada a la
   pantalla. La pone ui.js cuando la fotografía ya cargó, para no
   pedir el archivo dos veces ni duplicar un 404. */
.rm-halo {
  position: absolute; z-index: 0;
  max-width: 100%; max-height: min(72svh, calc(100svh - 11rem));
  width: auto; height: auto;
  filter: blur(26px) saturate(1.3);
  opacity: .34; transform: scale(1.05);
  pointer-events: none;
}
.rm-marco.is-vacio {
  width: min(100%, 420px); min-height: 42svh;
  background: var(--ink2);
  display: grid; place-items: center;
  border: 1px dashed rgba(255,255,255,.22);
}
.rm-marco.is-vacio img { display: none; }
.rm-marco.is-vacio::after {
  content: attr(data-aviso);
  font-size: .58rem; letter-spacing: .18em; text-transform: uppercase;
  color: rgba(255,255,255,.45);
}
/* La cartela viaja con la obra, debajo y nunca encima: tapar una
   fotografía para poner su título es perder las dos cosas. */
.rm-pie {
  display: flex; align-items: baseline; justify-content: center;
  gap: .9rem; flex-wrap: wrap; text-align: center;
  margin-top: 1.1rem;
}
.rm-num {
  font-family: var(--fd); font-size: .8rem; letter-spacing: .08em; color: var(--mid);
}
.rm-cartela {
  font-family: var(--fs); font-style: italic;
  font-size: .88rem; color: var(--body); min-width: 0;
}

/* ── Pasar de obra en obra ───────────────────────────────────── */
.rm-nav {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 3;
  display: flex; align-items: center; justify-content: center; gap: 1.1rem;
  padding: .55rem var(--gut) calc(.55rem + env(safe-area-inset-bottom));
  background: linear-gradient(to top, rgba(9,9,9,.94), rgba(9,9,9,.55) 65%, transparent);
  pointer-events: none;      /* el fondo no, los botones sí */
}
.rm-nav button {
  pointer-events: auto;
  width: 44px; height: 44px; flex: none;
  display: grid; place-items: center;
  font-size: 1.1rem; color: var(--white);
  border: 1px solid rgba(255,255,255,.2); border-radius: 50%;
  background: rgba(255,255,255,.04);
  transition: opacity .25s ease, border-color .25s ease;
}
.rm-nav button[disabled] { opacity: .28; }
.rm-indice {
  font-family: var(--fd); font-size: .82rem; letter-spacing: .12em;
  color: var(--mid); min-width: 5.5ch; text-align: center;
}

/* La pista de que hay que deslizar, sólo en la portada de la sala. */
.rm-pista {
  margin-top: clamp(1.8rem, 6vh, 3rem);
  font-size: .52rem; letter-spacing: .24em; text-transform: uppercase;
  color: var(--muted);
}
.rm-pista i {
  display: block; width: 1px; height: 34px; margin: .8rem 0 0;
  background: linear-gradient(to bottom, rgba(255,255,255,.45), transparent);
}

/* Quien pide menos movimiento se queda con la misma sala, plana y
   quieta: las obras no se inclinan ni se alejan, y el salto de una
   a otra es instantáneo. */
@media (prefers-reduced-motion: reduce) {
  .rm-obra { transform: none; opacity: 1; }
  .room-movil { scroll-behavior: auto; }
}

/* ── El final de la sala ────────────────────────────────────── */
.rm-fin {
  min-height: 100svh;
  display: flex; flex-direction: column; justify-content: center; gap: .9rem;
  scroll-snap-align: center;
  padding: 0 var(--gut) 5.5rem;
}
.rm-fin button {
  width: 100%; text-align: center; padding: .95rem 1.2rem;
  font-size: .58rem; letter-spacing: .18em; text-transform: uppercase;
}
.rm-volver-fin { border: 1px solid rgba(255,255,255,.26); color: var(--white); }
.rm-siguiente { background: var(--white); color: var(--ink); font-weight: 500; }

/* ══════════════════════════════════════════════════════════════
   MENÚ DE MANO · sólo teléfono
   ──────────────────────────────────────────────────────────────
   La cabecera tenía cinco botones y cuatro idiomas en una fila.
   En 390 px no caben: el logotipo partía en dos líneas y se metía
   debajo de «Volver al sitio». Aquí .mast-right deja de ser una
   fila y pasa a ser un panel que cuelga de la cabecera.

   Quien enciende esto es ui.js (initMenu), que pone .con-menu en
   #masthead sólo si U.esMovil(). El escritorio no ve ni una de
   estas reglas, porque todas cuelgan de esa clase.
   ══════════════════════════════════════════════════════════════ */
.mast-burger {
  display: none;
  width: 42px; height: 42px; flex: none;
  flex-direction: column; align-items: center; justify-content: center; gap: 5px;
  margin-right: calc(var(--gut) * -.35);   /* el área táctil, no el dibujo */
}
.mast-burger span {
  display: block; width: 20px; height: 1px;
  background: var(--white);
  transition: transform .28s ease, opacity .2s ease;
}
#masthead.con-menu .mast-burger { display: flex; }

/* Las tres rayas se convierten en una X al abrir. */
#masthead.menu-abierto .mast-burger span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
#masthead.menu-abierto .mast-burger span:nth-child(2) { opacity: 0; }
#masthead.menu-abierto .mast-burger span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

#masthead.con-menu .mast-right {
  position: absolute; top: 100%; right: var(--gut);
  flex-direction: column; align-items: stretch; gap: 0;
  min-width: min(16rem, 72vw);
  padding: .4rem 0;
  background: rgba(9,9,9,.96);
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  border: 1px solid var(--border);
  /* Cerrado no ocupa sitio NI recibe toques: visibility, no sólo
     opacidad, para que no se pueda pulsar a ciegas lo que no se ve. */
  opacity: 0; visibility: hidden; pointer-events: none;
  transform: translateY(-6px);
  transition: opacity .22s ease, transform .22s ease, visibility 0s linear .22s;
}
#masthead.menu-abierto .mast-right {
  opacity: 1; visibility: visible; pointer-events: auto;
  transform: translateY(0);
  transition: opacity .22s ease, transform .22s ease, visibility 0s linear 0s;
}
/* Cada opción, una línea entera: objetivo táctil cómodo y lectura
   de arriba abajo, no botones pegados unos a otros. */
#masthead.con-menu .mast-right > .mast-btn {
  display: block; width: 100%; text-align: left;
  padding: .85rem 1.1rem; border: 0;
  border-top: 1px solid rgba(255,255,255,.07);
  color: rgba(255,255,255,.82);
}
#masthead.con-menu .mast-right > .mast-btn:first-child { border-top: 0; }
#masthead.con-menu .mast-right > .mast-btn:hover { border-color: rgba(255,255,255,.07); }
/* El selector de idiomas lo mete traduccion.js como primer hijo:
   se queda en fila, que es como se lee bien. */
#masthead.con-menu .mast-right > :not(.mast-btn) {
  display: flex; align-items: center; justify-content: flex-start;
  flex-wrap: wrap; gap: .2rem;
  padding: .7rem 1.1rem;
  border-bottom: 1px solid rgba(255,255,255,.07);
}
