/* ============================================================
   MOVIMIENTO — gramática común del movimiento de la pieza
   Dalí, Genio Audiovisual de AIONOVA · 2026-09-03
   Presentación Claudia Restrepo / CREATE

   QUÉ ES
   Hasta hoy cada lámina inventaba sus propias animaciones sueltas
   (s05subir, s06entrar, s06respirar…). Este archivo es la gramática:
   un juego corto de curvas, un revelado orquestado por atributo, una
   tipografía que se COMPONE (no que hace fade), una luz común para
   toda la pieza y respuesta al puntero. Las secciones consumen; no
   vuelven a inventar.

   TRES LEYES QUE ESTE ARCHIVO NO ROMPE
   1 · CERO RED. Nada externo. Nada que descargar.
   2 · ESTADO BASE VISIBLE. El reposo de todo elemento es VISIBLE. Las
       animaciones parten de su propio from{} y solo adornan. Si se
       congelan todas con
         document.getAnimations().forEach(a=>a.cancel())
       la lámina queda COMPLETA. Ninguna animación de este archivo
       depende de que JS añada una clase para que algo exista.
   3 · CERO COLOR LITERAL. Todo sale de var(--token) de tokens-create.css.

   DOCTRINA DE PROPIEDADES — importante para quien integre
   Ninguna animación de este archivo toca `transform`. Usa las
   propiedades independientes `translate`, `scale` y `rotate`, que
   COMPONEN con el `transform` que la sección ya tenga puesto
   (posicionamientos translate(-50%,-50%), hovers de elevacion.css…).
   Así nada se pisa. Si el motor no soporta `translate`/`scale`, esas
   líneas se ignoran solas y queda el fade: degradación, no rotura.

   ORDEN DE CARGA
     tokens-create.css → elevacion.css → movimiento.css → <style> local
   ============================================================ */

:root{
  /* Centinela de presencia. movimiento.js NO trocea un titular si esto
     no vale 1: sin este CSS, el troceado dejaría el texto invisible. */
  --mv-ok: 1;
}

/* ============================================================
   1 · CURVAS Y TIEMPOS — el acento con el que habla la pieza
   ============================================================ */
:root{
  /* Entrada: llega rápido y frena largo. Lo que aparece, se posa. */
  --mv-entrada: cubic-bezier(.22,.61,.36,1);
  /* Salida: se va sin despedirse. Arranca lento, acelera, desaparece. */
  --mv-salida: cubic-bezier(.55,.06,.68,.19);

  /* Resorte suave — muelle amortiguado real (ζ=.78, ω=9.5), sobreimpulso 2%.
     Se pasa apenas de largo y se acomoda: peso, no rebote. */
  --mv-resorte-suave: linear(0, 0.0551 3.85%, 0.1814 7.69%, 0.3351 11.54%, 0.4890 15.38%, 0.6275 19.23%, 0.7433 23.08%, 0.8346 26.92%, 0.9029 30.77%, 0.9513 34.62%, 0.9835 38.46%, 1.0035 42.31%, 1.0144 46.15%, 1.0191 50%, 1.0198 53.85%, 1.0183 57.69%, 1.0155 61.54%, 1.0124 65.38%, 1.0094 69.23%, 1.0068 73.08%, 1.0046 76.92%, 1.0029 80.77%, 1.0016 84.62%, 1.0008 88.46%, 1.0002 92.31%, 0.9999 96.15%, 1);

  /* Resorte firme — (ζ=.58, ω=12.5), sobreimpulso 10.7%.
     Para lo que responde al dedo: acuse de recibo físico. */
  --mv-resorte-firme: linear(0, 0.0950 3.85%, 0.3067 7.69%, 0.5484 11.54%, 0.7652 15.38%, 0.9302 19.23%, 1.0368 23.08%, 1.0914 26.92%, 1.1068 30.77%, 1.0970 34.62%, 1.0746 38.46%, 1.0488 42.31%, 1.0255 46.15%, 1.0078 50%, 0.9963 53.85%, 0.9903 57.69%, 0.9886 61.54%, 0.9896 65.38%, 0.9920 69.23%, 0.9947 73.08%, 0.9972 76.92%, 0.9991 80.77%, 1.0004 84.62%, 1.0010 88.46%, 1.0012 92.31%, 1.0011 96.15%, 1);

  /* Duraciones con nombre. La pieza se proyecta y se narra en voz alta:
     lo lento aquí es virtud, no descuido. */
  --mv-t-toque:   140ms;  /* acuse de un control */
  --mv-t-corto:   260ms;  /* estado que cambia */
  --mv-t-medio:   440ms;  /* panel que abre */
  --mv-t-largo:   760ms;  /* elemento que entra a la lámina */
  --mv-t-relato: 1120ms;  /* titular que se compone */
  --mv-t-respiro: 108s;   /* el papel, que nunca se está del todo quieto */

  /* Escalonado por defecto del revelado. */
  --mv-escalon: 74ms;
  --mv-escalon-palabra: 62ms;
  --mv-escalon-letra: 26ms;
}

