/* ==========================================================================
   Kuntur — capa de movimiento (anim.css)
   Depende de tokens.css y complementa a style.css SIN cambiar el estado
   final de nada: style.css declara siempre el dibujo COMPLETO (ruta entera,
   estaciones encendidas, camión en la planta) y este archivo solo lo
   retrasa cuando se dan las tres condiciones a la vez:

     1. `html.js`             — reveal.js se cargó y ejecutó;
     2. `:not(.is-visible)`   — el elemento aún no entró en pantalla;
     3. prefers-reduced-motion: no-preference.

   Si falta cualquiera de las tres, no se oculta ni un trazo: quien navega
   sin JavaScript o pidió menos movimiento ve la lámina terminada, que es el
   estado correcto. Por eso los estados «pre-animación» viven aquí y no en
   style.css.

   La coreografía del hero cuenta el producto en un plano: el camión recorre
   la ruta y la traza ámbar se REGISTRA detrás de él —igual que la app del
   transportista—, y cada estación se enciende a su paso. Corre UNA vez y
   descansa en el estado final: la traza archivada es el mensaje; un bucle
   junto a texto de lectura solo restaría. Total ≈ 6.5 s.

     0.0 – 0.7 s   la lámina aparece (revelado genérico)
     0.8 – 6.4 s   viaje del camión + traza (mismo easing: van en fase)
     0.9 s         se enciende ORIGEN (el camión parte)
     3.4 s         se enciende CP-01 (el camión pasa, ≈ 49 % de la ruta)
     6.1 s         se enciende PLANTA (el camión llega)
   ========================================================================== */

