/* ==========================================================================
   Scroll reveal vocabulary
   Transcribed from the reference build. Elements declare a resting (hidden)
   state; js/reveal.js adds .is-inview and CSS transitions to identity.
   ========================================================================== */

/* Every reveal below is written twice: once for the element carrying
   .is-inview itself, and once for an ancestor carrying it. The reference
   build does the same, which is what allows a single [data-scroll] wrapper
   to release a whole group of children at once. */

/* --- Fade ---------------------------------------------------------------- */
.anim-fade {
  --ease: var(--ease-power2-out);
  --anim-duration: var(--transition-duration-slow);
  --anim-delay: 0s;
  opacity: 0;
}
.anim-fade.is-inview,
.is-inview .anim-fade {
  opacity: 1;
  transition: opacity var(--anim-duration) var(--anim-delay) var(--ease);
}

/* --- Fade + scale -------------------------------------------------------- */
.anim-fade-scale {
  --ease: var(--ease-power2-out);
  --anim-duration: var(--transition-duration-slow);
  --anim-delay: 0s;
  --anim-scale: 0.95;
  opacity: 0;
  transform: scale(var(--anim-scale));
}
.anim-fade-scale.is-inview,
.is-inview .anim-fade-scale {
  opacity: 1;
  transform: scale(1);
  transition: transform var(--anim-duration) var(--anim-delay) var(--ease),
    opacity var(--anim-duration) var(--anim-delay) var(--ease);
}

/* --- Rise ---------------------------------------------------------------- */
.anim-up {
  --ease: var(--ease-expo-out);
  --anim-duration: var(--transition-duration-slower);
  --anim-delay: 0s;
  opacity: 0;
  transform: translateY(20%);
}
.anim-up.is-inview,
.is-inview .anim-up {
  opacity: 1;
  transform: translate(0);
  transition: transform var(--anim-duration) var(--anim-delay) var(--ease),
    opacity var(--anim-duration) var(--anim-delay) var(--ease);
}

/* --- Rise + scale -------------------------------------------------------- */
.anim-up-scale {
  --ease: var(--ease-expo-out);
  --anim-duration: var(--transition-duration-slower);
  --anim-delay: 0s;
  --anim-scale: 0.9;
  opacity: 0;
  transform: translate3d(0, 20%, 0) scale(var(--anim-scale));
}
.anim-up-scale.is-inview,
.is-inview .anim-up-scale {
  opacity: 1;
  transform: translate(0) scale(1);
  transition: transform var(--anim-duration) var(--anim-delay) var(--ease),
    opacity var(--anim-duration) var(--anim-delay) var(--ease);
}

/* --- Axis wipes ---------------------------------------------------------- */
.anim-scale-x {
  --ease: var(--ease-expo-out);
  --anim-duration: var(--transition-duration-slower);
  --anim-delay: 0s;
  transform-origin: 0;
  transform: scaleX(0);
}
.anim-scale-x.is-inview,
.is-inview .anim-scale-x {
  transform: scaleX(1);
  transition: transform var(--anim-duration) var(--anim-delay) var(--ease);
}

.anim-scale-y {
  --ease: var(--ease-expo-out);
  --anim-duration: var(--transition-duration-slower);
  --anim-delay: 0s;
  transform-origin: top;
  transform: scaleY(0);
}
.anim-scale-y.is-inview,
.is-inview .anim-scale-y {
  transform: scaleY(1);
  transition: transform var(--anim-duration) var(--anim-delay) var(--ease);
}

/* --- Masked line-by-line text -------------------------------------------
   Lines are wrapped by js/split.js. The negative margin / positive padding
   pair creates the overflow mask without clipping descenders.
   ------------------------------------------------------------------------- */
.anim-text {
  --ease: var(--ease-expo-out);
  --anim-duration: var(--transition-duration-slowest);
  --anim-delay: 0s;
  --anim-stagger: 0.1s;
}
.anim-text .anim-text-line {
  display: block;
  overflow: hidden;
  /* Descender room, cancelled by the negative margin so the line rhythm of
     the tight display leading is preserved. Without this the mask crops the
     tails of g/y/p in the resting state. */
  padding-bottom: 0.16em;
  margin-bottom: -0.16em;
}
.anim-text .anim-text-item {
  display: block;
  /* Overshoots the taller mask so nothing peeks before the reveal. */
  transform: translateY(130%);
}
.anim-text.is-inview .anim-text-item,
.is-inview .anim-text-item {
  transform: translate(0);
  transition: transform var(--anim-duration) var(--ease);
  transition-delay: calc(
    var(--anim-delay, 0s) + (var(--index, 0) * var(--anim-stagger, 0.1s))
  );
}

/* --- Word scatter (galaxy headline) -------------------------------------
   Each word receives --tx / --ty / --delay from the galaxy component and
   converges from its offset on reveal.
   ------------------------------------------------------------------------- */
.word {
  display: inline-block;
  opacity: 0;
  transform: translate3d(var(--tx, 0), var(--ty, 0), 0);
  will-change: transform, opacity;
}
.is-inview .word {
  opacity: 1;
  transform: translate3d(0, 0, 0);
  transition: opacity var(--transition-duration-slow) var(--ease-sine-out)
      var(--delay, 0s),
    transform var(--transition-duration-slowest) var(--ease-expo-out)
      var(--delay, 0s);
}

/* --- Lazy image settle --------------------------------------------------- */
.c-image_img[loading="lazy"] {
  opacity: 0;
  transform: scale(1.05);
  transition: opacity var(--transition-duration-slow) var(--ease-power2-out),
    transform var(--transition-duration-slowest) var(--ease-expo-out);
}
.c-image.is-loaded .c-image_img[loading="lazy"],
.is-inview .c-image_img[loading="lazy"] {
  opacity: 1;
  transform: scale(1);
}

/* --- Reduced motion ------------------------------------------------------
   Durations are already zeroed in tokens.css; this resolves the resting
   transforms so nothing is left hidden or displaced.
   ------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .anim-fade,
  .anim-fade-scale,
  .anim-up,
  .anim-up-scale,
  .word,
  .c-image_img[loading="lazy"] {
    opacity: 1 !important;
    transform: none !important;
  }
  .anim-scale-x,
  .anim-scale-y,
  .anim-text .anim-text-item {
    transform: none !important;
  }
}
