/* wuld-layer.css -- library.wuld.ink shared presentation + cosmetic layer.
   Concatenated by pack_layer.py. Edit the sources in build/, never this file. */


/* ==============================================================================================
   wuld-type.css
   ============================================================================================== */

/* wuld-type.css -- shared presentation tokens for library.wuld.ink
 *
 * PART A: THE PALETTE. Four declarations, and they fix 473 failing elements per wing.
 *
 * THE FIRST ATTEMPT AT THIS FILE WAS A SELECTOR LIST and it was the wrong mechanism -- exactly the
 * failure wuld-vfx.css carries a comment warning about. It enumerated .rsi-axis-label, .rwe-byline,
 * .lbl, .kw and a dozen more, harvested from a probe's top-3-per-colour output, and it moved the
 * wing from 78.7% of elements below WCAG AA to 40.9%. Half the page, because a list of names can
 * only ever reach the names on it. The largest remaining group was 175 unclassed <span>s inside
 * div.kw -- and div.kw sets no colour at all, so they were inheriting from somewhere the list did
 * not go.
 *   The page does not style by selector. It styles by VARIABLE: nine of them in :root, redefined
 * per mode. Every one of the six surfaces -- right-to-die, abortion, veganism, transgenderism,
 * anthropocentrism, and the libraries umbrella -- declares the identical nine names. So the fix is
 * to redefine the variables, which reaches every element in every mode and cannot go stale, because
 * there is no list.
 *
 * MEASURED, all visible text >=3 chars, 1440x900, per mode, contrast against the painted ground:
 *
 *   mode            shipped                                    after
 *   STANDARD        dim 4.35!  faint 2.13!  accent 3.37!       dim 7.58  faint 5.28  accent 4.84
 *   legible                    faint 3.32!                               faint 5.06
 *   high-contrast   all pass                                   untouched
 *   both            all pass                                   untouched
 *
 * NOTHING THAT ALREADY PASSED WAS MOVED. legible's --dim (5.99) and --accent (6.40) clear AA and
 * are left exactly as designed; only --faint moves. Two of the four modes are not touched at all.
 * This is a correctness patch, not a re-design.
 *
 * WHY THESE VALUES. The ink is warm (#d8d4cc, #f0ebe5) so the dim tiers are warm too -- a neutral
 * grey reads cold beside it. They are chosen with headroom rather than at the 4.5 minimum: a
 * palette that scrapes the line fails again the next time a background moves one shade. And the
 * hierarchy survives: 13.31 > 7.58 > 5.28 is a wider, more legible ladder than 13.31 > 4.35 > 2.13,
 * which was drawing its bottom distinction between "dim" and "cannot be read".
 *
 * THE CRIMSON IS NOT SWAPPED FOR THE FLAGSHIP'S #FF3333. It is lifted in lightness with the hue
 * held -- #c41e3a -> #e83a56, 3.37:1 -> 4.84:1. The signature colour survives. --accent also draws
 * 1px hairlines, where contrast minima do not apply and where the change is imperceptible. */

/* SCOPING. The first version of this patch put the STANDARD values on :root and made LEGIBLE and
 * BOTH catastrophically worse -- BOTH went from 2.8% of elements failing to 81.5%. :root and
 * [data-mode="..."] carry the SAME specificity (0,1,0), so an injected :root that arrives after the
 * page's own mode blocks beats all of them, and the dark-ground values leaked onto the cream
 * grounds. Scope to the mode being fixed and nothing else; never restate a value that already
 * passes, so nothing here can drift out of sync with the page's own palette.
 *   This was caught only because the harness sweeps four modes on six surfaces rather than the one
 * mode being fixed. cccxxiv's counter-discipline, working: vary the property while holding the
 * mechanism fixed, and a regression you were not looking for shows up in a row you did not need. */
:root:not([data-mode]),
[data-mode="standard"]{
  --dim:    #a6a096;   /* was #7a766e -- 4.35:1, failing in 288 places per wing */
  --faint:  #88847c;   /* was #4a4742 -- 2.13:1, the worst text on the site */
  --accent: #ef3a58;   /* was #c41e3a -- 3.37:1 as text; hue held, lightness lifted */
}
/* /troubleshooting/ is NOT part of the nine-variable family -- its own palette (--ink #f0ebe5,
 * --dim #9a938b, --accent #c41e3a) sits on three grounds: #0a0a0a, --panel #141210 and --panel-2
 * #1b1815. Measured: --ink 16.7:1 and --dim 6.5:1 both clear everywhere and are left alone; only
 * the crimson fails, in six places, at 3.39 / 3.20 / 3.03.
 *   ONE CRIMSON FOR THE WHOLE LIBRARY. #e83a56 cleared the wings at 4.84 but dipped to 4.35 on
 * troubleshooting's deepest panel, which would have meant two reds. #ef3a58 clears every ground in
 * the library -- wings 5.07, and 5.11 / 4.82 / 4.56 here -- so there is one value, not a family of
 * near-identical ones that drift apart.
 *   Keyed to [data-mode="dark"], which cccxxiii called out as DECORATION on this page. That is
 * exactly why it is safe here: it identifies the page rather than standing proxy for a reader
 * preference, and it is the only page that carries it. The hazard is keying a rule to a name that
 * SUBSTITUTES for the property you care about; using an attribute as a page identifier, knowingly,
 * is a different thing. */