@media (prefers-reduced-motion: no-preference) {
  /* --- 1. Revelado genérico de láminas [data-reveal] ----------------------
     Andamiaje para L2.3: cualquier lámina entra con un fundido corto al
     cruzar el 35 % del viewport. Transición, no animación: si .is-visible
     llega antes del primer render (hero), el fundido corre igual. */
  .js [data-reveal] {
    transition:
      opacity 0.7s cubic-bezier(0.22, 0.61, 0.36, 1),
      transform 0.7s cubic-bezier(0.22, 0.61, 0.36, 1);
  }

  .js [data-reveal]:not(.is-visible) {
    opacity: 0;
    transform: translateY(0.85rem);
  }

  /* Escalonado por posición entre hermanos. Un solo peldaño sirve a los dos
     casos que hay en la página, porque en ambos los elementos revelables son
     hermanos: el encabezado de una lámina (rótulo, titular, entradilla,
     cuerpo, remate) y las rejillas de fichas. Así una sección se ARMA de
     arriba abajo en vez de aparecer entera de golpe, que es justo la
     diferencia entre «tiene efecto» y «parpadeó».

     Se corta en el sexto hermano: a partir de ahí la espera se nota más que
     el efecto, y en las rejillas de seis fichas el último ya entraría medio
     segundo tarde. Los .viz también son hermanos dentro de su sección y
     heredan el peldaño; son décimas, y su coreografía interna arranca
     igualmente contando desde que reciben `.is-visible`. */
  .js [data-reveal]:nth-child(2) {
    transition-delay: 0.07s;
  }

  .js [data-reveal]:nth-child(3) {
    transition-delay: 0.14s;
  }

  .js [data-reveal]:nth-child(4) {
    transition-delay: 0.21s;
  }

  .js [data-reveal]:nth-child(5) {
    transition-delay: 0.28s;
  }

  .js [data-reveal]:nth-child(n + 6) {
    transition-delay: 0.35s;
  }

  /* --- 2. Escena del traslado (hero) --------------------------------------
     Estados previos. El `stroke-dasharray` va SIN :not(): debe seguir
     presente mientras la animación mueve el dashoffset. Con pathLength=100
     en el SVG, un dash de 100 cubre la ruta entera: al terminar (offset 0)
     la línea se ve continua, idéntica al estado sin JS. */
  .js [data-viz="truck"] .ruta-trazo {
    stroke-dasharray: 100 100;
  }

  .js [data-viz="truck"]:not(.is-visible) .ruta-trazo {
    stroke-dashoffset: 100;
  }

  .js [data-viz="truck"]:not(.is-visible) .estacion {
    opacity: 0.25;
  }

  .js [data-viz="truck"]:not(.is-visible) .camion {
    opacity: 0;
  }

  /* Coreografía. `both` sostiene el fotograma inicial durante el retardo y
     el final para siempre: no hay salto entre estado previo y animación. */
  .js [data-viz="truck"].is-visible .ruta-trazo {
    animation: kuntur-traza 5.6s cubic-bezier(0.45, 0.05, 0.55, 0.95) 0.8s both;
  }

  .js [data-viz="truck"].is-visible .estacion {
    animation: kuntur-estacion 0.5s ease-out both;
  }

  .js [data-viz="truck"].is-visible .estacion-origen {
    animation-delay: 0.9s;
  }

  .js [data-viz="truck"].is-visible .estacion-cp {
    animation-delay: 3.4s;
  }

  .js [data-viz="truck"].is-visible .estacion-planta {
    animation-delay: 6.1s;
  }

  /* El viaje. El camión y la traza comparten duración, retardo y easing:
     como ambos son funciones lineales del mismo progreso, la punta de la
     línea ámbar queda siempre bajo el camión. */
  @supports (offset-path: path("M0 0H1")) {
    .js [data-viz="truck"].is-visible .camion {
      animation: kuntur-viaje 5.6s cubic-bezier(0.45, 0.05, 0.55, 0.95) 0.8s both;
    }

    /* Pulso GPS: 5 latidos suaves durante el viaje (≪ 3 destellos/s) y se
       apaga; `forwards` lo deja en opacidad 0, su estado de reposo. */
    .js [data-viz="truck"].is-visible .ping {
      animation: kuntur-ping 1.12s linear 0.9s 5 forwards;
    }
  }

  /* Sin offset-path (o sin path() en SVG): el camión no viaja, aparece en
     la planta cuando la traza está por llegar. Menos teatro, mismo final. */
  @supports not (offset-path: path("M0 0H1")) {
    .js [data-viz="truck"].is-visible .camion {
      animation: kuntur-llegada 0.6s ease-out 5.9s both;
    }
  }

  /* --- 3. El recorrido: una ruta, cuatro estaciones (Lám. 03) -------------
     La traza ámbar se dibuja de origen a expediente en 6 s LINEALES: es la
     línea de tiempo de la lámina. Las estaciones caen al 0 / 33 / 66 / 100 %
     del trazado, así que cuando la punta alcanza cada una (0.8 / 2.8 / 4.8 /
     6.8 s), la estación se enciende y su viñeta se dibuja con los gestos de
     las láminas grandes. Total ≈ 8.9 s, corre UNA vez y descansa en el
     estado final. Las mismas reglas sirven a las dos variantes (ancha y
     apilada): comparten clases y coordenadas locales de viñeta. */
  .js [data-viz="ruta"] .ruta-trazo {
    stroke-dasharray: 100 100;
  }

  .js [data-viz="ruta"]:not(.is-visible) .ruta-trazo {
    stroke-dashoffset: 100;
  }

  .js [data-viz="ruta"] .conforme-aro,
  .js [data-viz="ruta"] .conforme-visto,
  .js [data-viz="ruta"] .indice-linea,
  .js [data-viz="ruta"] .sello-aro,
  .js [data-viz="ruta"] .sello-visto {
    stroke-dasharray: 100 100;
  }

  .js [data-viz="ruta"]:not(.is-visible) .conforme-aro,
  .js [data-viz="ruta"]:not(.is-visible) .conforme-visto,
  .js [data-viz="ruta"]:not(.is-visible) .indice-linea,
  .js [data-viz="ruta"]:not(.is-visible) .sello-aro,
  .js [data-viz="ruta"]:not(.is-visible) .sello-visto {
    stroke-dashoffset: 100;
  }

  .js [data-viz="ruta"]:not(.is-visible) .estacion,
  .js [data-viz="ruta"]:not(.is-visible) .poligono {
    opacity: 0.25;
  }

  .js [data-viz="ruta"]:not(.is-visible) .vin-glifo,
  .js [data-viz="ruta"]:not(.is-visible) .radio-gps,
  .js [data-viz="ruta"]:not(.is-visible) .evidencia-punto,
  .js [data-viz="ruta"]:not(.is-visible) .punto,
  .js [data-viz="ruta"]:not(.is-visible) .marcador,
  .js [data-viz="ruta"]:not(.is-visible) .qr,
  .js [data-viz="ruta"]:not(.is-visible) .conforme text,
  .js [data-viz="ruta"]:not(.is-visible) .folio {
    opacity: 0;
  }

  /* La línea de tiempo: la traza avanza a ritmo constante. */
  .js [data-viz="ruta"].is-visible .ruta-trazo {
    animation: kuntur-traza 6s linear 0.8s both;
  }

  /* Las estaciones se encienden al paso de la punta. */
  .js [data-viz="ruta"].is-visible .estacion {
    animation: kuntur-estacion 0.5s ease-out both;
  }

  .js [data-viz="ruta"].is-visible .est-1 { animation-delay: 0.85s; }
  .js [data-viz="ruta"].is-visible .est-2 { animation-delay: 2.75s; }
  .js [data-viz="ruta"].is-visible .est-3 { animation-delay: 4.75s; }
  .js [data-viz="ruta"].is-visible .est-4 { animation-delay: 6.75s; }

  /* Viñeta ORIGEN (0.9–2.4 s): el D.M. sube de opacidad, la bocamina
     aparece, el GPS abre su radio y el punto de evidencia queda fijado. */
  .js [data-viz="ruta"].is-visible .poligono {
    animation: kuntur-estacion 0.6s ease-out 0.9s both;
  }

  .js [data-viz="ruta"].is-visible .vin-glifo {
    animation: kuntur-llegada 0.4s ease-out 1.25s both;
  }

  .js [data-viz="ruta"] .radio-anillo {
    transform-box: fill-box;
    transform-origin: center;
  }

  .js [data-viz="ruta"].is-visible .radio-gps {
    animation: kuntur-llegada 0.4s ease-out 1.55s both;
  }

  .js [data-viz="ruta"].is-visible .radio-anillo {
    animation: kuntur-radio 0.6s cubic-bezier(0.22, 0.61, 0.36, 1) 1.55s both;
  }

  .js [data-viz="ruta"].is-visible .evidencia-punto {
    animation: kuntur-llegada 0.3s ease-out 2.1s both;
  }

  /* Viñeta TRASLADO (2.9–4.3 s): el marcador recorre su tramo dejando caer
     los puntos registrados detrás. */
  .js [data-viz="ruta"].is-visible .punto {
    animation: kuntur-llegada 0.35s ease-out both;
  }

  .js [data-viz="ruta"].is-visible .punto:nth-child(1) { animation-delay: 3s; }
  .js [data-viz="ruta"].is-visible .punto:nth-child(2) { animation-delay: 3.25s; }
  .js [data-viz="ruta"].is-visible .punto:nth-child(3) { animation-delay: 3.5s; }
  .js [data-viz="ruta"].is-visible .punto:nth-child(4) { animation-delay: 3.75s; }

  @supports (offset-path: path("M0 0H1")) {
    .js [data-viz="ruta"].is-visible .marcador {
      animation: kuntur-viaje 1.4s cubic-bezier(0.45, 0.05, 0.55, 0.95) 2.95s both;
    }

    .js [data-viz="ruta"].is-visible .ping {
      animation: kuntur-ping 1.12s linear 3.05s 2 forwards;
    }
  }

  @supports not (offset-path: path("M0 0H1")) {
    .js [data-viz="ruta"].is-visible .marcador {
      animation: kuntur-llegada 0.5s ease-out 4.1s both;
    }
  }

  /* Viñeta RECEPCIÓN (4.9–6.9 s): el código aparece, la línea de lectura
     pasa UNA vez y el conforme se dibuja. */
  .js [data-viz="ruta"].is-visible .qr {
    animation: kuntur-llegada 0.5s ease-out 4.9s both;
  }

  .js [data-viz="ruta"].is-visible .lector {
    animation: kuntur-escaner 1.1s cubic-bezier(0.45, 0.05, 0.55, 0.95) 5.3s forwards;
  }

  .js [data-viz="ruta"].is-visible .conforme-aro {
    animation: kuntur-traza 0.45s ease-out 6.1s both;
  }

  .js [data-viz="ruta"].is-visible .conforme-visto {
    animation: kuntur-traza 0.35s ease-out 6.5s both;
  }

  .js [data-viz="ruta"].is-visible .conforme text {
    animation: kuntur-llegada 0.4s ease-out 6.7s both;
  }

  /* Viñeta EXPEDIENTE (6.9–8.9 s): los folios se asientan, el índice se
     escribe y el sello firma el cierre. */
  .js [data-viz="ruta"].is-visible .folio {
    animation: kuntur-asiento 0.45s cubic-bezier(0.22, 0.61, 0.36, 1) both;
  }

  .js [data-viz="ruta"].is-visible .folio:nth-child(1) { animation-delay: 6.9s; }
  .js [data-viz="ruta"].is-visible .folio:nth-child(2) { animation-delay: 7.1s; }
  .js [data-viz="ruta"].is-visible .folio:nth-child(3) { animation-delay: 7.3s; }

  .js [data-viz="ruta"].is-visible .indice-linea {
    animation: kuntur-traza 0.35s ease-out both;
  }

  .js [data-viz="ruta"].is-visible .indice-linea:nth-of-type(1) { animation-delay: 7.45s; }
  .js [data-viz="ruta"].is-visible .indice-linea:nth-of-type(2) { animation-delay: 7.6s; }
  .js [data-viz="ruta"].is-visible .indice-linea:nth-of-type(3) { animation-delay: 7.75s; }

  .js [data-viz="ruta"].is-visible .sello-aro {
    animation: kuntur-traza 0.6s cubic-bezier(0.45, 0.05, 0.55, 0.95) 7.95s both;
  }

  .js [data-viz="ruta"].is-visible .sello-visto {
    animation: kuntur-traza 0.4s ease-out 8.5s both;
  }

  /* --- 7. Reportes: la analítica (L2.3) -----------------------------------
     Las barras crecen escalonadas (0.8–2.1 s), la tendencia se traza sobre
     ellas y el anillo completa su 72 % antes de mostrar la cifra. */
  .js [data-viz="reportes"] .linea-tendencia {
    stroke-dasharray: 100 100;
  }

  .js [data-viz="reportes"]:not(.is-visible) .linea-tendencia {
    stroke-dashoffset: 100;
  }

  .js [data-viz="reportes"] .barra {
    transform-box: fill-box;
    transform-origin: bottom;
  }

  .js [data-viz="reportes"]:not(.is-visible) .barra {
    transform: scaleY(0);
  }

  .js [data-viz="reportes"]:not(.is-visible) .eje,
  .js [data-viz="reportes"]:not(.is-visible) .rotulo,
  .js [data-viz="reportes"]:not(.is-visible) .punto-tendencia,
  .js [data-viz="reportes"]:not(.is-visible) .cifra {
    opacity: 0;
  }

  .js [data-viz="reportes"]:not(.is-visible) .aro-progreso {
    stroke-dasharray: 0 100;
  }

  .js [data-viz="reportes"].is-visible .eje,
  .js [data-viz="reportes"].is-visible .rotulo {
    animation: kuntur-llegada 0.5s ease-out 0.5s both;
  }

  .js [data-viz="reportes"].is-visible .barra {
    animation: kuntur-barra 0.6s cubic-bezier(0.22, 0.61, 0.36, 1) both;
  }

  .js [data-viz="reportes"].is-visible .barra:nth-of-type(1) { animation-delay: 0.8s; }
  .js [data-viz="reportes"].is-visible .barra:nth-of-type(2) { animation-delay: 0.92s; }
  .js [data-viz="reportes"].is-visible .barra:nth-of-type(3) { animation-delay: 1.04s; }
  .js [data-viz="reportes"].is-visible .barra:nth-of-type(4) { animation-delay: 1.16s; }
  .js [data-viz="reportes"].is-visible .barra:nth-of-type(5) { animation-delay: 1.28s; }
  .js [data-viz="reportes"].is-visible .barra:nth-of-type(6) { animation-delay: 1.4s; }
  .js [data-viz="reportes"].is-visible .barra:nth-of-type(7) { animation-delay: 1.52s; }

  .js [data-viz="reportes"].is-visible .linea-tendencia {
    animation: kuntur-traza 1s cubic-bezier(0.45, 0.05, 0.55, 0.95) 2s both;
  }

  .js [data-viz="reportes"].is-visible .punto-tendencia {
    animation: kuntur-llegada 0.3s ease-out both;
  }

  .js [data-viz="reportes"].is-visible .punto-tendencia:nth-of-type(1) { animation-delay: 2.05s; }
  .js [data-viz="reportes"].is-visible .punto-tendencia:nth-of-type(2) { animation-delay: 2.25s; }
  .js [data-viz="reportes"].is-visible .punto-tendencia:nth-of-type(3) { animation-delay: 2.45s; }
  .js [data-viz="reportes"].is-visible .punto-tendencia:nth-of-type(4) { animation-delay: 2.65s; }
  .js [data-viz="reportes"].is-visible .punto-tendencia:nth-of-type(5) { animation-delay: 2.85s; }
  .js [data-viz="reportes"].is-visible .punto-tendencia:nth-of-type(6) { animation-delay: 3.05s; }

  .js [data-viz="reportes"].is-visible .aro-progreso {
    animation: kuntur-aro 1s cubic-bezier(0.45, 0.05, 0.55, 0.95) 3.2s both;
  }

  .js [data-viz="reportes"].is-visible .cifra {
    animation: kuntur-llegada 0.5s ease-out 4.1s both;
  }

  /* --- 8. Semáforo de recepción (Lám. 06) ---------------------------------
     Primero corre la revisión y después responde el semáforo: los 17 puntos
     se encienden en serie (0.8–2.1 s) y solo entonces aparecen los tres
     estados, de uno en uno. El orden importa —si el semáforo se encendiera
     antes, parecería que la respuesta no depende de la revisión— y los tres
     estados salen por igual porque son alternativas, no pasos. ≈ 4.1 s. */
  .js [data-viz="semaforo"]:not(.is-visible) .semaforo-caja,
  .js [data-viz="semaforo"]:not(.is-visible) .semaforo-titulo,
  .js [data-viz="semaforo"]:not(.is-visible) .pto {
    opacity: 0;
  }

  .js [data-viz="semaforo"]:not(.is-visible) .estado {
    opacity: 0.25;
  }

  .js [data-viz="semaforo"] .semaforo-filete {
    stroke-dasharray: 100 100;
  }

  .js [data-viz="semaforo"]:not(.is-visible) .semaforo-filete {
    stroke-dashoffset: 100;
  }

  .js [data-viz="semaforo"].is-visible .semaforo-titulo {
    animation: kuntur-llegada 0.4s ease-out 0.4s both;
  }

  .js [data-viz="semaforo"].is-visible .semaforo-filete {
    animation: kuntur-traza 0.6s ease-out 0.5s both;
  }

  .js [data-viz="semaforo"].is-visible .semaforo-caja {
    animation: kuntur-asiento 0.5s cubic-bezier(0.22, 0.61, 0.36, 1) 0.6s both;
  }

  /* Los 17, a 0.08 s de distancia: rápido de leer, lento de contar. */
  .js [data-viz="semaforo"].is-visible .pto {
    animation: kuntur-asiento 0.28s ease-out both;
  }

  .js [data-viz="semaforo"].is-visible .pto:nth-of-type(1) { animation-delay: 0.8s; }
  .js [data-viz="semaforo"].is-visible .pto:nth-of-type(2) { animation-delay: 0.88s; }
  .js [data-viz="semaforo"].is-visible .pto:nth-of-type(3) { animation-delay: 0.96s; }
  .js [data-viz="semaforo"].is-visible .pto:nth-of-type(4) { animation-delay: 1.04s; }
  .js [data-viz="semaforo"].is-visible .pto:nth-of-type(5) { animation-delay: 1.12s; }
  .js [data-viz="semaforo"].is-visible .pto:nth-of-type(6) { animation-delay: 1.2s; }
  .js [data-viz="semaforo"].is-visible .pto:nth-of-type(7) { animation-delay: 1.28s; }
  .js [data-viz="semaforo"].is-visible .pto:nth-of-type(8) { animation-delay: 1.36s; }
  .js [data-viz="semaforo"].is-visible .pto:nth-of-type(9) { animation-delay: 1.44s; }
  .js [data-viz="semaforo"].is-visible .pto:nth-of-type(10) { animation-delay: 1.52s; }
  .js [data-viz="semaforo"].is-visible .pto:nth-of-type(11) { animation-delay: 1.6s; }
  .js [data-viz="semaforo"].is-visible .pto:nth-of-type(12) { animation-delay: 1.68s; }
  .js [data-viz="semaforo"].is-visible .pto:nth-of-type(13) { animation-delay: 1.76s; }
  .js [data-viz="semaforo"].is-visible .pto:nth-of-type(14) { animation-delay: 1.84s; }
  .js [data-viz="semaforo"].is-visible .pto:nth-of-type(15) { animation-delay: 1.92s; }
  .js [data-viz="semaforo"].is-visible .pto:nth-of-type(16) { animation-delay: 2s; }
  .js [data-viz="semaforo"].is-visible .pto:nth-of-type(17) { animation-delay: 2.08s; }

  .js [data-viz="semaforo"].is-visible .estado {
    animation: kuntur-estacion 0.5s ease-out both;
  }

  .js [data-viz="semaforo"].is-visible .estado-1 { animation-delay: 2.4s; }
  .js [data-viz="semaforo"].is-visible .estado-2 { animation-delay: 2.95s; }
  .js [data-viz="semaforo"].is-visible .estado-3 { animation-delay: 3.5s; }

  /* --- 9. Arquitectura híbrida (Lám. 05) ----------------------------------
     El teléfono aparece, el enlace se tiende, los paquetes lo recorren de
     izquierda a derecha y las filas del expediente se escriben en la
     plataforma. ≈ 4.2 s. */
  .js [data-viz="hibrido"]:not(.is-visible) .nodo,
  .js [data-viz="hibrido"]:not(.is-visible) .candado,
  .js [data-viz="hibrido"]:not(.is-visible) .paquete {
    opacity: 0;
  }

  .js [data-viz="hibrido"] .enlace,
  .js [data-viz="hibrido"] .fila {
    stroke-dasharray: 100 100;
  }

  .js [data-viz="hibrido"]:not(.is-visible) .enlace,
  .js [data-viz="hibrido"]:not(.is-visible) .fila {
    stroke-dashoffset: 100;
  }

  .js [data-viz="hibrido"].is-visible .nodo-campo {
    animation: kuntur-asiento 0.55s cubic-bezier(0.22, 0.61, 0.36, 1) 0.5s both;
  }

  .js [data-viz="hibrido"].is-visible .enlace {
    animation: kuntur-traza 0.9s cubic-bezier(0.45, 0.05, 0.55, 0.95) 1.15s both;
  }

  .js [data-viz="hibrido"].is-visible .candado {
    animation: kuntur-llegada 0.4s ease-out 1.75s both;
  }

  .js [data-viz="hibrido"].is-visible .paquete {
    animation: kuntur-asiento 0.35s ease-out both;
  }

  .js [data-viz="hibrido"].is-visible .paquete:nth-of-type(1) { animation-delay: 2s; }
  .js [data-viz="hibrido"].is-visible .paquete:nth-of-type(2) { animation-delay: 2.2s; }
  .js [data-viz="hibrido"].is-visible .paquete:nth-of-type(3) { animation-delay: 2.4s; }

  .js [data-viz="hibrido"].is-visible .nodo-plataforma {
    animation: kuntur-asiento 0.5s cubic-bezier(0.22, 0.61, 0.36, 1) 2.55s both;
  }

  .js [data-viz="hibrido"].is-visible .fila {
    animation: kuntur-traza 0.4s ease-out both;
  }

  .js [data-viz="hibrido"].is-visible .fila:nth-of-type(1) { animation-delay: 3s; }
  .js [data-viz="hibrido"].is-visible .fila:nth-of-type(2) { animation-delay: 3.2s; }
  .js [data-viz="hibrido"].is-visible .fila:nth-of-type(3) { animation-delay: 3.4s; }
  .js [data-viz="hibrido"].is-visible .fila:nth-of-type(4) { animation-delay: 3.6s; }

  /* --- 10. El dossier se arma (Lám. 08) -----------------------------------
     La búsqueda, las cuatro piezas entrando de una en una y el expediente
     cerrándose con su sello. ≈ 5 s. */
  .js [data-viz="dossier"]:not(.is-visible) .busca,
  .js [data-viz="dossier"]:not(.is-visible) .pieza,
  .js [data-viz="dossier"]:not(.is-visible) .flecha-punta,
  .js [data-viz="dossier"]:not(.is-visible) .dossier {
    opacity: 0;
  }

  .js [data-viz="dossier"] .pieza-linea,
  .js [data-viz="dossier"] .dossier-linea,
  .js [data-viz="dossier"] .flecha,
  .js [data-viz="dossier"] .sello-aro,
  .js [data-viz="dossier"] .sello-visto {
    stroke-dasharray: 100 100;
  }

  .js [data-viz="dossier"]:not(.is-visible) .pieza-linea,
  .js [data-viz="dossier"]:not(.is-visible) .dossier-linea,
  .js [data-viz="dossier"]:not(.is-visible) .flecha,
  .js [data-viz="dossier"]:not(.is-visible) .sello-aro,
  .js [data-viz="dossier"]:not(.is-visible) .sello-visto {
    stroke-dashoffset: 100;
  }

  .js [data-viz="dossier"].is-visible .busca {
    animation: kuntur-asiento 0.5s cubic-bezier(0.22, 0.61, 0.36, 1) 0.45s both;
  }

  .js [data-viz="dossier"].is-visible .pieza {
    animation: kuntur-asiento 0.45s cubic-bezier(0.22, 0.61, 0.36, 1) both;
  }

  /* Clase por pieza, no `nth-of-type`: los <g> de las piezas comparten padre
     con el <g> de la búsqueda, y `nth-of-type` cuenta POR TIPO de elemento
     —todos los <g> del SVG—, así que la numeración saldría corrida. Es el
     mismo motivo por el que las estaciones de la Lám. 04 llevan est-1…est-4. */
  .js [data-viz="dossier"].is-visible .pieza-1 { animation-delay: 1.15s; }
  .js [data-viz="dossier"].is-visible .pieza-2 { animation-delay: 1.4s; }
  .js [data-viz="dossier"].is-visible .pieza-3 { animation-delay: 1.65s; }
  .js [data-viz="dossier"].is-visible .pieza-4 { animation-delay: 1.9s; }

  /* Las líneas de cada hoja se escriben justo después de que la hoja aterriza:
     el desfase de 0.25 s entre hojas se mantiene aquí. */
  .js [data-viz="dossier"].is-visible .pieza-linea {
    animation: kuntur-traza 0.3s ease-out both;
  }

  .js [data-viz="dossier"].is-visible .pieza-1 .pieza-linea { animation-delay: 1.4s; }
  .js [data-viz="dossier"].is-visible .pieza-2 .pieza-linea { animation-delay: 1.65s; }
  .js [data-viz="dossier"].is-visible .pieza-3 .pieza-linea { animation-delay: 1.9s; }
  .js [data-viz="dossier"].is-visible .pieza-4 .pieza-linea { animation-delay: 2.15s; }

  .js [data-viz="dossier"].is-visible .flecha {
    animation: kuntur-traza 0.35s ease-out 2.5s both;
  }

  .js [data-viz="dossier"].is-visible .flecha-punta {
    animation: kuntur-llegada 0.25s ease-out 2.8s both;
  }

  .js [data-viz="dossier"].is-visible .dossier {
    animation: kuntur-asiento 0.5s cubic-bezier(0.22, 0.61, 0.36, 1) 3s both;
  }

  .js [data-viz="dossier"].is-visible .dossier-linea {
    animation: kuntur-traza 0.32s ease-out both;
  }

  .js [data-viz="dossier"].is-visible .dossier-linea:nth-of-type(1) { animation-delay: 3.4s; }
  .js [data-viz="dossier"].is-visible .dossier-linea:nth-of-type(2) { animation-delay: 3.55s; }
  .js [data-viz="dossier"].is-visible .dossier-linea:nth-of-type(3) { animation-delay: 3.7s; }

  .js [data-viz="dossier"].is-visible .sello-aro {
    animation: kuntur-traza 0.6s cubic-bezier(0.45, 0.05, 0.55, 0.95) 3.95s both;
  }

  .js [data-viz="dossier"].is-visible .sello-visto {
    animation: kuntur-traza 0.4s ease-out 4.5s both;
  }

  /* --- 11. La huella SHA-256 (Lám. 09) ------------------------------------
     Dos tiempos con la misma estructura, y ahí está toda la lección: el
     segundo caso repite el primero paso por paso y solo cambia la línea
     retocada, la huella y la marca final. El caso alterado NO arranca hasta
     que el conforme cerró (2.6 s), para que se lean como comparación y no
     como dos cosas que pasan a la vez. ≈ 5.2 s. */
  .js [data-viz="huella"]:not(.is-visible) .huella-titulo,
  .js [data-viz="huella"]:not(.is-visible) .reg-ficha,
  .js [data-viz="huella"]:not(.is-visible) .flecha-punta,
  .js [data-viz="huella"]:not(.is-visible) .huella-cifra,
  .js [data-viz="huella"]:not(.is-visible) .caso .rotulo-suave {
    opacity: 0;
  }

  .js [data-viz="huella"] .huella-filete,
  .js [data-viz="huella"] .reg-linea,
  .js [data-viz="huella"] .flecha,
  .js [data-viz="huella"] .marca-aro,
  .js [data-viz="huella"] .marca-visto,
  .js [data-viz="huella"] .marca-aspa {
    stroke-dasharray: 100 100;
  }

  .js [data-viz="huella"]:not(.is-visible) .huella-filete,
  .js [data-viz="huella"]:not(.is-visible) .reg-linea,
  .js [data-viz="huella"]:not(.is-visible) .flecha,
  .js [data-viz="huella"]:not(.is-visible) .marca-aro,
  .js [data-viz="huella"]:not(.is-visible) .marca-visto,
  .js [data-viz="huella"]:not(.is-visible) .marca-aspa {
    stroke-dashoffset: 100;
  }

  .js [data-viz="huella"].is-visible .huella-titulo {
    animation: kuntur-llegada 0.4s ease-out 0.4s both;
  }

  .js [data-viz="huella"].is-visible .huella-filete {
    animation: kuntur-traza 0.6s ease-out 0.5s both;
  }

  /* Primer tiempo: el registro tal como se guardó (0.8–2.6 s). */
  .js [data-viz="huella"].is-visible .caso-ok .reg-ficha {
    animation: kuntur-asiento 0.45s cubic-bezier(0.22, 0.61, 0.36, 1) 0.8s both;
  }

  .js [data-viz="huella"].is-visible .caso-ok .reg-linea {
    animation: kuntur-traza 0.3s ease-out both;
  }

  .js [data-viz="huella"].is-visible .caso-ok .reg-linea:nth-of-type(1) { animation-delay: 1.1s; }
  .js [data-viz="huella"].is-visible .caso-ok .reg-linea:nth-of-type(2) { animation-delay: 1.22s; }
  .js [data-viz="huella"].is-visible .caso-ok .reg-linea:nth-of-type(3) { animation-delay: 1.34s; }

  .js [data-viz="huella"].is-visible .caso-ok .flecha {
    animation: kuntur-traza 0.3s ease-out 1.6s both;
  }

  .js [data-viz="huella"].is-visible .caso-ok .flecha-punta {
    animation: kuntur-llegada 0.2s ease-out 1.85s both;
  }

  .js [data-viz="huella"].is-visible .caso-ok .huella-cifra {
    animation: kuntur-asiento 0.4s ease-out 2s both;
  }

  .js [data-viz="huella"].is-visible .caso-ok .marca-aro {
    animation: kuntur-traza 0.45s ease-out 2.35s both;
  }

  .js [data-viz="huella"].is-visible .caso-ok .marca-visto {
    animation: kuntur-traza 0.3s ease-out 2.7s both;
  }

  .js [data-viz="huella"].is-visible .caso-ok .rotulo-suave {
    animation: kuntur-llegada 0.35s ease-out 2.85s both;
  }

  /* Segundo tiempo: el mismo registro, retocado (3.2–5.2 s). */
  .js [data-viz="huella"].is-visible .caso-alterado .reg-ficha {
    animation: kuntur-asiento 0.45s cubic-bezier(0.22, 0.61, 0.36, 1) 3.2s both;
  }

  .js [data-viz="huella"].is-visible .caso-alterado .reg-linea {
    animation: kuntur-traza 0.3s ease-out both;
  }

  .js [data-viz="huella"].is-visible .caso-alterado .reg-linea:nth-of-type(1) { animation-delay: 3.5s; }
  .js [data-viz="huella"].is-visible .caso-alterado .reg-linea:nth-of-type(2) { animation-delay: 3.62s; }
  .js [data-viz="huella"].is-visible .caso-alterado .reg-linea:nth-of-type(3) { animation-delay: 3.74s; }

  .js [data-viz="huella"].is-visible .caso-alterado .flecha {
    animation: kuntur-traza 0.3s ease-out 4s both;
  }

  .js [data-viz="huella"].is-visible .caso-alterado .flecha-punta {
    animation: kuntur-llegada 0.2s ease-out 4.25s both;
  }

  .js [data-viz="huella"].is-visible .caso-alterado .huella-cifra {
    animation: kuntur-asiento 0.4s ease-out 4.4s both;
  }

  .js [data-viz="huella"].is-visible .caso-alterado .marca-aro {
    animation: kuntur-traza 0.45s ease-out 4.75s both;
  }

  .js [data-viz="huella"].is-visible .caso-alterado .marca-aspa {
    animation: kuntur-traza 0.35s ease-out 5.1s both;
  }

  .js [data-viz="huella"].is-visible .caso-alterado .rotulo-suave {
    animation: kuntur-llegada 0.35s ease-out 5.3s both;
  }
}

