/* alma.css — los efectos de un sitio de Framer, sin el runtime de Framer.
 *
 * Cubre los cuatro efectos que medí en xhulia.com, sancts.co, elenastudio,
 * aa4.agency y flyingplasticbag.com: reveal escalonado, parallax por capas,
 * rotacion acoplada al scroll y marquee infinito. Mas sticky stack y barra de
 * progreso, que en esos sitios ya eran CSS.
 *
 * Framer los resuelve escribiendo transforms inline desde JS en cada frame
 * (medí 9,733 mutaciones de atributo en un solo scroll de xhulia). Aquí todo
 * corre en el compositor via animation-timeline, sin tocar el hilo principal.
 *
 * No trae colores ni tipografía: eso lo define cada sitio. Esto es solo el motor.
 */

/* ── Curvas ──────────────────────────────────────────────────────────────── */

:root {
  /* Las tres de siempre. */
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);

  /* Spring aproximado con linear(). Es lo mas cerca que llega el CSS a la física
   * de framer-motion: rebasa el 100% y regresa, en vez de frenar en seco. */
  --ease-spring: linear(
    0, 0.006, 0.025 2.8%, 0.101 6.1%, 0.539 18.9%, 0.721 25.3%,
    0.849 31.5%, 0.937 38.1%, 0.968 41.8%, 0.991 45.7%, 1.006 50.1%,
    1.015 55%, 1.009 68.2%, 1
  );

  --dur-fast: 0.2s;
  --dur: 0.5s;
  --dur-slow: 0.9s;
}

/* ── 1. Reveal escalonado ─────────────────────────────────────────────────
 * Uso:  <div data-reveal>              entra desde abajo
 *       <div data-reveal="left">       entra desde la izquierda
 *       <li data-reveal style="--i:2"> tercer elemento de un stagger
 *
 * El stagger NO va con animation-delay (no aplica a timelines de scroll):
 * se hace corriendo el animation-range de cada hijo.
 */

@keyframes alma-reveal {
  from {
    opacity: 0;
    transform: translate3d(var(--reveal-x, 0), var(--reveal-y, 26px), 0)
               scale(var(--reveal-scale, 1));
  }
  to { opacity: 1; transform: none; }
}

[data-reveal] { --i: 0; }
[data-reveal="left"]  { --reveal-x: -32px; --reveal-y: 0; }
[data-reveal="right"] { --reveal-x: 32px;  --reveal-y: 0; }
[data-reveal="zoom"]  { --reveal-y: 0; --reveal-scale: 0.94; }
[data-reveal="far"]   { --reveal-y: 90px; }

@supports (animation-timeline: view()) {
  [data-reveal] {
    animation: alma-reveal linear both;
    animation-timeline: view();
    animation-range:
      entry calc(6% + var(--i) * 5%)
      entry calc(58% + var(--i) * 5%);
  }
}

/* Fallback: Firefox y Safari viejos. 14 líneas de JS en alma.js ponen .is-in. */
@supports not (animation-timeline: view()) {
  [data-reveal] {
    opacity: 0;
    transform: translate3d(var(--reveal-x, 0), var(--reveal-y, 26px), 0)
               scale(var(--reveal-scale, 1));
    transition:
      opacity var(--dur) var(--ease-out) calc(var(--i) * 70ms),
      transform var(--dur-slow) var(--ease-out) calc(var(--i) * 70ms);
  }
  [data-reveal].is-in { opacity: 1; transform: none; }
}

/* ── 2. Parallax por capas ────────────────────────────────────────────────
 * Uso: <img data-parallax style="--depth:2">
 * depth 1, 2, 3 = 1x, 2x, 3x de recorrido. Es literalmente lo que hace
 * flyingplasticbag: medí translate3d de 1551.45px, 3102.9px y 4654.35px,
 * múltiplos exactos del mismo numero.
 */

@keyframes alma-parallax {
  from { transform: translate3d(0, calc(var(--depth, 1) * var(--parallax-range, 90px) * -1), 0); }
  to   { transform: translate3d(0, calc(var(--depth, 1) * var(--parallax-range, 90px)), 0); }
}

@supports (animation-timeline: view()) {
  [data-parallax] {
    animation: alma-parallax linear both;
    animation-timeline: view();
    animation-range: cover;
    will-change: transform;
  }
}

/* ── 3. Vuelo: traslacion + rotacion acopladas al scroll ──────────────────
 * El avión de flyingplasticbag. Medí translateY(-517.15px) rotate(19deg) y su
 * espejo translateY(517.15px) rotate(-9deg).
 *
 * Uso: <img data-fly style="--fly-y:60vh; --fly-r0:14deg; --fly-r1:-5deg">
 * Va dentro de un .stage (ver punto 5) para que tenga pista de scroll.
 */

