/* Animaciones para landings. Acompaña a reveal.js. Solo transform y opacity: nunca layout. */

:root {
  --anim-duration: 650ms;
  --anim-ease: cubic-bezier(0.22, 1, 0.36, 1); /* ease-out expo: llega rápido, asienta suave */
  --anim-distance: 28px;
}

/* ---------- estados iniciales: solo cuando el JS puede revelarlos ---------- */
.js-anim [data-anim] {
  opacity: 0;
  transition:
    opacity var(--anim-duration) var(--anim-ease),
    transform var(--anim-duration) var(--anim-ease),
    filter var(--anim-duration) var(--anim-ease),
    mask-size var(--anim-duration) var(--anim-ease),
    -webkit-mask-size var(--anim-duration) var(--anim-ease);
  will-change: opacity, transform;
}
.js-anim [data-anim="fade-up"]     { transform: translate3d(0, var(--anim-distance), 0); }
.js-anim [data-anim="slide-left"]  { transform: translate3d(calc(var(--anim-distance) * 1.6), 0, 0); }
.js-anim [data-anim="slide-right"] { transform: translate3d(calc(var(--anim-distance) * -1.6), 0, 0); }
.js-anim [data-anim="zoom-in"]     { transform: scale(0.92); }
.js-anim [data-anim="blur-in"]     { filter: blur(10px); transform: translate3d(0, 10px, 0); }
.js-anim [data-anim="clip-up"] {
  /* Con máscara y no con clip-path: Chrome descuenta el clip-path al medir si el elemento
     está en pantalla, y un elemento recortado al 100% nunca se revelaría. */
  opacity: 1;
  -webkit-mask-image: linear-gradient(#000 0 0); mask-image: linear-gradient(#000 0 0);
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: 0 100%; mask-position: 0 100%;
  -webkit-mask-size: 100% 0%; mask-size: 100% 0%;
}

.js-anim [data-anim].is-visible {
  opacity: 1;
  transform: none;
  filter: none;
  -webkit-mask-size: 100% 100%; mask-size: 100% 100%;
  will-change: auto;
}

/* El texto del hero no se desvanece: entra solo desplazándose.
   Chrome no considera pintado un elemento con opacity:0, así que un reveal con
   opacidad sobre el pliegue se suma entero al LCP (medido: 936 ms con fundido
   frente a 148 ms sin él). Manteniendo opacity:1 el movimiento se conserva y
   el LCP se registra en el primer pintado. */
.js-anim .hero-copy [data-anim] { opacity: 1; }

/* ---------- bucles para el hero y elementos vivos ---------- */
@keyframes float {
  0%, 100% { transform: translate3d(0, 0, 0) rotate(0deg); }
  50%      { transform: translate3d(0, -14px, 0) rotate(-1deg); }
}
.float { animation: float 6s ease-in-out infinite; }

@keyframes gradient-shift {
  0%   { background-position: 0% 50%; }
  50%  { background-position: 100% 50%; }
  100% { background-position: 0% 50%; }
}
.gradient-shift {
  background-size: 200% 200%;
  animation: gradient-shift 14s ease infinite;
}

@keyframes breathe {
  0%, 100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.12); }
  50%      { transform: scale(1.03); box-shadow: 0 10px 30px -10px rgba(0, 0, 0, 0.35); }
}
.breathe { animation: breathe 3.2s ease-in-out infinite; }

/* Marquee: duplica el contenido del track para que el bucle no tenga corte. */
.marquee { overflow: hidden; mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent); }
.marquee-track {
  display: flex;
  gap: 3rem;
  width: max-content;
  animation: marquee 32s linear infinite;
}
.marquee.is-paused .marquee-track, .marquee:hover .marquee-track { animation-play-state: paused; }
@keyframes marquee {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}

/* Línea que se dibuja (para "cómo funciona"): pon la clase en un SVG path con stroke. */
.js-anim .draw-line { stroke-dasharray: 1; stroke-dashoffset: 1; pathLength: 1; transition: stroke-dashoffset 1.4s var(--anim-ease); }
.js-anim .draw-line.is-visible, .js-anim .is-visible .draw-line { stroke-dashoffset: 0; }

/* ---------- micro-interacciones ---------- */
.lift { transition: transform 220ms var(--anim-ease), box-shadow 220ms var(--anim-ease); }
.lift:hover, .lift:focus-visible { transform: translate3d(0, -4px, 0); box-shadow: 0 18px 40px -18px rgba(0, 0, 0, 0.35); }

.press { transition: transform 120ms ease; }
.press:active { transform: scale(0.97); }

.underline-grow { position: relative; text-decoration: none; }
.underline-grow::after {
  content: "";
  position: absolute; left: 0; bottom: -2px;
  width: 100%; height: 2px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 240ms var(--anim-ease);
}
.underline-grow:hover::after, .underline-grow:focus-visible::after { transform: scaleX(1); }

/* ---------- accesibilidad: sin movimiento si el usuario lo pide ---------- */
@media (prefers-reduced-motion: reduce) {
  .js-anim [data-anim] { opacity: 1; transform: none; filter: none; -webkit-mask-image: none; mask-image: none; transition: none; }
  .float, .gradient-shift, .breathe, .marquee-track { animation: none; }
  .lift, .press, .underline-grow::after { transition: none; }
  [data-parallax] { transform: none !important; }
}
