/* ============================================================
 * passage.css — the Solar Crest passage between Hero Rising pages.
 *
 * Point → Solar Crest → the destination already inside → one continuous
 * flight into it → the real destination.
 *
 * What is inside the crest is the destination itself: the real page in a
 * hidden same-origin frame at this exact viewport (passage.js), never scaled,
 * moved or resampled. The crest-shaped aperture grows over it — a world behind
 * an opening, locked to its final coordinate system — while the crest's rim
 * and body travel with the aperture's edge. So the last frame of the flight and
 * the first frame of the live document are the same composition, and there is
 * no layer left over the arriving page to remove: the destination only holds
 * its own entrance choreography until it has settled (html.hrp-arrive).
 *
 * Every layer here is `pointer-events: none` — a decorative transition surface
 * never owns mouse, touch, wheel or keyboard input — and the passage never
 * writes a lock onto html or body.
 *
 * The crest is never redrawn or recolored: the canonical SVG is the crest, the
 * rim is that silhouette with its inner part cut away (same path, same
 * gradient), the aperture is that silhouette. Brand canon:
 * docs/HERO_RISING_LOGO_CANON.md. Stamped into every shared-chrome page by
 * scripts/gen-nav.mjs together with passage.js and the <head> handoff.
 * ============================================================ */

/* ---- the flight (on the page we leave) ---- */
/* The stage rests at zero opacity and is NEVER clipped away while it rests.
   That distinction matters: an IntersectionObserver inside the frame measures
   its target against the top-level viewport through every ancestor clip, so a
   frame clipped to nothing never fires one — its reveals would still be
   unrevealed when the flight ended on it. Opacity is not part of that
   calculation, so a transparent, unclipped, full-viewport frame is both
   invisible and fully alive: it reaches exactly the arrival state the real
   page will show. */
.hrp{position:fixed;inset:0;z-index:9000;pointer-events:none;overflow:hidden;opacity:0}
html.hrp-leave .hrp{opacity:1}
/* At rest the layer is also visibility:hidden. iOS Safari hit-tests the frame inside
   it for scroll gestures despite pointer-events:none: a sideways swipe over any
   horizontal strip under it (the 64 keys on the home page) moved nothing.
   visibility, like opacity, leaves the frame laid out and alive. */
html:not(.hrp-leave) .hrp{visibility:hidden}
/* THE OPENING is a mask on this layer, not a clip. A clip on the ancestor of the
   frame is what the browser uses to decide how much of that frame to keep
   painted, so the paint is redone as the opening grows and, on a destination
   whose first screen is expensive vector art, it arrives up to five frames late
   — and for those frames the page being left showed through the opening. A mask
   is applied to the composited layer: the destination is rastered once, whole,
   and the opening only decides what of it reaches the screen. The mask image is
   the canonical shield silhouette at 0.85 (see passage.js), pre-turned, and only
   its size changes during the flight. */
/* The opening's box is the crest's silhouette at one fixed width, centred on the
   viewport, and the mask fills it: the flight moves the box with a transform and
   never resizes the mask, so the mask is rastered once (passage.js, frameAt).
   The world is carried by .hrp-inner, which sits back at the viewport's origin
   and takes the inverse transform. Until the stage is prepared both boxes are
   the viewport itself. */
.hrp-portal{position:absolute;left:calc(50% - var(--hrp-pw,100%) / 2);top:calc(50% - var(--hrp-ph,100%) / 2);width:var(--hrp-pw,100%);height:var(--hrp-ph,100%);transform-origin:50% 50%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-position:center;mask-position:center;-webkit-mask-size:100% 100%;mask-size:100% 100%}
.hrp-inner{position:absolute;left:calc(var(--hrp-pw,100%) / 2 - var(--hrp-iw,100%) / 2);top:calc(var(--hrp-ph,100%) / 2 - var(--hrp-ih,100%) / 2);width:var(--hrp-iw,100%);height:var(--hrp-ih,100%);transform-origin:50% 50%}
/* THE GROUND behind the world, inside the same opening: the destination's own
   base colour (read off the page in the stage frame; a dark neutral until then).
   Paint follows the clip, so an area the aperture has only just reached can be a
   frame or two behind on a destination whose first screen is expensive to raster
   — and what showed through was the page being left. A solid colour is free to
   raster, so the opening always has the destination's own background under it,
   which is what a plain navigation to that page shows first. */
