/* ============================================================
   SPEACH. — motion.css · motor de movimiento (A-MOTION)
   No declara tokens (viven en speach.css) ni estiliza componentes.

   Regla del archivo: NADA NACE INVISIBLE. El reposo de cualquier
   pieza es su estado final legible; lo oculto sólo existe mientras
   el motor sostiene .will-animate, que JS pone ~260px antes de que
   el elemento entre. Si motion.js no carga o falta soporte de una
   API, el sitio se ve completo.
   ============================================================ */

/* ---- 1. Telón. Sin el guardián del <head> no existe. Entrada 100% CSS. ---- */
.ld { display: none; }
/* El telón lleva data-mode="ink" en el markup: los tokens semánticos se
   invierten solos y el logotipo nace blanco sobre la noche, sin un solo color
   escrito a mano. background:none porque §[data-mode="ink"] pinta
   --grad-tinta SOBRE el elemento, y esa capa no la clipa nadie: la tinta que
   se retira tiene que ser .ld-fondo y sólo ella.                             */
html.has-ld .ld {
  display: grid; position: fixed; inset: 0; z-index: var(--z-loader); contain: layout style;
  place-content: center center; justify-items: center; gap: clamp(18px, 4vh, 44px);
  background: none; color: var(--fg); pointer-events: none;
}
html.is-loading { overflow: hidden; }

/* LA TINTA. Es la única capa que se retira, y se retira HACIA ABAJO
   (inset(100% 0 0 0)): revelando primero el techo, que es justo donde el
   logotipo va a aterrizar — si cayera al revés, el telón seguiría tapando la
   esquina en el fotograma del aterrizaje. El clip-path vive aquí y no en .ld
   por lo mismo: clipar al padre decapitaría al logotipo en pleno vuelo.      */
.ld-fondo {
  position: absolute; inset: 0; z-index: -1;
  background: var(--grad-tinta); clip-path: inset(0);
  transition: clip-path var(--t-5) var(--e-snap);
}
.ld-fondo::after {
  content: ''; position: absolute; inset: 0;
  background: var(--grad-cielo); opacity: .06;
}
html.is-vuelo .ld-fondo { clip-path: inset(100% 0 0 0); }

/* LA ESCENA: la esfera y, debajo, el logotipo. La esfera y su halo van en el
   mismo hueco de rejilla (.ld-orbe) para que el resplandor viva detrás sin
   empujar el layout.                                                         */
.ld-escena { position: relative; display: grid; justify-items: center;
  gap: clamp(4px, 1.4vh, 22px); }
.ld-orbe { position: relative; display: grid; place-items: center;
  animation: ld-flota 5.5s var(--e-inout) var(--t-6) infinite alternate; }
/* El vaivén vive en el ENVOLTORIO y no en la imagen a propósito: así la
   salida (que sí anima la imagen) se compone con él en vez de pisarlo, y no
   hay el tirón de un translate que se reinicia al cortar la animación.       */
@keyframes ld-flota { to { translate: 0 -2.5%; rotate: 2deg; } }
.ld-halo {
  position: absolute; width: 190%; aspect-ratio: 1; border-radius: var(--r-full);
  background: radial-gradient(circle,
    color-mix(in srgb, var(--accent) 62%, transparent) 0%,
    color-mix(in srgb, var(--accent) 18%, transparent) 42%,
    transparent 66%);
  filter: blur(34px); pointer-events: none;
  animation: ld-halo-nace var(--t-7) var(--e-out) both;
}
@keyframes ld-halo-nace { from { opacity: 0; scale: .5; } to { opacity: .85; scale: 1; } }
.ld-esfera {
  width: clamp(148px, 27vh, 296px); max-width: 58vw; aspect-ratio: 760 / 706;
  background: url(/assets/img/ld-esfera.webp) center / contain no-repeat;
  animation: ld-esf-nace var(--t-7) var(--e-back) both;
}
@keyframes ld-esf-nace {
  from { opacity: 0; scale: .45; translate: 0 9%; filter: blur(18px); }
  to   { opacity: 1; scale: 1;   translate: 0 0;  filter: blur(0);    }
}

