/* =============================================================
   ANIMATIONS — keyframes y utilidades de revelado en scroll
   Solo transform + opacity (GPU-friendly, seguro en hardware
   modesto: Mali G31 / TV Box, gama baja de smartphones).
============================================================= */

@keyframes pulse-dot {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%       { opacity: 0.35; transform: scale(0.8); }
}

@keyframes cue-sweep {
  0%   { transform: translateX(-100%); }
  50%  { transform: translateX(0); }
  100% { transform: translateX(100%); }
}

/* Flujo ambiental del arco de ciclo del diagrama de Servicios (ver
   .services-diagram__cycle-path en layout.css) — dashes desplazándose a
   lo largo del path, sugiere dirección de flujo incluso en reposo.
   Reutilizado tal cual (mismo keyframe) por la variante mobile del arco
   (.services-diagram__cycle-mobile-path, ver layout.css). */
@keyframes cycle-flow {
  to { stroke-dashoffset: -24; }
}

/* "Empuje" de la flecha conectora entre pilares del diagrama de Servicios
   cuando el nodo anterior está activo (ver .services-diagram__arrow en
   components.css) — dos variantes según la dirección real del flujo en
   pantalla (vertical en mobile/tablet, horizontal desde 1100px). Solo
   transform (scale + translate), nunca filter — barato en cualquier GPU. */
@keyframes services-arrow-flow-y {
  0%, 100% { transform: scale(1.15) translateY(0); }
  50%      { transform: scale(1.15) translateY(5px); }
}

@keyframes services-arrow-flow-x {
  0%, 100% { transform: scale(1.15) translateX(0); }
  50%      { transform: scale(1.15) translateX(5px); }
}

/* Entrada individual de cada CTA del hero (.hero__actions .btn, ver más
   abajo) — one-shot, con animation-fill-mode "both" vía el shorthand
   (mantiene el estado "from" durante el animation-delay del stagger, y el
   estado "to" al terminar). Ver el comentario junto a ".hero__actions
   .btn" para por qué es @keyframes y no transition. */
