/* ============================================================================
   MOTION — la capa que hace que esto no parezca una web normal
   ----------------------------------------------------------------------------
   Organizada en cinco familias:

     1. REVELADO      entrada de contenido al hacer scroll
     2. KINÉTICA      tipografía partida en caracteres
     3. ADORNO        dibujo de líneas SVG y sellos giratorios
     4. TENSIÓN       marquesina con sesgo por velocidad, glitch de radio
     5. CONTRATO      qué se apaga y cuándo

   PRINCIPIO RECTOR
   Ninguna animación se declara dos veces. El revelado se resuelve con
   animaciones dirigidas por scroll (CSS, fuera del hilo principal) cuando el
   navegador las soporta, y con IntersectionObserver cuando no. js/core.js
   decide cuál de las dos rutas activa poniendo .has-sdt en <html>.
   ========================================================================= */

/* ============================================================== 1. REVELADO
   Todo lo que entra se marca con data-reveal. Nunca se anima el layout:
   solo opacity y transform. Y se escalona con --i.
   ======================================================================== */

.js [data-reveal] {
  opacity: 0;
  transform: translate3d(0, var(--rv-y, 30px), 0) scale(var(--rv-s, 1));
  transition:
    opacity var(--dur-slow) var(--ease-veil),
    transform var(--dur-slow) var(--ease-deco);
  transition-delay: calc(var(--i, 0) * 80ms);
}

.js [data-reveal].is-in {
  opacity: 1;
  transform: none;
}

/* Máscara: el contenido no aparece, se descubre. Mucho más elegante que un
   fundido, y cuesta exactamente lo mismo. */
.js [data-reveal~="mask"] {
  clip-path: inset(0 0 100% 0);
  transform: none;
  transition:
    clip-path var(--dur-slow) var(--ease-in-out-quint),
    opacity var(--dur-fast) var(--ease-veil);
  transition-delay: calc(var(--i, 0) * 110ms);
}

.js [data-reveal~="mask"].is-in {
  clip-path: inset(0 0 -12% 0);
}

/* Ruta A — animaciones dirigidas por scroll (CSS puro).
   Ventaja real: el navegador las ejecuta fuera del hilo principal, así que
   siguen siendo suaves aunque el JS esté ocupado. Chrome 115+, Safari 26;
   Firefox va por detrás, por eso existe la ruta B. */
@supports (animation-timeline: view()) {
  .js.has-sdt [data-reveal]:not([data-hold]):not([data-reveal~="manual"]) {
    opacity: 1;
    transform: none;
    transition: none;
    animation: rv-up linear both;
    animation-timeline: view();
    animation-range: entry 6% cover 34%;
  }

  .js.has-sdt [data-reveal~="mask"]:not([data-hold]):not([data-reveal~="manual"]) {
    clip-path: none;
    animation: rv-mask linear both;
    animation-timeline: view();
    animation-range: entry 0% cover 32%;
  }
}