/* EL LOGOTIPO. Hereda todo de .brand (fuente, versales, tracking 0, la
   línea base): aquí sólo crece. Es lo que hace posible el vuelo — el nodo
   que despega y el que aterriza son tipográficamente el mismo, así que
   basta una escala uniforme.                                                 */
.ld-word { font-size: var(--t-mega); }
/* .16em de máscara: los descendentes de CoFo Meteor bajan a .15em (medido
   con fontBoundingBoxDescent) y con .12em la p salía rasurada.               */
.ld-l { display: inline-block; overflow: hidden; vertical-align: top; padding-bottom: .16em; margin-bottom: -.16em; }
.ld-l > i {
  display: block; font-style: normal;
  animation: ld-sube var(--t-5) var(--e-expo) both;
  animation-delay: calc(var(--t-3) + var(--i, 0) * var(--st-0));   /* el shorthand va antes o pisa el delay */
}
/* El escalón se calcula aquí, no en el HTML: la CSP (style-src 'self') bloquea
   el style="--i:0" del markup de §5.10 y las seis letras entrarían a la vez. */
.ld-l:nth-child(1) { --i: 0; } .ld-l:nth-child(2) { --i: 1; } .ld-l:nth-child(3) { --i: 2; }
.ld-l:nth-child(4) { --i: 3; } .ld-l:nth-child(5) { --i: 4; } .ld-l:nth-child(6) { --i: 5; }
@keyframes ld-sube { from { translate: 0 var(--m-line); } to { translate: 0 0; } }
/* El punto ya no es un círculo dibujado sino el punto de verdad del
   logotipo: tiene que ser el mismo glifo que el de la esquina o el vuelo
   aterrizaría con dos puntos distintos.                                      */
.ld-dot {
  display: inline-block;
  animation: ld-punto var(--t-4) var(--e-back) both;
  animation-delay: calc(var(--t-3) + 6 * var(--st-0));
}
@keyframes ld-punto { from { translate: 0 -.5em; scale: .2; opacity: 0; } to { translate: 0 0; scale: 1; opacity: 1; } }
.ld-tag {
  font-size: var(--t-caption); color: var(--fg-muted); letter-spacing: var(--ls-ui);
  animation: sp-fade var(--t-5) var(--e-out) both; animation-delay: var(--t-5);
}
@keyframes sp-fade { from { opacity: 0; } to { opacity: 1; } }

/* ---- 1bis. LA SALIDA. Dos gestos encadenados que motion.js §telón dispara
   con dos clases y tres medidas (--ld-e*: el viaje de la esfera; --ld-w*: el
   del logotipo). Nada de esto puede vivir sólo en CSS: las tres medidas
   dependen de dónde está de verdad la barra.

   1) is-loaded — LA ESFERA SE VUELVE EL PUNTO. Se encoge y se va al punto
      final del logotipo, y se apaga ANTES de llegar del todo: así el vaivén
      del envoltorio, que sigue corriendo, nunca delata los pocos píxeles que
      le falten. El punto responde con un respingo: recibe la esfera.
   2) is-vuelo — EL LOGOTIPO TOMA SU SITIO. Escala pura desde la esquina
      superior izquierda del nodo hasta la del .brand de la barra, mientras
      la tinta cae. Aterriza y motion.js quita .has-ld: el .brand de verdad
      —oculto hasta ese instante— aparece exactamente debajo. Como los dos
      nodos son el mismo texto con la misma tipografía, el relevo no se ve. */
html.is-loaded .ld-esfera {
  animation: ld-esf-va var(--t-4) var(--e-in) both;
}
/* La opacidad muere en el 82%, no en el 100%: los últimos píxeles del viaje
   son los únicos donde el vaivén del envoltorio podría notarse, y ahí ya no
   hay nada que ver.                                                          */