@keyframes alma-fly {
  from {
    transform: translate3d(var(--fly-x0, 0), calc(var(--fly-y, 55vh) * -1), 0)
               rotate(var(--fly-r0, 12deg)) scale(var(--fly-s0, 1));
  }
  to {
    transform: translate3d(var(--fly-x1, 0), var(--fly-y, 55vh), 0)
               rotate(var(--fly-r1, -6deg)) scale(var(--fly-s1, 1));
  }
}

@supports (animation-timeline: view()) {
  [data-fly] {
    animation: alma-fly linear both;
    animation-timeline: view();
    animation-range: cover;
    will-change: transform;
  }
}

/* ── 4. Marquee infinito ──────────────────────────────────────────────────
 * Framer lo mueve con JS escribiendo transform cada frame. Aquí son dos pistas
 * idénticas (la segunda aria-hidden) y una keyframe. Cero JS.
 *
 * <div class="marquee" style="--marquee-dur:45s">
 *   <ul class="marquee__track">…</ul>
 *   <ul class="marquee__track" aria-hidden="true">…</ul>
 * </div>
 */

.marquee {
  display: flex;
  gap: var(--marquee-gap, 2rem);
  overflow: hidden;
  /* Difumina los bordes en vez de cortarlos a navaja. */
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
}

.marquee__track {
  display: flex;
  flex: 0 0 auto;
  gap: var(--marquee-gap, 2rem);
  margin: 0;
  padding: 0;
  list-style: none;
  min-width: max-content;
  animation: alma-marquee var(--marquee-dur, 40s) linear infinite;
}

.marquee--reverse .marquee__track { animation-direction: reverse; }
.marquee:hover .marquee__track { animation-play-state: paused; }

@keyframes alma-marquee {
  to { transform: translate3d(calc(-100% - var(--marquee-gap, 2rem)), 0, 0); }
}

/* ── 5. Sticky stack y escenario de scroll ────────────────────────────────
 * Dos usos de lo mismo.
 *
 * .stack + .stack__card = las tarjetas que se encaballan de xhulia.com
 *   (medí 4 elementos con position:sticky; top:0 y ~937px de alto).
 *
 * .stage + .stage__pin = el storytelling de flyingplasticbag
 *   (contenedor de 7,352 px con un hijo sticky de una pantalla: se queda
 *   pegado 8 pantallas de scroll mientras el progreso maneja los transforms).
 */

.stack__card {
  position: sticky;
  top: var(--stack-top, 0px);
  border-radius: var(--stack-radius, 28px);
  overflow: clip;
}

/* La de abajo se encoge tantito al ser tapada: da sensación de profundidad.
 * Framer esto lo hace con JS; aquí es una keyframe más. */
@supports (animation-timeline: view()) {
  .stack--depth .stack__card {
    animation: alma-stack-sink linear both;
    animation-timeline: view();
    animation-range: exit 0% exit 100%;
    transform-origin: 50% 0%;
  }
}
@keyframes alma-stack-sink {
  to { transform: scale(0.94); filter: brightness(0.82); }
}

/* ── 5bis. Progreso de una escena fija (el scroll-storytelling) ───────────
 * Lo que hace flyingplasticbag: un contenedor alto con un hijo sticky que se
 * queda quieto mientras pasan varias pantallas de scroll, y el contenido de
 * adentro va cambiando según cuánto llevas recorrido.
 *
 * `view()` no sirve aquí: el hijo sticky NO se mueve respecto al viewport, así
 * que su progreso se queda clavado. Lo que hace falta es que el CONTENEDOR
 * publique su propio avance con `view-timeline`, y que los hijos del pin lo
 * consuman por nombre.
 *
 *   <section class="stage" style="--stage-length: 500vh">
 *     <div class="stage__pin">
 *       <p data-paso="1">…</p>   ← aparece en el primer quinto
 *       <p data-paso="2">…</p>
 *     </div>
 *   </section>
 *
 * `--pasos` dice en cuántos tramos se reparte la escena.
 */

.stage {
  position: relative;
  height: var(--stage-length, 400vh);
  view-timeline: --escena block;
}

.stage__pin {
  position: sticky;
  top: 0;
  height: 100svh;
  overflow: clip;
}

/* Sin registrar el tipo, `calc(var(--paso) / var(--pasos))` no valida: el
 * navegador no puede dividir entre un token que no sabe que es número, y el
 * animation-range entero se cae a `normal` — o sea todos los pasos aparecen a
 * la vez y la escena no coreografía nada. */
@property --paso  { syntax: "<number>"; inherits: false; initial-value: 1; }
@property --pasos { syntax: "<number>"; inherits: true;  initial-value: 3; }

