/* ═══════════════════════════════════════════════════════════════════════════
   FEDERAL INDEX — SCROLL MOTION LAYER
   ═══════════════════════════════════════════════════════════════════════════

   Requires tokens.css (uses --ease, --dur-*, --stagger).

   WHY THIS FILE EXISTS
   The page already reveals sections with an IntersectionObserver that adds
   `.in` and a hand-written `transition-delay` on every element. That works,
   but it costs a main-thread callback per element and the delays are authored
   by hand in the markup — which is why the hero carries literal
   style="transition-delay:.08s" attributes.

   CSS scroll-driven animations (`animation-timeline: view()`) run entirely on
   the compositor, need no JS, and tie progress to the element's actual
   position in the scrollport rather than firing once on a threshold. Baseline
   across Chromium and Firefox as of 2026; Safari still lacks it, which is why
   every rule below sits inside @supports and the JS `.in` path stays as the
   fallback. Nothing here is load-bearing — if the feature is absent the page
   degrades to exactly what it does today.

   THE THREE MOTION JOBS — every animation here does one, or it was cut:
     orientation · where did this come from
     feedback    · did my action register
     atmosphere  · brand mood, under 8% visual weight
   ═══════════════════════════════════════════════════════════════════════════ */

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {

    /* ── 1 · REVEAL (orientation) ────────────────────────────────────────
       Replaces the JS threshold with a real timeline. The element animates
       across the bottom 30% of the scrollport and is fully settled well
       before it reaches reading position — content must never still be
       moving when the eye arrives.

       `both` keeps the end state after the range, so nothing snaps back on
       scroll-up. The JS still adds `.in`; that just becomes redundant here. */
    [data-rise] {
      animation: fi-rise linear both;
      animation-timeline: view();
      /* Range ends at `entry 100%` — the moment the element has FULLY entered
         the viewport — not at `cover 30%`. This matters more than it looks:
         with a cover-based range, anything already on screen at load sits
         partway through its own animation forever, holding partial opacity.
         axe-core measured exactly that on the hero proof strip: --ink-3
         compositing to #7a8496 for 3.77:1 against a 4.5:1 requirement.
         Ending at `entry 100%` guarantees anything fully visible is at 100%. */
      animation-range: entry 0% entry 100%;
      /* neutralise the JS transition so the two systems cannot fight */
      transition: none;
    }

    /* The hero is above the fold at load — there is no scroll to drive it, and
       a headline that waits for a scroll event is a broken page. It keeps the
       original CSS-transition reveal driven by the JS `.in` class. */
    .hero [data-rise] { animation: none; }

    @keyframes fi-rise {
      from { opacity: 0; transform: translateY(14px); }
      to   { opacity: 1; transform: none; }
    }

    /* Hand-authored transition-delay attributes in the markup are inert once
       the timeline drives the animation — stagger comes from position now,
       not from a number typed into the HTML. */
    [data-rise][style*="transition-delay"] { animation-delay: 0s; }

    /* ── 2 · STAGGER (orientation) ───────────────────────────────────────
       Siblings inside a revealed group offset by --stagger (60ms, inside the
       40–80ms band). Capped at six children: past that it reads as a slow
       list rather than a group arriving. */
    [data-stagger] > * {
      animation: fi-rise linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 100%;
    }
    [data-stagger] > *:nth-child(1) { animation-delay: calc(var(--stagger) * 0); }
    [data-stagger] > *:nth-child(2) { animation-delay: calc(var(--stagger) * 1); }
    [data-stagger] > *:nth-child(3) { animation-delay: calc(var(--stagger) * 2); }
    [data-stagger] > *:nth-child(4) { animation-delay: calc(var(--stagger) * 3); }
    [data-stagger] > *:nth-child(5) { animation-delay: calc(var(--stagger) * 4); }
    [data-stagger] > *:nth-child(n+6) { animation-delay: calc(var(--stagger) * 5); }

    /* ── 3 · SCROLL PROGRESS RAIL (feedback) ─────────────────────────────
       A 2px navy→red rail across the top, scaled by document progress. The
       gradient direction is the argument the whole site makes: government
       (navy) on the left, your desk (red) on the right.
       Add <div class="fi-progress"></div> as the first child of <body>. */
    .fi-progress {
      position: fixed; inset: 0 0 auto 0; height: 2px; z-index: 200;
      transform-origin: left center; transform: scaleX(0);
      background: linear-gradient(90deg, var(--navy), var(--red));
      animation: fi-progress linear both;
      animation-timeline: scroll(root block);
      pointer-events: none;
    }
    @keyframes fi-progress { to { transform: scaleX(1); } }

    /* ── 4 · SECTION LIFT (orientation) ──────────────────────────────────
       Panels rise and settle as they enter — heavier than text, so slower
       and travelling further, per weight mapping. Opt in with data-lift. */
    [data-lift] {
      animation: fi-lift linear both;
      animation-timeline: view();
      animation-range: entry 0% cover 26%;
    }
    @keyframes fi-lift {
      from { opacity: 0; transform: translateY(28px) scale(.985); }
      to   { opacity: 1; transform: none; }
    }

    /* ── 5 · AMBIENT PARALLAX (atmosphere) ───────────────────────────────
       Background-only drift across the full pass of the section. Deliberately
       tiny: 34px over an entire viewport of scrolling is felt, not seen.
       Never applied to anything carrying text. */
    [data-parallax] {
      animation: fi-parallax linear both;
      animation-timeline: view();
      animation-range: cover 0% cover 100%;
      will-change: transform;
    }
    @keyframes fi-parallax {
      from { transform: translateY(-17px); }
      to   { transform: translateY(17px); }
    }

    /* ── 6 · (REMOVED) STAT STRIP ANIMATION ──────────────────────────────
       A `.proof div` view-timeline animation was tried here and cut. Two
       reasons, both found by measurement rather than taste:

       1. REDUNDANT — the markup already carries `data-rise` on `.proof`, so
          the strip was being animated twice.
       2. IT FAILED ACCESSIBILITY — the strip sits in the hero, at the very
          edge of the fold. With `both` fill and an `entry 10%` range it held
          partial opacity at rest, and axe-core measured the labels compositing
          to #969eac and #dcdee3 on white — 2.69:1 and 1.34:1, far below the
          4.5:1 floor. An animation that renders load-bearing numbers
          unreadable is not a polish win.

       The lesson generalises: never put a view-timeline reveal on content that
       is above the fold at load. There is no scroll to drive it. */
  }
}

/* ── Reduced motion ───────────────────────────────────────────────────────
   Outside the @supports block on purpose: this must hold whether or not
   scroll-driven animations exist. Everything lands in its final state with no
   travel, and the progress rail is filled rather than animated so it still
   reads as chrome instead of vanishing. */
@media (prefers-reduced-motion: reduce) {
  [data-rise], [data-stagger] > *, [data-lift], [data-parallax], .proof div {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
  .fi-progress { animation: none !important; transform: scaleX(1) !important; opacity: .5; }
}