@keyframes ld-esf-va {
  0%   { translate: 0 0; scale: 1; opacity: 1; filter: blur(0); }
  82%  { opacity: 0; }
  100% { translate: var(--ld-ex, 0) var(--ld-ey, 0); scale: var(--ld-ek, .06);
         opacity: 0; filter: blur(7px); }
}
html.is-loaded .ld-halo { animation: ld-halo-va var(--t-3) var(--e-in) both; }
@keyframes ld-halo-va { from { opacity: .85; } to { opacity: 0; scale: .55; } }
html.is-loaded .ld-tag  { animation: ld-tag-va var(--t-3) var(--e-in) both; }
@keyframes ld-tag-va { to { opacity: 0; translate: 0 12px; } }
/* El respingo termina en 760 ms, y el vuelo no arranca hasta los 800: tiene
   que caber entero antes, porque al despegar motion.js cambia las letras por
   el texto llano del logotipo y cualquier animación en curso se cortaría.    */
html.is-loaded .ld-dot { animation: ld-punto-recibe var(--t-4) var(--e-back) var(--t-3) both; }
@keyframes ld-punto-recibe { 0% { scale: 1 } 38% { scale: 1.34 } 100% { scale: 1 } }

html.is-vuelo .ld-word {
  transform-origin: 0 0;
  transition: translate var(--t-5) var(--e-expo), scale var(--t-5) var(--e-expo);
  translate: var(--ld-wx, 0) var(--ld-wy, 0);
  scale: var(--ld-wk, 1);
}
/* El logotipo de la barra espera apagado mientras hay telón. Sin transición:
   el cambio ocurre en el mismo fotograma en que se retira el que volaba. */
html.has-ld .hd .brand { opacity: 0; }

/* ---- 2. Revelados. NO usan view(): atar la opacidad al scroll deja el texto
   a media tinta si el usuario se detiene, y eso es un fallo de 1.4.3 sobre
   contenido legible. Van por IO + transición. ---- */
.rv, .rv-group > * {
  transition: opacity var(--t-5) var(--e-out), translate var(--t-5) var(--e-out), scale var(--t-5) var(--e-out), clip-path var(--t-5) var(--e-expo);
}
.rv-group > * { transition-delay: calc(min(var(--i, 0), var(--st-cap)) * var(--st-2)); }

.rv.will-animate:not(.in), .rv-group.will-animate:not(.in) > * { opacity: 0; translate: 0 var(--m-3); transition: none; will-change: opacity, translate; }
.rv[data-rv="fade"].will-animate:not(.in) { translate: none; }
.rv[data-rv="scale"].will-animate:not(.in) { translate: none; scale: var(--s-press); }
.rv[data-rv="clip"].will-animate:not(.in) { opacity: 1; translate: none; clip-path: inset(0 0 100% 0); will-change: clip-path; }

/* Escalones: enum cerrado, CSS no lee atributos. */
.rv[data-rv-delay="1"] { transition-delay: var(--st-2); }
.rv[data-rv-delay="2"] { transition-delay: calc(2 * var(--st-2)); }
.rv[data-rv-delay="3"] { transition-delay: calc(3 * var(--st-2)); }
.rv[data-rv-delay="4"] { transition-delay: calc(4 * var(--st-2)); }
.rv[data-rv-delay="5"] { transition-delay: calc(5 * var(--st-2)); }
.rv[data-rv-delay="6"] { transition-delay: calc(6 * var(--st-2)); }

/* ---- 3. Splitter. El texto sigue siendo texto: espacios como nodos reales
   (selección y copiado intactos), nombre en aria-label, fragmentos aria-hidden. -- */
.is-split { text-wrap: pretty; }
/* La máscara sólo debe cortar en vertical: el padding lateral (compensado con
   margen negativo, así el avance del texto no cambia) deja salir el volado de
   la cursiva —la 'r' de "decir"— que si no se corta contra el borde del clip. */
