/* ==========================================================================
   Scroll reveal vocabulary
   Carried over from the Craftline build: elements declare a resting (hidden)
   state, js/reveal.js adds .is-inview, and CSS transitions to identity.

   Every reveal is written twice — once for the element carrying .is-inview
   itself, once for an ancestor carrying it. That is what lets a single
   [data-scroll] wrapper 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);
}

/* --- Pop ----------------------------------------------------------------
   The playful one. Overshoots on the way in and settles back, optionally
   unwinding a resting tilt so sticker-ish cards snap into place.
   ------------------------------------------------------------------------- */
.anim-pop {
  --ease: var(--ease-back-out);
  --anim-duration: var(--transition-duration-slower);
  --anim-delay: 0s;
  --anim-scale: 0.72;
  --anim-rotate: -6deg;
  opacity: 0;
  transform: scale(var(--anim-scale)) rotate(var(--anim-rotate));
}
.anim-pop.is-inview,
.is-inview .anim-pop {
  opacity: 1;
  transform: scale(1) rotate(var(--rest-rotate, 0deg));
  transition: transform var(--anim-duration) var(--anim-delay) var(--ease),
    opacity var(--transition-duration-slow) var(--anim-delay)
      var(--ease-power2-out);
}

/* --- 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 tight display
     leading survives. Without it the mask crops the tails of g/y/p. */
  padding-bottom: 0.18em;
  margin-bottom: -0.18em;
}
.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) rotate(var(--tr, 0deg));
  will-change: transform, opacity;
}
.is-inview .word {
  opacity: 1;
  transform: translate3d(0, 0, 0) rotate(0deg);
  transition: opacity var(--transition-duration-slow) var(--ease-sine-out)
      var(--delay, 0s),
    transform var(--transition-duration-slowest) var(--ease-back-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);
}

/* ==========================================================================
   Ambient loops
   Small, slow, and always running — the difference between a page that
   reveals once and a page that feels alive while you read it.
   ========================================================================== */

@keyframes catty-float {
  0%,
  100% {
    transform: translateY(0) rotate(var(--float-rotate, 0deg));
  }
  50% {
    transform: translateY(var(--float-distance, -12px))
      rotate(calc(var(--float-rotate, 0deg) * -1));
  }
}
.is-floating {
  animation: catty-float var(--float-duration, 6s) ease-in-out infinite;
  animation-delay: var(--float-delay, 0s);
}

@keyframes catty-wiggle {
  0%,
  100% {
    transform: rotate(-3deg);
  }
  50% {
    transform: rotate(3deg);
  }
}

@keyframes catty-spin {
  to {
    transform: rotate(360deg);
  }
}
.is-spinning {
  animation: catty-spin var(--spin-duration, 22s) linear infinite;
}

@keyframes catty-marquee {
  from {
    transform: translate3d(0, 0, 0);
  }
  to {
    transform: translate3d(-50%, 0, 0);
  }
}

/* --- Reduced motion ------------------------------------------------------
   Durations are already zeroed in tokens.css; this resolves the resting
   transforms so nothing is left hidden or displaced, and stops the loops.
   ------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .anim-fade,
  .anim-fade-scale,
  .anim-up,
  .anim-up-scale,
  .anim-pop,
  .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;
  }
  .is-floating,
  .is-spinning,
  .c-marquee_track {
    animation: none !important;
  }
}