[data-mode="dark"]{ --accent: #ef3a58; }
[data-mode="legible"]{
  --faint:  #6b6559;   /* was #8a8275 -- 3.32:1 on the cream ground */
  /* GROUND IS NOT ALWAYS THE PAGE GROUND. --stub clears 4.56:1 against --bg (#f5efe6) and was read
   * as passing on that basis -- but .rwe>.lbl sits on --panel (#ebe3d4), where the same gold is
   * 4.09:1. Eight elements per wing, failing only where they happen to be inside a panel. Contrast
   * is a property of a PAIR, so a colour does not "pass"; it passes against a named ground, and a
   * palette with two grounds has to clear the darker one. Dropped to 84% lightness, hue held:
   * 5.96:1 on bg, 5.35:1 on panel. */
  --stub:   #71571b;   /* was #876820 */
}
/* [data-mode="high-contrast"] and [data-mode="both"] are deliberately absent: every value in them
 * already clears AA and changing a passing colour is not a fix, it is churn. */

/* THE ONE COLOUR THAT IS NOT A VARIABLE. .rsi-badge is hardcoded #6699CC in every mode -- it is the
 * only text colour on the page that does not come from the nine. On the dark grounds it is fine
 * (6.29:1). On the light ones it is 3.00:1 on white and 2.63:1 on cream, and it was the ONLY thing
 * still failing after the palette patch: 17 elements on right-to-die, 2.8-6.5% of every wing, and a
 * failure that predates this work rather than one it introduced.
 * Hue held, lightness dropped to 66%: #6699CC -> #436587, 6.09:1 on white and 5.33:1 on cream. Only
 * the light modes are touched; the dark ones keep the original blue exactly. */
/* !important IS REQUIRED AND IS NOT LAZINESS. The badge colour is set INLINE -- 17 style attributes
 * carrying a colour on right-to-die, exactly the 17 badges -- and an inline declaration outranks any
 * author-stylesheet rule at normal weight regardless of specificity. !important in a sheet is the
 * only thing that beats it. The count was sitting in an earlier probe's output ("inline style=
 * attributes: 102, of which set colour: 17") and went unread until the fix silently did nothing. */
[data-mode="legible"] .rsi-badge,
[data-mode="both"] .rsi-badge{ color:#436587 !important; border-color:#436587 !important; }


/* ==============================================================================================
 * PART B: THE TYPE SCALE. Filed at first as an aesthetic item. That was wrong, and the numbers say
 * so: the wings put the MAJORITY of their text elements below 10px, and the flagship essentially
 * never does.
 *
 *   surface         elements   under 10px            distinct sizes
 *   flagship            274    1  (0.4 pct)          5   (9, 10, 11, 13, 14)
 *   right-to-die        601  429 (71.4 pct)         12   (8.3 .. 16.3)
 *   veganism            296  187 (63.2 pct)         10
 *   libraries            45   22 (48.9 pct)          8
 *
 * 429 of 601 elements under 10px, 327 of them at exactly 9.92px, and 17 at 8.3px. Sub-10px is below
 * any practical reading floor, and it is the larger part of what the operator was seeing as "unique
 * awkward looking text" -- and of why the palette patch, which was real, was hard to SEE: 324 of its
 * 473 changed elements sit at 9.9px, and only 46 of them are above the fold of an 8,394px document.
 * A contrast fix on type this small is a compliance win before it is a reading one.
 *
 * THE SELECTOR LIST BELOW IS NOT INFERRED. It is every rule in the page's own stylesheet that sets a
 * font-size, extracted by parsing that sheet with comments stripped first -- 36 rules, bucketed by
 * computed px. Part A taught the lesson: a list assembled from a probe's top-N output reaches only
 * the names on it, but a list that IS the stylesheet's own font-size rules is exhaustive by
 * construction. If the page gains a rule, re-extract; never add names by hand.
 *
 * Expressed in rem, not px, so [data-mode="legible"]'s 17px root still scales them -- that is the
 * whole purpose of the mode, and px would silently defeat it.
 * Three sizes, mapped onto the flagship's: 10 / 11 / 13. */

:root{ --wt-s: .625rem;  --wt-m: .6875rem;  --wt-b: .8125rem; }

/* --- 10px: labels, badges, chips, meta (20 rules across all six surfaces) --- */
.badge,
.ctl-label,
.obj-access,
.kw span,
.diag .lbl,
.resp .lbl,
.rwe>.lbl,
.rwe-src a::after,
.rwe-byline,
.rwe-more summary,
.rwe-meta,
.mode-toggle button,
.resp-d>summary,
.rsi-badge,
.rsi-detail .rsi-axis,
.rsi-detail .rsi-formula,
.register-toggle button,
.obj.lay .lay-lbl,
.sources .lbl,
.lib-meta{ font-size:var(--wt-s); }

/* --- 11px: secondary (10 rules) --- */
.eyebrow,
.tab,
.chip,
.count,
.obj-meta,
.mech,
.permalink,
footer.site,
.sources,
.psych{ font-size:var(--wt-m); }

/* --- 13px: body (9 rules) --- */
.sub,
.notice,
.search input,
.diag,
.resp p,
.rwe-src a,
.rwe-quote,
.rwe-more p,
.lib-card p{ font-size:var(--wt-b); }

/* WEIGHT IS THE OTHER HALF. The wings are font-weight 400 on every element on the page; the flagship
 * uses 400 / 500 / 700 with heavy tracking as structure. That is WHY the wings had to reach for a
 * third dim tier at 2.13:1 to build hierarchy -- brightness was the only axis they were using. With
 * weight and tracking carrying it, one readable ink tier is enough, which is what Part A assumed. */
.resp p, .diag, .rwe-quote{ font-weight:500; }
.badge, .rsi-badge, .diag .lbl, .resp .lbl, .rwe>.lbl, .ctl-label, .obj-access{
  font-weight:700; letter-spacing:.1em;
}
/* TAP TARGETS. WCAG 2.5.8 (AA) wants 24x24. Audited on a wing: 91 of 122 interactive elements were
 * under it. Most of that count is INLINE prose links, which the success criterion explicitly
 * exempts -- a target "in a sentence or whose size is constrained by the line-height of non-target
 * text" is not required to meet the minimum, and padding them would only make hit boxes on adjacent
 * lines overlap. The block-level ones have no such excuse: <summary> measured 870x15, which is a
 * disclosure control 9px short of usable, x22 per page.
 * Padding only, no size change, so nothing about the type reflows. */
 * A bare `summary` rule reached only half of them: .resp-d>summary carries its own padding at
 * (0,1,1) specificity and kept its 2.4px, landing at 22px -- two short. Named explicitly, from the
 * stylesheet, same discipline as the font-size buckets above. */
summary{ padding-block:.3rem; }
.resp-d>summary, .rwe-more summary{ padding-block:.3rem; }
.permalink{ display:inline-block; padding-block:.24rem; }

/* Hard edges are the flagship's identity; 2-3px is the visible tell that a wing is a different site. */
.obj, .card, .panel, .diag, .resp, .chip, .kw span, .badge, .rsi-badge, .lbl,
.mode-toggle button, .register-toggle button, .tab, .search input{ border-radius:0 !important; }


/* ==============================================================================================
 * THE WARM CAST, AS COLOUR RATHER THAN AS A BLEND LAYER.
 * The brief was "give all of the text a subtle orangish tint, like the chin's LED panels have."
 * The first build did it with a fixed soft-light overlay and it cost half the frame rate -- see the
 * measurement in wuld-vfx.css. These are the same colours the overlay was producing, pre-mixed 16%
 * toward --wz-tint (#FF9A78) and written as literals, because color-mix() cannot reference the
 * variable it is redefining without a cycle.
 *
 * ONLY WHERE THE GLOW IS, AND ONLY AT THE VFX TIER: stepping the power button down to cosmetic or
 * off restores the neutral palette, because html.wz-vfx is in every selector. The light modes are
 * untouched -- warming a cream ground muddies it -- and so is high-contrast, deliberately: that mode
 * exists to maximise separation and a tint spends a little of it for a look.
 *
 * Every value re-checked against its own ground; the worst is --faint at 5.58:1, so the AA result
 * survives the warming with room to spare. */
html.wz-vfx:not([data-mode]),
html.wz-vfx[data-mode="standard"]{
  --fg:     #decbbf;   /* from #d8d4cc  12.55:1 */
  --dim:    #b49f91;   /* from #a6a096   7.78:1 */
  --faint:  #9b887b;   /* from #88847c   5.81:1 */
  --accent: #f2495d;   /* from #ef3a58   5.51:1 */
  --stub:   #c39236;   /* from #b8902a   7.01:1 */
}
html.wz-vfx[data-mode="dark"]{     /* /troubleshooting/, its own palette */
  --ink:    #f2ded4;   /* from #f0ebe5  15.25:1 */
  --dim:    #aa9488;   /* from #9a938b   6.88:1 */
  --accent: #f2495d;   /* from #c41e3a   5.55:1 */
}


/* ==============================================================================================
   wuld-bezel.css
   ============================================================================================== */

/* wuld-bezel.css -- the monitor frame for library.wuld.ink
 *
 * GEOMETRY IS THE CORRECTED SET (handoff §1, after the 2026-09-10 correction). The earlier draft's
 * top lip of 20 px was BEZEL_CROP_TOP -- picture rows discarded off the top of the scaled footage,
 * not a bezel dimension -- and its 34 px was the chin's own depth rather than the whole band under
 * the picture. The tell was arithmetic: 20 + 1018 + 34 = 1072 against a 1080 frame. These close.
 *   film   1920x1080 frame, 1892x1018 opening, 14 px lip all four sides, chin adds 34 below
 *          14 + 1018 + 48 = 1080
 *   ratios side 0.740% of opening width | top 1.375% | bottom band 4.715% of opening height
 *
 * THE FILM CROPS; A PAGE MUST NOT. 46 picture rows are discarded in the film (20 top, 26 bottom).
 * A film frame is a fixed composition and can afford that. A page's top nav is functional, so this
 * builds the frame as an INSET -- the furniture is pushed in by the lip -- rather than as an overlay
 * painted over whatever happens to be at the viewport edge.
 */

:root{
  --wz-lip-x: 0.740vw;      /* side lip   */
  --wz-lip-y: 1.375vh;      /* top lip    */
  --wz-band:  4.715vh;      /* bottom band: lip + chin */
  --wz-body:  #0F0F11;      /* bezel body */
  /* INNER LIP. The handoff gives #191A1C-ish, measured off the film -- where the monitor is a small
   * bright object in a dark room. At full-screen 1:1 that same value is 11 luma above the bezel body
   * and effectively invisible; the frame read as a flat border, which §1 warns is the failure mode.
   * Re-derived by scanning the edge: body 15.1, and the lip line lands at
   *     #191A1C 25.9 (+10.8)   #2E3136 48.7 (+33.6)   #43474E 70.7 (+55.5)   #53585F 87.4 (+72.3)
   * #43474E reads as an edge at a glance without becoming a highlight. Same class of correction as
   * the bezel geometry: a film constant that does not survive the change of scale. */
  --wz-lip:      #43474E;
  --wz-lip-w:    1px;                       /* solid core -- thin; the ramp does the work */
  --wz-lip-mid:  rgba(67,71,78,.60);        /* first fall-off */
  --wz-lip-soft: rgba(67,71,78,.26);        /* second, reaching ~10px inward */
  --wz-led:   #FF8195;      /* idle. SPEC HEX, not eyedropped: the film renders (221,170,177),
                               which is the same hue at under half the saturation because only the
                               idle state was ever rendered and the render compresses it. */
  --wz-mark:  rgba(255,129,149,.62);
}

/* --- the frame ------------------------------------------------------------------------------- */
.wz-frame{
  position:fixed; inset:0; pointer-events:none; z-index:2147483000;
  border-style:solid; border-color:var(--wz-body);
  border-width:var(--wz-lip-y) var(--wz-lip-x) var(--wz-band) var(--wz-lip-x);
  /* Only the inward vignette lives on the frame itself now; the lip moved to ::before so its
   * gradient can face OUTWARD. */
  box-shadow: inset 0 0 26px 8px rgba(0,0,0,.5);
}

/* THE LIP FALLS OFF OUTWARD, ONTO THE FRAME -- NOT INWARD ONTO THE PICTURE.
 * The first ramp spread into the content, which reads as the screen being fogged at its edge. A real
 * bezel's inner lip catches light coming OFF the panel and scatters it onto the frame, so the bright
 * core sits exactly on the opening and the falloff runs outward across the bezel band.
 * Mechanically that needs a non-inset shadow: ::before is sized to .wz-frame's padding box, which IS
 * the content opening, so a plain box-shadow spreads outward into the border region and paints above
 * it (a child paints over its parent's border). Inset shadows can only ever go the other way, which
 * is why the first attempt faced the wrong direction. */
.wz-frame::before{
  content:''; position:absolute; inset:0; pointer-events:none;
  box-shadow: 0 0 0 var(--wz-lip-w) var(--wz-lip),
              0 0 4px  calc(var(--wz-lip-w) + 1px) var(--wz-lip-mid),
              0 0 11px calc(var(--wz-lip-w) + 2px) var(--wz-lip-soft);
}

/* THE CHIN NEEDS ITS OWN LIP. The frame's ::before draws a lip on all four sides, but the chin is a
 * separate fixed element painted ABOVE the frame (z 2147483001 vs 2147483000), so it covered the
 * bottom one -- three edges lit and one dark. Its own edge sits at the chin's TOP, which is where the
 * opening is, and falls off DOWNWARD into the chin: same rule as the other three, away from the
 * picture. Absolutely positioned so it is not a flex item in the chin's own layout. */
.wz-chin::before{
  content:''; position:absolute; left:0; right:0; top:0; height:13px; pointer-events:none;
  /* A REAL GRADIENT, NOT STACKED SHADOWS. The first attempt wrote `0 4px <blur>` where the 4px is
   * box-shadow's OFFSET-Y, not its blur radius, so it painted two displaced bands instead of a ramp:
   * the scan read 15, 16, 22, 30, 22, 16 going down -- a second bright ring 4px below the edge.
   * A linear-gradient states the falloff directly and is monotonic by construction. */
  background:linear-gradient(to bottom,
    var(--wz-lip)      0,
    var(--wz-lip)      var(--wz-lip-w),
    var(--wz-lip-mid)  calc(var(--wz-lip-w) + 1px),
    var(--wz-lip-soft) 5px,
    transparent        100%);
}

/* --- the chin, and its furniture ------------------------------------------------------------- */
.wz-chin{
  position:fixed; left:0; right:0; bottom:0; height:var(--wz-band);
  pointer-events:none; z-index:2147483001;
  display:flex; align-items:center; justify-content:center; gap:4.2vw;
  background:var(--wz-body);
  font:500 clamp(9px,1.15vh,13px)/1 ui-monospace,"IBM Plex Mono",Menlo,Consolas,monospace;
  letter-spacing:.42em;
}
.wz-perf{                                   /* speaker perforation */
  width:4.6vw; height:38%; opacity:.5;
  background-image:radial-gradient(circle, var(--wz-mark) 34%, transparent 36%);
  background-size:0.30vw 0.30vw;
}
.wz-mark{ display:flex; align-items:center; gap:.30em; color:var(--wz-mark); }
/* THE ORNAMENT YIELDS TO THE CONTROLS. The chin row is now four 44px buttons reaching 194px from
   the right edge, and the centred wordmark runs under the leftmost of them below a measured 476px.
   The first threshold here was 420px -- derived when the row had THREE buttons and never re-derived
   when the fourth was added, so between 421 and 468px the ? button sat under the wordmark and
   chinfit never noticed, because it sampled 1440/768/390/320 and nothing in between. 500 leaves a
   margin over the measured clash rather than a coincidence. The mark is aria-hidden decoration; the
   buttons are the only route to the tier, the magnifier, the sound and the tutorial. */
@media (max-width: 500px){ .wz-mark, .wz-perf{ display:none; } }
.wz-led{                                    /* the periods ARE the status lights */
  width:.42em; height:.42em; border-radius:50%;
  background:var(--wz-led); opacity:.55;
  box-shadow:0 0 .45em .06em var(--wz-led);
  transition:opacity .18s linear, box-shadow .18s linear;
}
/* THE POWER BUTTON IS A 44px TARGET WEARING A SMALL GLYPH. Measured at 13x16 desktop and 12x15 on
 * a 390px phone -- under the 24x24 WCAG 2.5.8 floor and far under the 44x44 that a thumb needs. It
 * matters more than it looks: with the tier defaulting to vfx this is the ONLY way out of the
 * effect, and the first-visit hint points straight at it. The glyph stays its old size; the button
 * grows around it, centred, so nothing about the chin's look changes.
 *   The hit area is allowed to exceed the chin's height and overhang upward -- the chin is
 * pointer-events:none, so the overhang costs the page nothing and gives the thumb somewhere to
 * land on a short chin. */
.wz-power{
  position:absolute; right:max(1.0vw, 6px); bottom:50%; transform:translateY(50%);
  min-width:44px; min-height:44px;
  display:flex; align-items:center; justify-content:center;
  color:var(--wz-mark); opacity:.75; font-size:1.5em; line-height:1;
  pointer-events:auto; cursor:pointer; background:none; border:0; padding:0;
  border-radius:50%;
}
.wz-power:hover{ opacity:1; }
/* Focus must be visible on a near-black chin, and the UA's default `outline:auto` is not reliably
 * so. :focus-visible only, so a mouse click does not leave a ring behind. */
.wz-power:focus-visible{
  outline:2px solid var(--wz-led); outline-offset:-6px; opacity:1;
}

/* --- THE INSET. Without this the frame is an overlay and eats whatever is pinned to the edge. -- */
/* THE INSET GOES ON THE STAGE, NOT ON <body>. Measured: `html.wz-on body{padding-inline:...}` lost
 * the cascade outright -- computed padding-left came back 0px -- and even had it won, three elements
 * on the graph views (nav.rl-wing, nav.top-nav and the coda div) carry an explicit width:1440px and
 * would have escaped a padded parent anyway. The result was ~10 px of the coda text living under the
 * left lip: "ecause suffering", "emands more of us", "o: yes". The stage is this layer's own element,
 * so nothing competes for it. */
html.wz-on body{ padding-bottom:var(--wz-band) !important; }
html.wz-on .wz-stage{ box-sizing:border-box; padding-inline:var(--wz-lip-x); }
/* nav.top-nav is the ONLY pinned element on this page -- enumerated, not assumed: every
   fixed/sticky box touching a viewport edge at 1440x900 was listed, and there is one.
   It is sticky and spans edge to edge, so the BAR keeps its full width (correct under a bezel)
   and its CONTENTS are pushed in. Insetting `top` alone left "LIBRARY" reading as "IBRARY". */
html.wz-on nav.top-nav{
  top:calc(var(--wz-lip-y) + 2px);
  box-sizing:border-box;
  padding-left:calc(var(--wz-lip-x) + 6px);
  padding-right:calc(var(--wz-lip-x) + 6px);
}

@media (prefers-reduced-motion:reduce){ .wz-led{ transition:none; } }

/* The mute button, third in the chin's control cluster. 44px like the others, and offset to clear
 * BOTH of them -- the magnifier sits at +50px and is itself 44px wide, so anything under +100px
 * overlaps it. That arithmetic is the same mistake the magnifier made against the power button
 * (offset 42 against a 44px target, 2px of overlap at every width); once is a bug, twice would be
 * not reading the note. */
/* The way back into a tour once it has been seen. Fourth in the chin row rather than a top-of-page
   label, because the tours are the layer's own artefact and the layer's controls live here -- and
   because it runs the tour for the view in front of you rather than offering a menu of five. At
   320px the row now reaches 194px from the right edge, which is why the wordmark hides below 420. */
.wz-help{
  position:absolute; right:calc(max(1.0vw,6px) + 150px); top:50%; transform:translateY(-50%);
  width:44px; height:44px; display:flex; align-items:center; justify-content:center;
  background:none; border:0; cursor:pointer; padding:0;
  color:var(--wz-mark); opacity:.75; font:700 .95em/1 ui-monospace,"IBM Plex Mono",Menlo,monospace;
}
.wz-help:hover{ opacity:1; }
.wz-help:focus-visible{ outline:2px solid var(--wz-led); outline-offset:-6px; opacity:1; }
.wz-mute{
  position:absolute; right:calc(max(1.0vw,6px) + 100px); bottom:50%; transform:translateY(50%);
  min-width:44px; min-height:44px;
  display:flex; align-items:center; justify-content:center;
  color:var(--wz-mark); opacity:.75; font-size:.80em; line-height:1;
  pointer-events:auto; cursor:pointer; background:none; border:0; padding:0; border-radius:50%;
}
.wz-mute:hover{ opacity:1; }
.wz-mute:focus-visible{ outline:2px solid var(--wz-led); outline-offset:-6px; opacity:1; }
html.wz-muted .wz-mute{ opacity:.45; }

/* --- THE FIRST-VISIT HINT ----------------------------------------------------------------------
 * The tier defaults to vfx, so the reader did not ask for the effect -- which is the objection that
 * used to be answered by "it defaults to off", and is not any more. The answer now is that they are
 * TOLD, once, where the exit is. Shown once per browser session, above the chin, beside the button
 * it is pointing at.
 * It lives OUTSIDE .wz-stage, so it neither glows nor pans -- a hint that is hard to read because it
 * is participating in the effect it offers to turn off would be a joke at the reader's expense. */
.wz-hint{
  position:fixed; right:max(1.0vw,6px); bottom:calc(var(--wz-band) + 8px);
  z-index:2147483002; max-width:min(19rem, calc(100vw - 24px));
  display:flex; align-items:flex-start; gap:.5rem;
  padding:.55rem .65rem .55rem .8rem;
  background:#16161a; border:1px solid var(--wz-lip);
  box-shadow:0 2px 14px rgba(0,0,0,.55);
  color:#ddd8d0; font:400 12px/1.45 ui-monospace,"IBM Plex Mono",Menlo,Consolas,monospace;
  letter-spacing:.01em;
  opacity:0; transform:translateY(6px); transition:opacity .35s ease, transform .35s ease;
}
.wz-hint.wz-in{ opacity:1; transform:none; }
.wz-hint b{ color:var(--wz-led); font-weight:700; }
.wz-hint button{
  flex:none; min-width:24px; min-height:24px; margin:-.2rem -.2rem 0 0;
  display:flex; align-items:center; justify-content:center;
  background:none; border:0; padding:0; cursor:pointer;
  color:#8a857d; font:inherit; font-size:15px; line-height:1;
}
.wz-hint button:hover{ color:#ddd8d0; }
.wz-hint button:focus-visible{ outline:2px solid var(--wz-led); outline-offset:1px; }
@media (prefers-reduced-motion:reduce){
  .wz-hint{ transition:none; transform:none; }
}

/* --- PRINT: THE LAYER IS NOT PART OF THE DOCUMENT ---------------------------------------------
 * Measured before this block existed: on emulate_media('print') the frame, chin, vignette, warm
 * wash, peripheral blur AND the power button all still painted, body kept its 42px chin reserve,
 * and the text kept its halo. Printing a reference page would have produced a black monitor frame
 * around haloed type with a vignette down the margins.
 * These pages are argument libraries -- people print them to argue from. The furniture is CHROME:
 * it belongs on a screen and nowhere else. Everything the layer adds comes off, the reserved band
 * comes back, and the page prints as the document it is. */
@media print{
  .wz-frame, .wz-chin, .wz-vig, .wz-soft-l, .wz-soft-r, .wz-hint{ display:none !important; }
  html.wz-on body{ padding-bottom:0 !important; }
  html.wz-on .wz-stage{ padding-inline:0 !important; }
  html.wz-vfx .wz-stage, html.wz-vfx .wz-stage *{
    text-shadow:none !important; box-shadow:none !important; filter:none !important;
    transform:none !important;
  }
}


/* ==============================================================================================
   PER-CARD FEEDBACK  --  .wz-fb
   Lives INSIDE .wz-stage, unlike the chin and the hint, because it belongs to its card: it should
   pan and scale with the card it reports on. What it does not do is glow -- the halo is for text
   the page wants read, and a report control is function, not emitter.
   Visible at every tier including `off`: cosmetics are optional, a way to report an error is not.
   ============================================================================================== */
/* ANCHORED, NOT FLOATED. A float is only contained by a block taller than itself, and .obj-meta is
   a 17px strip while the control has to be 26px to clear the WCAG 2.5.8 target floor -- so 9px of
   it hung past the strip and shortened the first line of every headline. Measured: the h2 rewrapped.
   Absolute positioning against the card touches no line box at all. The card measured position
   static, so making it relative changes nothing it does today; the meta strip reserves the lane by
   padding, keyed to the attribute the injector sets, so a card without a control reserves nothing. */
/* Scoped to cards the injector has actually marked: `.obj{position:relative}` unscoped would have
   applied to any element on any page that happens to use that class name. */
.obj[data-wz-fb]{ position:relative; }
.obj[data-wz-fb] > .obj-meta{ padding-right:5rem; }
.wz-fb{
  position:absolute; top:.6rem; right:.85rem;
  display:inline-flex; align-items:center; justify-content:center;
  min-width:26px; height:26px; padding:0 .4rem;   /* WCAG 2.5.8 AA floor is 24x24 */
  font:700 10px/1 ui-monospace,"IBM Plex Mono",Menlo,Consolas,monospace;
  letter-spacing:1px; text-transform:uppercase; text-decoration:none; white-space:nowrap;
  color:var(--dim);
  border:1px solid transparent; border-radius:3px;
  transition:color .16s ease, border-color .16s ease;
}
/* A WORD, NOT A GLYPH. The first build used an envelope; rendered at 12px in the card's mono stack
   it read as a small outlined box -- closer to a close button than to mail, and an ambiguous icon
   repeated on 82 cards is worse than a plain label. The card already speaks in tracked uppercase
   micro-labels (DIAGNOSIS, RESPONSE - PUNCH) at 10px/700/1px, so this borrows that voice exactly
   and reads as native rather than as something bolted on.
   NO OPACITY. The first version dimmed to .62 and measured 3.56:1 on the dark ground but 2.58:1 on
   the cream one -- under WCAG 1.4.11's 3:1 floor for a UI component. Opacity is the trap:
   getComputedStyle hands back the token, the compositor draws something else, and only a pixel
   sample can tell you which. At --dim, full strength, the worst mode measures 5.37:1.
   Hover raises it rather than revealing it: a control that only appears on hover does not exist on
   a touch screen. */
.wz-fb:hover{ color:var(--fg); border-color:var(--faint); }
.wz-fb:focus-visible{ outline:2px solid var(--accent); outline-offset:1px; }
html.wz-vfx .wz-fb{ text-shadow:none !important; }
@media print{ .wz-fb{ display:none !important; }
              .obj[data-wz-fb] > .obj-meta{ padding-right:0 !important; } }


/* ==============================================================================================
   FIRST-VISIT WALKTHROUGH  --  .wz-tour-*
   The darkening is one element with an enormous box-shadow spread, not a stack of four panels and
   not mix-blend-mode. The blend route is what cost half the frame rate when the warm wash used it;
   a spread shadow is plain paint on a single fixed box and moves by changing four lengths.
   Outside .wz-stage: the spotlight must not pan with the camera or glow, or it would drift off the
   thing it is pointing at.
   ============================================================================================== */
.wz-tour-mask{
  /* ABOVE THE FRAME AND THE CHIN, which sit at 2147483000/1. Under them the panels darkened the
     page but left the whole chin at full brightness and hid the ring behind it -- a spotlight
     pointing at a button nobody could see was being pointed at. The tour is the topmost thing on
     the page while it runs, by definition: that is what it is for. */
  position:fixed; z-index:2147483100; pointer-events:none;
  background:rgba(0,0,0,.74);
  transition:top .2s ease, left .2s ease, width .2s ease, height .2s ease;
}
.wz-tour-ring{
  position:fixed; z-index:2147483110; pointer-events:none;
  border:2px solid var(--wz-led, #FF8195); border-radius:6px;
  transition:top .2s ease, left .2s ease, width .2s ease, height .2s ease;
}
.wz-tour-card{
  position:fixed; z-index:2147483120; box-sizing:border-box;
  padding:.85rem .95rem .7rem;
  background:#131315; color:#ddd8d0;
  border:1px solid #34313a; border-radius:7px;
  box-shadow:0 10px 30px rgba(0,0,0,.6);
  font:400 12.5px/1.5 ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
  transition:top .22s ease, left .22s ease;
}
.wz-tour-text{ margin:0 0 .7rem; }
.wz-tour-nav{ display:flex; align-items:center; gap:.5rem; }
.wz-tour-dots{
  margin-left:auto; font:700 10px/1 ui-monospace,"IBM Plex Mono",Menlo,Consolas,monospace;
  letter-spacing:1px; color:#8b8690;
}
.wz-tour-nav button{
  min-width:30px; height:30px; padding:0 .5rem;   /* over the 24x24 floor on both axes */
  background:#1d1d21; color:#ddd8d0; cursor:pointer;
  border:1px solid #3a3740; border-radius:5px;
  font:600 12px/1 ui-sans-serif,system-ui,sans-serif;
}
.wz-tour-nav button:hover{ background:#26262c; }
.wz-tour-nav button:focus-visible{ outline:2px solid var(--wz-led, #FF8195); outline-offset:1px; }
.wz-tour-nav button[disabled]{ opacity:.4; cursor:default; }
.wz-tour-skip{ margin-right:auto; background:transparent !important; border-color:transparent !important;
               color:#9b9690 !important; font-weight:400 !important; }
.wz-tour-skip:hover{ color:#ddd8d0 !important; }
@media (prefers-reduced-motion:reduce){ .wz-tour-mask, .wz-tour-ring, .wz-tour-card{ transition:none; } }
@media print{ .wz-tour-mask, .wz-tour-ring, .wz-tour-card{ display:none !important; } }


/* ==============================================================================================
   wuld-vfx.css
   ============================================================================================== */

/* wuld-vfx.css -- the opt-in second tier. Requires wuld-bezel.css.
 *
 * THE POWER BUTTON CYCLES: off -> cosmetic -> vfx -> off. Right-click was considered and rejected --
 * undiscoverable, hijacks the context menu, no touch equivalent, unreachable from a keyboard. On a
 * site shipping LEGIBLE and HIGH-CONTRAST modes, a control only a mouse user can find is not a
 * control, and §3's argument is that this button is what makes the layer legitimate rather than imposed.
 *
 * WHAT THE FILM'S GLOW ACTUALLY IS. The film scales 1892 px of content down into a monitor a few
 * hundred px wide, which CONCENTRATES each glyph's energy into fewer, brighter pixels, and blooms
 * that. At 1:1 there is nothing to concentrate: a text-shadow sweep from nothing to past-legible
 * moved mean luma 19.90 -> 20.20 and was indistinguishable by eye at every step. So the bloom is
 * built where it reads in the film -- on POINT SOURCES (badges, graph nodes, LEDs) -- plus a whisper
 * on prose that costs nothing (background median held at exactly 17.00 across the whole sweep, glyph
 * contrast 10.28 -> 10.27) and contributes against the vignette below.
 */

:root{
  --wz-vig-in:   42%;    --wz-vig-mid:  74%;
  --wz-vig-a1:   .26;    --wz-vig-a2:   .58;
  --wz-glow:      rgba(255,150,165,.45);
  --wz-glow-hot:  rgba(255,196,206,.55);
  /* Glow the colour the thing actually emits: a rose glow measured +24.1% where white measured
   * +36.3% at comparable alpha, because rose's luma is 173 against white's 255 -- a tinted glow
   * throws away a third of the light before it starts. currentColor generalises that per element. */
  --wz-glow-mix:       80%;    --wz-text-blur:      10px;   /* plain text  */
  --wz-glow-mix-bold: 100%;    --wz-text-blur-bold: 16px;   /* bold / bright */
  /* THE WARM CAST. The chin's LEDs are #FF8195 and the operator reads the picture as lit by them:
   * "give all of the text a subtle orangish tint, like the chin's LED panels have." Two mechanisms,
   * because one cannot do it. The HALO mixes toward --wz-tint, which warms the emitted light where
   * the bloom actually lives. The WASH is a fixed soft-light overlay that warms the glyph cores and
   * everything else without touching any element's declared colour -- the reason a per-element
   * `color:` override is not used is that `currentColor` INSIDE the color property resolves to the
   * INHERITED colour, so `* { color: color-mix(currentColor ...) }` would replace every tier badge
   * and accent with a value derived from its parent and destroy the palette. soft-light warms
   * highlights and leaves black at black, which is what a phosphor does. */
  --wz-tint:       #FF9A78;   /* between the LED rose (#FF8195) and amber; see the tint strip */
  --wz-tint-glow:  34%;       /* share of the halo that is tint rather than the text's own colour */
  --wz-dark-max:   90;
  /* --- THE MAGNIFIER ------------------------------------------------------------------------
   * Clamped at 1 on the low side, and that clamp is load-bearing: below 1 the stage shrinks away
   * from the frame and the bezel stops being a frame around anything. Above 1 the content always
   * overflows, so the camera pan can never expose an edge -- the no-void guarantee gets STRONGER
   * as you zoom, which is the opposite of what was feared when this was written up as a risk. */
  --wz-zoom:       1;
  --wz-zoom-max:   4;
  --wz-grille:     3px;   /* phosphor triad pitch at 1x, before zoom multiplies it */
  /* THE FIRST BUILD OF THIS RAMP HIT FULL OPACITY BY 2.6x AND DROWNED THE PAGE -- at 4x the type was
   * behind a curtain of RGB stripes rather than lit by them. Without a blend mode the grille ADDS
   * light instead of modulating it, so its alpha has to stay low enough to read as texture. Ramp is
   * linear across the whole zoom range and peaks here, not at 1. */
  --wz-grille-max: .42;        /* background luma at or under which the layer is allowed to glow */
  --wz-svg-blur:  4px;
  --wz-svg-glow:  rgba(255,255,255,.45);   /* one glow for the whole graph; per-node currentColor
                                            * measured inert and cost more */

  /* THE CLAMP IS THE WHOLE DESIGN. The handoff's "immersive at 2, nauseating at 8" was written for
   * a camera move in a film, where the whole frame swings. On a page the reader's eye is fixed on
   * text, and 2deg reads as the page sliding rather than as a camera. Dropped to 0.7 on the
   * operator's call -- "just enough to give the user a sense of movement". */
  --wz-pan:      6px;      /* camera travel. MUST stay under the lip width (10.7px at 1440) or a
                            * gap opens outside the frame -- that is the whole no-void guarantee. */
  --wz-pan-ease: 260ms;
  --wz-soft-w:   34%;      /* how far in from the edge the far-side blur reaches */
  --wz-tilt-ease: 220ms;
}

/* --- 1. VIGNETTE -- and it is load-bearing, not decoration ------------------------------------
 * A bloom is perceived against its surround. The earlier glow test ran against a flat luma of 17
 * across the whole frame, which is why a real lift measured as nothing. Darkening the periphery is
 * what lets the centre's bright things read as emitting. A plain gradient also always paints, unlike
 * the masked backdrop-filter below, which measured inert to six decimal places. */
html.wz-vfx .wz-vig{
  position:fixed; inset:0; pointer-events:none; z-index:2147481500;
  /* DEFAULT (farthest-corner) SIZING, DELIBERATELY. The first build wrote `125% 135%`, which put
   * the ending shape so far outside the viewport that a point 180 px from the corner sat at
   * normalised radius 0.371 -- inside the 38% transparent stop. The whole frame was in the clear
   * zone and the measured centre/corner ratio moved 0.947 -> 0.942, i.e. nothing. With the default,
   * 100% IS the farthest corner and the stops mean what they say. Third time today a parameter was
   * carried over without checking it against the geometry it lands on. */
  background:radial-gradient(ellipse at 50% 45%,
             transparent var(--wz-vig-in),
             rgba(0,0,0,var(--wz-vig-a1)) var(--wz-vig-mid),
             rgba(0,0,0,var(--wz-vig-a2)) 100%);
}

/* --- 2. BLOOM ON POINT SOURCES ---------------------------------------------------------------- */
/* currentColor so each tier halos in its OWN hue rather than everything blooming rose. */
html.wz-vfx .tier-badge{
  box-shadow:0 0 11px 1px currentColor, 0 0 3px var(--wz-glow-hot);
}
/* --- THE GRAPH VIEWS, now measured rather than guessed --------------------------------------
 * d3 is vendored into the local test fixture, so the mechanism web, dependency graph and argument
 * flow render for real. What they are made of: SVG <text> for every label (116-233 of them), plus
 * circles, lines and rects for the structure.
 *   SVG <text> NEEDS NOTHING. It inherits text-shadow from .wz-stage like any other text and was
 *   already glowing: +29.7% on the annulus with no SVG rule at all.
 *   PER-ELEMENT FILTERS ARE INERT HERE. drop-shadow on every circle measured 43.074 against 43.045
 *   for no rule -- indistinguishable, on 82-199 filtered elements. Not shipped: an effect that
 *   changes nothing is the thing this whole harness exists to catch.
 *   ONE FILTER ON THE SVG ROOT WINS ON BOTH AXES: +63.3% against +38.8% for circles+lines, and a
 *   cheaper repaint (21.6 ms vs 24.3 ms) because it is one filter instead of ~600. */
html.wz-vfx .wz-stage svg{
  filter:drop-shadow(0 0 var(--wz-svg-blur) var(--wz-svg-glow));
}
/* Graph LABELS get a tighter, hotter glow than body prose. They render at ~10px against 13px in the
 * cards, so the same 10px blur spreads the same light over proportionally more area and reads as
 * less. Measured A/B on one page instance with the force layout frozen -- two separate page loads
 * cannot be compared here, because d3 re-simulates and the node positions move. */
html.wz-vfx .wz-stage svg text{
  text-shadow:0 0 5px color-mix(in srgb, currentColor 100%, transparent),
              0 0 2px color-mix(in srgb, currentColor 70%, transparent);
}
html.wz-vfx .wz-led{ opacity:.95; box-shadow:0 0 .8em .09em var(--wz-led); }

/* --- 3. THE GLOW ON THE TEXT ITSELF -----------------------------------------------------------
 * INHERITED FROM THE STAGE, AND KEYED TO currentColor. text-shadow inherits, so one rule reaches
 * every text node on every view -- no selector list to go stale, which is what went wrong twice:
 * an earlier build listed (p, span, td, h1, h3, h4) and matched NONE of the 82 `div.trigger-text`
 * elements that hold the bright prose, then concluded from that silence that a glow does not
 * transfer to prose at 1:1. That was a claim about a selector, not about the page.
 * currentColor makes it self-scaling: white text glows white, grey text glows grey. A fixed white
 * glow would put a white halo around a grey letter, which is the wrong picture and reads as haze.
 *
 * MEASURED on the annulus around the glyphs (light added NEAR a glyph, not ON it), against the
 * page's own pixels, with the dim-text ring watched separately so the tint stays proportionate:
 *      55% / 8px   +60.6%      70% / 9,14px  +81.8%      80% / 10,16px  +83.5%      92% / 12,18px  +78.9%
 * IT PEAKS AND THEN DEGRADES. Past ~80% the same light spreads over a wider ring and the halo
 * flattens -- more alpha is not more glow. 80/100 is the measured maximum and is what ships.
 * Background median held at exactly 17.00 at every level, so none of it costs legibility. */
html.wz-vfx .wz-stage{
  text-shadow:0 0 var(--wz-text-blur)
                color-mix(in srgb, color-mix(in srgb, currentColor calc(100% - var(--wz-tint-glow)),
                          var(--wz-tint)) var(--wz-glow-mix), transparent),
              0 0 2px color-mix(in srgb, currentColor 40%, transparent);
}
/* STRUCTURAL PROMINENCE TAKES MORE -- not brightness, which is what an earlier version of this
 * comment claimed and which is measurably the wrong rule. Audited across the wings: editorial <em>
 * renders at colour luma 160 while <strong> renders at 95, because <strong> mostly sits inside
 * deliberately quiet blocks (.rsi-formula) and inherits their colour. So the brighter element is
 * the one NOT on this list, and correctly so -- <em> is stress inside a sentence and appears 35
 * times in running prose on one wing, where the loudest paint on the view has no business. What
 * this list actually selects is text that is prominent by structure: headings, table headers,
 * definition terms, badges, and the trigger line. `.trigger-text` is w500 rather than w700 but is
 * a single isolated headline element, which is why it belongs here despite its weight.
 * Also audited and deliberately left on the base glow: <code> (luma 62 on the accent red -- a glow
 * scales with currentColor, so dark text cannot smear) and <pre> (rendered and inspected; the
 * monospace stays crisp at 10px blur). */
html.wz-vfx .wz-stage :is(strong,b,h1,h2,h3,h4,h5,h6,th,dt,.tier-badge,.trigger-text){
  text-shadow:0 0 var(--wz-text-blur-bold)
                color-mix(in srgb, color-mix(in srgb, currentColor calc(100% - var(--wz-tint-glow)),
                          var(--wz-tint)) var(--wz-glow-mix-bold), transparent),
              0 0 3px color-mix(in srgb, currentColor 55%, transparent);
}
/* THE WASH WAS CUT, AND THE REASON IS A NUMBER. It was a fixed full-viewport overlay with
 * mix-blend-mode:soft-light, and it cost HALF THE FRAME RATE. Measured over a scripted full-document
 * scroll at 1440x900, frame intervals in ms:
 *       original page, no layer     median 16.70   p95 16.70   0.0% of frames over 32ms
 *       vfx with the wash           median 33.30   p95 50.00  85.4%
 *       vfx with the wash removed   median 16.70   p95 16.75   1.1%
 * Isolated one mechanism at a time: killing the vignette, the peripheral blur, the text glow, the
 * stage transform, the SVG drop-shadow or the badge shadows changed NOTHING. Only the wash did. A
 * blend mode has to re-composite its whole backdrop every frame, and on an 8,400px document that
 * backdrop is the document. With the tier now defaulting to vfx, every visitor would have got a
 * 30fps scroll.
 *   The look did not have to go with it. The warmth is now baked into the PALETTE -- the same nine
 * variables, pre-mixed 16% toward --wz-tint and written out as literals in wuld-type.css. Same
 * picture, no compositing, and it is closer to the brief anyway: the operator asked for the TEXT to
 * be tinted, and the wash was tinting panels and grounds along with it.
 *   --wz-tint stays as the value those literals were derived from; --wz-tint-glow still tints the
 * halo, which costs nothing because a text-shadow is paint, not blend. */
/* THE GATE IS MEASURED, NOT NAMED. Three gates were written here and the first two were wrong in
 * the same way -- both asked WHICH MODE, and the answer to that question does not determine whether
 * a glow is a good idea.
 *   v1  html.wz-vfx[data-mode="legible"]          vacuous on the flagship, which sets body classes.
 *   v2  body.legible + [data-mode]:not(standard)  correct on both conventions, and still wrong:
 *       it fired on /troubleshooting/, whose data-mode="dark" is DECORATION (no rule selects on it,
 *       no switcher on the page), silencing the whole tier there. Measured: p and h1 both none.
 *   v3  detect whether [data-mode] is wired to any stylesheet rule. That repaired v2 and left the
 *       real defect untouched, which the operator found by looking: THE MODE NAME DOES NOT PREDICT
 *       THE BACKGROUND. The flagship is DARK in legible and CREAM in high-contrast; the libraries
 *       umbrella is CREAM in legible and BOTH, and DARK in high-contrast. One name, two grounds.
 * So the gate now measures the thing it actually cares about. wuld-vfx.js reads the computed
 * background of <body> (falling through to <html> when it is transparent), takes its Rec.709 luma,
 * and sets html.wz-lightbg above --wz-dark-max. A MutationObserver re-grades on every mode change,
 * so the toggles drive it without the layer knowing a single mode name. Unknown background -> light
 * -> silent, so the failure direction is unchanged.
 *   THE ACCESSIBILITY FLOOR MOVED TO WHERE IT BELONGS. Suppressing on a mode named "high-contrast"
 * was always a proxy; prefers-contrast and forced-colors are the real signals, they come from the
 * OS rather than from one site's vocabulary, and they are what a low-vision reader actually sets.
 *   ONE RESERVATION, RECORDED AND OVERRULED: a halo fills the luminance step at the glyph edge, so
 * on a dark high-contrast ground it does cost some of the contrast that mode exists to provide.
 * The operator's call is that the tier is opt-in behind a power button that defaults to off, so a
 * reader who switched it on has asked for it. The OS-level floor below is what remains of it.
 *
 * SCOPED TO .wz-stage, NOT TO `*`. The first version used a bare universal selector and stripped the
 * bezel's own inner shadow and the LED glow along with the content effects -- measured in legible
 * mode as frameShadow:none, ledShadow:none. The frame is CHROME, not an effect: a reader who wants
 * plain type still wants the monitor around it. Scoping to the stage spares the furniture for free,
 * because the furniture already lives outside the stage so that it cannot pivot -- one structural
 * decision paying twice. */
html.wz-vfx.wz-lightbg .wz-stage *{
  text-shadow:none !important; box-shadow:none !important; filter:none !important;
}
html.wz-vfx.wz-lightbg .wz-vig,
html.wz-vfx.wz-lightbg .wz-soft-l,
html.wz-vfx.wz-lightbg .wz-soft-r{ display:none !important; }

/* The floor. Not keyed to any site vocabulary, and it outranks a dark background. */
@media (prefers-contrast: more), (forced-colors: active){
  html.wz-vfx .wz-stage *{
    text-shadow:none !important; box-shadow:none !important; filter:none !important;
  }
  html.wz-vfx .wz-vig,
  html.wz-vfx .wz-soft-l, html.wz-vfx .wz-soft-r{ display:none !important; }
}

/* --- 4. PERIPHERAL SOFTENING -- CUT, AND THE REASON MATTERS ----------------------------------
 * Not shipped. Three mechanisms were built and all three measured INERT to the last decimal:
 *     unmasked backdrop-filter child        max abs delta 0.000, 0.0000% of pixels moved
 *     masked parent + filtered child        max abs delta 0.000
 *     + near-transparent background on it   max abs delta 0.000
 * backdrop-filter inside a mask-image'd parent does not composite in this engine. An effect that is
 * configured, rendered and changes nothing is exactly what §11 exists to catch, so it is cut rather
 * than tuned -- shipping it would be the failure this whole harness was built to prevent.
 * THE VIGNETTE DOES ITS PERCEPTUAL JOB. "The edges recede" is carried by darkening, measured at
 * centre/corner 0.964 -> 1.468; the film's own retention table already says the softening lives in
 * the corners (centre 100%, mid-edge 95.2%, corner 54.3%) where the vignette is strongest.
 * It comes back if a mechanism composites: a real duplicated layer with `filter: blur()` on a copy,
 * masked -- which costs a second paint of the subtree and should be measured before it is believed. */

/* --- 5. THE CAMERA -------------------------------------------------------------------------
 * A PAN, NOT A TILT. This was rotate3d, and a rotation bends the subject: the page visibly skewed,
 * the rules sloped, and the corners pulled away to show the ground behind. A camera that follows a
 * cursor translates -- the frame stays square to the picture and you simply see a little further to
 * one side. Nothing about the page's own geometry changes.
 * THE AMPLITUDE IS BOUNDED BY THE LIP, which is why no void can appear: the stage slides at most
 * --wz-pan, and the fixed bezel lip is wider than that, so the gap opened at one edge is always
 * underneath the frame that is painted on top of it. One number, derived from another number,
 * rather than two numbers that have to be kept in agreement by hand. */
/* TRANSFORM-ORIGIN IS 0 0, AND THAT IS THE DIFFERENCE BETWEEN A MAGNIFIER THAT WORKS AND ONE THAT
 * EATS THE PAGE. Measured with the origin at 50%: at zoom 2 the stage grows 720px past each side of
 * the viewport and only the RIGHT 714px is reachable, because there is no negative scroll -- the
 * left 726px of every line is permanently unreachable. With origin 0 0 the growth is right-and-down
 * only, maxScrollX matches the growth exactly at every zoom tested, and nothing is lost.
 *   The translate stays OUTSIDE the scale in the composed matrix, so the camera pan is a constant
 * pixel amount at any zoom rather than being multiplied by it: scale(3) with --wz-px:-10px composes
 * to matrix(3,0,0,3,-10,0). Verified on the computed style, not assumed from the spec. */
html.wz-vfx .wz-stage{
  will-change:transform;
  transition:transform var(--wz-pan-ease) cubic-bezier(.22,.61,.36,1);
  transform-origin:0 0;
  transform:translate3d(var(--wz-px,0px), var(--wz-py,0px), 0) scale(var(--wz-zoom,1));
}
/* While zooming, the transition would fight the scroll compensation and the anchor point would
 * visibly slide. Zoom steps are instant; the pan keeps its easing. */
html.wz-zooming .wz-stage{ transition:none !important; }

/* --- 6. PERIPHERAL BLUR, ON THE FAR SIDE FROM THE CAMERA -------------------------------------
 * RETRACTION: an earlier build cut this as "inert -- three mechanisms, max abs delta 0.000". That
 * was true of the form tested (a full-viewport element with a RADIAL mask) and false as a general
 * claim about backdrop-filter, which is how it was written down. Re-tested as a strip with a LINEAR
 * mask: max delta 167.93, 16.3% of pixels moved on the treated side and exactly 0.00 on the other.
 * The mechanism was never the problem; the configuration was. Same error as concluding a glow does
 * not transfer to prose from a selector that matched no prose.
 * Opacity is driven from the cursor, so the blur sits opposite the camera: look right, the left edge
 * softens. */
html.wz-vfx .wz-soft-l, html.wz-vfx .wz-soft-r{
  position:fixed; top:0; height:100%; width:var(--wz-soft-w);
  z-index:2147481200; pointer-events:none;
  backdrop-filter:blur(var(--wz-soft-blur)); -webkit-backdrop-filter:blur(var(--wz-soft-blur));
  transition:opacity var(--wz-pan-ease) linear;
}
html.wz-vfx .wz-soft-l{ left:0;  opacity:var(--wz-sl,0);
  -webkit-mask-image:linear-gradient(to right,#000 0%,transparent 100%);
          mask-image:linear-gradient(to right,#000 0%,transparent 100%); }
html.wz-vfx .wz-soft-r{ right:0; opacity:var(--wz-sr,0);
  -webkit-mask-image:linear-gradient(to left,#000 0%,transparent 100%);
          mask-image:linear-gradient(to left,#000 0%,transparent 100%); }

/* --- MOTION IS NOT NEGOTIABLE. The pivot is vestibular, not photosensitive: reduced-motion KILLS
 * it rather than softening it. Vignette, bloom and softening carry no motion and may stay. */
@media (prefers-reduced-motion:reduce){
  html.wz-vfx .wz-stage{ transform:none !important; transition:none !important; }
  html.wz-vfx .wz-soft-l, html.wz-vfx .wz-soft-r{ opacity:0 !important; }
}


/* --- THE PHOSPHOR --------------------------------------------------------------------------------
 * What there is to zoom INTO. Invisible at 1x by construction -- opacity is driven from the zoom by
 * wuld-vfx.js and is literally 0 until you magnify -- and it resolves into an aperture grille and
 * scanlines as the triad pitch grows past what the eye can resolve.
 *
 * A FIXED OVERLAY, NOT ONE INSIDE THE STAGE, AND NO BLEND MODE. Both are the warm wash's lesson paid
 * forward: a blend layer cost half the frame rate, and a texture painted across a 25,000px scaled
 * document would be worse. Fixed means one compositor layer the size of the viewport however far in
 * you go. It is also the better fiction -- phosphor belongs to the glass, not to the image -- and
 * growing its pitch with zoom is what sells "getting closer to the screen" rather than "the picture
 * got bigger". */
html.wz-vfx .wz-grille{
  position:fixed; inset:0; pointer-events:none; z-index:2147481250;
  opacity:var(--wz-grille-a, 0);
  background-image:
    repeating-linear-gradient(90deg,
      rgba(255,86,86,.34) 0 33.34%, rgba(86,255,130,.30) 33.34% 66.67%, rgba(110,130,255,.34) 66.67% 100%),
    repeating-linear-gradient(0deg,
      rgba(0,0,0,.30) 0 42%, rgba(0,0,0,0) 42% 100%);
  background-size:
    calc(var(--wz-grille) * var(--wz-zoom, 1)) 100%,
    100% calc(var(--wz-grille) * var(--wz-zoom, 1));
  transition:opacity .18s linear;
}
/* Zoom is part of the effect, so it dies with the effect -- with the ground and with the OS signals,
 * exactly like the glow. */
html.wz-vfx.wz-lightbg .wz-grille{ display:none !important; }
@media (prefers-contrast: more), (forced-colors: active){
  html.wz-vfx .wz-grille{ display:none !important; }
}
@media print{ .wz-grille{ display:none !important; } }
@media (prefers-reduced-motion:reduce){ .wz-grille{ transition:none; } }

/* The magnifier button, beside the power button in the chin. Same 44px target, same focus ring.
 * The offset must clear the power button's FULL 44px, not most of it: at +42px the two hit areas
 * overlapped by 2px at every width tested, and overlapping tap targets mean the thumb lands on
 * whichever the stacking order favours rather than the one it aimed at. +50px leaves a 6px gap. */
.wz-mag{
  position:absolute; right:calc(max(1.0vw,6px) + 50px); bottom:50%; transform:translateY(50%);
  min-width:44px; min-height:44px;
  display:flex; align-items:center; justify-content:center;
  color:var(--wz-mark); opacity:.75; font-size:1.05em; line-height:1;
  pointer-events:auto; cursor:pointer; background:none; border:0; padding:0; border-radius:50%;
}
.wz-mag:hover{ opacity:1; }
.wz-mag:focus-visible{ outline:2px solid var(--wz-led); outline-offset:-6px; opacity:1; }
html.wz-mag-on .wz-mag{ opacity:1; color:var(--wz-led); }