@keyframes alma-paso {
  0%   { opacity: 0; transform: translate3d(0, var(--paso-y, 34px), 0); }
  14%  { opacity: 1; transform: none; }
  72%  { opacity: 1; transform: none; }
  100% { opacity: 0; transform: translate3d(0, calc(var(--paso-y, 34px) * -0.6), 0); }
}

@supports (animation-timeline: view()) {
  [data-paso] {
    animation: alma-paso linear both;
    animation-timeline: --escena;
    /* Cada paso ocupa su tramo del recorrido: el 1 de 5 va de 0 % a 20 %. */
    animation-range:
      contain calc((var(--paso) - 1) / var(--pasos, 3) * 100%)
      contain calc(var(--paso) / var(--pasos, 3) * 100%);
  }

  /* Algo que avanza a lo largo de TODA la escena: una línea que se dibuja, un
   * objeto que cruza, un contador. No se reparte en tramos. */
  [data-escena] {
    animation: var(--escena-anim, alma-escena) linear both;
    animation-timeline: --escena;
    animation-range: contain 0% contain 100%;
  }
}

@keyframes alma-escena {
  from { scale: 1 0; }
  to   { scale: 1 1; }
}

/* Sin scroll-driven, los pasos se muestran todos y la escena se queda quieta:
 * pierdes la coreografía, no el contenido. */
@supports not (animation-timeline: view()) {
  .stage { height: auto; }
  .stage__pin { position: static; height: auto; }
}

/* ── 6. Barra de progreso de scroll ───────────────────────────────────────
 * La barra amarilla de xhulia.com. Una keyframe, sin listener de scroll.
 * <div class="scroll-progress"></div>
 */

.scroll-progress {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 100;
  height: var(--progress-height, 3px);
  background: var(--progress-color, currentColor);
  transform-origin: 0 50%;
  transform: scaleX(0);
}

@supports (animation-timeline: scroll()) {
  .scroll-progress {
    animation: alma-progress linear;
    animation-timeline: scroll(root block);
  }
}
@keyframes alma-progress { to { transform: scaleX(1); } }

/* ── 7. Micro-interacciones ───────────────────────────────────────────────
 * Lo único que de verdad conviene animar con transition, porque responde al
 * puntero y no al scroll.
 */

[data-lift] { transition: transform var(--dur-fast) var(--ease-spring); }
[data-lift]:hover { transform: translateY(-3px); }
[data-lift]:active { transform: translateY(0); }

/* ── 7bis. Caída con rebote ───────────────────────────────────────────────
 * El montón de píldoras del hero de xhulia.com cae del cielo al cargar y se
 * asienta en el fondo de la primera pantalla. Allá lo resuelve un motor de
 * física con tres <canvas>; aquí es una keyframe con `--ease-spring`, que
 * rebasa el 100 % y regresa — que es lo que se ve cuando algo aterriza.
 *
 * Lo que sí se pierde: las píldoras no colisionan entre ellas, así que su
 * posición final va escrita a mano en vez de emerger del choque. Quieto se ve
 * igual; en movimiento, la caída es coreografiada y no emergente.
 *
 * Uso: <li data-drop style="--orden:3; --giro:-13deg; --giro-ini:-52deg">
 */

@keyframes alma-drop {
  from {
    translate: 0 calc(-100vh - 100%);
    rotate: var(--giro-ini, -45deg);
    opacity: 0;
  }
  20% { opacity: 1; }
  to {
    translate: 0 0;
    rotate: var(--giro, 0deg);
    opacity: 1;
  }
}

[data-drop] {
  animation: alma-drop var(--drop-dur, 1.05s) var(--ease-spring) both;
  animation-delay: calc(var(--orden, 0) * var(--drop-stagger, 65ms));
}

/* ── 8. Respeto a prefers-reduced-motion ──────────────────────────────────
 * No se desactiva todo a ciegas: los reveals se resuelven al estado final
 * (si no, el contenido queda invisible) y el movimiento decorativo se apaga.
 */

@media (prefers-reduced-motion: reduce) {
  [data-reveal],
  [data-parallax],
  [data-fly],
  [data-drop],
  [data-paso],
  [data-escena],
  .stack--depth .stack__card {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  [data-drop] { rotate: var(--giro, 0deg) !important; }
  .marquee__track { animation-play-state: paused; }
  .scroll-progress { animation: none; transform: scaleX(1); }
}

/* ── 9. Higiene ───────────────────────────────────────────────────────────
 * En xhulia.com conté 22 elementos con will-change:transform pegado para
 * siempre. Cada uno es una capa de GPU que no se libera: es por lo que esos
 * sitios calientan una laptop modesta. Aquí will-change solo va en lo que se
 * mueve durante todo el scroll (parallax y fly), nunca en los reveals, que
 * terminan y se quedan quietos.
 */

@media (prefers-reduced-motion: reduce) {
  [data-parallax], [data-fly] { will-change: auto; }
}
