/* ==========================================================================
   Figuras animadas: marco común, mapa de América Latina y banners (plataforma,
   phishing, GAP, pentesting, análisis de vulnerabilidades, Nosotros y los doce
   de las demás páginas de servicio)
   Las cargan la portada, la página Nosotros (el mapa) y las páginas de servicio
   que tienen banner (en su cabecera, la ficha y la figura forman un slider de
   dos diapositivas).
   Se animan cada vez que su diapositiva se activa (.is-active). Sin animación,
   o con «reducir movimiento», cada figura muestra su estado final.
   Las animaciones comunes (sw-fade, sw-pop, sw-rise y sw-draw) están en base.css.
   ========================================================================== */

/* Marco común de las figuras ------------------------------------------------- */

.hero-figure { position: relative; }
.hero-figure > img,
.hero-figure > svg { width: 100%; height: auto; }

/* Texto alternativo visible solo en pantallas pequeñas, donde el SVG no se lee */
.hero-figure__caption {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
}

@media (max-width: 639px) {
  .hero-figure__caption {
    position: static;
    width: auto;
    height: auto;
    margin-top: 16px;
    overflow: visible;
    clip: auto;
    white-space: normal;
    font-family: var(--font-mono);
    font-size: var(--fs-mono-sm);
    font-weight: 500;
    letter-spacing: 0.08em;
    line-height: 1.7;
    text-transform: uppercase;
    color: var(--color-text-muted);
  }
}

/* Mapa de América Latina ------------------------------------------------------ */

@media (max-width: 899px) {
  .map-svg .map__scale { display: none; }
}
@media (max-width: 639px) {
  .map-svg .map__label,
  .map-svg .map__leader { display: none; }
}

/* Se anima cuando su figura se activa: la diapositiva del slider o, en Nosotros,
   al entrar en pantalla (data-animar-al-ver) */
@media (prefers-reduced-motion: no-preference) {
  .map-svg .map__row,
  .map-svg .map__dot-hi,
  .map-svg .map__marker,
  .map-svg .map__marker-dot,
  .map-svg .map__label,
  .map-svg .map__scale,
  .map-svg .map__north { opacity: 0; }
  .map-svg .map__leader { stroke-dasharray: 1; stroke-dashoffset: 1; }
  .map-svg .map__marker,
  .map-svg .map__marker-dot,
  .map-svg .map__ping { transform-box: fill-box; transform-origin: center; }

  /* Barrido de norte a sur: cada fila de puntos aparece en cascada */
  :is(.is-active, .is-leaving) .map-svg .map__row {
    animation: sw-fade-drop 0.55s var(--ease-out) forwards;
    animation-delay: calc(var(--r) * 9ms + 0.1s);
  }
  /* Los siete países se encienden después, de norte a sur */
  :is(.is-active, .is-leaving) .map-svg .map__dot-hi {
    animation:
      sw-fade-drop 0.55s var(--ease-out) calc(var(--r) * 9ms + 0.1s) forwards,
      sw-hi 0.7s var(--ease-out) calc(1.25s + var(--c) * 0.14s) both;
  }
  :is(.is-active, .is-leaving) .map-svg .map__scan { animation: sw-scan 1.3s ease-in-out 0.05s both; }
  :is(.is-active, .is-leaving) .map-svg :is(.map__marker, .map__marker-dot) {
    animation: sw-pop 0.45s var(--ease-spring) forwards;
    animation-delay: calc(1.35s + var(--c) * 0.14s);
  }
  :is(.is-active, .is-leaving) .map-svg .map__leader {
    animation: sw-draw 0.6s var(--ease-out) forwards;
    animation-delay: calc(1.5s + var(--c) * 0.14s);
  }
  :is(.is-active, .is-leaving) .map-svg .map__label {
    animation: sw-fade 0.5s var(--ease-out) forwards;
    animation-delay: calc(1.8s + var(--c) * 0.14s);
  }
  /* Pulso continuo y suave sobre cada país */
  :is(.is-active, .is-leaving) .map-svg .map__ping {
    animation: sw-ping 2.8s var(--ease-out) infinite;
    animation-delay: calc(2.6s + var(--c) * 0.4s);
  }
  :is(.is-active, .is-leaving) .map-svg :is(.map__scale, .map__north) {
    animation: sw-fade 0.8s var(--ease-out) 0.9s forwards;
  }
}