@keyframes hero-btn-in {
  from { opacity: 0; transform: translateY(14px) scale(0.96); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

/* "Espina" vertical decorativa detrás de la columna de pilares en mobile/
   tablet (ver .services-diagram::before en layout.css) — el mismo gesto
   ambiental que .services-diagram__cycle-path (dashes desplazándose),
   aplicado a background-position en vez de stroke-dashoffset porque es
   un gradiente repetido, no un <path> SVG. */
@keyframes services-spine-flow {
  to { background-position: 0 -14px; }
}

/* ---- Revelado genérico al entrar en viewport ---- */
.reveal {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity var(--dur-slow) var(--ease-out), transform var(--dur-slow) var(--ease-out);
}

.reveal.is-visible {
  opacity: 1;
  transform: translateY(0);
}

/* Stagger para grids (services, contact-channels) vía --i */
.reveal[style*="--i"] {
  transition-delay: calc(var(--i, 0) * 70ms);
}

@media (prefers-reduced-motion: reduce) {
  .reveal {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* ---- Entrada del hero — coreografía de CARGA, no de scroll (2026-07-29) ----
   Distinto de `.reveal` arriba: `.reveal` se dispara por scroll vía
   IntersectionObserver (reveal.js) para contenido que arranca fuera del
   viewport. El hero está SIEMPRE visible desde la carga inicial — no hay
   nada que un IntersectionObserver pueda "observar entrar" — por eso usa
   su propio gatillo manual en hero.js, encadenado al mismo momento en que
   arranca el reveal del H1 (scrambleReveal), para que eyebrow/rol/
   propuesta de valor/ticker/CTAs entren en cascada justo después del
   titular, en vez de aparecer estáticos de golpe. Mismo lenguaje visual
   (fade + slide, --ease-out/--dur-slow) y el mismo stagger vía --i que ya
   usa `.reveal`.

   Ronda 4 (2026-07-29, feedback explícito: "llevar la coreografía más
   lejos"): tres refuerzos sobre la base ya existente, mismo vocabulario
   de movimiento (transform + opacity, --ease-out, --dur-*) que el resto
   del sitio:
     1. Se suma un ligero scale(0.98→1) al fade+slide — la lectura pasa de
        "aparece y sube" a "se asienta en su lugar", eco sutil del
        scale(1.04→1) que ya usa el scramble del H1 (ver hero.js).
     2. .hero__ticker se incorpora a la cascada (antes aparecía de golpe,
        cortando el ritmo justo antes de los CTAs) — ver index.html.
     3. .hero__role gana un trazo editorial que se dibuja (scaleX) al
        mismo tiempo que el texto entra — mismo lenguaje visual que
        .scroll-cue__line (línea que se anima) ya usa en otra parte del
        hero, reutilizado en vez de inventar un efecto nuevo. */
.hero-fade-in {
  opacity: 0;
  transform: translateY(16px) scale(0.98);
  transition: opacity var(--dur-slow) var(--ease-out), transform var(--dur-slow) var(--ease-out);
}

.hero-fade-in.is-visible {
  opacity: 1;
  transform: translateY(0) scale(1);
}

.hero-fade-in[style*="--i"] {
  transition-delay: calc(var(--i, 0) * 110ms);
}

/* Trazo editorial sobre ".hero__role" — se dibuja (scaleX 0→1) desde el
   mismo instante en que el texto empieza a entrar (reutiliza el mismo
   --i, ver index.html). Deliberadamente ARRIBA del texto, no a su
   izquierda: el hero mantiene un único margen izquierdo compartido por
   eyebrow/H1/value/actions — indentar solo hero__role habría roto ese
   alineamiento. transform-origin left: crece hacia la derecha, como una
   regla tipográfica que se traza antes de "leerse" la línea. */
.hero__role {
  position: relative;
  padding-top: var(--space-3);
}

.hero__role::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  width: var(--space-6);
  height: 1px;
  background: var(--color-cyan);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur-slow) var(--ease-out);
  transition-delay: calc(var(--i, 0) * 110ms + 90ms);
}

.hero__role.is-visible::before {
  transform: scaleX(1);
}

/* .hero__actions no se desvanece como bloque — cada CTA entra por su
   cuenta con su propio pequeño stagger, más dinámico que un bloque único
   apareciendo de golpe. La regla de mayor especificidad
   (.hero__actions.hero-fade-in) neutraliza el opacity/transform genérico
   de arriba SOLO en el contenedor; los .btn hijos llevan su propio ciclo,
   disparado por la misma clase .is-visible que ya togglea hero.js (sin
   cambios en JS).

   Deliberadamente @keyframes/animation, NO transition, para la entrada
   de cada botón: `.btn` (components.css) ya define su propio
   `transition` (shorthand) para el feedback de hover/focus (transform,
   background, color, border-color, box-shadow, todos a --dur-fast). Si
   la entrada se hiciera con `transition` sobre ".hero__actions .btn"
   (misma especificidad de clase que ".btn"), el shorthand de quien cargue
   último en el <link> GANARÍA COMPLETO — no se mezclan, se reemplazan —
   rompiendo la transición de hover de los CTAs. Peor aún: un
   `transition-delay` por :nth-child con --i para el stagger quedaría
   pegado al elemento para SIEMPRE (nunca se remueve la clase
   .hero-fade-in), retrasando también cualquier transición futura de
   hover en ~0.5-0.6s. `animation` es un mecanismo independiente de
   `transition` — cero colisión con el `transition` de `.btn`, y al no
   ser `infinite` termina y deja de intervenir sin más gestión. */
.hero__actions.hero-fade-in {
  opacity: 1;
  transform: none;
  transition: none;
}

.hero__actions .btn {
  opacity: 0; /* estado antes de que hero.js dispare la entrada */
}

/* Ronda 4 — fix encontrado al verificar con prefers-reduced-motion
   simulado en Playwright: animation-delay NO está cubierto por la regla
   global de base.css que fuerza animation-duration/iteration-count bajo
   reduced-motion — el delay (hasta ~600ms con el --i:4 de este bloque)
   seguía aplicando igual, y mientras el delay corre el fill-mode
   "backwards" mantiene el estado "from" (opacity:0) POR ENCIMA de
   cualquier "opacity:1" estático que se declare para reduced-motion más
   abajo (una animación activa siempre gana sobre una declaración normal
   para la misma propiedad). Envolver el disparo de la animación en
   "not (prefers-reduced-motion: reduce)" evita el problema de raíz: bajo
   reduced-motion esta regla directamente no existe, así que la carve-out
   de más abajo (opacity:1 estático, sin animación) queda sin competencia. */
@media not (prefers-reduced-motion: reduce) {
  .hero__actions.is-visible .btn {
    animation: hero-btn-in var(--dur-base) var(--ease-out) both;
  }

  .hero__actions.is-visible .btn:nth-child(1) { animation-delay: calc(var(--i, 0) * 110ms + 60ms); }
  .hero__actions.is-visible .btn:nth-child(2) { animation-delay: calc(var(--i, 0) * 110ms + 170ms); }
}

@media (prefers-reduced-motion: reduce) {
  .hero-fade-in {
    opacity: 1;
    transform: none;
    transition: none;
  }

  .hero__role::before {
    transform: scaleX(1);
    transition: none;
  }

  .hero__actions .btn {
    opacity: 1;
    animation: none;
  }
}