@keyframes kuntur-traza {
  from {
    stroke-dashoffset: 100;
  }

  to {
    stroke-dashoffset: 0;
  }
}

@keyframes kuntur-estacion {
  from {
    opacity: 0.25;
  }

  to {
    opacity: 1;
  }
}

@keyframes kuntur-viaje {
  0% {
    offset-distance: 0%;
    opacity: 0;
  }

  4% {
    opacity: 1;
  }

  100% {
    offset-distance: 100%;
    opacity: 1;
  }
}

@keyframes kuntur-ping {
  from {
    opacity: 0.55;
  }

  to {
    opacity: 0;
  }
}

@keyframes kuntur-llegada {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}

/* El radio de precisión del GPS se abre desde el punto medido. */
@keyframes kuntur-radio {
  from {
    opacity: 0;
    transform: scale(0.3);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

/* La línea de lectura: una sola pasada sobre el código y se apaga. El
   desplazamiento cubre el alto del código en miniatura de la Lám. 03. */
@keyframes kuntur-escaner {
  0% {
    opacity: 0;
    transform: translateY(0);
  }

  10% {
    opacity: 0.9;
  }

  90% {
    opacity: 0.9;
  }

  100% {
    opacity: 0;
    transform: translateY(100px);
  }
}

/* Un dato o un folio que se asienta en su sitio. */
@keyframes kuntur-asiento {
  from {
    opacity: 0;
    transform: translateY(6px);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

/* Las barras crecen desde la línea base. */
@keyframes kuntur-barra {
  from {
    transform: scaleY(0);
  }

  to {
    transform: none;
  }
}

/* El anillo completa su arco: con pathLength=100, el dash final `72 28`
   es el mismo estado que declara style.css. */
@keyframes kuntur-aro {
  from {
    stroke-dasharray: 0 100;
  }

  to {
    stroke-dasharray: 72 28;
  }
}