@keyframes rv-up {
  from {
    opacity: 0;
    transform: translate3d(0, 30px, 0);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes rv-mask {
  from {
    clip-path: inset(0 0 100% 0);
    opacity: 0.2;
  }
  to {
    clip-path: inset(0 0 -12% 0);
    opacity: 1;
  }
}

/* ============================================================== 2. KINÉTICA
   kinetic.js envuelve cada carácter en .kin__ch y le asigna --k (índice).
   Accesibilidad: el texto original se conserva en aria-label y los spans
   quedan aria-hidden, así que un lector de pantalla lee la palabra entera,
   no letra a letra.
   ======================================================================== */

.kin {
  display: inline-block;
  overflow: clip;
  vertical-align: bottom;
  /* Evita que el recorte de una línea se coma los descendentes (g, j, p). */
  padding-block-end: 0.12em;
  margin-block-end: -0.12em;
}

.kin__word {
  display: inline-block;
  white-space: nowrap;
}

.kin__ch {
  display: inline-block;
  transform: translate3d(0, 115%, 0) rotate(4deg);
  opacity: 0;
  will-change: transform;
}

.is-in .kin__ch,
.kin.is-in .kin__ch {
  animation: ch-rise 1.05s var(--ease-deco) both;
  animation-delay: calc(var(--k, 0) * 34ms);
}

@keyframes ch-rise {
  from {
    transform: translate3d(0, 115%, 0) rotate(4deg);
    opacity: 0;
  }
  to {
    transform: none;
    opacity: 1;
  }
}

/* Descifrado de radio: sustitución de caracteres mientras "sintoniza".
   Lo aplica kinetic.js carácter a carácter sobre un textContent temporal. */
.is-tuning {
  font-family: var(--font-mono) !important;
  letter-spacing: 0.08em;
  color: var(--accent) !important;
}

/* =============================================================== 3. ADORNO ==
   Dibujo de trazo sin medir longitudes: el atributo pathLength="1" de SVG
   normaliza la longitud de cualquier camino, así que dasharray/offset valen
   siempre 1 y no hace falta getTotalLength() ni recalcular al redimensionar.
   ======================================================================== */

/* El contenedor de ornamento es un div vacío hasta que deco.js lo rellena. */
[data-deco] {
  display: block;
}

[data-deco] > svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}

/* Regla única para todo el ornamento generado: sin relleno, filete de 1 px
   real y trazo que NO engorda al escalar. Sin non-scaling-stroke, el mismo
   sunburst tendría un filete de 1 px en el móvil y de 6 px en un monitor 4K,
   y el Art Déco vive precisamente de la finura del filete. */
[data-deco] svg * {
  fill: none;
  stroke: currentColor;
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

[data-deco] [pathLength] {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}

[data-deco].is-drawn [pathLength]:not(.rail__progress) {
  animation: deco-draw 1.3s var(--ease-deco) both;
  animation-delay: calc(var(--d, 0) * 1ms);
}

/* La línea de progreso del raíl es la excepción: no se "dibuja", se RELLENA
   según el scroll. Es información, no adorno, así que no participa en la
   animación de entrada. */
[data-deco] .rail__progress {
  stroke-dasharray: 1;
  stroke-dashoffset: calc(1 - var(--hx, 0));
  stroke: var(--gold-500);
  animation: none;
}

@keyframes deco-draw {
  to {
    stroke-dashoffset: 0;
  }
}

/* Sellos, rosetones y sunbursts: giro lentísimo y respirar dorado. */
.spin-slow {
  animation: spin 60s linear infinite;
  transform-origin: center;
}

.spin-slow--rev {
  animation-direction: reverse;
  animation-duration: 84s;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

.breathe {
  animation: breathe 5.5s var(--ease-in-out-quint) infinite;
}

@keyframes breathe {
  0%,
  100% {
    opacity: 0.45;
  }
  50% {
    opacity: 1;
  }
}

/* ============================================================== 4. TENSIÓN ==
   Marquesina cuya inclinación depende de la velocidad de scroll. No es
   decoración: es el sitio diciéndote que te has movido rápido.
   ======================================================================== */

.marquee {
  position: relative;
  z-index: var(--z-content);
  overflow: clip;
  padding-block: var(--space-m);
  border-block: 1px solid var(--line-soft);
  background: color-mix(in oklab, var(--ink-900) 55%, transparent);
}

.marquee__skew {
  transform: skewX(calc(var(--vel) * -5deg))
    scaleX(calc(1 + var(--vel-abs, 0) * 0.05));
  transition: transform 90ms linear;
}

.marquee__inner {
  display: flex;
  width: max-content;
  gap: var(--space-l);
  animation: marquee 42s linear infinite;
}

.marquee__inner span {
  font-family: var(--font-display);
  font-size: var(--step-3);
  line-height: 1;
  text-transform: uppercase;
  color: transparent;
  -webkit-text-stroke: 1px color-mix(in oklab, var(--gold-500) 62%, transparent);
  white-space: nowrap;
}

.marquee__inner span:nth-child(2n) {
  color: var(--blood-700);
  -webkit-text-stroke: 0;
}

@keyframes marquee {
  to {
    transform: translate3d(-50%, 0, 0);
  }
}

/* ---------------------------------------------------- glitch de radio -----
   Rotura de señal al entrar en una sección. Se aplica desde kinetic.js
   durante ~420 ms. El desdoblamiento RGB usa text-shadow, que es gratis.
   ------------------------------------------------------------------------ */

.tear {
  animation: tear 460ms steps(1, end) both;
}

@keyframes tear {
  0% {
    transform: none;
    clip-path: inset(0 0 0 0);
    text-shadow: none;
  }
  10% {
    transform: translate3d(-8px, 0, 0);
    clip-path: inset(12% 0 64% 0);
    text-shadow:
      4px 0 var(--blood-500),
      -4px 0 #2ad6c8;
  }
  22% {
    transform: translate3d(6px, 0, 0);
    clip-path: inset(58% 0 18% 0);
    text-shadow:
      -5px 0 var(--blood-500),
      3px 0 #2ad6c8;
  }
  34% {
    transform: translate3d(-3px, 0, 0);
    clip-path: inset(28% 0 44% 0);
    text-shadow: 2px 0 var(--blood-500);
  }
  48% {
    transform: translate3d(12px, 0, 0);
    clip-path: inset(70% 0 6% 0);
    text-shadow: -6px 0 #2ad6c8;
  }
  62% {
    transform: translate3d(-1px, 0, 0);
    clip-path: inset(4% 0 82% 0);
    text-shadow: none;
  }
  100% {
    transform: none;
    clip-path: inset(0 0 0 0);
    text-shadow: none;
  }
}

/* Barrido de escaneo vertical que cruza la pantalla en las transiciones. */
.scanbeam {
  position: fixed;
  inset: 0;
  z-index: calc(var(--z-grain) + 1);
  pointer-events: none;
  background: linear-gradient(
    to bottom,
    transparent 0%,
    color-mix(in oklab, var(--gold-300) 22%, transparent) 48%,
    color-mix(in oklab, var(--bone-100) 55%, transparent) 50%,
    color-mix(in oklab, var(--gold-300) 22%, transparent) 52%,
    transparent 100%
  );
  height: 34vh;
  transform: translateY(-40vh);
  opacity: 0;
}

.scanbeam.is-firing {
  animation: beam 900ms var(--ease-in-out-quint) both;
}

@keyframes beam {
  0% {
    transform: translateY(-40vh);
    opacity: 0;
  }
  15% {
    opacity: 1;
  }
  85% {
    opacity: 1;
  }
  100% {
    transform: translateY(110vh);
    opacity: 0;
  }
}

/* Vista de detalle abierta desde el cartel. Usa la View Transitions API
   cuando existe; si no, aparece sin transición, que sigue siendo correcto. */
.detail {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: grid;
  place-items: center;
  padding: var(--space-m);
  background: color-mix(in oklab, var(--ink-900) 88%, transparent);
  backdrop-filter: blur(14px);
}

.detail[hidden] {
  display: none;
}

.detail__panel {
  position: relative;
  width: min(680px, 100%);
  max-height: 84svh;
  overflow: auto;
  display: grid;
  gap: var(--space-s);
  padding: var(--space-l);
  background: var(--bg-raised);
  clip-path: var(--notch-clip);
}

.detail__close {
  justify-self: end;
}

::view-transition-old(card),
::view-transition-new(card) {
  animation-duration: 520ms;
  animation-timing-function: var(--ease-deco);
}

/* ============================================================== 5. CONTRATO
   prefers-reduced-motion no significa "un poco menos". Significa: quieto,
   legible y completo. Nada de contenido oculto esperando una animación que
   nunca va a llegar.
   ======================================================================== */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }

  .js [data-reveal],
  .js [data-reveal~="mask"] {
    opacity: 1 !important;
    transform: none !important;
    clip-path: none !important;
  }

  .kin__ch {
    opacity: 1 !important;
    transform: none !important;
    animation: none !important;
  }

  .marquee__skew {
    transform: none !important;
  }

  .marquee__inner {
    animation: none;
  }

  [data-deco] [pathLength]:not(.rail__progress) {
    stroke-dashoffset: 0 !important;
  }

  .spin-slow,
  .breathe,
  .hero__scroll i,
  .curtain__seal {
    animation: none !important;
  }

  .cursor {
    display: none;
  }
}

/* Equipos modestos o conexiones medidas: apagamos el ruido de fondo y
   dejamos la composición, que es lo que de verdad comunica. */
@media (prefers-reduced-data: reduce) {
  #grain,
  .veil {
    display: none;
  }
}

/* ------------------------------------------------- interruptor de movimiento
   El despliegue progresivo de las escenas y el programa horizontal se
   desmontan en layout.css, junto a su propio marcado. Aquí solo queda la
   marquesina, que es lo único que este archivo conoce.
   ------------------------------------------------------------------------ */

.motion-off .marquee__skew {
  transform: none;
}

.motion-off .marquee__inner {
  animation: none;
}

/* El interruptor ofrece el mismo contenido completo que la preferencia del
   sistema. Ambas clases viven en <html>, por eso no son dos descendientes. */
.js.motion-off [data-reveal],
.js.motion-off .paso,
.js.motion-off .paso--maska {
  opacity: 1 !important;
  transform: none !important;
  translate: none !important;
  clip-path: none !important;
  animation: none !important;
  transition: none !important;
}

.motion-off .kin__ch {
  opacity: 1 !important;
  transform: none !important;
  animation: none !important;
}

.motion-off [data-deco] [pathLength]:not(.rail__progress) {
  stroke-dashoffset: 0 !important;
  animation: none !important;
}

.motion-off .spin-slow,
.motion-off .breathe,
.motion-off .hero__scroll i,
.motion-off .curtain__seal,
.motion-off .tear,
.motion-off .scanbeam {
  animation: none !important;
}

.motion-off .testigo__ambiente,
.motion-off .testigo__cuñas,
.motion-off .desfile__cadena {
  display: none;
}
