/* ==========================================================================
   animations.css — scroll reveal engine, marquee, parallax, motion primitives
   Driven by main.js (IntersectionObserver) — CSS owns the visuals.
   ========================================================================== */

/* ---------- Reveal engine ----------
   Markup: <div data-reveal="fade-up" data-delay="120">
   JS adds .is-in when the element enters the viewport (once).
   Elements start hidden ONLY if JS is running (html.js), so the page
   degrades to fully-visible content with JS disabled.
------------------------------------- */
.js [data-reveal] {
  opacity: 0;
  will-change: opacity, transform;
  transition:
    opacity var(--dur) var(--ease-out),
    transform var(--dur) var(--ease-out),
    clip-path var(--dur) var(--ease-out),
    filter var(--dur) var(--ease-out);
  transition-delay: var(--reveal-delay, 0ms);
}

.js [data-reveal="fade-up"]    { transform: translate3d(0, 34px, 0); }
.js [data-reveal="fade-down"]  { transform: translate3d(0, -28px, 0); }
.js [data-reveal="fade-right"] { transform: translate3d(-42px, 0, 0); }
.js [data-reveal="fade-left"]  { transform: translate3d(42px, 0, 0); }
.js [data-reveal="scale-in"]   { transform: scale(.92); }
.js [data-reveal="blur-in"]    { filter: blur(12px); transform: translate3d(0, 18px, 0); }
/* Clip variants stop 1% short of fully collapsing. A target clipped to zero
   area has an empty intersection rectangle, so IntersectionObserver would
   never report it as visible and the reveal could never fire. */
.js [data-reveal="clip-up"]    { clip-path: inset(99% 0 0 0); transform: translate3d(0, 28px, 0); }
.js [data-reveal="clip-right"] { clip-path: inset(0 99% 0 0); }
.js [data-reveal="fade"]       { /* opacity only */ }

.js [data-reveal].is-in {
  opacity: 1;
  transform: none;
  filter: none;
  clip-path: inset(0 0 0 0);
}

/* Stagger helper: parent adds incremental delay to children */
.js [data-stagger] > * { --reveal-delay: calc(var(--i, 0) * 80ms); }

/* On narrow screens a horizontal start offset parks the element outside the
   viewport until it reveals, which produces real horizontal scroll. Slide
   these in vertically instead. */
@media (max-width: 860px) {
  .js [data-reveal="fade-right"],
  .js [data-reveal="fade-left"] { transform: translate3d(0, 28px, 0); }
}

/* ---------- Headline word reveal ---------- */
.split-line { display: block; overflow: hidden; padding-bottom: .06em; }
.split-word {
  display: inline-block;
  transform: translate3d(0, 105%, 0);
  transition: transform .9s var(--ease-out);
  transition-delay: calc(var(--w, 0) * 55ms);
}
.js .is-in .split-word,
.js .split-ready.is-in .split-word { transform: none; }
html:not(.js) .split-word { transform: none; }

/* ---------- Parallax layers ----------
   JS writes --py (px). Element translates by that amount.
------------------------------------- */
.parallax { will-change: transform; transform: translate3d(0, var(--py, 0px), 0); }

/* ---------- Counters ---------- */
.counter { font-variant-numeric: tabular-nums; }

/* ---------- Logo marquee ---------- */
.marquee {
  position: relative;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
.marquee__track {
  display: flex;
  width: max-content;
  gap: clamp(2rem, 4vw, 3.5rem);
  animation: marquee var(--speed, 46s) linear infinite;
}
.marquee--reverse .marquee__track { animation-direction: reverse; }
.marquee:hover .marquee__track { animation-play-state: paused; }
@keyframes marquee { to { transform: translate3d(-50%, 0, 0); } }

/* Flex rather than grid for the same reason as .award-card__img: percentage
   max-height does not resolve reliably against a grid area. */
.marquee__item {
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  flex: none;
  height: 64px;
  width: 150px;
  padding: .5rem;
}
.marquee__item img {
  max-height: 100%;
  max-width: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  filter: grayscale(1);
  opacity: .55;
  transition: filter .35s ease, opacity .35s ease, transform .35s var(--ease-out);
}
.marquee__item:hover img { filter: none; opacity: 1; transform: scale(1.07); }

/* ---------- Pinned process rail ---------- */
.process__rail {
  position: relative;
  width: 2px;
  background: var(--line);
  border-radius: 2px;
  flex: none;
}
.process__rail-fill {
  position: absolute;
  top: 0; left: 0;
  width: 100%;
  height: var(--fill, 0%);
  background: linear-gradient(180deg, var(--orange-500), var(--orange-400));
  border-radius: 2px;
  transition: height .25s linear;
}

/* ---------- Counting/loading shimmer for estimator result ---------- */
@keyframes pop-in {
  0%   { opacity: 0; transform: translate3d(0, 14px, 0) scale(.97); }
  100% { opacity: 1; transform: none; }
}
.pop-in { animation: pop-in .55s var(--ease-out) both; }

/* ---------- Soft float (decorative) ---------- */
@keyframes float-y {
  0%, 100% { transform: translate3d(0, 0, 0); }
  50%      { transform: translate3d(0, -14px, 0); }
}
.float-slow { animation: float-y 7s var(--ease-in-out) infinite; }

/* ---------- Ripple ping (live indicator) ---------- */
.ping { position: relative; display: inline-flex; width: 9px; height: 9px; }
.ping::before, .ping::after {
  content: ""; position: absolute; inset: 0;
  border-radius: 50%; background: var(--ok);
}
.ping::before { animation: ping 1.9s cubic-bezier(0, 0, .2, 1) infinite; }
@keyframes ping { 75%, 100% { transform: scale(2.6); opacity: 0; } }

/* ==========================================================================
   Reduced motion — everything visible, nothing moves. Non-negotiable.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  .js [data-reveal],
  .js [data-reveal].is-in {
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
    clip-path: none !important;
    transition: none !important;
  }
  .split-word { transform: none !important; transition: none !important; }
  .parallax { transform: none !important; }
  .marquee__track { animation: none !important; }
  .float-slow { animation: none !important; }
  .ping::before { animation: none !important; }
  .pop-in { animation: none !important; }

  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}
