/* Motion for the landing site. Two primitives:
   1. Reveal: [data-reveal] fades and lifts in once, armed only when the head
      script has added `js-reveal` (so no-JS and reduced-motion see everything).
   2. Stage: a tall [data-stage] with a sticky 100vh .stage-pin; descendants
      with [data-seq] play their keyframes across the stage's scroll travel.
      Browsers with scroll-driven animations use a view timeline; others get
      a paused animation whose negative delay is driven by --p from stage.js.
   Base styles are always the FINAL state: with animation:none (reduced motion
   or no JS in an old browser) the finished picture renders. */

:root {
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
  --dur-press: 140ms;
  --dur-hover: 180ms;
  --dur-reveal: 360ms;
  --stagger: 50ms;
}

/* ---- Reveal ---- */
.js-reveal [data-reveal] { opacity: 0; transform: translateY(12px); will-change: opacity, transform; }
.js-reveal [data-reveal].is-in {
  opacity: 1; transform: none; will-change: auto;
  transition: opacity var(--dur-reveal) var(--ease-out), transform var(--dur-reveal) var(--ease-out);
  transition-delay: calc(var(--reveal-i, 0) * var(--stagger));
}

/* ---- Stage ---- */
[data-stage] { position: relative; height: var(--stage-h, 200vh); }
.stage-pin { position: sticky; top: 0; height: 100vh; height: 100svh; overflow: hidden; display: flex; align-items: center; }

/* Fallback: paused keyframes scrubbed by --p (0..1) set by stage.js. */
.js-stage [data-seq] {
  animation-duration: 1s;
  animation-timing-function: linear;
  animation-fill-mode: both;
  animation-play-state: paused;
  animation-delay: calc(var(--p, 0) * -1s);
}

/* Native scroll-driven animations: the stage is the view timeline; --r0/--r1
   are the cover offsets where the sticky pin engages and releases
   (100vh/(h+100vh) and h/(h+100vh) for a stage of height h). */
@supports (animation-timeline: scroll()) {
  [data-stage] { view-timeline: --stage block; }
  .js-stage [data-seq], .css-stage [data-seq] {
    animation-duration: auto;
    animation-timing-function: linear;
    animation-fill-mode: both;
    animation-play-state: running;
    animation-delay: 0s;
    animation-timeline: --stage;
    animation-range: cover var(--r0, 33.33%) cover var(--r1, 66.67%);
  }
}

@media (prefers-reduced-motion: reduce) {
  .js-reveal [data-reveal] { opacity: 1; transform: none; }
  [data-stage] { height: auto; }
  .stage-pin { position: static; height: auto; overflow: visible; }
  [data-seq] { animation: none !important; }
}

/* Pseudo-elements cannot carry [data-seq], so the invoice row highlights and
   the counter beats' accent bars need their own copies of the two stage rules
   above. Same values, same order. */
.js-stage .inv-row::after, .js-stage .beat[data-seq]::after { animation-duration: 1s; animation-timing-function: linear; animation-fill-mode: both; animation-play-state: paused; animation-delay: calc(var(--p, 0) * -1s); }
@supports (animation-timeline: scroll()) {
  /* fill-mode and timing-function repeated from the [data-seq] rule above:
     without `both` the rows keep their lit base style until the range opens,
     which shows the whole invoice already read at the top of the page. */
  .js-stage .inv-row::after, .css-stage .inv-row::after, .js-stage .beat[data-seq]::after, .css-stage .beat[data-seq]::after { animation-duration: auto; animation-timing-function: linear; animation-fill-mode: both; animation-play-state: running; animation-delay: 0s; animation-timeline: --stage; animation-range: cover var(--r0, 33.33%) cover var(--r1, 66.67%); }
}
@media (prefers-reduced-motion: reduce) { .inv-row::after, .beat::after { animation: none !important; } }

/* ---- Drift ----
   A scoped view timeline gives the browser frame a slow parallax as its
   section crosses the viewport. Native scroll-driven animations only (no JS
   scrubbing); the untransformed base position is the correct static picture,
   so browsers without them, and reduced motion, lose nothing. */
@supports (animation-timeline: scroll()) {
  [data-drift-scope] { view-timeline: --drift block; }
  .css-stage [data-drift] { animation: drift linear both; animation-timeline: --drift; animation-range: cover 0% cover 100%; }
}
@keyframes drift { from { transform: translateY(28px); } to { transform: translateY(-28px); } }
@media (prefers-reduced-motion: reduce) { [data-drift] { animation: none !important; } }

/* ---- Lift ----
   Like drift, but only across the scope's EXIT, so the resting picture (the
   hero at scroll 0) is untransformed and the two objects part as the hero
   leaves: the near one (far) travels 2.5x the distance of the far one. */
@supports (animation-timeline: scroll()) {
  [data-lift-scope] { view-timeline: --lift block; }
  .css-stage [data-lift] { animation: lift linear both; animation-timeline: --lift; animation-range: exit 0% exit 100%; }
  .css-stage [data-lift-far] { animation: lift-far linear both; animation-timeline: --lift; animation-range: exit 0% exit 100%; }
}
@keyframes lift { from { transform: translateY(0); } to { transform: translateY(-28px); } }
@keyframes lift-far { from { transform: translateY(0); } to { transform: translateY(-72px); } }
@media (prefers-reduced-motion: reduce) { [data-lift], [data-lift-far] { animation: none !important; } }