/* ============================================================
   2 · REVELADO ORQUESTADO — la lámina se arma sola, en orden
   Declarativo, por atributo, sin una clase por elemento:

     <div data-mv-reveal="subir"> … hijos … </div>

   El índice lo pone el CSS (nth-child). El disparo es la ENTRADA A LA
   LÁMINA — que en esta pieza sin scroll es la carga del documento —, no
   el scroll. IntersectionObserver es opt-in (§2.3), para panel interno.
   ============================================================ */

/* Índice de hermano, sin heredar: cada hijo trae el suyo. */
@property --mv-i{ syntax:"<number>"; inherits:false; initial-value:0; }

[data-mv-reveal] > *{
  animation-duration: var(--mv-t-largo);
  animation-timing-function: var(--mv-entrada);
  animation-delay: calc(var(--mv-arranque, 0ms) + var(--mv-i) * var(--mv-escalon));
  /* `backwards` sostiene el from{} durante el retardo. Es seguro porque
     esta animación la aplica el CSS solo, sin gate de JS: siempre corre.
     Y si se cancelan todas, el reposo es visible. */
  animation-fill-mode: backwards;
}
/* Escalera de índices. 24 escalones cubren cualquier lámina de esta
   pieza; a partir de ahí todos entran juntos en el último escalón. */
[data-mv-reveal] > :nth-child(1){--mv-i:0}   [data-mv-reveal] > :nth-child(2){--mv-i:1}
[data-mv-reveal] > :nth-child(3){--mv-i:2}   [data-mv-reveal] > :nth-child(4){--mv-i:3}
[data-mv-reveal] > :nth-child(5){--mv-i:4}   [data-mv-reveal] > :nth-child(6){--mv-i:5}
[data-mv-reveal] > :nth-child(7){--mv-i:6}   [data-mv-reveal] > :nth-child(8){--mv-i:7}
[data-mv-reveal] > :nth-child(9){--mv-i:8}   [data-mv-reveal] > :nth-child(10){--mv-i:9}
[data-mv-reveal] > :nth-child(11){--mv-i:10} [data-mv-reveal] > :nth-child(12){--mv-i:11}
[data-mv-reveal] > :nth-child(13){--mv-i:12} [data-mv-reveal] > :nth-child(14){--mv-i:13}
[data-mv-reveal] > :nth-child(15){--mv-i:14} [data-mv-reveal] > :nth-child(16){--mv-i:15}
[data-mv-reveal] > :nth-child(17){--mv-i:16} [data-mv-reveal] > :nth-child(18){--mv-i:17}
[data-mv-reveal] > :nth-child(19){--mv-i:18} [data-mv-reveal] > :nth-child(20){--mv-i:19}
[data-mv-reveal] > :nth-child(21){--mv-i:20} [data-mv-reveal] > :nth-child(22){--mv-i:21}
[data-mv-reveal] > :nth-child(23){--mv-i:22} [data-mv-reveal] > :nth-child(n+24){--mv-i:23}

/* 2.1 · Los cinco modos de entrar. */
/* Sube y se posa: el modo por defecto, para texto y tarjetas. */
[data-mv-reveal=""] > *,
[data-mv-reveal="subir"] > *{ animation-name: mv-subir; }
/* Se enfoca desde el desenfoque: para fotografía y bloques con imagen. */
[data-mv-reveal="velo"] > *{ animation-name: mv-velo; }
/* Nace desde su propio centro: para esferas y nodos del mapa. */
[data-mv-reveal="nacer"] > *{ animation-name: mv-nacer; animation-timing-function: var(--mv-resorte-suave); }
/* Solo aparece, sin moverse un pixel: para lo que ya está colocado con
   precisión y no admite que nada le toque la posición. */