.sp-ln { display: block; overflow: hidden; padding-bottom: .14em; margin-bottom: -.14em; padding-inline: .12em; margin-inline: -.12em; }
.sp-w { display: inline-block; overflow: hidden; vertical-align: top; padding-bottom: .14em; margin-bottom: -.14em; padding-inline: .12em; margin-inline: -.12em; }
/* El delay se congela en el 8º fragmento: si no, un titular de 14
   palabras tarda casi un segundo en empezar la última. */
.sp-w > i, .sp-ln > span {
  display: inline-block; font-style: inherit; transition: translate var(--t-6) var(--e-expo);
  transition-delay: calc(min(var(--i, 0), var(--st-cap)) * var(--st));
}
.is-split.will-animate:not(.in) .sp-w > i, .is-split.will-animate:not(.in) .sp-ln > span { translate: 0 var(--m-line); transition: none; will-change: translate; }
[data-split-dir="down"].will-animate:not(.in) .sp-w > i,
[data-split-dir="down"].will-animate:not(.in) .sp-ln > span { translate: 0 calc(var(--m-line) * -1); }
[data-split="lines"] .sp-w { overflow: visible; padding-bottom: 0; margin-bottom: 0; }
[data-split="lines"] .sp-w > i { translate: none; transition: none; }
.sp-c { display: inline-block; }

/* ---- 4. Peso variable (D6). font-weight: var(--wght), NUNCA
   font-variation-settings: anularía <strong> y font-synthesis del subárbol.
   Sin CLS: el motor sólo deja el atributo en titulares que ya caben en
   una línea a peso 800, el estado más ancho. ---- */
[data-wght] { --wght: 800; font-weight: var(--wght); }
/* Camino nativo. Aquí sí es correcto atarlo al scroll: un peso
   intermedio sigue siendo texto legible. */
@supports (animation-timeline: view()) {
  html.js [data-wght] { animation: sp-wght var(--e-lin) both; animation-timeline: view(); animation-range: entry 5% cover 45%; }
}
@keyframes sp-wght { from { --wght: 300; } to { --wght: 800; } }
/* Respaldo por IO. El reposo va FUERA del @supports: sin soporte el
   titular se ve en 800, no en 300. */
[data-wght].will-animate:not(.in) { --wght: 300; }
[data-wght].in { transition: --wght var(--t-6) var(--e-out); }
[data-wght-hover] { --wght: var(--wght-ui); font-weight: var(--wght); transition: --wght var(--t-3) var(--e-out); }
[data-wght-hover]:hover, [data-wght-hover]:focus-within { --wght: var(--wght-display); }

/* ---- 5. Parallax y zoom. Sólo translate/scale; el factor viaja en --pf. -- */
@supports (animation-timeline: view()) {
  html.js:not(.rm):not(.low) [data-parallax] { animation: sp-px var(--e-lin) both; animation-timeline: view(); animation-range: cover; }
  html.js:not(.rm) [data-zoom] { animation: sp-zoom var(--e-lin) both; animation-timeline: view(); animation-range: cover; }
}
@keyframes sp-px { from { translate: 0 calc(var(--pf, .2) * var(--m-px)); } to { translate: 0 calc(var(--pf, .2) * var(--m-px) * -1); } }
@keyframes sp-zoom { from { scale: 1; } to { scale: var(--s-zoom); } }

/* ---- 6. Barra de lectura, CSS puro. Sin scroll timelines no se muestra:
   una barra que miente es peor que ninguna. ---- */
/* Desde el 2026-08-02 el cristal del header es una cápsula flotante: la barra
   de progreso adopta sus mismos márgenes laterales y se redondea, como el
   líquido llenando el borde inferior del globo. */
