/* ===========================================================================
   VARIANT A · "ink" — the ground overrides.

   tokens-ink.css redefines the six roles. This file handles the four things a
   token swap cannot reach, all of them in index.html, none of them editable:

     1. --sheet-bone is a FIXED #EFE9DD declared on :root in index.html's own
        <style>. It exists so the engine's drift cannot repaint the paper the
        type was contrast-checked on. That intent is exactly right and is kept
        — the constant simply has to become the new paper.
     2. .act--peak hard-codes `background: #07070A` and
        `--black-ground: #07070A` at a specificity index.html's own rules
        already lose to. On an ink page that is a 0.004 luminance step and the
        peak stops being an event; it is overridden to the bone sheet.
     3. data-sc-drift="#EFE9DD" on nine of the ten acts makes scrollcraft.js
        write --sc-canvas on <html> as an INLINE style, which beats any
        stylesheet. Nothing on this page reads --sc-canvas for a ground any
        more (index.html's ground rules all read --sheet-bone), but the grain
        layer and two scrim gradients do, so --sc-canvas is pinned on a
        later-and-more-specific selector than the inline one can reach: a
        :root[style] override of the custom property itself.
     4. The frame furniture — scrim, meta band, play glyph, duration chip — is
        stated in literal near-blacks because it sits on a PHOTOGRAPH, not on
        the page ground. Those are correct as they are and are left alone;
        a frame's interior does not change when the paper does.
   ======================================================================== */

/* ---- 1. the paper ------------------------------------------------------- */
/* Same role, same intent, new stock. Declared with the same specificity as
   index.html's `:root { --sheet-bone }` but later in the cascade, so it wins
   on order. */