.hrp-ground{position:absolute;inset:0;background:var(--hrp-ground,#0b0a12)}
/* the world behind the opening: the destination, at this viewport, at 1:1 — no
   transform ever touches it, so it cannot soften, shift or recrop. It is as TALL
   AS THE PARENT'S 100vh, not as the fixed stage box: on a phone the fixed box
   follows the browser's toolbar (the small viewport while the toolbar shows,
   which it does at a tap on the header or the drawer), while vh — inside the
   frame, its own height; in the live document, the large viewport — does not.
   Sized to the box, the frame laid every vh of the first screen out against a
   shorter viewport than the landed page resolves, and the page re-settled as
   the crest ended. Top-anchored; the stage's overflow:hidden clips the rest. On
   a desktop the two heights are the same number. */
.hrp-world{position:absolute;inset:0 0 auto 0;width:100%;height:100vh;border:0;display:block;background:transparent;color-scheme:normal}
/* the rim and the whole crest travel with the edge of the opening */
.hrp-ring,.hrp-mark{position:absolute;left:50%;top:50%;display:block;object-fit:contain;transform-origin:50% 50%;opacity:0}
.hrp-ring{width:var(--hrp-rw,1400px);height:var(--hrp-rh,1494px);margin:calc(var(--hrp-rh,1494px) / -2) 0 0 calc(var(--hrp-rw,1400px) / -2)}
.hrp-mark{width:var(--hrp-mw,480px);height:var(--hrp-mh,512px);margin:calc(var(--hrp-mh,512px) / -2) 0 0 calc(var(--hrp-mw,480px) / -2)}

.hrp-portal,.hrp-inner,.hrp-ground,.hrp-world,.hrp-ring,.hrp-mark{pointer-events:none}
.hrp-ring,.hrp-mark{will-change:transform,opacity}
html.hrp-leave .hrp-portal,html.hrp-leave .hrp-inner{will-change:transform}

/* THE PAGE BEING LEFT STANDS STILL WHILE THE CREST FLIES — its ambient
   animations are paused and its frame callbacks held for the second the crest
   takes to cover it (on Earth they live inside SVG groups with large blur
   filters, and every frame of them re-filtered whole scene layers on the thread
   and GPU the flight needs). passage.js does it (standStill), on the animating
   elements only: a rule here matching every element of the page re-styled the
   whole document in the frame the crest was born. */

/* ---- arriving (in the destination document) ----
   Nothing covers this page. It is already the composition the flight ended on,
   so the only thing that could betray the swap is its own entrance
   choreography running to a different state while it settles: while the lock
   holds, reveals and entrance animations go straight to their final state, and
   ambient motion is paused rather than started mid-way. The lock is released by
   passage.js once fonts are active and the layout has stopped moving, and by
   the handoff's own bounded timer whatever happens. */
/* ANIMATIONS ARE PAUSED AT THEIR FIRST FRAME, NOT REMOVED. What the lock has to
   guarantee is that the picture it holds is the picture the page shows the
   moment the lock lifts. Removing animations by name showed each element at its
   static style; lifting the lock then started every ambient animation from its
   0% keyframe — and wherever the two differ the page changed in the release
   frame. Measured: the chimney smoke on Heart at opacity .7 under the lock and
   .55 the instant it lifted, eleven plumes on Pearl .6 → .55, the Fire glow
   1 → .8 — a small, simultaneous pop across the first screen, exactly at the
   moment the crest ends, on every page (41-48 dB between the live document's
   first frame and its release frame). Paused at time zero, an animation renders
   its first frame; released, it runs on from that same frame, so nothing can
   change at the release, and the stage frame — locked for good — shows that
   same first frame inside the crest. Entrance animations are the page's own
   contract under html.hrp-landed, which the stage frame now carries too: the
   only one in the site, the Library's `warm`, is switched off there on both
   sides. What must never come back is `animation-duration:0s`: a zero-length
   animation is created, fills, and shows its `from` state for one frame before
   resolving (measured on that same `warm` sheet: 47 levels lighter for a single
   frame at the swap, 35 dB against the frames either side). */
:root.hrp-arrive *,
:root.hrp-arrive *::before,
:root.hrp-arrive *::after{transition-duration:0s!important;transition-delay:0s!important;animation-play-state:paused!important}
/* Lifting the override is the one moment an un-promoted element could animate
   back to a pre-reveal state and then reveal itself again. For a moment longer,
   transitions stay off — so anything promotion did not cover changes instantly
   and invisibly instead of fading. Animations are deliberately not suppressed
   here: this is where the page's ambient life is allowed to begin. */
:root.hrp-settled *,
:root.hrp-settled *::before,
:root.hrp-settled *::after{transition-duration:0s!important;transition-delay:0s!important}
/* And the entrance must be final on the FIRST painted frame, not one frame
   after a script has observed it: every page here hides its first screen with
   `.reveal` (and the portal's `.anchor`) and switches it on from an
   IntersectionObserver, which cannot run before that first paint — so an
   arriving page would show its scenery for a frame or two with the words
   missing, and then the words would appear. This is the arrival contract of the
   shared chrome: a page that hides content until script reveals it takes part
   in the passage through these two classes. */
:root.hrp-arrive .reveal,
:root.hrp-arrive .anchor{opacity:1!important;transform:none!important;filter:none!important}