@keyframes sw-fade-drop { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
/* Los países se encienden: del color de los puntos al de los países (--map-dot y --map-hi,
   que Nosotros cambia para su versión de noche) */
@keyframes sw-hi { from { fill: var(--map-dot, var(--sw-dot)); } to { fill: var(--map-hi, var(--sw-cobalto)); } }
@keyframes sw-scan {
  0% { opacity: 1; transform: translateY(0); }
  85% { opacity: 1; }
  100% { opacity: 0; transform: translateY(801px); }
}
@keyframes sw-ping {
  0% { opacity: 0.75; transform: scale(1); }
  70%, 100% { opacity: 0; transform: scale(3.4); }
}

/* 3b. Banners animados: plataforma, ethical phishing y GAP ------------------ */
/* Sin animación (o con «reducir movimiento») cada figura muestra su estado final. */

@media (max-width: 899px) {
  :is(.vm-svg, .ph-svg, .gap-svg, .pt-svg, .av-svg, .ns-svg, .bx) .bn__frame text { display: none; }
}
@media (max-width: 639px) {
  :is(.vm-svg, .ph-svg, .gap-svg, .pt-svg, .av-svg, .ns-svg, .bx) .bn__label,
  .vm-svg .vm__asset text { display: none; }
  .vm-svg .vm__asset-skel { display: inline; }
  .ns-svg .ns__legend { display: none; }  /* sin sus rótulos, la leyenda no se entiende */
}

@media (prefers-reduced-motion: no-preference) {
  :is(.vm-svg, .ph-svg, .gap-svg, .pt-svg, .av-svg, .ns-svg, .bx) :is(.bn__frame, .bn__label) { opacity: 0; }
  :is(.is-active, .is-leaving) :is(.vm-svg, .ph-svg, .gap-svg, .pt-svg, .av-svg, .ns-svg, .bx) :is(.bn__frame, .bn__label) {
    animation: sw-fade 0.6s var(--ease-out) 0.15s forwards;
  }

  /* Plataforma: los hallazgos cruzan los filtros; solo los importantes llegan a la cola.
     Tras la primera pasada siguen llegando hallazgos, uno a uno (goteo). */
  .vm-svg :is(.vm__dot, .vm__chip, .vm__asset, .vm__owner, .vm__track, .vm__done) { opacity: 0; }
  .vm-svg :is(.vm__dot, .vm__chip, .vm__done, .vm__halo) { transform-box: fill-box; transform-origin: center; }
  .vm-svg .vm__gate { transform-box: fill-box; transform-origin: top; transform: scaleY(0); }
  .vm-svg .vm__fill { transform-box: fill-box; transform-origin: left center; transform: scaleX(0); }

  :is(.is-active, .is-leaving) .vm-svg .vm__gate {
    animation: bn-grow-y 0.7s var(--ease-out) forwards;
    animation-delay: calc(0.35s + var(--i) * 0.12s);
  }
  :is(.is-active, .is-leaving) .vm-svg .vm__dot--filtra {
    animation: vm-filtra 2.3s ease-in-out forwards, vm-goteo 8s ease-in-out infinite;
    animation-delay: calc(0.45s + var(--i) * 0.02s), calc(4.2s + var(--k) * 0.235s);
  }
  :is(.is-active, .is-leaving) .vm-svg .vm__dot--pasa {
    animation: vm-pasa 2.7s ease-in-out forwards, vm-goteo 8s ease-in-out infinite;
    animation-delay: calc(0.55s + var(--i) * 0.1s), calc(4.2s + var(--k) * 0.235s);
  }
  :is(.is-active, .is-leaving) .vm-svg .vm__chip {
    animation: sw-pop 0.45s var(--ease-spring) forwards;
    animation-delay: calc(3s + var(--i) * 0.1s);
  }
  :is(.is-active, .is-leaving) .vm-svg .vm__asset {
    animation: bn-slide-in 0.5s var(--ease-out) forwards;
    animation-delay: calc(3.05s + var(--i) * 0.1s);
  }
  :is(.is-active, .is-leaving) .vm-svg :is(.vm__owner, .vm__track) {
    animation: sw-fade 0.4s var(--ease-out) forwards;
    animation-delay: calc(3.1s + var(--i) * 0.1s);
  }
  :is(.is-active, .is-leaving) .vm-svg .vm__fill {
    animation: bn-grow-x 1s var(--ease-out) forwards;
    animation-delay: calc(3.4s + var(--i) * 0.14s);
  }
  :is(.is-active, .is-leaving) .vm-svg .vm__done {
    animation: sw-pop 0.45s var(--ease-spring) forwards;
    animation-delay: calc(4.4s + var(--i) * 0.14s);
  }
  :is(.is-active, .is-leaving) .vm-svg .vm__halo {
    animation: bn-ping 2.4s var(--ease-out) infinite;
    animation-delay: calc(4.9s + var(--i) * 0.5s);
  }

  /* Ethical phishing: un sobre por colaborador y, al llegar, su respuesta */
  .ph-svg :is(.ph__mail, .ph__result, .ph__bar) { opacity: 0; }
  .ph-svg :is(.ph__envelope, .ph__badge, .ph__halo) { transform-box: fill-box; transform-origin: center; }
  .ph-svg .ph__bar-fill { transform-box: fill-box; transform-origin: left center; transform: scaleX(0); }
  .ph-svg .ph__lure { transform-box: fill-box; transform-origin: center; }

  :is(.is-active, .is-leaving) .ph-svg .ph__mail { animation: sw-rise 0.7s var(--ease-out) 0.25s forwards; }
  :is(.is-active, .is-leaving) .ph-svg .ph__lure { animation: ph-bob 2.6s ease-in-out 1s infinite; }
  :is(.is-active, .is-leaving) .ph-svg .ph__envelope {
    animation: ph-send 1.1s ease-in-out forwards;
    animation-delay: calc(1s + var(--i) * 0.09s);
  }
  :is(.is-active, .is-leaving) .ph-svg .ph__result {
    animation: sw-fade 0.35s var(--ease-out) forwards;
    animation-delay: calc(2.05s + var(--i) * 0.09s);
  }
  :is(.is-active, .is-leaving) .ph-svg .ph__badge {
    animation: sw-pop 0.45s var(--ease-spring) both;
    animation-delay: calc(2.25s + var(--i) * 0.09s);
  }
  :is(.is-active, .is-leaving) .ph-svg .ph__halo {
    animation: bn-ping 2.4s var(--ease-out) infinite;
    animation-delay: calc(4.4s + var(--i) * 0.12s);
  }
  :is(.is-active, .is-leaving) .ph-svg .ph__bar {
    animation: sw-fade 0.3s var(--ease-out) forwards;
    animation-delay: calc(4.1s + var(--i) * 0.3s);
  }
  :is(.is-active, .is-leaving) .ph-svg .ph__bar-fill {
    animation: bn-grow-x 0.7s var(--ease-out) forwards;
    animation-delay: calc(4.1s + var(--i) * 0.3s);
  }

  /* GAP: el radar se arma, aparece el objetivo, crece el estado actual y se marca la brecha */
  .gap-svg .gap__ring { stroke-dasharray: 1; stroke-dashoffset: 1; }
  .gap-svg :is(.gap__axes, .gap__labels, .gap__target, .gap__point, .gap__gap, .gap__gap-label, .gap__sweep) { opacity: 0; }
  .gap-svg :is(.gap__target, .gap__current, .gap__sweep) { transform-box: view-box; transform-origin: 320px 350px; }
  .gap-svg .gap__current { transform: scale(0); }
  .gap-svg .gap__point { transform-box: fill-box; transform-origin: center; }
  .gap-svg .gap__gap-line { stroke-dasharray: 1; stroke-dashoffset: 1; }

  :is(.is-active, .is-leaving) .gap-svg .gap__ring {
    animation: sw-draw 1s var(--ease-out) forwards;
    animation-delay: calc(0.25s + var(--i) * 0.1s);
  }
  :is(.is-active, .is-leaving) .gap-svg .gap__axes { animation: sw-fade 0.6s var(--ease-out) 0.6s forwards; }
  :is(.is-active, .is-leaving) .gap-svg .gap__labels { animation: sw-fade 0.6s var(--ease-out) 0.9s forwards; }
  :is(.is-active, .is-leaving) .gap-svg .gap__target { animation: sw-pop 0.8s var(--ease-out) 1.3s forwards; }
  :is(.is-active, .is-leaving) .gap-svg .gap__current { animation: bn-grow 1s var(--ease-spring) 2s forwards; }
  :is(.is-active, .is-leaving) .gap-svg .gap__point {
    animation: sw-pop 0.4s var(--ease-spring) forwards;
    animation-delay: calc(2.7s + var(--i) * 0.08s);
  }
  :is(.is-active, .is-leaving) .gap-svg .gap__gap { animation: sw-fade 0.2s linear 3.3s forwards; }
  :is(.is-active, .is-leaving) .gap-svg .gap__gap-line { animation: sw-draw 0.6s var(--ease-out) 3.3s forwards; }
  :is(.is-active, .is-leaving) .gap-svg .gap__gap-label { animation: sw-fade 0.5s var(--ease-out) 3.7s forwards; }
  :is(.is-active, .is-leaving) .gap-svg .gap__sweep {
    animation: sw-fade 0.8s var(--ease-out) 3.9s forwards, bn-spin 6s linear 3.9s infinite;
  }
}

/* 3c. Banners animados: pentesting y análisis de vulnerabilidades ----------- */

@media (prefers-reduced-motion: no-preference) {
  /* Pentesting: el sello, la ruta hasta el objetivo, la metodología y el informe */
  .pt-svg :is(.pt__seal, .pt__mesh, .pt__node, .pt__crown, .pt__phase, .pt__report, .pt__finding, .pt__halo) { opacity: 0; }
  .pt-svg :is(.pt__seal, .pt__node, .pt__crown, .pt__phase, .pt__halo) { transform-box: fill-box; transform-origin: center; }
  .pt-svg :is(.pt__path, .pt__spine-fill) { stroke-dasharray: 1; stroke-dashoffset: 1; }

  :is(.is-active, .is-leaving) .pt-svg .pt__seal { animation: sw-pop 0.5s var(--ease-spring) 0.2s forwards; }
  :is(.is-active, .is-leaving) .pt-svg .pt__mesh { animation: sw-fade 0.7s var(--ease-out) 0.5s forwards; }
  :is(.is-active, .is-leaving) .pt-svg .pt__path { animation: sw-draw 1.2s var(--ease-out) 0.7s forwards; }
  :is(.is-active, .is-leaving) .pt-svg .pt__node {
    animation: sw-pop 0.4s var(--ease-spring) forwards;
    animation-delay: calc(0.75s + var(--i) * 0.55s);
  }
  :is(.is-active, .is-leaving) .pt-svg .pt__crown { animation: sw-pop 0.5s var(--ease-spring) 1.85s forwards; }
  :is(.is-active, .is-leaving) .pt-svg .pt__halo { animation: bn-ping 2.6s var(--ease-out) 2.3s infinite; }
  :is(.is-active, .is-leaving) .pt-svg .pt__spine-fill { animation: sw-draw 1.4s var(--ease-out) 1s forwards; }
  :is(.is-active, .is-leaving) .pt-svg .pt__phase {
    animation: sw-pop 0.45s var(--ease-spring) forwards;
    animation-delay: calc(1.15s + var(--i) * 0.3s);
  }
  :is(.is-active, .is-leaving) .pt-svg .pt__report { animation: sw-rise 0.7s var(--ease-out) 2.5s forwards; }
  :is(.is-active, .is-leaving) .pt-svg .pt__finding {
    animation: sw-fade 0.35s var(--ease-out) forwards;
    animation-delay: calc(2.75s + var(--i) * 0.18s);
  }

  /* Análisis: el barrido recorre la superficie, se tiñe el mapa de calor,
     se numera el plan y un control compensatorio repriorizada un hallazgo */
  .av-svg :is(.av__head, .av__asset, .av__cell, .av__badge, .av__reprio) { opacity: 0; }
  .av-svg :is(.av__cell, .av__badge) { transform-box: fill-box; transform-origin: center; }

  :is(.is-active, .is-leaving) .av-svg .av__head { animation: sw-fade 0.6s var(--ease-out) 0.3s forwards; }
  :is(.is-active, .is-leaving) .av-svg .av__asset {
    animation: bn-slide-in 0.5s var(--ease-out) forwards;
    animation-delay: calc(0.5s + var(--i) * 0.15s);
  }
  :is(.is-active, .is-leaving) .av-svg .av__scan { animation: av-scan 3s ease-in-out 0.6s infinite; }
  :is(.is-active, .is-leaving) .av-svg .av__cell {
    animation: sw-pop 0.4s var(--ease-spring) forwards;
    animation-delay: calc(0.9s + var(--i) * 0.28s + var(--k) * 0.05s);
  }
  :is(.is-active, .is-leaving) .av-svg .av__badge {
    animation: sw-pop 0.45s var(--ease-spring) forwards;
    animation-delay: calc(2.7s + var(--i) * 0.22s);
  }
  :is(.is-active, .is-leaving) .av-svg .av__reprio { animation: sw-fade 0.6s var(--ease-out) 3.5s forwards; }
}

/* 3d. Banner de Nosotros: evaluar, implementar y verificar ------------------- */
/* El mismo equipo recorre la arquitectura, encuentra los hallazgos, los cierra con
   controles (primero lo crítico) y verifica. Se anima al entrar en pantalla. */

@media (prefers-reduced-motion: no-preference) {
  .ns-svg :is(.ns__node, .ns__legend, .ns__guard, .ns__check, .ns__phase) { opacity: 0; }
  .ns-svg :is(.ns__node, .ns__finding, .ns__check, .ns__phase, .ns__halo) { transform-box: fill-box; transform-origin: center; }
  .ns-svg :is(.ns__link, .ns__spine-fill) { stroke-dasharray: 1; stroke-dashoffset: 1; }

  /* La arquitectura se arma de arriba hacia abajo */
  :is(.is-active, .is-leaving) .ns-svg .ns__node {
    animation: sw-pop 0.45s var(--ease-spring) forwards;
    animation-delay: calc(0.2s + var(--i) * 0.14s);
  }
  :is(.is-active, .is-leaving) .ns-svg .ns__link { animation: sw-draw 0.8s var(--ease-out) 0.55s forwards; }
  :is(.is-active, .is-leaving) .ns-svg .ns__legend { animation: sw-fade 0.6s var(--ease-out) 0.9s forwards; }
  /* Evaluar: el barrido pasa y deja los hallazgos de cada fila */
  :is(.is-active, .is-leaving) .ns-svg .ns__scan { animation: ns-barrido 1.5s ease-in-out 1s forwards; }
  :is(.is-active, .is-leaving) .ns-svg .ns__finding {
    animation:
      sw-pop 0.4s var(--ease-spring) calc(1.1s + var(--i) * 0.4s) forwards,
      ns-cierra 0.3s ease-in calc(2.8s + var(--k) * 0.2s) forwards;
  }
  /* Implementar: cada hallazgo se cierra con un control, primero los críticos */
  :is(.is-active, .is-leaving) .ns-svg .ns__guard {
    animation: sw-fade 0.5s var(--ease-out) forwards;
    animation-delay: calc(2.85s + var(--k) * 0.2s);
  }
  :is(.is-active, .is-leaving) .ns-svg .ns__check {
    animation: sw-pop 0.45s var(--ease-spring) forwards;
    animation-delay: calc(2.85s + var(--k) * 0.2s);
  }
  /* Las fases avanzan con la línea; verificar queda latiendo */
  :is(.is-active, .is-leaving) .ns-svg .ns__spine-fill { animation: sw-draw 3.3s linear 0.9s forwards; }
  :is(.is-active, .is-leaving) .ns-svg .ns__phase {
    animation: sw-pop 0.45s var(--ease-spring) forwards;
    animation-delay: calc(0.8s + var(--i) * 1.65s);
  }
  :is(.is-active, .is-leaving) .ns-svg .ns__halo { animation: bn-ping 2.6s var(--ease-out) 4.6s infinite; }
}

/* 3e. Banners de las demás páginas de servicio (tools/generar_banners_servicios.py) */
/* Una sola gramática para los doce (clase .bx): cada elemento dice cómo entra (a-pop,
   a-fade, a-draw…) y cuándo (data-i, en décimas de segundo desde que su diapositiva se
   activa). a-sale aparece en data-i y se va en data-k. a-giro, a-orbita, a-viaje, a-ping
   y a-parpadeo no se detienen. Sin animación, cada figura muestra su estado final. */

@media (prefers-reduced-motion: no-preference) {
  .bx :is(.a-pop, .a-fade, .a-rise, .a-slide) { opacity: 0; }
  .bx :is(.a-pop, .a-sale, .a-ping, .a-giro, .a-orbita) { transform-box: fill-box; transform-origin: center; }
  .bx :is(.a-draw, .a-lento) { stroke-dasharray: 1; stroke-dashoffset: 1; }
  .bx .a-grow-x { transform-box: fill-box; transform-origin: left center; transform: scaleX(0); }
  .bx .a-grow-y { transform-box: fill-box; transform-origin: center bottom; transform: scaleY(0); }

  :is(.is-active, .is-leaving) .bx .a-pop { animation: sw-pop 0.45s var(--ease-spring) calc(var(--i, 0) * 0.1s) forwards; }
  :is(.is-active, .is-leaving) .bx .a-fade { animation: sw-fade 0.5s var(--ease-out) calc(var(--i, 0) * 0.1s) forwards; }
  :is(.is-active, .is-leaving) .bx .a-rise { animation: sw-rise 0.6s var(--ease-out) calc(var(--i, 0) * 0.1s) forwards; }
  :is(.is-active, .is-leaving) .bx .a-slide { animation: bn-slide-in 0.5s var(--ease-out) calc(var(--i, 0) * 0.1s) forwards; }
  :is(.is-active, .is-leaving) .bx .a-draw { animation: sw-draw 0.8s var(--ease-out) calc(var(--i, 0) * 0.1s) forwards; }
  :is(.is-active, .is-leaving) .bx .a-lento { animation: sw-draw 3s linear calc(var(--i, 0) * 0.1s) forwards; }
  :is(.is-active, .is-leaving) .bx .a-grow-x { animation: bn-grow-x 0.9s var(--ease-out) calc(var(--i, 0) * 0.1s) forwards; }
  :is(.is-active, .is-leaving) .bx .a-grow-y { animation: bn-grow-y 0.8s var(--ease-out) calc(var(--i, 0) * 0.1s) forwards; }
  :is(.is-active, .is-leaving) .bx .a-llega { animation: bx-llega 1.2s var(--ease-out) calc(var(--i, 0) * 0.1s) both; }
  :is(.is-active, .is-leaving) .bx .a-sale {
    animation:
      sw-pop 0.4s var(--ease-spring) calc(var(--i, 0) * 0.1s) forwards,
      ns-cierra 0.3s ease-in calc(var(--k, 0) * 0.1s) forwards;
  }
  :is(.is-active, .is-leaving) .bx .a-ping { animation: bn-ping 2.6s var(--ease-out) calc(var(--i, 0) * 0.1s) infinite; }
  :is(.is-active, .is-leaving) .bx .a-giro { animation: bn-spin 6s linear calc(var(--i, 0) * 0.1s) infinite; }
  :is(.is-active, .is-leaving) .bx .a-orbita { animation: bn-spin 4s linear calc(var(--i, 0) * 0.1s) infinite; }
  :is(.is-active, .is-leaving) .bx .a-viaje { animation: bx-viaje 2.4s ease-in-out calc(var(--i, 0) * 0.1s) infinite; }
  :is(.is-active, .is-leaving) .bx .a-parpadeo { animation: bx-parpadeo 1s linear calc(var(--i, 0) * 0.1s) infinite; }
}

/* Láminas de metodología (tools/generar_metodologias.py) ---------------------- */
/* Apaisadas y a todo el ancho, sobre los pasos en texto. En teléfonos no se leen:
   ahí queda solo la lista de pasos. Se animan al entrar en pantalla (.is-active). */

.lamina { margin: 8px 0 56px; }
.lamina > img,
.lamina > svg { width: 100%; height: auto; }
@media (max-width: 639px) {
  .lamina { display: none; }
}

@media (prefers-reduced-motion: no-preference) {
  .mt-svg :is(.bn__frame, .mt__node, .mt__label, .mt__arrow, .mt__center, .mt__stage) { opacity: 0; }
  .mt-svg .mt__node { transform-box: fill-box; transform-origin: center; }
  .mt-svg .mt__track-fill { stroke-dasharray: 1; stroke-dashoffset: 1; }
  /* El recorrido del punto repite el trazado de cada pista (PISTA_* en el generador) */
  .mt-gestion .mt__traveler { offset-path: path("M260 150H940A110 110 0 0 1 940 370H260A110 110 0 0 1 260 150Z"); }
  .mt-phishing .mt__traveler { offset-path: path("M330 150H870A110 110 0 0 1 870 370H330A110 110 0 0 1 330 150Z"); }
  .mt-analisis .mt__traveler { offset-path: path("M200 355H980"); }
  .mt-svg .mt__traveler { offset-rotate: 0deg; }

  .lamina.is-active .mt-svg .bn__frame { animation: sw-fade 0.6s var(--ease-out) forwards; }
  .lamina.is-active .mt-svg .mt__track-fill { animation: sw-draw 1.8s var(--ease-out) 0.2s forwards; }
  .lamina.is-active .mt-svg :is(.mt__node, .mt__label) {
    animation: sw-pop 0.45s var(--ease-spring) forwards;
    animation-delay: calc(0.35s + var(--i) * 0.18s);
  }
  .lamina.is-active .mt-svg .mt__label { animation-name: sw-fade; }
  .lamina.is-active .mt-svg .mt__arrow {
    animation: sw-fade 0.4s var(--ease-out) forwards;
    animation-delay: calc(0.7s + var(--i) * 0.15s);
  }
  .lamina.is-active .mt-svg .mt__stage {
    animation: sw-rise 0.6s var(--ease-out) forwards;
    animation-delay: calc(0.6s + var(--i) * 0.35s);
  }
  .lamina.is-active .mt-svg .mt__center { animation: sw-fade 0.6s var(--ease-out) 1.7s forwards; }
  .lamina.is-active :is(.mt-gestion, .mt-phishing) .mt__traveler { animation: mt-recorrer 10s linear 2.4s infinite; }
  .lamina.is-active .mt-analisis .mt__traveler { animation: mt-cruzar 5s ease-in-out 2.4s infinite; }
}

/* Animaciones propias de los banners */
@keyframes bn-grow { from { transform: scale(0); } to { transform: scale(1); } }
@keyframes bn-grow-x { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@keyframes bn-grow-y { from { transform: scaleY(0); } to { transform: scaleY(1); } }
@keyframes bn-spin { to { transform: rotate(360deg); } }
@keyframes bn-slide-in { from { opacity: 0; transform: translateX(-8px); } to { opacity: 1; transform: none; } }
@keyframes av-scan {
  0% { opacity: 0; transform: translateY(0); }
  10% { opacity: 0.55; }
  48% { opacity: 0.55; transform: translateY(332px); }
  58%, 100% { opacity: 0; transform: translateY(332px); }
}
/* Banners de servicio (3e): algo que llega desde (--dx, --dy), algo que viaja una y otra vez, el cursor */
@keyframes bx-llega { from { transform: translate(var(--dx), var(--dy)); } to { transform: none; } }
@keyframes bx-viaje {
  0% { opacity: 0; transform: translate(0, 0); }
  12%, 80% { opacity: 1; }
  100% { opacity: 0; transform: translate(var(--dx), var(--dy)); }
}
@keyframes bx-parpadeo { 0%, 49% { opacity: 1; } 50%, 100% { opacity: 0; } }
/* Nosotros: el barrido de la evaluación y el hallazgo que se cierra */
@keyframes ns-barrido {
  0% { opacity: 0; transform: translateY(0); }
  12%, 88% { opacity: 1; }
  100% { opacity: 0; transform: translateY(380px); }
}
@keyframes ns-cierra { from { opacity: 1; transform: scale(1); } to { opacity: 0; transform: scale(0.4); } }
@keyframes bn-ping {
  0% { opacity: 0.8; transform: scale(1); }
  70%, 100% { opacity: 0; transform: scale(2.2); }
}
/* Hallazgos: aparecen, esperan y viajan a su filtro (donde se apagan) o hasta la cola */
@keyframes vm-filtra {
  0% { opacity: 0; transform: translate(0, 0); }
  12%, 42% { opacity: 1; transform: translate(0, 0); }
  86% { opacity: 1; transform: translate(var(--dx), var(--dy)); }
  100% { opacity: 0; transform: translate(var(--dx), var(--dy)) scale(0.4); }
}
@keyframes vm-pasa {
  0% { opacity: 0; transform: translate(0, 0); }
  10%, 40% { opacity: 1; transform: translate(0, 0); }
  92% { opacity: 1; transform: translate(var(--dx), var(--dy)); }
  100% { opacity: 0; transform: translate(var(--dx), var(--dy)); }
}
@keyframes vm-goteo {
  0% { opacity: 0; transform: translate(0, 0) scale(0.4); }
  5%, 12% { opacity: 1; transform: translate(0, 0) scale(1); }
  36% { opacity: 1; transform: translate(var(--dx), var(--dy)); }
  42%, 100% { opacity: 0; transform: translate(var(--dx), var(--dy)) scale(0.4); }
}
@keyframes ph-send {
  0% { opacity: 0; transform: translate(0, 0) scale(0.6); }
  15% { opacity: 1; transform: translate(0, 0) scale(1); }
  85% { opacity: 1; transform: translate(var(--dx), var(--dy)) scale(1); }
  100% { opacity: 0; transform: translate(var(--dx), var(--dy)) scale(0.6); }
}
@keyframes ph-bob {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-3px); }
}
@keyframes mt-recorrer {
  from { opacity: 1; offset-distance: 0%; }
  to { opacity: 1; offset-distance: 100%; }
}
@keyframes mt-cruzar {
  0% { opacity: 0; offset-distance: 0%; }
  10% { opacity: 1; }
  80% { opacity: 1; offset-distance: 100%; }
  90%, 100% { opacity: 0; offset-distance: 100%; }
}