[data-mv-reveal="aparecer"] > *{ animation-name: mv-aparecer; }
/* La línea se traza de izquierda a derecha: cenefas, filetes, reglas. */
[data-mv-reveal="filete"] > *{ animation-name: mv-filete; transform-origin: left center; }

/* 2.2 · Excepciones y ajustes de mano.
   [data-mv-quieto] saca a un hijo de la coreografía sin sacarlo del grupo.
   style="--mv-i:7"        reordena cuando el orden visual no es el del DOM.
   style="--mv-arranque:400ms" en el contenedor lo entra después de otro. */
[data-mv-reveal] > [data-mv-quieto]{ animation: none !important; }

@keyframes mv-subir{    from{ opacity:0; translate:0 14px; } }
@keyframes mv-velo{     from{ opacity:0; translate:0 8px; filter:blur(7px); } }
@keyframes mv-nacer{    from{ opacity:0; scale:.9; } }
@keyframes mv-aparecer{ from{ opacity:0; } }
@keyframes mv-filete{   from{ opacity:0; scale:0 1; } }
/* Para retirar algo por JS justo antes de quitarlo del DOM. */
@keyframes mv-desvanecer{ to{ opacity:0; translate:0 -6px; } }
.mv-saliendo{ animation: mv-desvanecer var(--mv-t-corto) var(--mv-salida) forwards; }

/* 2.3 · Disparo por visibilidad — OPT-IN, solo donde tenga sentido
   (un panel que scrollea por dentro, una bandeja que se abre).
   El estado oculto SOLO existe cuando movimiento.js ya armó el
   contenedor; sin JS, o con JS roto, nunca se arma y todo se ve.
   Además el JS lleva temporizador de rescate: si el observer no
   dispara, revela igual. */
[data-mv-observar][data-mv-armado] > *{ animation-play-state: paused; opacity: 0; }
[data-mv-observar][data-mv-armado][data-mv-dentro] > *{ animation-play-state: running; opacity: 1; }

/* ============================================================
   3 · TIPOGRAFÍA VARIABLE EN MOVIMIENTO — el sello de la pieza
   No es texto que hace fade: es tipografía que se COMPONE. El peso y el
   tamaño óptico se asientan en el sitio, como si el titular estuviera
   decidiendo su corte mientras llega.
   VERIFICADO EN EL BINARIO (fontTools sobre fraunces-latin.woff2): el
   subset embarcado trae DOS ejes reales, opsz (9–144) y wght (100–900).
   NO trae SOFT. La línea "SOFT" se deja escrita porque un eje desconocido
   se ignora sin romper nada y el día que entre la familia completa el
   efecto gana solo; pero hoy quien hace el trabajo es opsz, y opsz basta:
   de 14 a 96 la letra cambia de corte, no de tamaño.
   ============================================================ */

/* Registradas: sin @property un eje tipográfico no interpola, salta. */
@property --mv-wght{ syntax:"<number>"; inherits:true; initial-value:420; }
@property --mv-opsz{ syntax:"<number>"; inherits:true; initial-value:96; }
@property --mv-soft{ syntax:"<number>"; inherits:true; initial-value:0; }

/* El reposo YA es el estado final (420/96): si nada anima, el titular está
   compuesto y correcto. Los pesos viven dentro de la banda 380–440 de la
   pieza también DURANTE el recorrido: nunca asoma un bold.

   AVISO AL QUE INTEGRA — font-variation-settings MANDA sobre font-weight.
   Poner .mv-componer fija el peso en reposo a --mv-wght-a (420) y tu
   font-weight local deja de oírse. Si tu titular estaba en otro peso,
   dilo aquí y no en font-weight:
       style="--mv-wght-a:400"     (y sube --mv-wght-de si quieres menos viaje)
   Fuera de esa banda no: 380–440 es ley de la pieza. */
