/* ============================================================
   footnote — Home · v2
   Todo lo afinable vive en :root. Las medidas de layout están en
   "px del Figma": 1 --u = 1 px del frame «MacBook Pro 14' - 9»
   (1512×982). Comparar un número de aquí con el Figma es directo:
   calc(320 * var(--u)) es el título de 320 px.
   El JS solo LEE estos tokens (motor, ritmos, tamaños de tarjeta).
   ============================================================ */

@font-face {
  font-family: "Sharp Grotesk Black 15";
  src: url("../fonts/sharp-grotesk-black15.woff2") format("woff2");
  font-weight: 400;
  font-display: block; /* el título ES el diseño: mejor esperar que un FOUT de 300px */
}
/* Corte Medium de la misma familia condensada: etiquetas de servicio y
   texto de apoyo. Mismo cap-height que la Black (0.72 del em). */
@font-face {
  font-family: "Sharp Grotesk Medium 15";
  src: url("../fonts/sharp-grotesk-medium15.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}
/* Anton se conserva como respaldo: si el archivo de Sharp Grotesk faltara
   el título degrada a algo igual de condensado y no a la fuente del sistema. */
@font-face {
  font-family: "Anton";
  src: url("../fonts/anton.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: "Geist Mono";
  src: url("../fonts/geist-mono.woff2") format("woff2");
  font-weight: 300 700;
  font-display: swap;
}
@font-face {
  font-family: "Instrument Sans";
  src: url("../fonts/instrument-sans.woff2") format("woff2");
  font-weight: 400 700;
  font-display: swap;
}

:root {
  /* — marca — */
  --orange: #FF6022;
  --green: #CCFB43;
  --white: #FFFFFF;
  --black: #0B0B0B;
  --ink-deep: #0E0E0E;  /* extremo oscuro del degradado izquierdo del Figma */

  /* — ESCALA — una sola perilla para el tamaño de TODO: 1 = como está,
     0.9 = 10 % más pequeño, 1.1 = 10 % más grande. */
  --scale: 1;

  /* — UNIDAD — 1 --u = 1 px de un marco de referencia de 1512 de ancho. Se
     escala con el ancho y con el ALTO: el Figma es un marco de 982 px sin
     barras del navegador, pero una ventana real de laptop mide ~780 de alto,
     así que la referencia de altura es 920 y el titular se queda en ~15 %
     del alto en cualquier ventana (a la escala del Figma llegaba al 28 %). */
  --vh: 1vh;
  --u: calc(min(calc(100vw / 1512), calc(var(--vh) * 100 / 920)) * var(--scale));

  /* — retícula — */
  --gutter: max(20px, calc(28 * var(--u)));  /* logo, título, wordmark: mismo riel */
  --edge-top: calc(22 * var(--u));           /* cima del logo */
  --edge-bottom: max(16px, calc(26 * var(--u)));   /* borde inferior del wordmark */
  /* Antes esto era un solo max(150px, 150·u): fijo con --u, que combina ancho
     Y alto (min de los dos). En una ventana ancha pero de la altura del Figma
     (982) o más alta, --u se topaba en 1 por el ANCHO — así que toda la altura
     de más (982→1200, por ejemplo) no movía este número ni un píxel: se iba
     entera como aire vacío arriba de las etiquetas, sin usarse ("aprovechemos
     mejor el alto de la pantalla", pedido explícito). Y en una ventana BAJA,
     --u caía con la altura, encogiendo título/etiquetas/link, pero el piso de
     150px no los seguía — el hueco entre la línea y el pie se disparaba de
     ~19px a ~34px en vez de encogerse junto con el resto (la sensación de
     "espacios irregulares" del pedido: el mismo hueco se ve angosto en una
     ventana y suelto en otra, sin relación aparente con nada).
     Ahora un tercer término, en vh puro (no en --u), entra en juego cuando la
     ventana es más alta que el marco de referencia: crece con la altura sola,
     empuja el bloque de texto hacia arriba y USA el aire de más en vez de
     dejarlo vacío. El piso en px puro sigue siendo el resguardo de ventanas
     muy bajas (evita que el bloque se pegue al pie). */
  --slide-bottom: max(130px, calc(190 * var(--u)), 18vh);   /* baseline del título: deja sitio al link y al pie, y escala con el alto real */

  /* — tipografía (px del Figma) — */
  --f-black: "Sharp Grotesk Black 15", "Anton", "Arial Narrow", Impact, sans-serif;
  --f-medium: "Sharp Grotesk Medium 15", "Instrument Sans", system-ui, sans-serif;
  --f-mono: "Geist Mono", ui-monospace, monospace;
  --f-ui: "Instrument Sans", system-ui, sans-serif;

  /* Jerarquía (en px del marco de referencia). El Figma tiene 320 / 50 / 40 /
     33 / 14; aquí el titular baja a 200 y el chrome baja MÁS que el titular
     (menú 33→21, wordmark 40→24): los controles acompañan, no compiten. */
  --t-title: calc(200 * var(--u));         /* cap-height 144 */
  --t-title-track: 0.0219em;               /* el tracking del Figma (7 px sobre 320), en em */
  --t-tag: calc(30 * var(--u));
  --t-tag-lh: calc(46 * var(--u));         /* baseline a baseline */
  --t-wordmark: calc(24 * var(--u));
  /* tagline, contacto, menú, idioma, link, contador, flechas: TODO el texto
     de UI comparte este tamaño — es lo que los hace leer como un sistema */
  --t-ui: max(11px, calc(12 * var(--u)));
  /* mismo criterio: un solo tracking para todo ese texto de UI. El link ya
     vivía en 0.14em y leía bien; tagline (0.02em) y contador (0.04em)
     quedaban mezquinos al lado — pedido explícito: "ampliemos el
     interletrado" en tagline y llevar lo mismo a contacto/contador/menú/
     idioma. El wordmark FOOTNOTE (0.175em) es otro nivel —marca, no UI— y
     no se toca: el usuario lo señaló como la referencia que ya está bien. */
  --track-ui: 0.14em;

  /* — chrome — */
  --logo-w: calc(45 * var(--u));           /* grupo F + punto */
  --logo-h: calc(41.3 * var(--u));
  --logo-f: calc(48 * var(--u));           /* cuerpo de la F */
  --logo-dot: calc(19.6 * var(--u));
  --menu-dot: calc(10 * var(--u));

  /* — onda de marca (cotas del Figma: 1773 de ancho, 236 fuera por la izquierda,
       base 731 bajo el frame). max()/min() la mantienen cubriendo el ancho en
       ventanas muy anchas y bajas, donde --u queda topado por la altura. — */
  --wave-w: max(calc(1773.4 * var(--u)), 117.3vw);
  --wave-l: min(calc(-236 * var(--u)), -15.6vw);
  --wave-b: calc(-731 * var(--u));

  /* — geometría del bloque de texto — */
  --tags-gap: calc(20 * var(--u));         /* fondo de la 2ª etiqueta → tope de las mayúsculas */
  --tags-inset: calc(0.5 * var(--u));      /* alinea la tinta de las etiquetas con la del título */
  --tag2-fallback: calc(300 * var(--u));   /* sangría de la 2ª etiqueta si el JS no midió */
  --link-drop: max(14px, calc(18 * var(--u)));   /* "View project" bajo la línea base */

  /* — ritmo del carrusel (lo lee el JS) — */
  /* Dos muelles críticos en cascada: 10 % del recorrido a 0.32 s, 90 % a
     1.22 s, pico de velocidad a los 0.54 s. Bajado de 8.4 a 5.5 a pedido
     del usuario ("mucho más despacio"): da tiempo a leer la máscara
     cerrándose antes de que se asiente el siguiente. El motor anterior
     (filtro de 1er orden) tenía la velocidad MÁXIMA en t=0: arranque en
     seco. ω mayor = más rápido; ω menor = más pesado. */
  /* 5-oct (noche): 5,5 → 7,0. Con 5 proyectos recorrer el carrusel entero a 5,5
     (90 % a 1,22 s) se sentía «trabado». A 7,0: 90 % a ~0,95 s. ?w=5.5 en la URL
     devuelve el ritmo anterior para comparar. */
  --spring-w1: 7;
  --spring-w2: 7;
  --drag-w: 26;                            /* muelle rígido mientras el dedo arrastra */
  /* Un gesto de rueda/trackpad = un proyecto. Más permisivo que antes (40 / 650
     / 140): un scroll suave de trackpad no alcanzaba el umbral y la página
     parecía no responder. */
  --wheel-threshold: 26;                   /* px acumulados (con fuga) para disparar */
  --wheel-lock: 460;                       /* ms sin aceptar otro gesto (era 520) */
  --wheel-gap: 110;                        /* ms de silencio = gesto nuevo */

  /* Curvas por capa: sign(d)·|d|^γ. <1 sale antes y llega tarde; >1 se
     queda pegada al reposo. Las etiquetas (0.8) salen primero y llegan
     últimas; el título (0.92) va apenas por detrás de la tarjeta. */
  --g-title: 0.92;
  --g-tag: 0.85;
  /* ventana (en |d|) donde relleno y contorno se cruzan; fuera de ella la
     letra queda SOLO en contorno, sin relleno — pedido explícito del
     usuario, no una aproximación por opacidad */
  --fill-window: 0.12;
  --letter-lag: 0.075;                     /* s de retraso de la última letra vs. la primera */
  --tag-speed-1: 1.22;                     /* etiquetas viajan más rápido que el título */
  --tag-speed-2: 1.10;
  --rw-tag1: 0.40;                         /* ventanas de la cortina, en unidades de |d| */
  --rw-tag2: 0.34;
  --rw-link: 0.14;

  /* Tarjeta (Siena): asentada es full-bleed; al moverse toma márgenes y
     esquinas — el "marco" se cierra mucho más que antes (24→80) y se
     sostiene más tiempo (0.30→0.38) a pedido del usuario: quería una
     máscara que se note, no un detalle, para que la transición se sienta
     3D (la escena se achica a una tarjeta flotante antes de la siguiente).
     La foto viaja MENOS que la tarjeta y la onda MÁS: paralaje. */
  --card-form: 0.38;                       /* |d| en que ya es tarjeta completa */
  --card-gap: calc(12 * var(--u));         /* hueco entre tarjetas */
  --card-inset: calc(80 * var(--u));       /* margen lateral en movimiento */
  --card-radius: max(20px, calc(34 * var(--u)));
  --photo-shift: 0.10;
  --photo-ov: calc(44 * var(--u));         /* tope del paralaje = sobrante del Figma (53) */
  --photo-zoom: 0.06;                      /* zoom-out con que la foto asienta */
  --wave-shift: 0.26;
  --wave-x: 0.14;                          /* la onda fluye 14 % del ancho por proyecto */
  --dim-max: 0.42;                         /* la tarjeta que se va se oscurece */
  --glow-max: 0.2;                         /* luz lateral en el cruce */
  --grain-o: 0.055;                        /* grano fijo; 0.03–0.08 es el rango que no lava la foto */
  --blur-max: calc(9 * var(--u));          /* desenfoque de movimiento: pico a medio cruce, 0 asentado */
  --stroke-max: 0.55;                      /* contorno de las letras inactivas (referente: nominados de Oscars) */

  /* Fila de títulos: el vecino arranca a --step-min del activo (fracción del
     ANCHO: 70 % deja asomar ~60 % de LAFAYETTE, como una vista previa). Si el
     título activo es más ancho, el paso se abre solo (lo mide el JS). */
  --step-min: 70vw;
  --step-gap: calc(110 * var(--u));        /* aire mínimo título → vecino */
  --tag-overlap: calc(9 * var(--u));       /* la 2ª etiqueta arranca 9 antes de que acabe la 1ª */
  --drift-y: calc(14 * var(--u));          /* el texto se inclina hacia el sentido del scroll */
  --drift-y-tag: calc(9 * var(--u));

  /* — capas (escala única de z-index) — */
  --z-scene: 0;
  --z-scrim: 1;
  --z-grain: 2;
  --z-glow: 3;
  --z-slides: 4;
  --z-chrome: 10;
  --z-nav: 20;
  --z-nav-chrome: 30;
  --z-cursor: 40;
  --z-reveal: 45;   /* por encima del cursor: cuando se abre un proyecto, tapa todo hasta la navegación */

  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-io: cubic-bezier(0.76, 0, 0.24, 1);
}
@supports (height: 1svh) { :root { --vh: 1svh; } }

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

html, body {
  margin: 0;
  height: 100%;
  background: var(--black);
  color: var(--white);
  font-family: var(--f-ui);
  overflow: hidden;              /* el scroll lo gobierna el carrusel */
  overscroll-behavior: none;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* Foco de dos tonos (naranja de la marca, igual que el hover): el anillo se ve sobre lo oscuro; el halo negro lo
   rescata sobre un cielo claro (lima sobre crema es ~1.2:1). */
:focus-visible { outline: 2px solid var(--orange); outline-offset: 4px; box-shadow: 0 0 0 5px rgb(11 11 11 / 0.55); }

/* ============================================================
   ESCENARIO
   ============================================================ */
.stage {
  position: fixed;
  inset: 0;
  overflow: hidden;
  touch-action: none;            /* el arrastre es nuestro */
  outline: none;
  background: var(--black);
}
.stage:focus-visible { box-shadow: inset 0 0 0 2px var(--orange); }

/* El cursor nativo se apaga SOLO mientras el punto naranja está de verdad
   visible en pantalla (`:has()`, se autocorrige: si el punto nunca aparece
   -mouse ya sobre el hero al cargar, JS falla, navegador viejo- el cursor
   nativo simplemente nunca se oculta, en vez de desaparecer los dos).
   Antes la regla era incondicional con solo `hover:hover and pointer:fine`
   como guardia, y el punto solo se mostraba al CRUZAR el borde del hero
   (pointerenter): con el mouse ya adentro al cargar, no había pointerenter
   que disparar y el visitante se quedaba sin cursor alguno. */
body:has(.cursor-dot.is-visible) .stage,
body:has(.cursor-dot.is-visible) .stage * { cursor: none; }
/* El punto sigue el mouse (JS escribe translate3d en .cursor-dot, la caja
   exterior — nunca toca su transform, así que no compite con esto). La
   FORMA vive en el hijo `__shape`: separar posición (JS) de estado (CSS por
   clase) evita que ambos se peleen por la misma propiedad `transform`.
   Sobre el título activo crece a una insignia con "VIEW" — aclara que ese
   bloque enorme de texto es, de hecho, un link (pedido explícito del
   usuario: "un cursor interactivo para aclarar mejor las acciones"). */
.cursor-dot {
  position: fixed;
  top: 0; left: 0;
  width: 0; height: 0;
  pointer-events: none;
  z-index: var(--z-cursor);
  opacity: 0;
  transition: opacity 0.2s ease;
  will-change: transform;
}
.cursor-dot.is-visible { opacity: 1; }
.cursor-dot__shape {
  position: absolute;
  top: 0; left: 0;
  width: 72px; height: 72px;
  margin: -36px 0 0 -36px;
  border-radius: 50%;
  background: var(--orange);
  display: flex; align-items: center; justify-content: center;
  transform: scale(0.3);   /* 72 × 0.3 ≈ 22px: punto más grande, regla general en todo el sitio */
  transition: transform 0.4s var(--ease-out), background-color 0.3s ease;
}
.cursor-dot.is-label .cursor-dot__shape { transform: scale(1); }   /* mismo naranja del punto/logo: no cambia de color, solo de tamaño */
.cursor-dot__label {
  font-family: var(--f-mono);
  font-weight: 600;
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--black);
  opacity: 0;
  transition: opacity 0.15s ease;
}
.cursor-dot.is-label .cursor-dot__label { opacity: 1; transition-delay: 0.12s; }

/* Transición circular al abrir un proyecto: un círculo crece desde el
   punto exacto del clic (--rx/--ry, en %) hasta cubrir toda la pantalla,
   en el color de acento del proyecto de destino (--reveal-color, lo
   escribe app.js). `clip-path`, no width/height: un solo elemento fijo
   del tamaño del viewport que se "revela" con una máscara circular en
   vez de animar el tamaño real de una caja — más barato para el GPU.
   La página de destino hace lo inverso al cargar (ver el CSS de cada
   proyecto, en proyectos/<slug>/ y assets/css/proyecto.css). OJO: dentro de un
   comentario CSS no se puede escribir el cierre de comentario (asterisco y barra
   juntos, ni siquiera como ejemplo de una ruta): cierra el comentario antes de
   tiempo y el resto se lee como CSS, llevándose por delante la regla que sigue
   (así se perdió este círculo hasta el 5-oct-2026):
   arranca cubierta y se ENCOGE desde el mismo punto — sessionStorage es
   el único hilo que conecta las dos mitades del gesto. */
.reveal-circle {
  position: fixed; inset: 0; z-index: var(--z-reveal);
  background: var(--reveal-color, var(--orange));
  pointer-events: none;
  clip-path: circle(0% at var(--rx, 50%) var(--ry, 50%));
  transition: clip-path 0.7s var(--ease-io);
}
.is-leaving .reveal-circle { clip-path: circle(150% at var(--rx, 50%) var(--ry, 50%)); }

/* Llegada DESDE un despliegue ("← All projects", el logo, el menú): el home
   no tiene un --proj-ink fijo como los proyectos, así que el círculo llega
   cubriendo en el MISMO color que creció del lado de salida (--reveal-color,
   viaja por sessionStorage) y se encoge solo con una animación CSS pura —
   mismo mecanismo que ya usan imatana.css/lafayette.css. `reveal-arrived`
   (la añade el script del <head>) solo existe cuando de verdad hay datos de
   una transición: una visita directa o un refresh no ganan este círculo de
   la nada, siguen con el intro de siempre del carrusel. */
.reveal-arrived .reveal-circle {
  transition: none;
  clip-path: circle(150% at var(--rx, 50%) var(--ry, 50%));
  animation: reveal-shrink 0.95s var(--ease-io) forwards;
}
@keyframes reveal-shrink {
  from { clip-path: circle(150% at var(--rx, 50%) var(--ry, 50%)); }
  to   { clip-path: circle(0% at var(--rx, 50%) var(--ry, 50%)); }
}

@media (prefers-reduced-motion: reduce) {
  .reveal-circle { transition: opacity 0.3s ease; clip-path: none; opacity: 0; }
  .is-leaving .reveal-circle { opacity: 1; }
  .reveal-arrived .reveal-circle { animation: none; }
}

/* — escena: una tarjeta por proyecto —
   Cada tarjeta es full-bleed en reposo y viaja en Y con un hueco entre
   ellas (Siena). Adentro, tres planos con velocidades propias: foto (lenta),
   onda (rápida, más una deriva ambiental) y sombra. El JS escribe
   transform / clip-path en cada tarjeta; nada de esto usa transition. */
.scene { position: absolute; inset: 0; z-index: var(--z-scene); }
.card {
  position: absolute; inset: 0;
  overflow: hidden;
  backface-visibility: hidden;
  will-change: transform;
}
.card picture { display: contents; }

/* La foto ocupa la caja del Figma (1630×1087 en un frame de 1512×982): el
   sobrante de ~5 % por lado es el margen del paralaje. */
.card__photo {
  position: absolute;
  left: -5.688%; top: -5.397%;
  width: 107.8%; height: 110.7%;
  max-width: none;
  object-fit: cover;
  object-position: 50% 45%;      /* protege la mano al recortar */
  display: block;
  transform-origin: 50% 50%;
  will-change: transform;
}

/* Tarjeta SIN asset (proyectos en espera): fondo neutro y un marcador punteado,
   igual que los "pending asset" de las páginas de proyecto. Es un <div> con la
   misma clase .card__photo, así que hereda caja, paralaje y desenfoque sin
   cambios en el motor. Los dos tonos se fijan por slot con --ph-a / --ph-b. */
.card__photo--empty {
  display: grid; place-items: center;
  background:
    radial-gradient(110% 80% at 72% 28%, rgb(255 255 255 / 0.08), transparent 62%),
    linear-gradient(160deg, var(--ph-a, #1d2025), var(--ph-b, #0f1114));
}
.card__ph {
  padding: 1em 1.6em;
  border: 1.5px dashed rgb(255 255 255 / 0.45);
  border-radius: max(10px, calc(16 * var(--u)));
  font-family: var(--f-mono); font-size: var(--t-ui); letter-spacing: var(--track-ui); text-transform: uppercase;
  color: rgb(255 255 255 / 0.7);
  transform: translateY(-6%);   /* un poco sobre el centro: el título ocupa la franja baja */
}

/* La onda de marca es una capa aparte (PNG con alfa), anclada abajo a la
   izquierda con las cotas del Figma: 1773 px de ancho, 236 fuera por la
   izquierda y su base 731 por debajo del frame. */
.card__wave {
  position: absolute;
  left: var(--wave-l);
  bottom: var(--wave-b);
  width: var(--wave-w);
  pointer-events: none;
  will-change: transform;
}
.card__wave-drift {
  transform-origin: 50% 100%;
  animation: wave-drift 19s ease-in-out infinite alternate;
  will-change: transform;
}
.card__wave img { display: block; width: 100%; height: auto; }
.card:not([data-active]) .card__wave-drift { animation-play-state: paused; }
@keyframes wave-drift {
  from { transform: translate3d(-1.1%, 0, 0) scale(1); }
  to   { transform: translate3d(1.1%, -0.8%, 0) scale(1.014); }
}

/* Degradado izquierdo del Figma (650 px de 1512, #0E0E0E → transparente):
   el único oscurecimiento fijo; el texto lo necesita, la foto no. `--dim`
   lo escribe el JS para apagar la tarjeta que se va. */
.card__shade { position: absolute; inset: 0; pointer-events: none; }
.card__shade::before {
  content: "";
  position: absolute; left: 0; top: 0; bottom: 0;
  width: 43%;
  background: linear-gradient(to right, var(--ink-deep) 0%, rgb(14 14 14 / 0) 97.5%);
}
.card__shade::after {
  content: "";
  position: absolute; inset: 0;
  background: var(--black);   /* --black (#0B0B0B), no negro puro: mismo criterio que el resto de la paleta */
  opacity: var(--dim, 0);
}

/* Bandas cortas tras el chrome (no un velo sobre toda la escena): el logo y
   el pie tienen que leerse aunque a una tarjeta le toque un cielo abierto. */
.scrim {
  position: absolute; inset: 0; z-index: var(--z-scrim); pointer-events: none;
  background:
    linear-gradient(to bottom, rgb(11 11 11 / 0.38) 0, transparent calc(150 * var(--u))),
    linear-gradient(to top,    rgb(11 11 11 / 0.45) 0, transparent calc(170 * var(--u)));
}

/* Grano fijo: rompe el degradado plano de la oscuridad y une la foto con el
   punteado de la onda. Estático (sin repintado continuo) y bajo el texto. */
.grain {
  position: absolute; inset: 0; z-index: var(--z-grain); pointer-events: none;
  opacity: var(--grain-o);
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='240' height='240'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 1  0 0 0 0 1  0 0 0 0 1  0 0 0 0.9 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");
  background-size: 240px 240px;
}

/* Luz muy sutil en los costados durante el cruce entre dos proyectos.
   --glow: 0 con un proyecto asentado, pico a medio camino. Un pulso por
   transición, nunca un estado fijo. Gradientes ya suaves: sin filter ni
   blend-mode (un desenfoque a pantalla completa cuesta caro cada frame). */
.glow {
  position: absolute; inset: 0; z-index: var(--z-glow); pointer-events: none;
  opacity: var(--glow, 0);
  background:
    radial-gradient(38% 65% at 0% 50%,   rgb(255 246 225 / 0.85), transparent 70%),
    radial-gradient(38% 65% at 100% 50%, rgb(255 246 225 / 0.85), transparent 70%);
}

/* ============================================================
   TEXTO — la fila de títulos
   El título viaja en X (el que entra, desde la derecha; el que sale, hacia
   la izquierda) mientras el fondo sube en Y. Un solo `d` por proyecto es la
   fuente; cada capa lo proyecta con su curva. El JS escribe --x, --op y las
   variables de las etiquetas; las letras llevan su propio transform.
   ============================================================ */
.slides {
  position: absolute; inset: 0; z-index: var(--z-slides);
  pointer-events: none;
}
.slide {
  position: absolute;
  left: var(--gutter); right: var(--gutter);
  bottom: var(--slide-bottom);   /* la línea base del título es el borde inferior */
  margin: 0;
  transform: translate3d(var(--x, 0px), 0, 0);
  opacity: var(--op, 1);
  will-change: transform, opacity;
}
.slide[data-active] { will-change: auto; }

/* Título + link: pasar el cursor por CUALQUIER punto de esta franja (el
   título, el hueco, el link) enciende "VIEW PROJECT". */
.slide__head { position: relative; }
.slide[data-active] .slide__head { pointer-events: auto; }

/* Las etiquetas viven ARRIBA del título: dos líneas escalonadas en diagonal. */
.slide__tags { margin: 0 0 var(--tags-gap) var(--tags-inset); }
.slide__tag-line {
  margin: 0;
  width: fit-content;
  font-family: var(--f-medium);
  font-weight: 400;
  font-size: var(--t-tag);
  line-height: var(--t-tag-lh);
  letter-spacing: 0;
  text-transform: uppercase;
  color: var(--white);
  white-space: nowrap;
  /* medido: el 10 % más claro del fondo tras la 2ª etiqueta da 2.4:1 (mín. 3:1
     en texto grande). Sombra corta y baja, no un halo. */
  text-shadow: 0 1px 10px rgb(0 0 0 / 0.4);
}
/* Cortina lateral: --tl y --tr son el recorte (0 % = visible). Las etiquetas
   entran/salen con su propia velocidad (--tx*) y curva; sin `transition`,
   el propio muelle es el suavizado y un segundo reloj sería el retardo. */
.slide__tag-line:nth-child(1) {
  transform: translate3d(var(--tx1, 0px), var(--ty, 0px), 0);
  clip-path: inset(0 var(--tr1, 0%) 0 var(--tl1, 0%));
}
.slide__tag-line:nth-child(2) {
  margin-left: var(--tag2-x, var(--tag2-fallback));
  transform: translate3d(var(--tx2, 0px), var(--ty, 0px), 0);
  clip-path: inset(0 var(--tr2, 0%) 0 var(--tl2, 0%));
}

/* La línea base de cada línea de título coincide con el borde inferior de su
   caja (line-height 0.6): así todas las baselines caen en la misma horizontal
   sea cual sea el cuerpo, y `padding-top` devuelve el aire de las mayúsculas
   (cap-height 0.72 rebasa la caja 0.12 por arriba). */
.slide__title {
  display: block;            /* bloque (no inline-block): sin línea contenedora, la base del título es EXACTAMENTE el borde inferior */
  width: fit-content;
  margin: 0;
  font-family: var(--f-black);
  font-weight: 400;
  font-size: calc(var(--t-title) * var(--fit, 1) * var(--fit-auto, 1));
  line-height: 0.6;
  padding-top: 0.12em;
  letter-spacing: var(--t-title-track);
  text-transform: uppercase;
  white-space: nowrap;
  /* el color vive por LETRA (--fill/--stroke, ver .ch): el título no fija uno propio */
}
.slide__title-line { display: block; }
.slide__title-line + .slide__title-line { margin-top: 0.26em; }   /* base a base = 0.86em: aire de 0.14em entre líneas */
/* Título de dos líneas (VIRGIN VOYAGE): a pedido del usuario va al MISMO cuerpo
   que los de una línea (--fit: 1) para darle igual peso que LAFAYETTE/IMATANA.
   Con el --t-title actual (200u) las dos líneas caben de sobra entre el header y
   el pie en todo el rango de escritorio — verificado a 1512×900, 1440×800,
   1366×768 y 1280×720: el bloque deja ~280px de aire sobre el título (nunca
   pisa el logo) y el ancho lo protege --fit-auto si algún día hiciera falta. */
.slide__title--duo { --fit: 1; }

/* Cada letra tiene relleno Y contorno como variables INDEPENDIENTES, no una
   sola opacidad de elemento (ver render() en app.js). En reposo (activo):
   --fill=1, --stroke=0 — sólida, limpia, sin línea. Lejos del activo: el
   relleno baja y el contorno sube — la letra no se "apaga", se VACÍA: queda
   hueca, como los nominados inactivos del referente (sitio de los Oscars)
   que trajo el usuario. `paint-order` mete el trazo detrás del relleno para
   que no le coma el grosor a las astas finas cuando --fill es alto. Valores
   por defecto (1, 0) para que el primer cuadro, antes de que el JS escriba
   nada, ya se vea sólida y no transparente. */
.ch {
  display: inline-block;
  --fill: 1;
  --stroke: 0;
  color: rgb(255 255 255 / var(--fill));
  /* en em, no px: así el contorno guarda la misma proporción contra el
     cap-height en CUALQUIER cuerpo (título normal, dúo a 0.72×, móvil) sin
     tener que recalcularlo por breakpoint */
  -webkit-text-stroke: 0.018em rgb(255 255 255 / var(--stroke));
  paint-order: stroke fill;
}

/* Ver proyecto: bajo la línea base, alineado con el título. Siempre visible
   en el activo (también en touch: sin él el proyecto era inalcanzable);
   el hover solo lo enciende. Sin halos: color, subrayado y tracking. */
.slide__link {
  position: absolute;
  left: var(--tags-inset);
  top: calc(100% + var(--link-drop));
  font-family: var(--f-mono);
  font-weight: 500;
  font-size: var(--t-ui);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  padding: 0.5em 0;
  color: rgb(255 255 255 / 0.92);
  text-shadow: 0 1px 12px rgb(0 0 0 / 0.5);
  opacity: calc(1 - var(--hl, 0));
  transform: translate3d(0, calc(var(--hl, 0) * 10px), 0);
  pointer-events: none;
  transition: color 0.35s ease, letter-spacing 0.6s var(--ease-out);
}
.slide[data-active] .slide__link { pointer-events: auto; }
/* el subrayado se dibuja de izquierda a derecha al encender: segunda señal,
   además del color, de que ahí hay algo interactivo */
.slide__link::after {
  content: "";
  position: absolute;
  left: 0; right: 100%; bottom: 0.3em;
  height: 1px;
  background: currentColor;
  transition: right 0.6s var(--ease-out);
}
/* El encendido (verde + espaciado) SOLO en dispositivos con hover real (mouse).
   En táctil iOS aplica :hover al tocar y el CTA parpadeaba a verde/espaciado
   antes de navegar ("va y vuelve"). El foco de teclado usa :focus-visible, que
   NO se dispara en un tap táctil. */
@media (hover: hover) {
  .slide__head:hover .slide__link { color: var(--orange); letter-spacing: 0.2em; }
  .slide__head:hover .slide__link::after { right: 0; }
}
.slide__link:focus-visible { color: var(--orange); letter-spacing: 0.2em; }
.slide__link:focus-visible::after { right: 0; }

/* Línea bajo el bloque de texto: el eco del referente de Oscars que trajo el
   usuario (ahí cada nominado —activo o no— lleva su propia línea divisoria
   debajo). Absoluta dentro de .slide__head, como el link: NO participa del
   flujo, así que no mueve el ancla (la base del título sigue siendo
   exactamente --slide-bottom, medida contra el Figma). No hace falta JS
   aparte: vive dentro de .slide, viaja con el mismo --x y se apaga con el
   mismo --op que el título — un dato menos que mantener, no una animación
   nueva. Ancla a la izquierda, como el subrayado del link. */
.slide__rule {
  position: absolute;
  left: var(--tags-inset);
  /* 3.65, no 3.3: el link es absoluto y lleva su propio padding (0.5em arriba
     y abajo, para el toque), así que su caja "come" parte del hueco antes de
     llegar al texto real. Con 3.3 el aire visible título→link (18px) y
     link→línea (14px) no eran el mismo número — pequeño pero se nota al lado
     del ritmo de 20px que usan las etiquetas. En 3.65 el hueco real hasta la
     línea iguala esos ~20px: un solo paso de ritmo para todo el bloque, no
     tres valores parecidos por casualidad. */
  top: calc(100% + var(--link-drop) * 3.65);
  height: 1px;
  width: min(45vw, calc(100% - var(--tags-inset)));
  background: linear-gradient(to right, rgb(255 255 255 / 0.28), rgb(255 255 255 / 0.28) 55%, transparent);
  /* sombra corta, mismo criterio que el link: sin ella se pierde contra un
     frame claro de la foto (medido antes en la 2ª etiqueta: 2.4:1 sin sombra) */
  filter: drop-shadow(0 1px 2px rgb(0 0 0 / 0.4));
  transform-origin: left center;
}

/* ============================================================
   CHROME
   Las cajas de .chrome no capturan puntero: la rueda y el arrastre pasan
   a través de las franjas vacías (antes eran una zona muerta).
   ============================================================ */
.chrome {
  position: fixed;
  left: var(--gutter); right: var(--gutter);
  z-index: var(--z-chrome);
  display: flex;
  /* centrado, no flex-start/baseline: logo, idioma, menú (o wordmark,
     tagline, contacto, cluster) miden distinto pero así comparten un mismo
     eje óptico sin un solo margen suelto por elemento — antes cada control
     llevaba su propio ajuste a mano y terminaban desalineados entre sí. */
  align-items: center;
  gap: calc(20 * var(--u));
  text-transform: uppercase;
  pointer-events: none;
}
.chrome > * { pointer-events: auto; }
.chrome--top { top: var(--edge-top); z-index: var(--z-nav-chrome); }
.chrome--bottom { bottom: var(--edge-bottom); }

/* Logo: imagen del logotipo real de footnote. */
.logo { position: relative; display: flex; align-items: center; flex: none; height: var(--logo-h); text-decoration: none; }
.logo__img {
  display: block;
  height: 100%;
  width: auto;
  transition: opacity 0.3s ease;
}
.logo:hover .logo__img { opacity: 0.8; }

/* Controles pequeños del chrome — idioma, menú, contacto: un solo sistema
   (misma fuente, tamaño, tracking, hover) para que lean como el MISMO tipo
   de control en vez de tres piezas con su propio estilo cada una. El menú
   ya no lleva el bloque azul: en este sistema minimal (blanco + naranja de
   acento + verde de hover) un rectángulo de color sólido competía con todo
   lo demás. Mismo patrón de subrayado que ya tenía `.contact`. */
.ui-btn {
  appearance: none; background: none; border: 0;
  position: relative;
  display: inline-flex; align-items: center; gap: 0.5em;
  color: var(--white); text-decoration: none; cursor: pointer;
  font-family: var(--f-mono); font-weight: 500;
  font-size: var(--t-ui); letter-spacing: var(--track-ui); text-transform: uppercase;
  line-height: 1;
  padding: 12px 10px;      /* toque ≥44px de alto */
  margin: -12px -10px;     /* el padding no mueve el layout alrededor */
  transition: color 0.25s ease;
}
.ui-btn::after {
  content: "";
  position: absolute; left: 10px; right: calc(100% - 10px); bottom: 10px;
  height: 1px; background: currentColor;
  transition: right 0.5s var(--ease-out);
}
.ui-btn:hover, .ui-btn:focus-visible { color: var(--orange); }
.ui-btn:hover::after, .ui-btn:focus-visible::after { right: 10px; }

.nav-slot { margin-left: auto; display: flex; align-items: center; gap: calc(20 * var(--u)); }
.nav-toggle { font-size: max(16px, calc(20 * var(--u))); }
.nav-toggle:active { transform: translateY(1px); }
.nav-toggle__hamburger {
  display: block;
  flex: none;
  width: 1.1em;
  height: 0.78em;
  transition: opacity 0.25s ease;
}
.nav-toggle:hover .nav-toggle__hamburger { opacity: 0.7; }
/* Menú abierto → la hamburguesa se convierte en X (el botón ya cambia el texto
   a "CERRAR"). Las barras de arriba y abajo giran ±45° y se centran; la del
   medio se desvanece. transform-box: fill-box para girar alrededor del centro
   de cada barra. */
.nav-toggle__hamburger rect {
  transform-box: fill-box; transform-origin: center;
  transition: transform 0.34s ease, opacity 0.2s ease;
}
[data-nav-toggle][aria-expanded="true"] .nav-toggle__hamburger rect:nth-of-type(1) { transform: translateY(6.25px) rotate(45deg); }
[data-nav-toggle][aria-expanded="true"] .nav-toggle__hamburger rect:nth-of-type(2) { opacity: 0; }
[data-nav-toggle][aria-expanded="true"] .nav-toggle__hamburger rect:nth-of-type(3) { transform: translateY(-6.25px) rotate(-45deg); }
@media (prefers-reduced-motion: reduce) { .nav-toggle__hamburger rect { transition: none; } }

/* Cluster de navegación: flecha·indicador·flecha como UN control, no tres
   piezas separadas — mismo espaciado apretado, misma línea base que el
   resto del chrome. `margin-left: auto` lo empuja al final de la fila del
   footer, igual que `.nav-slot` en el header. */
.nav-cluster {
  display: flex;
  align-items: center;
  gap: calc(10 * var(--u));
  margin-left: auto;
  font-size: var(--t-ui);   /* referencia para el em de los chevrons: escalan con el resto del texto de UI */
}

/* Flechas: chevrons propios, pequeños, del mismo peso que el resto del texto
   de UI — no el trazo alto del Figma (pensado vertical) girado 90°, que
   desbordaba su caja angosta al rotar y se veía torcido. Mismo go() que
   teclado y rueda. Área táctil ≥44px vía padding, con margen negativo para
   que el padding no separe la flecha del indicador. */
.arrow-nav {
  appearance: none; border: 0; background: none;
  display: flex; align-items: center; justify-content: center;
  padding: 16px;
  /* El margen negativo NO encoge la caja pintada (el padding sigue ahí,
     clicable) — solo adelanta dónde empieza el siguiente ítem del flex.
     La fórmula correcta es (separación visual deseada − gap del row), NO
     "−padding": con eso último el padding se comía el gap y el contador
     quedaba 2px por debajo del botón (medido, no a ojo). */
  margin-inline: -8px;   /* separación visual: 16 − 8 + gap(10) = 18px del icono al contador */
  color: var(--white);
  cursor: pointer;
  transition: color 0.25s ease, transform 0.3s var(--ease-out);
}
.arrow-nav svg { display: block; width: 1em; height: 1em; }
.arrow-nav svg path { vector-effect: non-scaling-stroke; }
.arrow-nav:hover, .arrow-nav:focus-visible { color: var(--orange); }
.arrow-nav--up:hover { transform: translateX(-3px); }
.arrow-nav--down:hover { transform: translateX(3px); }
.arrow-nav:active { transform: scale(0.88); }

.footer-wordmark {
  margin: 0;
  font-family: var(--f-black);
  font-weight: 400;
  font-size: var(--t-wordmark);
  line-height: 1;
  letter-spacing: 0.175em;       /* 7 px sobre 40 */
  text-transform: uppercase;
  color: var(--white);
}
.tagline {
  margin: 0;
  font-family: var(--f-medium);
  font-weight: 400;
  font-size: var(--t-ui);
  line-height: 1.6;
  letter-spacing: var(--track-ui);
  text-transform: uppercase;
  white-space: nowrap;
}
/* "Designers with attitude" en Instrument Sans Bold: más legible que la Black condensada. */
.tagline strong { font-family: var(--f-ui); font-weight: 700; letter-spacing: var(--track-ui); }
/* "Based in Bogotá" atenuado, igual que el footer de los proyectos (.close__place). */
.tagline__place { opacity: 0.6; }
/* .contact ya es un .ui-btn: mismo subrayado, mismo hover, sin reglas propias. */

/* Indicador: horizontal, entre las dos flechas del cluster del pie. */
.counter {
  display: flex; flex-direction: row; align-items: center;
  gap: 0.6em;
  font-family: var(--f-mono);
  font-weight: 400;
  font-size: var(--t-ui);
  letter-spacing: var(--track-ui);
  color: var(--white);
  font-variant-numeric: tabular-nums;
  text-shadow: 0 1px 8px rgb(0 0 0 / 0.5);
  pointer-events: none;
}
.counter__track {
  display: block; position: relative; overflow: hidden;
  width: max(40px, calc(48 * var(--u)));
  height: 1px;
  background: rgb(255 255 255 / 0.28);
}
.counter__bar {
  display: block; position: absolute; top: 0; left: 0;
  width: 33.333%; height: 100%;
  background: var(--green);
  /* --counter-frac (0–300, un proyecto = 100) lo escribe el JS */
  transform: translateX(calc(var(--counter-frac, 0) * 1%));
}

/* — panel del menú: cortina que baja desde arriba; los ítems suben con
   escalón. El header queda por encima (z-nav-chrome) y sigue siendo el
   control para cerrar. — */
.nav-panel {
  position: fixed; inset: 0; z-index: var(--z-nav);
  background: #00009E;   /* azul de marca, a pedido del usuario */
  display: grid; align-content: center;
  padding: calc(120 * var(--u)) var(--gutter) calc(80 * var(--u));
  clip-path: inset(0 0 100% 0);
  transition: clip-path 0.75s var(--ease-io);
}
.nav-panel.is-open { clip-path: inset(0 0 0 0); }
.nav-panel[hidden] { display: none; }
.nav-panel ul { list-style: none; margin: 0; padding: 0; }
.nav-panel li { overflow: hidden; padding-top: 0.12em; }
.nav-panel a {
  display: block;
  font-family: var(--f-black);
  font-size: calc(110 * var(--u));
  line-height: 0.86;
  letter-spacing: var(--t-title-track);
  text-transform: uppercase;
  text-decoration: none;
  color: var(--white);
  opacity: 0.72;   /* letra blanca legible sobre el azul (antes 0.5, se veía gris) */
  transition: opacity 0.3s ease, color 0.3s ease;
}
.nav-panel a span {
  display: block;
  transform: translate3d(0, 115%, 0);
  transition: transform 0.9s var(--ease-out) calc(0.25s + var(--i, 0) * 0.07s);
}
.nav-panel.is-open a span { transform: none; }
.nav-panel a:hover, .nav-panel a:focus-visible { opacity: 1; color: var(--orange); outline-offset: 0; }
.nav-panel:has(a:hover) a:not(:hover) { opacity: 0.25; }

/* Con el menú abierto (o abriéndose), oculta el carrusel y el pie detrás: si no,
   durante el barrido de apertura se asoman elementos del home (el usuario vio
   colarse la línea/barra lima del contador la primera vez). El fondo del .stage
   y del body es --black, así que el barrido azul queda limpio sobre negro. El
   header (logo + CERRAR) queda por encima del panel, no se toca. */
body:has(.nav-panel.is-open) .stage,
body:has(.nav-panel.is-open) .chrome--bottom { visibility: hidden; }

/* ============================================================
   INTRO — un solo momento orquestado al cargar: la escena se abre desde
   un recuadro, la onda sube, el titular sale de su línea base letra a
   letra, y solo después llega el chrome. Espera a las fuentes (el JS
   añade .is-ready). Todo con transform/opacity/clip-path.
   ============================================================ */
/* .arrow-nav y .counter ya no están aparte: viven dentro de .chrome--bottom
   (cluster del pie) y heredan su fundido — un dato menos que mantener. */
.js:not(.is-ready) .scene,
.js:not(.is-ready) .scrim,
.js:not(.is-ready) .slides,
.js:not(.is-ready) .chrome { opacity: 0; }

/* `backwards` (no `both`): al terminar, la animación SUELTA la propiedad y
   vuelve a mandar el estilo base (el grano vale 0.06, no 1). */
.is-ready .scene { animation: scene-in 1.6s var(--ease-out) backwards; }
.is-ready .scrim { animation: fade-in 1.4s ease 0.2s backwards; }
.is-ready .grain { animation: grain-in 1.4s ease 0.2s backwards; }   /* llega a SU opacidad, no a 1 */
.is-ready .card__wave img { animation: wave-in 2s var(--ease-out) 0.25s backwards; }
.is-ready .ch { animation: ch-in 1.1s var(--ease-out) calc(0.45s + var(--i, 0) * 0.05s) backwards; }
.is-ready .slide__tag-line { animation: rise-in 1.1s var(--ease-out) backwards; }
.is-ready .slide__tag-line:nth-child(1) { animation-delay: 0.9s; }
.is-ready .slide__tag-line:nth-child(2) { animation-delay: 1.02s; }
.is-ready .slide[data-active] .slide__link { animation: fade-in 0.9s ease 1.3s backwards; }   /* solo el activo: el link del vecino asomaría de golpe */
.is-ready .slide__rule { animation: rule-in 1s var(--ease-out) 1.32s backwards; }   /* último en llegar: cierra la cascada, como los divisores del referente */
.is-ready .chrome { animation: fade-in 1s ease 1.15s backwards; }

/* mientras dura el intro, cada línea recorta por su base: las letras nacen
   del suelo del titular. Después el recorte se quita: una O o una G se pasan
   de la línea base y no deben quedar cortadas. */
html:not(.is-live) .slide__title-line { clip-path: inset(-100% -100% 0 -100%); }

@keyframes scene-in {
  from { clip-path: inset(5% 1.6% round calc(34 * var(--u))); transform: scale(1.05); }
  to   { clip-path: inset(0 0 round 0px); transform: none; }
}
@keyframes wave-in {
  from { transform: translate3d(0, 16%, 0); opacity: 0; }
  to   { transform: none; opacity: 1; }
}
@keyframes ch-in { from { translate: 0 140%; } to { translate: 0 0; } }   /* 140 %: la punta de la mayúscula (0.72em) empieza oculta bajo la línea */
@keyframes grain-in { from { opacity: 0; } to { opacity: var(--grain-o); } }
@keyframes rise-in { from { translate: 0 40%; opacity: 0; } to { translate: 0 0; opacity: 1; } }
@keyframes rule-in { from { transform: scaleX(0); opacity: 0; } to { transform: none; opacity: 1; } }
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }

/* ============================================================
   SEÑAL DE SCROLL (UX) — el home es un carrusel VERTICAL (rueda/flechas/
   teclado); mucha gente no adivina que hay que desplazarse. Este cue lo
   indica: "Scroll" + una línea con un punto naranja que baja en loop.
   Aparece con el intro (.is-live) y el JS lo oculta al PRIMER gesto de
   navegación (rueda, touch, teclado o flechas). Decorativo (aria-hidden).
   ============================================================ */
.scroll-cue {
  position: fixed; left: 50%; bottom: calc(74 * var(--u));
  transform: translateX(-50%);
  z-index: var(--z-chrome);
  display: flex; flex-direction: column; align-items: center;
  gap: calc(9 * var(--u));
  color: var(--white);
  opacity: 0; visibility: hidden;
  transition: opacity 0.6s ease, visibility 0s linear 0.6s;
  pointer-events: none;
}
.is-live .scroll-cue { opacity: 0.72; visibility: visible; transition: opacity 0.7s ease 0.6s; }
.scroll-cue.is-hidden { opacity: 0; visibility: hidden; transition: opacity 0.5s ease, visibility 0s linear 0.5s; }
.scroll-cue__label {
  font-family: var(--f-mono); font-size: var(--t-ui);
  letter-spacing: var(--track-ui); text-transform: uppercase;
}
.scroll-cue__line {
  position: relative; width: 1.5px; height: calc(38 * var(--u));
  border-radius: 2px; background: rgb(255 255 255 / 0.28); overflow: hidden;
}
.scroll-cue__dot {
  position: absolute; left: 0; right: 0; top: 0; height: 40%;
  border-radius: 2px; background: var(--orange);
  animation: scroll-cue-travel 1.9s var(--ease-io) infinite;
}
@keyframes scroll-cue-travel {
  0%   { top: -40%; opacity: 0; }
  25%  { opacity: 1; }
  75%  { opacity: 1; }
  100% { top: 100%; opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .scroll-cue__dot { animation: none; top: 20%; }
}
/* El indicador de scroll también en móvil (a pedido del usuario). */

/* Sin JS, o si el motor no arrancó a tiempo (red muy lenta / error): solo el
   proyecto inicial (data-start="1" en index.html), quieto y legible, en vez
   de tres titulares apilados en x=0. Su "View project" tiene que quedar
   clicable y alcanzable por teclado: normalmente eso lo habilita el JS al
   marcar el slide [data-active], pero aquí no hay JS que lo haga. */
html:not(.js) .slide:not([data-slide="1"]),
html:not(.js) .card:not([data-card="1"]),
.no-engine .slide:not([data-slide="1"]),
.no-engine .card:not([data-card="1"]) { display: none; }
html:not(.js) [data-slide="1"] .slide__link,
.no-engine [data-slide="1"] .slide__link { pointer-events: auto; opacity: 1; }

/* ============================================================
   RESPONSIVE
   ============================================================ */
@media (max-width: 1024px) {
  :root { --wheel-threshold: 34; }

  /* Mismo problema que en mobile (ver más abajo, ≤768px) pero en el rango
     de tablet en horizontal (769–1024px): ahí NO entra la composición móvil
     propia, sigue siendo la de escritorio con su --t-title de 320u — y con
     ella, el 0.72× de VIRGIN VOYAGE (pensado para caber bajo ESE cuerpo
     grande) vuelve a hacerlo ver más chico que LAFAYETTE/IMATANA. Probado a
     1024×768 (el más bajo de este rango, el más ajustado): el bloque de dos
     líneas a tamaño completo sigue cabiendo sin tocar el header ni el pie. */
  .slide__title--duo { --fit: 1; }
}

/* Móvil: composición propia sobre un marco de 390 px (1 --u = 1/390 del
   ancho). No es el escritorio encogido: el titular sube de cuerpo relativo,
   las etiquetas envuelven sin escalón, el vecino asoma menos y el link de
   "ver proyecto" deja de depender del hover. Las flechas se ocultan (rueda,
   arrastre y teclado siguen navegando). El chrome (logo, menú, flechas,
   contador) sigue los tokens de :root: no hace falta redefinirlo aquí.

   `--u` tiene TECHO (1.35px), algo que el `--u` del escritorio ya hace
   contra SU propio marco (min() contra 1512×920) pero que a este le
   faltaba. Sin techo, `--u` crecía sin freno hasta los 768px del propio
   breakpoint: a 674 (una ventana de escritorio angosta, no un teléfono,
   pero AÚN dentro de "≤768px") daba 1.73 — un 73 % más grande que el marco
   de 390 contra el que se afinó cada número. Con todo inflado así, el
   título ya no cabía en su ancho, invadía el paso reservado para el vecino
   (choque IMATANA/LAFAYETTE), y el pie —ya al límite de su fila a escala
   normal— se desbordaba a una segunda línea desordenada: el "no organizado,
   sobre todo el pie" que reportó el usuario, reproducido exacto a 674×1272.
   1.35 es el techo: a partir de ~527px de ancho (390 × 1.35) el bloque deja
   de crecer y el resto del ancho se queda como aire a los lados, en vez de
   seguir inflando una composición que ya no fue pensada para eso. */
@media (max-width: 768px) {
  :root {
    --u: min(calc(100vw / 390), 1.35px);
    --gutter: max(18px, calc(18 * var(--u)));
    --edge-top: calc(16 * var(--u));
    --edge-bottom: calc(16 * var(--u));
    /* Título más arriba en móvil (a pedido del usuario): 126 → 190 sube el
       bloque ~64u, dejando más aire entre el nombre y el pie. */
    --slide-bottom: calc(190 * var(--u));

    /* Título más grande a pedido del usuario ("dar más importancia a los
       nombres"): de 58 a 74 — sube del 14.9 % al 19 % del ancho. */
    --t-title: calc(74 * var(--u));
    --t-tag: calc(14 * var(--u));
    --t-tag-lh: calc(22 * var(--u));
    --t-wordmark: calc(15 * var(--u));
    --t-ui: max(10px, calc(10.5 * var(--u)));

    --logo-w: calc(34 * var(--u));
    --logo-h: calc(31 * var(--u));
    --logo-f: calc(36 * var(--u));
    --logo-dot: calc(15 * var(--u));
    --menu-dot: calc(7 * var(--u));

    --tags-gap: calc(12 * var(--u));
    --tags-inset: 0px;
    --tag2-fallback: calc(20 * var(--u));
    --link-drop: calc(20 * var(--u));   /* más aire título→CTA (el CTA crece abajo) */

    /* la onda, recortada al retrato: dos crestas asomando bajo el título */
    --wave-w: calc(1040 * var(--u));
    --wave-l: calc(-400 * var(--u));
    --wave-b: calc(-405 * var(--u));

    --step-min: 78vw;
    --step-gap: calc(30 * var(--u));
    --tag-overlap: 0px;
    --card-gap: calc(6 * var(--u));
    --card-inset: calc(33 * var(--u));       /* mismo cierre dramático que escritorio, misma proporción */
    --card-radius: calc(26 * var(--u));
    --photo-ov: calc(30 * var(--u));
    --drift-y: calc(7 * var(--u));
    --drift-y-tag: calc(5 * var(--u));
    --letter-lag: 0.06;
    --wheel-threshold: 22;
  }
  .stage { height: 100svh; }
  /* CTA "View project" más grande en móvil (a pedido del usuario: "es
     importante que se vea más grande"). De ~10px (--t-ui) a ~16u. */
  .slide__link { font-size: calc(16 * var(--u)); letter-spacing: 0.12em; }
  /* .slide__title--duo ya vuelve a --fit:1 en el bloque de ≤1024px de
     arriba (este breakpoint es un subconjunto de ese rango); como el bloque
     cuelga de `bottom: var(--slide-bottom)`, el titular de dos líneas ocupa
     más alto y sube un poco más arriba — efecto esperado, pedido explícito
     del usuario. */

  /* Pie móvil: antes un solo `flex-wrap` sin orden — 4 piezas (wordmark,
     tagline de 2 líneas, contacto, cluster) se repartían donde alcanzaran,
     así que el punto de quiebre cambiaba con cada ancho y el resultado se
     veía distinto (y descuadrado) en cada prueba — "la parte de abajo está
     completamente desordenada", reportado con capturas a 425 y 674 de ancho
     que NO se parecían entre sí. Ahora son dos filas fijas, no una que se
     parte donde pueda: fila 1 = wordmark a la izquierda + contacto a la
     derecha (mismo eje que header: logo a la izquierda, menú a la derecha);
     fila 2 = tagline, sola, a todo el ancho. `order` reordena visualmente
     sin tocar el HTML (el orden de lectura para lectores de pantalla sigue
     siendo wordmark → tagline → contacto). El contador ya no vive aquí — ver
     `.nav-cluster` más abajo — así que esta fila nunca compite por espacio
     con la barra de progreso. */
  .chrome--bottom {
    flex-wrap: wrap;
    justify-content: space-between;
    row-gap: calc(10 * var(--u));
    column-gap: calc(14 * var(--u));
  }
  .footer-wordmark { order: 1; }
  .contact { order: 2; }
  .tagline {
    order: 3;
    flex-basis: 100%;   /* fuerza su propia fila: no cabe junto a wordmark+contacto, así que envuelve sola */
    white-space: normal;
    line-height: 1.35;
  }
  .tagline__place { display: block; }

  .slide__tag-line { white-space: normal; }
  .slide__tag-line:nth-child(2) { margin-left: var(--tag2-fallback); }

  /* Flechas: ya no caben ni hacen falta (rueda, arrastre y teclado siguen
     navegando). El contador se muda de la fila del pie a un riel vertical
     propio — pedido explícito: "la línea de progreso en mobile cambiémosla
     a la parte derecha, al centro de forma vertical". `position: fixed`
     lo saca del flujo de `.chrome--bottom` (dos filas de arriba quedan
     limpias, sin un tercer elemento disputando el ancho) y lo ancla al
     borde derecho de la pantalla, centrado verticalmente — fuera del
     camino del texto, siempre en el mismo sitio sin importar cuál slide
     esté activo. */
  .arrow-nav { display: none; }
  .nav-cluster {
    position: fixed;
    right: var(--gutter);
    /* 42 %, no 50 %: mismo criterio que subir --slide-bottom en escritorio
       (§0.5) — el centro geométrico exacto de la pantalla no es el centro
       ÓPTICO de la composición, porque el header ocupa aire arriba y el pie
       ocupa aire abajo; 50 % se sentía un poco bajo. Pedido explícito del
       usuario: correrlo "más hacia el centro para que la línea quede más
       arriba". */
    top: 42%;
    transform: translateY(-50%);
    flex-direction: column;
    margin-left: 0;
    gap: 0.7em;
  }
  .counter { flex-direction: column; gap: inherit; }
  .counter__track {
    width: 1px;
    height: max(64px, calc(90 * var(--u)));
  }
  .counter__bar {
    width: 100%; height: 33.333%;
    /* mismo --counter-frac que escribe el JS; en horizontal avanzaba en X,
       aquí avanza en Y — un solo dato, dos ejes según la orientación */
    transform: translateY(calc(var(--counter-frac, 0) * 1%));
  }

  .nav-panel a { font-size: calc(52 * var(--u)); }

  /* Degradado adicional, sutil, detrás del pie: pedido explícito para que
     el texto (ahora en dos filas limpias) siga leyéndose bien encima de
     una foto que puede traer cualquier tono en el borde inferior. Es una
     TERCERA capa sobre las dos que ya tiene `.scrim` (arriba del chrome
     superior, arriba del chrome inferior) — más baja y más ancha que
     esa segunda, pero con poco pico de opacidad (0.3, no 0.45+) para que
     se note como aire oscuro, no como una barra sólida. */
  .scrim {
    background:
      linear-gradient(to bottom, rgb(11 11 11 / 0.38) 0, transparent calc(150 * var(--u))),
      linear-gradient(to top,    rgb(11 11 11 / 0.45) 0, transparent calc(170 * var(--u))),
      linear-gradient(to top,    rgb(11 11 11 / 0.3) 0, transparent calc(260 * var(--u)));
  }
}

/* ============================================================
   REDUCED MOTION
   Se conserva la navegación y la jerarquía (qué proyecto está activo:
   fundido entre tarjetas y títulos). Se elimina lo que viaja: el paso
   horizontal, el zoom, la inclinación, la deriva de la onda, las letras
   en cascada y el intro. El JS ya trata estos casos en render().
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  .is-ready .scene, .is-ready .scrim, .is-ready .grain, .is-ready .card__wave img,
  .is-ready .ch, .is-ready .slide__tag-line, .is-ready .slide__link, .is-ready .slide__rule,
  .is-ready .chrome { animation: none; }
  .card__wave-drift { animation: none; }
  .card { transition: opacity 0.35s ease; }      /* el fondo cruza con un fundido corto: el JS pone opacidad 1/0 */
  .glow { display: none; }
  .slide { will-change: auto; }
  .nav-panel, .nav-panel a span, .slide__link, .slide__link::after { transition: none; }
}

/* ============================================================
   RENDIMIENTO — modo LIGERO y medidor de FPS (ver Carousel.sample en app.js)
   El modo ligero se activa solo si los viajes van por debajo de ~42 fps (o con
   ?lite=1): quita lo más caro de pintar —luz lateral y grano a pantalla
   completa; el desenfoque de movimiento lo apaga el JS—. El medidor (?fps) es
   solo una herramienta de diagnóstico.
   ============================================================ */
.perf-lite .grain, .perf-lite .glow { display: none; }
.fps-meter {
  position: fixed; left: 12px; bottom: 12px; z-index: 60; pointer-events: none;
  padding: 6px 9px; border-radius: 6px; background: rgb(11 11 11 / 0.82); color: #fff;
  font: 500 11px/1.4 var(--f-mono); letter-spacing: 0.04em;
}
