/**
 * header-collapse.css — P3.20: sticky collapsing header + reading progress.
 *
 * Spec: docs/mobile-first-reference.md §9.1 (header) and §10.3 (progress bar).
 *
 * ── THE ONE DECISION EVERYTHING ELSE FOLLOWS ───────────────────────────────
 * THE HEADER'S LAYOUT HEIGHT NEVER CHANGES. It collapses by `transform` only.
 *
 * Four subsystems are pinned to it being exactly 95px — the `--pu-header-h`
 * token, the sticky article TOC offset, the legal/privacy TOC offset, and
 * `scroll-padding-top` (P1.10) — plus `HEIGHT_CONTRACT` in
 * `scripts/check_computed_styles.py`, which is one of the few places a computed
 * `height` is compared at all. Measured live 2026-09-05: 95px at 390, 768 and
 * 1280, `scroll-padding-top` 103px at all three.
 *
 * But the deciding reason is not the four dependants — it is layout shift.
 * `.pu-header` is `position: sticky`, so it KEEPS its slot in normal flow at the
 * top of the document even while it is stuck. Shrinking its height mid-scroll
 * would shrink that slot, and everything below would jump up by the difference
 * while the reader is reading. CLS is currently a verified 0.000 on all five
 * measured pages, and that was checked rather than assumed precisely because a
 * row of perfect zeros is what a dead observer also produces. A height-animating
 * header would spend that result for nothing.
 *
 * `transform` is composited and takes no part in layout, so the slot, the token,
 * the four offsets and the height contract are all untouched by construction —
 * not by our remembering to keep them in sync.
 *
 * ── WHY JS AND NOT `animation-timeline: scroll()` ──────────────────────────
 * §9.1 asks for `animation-timeline: scroll()` where supported, and it IS
 * supported here (verified live, along with `animation-range`). It is still the
 * wrong tool for this behaviour: a scroll timeline maps progress to scroll
 * POSITION, and hide-on-down / reveal-on-up is a function of scroll DIRECTION,
 * which a scroll timeline cannot express. §9.1's own fallback clause is the
 * sanctioned path, and the JS is ~2 KB.
 *
 * The reading-progress bar is the opposite case — pure position — and it is
 * driven by a custom property rather than a scroll timeline only so that both
 * halves share one rAF-throttled read and cannot disagree about the scroll
 * position they are describing.
 *
 * ── SCOPE ──────────────────────────────────────────────────────────────────
 * Below 1024px only. §9.1 says ≥1024 is "Unchanged from current desktop", and
 * desktop is the protected surface P0–P3 spent months keeping byte-identical.
 * `.pu-m1` gates it as with every sheet in this layer.
 */

@media (max-width: 1023.98px) {

    /* The collapse itself. `will-change` is deliberately NOT set: it would
       promote the header to its own layer on every page for the whole session,
       and the transform only runs while scrolling. */
    .pu-m1 .pu-header {
        transition: transform 220ms ease;
    }

    /* Hidden state. -100% of the header's own box, so it clears whatever the
       header's height happens to be — this does not hard-code 95px, and it
       survives the §9.1 restructure to a 56–64px phone header. */
    .pu-m1 .pu-header[data-pu-header="hidden"] {
        transform: translateY(-100%);
    }

    /* ── Reading progress ──────────────────────────────────────────────────
       `scaleX` on a composited transform, driven by a custom property the JS
       writes once per frame. NOT `style.width`, which would lay out and paint
       the bar on every scroll frame — the reason the plan names the property
       explicitly.

       Fixed rather than sticky so it stays put while the header is away, and
       one z-index above the header (1000) so it is never covered. */
    .pu-m1 .pu-read-progress {
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        height: 3px;
        z-index: 1001;
        pointer-events: none;
        background: transparent;
    }

    /*
     * THE FILL FADES IN ACROSS 2%–6%, AND THE REASON IS WORTH KEEPING.
     *
     * The measurements said this was finished: progress 0 at the top, exactly 1
     * at the bottom, 0.0294 at y=2000. All correct, and all blind to how it
     * LOOKS. A screenshot of the same state showed the flaw immediately — an
     * 8px navy stub wedged in the top-left corner, reading as a rendering
     * artifact rather than as progress.
     *
     * It is not an edge case on this corpus. The profile it was measured on is
     * ~68,000px of scroll, so 2% is over a screen of reading; long accountability
     * profiles are the common case here, which makes the illegible state the
     * common one too.
     *
     * `opacity`, NOT a width floor. A floor would draw ~24px while the reader is
     * 8px in — overstating position in a bar whose only job is to report it.
     * Ramping opacity keeps `scaleX` truthful at every instant and simply does
     * not draw the fill while it would be too small to read: 0 at 2%, 1 at 6%.
     *
     * The ramp is arithmetic on the same custom property, not a transition, so
     * it tracks the reader's own scrolling exactly like the scaleX beside it and
     * carries no vestibular risk — which is why it is not gated on
     * `prefers-reduced-motion`. A step change at 2% would have been the
     * alternative, and it pops.
     */
    .pu-m1 .pu-read-progress::before {
        content: "";
        display: block;
        height: 100%;
        background: var(--pu-navy, #14346b);
        transform: scaleX(var(--pu-read-progress, 0));
        transform-origin: 0 50%;
        opacity: clamp(0, calc((var(--pu-read-progress, 0) - 0.02) * 25), 1);
    }

    /* ── Reduced motion ───────────────────────────────────────────────────
       §9.1: "Snap rather than animate under prefers-reduced-motion." The
       behaviour is kept — a reader who wants the space still gets it — and only
       the interpolation is removed. Removing the behaviour instead would be a
       different, worse reading of the preference.

       The progress bar's own scaleX is not a transition; it tracks scroll, which
       is the reader's own motion, and it carries no vestibular risk. */
    @media (prefers-reduced-motion: reduce) {
        .pu-m1 .pu-header {
            transition: none;
        }
    }
}

/* At ≥1024px the bar is never injected (the script bails) and the header rules
   above do not apply, so desktop renders exactly as it did. Stated here because
   "it happens not to match" and "it cannot match" are different guarantees, and
   only the second one survives someone editing the media query. */