.pbar { position: absolute; inset: auto calc(clamp(12px, 1.6vw, 24px) + 26px) 4px calc(clamp(12px, 1.6vw, 24px) + 8px); height: 2px; border-radius: var(--r-full); background: var(--accent); transform-origin: 0 50%; scale: 0 1; pointer-events: none; }
@supports (animation-timeline: scroll()) {
  .pbar { animation: sp-pbar var(--e-lin) both; animation-timeline: scroll(root block); }
}
@keyframes sp-pbar { from { scale: 0 1; } to { scale: 1 1; } }

/* ---- 7. Swap: tarjetas que cambian de imagen cada 250 ms (§hero).
   El motor (motion.js §swap) sólo mueve la clase .on; el estado vive aquí.
   Sin JS la primera imagen queda puesta —nada nace invisible— y con rm el
   motor ni arranca: la tarjeta es un cuadro fijo. CORTE SECO a propósito,
   sin fundido: dos láminas a media opacidad se leen como doble exposición
   sobre el B/N; la referencia cambia de golpe, como un carrusel de deck. ---- */
[data-swap] img { opacity: 0; }
[data-swap] img:first-child { opacity: 1; }
html.js [data-swap] img:not(.on) { opacity: 0; }
html.js [data-swap] img.on { opacity: 1; }

/* ---- 8. Marquee lineal (D7). El motor duplica con cloneNode. ---- */
@keyframes sp-mq { from { translate: 0 0; } to { translate: -50% 0; } }
[data-marquee]:hover > * { animation-play-state: paused; }

/* ---- 9. Grano. pointer-events:none e isolation:isolate son obligatorios. -- */
.grain {
  position: fixed; inset: 0; z-index: var(--z-grain); pointer-events: none; isolation: isolate;
  opacity: var(--grain); background: url('/assets/img/tex-grain.svg') repeat 0 0 / 256px 256px;
}

/* ---- 10. Transiciones de página. Header y logo salen del snapshot de root:
   quedan quietos mientras el contenido cambia debajo. ---- */
@view-transition { navigation: auto; }
::view-transition-group(root) { animation-duration: var(--t-4); }
::view-transition-old(root) { animation: vt-out var(--t-3) var(--e-in) both; }
::view-transition-new(root) { animation: vt-in var(--t-4) var(--e-expo) both; }
@keyframes vt-out { to { opacity: 0; translate: 0 -14px; } }
@keyframes vt-in { from { opacity: 0; clip-path: inset(0 0 100% 0); } }
.hd { view-transition-name: sp-header; }
.hd .brand-logo { view-transition-name: sp-logo; }
.pbar, .grain, .ld { view-transition-name: none; }
::view-transition-old(sp-header), ::view-transition-new(sp-header),
::view-transition-old(sp-logo), ::view-transition-new(sp-logo) { animation: none; mix-blend-mode: normal; }

/* ---- 11. Paso claro → tinta: CORTE limpio. El degradado difuminado que vivía
   aquí ensuciaba ambas secciones y se retiró en el rediseño. ---- */

/* ---- 12. Reduced motion. No es sitio muerto: se conservan color y opacidad.
   Lo innegociable es que TODO el contenido quede visible; el motor tampoco
   marca .will-animate en este modo. ---- */
@media (prefers-reduced-motion: reduce) {
  *:not([data-rm-keep]), *:not([data-rm-keep])::before, *:not([data-rm-keep])::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important; scroll-behavior: auto !important;
  }
  .pbar { animation-duration: auto !important; }
  /* Red de seguridad: nada puede quedarse oculto. */
  .rv, .rv-group > *, .rv.will-animate:not(.in), .rv-group.will-animate:not(.in) > * { opacity: 1; translate: none; scale: none; clip-path: none; will-change: auto; }
  .sp-w > i, .sp-ln > span { translate: none !important; transition: none; }
  [data-parallax], [data-zoom] { animation: none !important; translate: none; scale: none; }
  /* animation:none o el keyframe atado al scroll deja el peso a media escala. */
  [data-wght] { animation: none !important; --wght: 800; }
  [data-marquee] > * { animation: none !important; }
  .ld { display: none !important; }
  @view-transition { navigation: none; }
}