.mv-componer,
.mv-componer .mv-parte{
  font-family: var(--display);
  font-variation-settings: "opsz" var(--mv-opsz), "wght" var(--mv-wght), "SOFT" var(--mv-soft);
}
/* Los extremos del recorrido, declarados aquí y no solo como respaldo
   dentro del keyframe: así el valor que interpola el motor es siempre un
   número ya resuelto. Se pisan por elemento con style="--mv-opsz-de:40".
   Peso: 386 → 420. Ambos extremos dentro de la banda 380–440 de la pieza. */
.mv-componer{
  --mv-wght-de: 386;  --mv-wght-a: 420;
  --mv-opsz-de: 14;   --mv-opsz-a: 96;
  --mv-soft-de: 58;
  --mv-track-de: .05em; --mv-track-a: -.016em;
  --mv-alza: .22em;
}

@keyframes mv-componer{
  from{
    opacity: 0;
    --mv-wght: var(--mv-wght-de, 386);
    --mv-opsz: var(--mv-opsz-de, 14);
    --mv-soft: var(--mv-soft-de, 58);
    letter-spacing: var(--mv-track-de, .05em);
    translate: 0 var(--mv-alza, .22em);
  }
  62%{ opacity: 1; }
  to{
    opacity: 1;
    --mv-wght: var(--mv-wght-a, 420);
    --mv-opsz: var(--mv-opsz-a, 96);
    --mv-soft: 0;
    letter-spacing: var(--mv-track-a, -.016em);
    translate: 0 0;
  }
}

/* Titular entero: se compone de una sola pieza.
   El retardo suma su índice, así que un titular que además sea hijo de un
   [data-mv-reveal] entra en su turno de la coreografía, no por su cuenta. */
.mv-componer{
  animation: mv-componer var(--mv-t-relato) var(--mv-entrada)
             calc(var(--mv-arranque, 0ms) + var(--mv-i, 0) * var(--mv-escalon))
             backwards;
}

/* 3.1 · Partición por palabra o por letra.
   El troceado lo hace movimiento.js y va aria-hidden; el texto real
   queda íntegro para el lector de pantalla en .mv-solo-lectura.
   Cuando el titular está partido la animación vive en las partes, no en
   el contenedor: por eso se apaga aquí. */
.mv-componer[data-mv-partido]{ animation: none; }
.mv-componer .mv-parte{
  --mv-i: 0;
  display: inline-block;
  white-space: pre;
  animation: mv-componer var(--mv-t-relato) var(--mv-entrada)
             calc(var(--mv-arranque, 0ms) + var(--mv-i) * var(--mv-paso, var(--mv-escalon-palabra)))
             backwards;
}
/* Modo letra: paso corto y SIN recorrido de tracking. Animar los ejes ya
   obliga a recomponer la línea cada cuadro; sumarle letter-spacing en
   decenas de trozos es pagar dos veces por el mismo efecto.
   PRESUPUESTO: el modo letra es para titulares cortos (una línea, ~40
   signos). Para un párrafo o un titular largo, modo palabra. */
.mv-componer[data-mv-partido="letra"] .mv-parte{
  --mv-paso: var(--mv-escalon-letra);
  --mv-track-de: var(--mv-track-a, -.016em);
}

/* Texto real: invisible a la vista, íntegro para tecnología asistiva. */
.mv-solo-lectura{
  position:absolute; width:1px; height:1px; overflow:hidden;
  clip-path: inset(50%); white-space: nowrap;
}

/* ============================================================
   4 · LUZ Y PROFUNDIDAD
   ============================================================ */

/* 4.1 · UNA SOLA FUENTE DE LUZ para toda la pieza.
   Declarada como ángulo (0deg = arriba, gira en sentido horario). En
   315deg la luz entra por el hombro izquierdo, que es de donde ya venía
   el brillo especular de las esferas de elevacion.css. Todas las sombras
   caen entonces hacia el mismo lado y el ojo lee un objeto sólido en vez
   de un collage. */
