/* ============================================================
   TIPOGRAFÍA AUTOALOJADA
   Inter y Fraunces servidas desde nuestro propio dominio, en
   variable woff2. Antes se pedían a fonts.googleapis.com, lo que
   costaba dos conexiones a un tercero (DNS + TLS a googleapis y a
   gstatic) ANTES de poder pintar una sola letra, y le entregaba a
   Google la IP y el User-Agent de cada visitante — con la Ley 1581
   encima, eso es un tratamiento de datos que no necesitamos.

   Variable = un archivo cubre todos los pesos. `unicode-range` hace
   que el navegador baje `latin-ext` solo si la página lo necesita:
   en español nunca se baja.

   Ambas familias son SIL Open Font License 1.1 (ver LICENSE-*.txt),
   que permite el alojamiento propio sin condiciones adicionales.
   ============================================================ */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url('./fonts/inter-latin-wght-normal.woff2') format('woff2-variations');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url('./fonts/inter-latin-ext-wght-normal.woff2') format('woff2-variations');
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url('./fonts/fraunces-latin-wght-normal.woff2') format('woff2-variations');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url('./fonts/fraunces-latin-ext-wght-normal.woff2') format('woff2-variations');
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}

/* ============================================================
   CiudadFuria — landing cinematográfica
   Tokens, capas del stage, beats narrativos, rail, contacto.
   ============================================================ */

.filtros { position: absolute; width: 0; height: 0; overflow: hidden; }

:root {
  /* la tela del centro — scroll.js escribe estos tres en cada cuadro.
     Van acá y no en .tela: una declaración en el propio elemento le gana
     al valor heredado, y el clímax se quedaba sin efecto. */
  --tela-escala: 1;
  --tela-alzada: 0;
  --tela-vaiven: 0px;
  --tela-presencia: 1;

  /* marca */
  --cf-primary: #3b82f6;
  --cf-emerald: #10b981;
  --cf-orange: #f97316;
  --cf-magenta: #c850a0;
  /* superficies */
  --cf-bg: #0b1220;
  --cf-bg-2: #0e1526;
  --cf-ink: #f4f7fb;
  --cf-ink-soft: #aeb9cc;
  --cf-line: rgba(174, 185, 204, 0.16);
  --cf-card: rgba(9, 15, 29, 0.84);
  --cf-card-border: rgba(160, 180, 210, 0.24);
  /* layout */
  --radius: 14px;
  --pad: clamp(1.1rem, 3vw, 2rem);
  --maxw: 1200px;
  /* motion playhead (lo escribe scroll.js) */
  --p: 0;
  --tint: 0;
}

* { box-sizing: border-box; }

/* overflow-x en <html> con 'clip' (no crea contexto de scroll y NO rompe el
   position:sticky del stage, a diferencia de overflow-x:hidden en el body). */
html { scroll-behavior: smooth; overflow-x: clip; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--cf-bg);
  color: var(--cf-ink);
  font-family: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
  -webkit-font-smoothing: antialiased;
}

.skip-link {
  position: absolute; left: -999px; top: 0; z-index: 100;
  font-weight: 600;
  background: var(--cf-primary); color: #fff; padding: 0.6rem 1rem; border-radius: 0 0 8px 0;
}
.skip-link:focus { left: 0; }
.skip-link + .skip-link:focus { left: 0; top: 2.6rem; }
.honey { position: absolute; left: -9999px; width: 1px; height: 1px; opacity: 0; }

a { color: inherit; }

/* ---------------- header persistente ---------------- */
.site-header {
  position: fixed; inset: 0 0 auto 0; z-index: 40;
  display: flex; align-items: center; justify-content: space-between;
  padding: clamp(0.7rem, 2vw, 1.1rem) var(--pad);
  padding-top: max(clamp(0.7rem, 2vw, 1.1rem), env(safe-area-inset-top));
}
.brand { display: flex; align-items: center; gap: 0.55rem; text-decoration: none; }
/* Logo aprobado (sesión 29): domo naranja detrás de tres barras azules +
   «CiudadFuria» en Nunito Black, ya convertido a contornos dentro del SVG
   (no carga ninguna fuente extra). Versión negativa para el fondo noche. */
.brand-logo { display: block; height: 30px; width: auto; }
.brand-mark {
  width: 30px; height: 30px; flex: 0 0 auto;
  background: url("./mark.svg") center / contain no-repeat;
}
.brand-word { font-weight: 700; letter-spacing: -0.01em; font-size: 1.05rem; }
.brand-word b { color: var(--cf-primary); font-weight: 700; }