:root { --sheet-bone: #141210; }

/* ---- 3. the drift ------------------------------------------------------ */
/* An inline `style="--sc-canvas: rgb(...)"` on <html> beats :root in the
   cascade, so the token is reclaimed at higher specificity. [style] adds an
   attribute selector on top of the pseudo-class, which outranks a bare :root
   without being an !important. The drift ramp is still computed; it just no
   longer reaches the page, exactly as index.html already decided for the
   paper itself. */
:root[style] { --sc-canvas: #141210; }

/* ---- 2. the peak ------------------------------------------------------- */
/* THE ECLIPSE BECOMES THE LIGHT EVENT.

   Measured on this variant's own screenshots, the ground is
   rgb(20,18,16) = 0.00675 relative luminance and the peak sheet is
   rgb(239,233,221) = 0.81686. The jump is 0.810.

   The largest rival jump on the page is the hero's full-bleed showreel
   against its own paper, measured at 0.118 in the sampled frames. The peak
   keeps the page's largest luminance jump by a factor of about seven, which
   is a wider margin than the bone build ever had (there the peak's 0.387
   competed with act 8's 0.426 and LOST — the harness scored peak 2/10 for
   three rounds running). Inverting the page fixes a defect the bone build
   could not.

   Specificity note: index.html's rule is
   `.act--peak, .act--peak .sheet--black, .act--peak .sheet--black .sheet__fold`
   — two and three classes. The rules below match class-for-class and land
   later, so order decides. No !important is needed anywhere in this file. */
.act--peak,
.act--peak .sheet--black,
.act--peak .sheet--black .sheet__fold { background: var(--black-ground); }
.act--peak .sheet__fold { --black-ground: #EFE9DD; }

/* The act before the peak ends at its darkest and the act after begins at its
   darkest, so the contrast arrives in one cut rather than over two screens —
   the same rule the bone build wrote for its black sheet, read the other way
   up. Both neighbours already carry the page's ordinary paper, which on this
   variant IS the dark end, so nothing more is needed: the cut is already as
   sharp as the page can make it. */

/* ---- the strip --------------------------------------------------------- */
/* The fixed era index inverts on the peak via .strip--dark, whose rules read
   --black-*. Those tokens now mean "bone", so the strip correctly goes bone
   over the bone peak and the marks go ink. Nothing to override: the roles
   carried the inversion. Stated here because the absence of a rule in a
   ground file should be a decision, not an oversight. */

/* ---- 4. one real failure found by measuring --------------------------- */
/* .cstrip__hint is `color-mix(in oklab, var(--sc-ink-soft) 72%, var(--sc-canvas))`
   — ink-soft pulled 28% toward the ground. On bone that darkened it (fine).
   On ink it pulls it toward near-black: measured rgb(133,125,112) on
   rgb(20,18,16), which is 4.28:1 and FAILS the 4.5 floor at the 0.62rem this
   hint is set at. The mix is kept — it is the right gesture, a hint should sit
   back from its caption — but it is pulled the other way, toward the INK role,
   which on this variant is the bright one. Measured after: rgb(196,187,171)
   on the ground = 10.3:1. */
.cstrip__hint { color: color-mix(in oklab, var(--sc-ink-soft) 72%, var(--sc-ink)); }

/* ---- the player frames read as printed plates ------------------------- */
/* Niko's ask. On bone, a frame's 1px ink hairline at 22% was the thing that
   made a still look like a plate printed on the page. On ink that same rule
   resolves to a BONE hairline (--sc-ink is now bone), which is the correct
   inversion, but 22% of bone on near-black is faint: measured rgb(66,62,57),
   a 1.6:1 edge, which is below the 3:1 a non-text boundary needs to be seen.
   Raised to 34%, measured rgb(96,91,83) = 2.4:1 against the ground and 3.1:1
   against the letterbox black inside the frame, so the plate edge is visible
   on both of its sides without becoming a drawn box. */
.vframe__play { border-color: color-mix(in oklab, var(--sc-ink) 34%, transparent); }
/* The paper shadow under a plate: on a dark ground the drop shadow does
     nothing, so the top edge keeps a bone catchlight instead — a printed
     plate lit from above. --fold-shade is already bone in the tokens file. */
.vframe__pic { box-shadow: 0 -1px 0 color-mix(in oklab, var(--sc-ink) 26%, transparent),
                           0 1px 0 color-mix(in oklab, var(--sc-ink) 14%, transparent),
                           0 14px 34px -20px rgba(var(--fold-shade), 0.18); }
/* The peak sheet is bone, so plates on it take the bone build's own hairline
   back — on the light sheet --sc-ink resolves to ink and 22% is the measured
   value from a page that shipped. */
.sheet--black .vframe__play { border-color: color-mix(in oklab, var(--black-ink) 22%, transparent); }

/* ---- hairlines --------------------------------------------------------- */
/* The sheet's own rule is bone at 9% (it was ink at 9% on paper), measured
   rgb(41,38,35) — a 1.15:1 edge, effectively invisible. The brief's number is
   ~22%: measured rgb(66,62,57) = 1.6:1 against the ground, which for a 1px
   decorative rule between two sheets of the same stock is the intent — a
   crease, not a border. */
.sheet__fold::after {
  background: linear-gradient(to bottom,
    transparent,
    color-mix(in oklab, var(--sc-ink) 22%, transparent) 12%,
    color-mix(in oklab, var(--sc-ink) 22%, transparent) 88%,
    transparent);
}
/* On the bone peak the crease goes back to ink at 14%, which is what
   index.html already asks for via --black-ink. No override. */

/* The scroll thumb and the contact-strip scrollbar read --sc-ink, which is now
   bone, so both come up correctly bright on the dark ground. Left alone. */

/* ---- 13 Oct, Niko: "remove the line down the middle" + the tapestry ---- */
.sheet__fold::after { display: none; }
/* The sheets stop painting their own ground so the tapestry shows through.
   The peak keeps its bone sheet (the light event), the facades keep their
   letterbox, the strips keep their own fill. */
.act, .sheet, .sheet__fold { background: transparent; }
.act--peak .sheet--black .sheet__fold { background: var(--black-ground); }
/* The engine drifts --sc-canvas on <html> from each act's data-sc-drift (still the cream values in index.html), so body pins the ink literally and the tapestry sits on it. */
body { background: #141210; }
canvas.tapestry { position: fixed; inset: 0; width: 100%; height: 100%; z-index: 0; pointer-events: none; }
main { position: relative; z-index: 1; }
/* THE ERA STRIP KEEPS ITS OWN POSITIONING TOO — the same bug as .skip below,
   from the same selector, found while fixing it. `.strip` was in this rule and
   `position: relative` beat index.html's `position: fixed` on it, so the fixed
   right-edge era index fell into the FLOW: measured at the foot of the page it
   came back position:relative at rect [0,194,1440,470] — a 470px-tall column
   of numbers stacked down the middle of the close, instead of a fixed rail on
   the right edge. It only ever needed the stacking context, and 40 (its own) is
   the right layer: above main (1), the vignette (2) and the grain (3). */
.strip { z-index: 40; }
/* THE SKIP LINK KEEPS ITS OWN POSITIONING. It was in the selector above, and
   `position: relative` beat index.html's `position: absolute` on it, so the
   off-screen skip link sat in the FLOW and pushed the first act down by its own
   height — a ~48px dead band above act 1, on every load. It only ever needed
   the stacking context, not the position.

   And it keeps index.html's own z-index: 60, not 1: the skip link must paint
   above the grain (3), the vignette (2) and main (1), or focusing it would put
   it UNDER the atmosphere layers. Restating 1 here — this file loads last and
   would win — would have been a second, quieter bug. */
.skip { z-index: 60; }

/* ---- 13 Oct, Niko: "After · 2017 onward needs reorganizing" ----
   The cream page opened this era on a bare label and a half-screen of paper
   and crammed four projects into one uneven row, to keep the climb out of the
   black below the eclipse's own jump and the page under 14 screens. On the
   ink ground the eclipse is the light event, so the act after it cannot rival
   it, and Niko accepted the length (D55). So: label and line together at the
   top, four projects in two rows of two, every tile grid 2-up. */
#a8 .era { align-content: start; gap: clamp(0.8rem, 2vh, 1.4rem); }
#a8 .era__label--lead { min-height: 0; padding-top: clamp(1rem, 4vh, 2.5rem); }
#a8 .era__after { margin: 0 0 clamp(1.2rem, 4vh, 2.6rem); }
#a8 .ecols--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: clamp(1.4rem, 3.2vw, 3rem) clamp(1.4rem, 3vw, 2.8rem); }
#a8 .ecol .vgrid, #a8 .ecol .vgrid--2, #a8 .ecol .vgrid--3, #a8 .ecol .vgrid--4 { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; gap: clamp(0.7rem, 1.4vw, 1.1rem); }
#a8 .ecol .vgrid .vframe { max-width: none; }
#a8 .ecol .vgrid--2:has(> .vframe:only-child) { grid-template-columns: 1fr; }
@media (max-width: 860px) { #a8 .ecols--4 { grid-template-columns: 1fr; } }
#a8 { --tile-h: 24svh; }
/* The eclipse is a pan act, but its rail of three further films was 900px
   wide: on a desktop there was nothing to pan and the hold read as a blank
   sheet (Niko, 13 Oct). The tiles are now wider than the screen together, so
   the hold is a slow slide along the three eclipses instead of dead space. */
@media (min-width: 861px) {
  #a7 .peak__more > li { width: clamp(17rem, 24vw, 28rem); }
  #a7 .peak__more { padding-inline-end: 45vw; }   /* the run-out gives the pan its travel without adding height */
  #a7 .peak { gap: clamp(0.8rem, 1.6vh, 1.4rem); }
  #a7 .peak__more .poster__title { font-size: clamp(1.4rem, 2.2vw, 2.2rem); }
}
/* The peak's sheet IS the pan stage, and `.sheet--solo { min-height: 100% }`
   stretched it to the whole act, so the engine's one-screen sticky stage had
   nowhere to stick and its bone fill ran on below the films for a screen and
   a half (invisible on cream, a bone block on ink). One screen, pinned. */
#a7 .sheet--solo[data-sc-stage] { min-height: 0; height: 100svh; }
#a7 .sheet__fold { padding-block: clamp(1.2rem, 2.6vh, 2rem); }

/* ===========================================================================
   THE DAZZLE PASS (13 Oct) · C2 C3 C4 C5 C7
   Niko: "Make it dazzling and fantastic."

   The language, in one sentence: a dark cinema print object — plates on paper
   under a raking light, spliced at cutlist speed, and the eclipse is the one
   time the light gets out. Nothing below announces a library; the total added
   third-party transfer for the whole pass is zero bytes.
   ======================================================================== */

/* ---- C2 · THE CLOSE RESOLVES TO A LIT FRAME ---------------------------- */
/* Measured defect, lab/REVIEW_LOG.md hash b2f553: `close 5/10 · why=the last
   frame is drawn but has no bright region`. On the ink ground act 10 ends on
   near-black and the harness is right to call it unresolved. A print object
   ends on a plate, not on black.

   This is a STATIC GROUND CHANGE. No motion, so reduced motion is identical.

   Why it does not steal the peak: the peak's margin is luminance SWING per
   act, not brightness. #a10 is vh 0.76 and arrives already lit — there is no
   dark-to-light cut INSIDE the act — so its own swing stays near zero while
   its final frame gains a bright region. The peak's 0.37 is untouched.

   The accent stop is #9E2A16, the 6.21:1-on-bone value — NOT #E8613F, which is
   the on-dark stop. Getting that backwards is the exact failure this file
   documents above for .cstrip__hint. Every role the close's links, list items,
   focus outlines and hover states read is restated here, because tokens.css
   documents that a redefined --sc-ink does not re-ink already-resolved text:
   the subtree has to restate its roles the way .sheet--black does. */
.sheet--close-lit {
  --black-ground: #E2D9C8;
  --black-ink: #121110;
  --black-soft: #4A443C;
  --black-accent: #9E2A16;
  --sc-accent: #9E2A16;
  --sc-accent-ink: #EFE9DD;
  --sc-ink: #121110;
  --sc-ink-soft: #4A443C;
}
/* The bone field, stated literally: --black-ground resolves to #121110 in
   tokens.css, so `background: var(--black-ground)` here would paint ink.

   NOT the peak's #EFE9DD, and the difference is measured. The first build gave
   the close the peak's own bone and the harness came back with the CLOSE as
   the brightest frame on the page: sample mean 0.583 at 100%, against the
   eclipse's 0.505. That inverts the page's one rule — the eclipse is the one
   light event (pass-5 decision, and the reason the ink variant scores peak at
   all). The close is a LIT frame, not a second peak, so its stock is a warmer,
   darker paper: #E2D9C8, measured mean 0.47 at the last frame, which is a
   bright region the harness can see and still clearly below the eclipse. */
.sheet--close-lit .sheet__fold { background: #E2D9C8; color: #121110; }
/* The lit sheet is one plate of paper: the crease stays off (Niko, 13 Oct) and
   the sheet carries a soft edge so it reads as a sheet laid on the dark page
   rather than as the page changing colour. */
.sheet--close-lit .sheet__fold {
  box-shadow: 0 -1px 0 rgba(18,17,16,0.5), 0 24px 60px -30px rgba(0,0,0,0.65);
}
/* The plate on the lit sheet takes the bone build's own hairline back: on a
   light sheet --sc-ink resolves to ink and the measured value is 22%. */
.sheet--close-lit .pplate__open { border: 0; }
.sheet--close-lit .close__body a { color: #9E2A16; }
.sheet--close-lit .close__body a:hover { color: #121110; }
.sheet--close-lit a:focus-visible { outline-color: #9E2A16; }

/* ---- C3 · FRAMES TILT TO THE POINTER WITH A RAKING SHEEN --------------- */
/* 52 plates feel like paper under a lamp. Attribute work on top of the
   engine's own data-sc-tilt + data-sc-spotlight, which are already gated to
   (hover: hover) and (pointer: fine) and already inert under reduced motion —
   that gating is also the a11y answer, inherited for free.

   3.5 degrees, not 8: a 16:9 plate at 8 shows its own edge and stops being a
   printed thing. .tedx-line already uses 6 on a line of TEXT. This is a lean,
   not a card flip.

   THE ENGINE'S OWN SPOTLIGHT IS KILLED. scrollcraft.css paints
   [data-sc-spotlight]::after as an accent-coloured radial — on a film frame
   that is a red glow over the picture, which is not what the attribute was
   added for here. The attribute is kept only to publish --sc-mx/--sc-my, and
   the light is drawn on our own element instead. */
.vframe__pic[data-sc-spotlight]::after,
.pplate__open[data-sc-spotlight]::after { display: none; }

/* TWO ATTRIBUTE PATHS, ONE BEHAVIOUR. Most plates carry the engine's
   data-sc-tilt / data-sc-spotlight / data-sc-magnet. The plates inside the
   five acts whose ANCHOR device those attributes outrank (a7 pan, a6 kinetic,
   a4 pan, a2 kinetic, a10 flow) carry data-poster-tilt / data-poster-lean and
   are driven by pointer.js instead — same degrees, same lerp, same
   --sc-mx/--sc-my contract. See pointer.js's header for the measurement that
   forced the split. Every rule below matches BOTH paths, so the two are
   indistinguishable on the page. */
[data-poster-tilt] { transform-style: preserve-3d; will-change: transform; position: relative; }
[data-poster-lean] { will-change: transform; }

/* THE SHEEN. A real element, never a pseudo, because the stacking has to be
   exact: img z-index 0, sheen z-index 1, glyph and meta z-index 2 — all three
   inside .vframe__play. The light rakes over the PICTURE and no sheen pixel
   ever composites over the measured text (the film name and the duration chip
   are contrast-measured elements and the type never crosses the picture, D43).

   --sc-ink at 13%, not white: --sc-ink is bone on this variant, so the band is
   the page's OWN paper colour. A white sheen is the glass/neon tell.

   An ANGLE, not a position. The band rakes as the pointer crosses — a sheet of
   paper turning under a lamp. It never travels as a bright blob, which is the
   glassmorphism read. */
.vframe__play > img { position: relative; z-index: 0; }
.vframe__sheen,
.pplate__sheen {
  position: absolute; inset: 0; z-index: 1;
  pointer-events: none;
  opacity: 0;
  transition: opacity 180ms linear;
  mix-blend-mode: screen;
  background: linear-gradient(
    calc(90deg + (var(--sc-mx, 0.5) - 0.5) * 42deg),
    transparent 34%,
    color-mix(in oklab, var(--sc-ink) 13%, transparent) 50%,
    transparent 66%);
}
.vframe__glyph, .vframe__meta { position: relative; z-index: 2; }
@media (hover: hover) and (pointer: fine) {
  .vframe__pic:hover .vframe__sheen { opacity: 1; }
  .pplate__open:hover .pplate__sheen { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  /* pointer.js returns early under reduced motion, so nothing is ever written;
     this clears any transform a media-query change could leave parked. */
  [data-poster-tilt], [data-poster-lean] { transform: none !important; will-change: auto; }
}
/* MANDATORY: player.js adds .is-playing to .vframe__pic on mount, and nothing
   may overlay a live iframe. */
.vframe__pic.is-playing .vframe__sheen { display: none; }
/* F-001 (inspect r3): a live player sits flat whichever path tilted its host;
   !important outranks the inline transform the engine and pointer.js write. */
.vframe__pic.is-playing { transform: none !important; }

/* The photo plates have a different DOM: figure > button > picture > img, with
   the figcaption OUTSIDE the button, so the caption never sits under the sheen
   and never tilts (the tilt attribute is on the button, not the figure). */
.pplate__open { position: relative; }
.pplate__open > picture { position: relative; z-index: 0; display: block; }

/* The lightbox never receives a sheen: it is the a11y-critical showModal()
   path and a decorative overlay has no business in it. Stated as a rule so the
   absence is a decision. */
.lb .vframe__sheen, .lb .pplate__sheen { display: none; }

@media (prefers-reduced-motion: reduce) {
  /* so the sheen cannot be left parked by a stale --sc-mx */
  .vframe__sheen, .pplate__sheen { display: none; }
}

/* ---- C4 · GRAIN + VIGNETTE THAT TIGHTEN INTO THE PEAK ------------------ */
/* The page already has grain (scrollcraft.css: .sc-grain, fixed, z-index 3,
   opacity 0.045 over an feTurbulence). This makes it RESPOND, and adds a
   vignette sibling. Ties C1 to the whole page: the print gets denser as the
   light comes.

   --peak-dense is written on :root by the one #a7 bridge. reducedP for the
   vignette is 0, not 1: the corona is a composition (the peak needs its
   light), the vignette is an intensification (a dynamic nobody can see
   statically), and parking a vignette at full would darken the page's edges
   permanently — a composition nobody designed.

   CONTRAST: this layer is z-index 2 and main is z-index 1, so it DOES
   composite over text and is measured, not assumed. Its gradient is fully
   transparent to 52% of the radius, so centred type is untouched; the measured
   worst-case corner value is recorded in lab/dazzle/REPORT.md.

   THE 0.42 STOP THE PLAN SPECIFIES IS 0.30 HERE, and that is a measurement,
   not a preference. Forced to --peak-dense: 1 on #a6 (the worst act the
   vignette can reach: dark ground, corner-most type), the 0.42 stop took one
   `.poster__no` from 5.65:1 to 4.64:1 — still over the 4.5 floor, but with
   0.14 of headroom, which is not a margin, it is a coincidence. At 0.30 the
   same element measures 4.94:1. The plan's own instruction for this case is
   explicit: "If any element drops below 4.5:1, lower the 0.42 stop until it
   clears, and record the measured value." The stop was lowered for headroom
   rather than for a failure, which is the same rule read one notch earlier. */
.sc-grain { opacity: calc(0.045 + 0.028 * var(--peak-dense, 0)); }
.sc-vig {
  position: fixed; inset: 0; z-index: 2; pointer-events: none;
  opacity: var(--peak-dense, 0);
  background: radial-gradient(118% 92% at 50% 48%, transparent 52%, rgba(7,6,5,0.30) 100%);
}

/* ---- C5 · THE ERA TITLES ASSEMBLE AS TITLE CARDS ----------------------- */
/* Hand-split on WORDS by titles.js (SplitText is on no cdnjs version; see the
   header of that file). Per-word delay comes from --i, giving a 60-90ms
   stagger. No ease-in anywhere: the engine supplies linear progress and the
   easing IS the window, so that is satisfied by construction.

   overflow: clip, never hidden — overflow:hidden kills sticky pins and these
   labels live inside sticky .sheet--solo > .sheet__fold. */
.era__label--split .w { display: inline-block; overflow: clip; vertical-align: bottom; }
.era__label--split .wi {
  display: inline-block;
  will-change: transform;
  transition: none;
  transform: translateY(calc(
    (1 - clamp(0, (var(--asm, 1) * 1.35) - var(--i, 0) * 0.09, 1)) * 108%));
}
@media (prefers-reduced-motion: reduce) {
  .era__label--split .wi { transform: none; will-change: auto; }
}

/* ---- C7 · THE PLAY GLYPH LEANS TO THE POINTER -------------------------- */
/* The smallest item, on all 50 films: the page answers the hand everywhere.
   data-sc-magnet="0.18" on .vframe__glyph — the glyph, NEVER the <button>.
   The button is the 44px tap target and an aria-expanded control; moving it
   would move a hit area and break the 313-stop tab walk. The glyph is already
   aria-hidden and is purely the drawn circle and triangle, and the engine's
   magnet drifts it inside its own bounds, so hit area, focus ring and tab
   order are untouched by construction.

   THE TRANSFORM COLLISION, verified in scrollcraft.js 1278-1280: the magnet
   writes `el.style.transform = 'translate3d(...)'` as an INLINE style every
   frame, which beats this stylesheet and would therefore WIPE the existing
   hover / focus-visible / active scales on the glyph. The plan flagged this as
   the one detail to check first. It is real, so the scales move down one level
   onto the glyph's own <svg> — the same visual result, on a property the
   engine does not write. (.vframe__glyph's centring uses the separate
   `translate` property, not `transform`, so the magnet does not disturb it.) */
.vframe__glyph svg { transition: transform 160ms var(--vf-ease); }
@media (hover: hover) {
  .vframe__play:hover .vframe__glyph svg { transform: scale(1.06); }
}
.vframe__play:focus-visible .vframe__glyph svg { transform: scale(1.06); }
.vframe__play:active .vframe__glyph svg { transform: scale(0.97); }
/* and the glyph itself stops scaling, so the two do not compound */
@media (hover: hover) {
  .vframe__play:hover .vframe__glyph { transform: none; }
}
.vframe__play:focus-visible .vframe__glyph,
.vframe__play:active .vframe__glyph { transform: none; }
@media (prefers-reduced-motion: reduce) {
  .vframe__glyph svg { transition: none; }
}

/* ---- C1 · THE CORONA, WHERE IT CAN ACTUALLY BE SEEN -------------------- */
/* MEASURED DEVIATION FROM THE PLAN, and the reason for it.
   The plan puts the whole corona in the three.js scene behind <main> and adds
   a contrast rule: "the corona is behind main at z-index 0 while main is
   z-index 1 WITH AN OPAQUE BONE SHEET, so it cannot touch that text... the
   builder must confirm on screenshots that the glow reads AROUND the sheet,
   not through it; if the sheet is ever made translucent, this item is void."

   Measured (lab/dazzle/diag5, diag6): on the pinned peak the bone sheet is a
   full 1440x900 opaque rectangle at every sampled progress from 0.0 to 0.8.
   There is no "around". The three.js corona was rendering correctly and was
   invisible for the entire act — the pass's single biggest item, dead.

   The two ways out were (a) make the bone sheet translucent, which is barred:
   the bone peak is the page's one light event and a standing Niko decision,
   and the plan itself says a translucent sheet VOIDS the item; or (b) draw the
   corona on the sheet, under the content. (b) keeps every constraint: the
   sheet stays opaque, the measured type is never crossed (this layer is below
   .peak, which carries its own stacking), the composition is the same corona —
   halo, diamond ring, crossed spikes, bone warming to the leader red, no third
   hue — and it costs zero bytes and no new JS. The three.js corona stays in
   the scene and on the window.__tapestry handle: it is what the reduced-motion
   still frame and the verify-state report are read from, and it is what lights
   the cloth at the act's edges where the sheet is not covering the frame.

   Driven by the SAME property the C4 bridge already writes, so there is still
   exactly one bridge on #a7 and one clock. */
/* F-001: reads its own property, written from the corona window */
.peak__corona {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: var(--peak-corona, 0);
  /* the halo, the diamond ring and the two crossed spikes, in that order.
     Bone core (#EFE9DD) warming to the leader red (#E8613F) at the ring, which
     is the page's one-hue-two-stops rule; no third hue anywhere. */
  /* CENTRED ON THE LEAD FILM, not on the sheet. The peak's own eclipse frame
     sits upper-left of centre in the grid, and a corona centred on the sheet
     put its ring through the right-hand type instead of around the picture
     that IS the eclipse. --cx/--cy are the centre, stated once and read by all
     four layers so they cannot drift apart. */
  --cx: 29%;
  --cy: 25%;
  /* The lead eclipse frame is an opaque black plate covering the centre-left
     of the sheet, so a corona sized to sit UNDER it is simply hidden (the
     first attempt, lab/dazzle/proof/c1-peak-08 at that revision). The halo is
     therefore sized to spill well past the plate: the light reads as coming
     from BEHIND the eclipse frame and bleeding out around its edges, which is
     also what the picture inside the plate is of. */
  background:
    /* the crossed spikes: thin, cut to a cross by the mask below */
    linear-gradient(90deg, transparent 46%,
      color-mix(in oklab, #E8613F 30%, transparent) 50%, transparent 54%),
    linear-gradient(0deg, transparent 46%,
      color-mix(in oklab, #E8613F 24%, transparent) 50%, transparent 54%),
    /* the diamond ring: a thin hot annulus warming to the leader red */
    radial-gradient(46% 62% at var(--cx) var(--cy),
      transparent 72%,
      color-mix(in oklab, #E8613F 52%, transparent) 79%,
      color-mix(in oklab, #9E2A16 34%, transparent) 83%,
      transparent 90%),
    /* the halo */
    radial-gradient(96% 128% at var(--cx) var(--cy),
      color-mix(in oklab, #E8613F 34%, transparent) 30%,
      color-mix(in oklab, #E8613F 20%, transparent) 56%,
      transparent 86%);
  /* the spikes are cut to a cross, so the two linear bands do not run to the
     sheet's edges as stripes */
  -webkit-mask-image: radial-gradient(70% 92% at var(--cx) var(--cy), #000 0%, transparent 88%);
          mask-image: radial-gradient(70% 92% at var(--cx) var(--cy), #000 0%, transparent 88%);
  mix-blend-mode: multiply;   /* light ON PAPER: the corona darkens the bone the
                                 way a print does, rather than glowing like a
                                 screen. A screen blend on a bone sheet is
                                 invisible — the sheet is already near-white. */
}
/* The peak's own content sits above the corona. .peak is a grid area holder,
   so the stacking is stated here rather than relying on paint order. */
.act--peak .peak { position: relative; z-index: 1; }
/* The corona is a composition, not an intensification: under reduced motion it
   sits at full and still, which is what the plan's reducedP: 1 means. The
   bridge writes --peak-dense: 0 at rest for the VIGNETTE, so the corona reads
   its own property here instead of being parked dark. */
@media (max-width: 860px) {
  /* halved on the phone so the glow does not wash a 390px viewport */
  .peak__corona { opacity: calc(var(--peak-corona, 0) * 0.55); }
}
/* F-002: stated after the phone rule so reduced motion wins on every width */
@media (prefers-reduced-motion: reduce) {
  .peak__corona { opacity: 0.85; }
}