:root{
  --mv-luz-ang: 315deg;
  --mv-luz-x: .707;   /* respaldo estático para 315deg */
  --mv-luz-y: .707;
  --mv-sombra-color: var(--accent-tint);  /* token con alfa: siempre válido */
}
@supports (top: calc(sin(45deg) * 1px)){
  :root{
    --mv-luz-x: calc(-1 * sin(var(--mv-luz-ang)));
    --mv-luz-y: calc(cos(var(--mv-luz-ang)));
  }
}
@supports (color: color-mix(in srgb, currentColor 10%, transparent)){
  :root{ --mv-sombra-color: color-mix(in srgb, var(--ink) 13%, transparent); }
}

/* Tres alturas sobre el papel. Se suman a --el-s* de elevacion.css: esa
   capa da el aire, esta da la DIRECCIÓN. */
.mv-elev-1{ box-shadow: var(--el-s1), calc(var(--mv-luz-x) * 3px)  calc(var(--mv-luz-y) * 3px)   8px var(--mv-sombra-color); }
.mv-elev-2{ box-shadow: var(--el-s2), calc(var(--mv-luz-x) * 7px)  calc(var(--mv-luz-y) * 7px)  20px var(--mv-sombra-color); }
.mv-elev-3{ box-shadow: var(--el-s3), calc(var(--mv-luz-x) * 14px) calc(var(--mv-luz-y) * 14px) 40px var(--mv-sombra-color); }

/* Para que una superficie curva reciba la misma luz que las esferas. */
.mv-luz-especular{ position: relative; }
.mv-luz-especular::after{
  content:""; position:absolute; inset:0; border-radius:inherit; pointer-events:none;
  background: radial-gradient(46% 34% at
      calc(50% - var(--mv-luz-x) * 26%) calc(50% - var(--mv-luz-y) * 34%),
      var(--paper-2) 0%, transparent 70%);
  opacity:.7;
}

/* 4.2 · EL PAPEL RESPIRA.
   Malla de tres focos de luz que derivan lentísimo por el fondo. La
   lámina deja de ser una superficie muerta sin que nadie pueda señalar
   qué se está moviendo.
   PRESUPUESTO DE RENDER: un degradado a pantalla completa repintado a
   60 fps es caro en un portátil flojo. Por eso la interpolación va en
   steps(360): el recorrido dura 108 s y solo repinta ~3 veces por
   segundo. Cada escalón mueve centésimas de porcentaje — invisible al
   ojo, barato para la GPU. */
@property --mv-m1x{ syntax:"<percentage>"; inherits:false; initial-value:16%; }
@property --mv-m1y{ syntax:"<percentage>"; inherits:false; initial-value:14%; }
@property --mv-m2x{ syntax:"<percentage>"; inherits:false; initial-value:84%; }
@property --mv-m2y{ syntax:"<percentage>"; inherits:false; initial-value:32%; }
@property --mv-m3x{ syntax:"<percentage>"; inherits:false; initial-value:52%; }
@property --mv-m3y{ syntax:"<percentage>"; inherits:false; initial-value:92%; }
@property --mv-malla-giro{ syntax:"<angle>"; inherits:false; initial-value:0deg; }

:root{
  --mv-papel-luz:   var(--paper-warm);
  --mv-papel-anil:  var(--accent-tint);
  --mv-papel-calor: var(--data-tint);
}
@supports (color: color-mix(in srgb, currentColor 10%, transparent)){
  :root{
    --mv-papel-luz:   color-mix(in srgb, var(--paper-2) 78%, transparent);
    --mv-papel-anil:  color-mix(in srgb, var(--accent) 9%, transparent);
    --mv-papel-calor: color-mix(in srgb, var(--gold) 11%, transparent);
  }
}

.mv-papel{
  position:absolute; inset:0; z-index:0; pointer-events:none;
  contain: paint;
  background:
    radial-gradient(44% 40% at var(--mv-m1x) var(--mv-m1y), var(--mv-papel-luz)   0%, transparent 70%),
    radial-gradient(38% 36% at var(--mv-m2x) var(--mv-m2y), var(--mv-papel-anil)  0%, transparent 72%),
    radial-gradient(56% 48% at var(--mv-m3x) var(--mv-m3y), var(--mv-papel-calor) 0%, transparent 76%),
    linear-gradient(calc(var(--mv-luz-ang) + var(--mv-malla-giro)), var(--paper-2) 0%, transparent 58%);
  animation: mv-respirar var(--mv-t-respiro) steps(360, end) infinite alternate;
}
@keyframes mv-respirar{
  to{
    --mv-m1x: 27%; --mv-m1y: 26%;
    --mv-m2x: 73%; --mv-m2y: 21%;
    --mv-m3x: 44%; --mv-m3y: 78%;
    --mv-malla-giro: 26deg;
  }
}