.btn-access {
  background: #2563eb; color: #fff; font-weight: 600; font-size: 0.92rem;
  text-decoration: none; padding: 0.58rem 1.2rem; border-radius: 10px;
  border: 0; border-bottom: 3px solid #1d4ed8; cursor: pointer;
  transition: transform 0.15s ease, filter 0.2s ease;
}
.btn-access:hover { filter: brightness(1.07); }
.btn-access:active { transform: translateY(1px); border-bottom-width: 1px; }
.btn-access:focus-visible { outline: 2px solid #fff; outline-offset: 3px; }

/* ---- botón de música ----
   Debajo de Acceder, en columna. La nota lleva un tachado que se desvanece
   al encender: un solo icono para los dos estados, sin cambiar de dibujo. */
.acciones { display: flex; flex-direction: column; align-items: flex-end; gap: 0.5rem; }
.btn-sonido {
  width: 40px; height: 40px; padding: 0;
  display: grid; place-items: center;
  border-radius: 50%; cursor: pointer;
  color: var(--cf-ink-soft);
  background: rgba(9, 15, 29, 0.42);
  border: 1px solid var(--cf-line);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  transition: color 0.2s ease, border-color 0.2s ease, background 0.2s ease;
}
.btn-sonido svg { width: 18px; height: 18px; display: block; }
.btn-sonido .tachado { transition: opacity 0.22s ease; }
.btn-sonido:hover { color: var(--cf-ink); }
.btn-sonido.suena {
  color: #a8c7ff;
  border-color: rgba(110, 162, 255, 0.55);
  background: rgba(37, 99, 235, 0.20);
}
.btn-sonido.suena .tachado { opacity: 0; }
.btn-sonido:focus-visible { outline: 2px solid #fff; outline-offset: 3px; }

/* ---------------- barra lateral de beats ---------------- */
.beat-nav {
  position: fixed; right: clamp(0.5rem, 1.5vw, 1rem); top: 50%; transform: translateY(-50%);
  z-index: 35; opacity: 0; transition: opacity 0.4s ease; pointer-events: none;
}
.beat-nav.show { opacity: 1; pointer-events: auto; }
.beat-nav ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.85rem; align-items: flex-end; }
.beat-nav button {
  display: flex; align-items: center; gap: 0.5rem; background: none; border: 0; cursor: pointer; color: var(--cf-ink-soft);
  font-family: inherit; font-size: 0.7rem; padding: 0;
}
.beat-nav .dot { width: 9px; height: 9px; border-radius: 50%; border: 1.5px solid var(--cf-ink-soft); transition: all 0.25s ease; }
.beat-nav button .label { opacity: 0; transform: translateX(6px); transition: all 0.25s ease; white-space: nowrap; }
.beat-nav button:hover .label, .beat-nav button:focus-visible .label { opacity: 1; transform: translateX(0); }
.beat-nav button[aria-current="true"] .dot { background: var(--cf-primary); border-color: var(--cf-primary); transform: scale(1.25); }
.beat-nav button:focus-visible { outline: 2px solid var(--cf-primary); outline-offset: 4px; border-radius: 4px; }

/* ---------------- sección cinematográfica ---------------- */
/* El alto de esta sección ES la sensibilidad del scroll: cuanto más alta,
   más hay que deslizar para recorrer la misma calle. Estaba en 900vh —ocho
   pantallas de scroll para seis beats— y se sentía pesado. A 620vh el
   recorrido completo son 5,2 pantallas: un 35% menos de dedo para el mismo
   viaje.
   OJO si se vuelve a tocar: bajarlo también acorta el tiempo que cada panel
   de texto queda legible. Por eso este cambio vino acompañado de repartir
   las ventanas de los beats en scroll.js; con las de antes, "La demanda"
   habría quedado a plena opacidad 16vh, un sexto de pantalla. */
.cinematic { position: relative; height: 780vh; }  /* 8 beats (sept. 2026); antes 620vh con 6 */
.cinematic-old-note { display: none; }  /* timeline virtual; scroll.js lo mide */
/* 100lvh y no 100svh.

   svh es el alto del viewport CON la barra del navegador desplegada. En
   cuanto la barra se retrae —que es lo primero que pasa al deslizar— el
   área visible crece y el escenario, medido en svh, se queda corto: queda
   una franja por debajo donde se alcanza a ver el fondo de la página, y
   parece que empezara otra sección. lvh es el alto con la barra retraída,
   que es como se ve el 99% del tiempo.

   dvh sería lo "exacto", pero cambia mientras uno desliza y obligaría a
   reescalar el lienzo en cada cuadro. Ese remedio es peor. */
.stage {
  position: sticky; top: 0;
  height: 100vh;
  height: 100lvh;
  overflow: clip; isolation: isolate;
}

.layer-atmos, .layer-world, .layer-grain, .layer-vignette { position: absolute; inset: 0; }

/* ---- Capa 0 · atmósfera ----------------------------------------------
   Ya no es un placeholder plano: es un cielo con horizonte real, alineado
   con el punto de fuga del canvas (54% de la altura). El resplandor del
   horizonte vira de frío a cálido con el avance (--p): la misma historia
   del morph —caos ciudadano → orden institucional— contada por la luz.  */
.layer-atmos {
  z-index: 0;
  background:
    /* halo del punto de fuga: azul que se calienta al avanzar */
    radial-gradient(58% 34% at 50% 54%, rgba(96,150,255, calc(0.13 + var(--p) * 0.05)) 0%, transparent 72%),
    radial-gradient(30% 17% at 50% 54%, rgba(255,168,86, calc(var(--p) * 0.20)) 0%, transparent 78%),
    /* tintes de las dos capas del producto, bajos y laterales */
    radial-gradient(72% 52% at 8% 88%, rgba(200,80,160, calc(0.13 - var(--p) * 0.09)) 0%, transparent 66%),
    radial-gradient(72% 52% at 94% 84%, rgba(16,185,129, calc(0.11 - var(--p) * 0.07)) 0%, transparent 64%),
    /* suelo bajo el horizonte */
    linear-gradient(to bottom, transparent 0 54%, rgba(4,7,14,0.55) 74%, rgba(3,5,11,0.9) 100%),
    /* cielo */
    linear-gradient(to bottom, #060a14 0%, #0a1122 38%, #0e1830 54%, #0a0f1e 100%);
}
.layer-world { z-index: 1; width: 100%; height: 100%; display: block; }

/* ---- Capa 1b · la tela ------------------------------------------------
   Reemplaza al emblema-constelación que se dibujaba en el lienzo. Son 32
   cuadros de un render en una sola hoja de sprites, recorridos con dos
   `translate` anidados.

   Por qué translate y no background-position: un porcentaje en
   background-position se resuelve contra (caja − imagen), así que con una
   hoja ocho veces más ancha que la ventana los cuadros caerían en k/7 y
   los pasos quedarían ENTRE cuadros. Un porcentaje en translate se
   resuelve contra la caja del propio elemento: la tira mide 800% × 400%,
   luego 12,5% es exactamente un cuadro.

   Son dos capas porque una sola no puede animar los dos ejes: dos
   animaciones sobre `transform` se pisan entre sí.

   Solo se anima transform → el navegador lo compone en la GPU y no toca
   layout. 32 capas apiladas haciendo fundido serían 32 texturas en
   memoria, y eso en un teléfono de gama media se siente.               */
.layer-tela { position: absolute; inset: 0; z-index: 1; pointer-events: none; }

.tela {
  --tela-dur: 8s;                 /* 32 cuadros / 8 s = 4 fps */

  position: absolute;
  left: 50%;
  /* 54% es el punto de fuga del lienzo: la tela queda donde estaba el
     emblema. La alzada se resta en el mismo porcentaje de la escena. */
  top: calc(54% - var(--tela-alzada) * 100%);
  width: clamp(126px, 13.5vw, 189px);
  aspect-ratio: 400 / 449;
  overflow: hidden;
  transform: translate(-50%, -50%) translateX(var(--tela-vaiven)) scale(var(--tela-escala));
  opacity: var(--tela-presencia);
  will-change: transform, opacity;
  /* Hasta que llegue la hoja (649 KB) se ve el primer cuadro (47 KB): el
     centro de la escena no queda vacío en el primer pintado. */
  background: url("./tela-poster.webp") center / contain no-repeat;
}
.tela-lista .tela { background-image: none; }

.tela-tira {
  position: absolute; top: 0; left: 0;
  width: 800%; height: 400%;
  animation: tela-filas var(--tela-dur) steps(4) infinite;
  will-change: transform;
}
.tela-cuadros {
  width: 100%; height: 100%;
  background-repeat: no-repeat;
  background-size: 100% 100%;
  animation: tela-columnas calc(var(--tela-dur) / 4) steps(8) infinite;
  will-change: transform;
}
.tela-lista .tela-cuadros { background-image: url("./tela.webp"); }

@keyframes tela-columnas {
  from { transform: translateX(0); }
  to   { transform: translateX(-100%); }
}
@keyframes tela-filas {
  from { transform: translateY(0); }
  to   { transform: translateY(-100%); }
}

/* Fuera de cuadro no hay nada que animar: repintar un raster de 400 px que
   nadie ve es batería regalada. scroll.js pone y quita .tela-quieta. */
.tela-quieta .tela-tira, .tela-quieta .tela-cuadros { animation-play-state: paused; }

/* En pantalla angosta la pieza nunca pasa de 210 px, así que la hoja
   grande sería ancho de banda tirado: 328 KB en vez de 649. */
@media (max-width: 640px) {
  .tela-lista .tela-cuadros { background-image: url("./tela-360.webp"); }
}
/* Grano en mezcla normal: `mix-blend-mode: overlay` sobre un lienzo animado
   fuerza una capa de composición aparte y costaba ~6 fps. */
.layer-grain {
  z-index: 2; opacity: 0.035; pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}
.layer-vignette {
  z-index: 3; pointer-events: none;
  background:
    radial-gradient(118% 96% at 50% 48%, transparent 48%, rgba(4,7,14,0.62) 100%),
    linear-gradient(to bottom, rgba(6,10,20,0.72) 0%, transparent 16%);
}

/* ---------------- capa de texto ---------------- */
.copy-layer { position: absolute; inset: 0; z-index: 10; }

/* --out (0 dentro, 1 fuera) lo escribe scroll.js. El desplazamiento vive
   ACÁ y no en un transform en línea: el transform en línea pisaba el
   centrado del media query de móvil y sacaba los paneles de la pantalla. */
.beat {
  --out: 1;
  --dy: calc(var(--out) * 18px);
  position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, calc(-50% + var(--dy)));
  width: min(92vw, var(--maxw)); padding: 0 var(--pad);
  opacity: 0; pointer-events: none;
}
.beat.is-active { pointer-events: auto; }
/* El titular se ve desde el PRIMER pintado, sin esperar a ningun script.
   Antes todos los beats nacian en opacity:0 y el hero solo aparecia cuando
   scroll.js le escribia opacity:1 — y scroll.js corre despues de city.js,
   que arma 18.000 puntos antes de soltar el hilo. Medido con Lighthouse en
   movil: el LCP (este h1) era de 3,7 s y el 88 % era "render delay", texto
   listo esperando a que el hilo se desocupara. scroll.js escribe el mismo
   valor (a p=0 la ventana del hero da 1), asi que no hay parpadeo. */
.beat-hero { opacity: 1; --out: 0; pointer-events: auto; }

.eyebrow { text-transform: uppercase; letter-spacing: 0.22em; font-size: 0.72rem; font-weight: 600; color: var(--cf-ink-soft); margin: 0 0 0.9rem; }

/* Scrim del hero: el titular no puede pelear con los puntos del mundo.
   Es un velo suave anclado a la izquierda, no una caja. */
.beat-hero { position: absolute; }
/* Velo del hero.

   Tenía una línea vertical visible en el costado izquierdo, y la causa era
   aritmética: la caja medía 14vw hacia la izquierda desde el bloque de
   texto, pero el degradado tenía radio 60% de esa caja y solo llegaba a
   transparente en el 78% del radio. O sea que al filo izquierdo de la caja
   el velo todavía estaba a media opacidad, y ahí se cortaba en seco.

   La salida no es ajustar porcentajes hasta que cuadre —eso vuelve a
   romperse con otro ancho de ventana— sino separar las dos medidas: la
   caja se declara enorme y el degradado en unidades de viewport, de modo
   que llegue a transparente MUCHO antes del borde, en cualquier pantalla. */
.beat-hero::before {
  content: ''; position: absolute; z-index: -1;
  left: 50%; top: 50%;
  width: 260vw; height: 260vh;
  transform: translate(-50%, -50%);
  /* Centro corrido 18vw a la izquierda: el texto vive en esa mitad. Va en
     calc() y no en porcentaje porque el porcentaje se mide contra la caja
     de 260vw y no significaría nada legible.

     Medí las opciones contra dos cosas a la vez: el contraste del texto
     sobre el punto más claro del fondo, y cuánta luz le queda a la catedral
     —o sea, cuánta atmósfera se pierde—. El velo viejo dejaba la bajada en
     3,45:1, por debajo del mínimo. Este la sube a 4,63 y solo le quita un
     15% de luz a la escena; apretarlo más subía el contraste pero apagaba
     la calle. Si alguna vez hay que moverlo, el número es la opacidad del
     segundo tramo. */
  background: radial-gradient(58vw 54vh at calc(50% - 18vw) calc(50% + 2vh),
    rgba(4, 8, 17, 0.93) 0%, rgba(4, 8, 17, 0.70) 44%, transparent 78%);
  pointer-events: none;
}
.hero-title {
  font-family: 'Fraunces', Georgia, serif; font-weight: 600;
  font-size: clamp(2.6rem, 8vw, 6rem); line-height: 0.98; letter-spacing: -0.02em; margin: 0;
  text-wrap: balance;
}
.hero-sub { max-width: 34ch; margin: 1.4rem 0 0; font-size: clamp(1rem, 1.6vw, 1.25rem); color: var(--cf-ink-soft); line-height: 1.6; }
.pulse {
  display: flex; align-items: center; gap: 0.55rem;
  margin: 1.6rem 0 0; font-size: 0.86rem; color: var(--cf-ink-soft);
}
.pulse-txt { display: flex; flex-wrap: wrap; align-items: center; gap: 0.3rem 0.85rem; }
.pulse-num { color: var(--cf-ink); }
.pulse-num b { font-family: 'Fraunces', Georgia, serif; font-weight: 600; font-size: 1.15rem; color: #93b8fb; }
.pulse-dot {
  width: 8px; height: 8px; border-radius: 50%; background: var(--cf-emerald);
  box-shadow: 0 0 0 0 rgba(16,185,129,0.55); flex: 0 0 auto;
}
.pulse.is-live .pulse-dot { animation: latido 2.4s ease-out infinite; }
/* El respaldo y el dato en vivo se turnan: nunca se ven los dos, y si
   Supabase no responde el visitante lee la frase de siempre. */
.pulse-live { display: none; }
.pulse.is-live .pulse-txt { display: none; }
.pulse.is-live .pulse-live { display: inline; }
.pulse-live b { font-variant-numeric: tabular-nums; color: var(--cf-orange); }
@keyframes latido {
  0% { box-shadow: 0 0 0 0 rgba(16,185,129,0.5); }
  70% { box-shadow: 0 0 0 9px rgba(16,185,129,0); }
  100% { box-shadow: 0 0 0 0 rgba(16,185,129,0); }
}
@media (prefers-reduced-motion: reduce) { .pulse.is-live .pulse-dot { animation: none; } }

.scroll-hint { margin-top: 1.8rem; font-size: 0.8rem; color: var(--cf-ink-soft); letter-spacing: 0.04em; animation: bob 2.4s ease-in-out infinite; }
@keyframes bob { 0%,100%{transform:translateY(0)} 50%{transform:translateY(6px)} }

/* paneles narrativos: tarjeta glass, alineación variable */
/* Sin backdrop-filter a propósito. Un blur de fondo sobre un lienzo que se
   anima obliga a recomponer la zona borrosa en CADA cuadro: medido, se
   llevaba de 54 a 23 fps él solo. La tarjeta ya es casi opaca, así que el
   desenfoque no aportaba nada visible. El aire de vidrio se consigue con un
   degradado sutil y un filo claro arriba, que no cuestan nada. */
/* ---- los recuadros de texto: vidrio ---------------------------------
   Tres escalones, del más pobre al más rico, y cada uno es legible por sí
   solo. El texto nunca depende del efecto.

   1 · base — tarjeta sólida. Es lo que ve un navegador viejo, y lo que ve
       cualquiera con "más contraste" activado.
   2 · con backdrop-filter — el panel se vuelve translúcido y desenfoca lo
       que tiene detrás. Esto lo tienen Safari, Firefox y Chrome.
   3 · con backdrop-filter: url() — además refracta: las líneas del mundo
       se curvan al pasar por debajo. Hoy esto solo lo hace Chromium.

   El orden importa: si un navegador acepta la propiedad pero descarta la
   declaración con url(), se queda con el escalón 2 en vez de quedarse sin
   nada.                                                                */
.panel {
  width: min(92vw, 560px);
  background:
    linear-gradient(160deg, rgba(255,255,255,0.055), rgba(255,255,255,0) 42%),
    var(--cf-card);
  border: 1px solid var(--cf-card-border);
  border-radius: 18px; padding: clamp(1.3rem, 3vw, 2.1rem);
  box-shadow: 0 24px 60px -28px rgba(0,0,0,0.9), inset 0 1px 0 rgba(255,255,255,0.10);
}

@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .panel {
    /* Dos capas con trabajos distintos.

       El brillo es del vidrio: un barrido claro en diagonal, como la luz
       que resbala por un canto pulido.

       La mancha oscura es por legibilidad, y va concentrada en el centro
       —donde está el texto— y se abre hacia los bordes. Medí el contraste
       del cuerpo de texto contra el punto más claro del fondo en los cinco
       beats: con el vidrio parejo y claro caía a 1,9:1, y con el vidrio
       parejo y oscuro subía a 5:1 pero ya no se veía refractar nada. Así,
       el texto va sobre fondo oscuro y el canto sigue siendo vidrio. */
    background:
      linear-gradient(158deg, rgba(255,255,255,0.13), rgba(255,255,255,0.02) 46%, rgba(255,255,255,0.06)),
      radial-gradient(118% 124% at 50% 52%,
        rgba(6, 10, 20, 0.54) 0%, rgba(6, 10, 20, 0.52) 54%, rgba(6, 10, 20, 0.10) 100%),
      rgba(9, 15, 29, 0.08);
    border-color: rgba(200, 216, 240, 0.22);
    border-radius: 22px;
    /* El bisel: filo claro arriba, sombra abajo, y un halo interior muy
       tenue. Es lo que convierte un rectángulo borroso en una pieza con
       grosor — y es lo único de este bloque que funciona en todas partes. */
    box-shadow:
      0 24px 60px -28px rgba(0,0,0,0.9),
      inset 0 1px 0 rgba(255,255,255,0.30),
      inset 0 -1px 0 rgba(255,255,255,0.07),
      inset 0 0 26px rgba(255,255,255,0.045);
  }
  /* El filtro solo lo paga el panel que está en pantalla. Detrás de estos
     recuadros hay un lienzo que se repinta 60 veces por segundo, así que
     el navegador vuelve a filtrar el fondo en cada cuadro: con los cinco
     paneles filtrando a la vez —cuatro de ellos invisibles— se regalaban
     fotogramas. is-active la pone scroll.js. */
  .panel.is-active {
    -webkit-backdrop-filter: blur(10px) saturate(1.45) brightness(0.66);
    backdrop-filter: blur(10px) saturate(1.45) brightness(0.66);
  }
}

@supports (backdrop-filter: url("#x")) {
  /* Menos desenfoque acá a propósito: si se desenfoca mucho, la refracción
     no se ve — no queda nada nítido que curvar. */
  .panel.is-active {
    backdrop-filter: url("#vidrio-liquido") blur(3px) saturate(1.45) brightness(0.66);
  }
}

/* Quien pide más contraste no quiere un efecto: quiere leer. */
@media (prefers-contrast: more) {
  .panel, .panel.is-active {
    background: var(--cf-card);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}
.panel-left { left: clamp(1rem, 8vw, 12%); transform: translate(0, calc(-50% + var(--dy))); }
.panel-right { left: auto; right: clamp(1rem, 8vw, 12%); transform: translate(0, calc(-50% + var(--dy))); }
/* El beat de la solución baja a la franja inferior: arriba pasa el morph
   MapaFuria → CiudadFuria, que es el clímax del recorrido. Antes la tarjeta
   estaba centrada justo encima y lo tapaba por completo. */
.panel-center {
  left: 50%; top: 76%; transform: translate(-50%, calc(-50% + var(--dy)));
  text-align: center; width: min(92vw, 640px);
}

.panel h2 { font-family: 'Fraunces', Georgia, serif; font-weight: 600; font-size: clamp(1.6rem, 3.4vw, 2.5rem); line-height: 1.08; margin: 0 0 0.9rem; letter-spacing: -0.01em; }
.panel h2 i { color: var(--cf-primary); font-style: italic; }
.panel p { margin: 0 0 1rem; color: var(--cf-ink-soft); line-height: 1.65; font-size: clamp(0.95rem, 1.4vw, 1.08rem); }
.panel p b { color: var(--cf-ink); font-weight: 600; }

.stat-row { list-style: none; display: flex; flex-wrap: wrap; gap: 1.2rem 1.8rem; padding: 0; margin: 1.2rem 0; }
.stat-row li { display: flex; flex-direction: column; }
.stat-row b { font-family: 'Fraunces', serif; font-size: clamp(1.8rem, 4vw, 2.6rem); color: var(--cf-primary); line-height: 1; }
.stat-row span { font-size: 0.82rem; color: var(--cf-ink-soft); margin-top: 0.35rem; max-width: 16ch; }

blockquote { margin: 1.2rem 0 0.6rem; padding-left: 1rem; border-left: 3px solid var(--cf-orange); font-family: 'Fraunces', serif; font-style: italic; font-size: 1.1rem; color: var(--cf-ink); }
/* Sube de #98a6bd: sobre el vidrio, la letra chica de la fuente era la
   única que no llegaba a 4,5:1 contra el punto más claro del fondo. */
.note { font-size: 0.78rem !important; color: #afbacc !important; margin-top: 0.9rem !important; }

.cycle { counter-reset: step; list-style: none; padding: 0; margin: 1.2rem 0 0; display: flex; flex-direction: column; gap: 0.7rem; }
.cycle li { counter-increment: step; display: flex; align-items: center; gap: 0.7rem; color: var(--cf-ink); font-weight: 500; }
.cycle li::before { content: counter(step); flex: 0 0 auto; width: 26px; height: 26px; border-radius: 50%; background: var(--cf-primary); color: #fff; display: grid; place-items: center; font-size: 0.85rem; font-weight: 700; }

/* ---------------- apoyo: el cierre del recorrido ----------------
   Misma composición que el final del video para redes. El resplandor
   va con `radial-gradient` y no con una sombra: tiene que leerse como
   que la luz viene del recorrido que acaba de terminar arriba, no como
   un borde de tarjeta. */
.apoyo {
  position: relative; z-index: 1;
  /* El resplandor arranca en cero arriba: si nace encendido en el borde,
     se lee como el techo de otra caja y la costura vuelve a aparecer. */
  /* Traslúcido: arriba recibe el azul noche del recorrido y a los pocos
     centímetros ya deja ver la red de puntos del tramo final. */
  background:
    linear-gradient(var(--cf-bg) 0, rgba(11, 18, 32, 0.82) 10%, rgba(9, 9, 29, 0) 36%),
    radial-gradient(90% 70% at 30% 40%, rgba(59, 130, 246, 0.10), transparent 62%);
  padding: clamp(3.5rem, 9vw, 6.5rem) 0 clamp(3.5rem, 9vw, 6.5rem);
}
/* El recorrido se funde en esta sección en vez de cortarse: antes el
   escenario terminaba en seco y una línea marcaba la costura. El velo sube
   por encima del final del lienzo y lo lleva al mismo azul noche. */
.apoyo::before {
  content: ''; position: absolute; left: 0; right: 0; bottom: 100%;
  height: clamp(120px, 26vh, 260px); pointer-events: none;
  background: linear-gradient(rgba(11, 18, 32, 0), var(--cf-bg) 92%);
}
.apoyo-in {
  max-width: var(--maxw); margin: 0 auto; padding: 0 var(--pad);
  display: grid; grid-template-columns: minmax(0, 1.45fr) minmax(0, 1fr);
  gap: clamp(2rem, 6vw, 5rem); align-items: end;
}
.apoyo-caja { min-width: 0; }
.apoyo h2 {
  font-family: 'Fraunces', serif; font-weight: 700;
  font-size: clamp(1.9rem, 4.4vw, 3.1rem); line-height: 1.08;
  letter-spacing: -0.015em; margin: 0 0 0.8rem;
  max-width: 23ch; text-wrap: balance;
}
.apoyo h2 em { font-style: normal; color: #6ea2ff; }
.apoyo-sub { color: var(--cf-ink-soft); margin: 0 0 1.8rem; font-size: 1.05rem; }

.btn-apoyo {
  display: inline-flex; align-items: center; gap: 0.55rem;
  background: #2563eb; color: #fff; font-weight: 700; font-size: 1rem;
  text-decoration: none; padding: 0.82rem 1.5rem; border-radius: 12px;
  border-bottom: 3px solid #1d4ed8;
  transition: transform 0.15s ease, filter 0.2s ease;
}
.btn-apoyo svg { width: 18px; height: 18px; }
.btn-apoyo:hover { filter: brightness(1.08); }
.btn-apoyo:active { transform: translateY(1px); border-bottom-width: 1px; }
.btn-apoyo:focus-visible { outline: 2px solid #fff; outline-offset: 3px; }

/* ---------------- cierre: capacidades + contacto ---------------- */
.tramo-final { position: relative; z-index: 1; isolation: isolate; background: #09091D; }
.closing { position: relative; z-index: 1; padding: clamp(3rem, 8vw, 6rem) 0 0; overflow: clip; }
/* Fondo "Particle Drift" (fondo-contacto.js). El lienzo va detrás de todo
   el contenido de la sección; el degradado de arriba cose el azul noche de
   la sección anterior con el #09091D del fondo, para que no haya corte. */
.fondo-deriva { position: absolute; inset: 0; width: 100%; height: 100%; z-index: -2; display: block; opacity: 0; transition: opacity 1.2s ease; }
.fondo-deriva.listo { opacity: 1; }
.closing-head { max-width: var(--maxw); margin: 0 auto; padding: 0 var(--pad); }
.closing-head h2 { font-family: 'Fraunces', serif; font-weight: 600; font-size: clamp(1.8rem, 4vw, 3rem); margin: 0 0 0.6rem; letter-spacing: -0.01em; }
.closing-head p { color: var(--cf-ink-soft); margin: 0; font-size: 1.05rem; }

.rail { max-width: var(--maxw); margin: clamp(1.5rem, 4vw, 2.5rem) auto 0; padding: 0 var(--pad); display: flex; align-items: stretch; gap: 0.6rem; }
.rail-track { list-style: none; display: flex; gap: 1rem; overflow-x: auto; scroll-snap-type: x mandatory; padding: 0.4rem 0.2rem 1.2rem; margin: 0; flex: 1; scrollbar-width: thin; }
.rail-track::-webkit-scrollbar { height: 6px; }
.rail-track::-webkit-scrollbar-thumb { background: rgba(59,130,246,0.35); border-radius: 6px; }
.rail-track:focus-visible { outline: 2px solid var(--cf-primary); outline-offset: 4px; border-radius: 12px; }
/* El ancho va en el <li>, que es el hijo real del flex. Estaba en el <a> y
   las tarjetas quedaban aplastadas a ~160 px con el texto en dos palabras. */
.rail-track > li {
  flex: 0 0 clamp(238px, 26vw, 292px);
  scroll-snap-align: start; display: flex;
}
.rail-card {
  flex: 1; background: var(--cf-bg-2); border: 1px solid var(--cf-line); border-radius: var(--radius);
  padding: 1.5rem 1.4rem; text-decoration: none; color: var(--cf-ink); display: flex; flex-direction: column; gap: 0.5rem;
  transition: transform 0.2s ease, border-color 0.2s ease, background 0.2s ease;
}
.rail-card:hover { transform: translateY(-4px); border-color: rgba(59,130,246,0.5); background: #111c33; }
.rail-card:focus-visible { outline: 2px solid var(--cf-primary); outline-offset: 3px; }
.rail-card .ico {
  width: 38px; height: 38px; border-radius: 10px; display: grid; place-items: center;
  background: rgba(59,130,246,0.14); color: #93b8fb; flex: 0 0 auto;
}
.rail-card .ico svg { width: 19px; height: 19px; }
.rail-card h3 { margin: 0.45rem 0 0; font-size: 1.05rem; font-weight: 600; letter-spacing: -0.01em; }
.rail-card p { margin: 0; font-size: 0.87rem; color: var(--cf-ink-soft); line-height: 1.55; }
.rail-btn { flex: 0 0 auto; align-self: center; width: 40px; height: 40px; border-radius: 50%; background: var(--cf-bg-2); border: 1px solid var(--cf-line); color: var(--cf-ink); font-size: 1.3rem; cursor: pointer; }
.rail-btn:hover { border-color: var(--cf-primary); }
.rail-btn:focus-visible { outline: 2px solid var(--cf-primary); outline-offset: 2px; }

.contact { max-width: var(--maxw); margin: clamp(2.5rem, 6vw, 4.5rem) auto 0; padding: 0 var(--pad); display: grid; grid-template-columns: 1fr 1fr; gap: clamp(1.5rem, 4vw, 3rem); align-items: start; }
.contact h2 { font-family: 'Fraunces', serif; font-weight: 600; font-size: clamp(1.6rem, 3.4vw, 2.4rem); margin: 0 0 0.6rem; }
.contact p { color: var(--cf-ink-soft); line-height: 1.6; margin: 0; }
.contact-form { display: flex; flex-direction: column; gap: 0.8rem; }
.contact-form label { display: flex; flex-direction: column; gap: 0.35rem; font-size: 0.82rem; color: var(--cf-ink-soft); }
.contact-form input, .contact-form textarea {
  background: var(--cf-bg-2); border: 1px solid var(--cf-line); border-radius: 10px; color: var(--cf-ink);
  padding: 0.7rem 0.9rem; font-family: inherit; font-size: 0.95rem; resize: vertical;
}
.contact-form input:focus, .contact-form textarea:focus { outline: none; border-color: var(--cf-primary); box-shadow: 0 0 0 3px rgba(59,130,246,0.18); }
.btn-submit { align-self: flex-start; margin-top: 0.3rem; }
.contact-alt { font-size: 0.85rem; margin-top: 0.4rem; }

/* ---- invitación al video y su visor ---- */
.invita-video {
  margin-top: 1.4rem; display: flex; flex-wrap: wrap; align-items: center;
  gap: 0.6rem 0.8rem; font-size: 0.98rem; color: var(--cf-ink-soft);
}
.ver-video {
  display: inline-flex; align-items: center; gap: 0.45rem;
  font: inherit; font-size: 0.92rem; font-weight: 600; color: #cfe0ff;
  background: rgba(37, 99, 235, 0.20);
  border: 1px solid rgba(110, 162, 255, 0.55);
  border-radius: 10px; padding: 0.5rem 0.95rem; cursor: pointer;
  transition: background 0.18s ease, border-color 0.18s ease;
}
.ver-video svg { width: 15px; height: 15px; display: block; }
.ver-video:hover { background: rgba(37, 99, 235, 0.34); border-color: rgba(110, 162, 255, 0.85); }
.ver-video:focus-visible { outline: 2px solid #fff; outline-offset: 3px; }
/* El peso va escrito: quien está con datos móviles decide antes de tocar,
   no después de que empezó a bajar. */
.dato-video { font-size: 0.78rem; opacity: 0.65; width: 100%; }

.visor {
  border: 0; padding: 0; background: transparent;
  max-width: min(94vw, 420px); max-height: 92vh;
  color: var(--cf-ink);
}
.visor::backdrop { background: rgba(4, 8, 17, 0.86); backdrop-filter: blur(6px); }
.visor-barra {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: 0 0.2rem 0.7rem;
}
.visor-barra h2 {
  margin: 0; font-family: 'Fraunces', Georgia, serif; font-weight: 600;
  font-size: 1.05rem; color: var(--cf-ink);
}
.visor-cerrar {
  font: inherit; font-size: 1rem; line-height: 1; color: var(--cf-ink-soft);
  background: rgba(255, 255, 255, 0.06); border: 1px solid var(--cf-line);
  border-radius: 50%; width: 34px; height: 34px; cursor: pointer; flex: 0 0 auto;
}
.visor-cerrar:hover { color: var(--cf-ink); background: rgba(255, 255, 255, 0.12); }
.visor-cerrar:focus-visible { outline: 2px solid #fff; outline-offset: 3px; }
.visor-pie {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.35rem 0.6rem;
  margin: 0.7rem 0.2rem 0; font-size: 0.82rem; color: var(--cf-ink-soft);
}
.visor-pie span { opacity: 0.6; font-size: 0.76rem; }
.visor-completo {
  font: inherit; font-size: 0.82rem; font-weight: 600; color: #a8c7ff;
  background: none; border: 0; padding: 0; cursor: pointer;
  text-decoration: underline; text-underline-offset: 3px;
}
.visor-completo:hover { color: #cfe0ff; }
.visor-completo:focus-visible { outline: 2px solid #fff; outline-offset: 3px; border-radius: 3px; }

.visor video {
  display: block; width: 100%; height: auto; max-height: 82vh;
  border-radius: 14px; border: 1px solid var(--cf-card-border);
  background: #000;
}
.contact-alt a { color: var(--cf-primary); }

.site-footer { max-width: var(--maxw); margin: clamp(3rem, 7vw, 5rem) auto 0; padding: 2rem var(--pad) 3rem; border-top: 1px solid var(--cf-line); color: var(--cf-ink-soft); font-size: 0.85rem; display: flex; justify-content: space-between; flex-wrap: wrap; gap: 0.6rem; }
.site-footer a { color: var(--cf-primary); }
.credito { width: 100%; margin: 0.4rem 0 0; font-size: 0.78rem; opacity: 0.75; }

/* ---------------- responsive ---------------- */
@media (max-width: 760px) {
  .panel-left, .panel-right, .panel-center {
    left: 50%; right: auto; top: 50%;
    transform: translate(-50%, calc(-50% + var(--dy)));
    width: min(92vw, 460px);
  }
  .panel-center { top: 62%; }
  /* En una pantalla angosta no hay izquierda ni derecha: el mundo y el
     titular se pisan. El hero baja por debajo del horizonte del canvas
     (54%) y se lleva su propio velo a todo el ancho. */
  .beat-hero { top: 64%; }
  /* Misma caja holgada; solo cambia la forma del degradado, más ancho y
     más bajo porque en móvil el hero vive en la franja de abajo. */
  .beat-hero::before {
    background: radial-gradient(78vw 42vh at 50% 46%,
      rgba(4, 8, 17, 0.95) 0%, rgba(4, 8, 17, 0.88) 44%, transparent 80%);
  }
  .scroll-hint { margin-top: 1.6rem; }
  .contact { grid-template-columns: 1fr; }
  .beat-nav { display: none; }
  .hero-sub { max-width: 90vw; }
  .hero-title { font-size: clamp(2.3rem, 11vw, 3.4rem); }
  .stat-row { gap: 1rem 1.4rem; }
  .rail-btn { display: none; }          /* en táctil se desliza con el dedo */
}

/* ---------------- reduced motion: mundo estático, contenido en flujo ---------------- */
@media (prefers-reduced-motion: reduce) {
  .cinematic { height: auto; }
  .stage { position: relative; height: auto; min-height: 0; overflow: visible; padding: 6rem 0; }
  .layer-world { display: none; }
  .layer-tela { display: none; }
  .layer-vignette { display: none; }
  .copy-layer { position: static; }
  .beat { position: relative; left: auto; top: auto; transform: none; opacity: 1; pointer-events: auto;
    width: min(92vw, var(--maxw)); margin: 0 auto 4rem; }
  .panel { margin: 0 auto 4rem; }
  .scroll-hint { display: none; }
  .beat-nav { display: none; }
  .beat-hero::before { display: none; }
}


/* ---------------- preferencia de contraste alto ---------------- */
@media (prefers-contrast: more) {
  :root {
    --cf-ink-soft: #d3dcea;
    --cf-line: rgba(200, 214, 232, 0.34);
    --cf-card: rgba(6, 10, 20, 0.95);
    --cf-card-border: rgba(200, 214, 232, 0.45);
  }
  .note { color: #c2ccdb !important; }
  .layer-grain, .layer-vignette { display: none; }
  /* Con la caja de 260vw un color plano taparía el escenario entero: acá
     también tiene que ser un degradado acotado, solo que más opaco. */
  .beat-hero::before {
    background: radial-gradient(46vw 40vh at 44% 50%,
      rgba(4, 8, 17, 0.97) 0%, rgba(4, 8, 17, 0.93) 56%, transparent 82%);
  }
}

/* enlaces a las páginas de ciudad */
.footer-ciudad { width: 100%; display: flex; flex-wrap: wrap; gap: 0.4rem 1.4rem; padding-top: 0.6rem; }
.footer-ciudad a { color: var(--cf-ink-soft); }
.footer-ciudad a:hover { color: var(--cf-ink); }

/* ============================================================
   Revisión de diseño (septiembre 2026). Todo lo de abajo es del tramo
   que viene después del recorrido: el escenario no se tocó.
   ============================================================ */

/* Titulares sin viudas; párrafos sin una palabra sola en la última línea. */
.closing-head h2, .contact h2, .panel h2 { text-wrap: balance; }
.panel p, .closing-head p, .contact p, .apoyo-sub, .rail-card p { text-wrap: pretty; }

/* ---- cabecera sólida fuera del escenario (la pone pagina.js) ---- */
.site-header {
  transition: background-color 0.35s ease, box-shadow 0.35s ease, padding 0.35s ease;
}
.site-header.is-solida {
  background: rgba(11, 18, 32, 0.78);
  box-shadow: 0 1px 0 var(--cf-line), 0 10px 30px -18px rgba(2, 6, 16, 0.9);
  -webkit-backdrop-filter: blur(12px) saturate(1.2);
  backdrop-filter: blur(12px) saturate(1.2);
}
/* Sobre el escenario la música va debajo de Acceder; con la barra sólida
   se pone al lado para que la barra no quede de dos pisos. */
.site-header.is-solida .acciones { flex-direction: row-reverse; align-items: center; }
@media (prefers-reduced-motion: reduce) { .site-header { transition: none; } }

/* ---- apoyo: la otra mitad ---- */
.apoyo-otras {
  min-width: 0; padding-left: clamp(1.2rem, 2.5vw, 2rem);
  border-left: 1px solid var(--cf-line);
}
.apoyo-otras-titulo {
  margin: 0 0 1rem; font-size: 0.8rem; font-weight: 600; letter-spacing: 0.02em;
  color: var(--cf-ink-soft);
}
.apoyo-otras ol { list-style: none; margin: 0; padding: 0; display: grid; gap: 1.35rem; counter-reset: otra; }
.apoyo-otras li { counter-increment: otra; display: grid; grid-template-columns: 2rem 1fr; column-gap: 0.4rem; }
.apoyo-otras li::before {
  content: counter(otra, decimal-leading-zero); grid-row: span 2;
  font-family: 'Fraunces', Georgia, serif; font-weight: 600; font-size: 1.05rem; line-height: 1.35;
  color: #fdba74; font-variant-numeric: tabular-nums;
}
.apoyo-otras a {
  font-weight: 600; font-size: 1.05rem; color: var(--cf-ink); text-decoration: none;
  background: linear-gradient(currentColor, currentColor) 0 100% / 0 1px no-repeat;
  transition: background-size 0.25s ease, color 0.2s ease;
  justify-self: start;
}
.apoyo-otras a:hover { color: #fff; background-size: 100% 1px; }
.apoyo-otras a:focus-visible { outline: 2px solid #fff; outline-offset: 3px; border-radius: 3px; }
.apoyo-otras span { color: var(--cf-ink-soft); font-size: 0.92rem; line-height: 1.55; margin-top: 0.2rem; }

/* ---- carrusel de módulos ---- */
.rail-card { position: relative; }
.rail-num {
  position: absolute; top: 1.35rem; right: 1.3rem;
  font-family: 'Fraunces', Georgia, serif; font-weight: 600; font-size: 0.85rem;
  color: rgba(174, 185, 204, 0.55); font-variant-numeric: tabular-nums;
}
.rail-card p { flex: 1; }
.rail-card:active { transform: translateY(-1px) scale(0.99); }
.rail-btn { transition: border-color 0.2s ease, opacity 0.2s ease, transform 0.15s ease; }
.rail-btn:active:not(:disabled) { transform: scale(0.94); }
.rail-btn:disabled { opacity: 0.35; cursor: default; border-color: var(--cf-line); }

/* ---- contacto: el formulario es una pieza, no campos sueltos ---- */
.contact-form {
  padding: clamp(1.3rem, 3vw, 2rem);
  border-radius: 22px;
  background:
    linear-gradient(158deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.015) 46%, rgba(255, 255, 255, 0.04)),
    rgba(9, 15, 29, 0.78);
  border: 1px solid rgba(200, 216, 240, 0.18);
  box-shadow: 0 24px 60px -30px rgba(2, 6, 16, 0.95), inset 0 1px 0 rgba(255, 255, 255, 0.16);
}
.contact-form label { font-size: 0.86rem; font-weight: 500; color: #c4cede; }
.contact-form input, .contact-form textarea {
  background: rgba(4, 8, 17, 0.55); border-color: rgba(174, 185, 204, 0.22);
  transition: border-color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
}
.contact-form input:hover, .contact-form textarea:hover { border-color: rgba(174, 185, 204, 0.4); }
/* :user-invalid solo marca lo que la persona ya tocó; no pinta de rojo
   un formulario vacío apenas carga. */
.contact-form input:user-invalid, .contact-form textarea:user-invalid {
  border-color: #fb923c; box-shadow: 0 0 0 3px rgba(251, 146, 60, 0.16);
}
.btn-submit { padding-inline: 1.6rem; }
.form-ok {
  margin: 0 0 0.4rem; padding: 0.8rem 1rem; border-radius: 12px;
  background: rgba(16, 185, 129, 0.12); border: 1px solid rgba(16, 185, 129, 0.4);
  color: #a7f3d0; font-size: 0.92rem; line-height: 1.5;
}
.contact > div:first-child { padding-top: clamp(0rem, 1.5vw, 1rem); }

/* ---- pie ---- */
.pie-marca { display: flex; align-items: center; gap: 0.7rem; margin: 0; }
.pie-marca img { display: block; height: 22px; width: auto; }
.pie-marca span::before { content: '·'; margin-right: 0.7rem; opacity: 0.6; }

@media (max-width: 760px) {
  .apoyo-in { grid-template-columns: 1fr; align-items: start; }
  .apoyo-otras { padding-left: 0; border-left: 0; padding-top: 1.6rem; border-top: 1px solid var(--cf-line); }
}
@media (prefers-contrast: more) {
  .site-header.is-solida { background: rgba(6, 10, 20, 0.97); }
  .contact-form { background: var(--cf-card); }
}

/* Al llegar por un ancla (#contacto, o de vuelta del formulario), el
   titular no queda escondido debajo de la barra sólida. */
#apoyo, #capacidades, #contacto { scroll-margin-top: 84px; }

/* ---- beat «Lo que ya funciona»: los tres modos de MapaFuria ---- */
.panel .eyebrow { margin-bottom: 0.7rem; }
.modos { list-style: none; margin: 1.1rem 0 0; padding: 0; display: grid; gap: 0.55rem; }
.modos li { display: grid; grid-template-columns: 7.2rem 1fr; column-gap: 0.6rem; align-items: baseline; font-size: 0.95rem; color: var(--cf-ink-soft); }
.modos b { font-weight: 600; white-space: nowrap; }
.modos b::before { content: ''; display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 0.5rem; vertical-align: 0.08em; background: currentColor; }
.m-rep { color: #34d399; }
.m-com { color: #a5b4fc; }
.m-not { color: #e79ccf; }

/* =====================================================================
   SESIÓN 30 · El satén de día, la noche al final
   ---------------------------------------------------------------------
   El fondo del recorrido dejó de ser la plancha azul oscura: ahora es la
   tela satinada de los videos (escena.js, Three.js) teñida por la hora.
   El amanecer y el día son claros, así que el texto del escenario tiene
   dos temas y city.js los alterna según la luz del satén:
     html[data-tema="claro"]  → tinta pizarra sobre vidrio claro
     html[data-tema="oscuro"] → lo de siempre, letra clara sobre vidrio
   Solo se re-tiñe el escenario, el header y la barra lateral: el tramo
   final (apoyo, capacidades, contacto) sigue siendo noche, que es justo
   como termina el recorrido.
   ===================================================================== */

/* Respaldo sin WebGL: el mismo degradado del satén, que city.js mueve con
   la hora. Los valores por defecto son los del amanecer, así el primer
   pintado ya es claro y no hay destello oscuro. */
:root { --cielo-a: rgb(214, 204, 198); --cielo-b: rgb(190, 180, 176); }
.layer-atmos {
  background:
    radial-gradient(70% 40% at 50% 54%, rgba(255, 255, 255, 0.28), transparent 72%),
    linear-gradient(to bottom, var(--cielo-a) 0%, var(--cielo-a) 40%, var(--cielo-b) 100%);
  transition: background 0.4s linear;
}
.layer-satin {
  position: absolute; inset: 0; z-index: 0;
  width: 100%; height: 100%; display: block;
  opacity: 0; transition: opacity 0.7s ease;
}
.layer-satin.listo { opacity: 1; }

/* El hero pasa al costado derecho: la iglesia entra por la izquierda. */
.hero-caja { margin-left: auto; width: min(100%, 42rem); }
.hero-title { font-size: clamp(2.6rem, 5.2vw, 4.6rem); }
.beat-hero::before {
  background: radial-gradient(56vw 56vh at calc(50% + 20vw) calc(50% + 2vh),
    rgba(4, 8, 17, 0.93) 0%, rgba(4, 8, 17, 0.70) 44%, transparent 78%);
}

/* Logo del header: positivo de día, negativo de noche. */
.brand-logo-dia { display: none; }

/* Los cambios de tema no saltan: se funden. */
.panel, .hero-title, .hero-sub, .eyebrow, .pulse, .scroll-hint,
.btn-sonido, .beat-nav button, .beat-nav .dot {
  transition: color 0.6s ease, background-color 0.6s ease, border-color 0.6s ease, box-shadow 0.6s ease;
}

@media (prefers-reduced-motion: no-preference) {
  [data-tema="claro"] .stage,
  [data-tema="claro"] .site-header,
  [data-tema="claro"] .beat-nav {
    --cf-ink: #1e293b;
    --cf-ink-soft: #475569;
    --cf-line: rgba(30, 41, 59, 0.14);
    --cf-card: rgba(255, 255, 255, 0.90);
    --cf-card-border: rgba(255, 255, 255, 0.92);
  }
  [data-tema="claro"] .hero-title { color: #1e293b; }
  [data-tema="claro"] .beat-hero::before {
    background: radial-gradient(56vw 56vh at calc(50% + 20vw) calc(50% + 2vh),
      rgba(248, 250, 252, 0.80) 0%, rgba(248, 250, 252, 0.52) 44%, transparent 78%);
  }
  [data-tema="claro"] .pulse-num b { color: #2563eb; }
  [data-tema="claro"] .pulse-live b { color: #c2410c; }
  [data-tema="claro"] .brand-logo-dia { display: block; }
  [data-tema="claro"] .brand-logo-noche { display: none; }
  [data-tema="claro"] .btn-sonido { background: rgba(255, 255, 255, 0.62); }
  [data-tema="claro"] .btn-sonido.suena { color: #1d4ed8; background: rgba(219, 234, 254, 0.85); border-color: rgba(37, 99, 235, 0.45); }
  [data-tema="claro"] .btn-sonido:focus-visible,
  [data-tema="claro"] .btn-access:focus-visible { outline-color: #1e293b; }
  [data-tema="claro"] .note { color: #64748b !important; }
  [data-tema="claro"] .stat-row b,
  [data-tema="claro"] .panel h2 i { color: #2563eb; }
  [data-tema="claro"] .layer-vignette {
    background:
      radial-gradient(118% 96% at 50% 48%, transparent 55%, rgba(30, 41, 59, 0.16) 100%),
      linear-gradient(to bottom, rgba(255, 255, 255, 0.30) 0%, transparent 14%);
  }
  /* Vidrio claro: blanco lechoso, borde blanco y sombra pizarra suave.
     Mismo esquema de tres escalones que el vidrio oscuro. */
  [data-tema="claro"] .panel {
    background: linear-gradient(158deg, rgba(255, 255, 255, 0.94), rgba(255, 255, 255, 0.82));
    border-color: rgba(255, 255, 255, 0.95);
    box-shadow: 0 26px 60px -30px rgba(30, 41, 59, 0.45), inset 0 1px 0 #fff;
  }
  @supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    [data-tema="claro"] .panel {
      background:
        linear-gradient(158deg, rgba(255, 255, 255, 0.70), rgba(255, 255, 255, 0.46) 46%, rgba(255, 255, 255, 0.58)),
        radial-gradient(118% 124% at 50% 52%, rgba(255, 255, 255, 0.40) 0%, rgba(255, 255, 255, 0.30) 54%, rgba(255, 255, 255, 0.06) 100%);
    }
    [data-tema="claro"] .panel.is-active {
      -webkit-backdrop-filter: blur(14px) saturate(1.25) brightness(1.06);
      backdrop-filter: blur(14px) saturate(1.25) brightness(1.06);
    }
  }
  @supports (backdrop-filter: url("#x")) {
    [data-tema="claro"] .panel.is-active {
      backdrop-filter: url("#vidrio-liquido") blur(5px) saturate(1.25) brightness(1.06);
    }
  }
}

@media (max-width: 760px) {
  .hero-caja { width: 100%; }
  .hero-title { font-size: clamp(2.3rem, 11vw, 3.4rem); }
  .beat-hero::before {
    background: radial-gradient(78vw 42vh at 50% 46%,
      rgba(4, 8, 17, 0.95) 0%, rgba(4, 8, 17, 0.88) 44%, transparent 80%);
  }
}
@media (max-width: 760px) and (prefers-reduced-motion: no-preference) {
  [data-tema="claro"] .beat-hero::before {
    background: radial-gradient(78vw 42vh at 50% 46%,
      rgba(248, 250, 252, 0.88) 0%, rgba(248, 250, 252, 0.72) 44%, transparent 80%);
  }
}
@media (prefers-reduced-motion: reduce) {
  .layer-satin { display: none; }
  .layer-atmos { background: var(--cf-bg); }
}
/* El texto del escenario sigue al tema (los h2 heredaban el color del body). */
.copy-layer { color: var(--cf-ink); }

/* =====================================================================
   SESIÓN 30 · El tramo final es la misma noche del recorrido
   ---------------------------------------------------------------------
   El recorrido termina en el satén de noche (≈ #262d41, con bruma
   azulada en el horizonte) y el tramo final arrancaba en otro azul,
   #09091D, casi negro. Ahora apoyo, capacidades, contacto y pie usan el
   mismo satén de noche: la bruma cose la costura con el escenario y sigue
   flotando por la sección, detrás de la red de puntos.
   El fondo es CSS puro: si la red de puntos (WebGL) no arranca en algún
   teléfono, la sección se ve igual de noche, nunca en blanco.
   ===================================================================== */
:root {
  --noche: #252c40;          /* el satén a p = 1 (city.js · CIELO) */
  --noche-honda: #1b2133;    /* un poco más hondo, al pie de la página */
  --bruma: 104, 116, 150;    /* la bruma del horizonte, en rgb */
  --cf-bg: var(--noche);
  --cf-bg-2: rgba(20, 26, 42, 0.74);
}
.tramo-final {
  color-scheme: dark;
  overflow-x: clip;
  background: linear-gradient(to bottom, var(--noche) 0%, var(--noche) 45%, var(--noche-honda) 100%);
}
/* La bruma: bandas anchas y suaves, como la del satén. Va debajo de la
   red de puntos (z -3 < -2) y encima del color de fondo. */
.tramo-final::before {
  content: ''; position: absolute; z-index: -3; pointer-events: none;
  top: 0; bottom: 0; left: -8%; right: -8%;
  background:
    radial-gradient(70% 220px at 68% 0, rgba(var(--bruma), 0.34), transparent 72%),
    radial-gradient(60% 9% at 22% 16%, rgba(var(--bruma), 0.20), transparent 70%),
    radial-gradient(75% 8% at 78% 38%, rgba(var(--bruma), 0.16), transparent 70%),
    radial-gradient(65% 9% at 30% 63%, rgba(var(--bruma), 0.14), transparent 70%),
    radial-gradient(80% 10% at 70% 88%, rgba(var(--bruma), 0.12), transparent 70%),
    linear-gradient(112deg, transparent 32%, rgba(150, 160, 200, 0.045) 46%, transparent 60%);
}
@media (min-width: 761px) and (prefers-reduced-motion: no-preference) {
  .tramo-final::before { animation: bruma-deriva 46s ease-in-out infinite alternate; }
}
@keyframes bruma-deriva {
  from { transform: translate3d(-4%, 0, 0); }
  to   { transform: translate3d(4%, 0, 0); }
}
/* La costura: el velo que sube sobre el final del escenario ya no oscurece
   hacia otro azul; lleva el satén a su propio tono y deja una banda de
   bruma justo en el borde, como el horizonte del recorrido. */
.apoyo {
  background: radial-gradient(90% 70% at 30% 40%, rgba(59, 130, 246, 0.08), transparent 62%);
}
.apoyo::before {
  background:
    radial-gradient(80% 60% at 66% 100%, rgba(var(--bruma), 0.30), transparent 72%),
    linear-gradient(rgba(37, 44, 64, 0), var(--noche) 94%);
}
/* Tarjetas, flechas y campos: vidrio de noche, como los paneles oscuros. */
.rail-card, .rail-btn, .contact-form input, .contact-form textarea {
  background: var(--cf-bg-2);
  border-color: rgba(174, 185, 204, 0.18);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
}
.rail-card:hover { background: rgba(28, 36, 58, 0.86); }
.site-header.is-solida { background: rgba(33, 40, 58, 0.80); }