/* ============================================================
   5 · RESPUESTA AL PUNTERO — la lámina sabe dónde está la mano
   El CSS solo declara CÓMO se consume el desplazamiento; quién lo
   calcula es movimiento.js. Sin JS los valores son 0 y no pasa nada.
   ============================================================ */
/* Amplitud del parallax, en pixeles: cuanto se corre una capa de factor 1
   cuando el puntero cruza de un borde de la pantalla al otro. Es GLOBAL —
   movimiento.js la lee del elemento raiz, no de la seccion —, asi que se
   ajusta en :root o en <html style="--mv-amplitud:8">. Para graduar UNA
   capa concreta no se toca esto: se le da su factor en data-mv-capa="0.4".
   14 es mano tranquila; por encima de 24 se nota el truco. */
:root{ --mv-amplitud: 14; }

/* Parallax: el fondo se queda atrás y la lámina gana profundidad real. */
[data-mv-capa]{
  translate: calc(var(--mv-px, 0) * 1px) calc(var(--mv-py, 0) * 1px);
  will-change: translate;
}
/* Imantado: el control se inclina hacia el dedo antes de ser tocado. */
[data-mv-iman]{
  translate: calc(var(--mv-ix, 0) * 1px) calc(var(--mv-iy, 0) * 1px);
}

/* ============================================================
   6 · TRANSICIÓN DE FOCO — cuando algo se abre, la lámina cede
   Los hermanos se retiran un paso y bajan la voz; lo abierto se queda
   con la luz. La sala entiende dónde mirar sin que nadie lo diga.
   Se invoca desde JS: Movimiento.foco(el)
   ============================================================ */
[data-mv-focable]{
  transition:
    opacity var(--mv-t-medio) var(--mv-entrada),
    filter  var(--mv-t-medio) var(--mv-entrada),
    scale   var(--mv-t-medio) var(--mv-entrada);
}
[data-mv-atenuado]{ opacity: var(--mv-atenuado-op, .42); filter: saturate(.68); scale: .988; }
[data-mv-en-foco]{ z-index: 5; position: relative; }
/* El fondo de la lámina (malla, atmósfera, numeral) también se retira. */
[data-mv-foco] .mv-papel,
[data-mv-foco] [data-mv-fondo]{
  opacity: .45;
  transition: opacity var(--mv-t-medio) var(--mv-entrada);
}

/* Foco de teclado: no se pierde pase lo que pase con el movimiento. */
:where(a, button, [tabindex]):focus-visible{
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

/* ============================================================
   7 · MOVIMIENTO REDUCIDO — se degrada con dignidad, no se apaga a lo bruto
   Lo que se va: recorridos, desenfoques, escalas, deriva del papel,
   parallax, imantado. Lo que se queda: la jerarquía temporal (siguen
   entrando en orden, solo que muy corto) y todo cambio de estado, que
   sigue siendo legible. Nadie pierde información por pedir calma.
   ============================================================ */
@media (prefers-reduced-motion: reduce){
  :root{
    --mv-t-toque: 1ms; --mv-t-corto: 90ms; --mv-t-medio: 120ms;
    --mv-t-largo: 150ms; --mv-t-relato: 160ms;
    --mv-escalon: 22ms; --mv-escalon-palabra: 0ms; --mv-escalon-letra: 0ms;
  }
  /* Entrar sigue siendo entrar, pero solo con opacidad. */
  [data-mv-reveal] > *{ animation-name: mv-aparecer !important; animation-timing-function: linear !important; }
  .mv-componer, .mv-componer .mv-parte{ animation-name: mv-aparecer !important; }
  /* El papel se queda quieto en su primer fotograma: sigue teniendo luz. */
  .mv-papel{ animation: none !important; }
  [data-mv-capa], [data-mv-iman]{ translate: none !important; will-change: auto !important; }
  [data-mv-atenuado]{ filter: none; scale: none; opacity: var(--mv-atenuado-op, .5); }
}
