/* ==========================================================================
   effects.css - styling for the five signature effects.
   Loaded after style.css. Delete this file and effects.js and the site is
   the plain document again, intact.

   THE MOTION RULE, in one sentence: nothing here moves on a designer's
   easing curve. Every motion is a quantity changing at the rate its own
   physics sets - mass flow drifts at constant velocity, a transducer rings
   at its own damping ratio, a stepper does not ease. There is no
   cubic-bezier chosen for looks anywhere in this file.
   ========================================================================== */

/* --------------------------------------------------------------------------
   TOKEN BRIDGE. style.css owns the page palette; the effects were authored
   against a slightly different set of names. Map rather than duplicate, so
   there is still exactly one place where a colour is decided.
   -------------------------------------------------------------------------- */
:root {
  --surface:   var(--paper);
  --surface-2: var(--paper-2);
  --ink-2:     var(--ink-muted);
  --mono:      var(--font-meta);

  /* Categorical stream inks. Three hues plus a deliberately desaturated
     neutral for losses - that desaturation is an exergy statement, not a
     fourth series colour. Validated for colour-vision deficiency and
     contrast in both themes; every stream also carries a distinct dash
     signature, a text label and a number, so colour is never the only
     channel. */
  --s-elec: #2A78D6;   /* electric power     */
  --s-heat: #C2521F;   /* recovered heat     */
  --s-cool: #14806A;   /* absorption cooling */
  --s-loss: #6B7280;   /* losses             */

  /* Single-hue ramp, used wherever the quantity is a magnitude rather than
     an identity. No rainbow, no jet - using jet is the fastest way to look
     like a non-expert to a thermal-sciences professor. */
  --seq-0: #CDE2FB; --seq-1: #9EC5F4; --seq-2: #6DA7EC; --seq-3: #3987E5;
  --seq-4: #256ABF; --seq-5: #184F95; --seq-6: #0D366B;
}

/* Same two-block pattern as style.css, for the same reason: without the
   attribute copy, the scheme control would repaint the page but leave every
   canvas stream and every sequential ramp on the OS palette. THE TWO BLOCKS
   MUST STAY IDENTICAL. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --s-elec: #79BDD6;
    --s-heat: #E0885C;
    --s-cool: #4FBF9B;
    --s-loss: #99A3B0;

    --seq-0: #0D366B; --seq-1: #184F95; --seq-2: #256ABF; --seq-3: #3987E5;
    --seq-4: #6DA7EC; --seq-5: #9EC5F4; --seq-6: #CDE2FB;
  }
}
:root[data-theme="dark"] {
  --s-elec: #79BDD6;
  --s-heat: #E0885C;
  --s-cool: #4FBF9B;
  --s-loss: #99A3B0;

  --seq-0: #0D366B; --seq-1: #184F95; --seq-2: #256ABF; --seq-3: #3987E5;
  --seq-4: #6DA7EC; --seq-5: #9EC5F4; --seq-6: #CDE2FB;
}

/* The effect markup uses .sr-only; style.css calls the same thing .vh. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

/* Every value an effect displays also exists as permanent text or in one of
   these tables. No information is hover-gated anywhere on the site. */
.fx-table { margin-top: .75rem; font-size: .8rem; color: var(--ink-2); }
.fx-table summary {
  cursor: pointer; font-family: var(--mono);
  font-size: var(--t-mono-sm); letter-spacing: .04em;
  padding-block: .35rem;
}
.fx-table table {
  border-collapse: collapse; margin-top: .6rem;
  font-variant-numeric: tabular-nums; font-family: var(--mono);
  font-size: var(--t-mono-md);
}
.fx-table th, .fx-table td {
  border-bottom: 1px solid var(--rule);
  padding: .25rem .8rem .25rem 0; text-align: right;
}
.fx-table th:first-child, .fx-table td:first-child { text-align: left; }
.fx-table caption { text-align: left; padding-bottom: .5rem; }

/* WCAG 2.2.2 needs an on-page mechanism to stop motion. An OS preference
   is not one, so this button exists and is remembered in localStorage. */
.fx-toggle {
  font: 500 var(--t-mono-sm)/1 var(--mono); letter-spacing: .04em;
  padding: .5rem .7rem; color: var(--ink-muted); background: var(--paper-2);
  border: 1px solid var(--edge); border-radius: 2px; cursor: pointer;
  min-height: 2.25rem;
}
.fx-toggle[aria-pressed="true"] { color: var(--ink); border-color: var(--accent); }
.fx-toggle:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

@media print {
  .fx-toggle, .h2v__ctl { display: none; }
  .fx-table[open] table { display: table; }
}


/* ---- Fig. 1 · Where the hydrogen goes --------------------------------
   Replaces the .plant rules in effects.css. Colours come from tokens only;
   --mark is NOT used (style.css reserves it for the first-author chip) and
   --edge is never used for text (it is a 3.3:1 non-text boundary token).  */
.h2v { margin: 0 0 3.5rem; }

.h2v__eyebrow {
  margin: 0 0 .2rem;
  font: 500 var(--t-mono-sm)/1 var(--mono);
  letter-spacing: .08em; text-transform: uppercase; color: var(--ink-2);
}
.h2v__where {
  margin: 0 0 .55rem; min-height: 1.35em;      /* reserved: no reflow */
  font: 500 .8rem/1.35 var(--mono); color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}
.h2v__chain {
  margin: 0 0 1rem; display: flex; flex-wrap: wrap;
  align-items: baseline; gap: .1rem .45rem;
  font: 600 clamp(1.25rem, 2.9vw, 1.9rem)/1.15 var(--mono);
  font-variant-numeric: tabular-nums; color: var(--ink);
}
.h2v__chain b { display: inline-block; min-width: 3.4ch; text-align: right; }
.h2v__chain i { font-style: normal; font-size: .46em; font-weight: 500; color: var(--ink-2); }
.h2v__chain s { text-decoration: none; font-size: .5em; color: var(--ink-2); padding: 0 .15rem; }

/* The box owns the size, so nothing the model does can shift the page. */
.h2v__frame {
  position: relative; aspect-ratio: 720 / 430; width: 100%;
  background: var(--paper);
}
@media (max-width: 640px) { .h2v__frame { aspect-ratio: 620 / 720; } }

.h2v__cv, .h2v__static { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.h2v.is-live .h2v__static { display: none; }

/* the no-JS / print / forced-colours still: the finished summer week */
.h2v__static .st--prop { fill: var(--s-elec); fill-opacity: .62; }
.h2v__static .st--hot  { fill: var(--s-elec); fill-opacity: .22; }
.h2v__static .st--heat { fill: var(--s-heat); fill-opacity: .68; }
.h2v__static .st--loss { fill: var(--s-loss); fill-opacity: .20; }
.h2v__static .st--cold { fill: var(--s-cool); fill-opacity: .60; }
.h2v__static .st--kg   { fill: none; stroke: var(--accent); stroke-width: 1.8; }
.h2v__static .st--chp  { fill: none; stroke: var(--ink); stroke-width: 1.4; opacity: .78; }
.h2v__static .st--elec { fill: none; stroke: var(--s-elec); stroke-width: 1.4; }
.h2v__static .st--rule { fill: none; stroke: var(--rule-strong); stroke-width: 1; }
.h2v__static .st--peak { fill: none; stroke: var(--edge); stroke-width: 1; stroke-dasharray: 4 4; }

/* Labels are real HTML, never canvas text, and every position is written
   by the SAME layout object the canvas paints from — one source of truth. */
.h2v__frame [data-hr], .h2v__frame [data-mw], .h2v__frame [data-tn],
.h2v__frame [data-panel], .h2v__frame [data-cold] { position: absolute; }

.h2v__mw, .h2v__tn { list-style: none; margin: 0; padding: 0; }
.h2v__mw li, .h2v__tn li {
  position: absolute; left: 0; top: var(--y);
  transform: translateY(-50%);
  width: 5.6%; text-align: right;
  font: 500 var(--t-mono-xs)/1 var(--mono); color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}
.h2v__ports { list-style: none; margin: 0; padding: 0; position: absolute;
              top: var(--y); left: 0; right: 0; height: 1.2em; }
.h2v__ports li {
  position: absolute; left: var(--x); transform: translateX(-50%);
  white-space: nowrap;
  font: 500 var(--t-mono-xs)/1.2 var(--mono);
  letter-spacing: .02em; color: var(--ink-2);
}
.h2v__ports li:first-child { transform: none; }
.h2v__ports li:last-child  { transform: translateX(-100%); }

.h2v__tag {
  left: var(--x); top: var(--y);
  font: 500 var(--t-mono-xs)/1.25 var(--mono);
  letter-spacing: .03em; color: var(--ink-2); pointer-events: none;
}
.h2v__tag--peak  { transform: translate(-100%, -125%); padding-right: .4rem; }
.h2v__tag--cold  { transform: translate(.4rem, 0); }
.h2v__tag--kg    { transform: translate(.2rem, -110%); }
.h2v__tag--panel { width: var(--w); color: var(--ink-2); }

/* Key: five entries. Anything a sixth entry would say is in the caption
   or the data table instead. */
.h2v__key {
  list-style: none; margin: .85rem 0 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: .3rem 1.1rem;
  font: 500 var(--t-mono-xs)/1.5 var(--mono); color: var(--ink-2);
}
.h2v__key li { display: flex; align-items: center; gap: .4rem; }
.h2v__key i { width: 16px; height: 8px; border-radius: 1px; flex: none; }
.h2v__key .k--prop { background: var(--s-elec); opacity: .62; }
.h2v__key .k--hot  { background: var(--s-elec); opacity: .22; outline: 1px solid var(--rule); }
.h2v__key .k--heat { background: var(--s-heat); opacity: .68; }
.h2v__key .k--loss { background: var(--s-loss); opacity: .20; outline: 1px solid var(--rule); }
.h2v__key .k--cool { background: var(--s-cool); opacity: .60; }

.h2v__ctl {
  display: flex; align-items: center; flex-wrap: wrap; gap: .5rem 1.1rem;
  margin: 1rem 0 .35rem;
  font: 500 .74rem/1 var(--mono); letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-2);
}
.h2v__scrub { flex: 1 1 16rem; max-width: 26rem; accent-color: var(--accent); }
.h2v__scrub:focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; }
.h2v__sw { display: flex; align-items: center; gap: .4rem; cursor: pointer; }
.h2v__sw input { accent-color: var(--accent); min-width: 1rem; min-height: 1rem; }

.h2v__cap { margin: .6rem 0 0; max-width: 46rem;
            font-size: .84rem; line-height: 1.55; color: var(--ink-2); }
.h2v__cap b, .h2v__cap strong { color: var(--ink); }
.h2v__cap cite { font-style: normal; }
.h2v__cap .h2v__doi { font-family: var(--mono); font-size: .78rem; }

@media (max-width: 640px) {
  .h2v__chain { font-size: 1.15rem; gap: .05rem .3rem; }
  .h2v__key { gap: .25rem .8rem; }
}
@media print {
  .h2v__ctl { display: none; }
  .h2v .h2v__cv { display: none !important; }
  .h2v .h2v__static { display: block !important; }
}
@media (forced-colors: active) {
  .h2v .h2v__cv { display: none !important; }
  .h2v .h2v__static { display: block !important; }
  .h2v__static path { fill: none !important; stroke: CanvasText !important;
                      stroke-width: 1 !important; opacity: 1 !important; }
  .h2v__key i { background: CanvasText !important; opacity: 1 !important; }
}

/* NOTE: effects.css currently has `@media print { .fx-toggle, .plant__ctl
   { display:none } }`. Change .plant__ctl to .h2v__ctl there, or delete
   that half of the selector — the rule above already covers it. */

/* ---- Instrument Row --------------------------------------------------- */
.figs {
  list-style: none; margin: 0 0 3.5rem; padding: 0;
  display: grid; gap: 1.75rem 2.5rem;
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
}
.figs__num {
  margin: 0; display: flex; align-items: baseline; gap: .3rem;
  font: 600 clamp(1.9rem, 4.4vw, 2.9rem)/1 var(--mono);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.figs__val { display: inline-block; text-align: right; min-width: 3.4ch; }
.figs__unit { font-size: .46em; font-weight: 500; color: var(--ink-2); letter-spacing: .04em; }
.figs__trace { display: block; width: 7.5rem; height: 1.35rem; margin: .3rem 0 .45rem; overflow: visible; }
.figs__trace path { fill: none; stroke: var(--accent); stroke-width: 1.2;
                    vector-effect: non-scaling-stroke; stroke-linejoin: round; }
.figs__base { stroke: var(--rule); stroke-width: 1; vector-effect: non-scaling-stroke; }
.figs__lbl { margin: 0; max-width: 20rem; font-size: .78rem; line-height: 1.45; color: var(--ink-2); }
.figs__lbl span { display: block; color: var(--ink-3, var(--ink-2)); opacity: .82; }
@media (forced-colors: active) { .figs__trace path { stroke: CanvasText; } }

/* ---- Fig. 2 · Polarization -------------------------------------------- */
/* ---- Fig. 2 · ATLAS vs literature electrodes --------------------------- */
.ap { margin: var(--s-6) 0 var(--s-7); max-width: var(--page); }
.ap__svg { display: block; width: 100%; height: auto; overflow: visible; }

.ap__read {
  display: flex; flex-wrap: wrap; gap: .25rem .75rem; align-items: baseline;
  margin: 0 0 var(--s-3);
  font-family: var(--font-meta); font-size: var(--t-mono-md);
  font-variant-numeric: tabular-nums; color: var(--ink-muted);
}
.ap__read b { color: var(--ink); font-weight: 500; }

.ap__grid path { fill: none; stroke: var(--rule); stroke-width: 1;
                 vector-effect: non-scaling-stroke; }
.ap__axis path { fill: none; stroke: var(--rule-strong); stroke-width: 1;
                 vector-effect: non-scaling-stroke; }

/* Axis numerals are SVG text, so they are given a real size rather than a
   scaled one - they must stay legible when the figure is narrow. */
.ap__tick text, .ap__ax {
  font-family: var(--font-meta); font-size: 11px; fill: var(--ink-muted);
}
.ap__tick text { text-anchor: end; }
.ap__tick .ap__tx { text-anchor: middle; }
.ap__ax { font-size: 11.5px; }

/* The two curves. ATLAS is the accent because it is the thing being measured;
   literature is a neutral reference. Colour is never the only channel - the
   key below names both, and the data table carries every value. */
.ap__l { fill: none; stroke: var(--ink-muted); stroke-width: 2;
         stroke-dasharray: 6 4; vector-effect: non-scaling-stroke;
         stroke-linejoin: round; stroke-linecap: round; }
.ap__a { fill: none; stroke: var(--accent); stroke-width: 2.4;
         vector-effect: non-scaling-stroke; stroke-linejoin: round;
         stroke-linecap: round; }
.ap__band { fill: var(--accent); opacity: .10; stroke: none; }

.ap__key { display: flex; flex-wrap: wrap; gap: .35rem 1.25rem; margin: var(--s-3) 0 0;
           font-size: var(--t-mono-xs); text-transform: uppercase;
           letter-spacing: .09em; color: var(--ink-muted); }
.ap__k { display: inline-flex; align-items: center; gap: .45rem; }
.ap__k::before { content: ""; width: 1.4rem; height: 0; border-top-width: 2px;
                 border-top-style: solid; }
.ap__k--a::before { border-top-color: var(--accent); }
.ap__k--l::before { border-top-color: var(--ink-muted); border-top-style: dashed; }

.ap__cap { margin: var(--s-3) 0 0; max-width: var(--measure);
           font-size: var(--t-sm); line-height: 1.5; color: var(--ink-muted); }
.ap__cap strong { color: var(--ink); }

@media (forced-colors: active) {
  .ap__a, .ap__l { stroke: CanvasText; }
  .ap__band { fill: CanvasText; opacity: .15; }
}

/* ---- Fig. 3 · Catalyst sprayer ---------------------------------------- */
.spray { margin: 0 0 2.5rem; }
.spray__box { position: relative; aspect-ratio: 1 / 1; max-width: 26rem; }
.spray__static, .spray__cv { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }

.sp-rail { fill: var(--rule-strong); opacity: .5; }
.sp-gde  { fill: var(--surface-2); stroke: var(--rule-strong); stroke-width: 1; }
.sp-path { fill: none; stroke: var(--accent); stroke-width: .6; opacity: .55;
           clip-path: inset(11% 11% 11% 11%); }

.spray__hud {
  margin: .8rem 0 0; font: 500 .82rem/1.6 var(--mono);
  letter-spacing: .05em; color: var(--ink-2); font-variant-numeric: tabular-nums;
}
.spray__hud span { text-transform: uppercase; opacity: .7; margin-left: 1rem; }
.spray__hud span:first-child { margin-left: 0; }
.spray__hud b { color: var(--ink); font-weight: 600; }

.spray__again {
  margin-top: .6rem; padding: .35rem .7rem;
  font: 500 .75rem/1 var(--mono); letter-spacing: .04em;
  color: var(--ink-2); background: var(--surface-2);
  border: 1px solid var(--rule-strong); border-radius: 3px; cursor: pointer;
}
.spray__again:hover { color: var(--ink); }
.spray__again:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.spray__cap { margin: .7rem 0 0; max-width: 42rem;
  font-size: .82rem; line-height: 1.5; color: var(--ink-2); }
.spray__cap b { color: var(--ink); font-variant-numeric: tabular-nums; }

@media (forced-colors: active) {
  .spray__cv { display: none !important; }
  .spray__static { display: block !important; }
  .sp-gde { fill: Canvas; stroke: CanvasText; }
  .sp-path { stroke: CanvasText; opacity: 1; }
  .sp-rail { fill: CanvasText; }
}

/* ---- Bubble wire ------------------------------------------------------ */
.blwire { position: relative; height: 30px; margin: .25rem 0 .35rem; }
.blwire__wall {
  position: absolute; left: 0; right: 0; bottom: 0; margin: 0;
  border: 0; border-top: 1px solid var(--rule-strong);
}
.blwire__cv { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.blwire__cap {
  margin: 0 0 2rem; font: 500 .7rem/1.4 var(--mono);
  letter-spacing: .05em; color: var(--ink-2);
}
.blwire__cap .lam  { color: var(--accent); }
.blwire__cap .turb { color: var(--s-heat); }
@media (forced-colors: active) { .blwire__cv { display: none; } }

/* ---- Integration glue: placement of the figures inside the letter ------- */
.figs-wrap { padding-top: 0; }
.figs-wrap { padding-bottom: clamp(2rem, 1.5rem + 2vw, 3rem); }

/* Provenance lines. Where a number is modelled rather than measured, the page
   says so in place - never in a footnote a reader will not reach. */
.plant__model, .spray__model {
  display: block; margin-top: .45rem;
  font-family: var(--font-meta); font-size: var(--t-mono-xs);
  line-height: 1.5; letter-spacing: .02em; color: var(--ink-muted);
}

/* ---- Advocacy / press record ------------------------------------------- */
.recs li { padding-block: var(--s-3); border-top: 1px solid var(--rule); }
.recs li:first-child { border-top: 0; padding-top: 0; }
.rec-t { display: block; font-size: var(--t-sm); line-height: 1.5; }
.rec-t strong { font-weight: 600; }
.rec-t em { font-style: italic; }
.rec-l {
  display: inline-block; margin-top: .3rem;
  font-size: var(--t-mono-md); padding-block: .35rem;   /* 44px tap target */
  overflow-wrap: anywhere;
}

/* ---- Motion toggle in the footer --------------------------------------- */
.foot-motion { margin: 0 0 var(--s-4); }

/* CV row sub-label in the contact ledger */
.contact-sub { color: var(--ink-muted); font-size: var(--t-sm); }

/* ---- Pull quote in the press record ------------------------------------
   A quotation mark drawn as a mark, not typed, so no screen reader announces
   it and forced-colors survives. The rule is --accent because this is the one
   place on the page where someone else is speaking about him. */
.pull {
  position: relative; margin: var(--s-4) 0 var(--s-3);
  padding: 0 0 0 var(--s-5);
  border-left: 2px solid var(--accent);
  max-width: var(--measure);
}
.pull p {
  margin: 0 0 var(--s-2);
  font-size: var(--t-lede); line-height: 1.4; letter-spacing: -.008em;
  color: var(--ink); text-wrap: pretty;
}
.pull__by {
  font-size: var(--t-mono-xs); text-transform: uppercase; letter-spacing: .09em;
  color: var(--ink-muted);
}
.rec-after { margin-top: var(--s-3); color: var(--ink-muted); }
@media (forced-colors: active) { .pull { border-left-color: CanvasText; } }

/* ==========================================================================
   SECTION GROUNDS - seven sections, seven vibes.

   The problem this solves: the page was one paper colour end to end, divided
   only by hairlines. A reader skimming for "does he build things" had no
   landmark to aim at, and every section scanned the same.

   Each section now gets its own full-bleed ground, banded edge to edge so it
   reads as a distinct place at a glance, plus a texture drawn from its own
   subject. The tints are deliberately quiet - this is a letter to a professor,
   not a brochure - but they are far enough apart in hue to be told apart in
   peripheral vision while scrolling fast.

   CONTRAST - computed, not asserted. Every ground is near-white in light and
   near-black in dark, so body text never drops below AA. Across all six
   grounds plus the two base papers, in both themes:
     --ink        15.10 - 15.95 : 1  (light)   13.57 - 14.71 : 1  (dark)
     --ink-muted   6.14 -  6.64 : 1  (light)    6.97 -  7.56 : 1  (dark)
     --accent      7.96 -  8.61 : 1  (light)    8.01 -  8.68 : 1  (dark)
   Worst pair anywhere is --ink-muted on the reply band at 6.14:1, which is
   well clear of the 4.5:1 body-text threshold.
   ========================================================================== */

:root {
  --g-question: #EEF3F7;   /* hydrogen, electric   - cool blue   */
  --g-evidence: #FAF8F4;   /* the record           - base paper  */
  --g-built:    #F7F2E8;   /* the shop, heat       - warm sand   */
  --g-range:    #EFF4F0;   /* scale, breadth       - cool green  */
  --g-training: #F3F1F8;   /* training, procedure  - violet      */
  --g-outside:  #FBF1E8;   /* venture, energy      - terracotta  */
}
@media (prefers-color-scheme: dark) {
  :root {
    --g-question: #121A21;
    --g-evidence: #14161A;
    --g-built:    #1B1712;
    --g-range:    #121A16;
    --g-training: #17151F;
    --g-outside:  #1E1813;
  }
}

/* Full-bleed: the band escapes the centred letter column and runs the whole
   width of the viewport. html{overflow-x:clip} in style.css is the backstop
   that keeps 100vw from producing a horizontal scrollbar. */
.letter > section { position: relative; isolation: isolate; }
.letter > section::before {
  content: ""; position: absolute; z-index: -1; pointer-events: none;
  top: 0; bottom: 0; left: 50%; width: 100vw; margin-left: -50vw;
}

#question::before { background-color: var(--g-question); }
#evidence::before,
.pubs-wrap::before { background-color: var(--g-evidence); }
#built::before    { background-color: var(--g-built); }
#range::before    { background-color: var(--g-range); }
#training::before { background-color: var(--g-training); }
#outside::before  { background-color: var(--g-outside); }

.pubs-wrap { position: relative; isolation: isolate; }
.pubs-wrap::before {
  content: ""; position: absolute; z-index: -1; pointer-events: none;
  top: 0; bottom: 0; left: 50%; width: 100vw; margin-left: -50vw;
}

/* --- and a texture per section, on top of its ground ------------------- */
.letter > section::after {
  content: ""; position: absolute; z-index: -1; pointer-events: none;
  top: 0; bottom: 0; left: 50%; width: 100vw; margin-left: -50vw;
}

/* 01 hydrogen - a molecular lattice of paired atoms */
#question::after {
  background-image:
    radial-gradient(circle at 10px 12px, var(--rule-strong) 1.4px, transparent 1.5px),
    radial-gradient(circle at 22px 12px, var(--rule-strong) 1.4px, transparent 1.5px);
  background-size: 68px 54px; opacity: .30;
}
/* 03 the shop - engineering graph paper, fine grid over coarse */
#built::after {
  background-image:
    linear-gradient(to right,  var(--rule) 1px, transparent 1px),
    linear-gradient(to bottom, var(--rule) 1px, transparent 1px),
    linear-gradient(to right,  var(--rule-strong) 1px, transparent 1px),
    linear-gradient(to bottom, var(--rule-strong) 1px, transparent 1px);
  background-size: 16px 16px, 16px 16px, 80px 80px, 80px 80px; opacity: .30;
}
/* 04 range - a logarithmic axis, decade by decade */
#range::after {
  background-image: repeating-linear-gradient(to right,
    var(--rule-strong) 0 1px, transparent 1px 30px,
    var(--rule) 30px 31px, transparent 31px 48px,
    var(--rule) 48px 49px, transparent 49px 60px,
    var(--rule) 60px 61px, transparent 61px 69px,
    var(--rule) 69px 70px, transparent 70px 120px);
  opacity: .34;
}
/* 05 training - ticks marching down, like a qualification card */
#training::after {
  background-image: repeating-linear-gradient(to bottom,
    var(--rule) 0 1px, transparent 1px 34px);
  opacity: .34;
}
/* 06 outside the lab - perfboard, the Maker Room in one texture */
/* #outside::after: the perfboard is gone - the docket draws ruled paper of
   its own and the dots composited under it. */

/* A hairline where two grounds meet, so the band edge is deliberate. */
.letter > section + section::before { border-top: 1px solid var(--rule); }

/* The reply band already has its own recessed ground and is the only bare
   place on the page - the eye should land there with nothing competing. */

@media (forced-colors: active) {
  .letter > section::before, .letter > section::after,
  .pubs-wrap::before { display: none; }
}

/* ==========================================================================
   PHOTOGRAPHIC GROUNDS.

   Two sections get a real photograph behind them, chosen because each IS the
   subject of its section rather than decoration:

     02 The evidence  - MS Oasis of the Seas. This is the specific vessel
                        modelled in J. Marine Sci. Eng. 14(5) 431. The section
                        is about the papers; this is what the paper is about.
     05 Training      - Cherenkov radiation in a research reactor pool.

   Both are public-domain US federal photographs (Coast Guard; NRC). Credits
   are in assets/CREDITS.md. The reactor image is a GENERIC research reactor,
   not the facility where the training happens, and the page says so.

   HOW TEXT STAYS READABLE. The photo is not faded with opacity - opacity on a
   parent would drag the text with it, and a bare low-opacity photo still
   swings local contrast wherever the image is dark. Instead each ::before
   paints, in one layer stack: a near-opaque scrim in the section's own ground
   colour, over the photo, over the ground colour. The scrim is 93% in light
   and 91% in dark, so at most 7-9% of the photograph reaches the eye.

   Worst case, computed across both extremes an image can produce:

     light, black pixel   #E9E7E3 / #E2E0E7   ink 12.93-13.65   muted 5.39-5.69
     dark,  white pixel   #292B2F / #2C2A33   ink 11.47-11.52   muted 5.90-5.92

   The worst ratio anywhere under a photograph is 5.39:1 for muted text on the
   training ground - still clear of the 4.5:1 body-text threshold, and body
   text itself never drops below 11.47:1.
   ========================================================================== */

#evidence::before {
  background-image:
    linear-gradient(rgba(250, 248, 244, .93), rgba(250, 248, 244, .93)),
    url("assets/ship.jpg");
  background-size: cover, cover;
  background-position: center, center 42%;
  background-repeat: no-repeat, no-repeat;
}
#training::before {
  background-image:
    linear-gradient(rgba(243, 241, 248, .93), rgba(243, 241, 248, .93)),
    url("assets/cherenkov.jpg");
  background-size: cover, cover;
  background-position: center, center 30%;
  background-repeat: no-repeat, no-repeat;
}

@media (prefers-color-scheme: dark) {
  #evidence::before {
    background-image:
      linear-gradient(rgba(20, 22, 26, .91), rgba(20, 22, 26, .91)),
      url("assets/ship.jpg");
  }
  #training::before {
    background-image:
      linear-gradient(rgba(23, 21, 31, .91), rgba(23, 21, 31, .91)),
      url("assets/cherenkov.jpg");
  }
}

/* The publications list shares the evidence ground but stays photo-free: a
   citation is the one thing on this page that should sit on plain paper. */
.pubs-wrap::before { background-image: none; }

/* Data saver and forced colours: drop the photographs entirely. */
@media (prefers-reduced-data: reduce), (forced-colors: active) {
  #evidence::before, #training::before { background-image: none; }
}

/* ---- Background · BURNOUT MARGIN --------------------------------------
   Append to effects.css.  Adds no tokens of its own: the field is drawn from
   --seq-0..6 (magnitude), --accent (fluid and curve), --rule / --rule-strong
   (frame), --ink-muted (annotation) and --s-heat (burnout).  --mark keeps its
   single reserved use elsewhere on the site.
   ---------------------------------------------------------------------- */

/* The layer sits ABOVE the page content and BELOW the sticky bar, and each
   canvas is an element sized to one gutter.  It therefore never overlaps a
   glyph — that is geometry, not a promise — and because the canvases start at
   the bar's bottom edge, the bar's backdrop-filter never has an animating
   backdrop to re-blur. */
.bm {
  position: fixed; inset: 0; z-index: 5;
  pointer-events: none; overflow: hidden;
  contain: layout paint;
}
.bm__cv  { position: absolute; display: block; }
.bm__lab { position: absolute; top: 0; left: 0; width: 0; height: 0; }

/* Annotation is REAL DOM TEXT, never rasterised into the canvas, so it obeys
   browser minimum font size and text-only zoom.  12 px, full token opacity:
   --ink-muted on --paper is 6.64:1 light and 7.56:1 dark.  Every label carries
   an explicit pixel width plus overflow:hidden, so it is geometrically
   incapable of spilling out of its rail toward the prose. */
.bm-t {
  position: absolute; top: 0; left: 0;
  font: 400 var(--t-mono-sm)/1.5 var(--font-meta);
  letter-spacing: .02em;
  color: var(--ink-muted);
  white-space: pre;
  overflow: hidden;
  font-variant-numeric: tabular-nums;
}
/* --s-heat measures 4.38:1 on light paper, which FAILS AA for text.  So the
   burnout event is marked in type by WEIGHT and a glyph, never by hue: hot
   labels stay --ink at 15.94:1.  The hue lives on the canvas, where it is
   ornament and nobody has to read it. */
.bm-t.is-hot { color: var(--ink); font-weight: 600; }

/* ---- the no-JS / no-canvas plate --------------------------------------
   Two ribbons, pure CSS, present before a byte of JS parses.  The left one is
   the real wall-temperature profile of his boiling_demo case at 200 kW/m^2,
   quantised onto the --seq ramp: it climbs to the ONB station, NOTCHES BACK
   as nucleate boiling relieves the wall, then runs flat.  The right one is
   the flux axis with both burnout fluxes marked.  Discrete steps, not a
   gradient — a 7-step ramp cannot band or dither on a 6-bit laptop panel.
   JS replaces them with the live plate by stamping [data-bm] on <html>. */
.bm__ns::before, .bm__ns::after {
  content: ""; position: absolute;
  top: var(--bar-h); bottom: 0; width: 5px;
}
.bm__ns::before {
  left: 0;
  background: linear-gradient(to bottom,
    var(--seq-1)  0     8%,
    var(--seq-2)  8%   18%,
    var(--seq-3) 18%   27%,
    var(--seq-4) 27%   33%,
    var(--seq-3) 33% 34.5%,     /* ONB: boiling cools the wall */
    var(--seq-4) 34.5% 100%);
}
.bm__ns::after {
  right: 0;
  background: linear-gradient(to bottom,
    var(--rule)     0   60.4%,
    var(--s-heat) 60.4% 61.2%,  /* q"_CHF, his channel   319 kW/m^2 */
    var(--rule)   61.2% 84.3%,
    var(--s-heat) 84.3% 85.1%,  /* q"_max, pool         1.25 MW/m^2 */
    var(--rule)   85.1% 100%);
}
html[data-bm] .bm__ns { display: none; }

/* The field is decoration with a text-only equivalent (the fx-table below the
   letter), so in forced-colors and in print it simply gets out of the way. */
@media (forced-colors: active) { .bm { display: none !important; } }
@media print                   { .bm { display: none !important; } }


/* SECTIONBLOCK:reactor */
/* --------------------------------------------------------------------------
   08 · UNDER INSTRUCTION — the qualification sheet.

   The section's ground is five-cycle semi-log recorder paper, built out of
   CSS gradients at REAL log spacing (minor divisions at log10(2)...log10(9)),
   with tractor-feed sprocket margins punched down the wide-screen gutter the
   rest of the letter leaves empty, and a diagonal security print under
   everything. Nothing in this file animates: the section's only motion is the
   chart-recorder pen in the canvas, which is physics, on the fx.js loop.

   CONTRAST, both themes, measured against style.css's own token table:
     · every glyph of prose, every figure label and every annunciator legend
       sits on an OPAQUE plate of --paper. --ink 15.95:1, --ink-muted 6.64:1,
       --accent 8.61:1 light; 14.71 / 7.56 / 8.68 dark. AA everywhere.
     · the heading and the section chip sit directly on the ruled sheet. Worst
       case is a glyph crossing a hairline: --rule at 50 % over --paper-2 gives
       an effective ground of #DDD7CB light / #2B2E34 dark, and --ink on those
       is 11.8:1 and 10.7:1. Still AAA.
     · the paper ruling (1.72:1) and the security print (~1.3:1) are decorative
       and never underlie type. That is what makes them legal.
   -------------------------------------------------------------------------- */

.rx { position: relative; }

/* The letter's single margin hairline must run UNBROKEN across the sheet
   rather than vanishing behind it. One additive line; the rule sits in the
   letter's own padding, where no content ever is, so nothing else changes. */
.letter::before { z-index: 1; }

/* Content wrapper. One positioned parent instead of `.rx > *`, so the paint
   order of the three background layers can never depend on rule order. */
.rx__in { position: relative; }
.rx__in > .body { max-width: calc(var(--measure) + 2 * var(--s-5)); }
.rx__in > *:not(.sec-h) { margin-bottom: var(--s-5); }
.rx__in > *:last-child { margin-bottom: 0; }

/* ---- the sheet ---------------------------------------------------------- */
.rx__sheet, .rx__wm, .rx__feed {
  position: absolute;
  inset-block: 0;
  pointer-events: none;
  user-select: none;
  -webkit-user-select: none;
}
.rx__sheet {
  inset-inline: 0;
  background: var(--paper-2);
  border-block: 1px solid var(--rule-strong);
}
/* Five-cycle semi-log recorder paper. The minor line positions are the real
   log stops, not decoration; one decade is one background tile. Kept on its
   own layer at one opacity so that a browser which drops calc() inside a
   gradient loses the WHOLE declaration and lands on flat --paper-2 — a
   graceful floor, not a broken one. */
.rx__sheet::after {
  content: "";
  position: absolute;
  inset: 0;
  opacity: .5;
  --dec: 4.25rem;
  --div: 3.5rem;
  background-image:
    linear-gradient(to top,
      var(--rule-strong) 0 1px,
      transparent 1px 30.10%,                 var(--rule) 30.10% calc(30.10% + 1px),
      transparent calc(30.10% + 1px) 47.71%,  var(--rule) 47.71% calc(47.71% + 1px),
      transparent calc(47.71% + 1px) 60.21%,  var(--rule) 60.21% calc(60.21% + 1px),
      transparent calc(60.21% + 1px) 69.90%,  var(--rule) 69.90% calc(69.90% + 1px),
      transparent calc(69.90% + 1px) 77.82%,  var(--rule) 77.82% calc(77.82% + 1px),
      transparent calc(77.82% + 1px) 84.51%,  var(--rule) 84.51% calc(84.51% + 1px),
      transparent calc(84.51% + 1px) 90.31%,  var(--rule) 90.31% calc(90.31% + 1px),
      transparent calc(90.31% + 1px) 95.42%,  var(--rule) 95.42% calc(95.42% + 1px),
      transparent calc(95.42% + 1px) 100%),
    repeating-linear-gradient(to right, var(--rule) 0 1px, transparent 1px var(--div));
  background-size: 100% var(--dec), auto;
  background-position: left bottom, left top;
}

/* THE BLEED. Bounded negative insets, never 100vw: style.css calls
   html{overflow-x:clip} "backstop only — never load-bearing", and a backstop
   holding a layout together is not a backstop. .letter is 55.5rem wide, so at
   82rem there is 13.25rem of gutter per side; -6.5rem fits with 6.75rem to
   spare, and above 82rem there is only more room. Zero viewport units, zero
   overflow, with or without a scrollbar. Deliberately NOT the full gutter:
   the two-phase cooling background is entitled to the bigger gesture. */
.rx__feed {
  display: none;
  width: 1.9rem;
  border-inline: 1px solid var(--rule);
  background-image: radial-gradient(circle at 50% 50%, var(--paper) 0 3.5px, transparent 3.6px);
  background-size: 100% 1.25rem;
}
@media (min-width: 82rem) {
  .rx__sheet, .rx__wm { inset-inline: -6.5rem; }
  .rx__feed { display: block; }
  .rx__feed--l { left: -6.5rem; }
  .rx__feed--r { right: -6.5rem; }
}
@media (min-width: 100rem) {
  .rx__sheet, .rx__wm { inset-inline: -10rem; }
  .rx__feed--l { left: -10rem; }
  .rx__feed--r { right: -10rem; }
}

/* ---- the security print -------------------------------------------------
   The disclaimer as the paper stock, the way a real controlled training
   document is printed. It is the only disclaimer that survives a screenshot,
   because no crop of the chart can be taken without it, and it is plain DOM,
   so it survives with JavaScript off. Do not delete it. -------------------- */
.rx__wm { inset-inline: 0; overflow: hidden; }
.rx__wmr {
  position: absolute;
  left: 50%; top: 50%;
  width: 210%; height: 190%;
  transform: translate(-50%, -50%) rotate(-22deg);
  display: flex;
  flex-direction: column;
  justify-content: space-around;
  opacity: .38;
}
.rx__wmr i {
  display: block;
  white-space: nowrap;
  text-align: center;
  font: 500 var(--t-mono-sm)/1 var(--font-meta);
  font-style: normal;
  letter-spacing: .34em;
  text-transform: uppercase;
  color: var(--rule-strong);
}

/* ---- plates: opaque fields laid on the sheet ---------------------------- */
.rx-plate {
  background: var(--paper);
  border: 1px solid var(--rule);
  padding: var(--s-5);
}
.rx-plate > p:last-child { margin-bottom: 0; }

.rx__stamp {
  outline: 1px solid var(--edge);   /* a double rule that costs no layout */
  outline-offset: 3px;
  margin-top: var(--s-2);
}
.rx__stamp p {
  margin: 0;
  font: 500 var(--t-mono-md)/1.65 var(--font-meta);
  color: var(--ink);
  letter-spacing: .015em;
  max-width: 46rem;
}
.rx__stamp .rx__asof {
  margin-top: .7rem;
  font-size: var(--t-mono-sm);
  font-weight: 400;
  color: var(--ink-muted);
  letter-spacing: .06em;
  font-variant-numeric: tabular-nums;
  min-height: 1.5em;                /* the live day count cannot reflow */
}
@media (max-width: 30rem) { .rx__stamp .rx__asof { min-height: 3em; } }

/* ---- the annunciator ----------------------------------------------------
   Eight windows. Two lit, one pending, five dark — a formal enumeration of
   what he does NOT have, drawn at the same size and in the same typography as
   what he does. A dark window's legend keeps 6.64:1: an annunciator you
   cannot read is a picture of an annunciator, not an accessible one. -------- */
.rx-ann {
  list-style: none;
  display: grid;
  gap: 1px;
  grid-template-columns: repeat(2, 1fr);
  background: var(--rule-strong);
  border: 1px solid var(--rule-strong);
}
@media (min-width: 46rem) { .rx-ann { grid-template-columns: repeat(4, 1fr); } }
.rx-ann li {
  background: var(--paper);
  padding: .7rem .75rem;
  min-height: 4.4rem;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: .4rem;
  border-left: 3px solid transparent;
}
.rx-ann__lg {
  font: 500 var(--t-mono-xs)/1.35 var(--font-meta);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-muted);
}
.rx-ann__st {
  font: 400 var(--t-mono-xs)/1 var(--font-meta);
  letter-spacing: .07em;
  color: var(--ink-muted);
}
/* lit = field + bar + word, never a contrast drop */
.rx-ann li.is-lit { background: var(--paper-2); border-left-color: var(--accent); }
.rx-ann li.is-lit .rx-ann__lg { color: var(--ink); }
.rx-ann li.is-lit .rx-ann__st { color: var(--accent); }
.rx-ann li.is-pend { border-left-color: var(--edge); }

/* ---- the figure --------------------------------------------------------- */
.rx__step {
  margin: 0 0 .55rem;
  padding: .3rem 0 .3rem .6rem;
  border-left: 2px solid var(--accent);
  font-size: var(--t-mono-sm);
  letter-spacing: .07em;
  line-height: 1.45;
  color: var(--ink);
  min-height: 2.9em;                /* two lines reserved: no reflow, ever */
}
.rx__read {
  display: flex;
  flex-wrap: wrap;
  gap: .15rem 1.05rem;
  margin: 0 0 .7rem;
  font-size: var(--t-mono-sm);
  line-height: 1.8;
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
}
.rx__read b { color: var(--ink); font-weight: 500; }
.rx__frame {
  position: relative;
  aspect-ratio: 720 / 344;          /* owns the box: zero CLS, both branches */
}
@media (max-width: 46rem) { .rx__frame { aspect-ratio: 4 / 5; } }
.rx__cv, .rx__static { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.rx__cv[hidden], .rx__static[hidden] { display: none; }
.rx__again { margin-top: .8rem; }
.rx__fig figcaption {
  margin-top: .8rem;
  font-size: var(--t-sm);
  line-height: 1.55;
  color: var(--ink-muted);
}
.rx__fig figcaption strong { color: var(--ink); font-weight: 600; }

/* the static, no-JS chart. Same tokens as the canvas, so a theme change
   repaints it for free and there is nothing for FX.onTheme to do here. */
.rx-mi   { fill: none; stroke: var(--rule); stroke-width: 1; }
.rx-mj   { fill: none; stroke: var(--rule-strong); stroke-width: 1; }
.rx-fr   { fill: none; stroke: var(--rule-strong); stroke-width: 1; }
.rx-lim  { fill: none; stroke: var(--s-heat); stroke-width: 1; stroke-dasharray: 4 3; }
.rx-f    { fill: none; stroke: var(--s-elec); stroke-width: 1.7;
           stroke-linejoin: round; stroke-linecap: round; }
.rx-r    { fill: none; stroke: var(--s-heat); stroke-width: 1.1; }
.rx-fl   { fill: none; stroke: var(--s-cool); stroke-width: 1; }
.rx-fit  { fill: none; stroke: var(--accent); stroke-width: 1.3; }
.rx-dot  { fill: var(--s-elec); }
.rx-bank { fill: var(--accent); }
.rx-ax   { fill: var(--ink-muted); font: 400 11px var(--font-meta); }
.rx-flt  { fill: var(--s-cool); font: 400 10px var(--font-meta); }
.rx-ecp  { fill: var(--accent); font: 500 10px var(--font-meta); }

/* ---- the signature block ------------------------------------------------ */
.rx__sig {
  display: grid;
  gap: .25rem 1.5rem;
  grid-template-columns: 1fr;
}
@media (min-width: 40rem) { .rx__sig { grid-template-columns: 1fr 13rem; } }
.rx__rule { border-bottom: 1px solid var(--ink-muted); height: 2.1rem; }
.rx__siglb {
  font: 400 var(--t-mono-xs) var(--font-meta);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-muted);
}
.rx__signote {
  grid-column: 1 / -1;
  margin: .85rem 0 0;
  font-size: var(--t-mono-sm);
  font-weight: 500;
  letter-spacing: .06em;
  color: var(--ink);
}

.rx__foot {
  font-size: var(--t-sm);
  line-height: 1.55;
  color: var(--ink-muted);
  margin-bottom: 0;
}

/* ---- print, and forced colours ------------------------------------------
   The ruling and the sprocket margins go; the SECURITY PRINT stays, because
   a printed copy of this page is exactly the artefact that most needs it. */
@media print {
  .rx__sheet::after, .rx__feed { display: none; }
  .rx__sheet { border-block-color: #999; }
  .rx-plate { border-color: #999; }
}
@media (forced-colors: active) {
  .rx__sheet::after, .rx__feed { display: none; }
  .rx-plate, .rx-ann, .rx-ann li { border-color: CanvasText; }
  .rx-ann li.is-lit { background: Highlight; }
  .rx-ann li.is-lit .rx-ann__lg, .rx-ann li.is-lit .rx-ann__st { color: HighlightText; }
  .rx__wmr i { color: GrayText; }
}


/* SECTIONBLOCK:cook-cycle */
/* ---- Fig. 4 · The cook cycle -------------------------------------------
   The section is its own stratum, and it gets there without touching the
   letter's structure: it stays a child of .letter, so .letter::before -
   the site's one unbroken margin rule - is never segmented. The band runs
   from that rule to the letter's outer border edge by cancelling exactly
   the padding it sits inside, so it can never overflow behind a scrollbar
   and needs no 100vw and no margin-inline: calc(50% - 50vw).

   Text readability is solved structurally, not by tuning opacity: the
   prose sits on flat --paper-2 (--ink is 15.35:1 there), and the 24px
   chart-paper grid is confined to the instrument plate, where the only
   text is mono labelling that carries its own opaque --paper chip. There
   is no frame in which a glyph sits on a gradient.
   ------------------------------------------------------------------------ */
.cook {
  position: relative;
  max-width: calc(var(--page) + 1.25rem + var(--pad-x));
  margin-left: -1.25rem;
  margin-right: calc(-1 * var(--pad-x));
  padding-left: 1.25rem;
  padding-right: var(--pad-x);
  background: var(--paper-2);
  border-block: 1px solid var(--rule-strong);
}
/* the band's own top rule does the job .sec-h's border was doing */
.cook .sec-h { border-top: 0; padding-top: 0; }
.cook__fig { margin: var(--s-6) 0 0; }

/* ---- the instrument plate ---------------------------------------------
   Chart paper is static. Paper does not move; only the trace moves across
   it - so this is a CSS background and never a per-frame paint. It exists
   with JS off, with canvas unsupported, and in print. */
.cook__plate {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  border: 1px solid var(--edge);
  background:
    repeating-linear-gradient(0deg,  transparent 0 23px, var(--rule) 23px 24px),
    repeating-linear-gradient(90deg, transparent 0 23px, var(--rule) 23px 24px),
    var(--paper);
}
.cook__mech, .cook__chart { position: relative; }
.cook__mech  { height: 10rem; border-bottom: 1px solid var(--rule); }
.cook__chart { height: 13rem; }
@media (min-width: 46rem) {
  .cook__plate { grid-template-columns: 15rem minmax(0, 1fr); }
  .cook__mech  { height: 13rem; border-bottom: 0; border-right: 1px solid var(--rule); }
}
/* Fixed heights in rem, and both layers absolutely positioned inside them,
   so the box is identical before the script runs, after it runs, if it
   never runs and if it throws. There is no layout shift to measure. */
.cook__skel, .cook__ccv, .cook__mcv {
  position: absolute; inset: 0; width: 100%; height: 100%; display: block;
}
.cook__skel { z-index: 0; }
.cook__ccv, .cook__mcv { z-index: 1; }
[data-cook][data-mode] .cook__skel { display: none; }   /* JS has taken over */

/* the static skeleton: the same figure, minus the clock */
.cook__k-plate { fill: var(--paper); stroke: var(--edge); stroke-width: 1; }
.cook__k-heat  { fill: var(--s-heat); }
.cook__k-el, .cook__k-arm { fill: none; stroke-width: 2; vector-effect: non-scaling-stroke; }
.cook__k-el   { stroke: var(--s-heat); }
.cook__k-arm  { stroke: var(--s-elec); }
.cook__k-piv  { fill: var(--s-elec); }
.cook__k-noz  { fill: var(--s-cool); }
.cook__k-bat  { fill: var(--s-loss); fill-opacity: .3; stroke: var(--edge); }
.cook__k-rail, .cook__k-tray {
  fill: none; stroke: var(--rule); stroke-width: 1; vector-effect: non-scaling-stroke;
}
.cook__k-sp, .cook__k-th, .cook__k-u, .cook__k-dp, .cook__k-fo {
  fill: none; vector-effect: non-scaling-stroke;
}
.cook__k-sp { stroke: var(--ink-muted); stroke-width: 1; stroke-dasharray: 4 3; }
.cook__k-th { stroke: var(--s-heat); stroke-width: 1.5; }
.cook__k-u  { stroke: var(--accent);  stroke-width: 1.25; }
.cook__k-dp { stroke: var(--s-cool);  stroke-width: 1.25; }
.cook__k-fo { stroke: var(--s-elec);  stroke-width: 1.25; }

/* ---- labels are DOM, never canvas text: they answer text-only zoom, and
   each carries an opaque chip so its contrast is exact rather than
   "probably fine over a hairline". --ink-muted on --paper is 6.64:1. */
.cook__lab {
  position: absolute; z-index: 2; pointer-events: none;
  padding: 0 .25rem; background: var(--paper);
  font-family: var(--font-meta); font-size: var(--t-mono-xs); line-height: 1.1;
  letter-spacing: .09em; text-transform: uppercase; color: var(--ink-muted);
}
/* chart: fractions match Y() and LANE() in the script exactly */
.cook__lab--t1   { left: .3rem; top: 19.58%; transform: translateY(-50%); }
.cook__lab--t0   { left: .3rem; top: 55%;    transform: translateY(-50%); }
.cook__lab--heat { left: .3rem; top: 63.64%; transform: translateY(-100%); }
.cook__lab--disp { left: .3rem; top: 77.27%; transform: translateY(-100%); }
.cook__lab--fold { left: .3rem; top: 90.91%; transform: translateY(-100%); }
/* machine */
.cook__lab--a { left: .3rem;  top: .35rem; }
.cook__lab--b { right: .3rem; top: 34%; }
.cook__lab--c { left: .3rem;  top: 80%; }

/* ---- the state ladder --------------------------------------------------
   The active cell is the one thing the eye tracks, so it is never signalled
   by colour alone: it gains a marker as well as the fill. --accent-ink on
   --accent is 8.61:1 light / 9.01:1 dark. */
.cook__ladder {
  list-style: none; display: grid; gap: 1px;
  grid-template-columns: repeat(auto-fit, minmax(6.75rem, 1fr));
  margin: var(--s-4) 0 0;
}
.cook__ladder li {
  background: var(--paper); border: 1px solid var(--rule);
  padding: .4rem .5rem;
  font-family: var(--font-meta); font-size: var(--t-mono-xs);
  letter-spacing: .06em; text-transform: uppercase; color: var(--ink);
}
.cook__ladder b    { display: block; font-weight: 500; }
.cook__ladder span { color: var(--ink-muted); font-variant-numeric: tabular-nums; }
.cook__ladder li[data-on="1"] {
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink);
}
.cook__ladder li[data-on="1"] span { color: var(--accent-ink); }
.cook__ladder li[data-on="1"] b::before { content: "\25B8\00A0"; }

/* ---- readouts ---------------------------------------------------------- */
.cook__hud {
  display: flex; flex-wrap: wrap; gap: .3rem 1.15rem;
  margin: var(--s-4) 0 0;
  font-family: var(--font-meta); font-size: var(--t-mono-xs);
  letter-spacing: .06em; text-transform: uppercase; color: var(--ink-muted);
}
.cook__hud b { color: var(--ink); font-weight: 500; font-variant-numeric: tabular-nums; }

/* ---- the one control. A handle on a physical constant is not an easing
   curve: it changes how much thermal load the batter puts on the surface,
   and everything downstream re-times itself. -------------------------- */
/* main.js stamps .js on <html>. The control is opted into rather than
   hidden later, so an inert range input never renders with JS off and the
   box is decided at first paint - no shift when the section arms. */
.cook__ctl { display: none; }
.js .cook__ctl {
  display: flex; align-items: center; flex-wrap: wrap; gap: .75rem;
  margin: var(--s-5) 0 .35rem;
  font: 500 var(--t-mono-sm)/1 var(--font-meta); letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-muted);
}
.cook__slider { flex: 0 1 18rem; accent-color: var(--accent); }
.cook__slider:focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; }
.cook__val { color: var(--ink); font-variant-numeric: tabular-nums; min-width: 3ch; }
.cook__val b { font-weight: 500; }
.cook__probe {
  margin: .35rem 0 0; max-width: var(--measure);
  font-family: var(--font-meta); font-size: var(--t-mono-xs);
  line-height: 1.5; letter-spacing: .02em; color: var(--ink-muted);
}

/* ---- caption and provenance -------------------------------------------- */
.cook__cap {
  margin: .6rem 0 0; max-width: var(--measure);
  font-size: var(--t-sm); line-height: 1.5; color: var(--ink-muted);
}
.cook__cap b { color: var(--ink); font-variant-numeric: tabular-nums; }
/* With JS off the chart shows one complete cycle, so the still caption is
   the DEFAULT and the live one is opted into by the script. */
.cook__cap--live { display: none; }
[data-cook][data-mode="live"] .cook__cap--live  { display: inline; }
[data-cook][data-mode="live"] .cook__cap--still { display: none; }
/* Provenance in place, in mono, never in a footnote nobody reaches -
   the same mechanism as .plant__model and .spray__model. */
.cook__model {
  display: block; margin-top: .45rem;
  font-family: var(--font-meta); font-size: var(--t-mono-xs);
  line-height: 1.5; letter-spacing: .02em; color: var(--ink-muted);
}

@media (forced-colors: active) {
  .cook__ccv, .cook__mcv { display: none !important; }
  .cook__skel { display: block !important; }
  .cook__k-sp, .cook__k-th, .cook__k-u, .cook__k-dp, .cook__k-fo,
  .cook__k-el, .cook__k-arm, .cook__k-rail, .cook__k-tray { stroke: CanvasText; }
  .cook__k-plate, .cook__k-bat, .cook__k-noz { fill: Canvas; stroke: CanvasText; }
  .cook__k-heat, .cook__k-piv { fill: CanvasText; }
  .cook__ladder li[data-on="1"] { background: Highlight; color: HighlightText; }
}

@media print {
  .cook__ctl, .cook__hud { display: none; }
  .cook { border-block-color: var(--rule-strong); }
}


/* SECTIONBLOCK:webreg */
/* ==================================================================== *
 * 08 - The forty-year-old machine.                    section + listing
 * -------------------------------------------------------------------- *
 * GROUND.  The band is the recessed --paper-2 the site already uses for
 * .reply, torn off top and bottom with a dashed rule, and it reaches
 * into the gutter by at most 3rem - bounded so it can never exceed the
 * viewport, never needs overflow-x:clip to be load-bearing, and never
 * has to split .letter's single unbroken margin rule.  Every sentence
 * in the band sits on --paper-2, a ground the site already ships as AA.
 *
 * STOCK.  The listing sits on --paper, so listing text is on exactly
 * the ground the rest of the site sets text on.  The greenbar bar is a
 * 1.12:1 tint OF THAT GROUND in both themes (computed against the
 * palette's --paper: #FAF8F4 light, #14161A dark).  A tint, not a
 * boundary: any text that passes AA on --paper passes within 12% of
 * the same ratio on the bar, so no character's legibility depends on
 * which band it happened to land in.  Listing text is --ink, the
 * site's maximum-contrast pair, so the headroom is large.
 *
 * No transition and no animation property appears anywhere below.  The
 * still state has exactly one source - the JS still() path - so it
 * cannot fall out of sync with a parallel CSS rule.
 * ==================================================================== */

.wr {
  /* reaches into the gutter, but max(0px, ...) means it can never
     overflow, and -1rem leaves slack for a classic overlay scrollbar */
  --wr-bleed: max(0px, min(3rem, calc((100vw - 100%) / 2 - 1rem)));
  --gb-bar:  #E8EFE1;      /* 1.12:1 on #FAF8F4 */
  --gb-mar:  1.25em;       /* sprocket margin, both sides */
  --gb-lh:   1.5em;        /* 6 lines to the inch */
  --gb-rows: 22;           /* line spaces the platen shows == VIEW in JS */
  position: relative;
  margin-inline: calc(-1 * var(--wr-bleed));
  padding: clamp(2rem, 1.25rem + 3vw, 3.25rem) var(--wr-bleed);
  background: var(--paper-2);
  border-block: 1px dashed var(--rule-strong);   /* the tear-off */
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .wr { --gb-bar: #1C221A; } /* 1.12:1 on #14161A */
}
:root[data-theme="dark"] .wr { --gb-bar: #1C221A; }

.wr > p,
.wr > h2,
.wr > .wr__out,
.wr > .wr__model { max-width: var(--measure, 34rem); }

/* ---- the punched card, used as the section heading device ---------- */
.wr__card { display: block; width: 100%; max-width: 32rem; height: auto;
            margin: 0 0 1.5rem; }
.wr__card-body  { fill: var(--paper); stroke: var(--rule-strong); stroke-width: 2; }
.wr__card-punch { fill: var(--ink); }
.wr__card-txt   { fill: var(--ink-muted);
                  font-family: var(--mono, ui-monospace, monospace);
                  font-size: 15px; }
@media (max-width: 34rem) {
  /* below this the interpretation would render under 6px: crop to the
     punched columns, which is still unmistakably a card */
  .wr__card-txt { display: none; }
}

/* ---- the finals link, given standing rather than buried ------------ */
.wr__out {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .25rem .75rem;
  margin: 1.75rem 0; padding: .8rem 1rem;
  border: 1px solid var(--rule-strong); background: var(--paper);
}
.wr__tag  { font-family: var(--mono, ui-monospace, monospace);
            font-size: .72rem; letter-spacing: .12em; color: var(--ink-muted); }
.wr__out a { color: var(--accent); text-underline-offset: .18em; }
.wr__note { font-family: var(--mono, ui-monospace, monospace);
            font-size: .72rem; color: var(--ink-muted); }

/* ---- the printer --------------------------------------------------- */
.gb { margin: 1.75rem 0 0; container-type: inline-size; }

.gb__platen {
  position: relative;
  height: calc(var(--gb-rows) * var(--gb-lh));
  overflow: auto;                       /* no JS: the listing scrolls  */
  contain: paint;
  border: 1px solid var(--rule-strong);
  background: var(--paper);
  color: var(--ink);
  font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: .82rem;                    /* fallback if cqw unsupported */
  font-size: min(.9rem, 3.7cqw);        /* 40 columns always fit, to 320px */
  line-height: 1.5;                     /* == --gb-lh */
}
.gb.is-live .gb__platen { overflow: hidden; }

/* one declaration, so the no-JS listing and the printed paper are
   literally the same stock and can never diverge */
.gb__src,
.gb__paper {
  margin: 0;
  padding-inline: var(--gb-mar);
  background-image:
    /* left sprocket column, half-inch pitch = every 3 line spaces */
    radial-gradient(circle at 50% 50%,
      transparent 0 .17em, var(--rule-strong) .17em .23em, transparent .23em),
    /* right sprocket column */
    radial-gradient(circle at 50% 50%,
      transparent 0 .17em, var(--rule-strong) .17em .23em, transparent .23em),
    /* greenbar: 3 lines on, 3 lines off */
    linear-gradient(to bottom, var(--gb-bar) 0 50%, transparent 50% 100%);
  background-size:
    var(--gb-mar) calc(var(--gb-lh) * 3),
    var(--gb-mar) calc(var(--gb-lh) * 3),
    100% calc(var(--gb-lh) * 6);
  background-position: left top, right top, left top;
  background-repeat: repeat-y, repeat-y, repeat;
}
.gb__src { white-space: pre; font: inherit; color: inherit; }

.gb__paper { position: absolute; top: 0; left: 0; right: 0;
             transform: translateY(0); }
.gb__paper[hidden] { display: none; }

/* the carriage-control channel is shown, dimmed, not hidden: a reader
   can watch column 1 driving the paper */
.gb__paper::before {
  content: ""; position: absolute; top: 0; bottom: 0;
  left: calc(var(--gb-mar) + 1.5ch); width: 1px;
  background: var(--rule);
}

.gb__ln { position: relative; display: flex; white-space: pre;
          visibility: hidden; }
.gb__ln.is-on { visibility: visible; }
.gb__cc { flex: 0 0 1ch; color: var(--ink-muted); }
.gb__tx { margin-left: 2ch; color: var(--ink); }

/* the fold, on the last line space of each form.  Absolute, so the
   perforation costs the line grid nothing - a border here would push
   every following line out of register. */
.gb__ln:nth-child(66n + 66)::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
  background: repeating-linear-gradient(to right,
    var(--rule-strong) 0 3px, transparent 3px 7px);
}

/* with JS on the <pre> stays in the DOM as the screen-reader copy */
.gb.is-live .gb__src {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
  background: none;
}

.gb__hud {
  display: flex; flex-wrap: wrap; gap: .2rem 1.25rem; margin: .5rem 0 0;
  font-family: var(--mono, ui-monospace, monospace);
  font-size: .72rem; letter-spacing: .06em; color: var(--ink-muted);
}
.gb__hud b { font-weight: 500; color: var(--ink);
             font-variant-numeric: tabular-nums; }

.gb__again {
  margin: .75rem 0 0; padding: .5rem .8rem; min-height: 2.25rem;
  font: 500 .72rem/1 var(--mono, ui-monospace, monospace);
  letter-spacing: .06em;
  color: var(--ink-muted); background: none;
  border: 1px solid var(--edge); border-radius: 2px; cursor: pointer;
}
.gb__again:hover { color: var(--ink); border-color: var(--rule-strong); }
.gb__again:focus-visible { outline: 2px solid var(--focus, var(--accent));
                           outline-offset: 2px; }

.gb figcaption { margin-top: 1rem; max-width: var(--measure, 34rem); }
.wr__model { margin-top: .75rem;
  font-family: var(--mono, ui-monospace, monospace);
  font-size: .74rem; line-height: 1.6; color: var(--ink-muted); }

@media (forced-colors: active) {
  /* greenbar, sprockets and fold carry no information, so they drop
     out; the listing is text, and text is what survives */
  .gb__src, .gb__paper { background-image: none; }
  .gb__ln:nth-child(66n + 66)::after,
  .gb__paper::before { background: CanvasText; }
  .wr, .gb__platen, .wr__out { border-color: CanvasText; }
  .wr__card-body  { fill: Canvas; stroke: CanvasText; }
  .wr__card-punch { fill: CanvasText; }
  .wr__card-txt   { fill: CanvasText; }
}

@media print {
  .gb__platen { height: auto; overflow: visible; }
  .gb__paper, .gb__again { display: none; }
  .gb.is-live .gb__src { position: static; width: auto; height: auto;
    margin: 0; clip-path: none; white-space: pre; }
}

/* ==========================================================================
   GROUNDS FOR THE THREE NEW SECTIONS.
   Same system as the others: a full-bleed band in a hue drawn from the
   section's own subject, plus a texture. Contrast verified alongside the
   original six - all pairs clear AA in both themes.
   ========================================================================== */
:root {
  --g-reactor: #ECF2F5;   /* reactor pool water   - cold steel cyan */
  --g-cook:    #FAF0E6;   /* a hot griddle        - amber           */
  --g-webreg:  #EDF4EC;   /* greenbar fanfold     - printer paper    */
}
@media (prefers-color-scheme: dark) {
  :root {
    --g-reactor: #101A1E;
    --g-cook:    #1E1710;
    --g-webreg:  #121A14;
  }
}

#reactor::before   { background-color: var(--g-reactor); }
#cook-cycle::before{ background-color: var(--g-cook); }
#webreg::before    { background-color: var(--g-webreg); }

/* 06 the reactor - a control-rod lattice, square-pitch like a core map */
#reactor::after {
  background-image:
    linear-gradient(to right,  var(--rule-strong) 1px, transparent 1px),
    linear-gradient(to bottom, var(--rule-strong) 1px, transparent 1px);
  background-size: 34px 34px;
  opacity: .22;
}
/* 07 the cook cycle - isotherms spreading from a heated plate */
#cook-cycle::after {
  background-image: repeating-radial-gradient(circle at 22% 62%,
    transparent 0 26px, var(--rule-strong) 26px 27px);
  opacity: .20;
}
/* 08 the forty-year-old machine - greenbar. The real thing: alternating
   bands every six lines, which is exactly what 6 lines-per-inch fanfold did. */
#webreg::after {
  background-image: repeating-linear-gradient(to bottom,
    color-mix(in srgb, var(--s-cool) 14%, transparent) 0 66px,
    transparent 66px 132px);
  opacity: .55;
}
@supports not (background-image: repeating-linear-gradient(to bottom, color-mix(in srgb, red 10%, transparent) 0 1px, transparent 1px 2px)) {
  #webreg::after {
    background-image: repeating-linear-gradient(to bottom,
      var(--rule) 0 66px, transparent 66px 132px);
    opacity: .30;
  }
}


/* R3CSS:vanguard */
/* ---- Fig. 5 · The split (FCV Vanguard) -------------------------------- */
/* Nothing in this block has a transition or an animation. The figure's only
   motion is the pen advancing at a constant rate, and that lives in the
   canvas, so there is nothing here for prefers-reduced-motion to switch off.
   Every colour comes from a token; every fill the canvas paints is
   pre-blended against --vg-ground in JS so its contrast is a computed
   number rather than an assertion, and so an append-only repaint of an
   overlapping column is idempotent instead of double-composited. */

.vg-wrap { padding-top: clamp(1.5rem, 1rem + 2vw, 2.5rem); position: relative; isolation: isolate; }
/* Same ground as the publications list it follows - the record and its one
   expansion sit on the same paper. Mirrors .pubs-wrap::before exactly and
   changes no existing selector. */
.vg-wrap::before {
  content: ""; position: absolute; z-index: -1; pointer-events: none;
  top: 0; bottom: 0; left: 50%; width: 100vw; margin-left: -50vw;
  background-color: var(--g-evidence, var(--paper));
}

.vg {
  margin: var(--s-5) 0 var(--s-6);
  max-width: var(--page);
  /* The ground the canvas blends against. The figure lives in section 02,
     whose ground is --g-evidence; move the figure and change this one line. */
  --vg-ground: var(--g-evidence, var(--paper));
}

/* ---- live readout. Fixed slots, so no digit ever reflows the row ------ */
.vg__read {
  margin: 0 0 .7rem;
  font: 500 .82rem/1.75 var(--mono);
  letter-spacing: .05em; color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}
.vg__read span { text-transform: uppercase; opacity: .75; margin-left: 1.1rem; }
.vg__read span:first-child { margin-left: 0; }
.vg__read b {
  color: var(--ink); font-weight: 600;
  display: inline-block; min-width: 2.6ch; text-align: right;
}
.vg__read b[data-vg="bt"]  { min-width: 3.4ch; }
.vg__read b[data-vg="soc"],
.vg__read b[data-vg="eta"] { min-width: 4.2ch; }

/* ---- layout. 640fr : 300fr, so the two columns keep the ratio the
   viewBoxes were drawn for and neither panel can shift the other. Every
   stage carries an aspect-ratio, so its box is reserved before a byte of
   script runs and upgrading from baked SVG to canvas moves nothing. ---- */
.vg__grid { display: grid; gap: 1rem; grid-template-columns: minmax(0, 1fr); }
.vg__col  { display: grid; gap: 1rem; align-content: start; }
@media (min-width: 46rem) {
  .vg__grid { grid-template-columns: minmax(0, 640fr) minmax(0, 300fr); gap: 1.25rem; align-items: start; }
}

.vg__stage { position: relative; }
.vg__stage--tape { aspect-ratio: 640 / 360; }
.vg__stage--zoom { aspect-ratio: 640 / 170; }
.vg__stage--map  { aspect-ratio: 300 / 350; }
@media (max-width: 46rem) {
  .vg__stage--map { max-width: 20rem; margin-inline: auto; }
}

.vg__cv, .vg__frame {
  position: absolute; inset: 0; width: 100%; height: 100%; display: block;
}
/* Explicit, and deliberately not the looser pattern elsewhere in this file:
   a bare `display: block` on the canvas would defeat the [hidden] attribute
   the script uses to hold it back until it can draw. */
.vg__cv[hidden] { display: none; }
.vg__frame { pointer-events: none; }
/* `hidden` is an HTML attribute and does not style SVG elements on its own. */
.vg__baked[hidden] { display: none; }

/* ---- the frame: hairlines, axes, guard rails ------------------------- */
.vg__grid-l path { fill: none; stroke: var(--rule); stroke-width: 1; vector-effect: non-scaling-stroke; }
.vg__axis  path  { fill: none; stroke: var(--rule-strong); stroke-width: 1; vector-effect: non-scaling-stroke; }
.vg__axis  .vg__zero { stroke: var(--edge); stroke-width: 1.25; }   /* 3.32:1 - SC 1.4.11 */
.vg__rail  path  { fill: none; stroke: var(--edge); stroke-width: 1;
                   stroke-dasharray: 5 4; vector-effect: non-scaling-stroke; }
.vg__seg   path  { fill: none; stroke: var(--rule-strong); stroke-width: 1; vector-effect: non-scaling-stroke; }

/* Axis text is SVG, so it is sized for the pixels it will actually occupy.
   The tape is 640 units wide in a ~569 px column at the 53.5rem page, so
   12 px renders at ~10.7 CSS px; the map is 300 units in ~267 px, the same.
   Stacked below 46rem the tape is ~328 px wide, a scale of 0.51, so it takes
   21 px to render the same ~10.8 CSS px - the bump is UP, not down. */
.vg__t {
  font-family: var(--mono); font-size: 12px; font-weight: 500;
  letter-spacing: .06em; fill: var(--ink-muted);
}
.vg__ax, .vg__seg-l { text-transform: uppercase; }
.vg__ax { letter-spacing: .09em; }
@media (max-width: 46rem) {
  .vg__stage--tape .vg__t, .vg__stage--zoom .vg__t { font-size: 21px; }
  .vg__stage--map  .vg__t { font-size: 11px; }
  /* the minor ticks and the narrow regime labels would collide at that size */
  .vg__stage--tape .vg-minor, .vg__stage--zoom .vg-minor { display: none; }
}

/* ---- the baked traces: the whole figure, with JavaScript off ----------
   Generated by vgRegen() and pasted in - never hand-edited. Each band is
   bounded by a full-opacity stroke, which is the mark that carries the
   meaning; the fill inside it is decorative and has no contrast duty.
   Light / dark measured: --s-loss 4.56 / 7.09, --s-elec 4.16 / 8.68,
   --s-cool 4.58 / 7.99, --ink 15.95 / 14.71 against --paper. */
.vg-dem { fill: var(--s-loss); fill-opacity: .20; stroke: var(--s-loss);
          stroke-width: 1.2; vector-effect: non-scaling-stroke; stroke-linejoin: round; }
.vg-bt  { fill: var(--s-cool); fill-opacity: .55; stroke: var(--s-cool);
          stroke-width: 1.2; vector-effect: non-scaling-stroke; }
.vg-fc  { fill: none; stroke: var(--s-elec); stroke-width: 2.2;
          vector-effect: non-scaling-stroke; stroke-linejoin: round; }
.vg-so  { fill: none; stroke: var(--ink); stroke-width: 1.5; vector-effect: non-scaling-stroke; }
.vg-kd  { fill: none; stroke: var(--s-loss); stroke-width: 1.2; vector-effect: non-scaling-stroke; }
.vg-kf  { fill: none; stroke: var(--s-elec); stroke-width: 2.4;
          vector-effect: non-scaling-stroke; stroke-linejoin: miter; }
.vg-eta { fill: none; stroke: var(--ink); stroke-width: 2;
          vector-effect: non-scaling-stroke; stroke-linejoin: round; }
.vg-rug-d rect { fill: var(--s-loss); }
.vg-rug-f rect { fill: var(--s-elec); }

/* ---- legend. HTML rather than SVG, so the DOI is a real link and the
   type scales with the page instead of with a viewBox. ------------------ */
.vg__key { list-style: none; margin: .2rem 0 0; padding: 0; display: grid; gap: .5rem; }
.vg__k {
  display: grid; grid-template-columns: 1.7rem 1fr; gap: .55rem; align-items: start;
  font-family: var(--font-meta); font-size: var(--t-mono-xs); line-height: 1.55;
  letter-spacing: .02em; color: var(--ink-muted);
}
.vg__k b  { color: var(--ink); font-weight: 600; }
.vg__k em { font-style: normal; text-transform: uppercase; letter-spacing: .09em; }
.vg__k a  { overflow-wrap: anywhere; }
.vg__sw { display: block; width: 1.7rem; height: .7rem; margin-top: .25rem; }
.vg__sw--eta { height: 0; border-top: 2px solid var(--ink); }
.vg__sw--fc  { background: var(--s-elec); }
.vg__sw--dem { background: var(--s-loss); }
/* the seat kept for the paper's measured map: hollow, dashed, no fill.
   A declared-absent series is ordinary practice; a blank axis is not. */
.vg__sw--res { background: none; border: 1.5px dashed var(--edge); }
.vg__k--res { color: var(--ink-2); }

/* ---- status line. It ships with text and keeps a reserved height, so the
   run finishing can never move anything below it. ---------------------- */
.vg__status {
  margin: .8rem 0 0; min-height: 1.7em;
  font: 500 .78rem/1.7 var(--mono); letter-spacing: .05em;
  color: var(--ink-muted); font-variant-numeric: tabular-nums;
}

/* ---- controls. Rendered hidden; the script unhides them, so a reader
   without JavaScript never meets a control that does nothing. ---------- */
.vg__ctl {
  display: flex; flex-wrap: wrap; align-items: center; gap: .6rem 1rem;
  margin-top: .7rem;
  font: 500 var(--t-mono-sm)/1.4 var(--mono); letter-spacing: .04em; color: var(--ink-2);
}
.vg__ctl[hidden] { display: none; }
.vg__ctl-l { text-transform: uppercase; letter-spacing: .09em; }
.vg__again {
  padding: .4rem .7rem; min-height: 2.25rem;
  font: inherit; color: var(--ink-2); background: var(--surface-2);
  border: 1px solid var(--rule-strong); border-radius: 3px; cursor: pointer;
}
.vg__again[hidden] { display: none; }
.vg__again:hover { color: var(--ink); }
.vg__ramp { flex: 0 1 15rem; min-height: 2.25rem; accent-color: var(--accent); }
.vg__again:focus-visible,
.vg__ramp:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.vg__ramp-val { color: var(--ink); font-variant-numeric: tabular-nums; }

/* ---- caption and the provenance ledger ------------------------------- */
.vg__cap {
  margin: .9rem 0 0; max-width: var(--measure);
  font-size: .84rem; line-height: 1.55; color: var(--ink-2);
}
.vg__cap b, .vg__cap strong { color: var(--ink); font-variant-numeric: tabular-nums; }
.vg__cap cite { font-style: normal; }
.vg__cap code { font-family: var(--font-meta); font-size: .92em; }
.vg__p { display: block; margin-top: .6rem; }
/* Same mechanism as .plant__model and .spray__model: where a number is
   modelled rather than measured, the page says so in place. */
.vg__model {
  display: block; margin-top: .8rem;
  font-family: var(--font-meta); font-size: var(--t-mono-xs);
  line-height: 1.7; letter-spacing: .02em; color: var(--ink-muted);
}

/* ---- forced colours: drop the canvas, show the baked SVG ------------- */
@media (forced-colors: active) {
  .vg-wrap::before { display: none; }
  .vg__cv { display: none !important; }
  .vg__baked { display: block !important; }
  .vg-dem, .vg-bt { fill: Canvas; stroke: CanvasText; }
  .vg-fc, .vg-so, .vg-kd, .vg-kf, .vg-eta { stroke: CanvasText; }
  .vg-rug-d rect, .vg-rug-f rect { fill: CanvasText; }
  .vg__sw--fc, .vg__sw--dem { background: CanvasText; forced-color-adjust: none; }
  .vg__sw--eta { border-top-color: CanvasText; }
  .vg__sw--res { border-color: CanvasText; }
}

/* ---- glue: the 217 nmi instrument tile now leads somewhere ------------ */
.figs__more {
  display: inline-block; margin-left: .35rem; padding-block: .3rem;
  font-family: var(--font-meta); font-size: var(--t-mono-xs);
  letter-spacing: .07em; text-transform: uppercase; white-space: nowrap;
}

/* ---- print: the baked traces are what the printer gets --------------- */
@media print {
  .vg__ctl, .vg__status { display: none; }
  .vg__cv { display: none !important; }
  .vg__baked { display: block !important; }
  .vg__grid { grid-template-columns: minmax(0, 640fr) minmax(0, 300fr); }
}


/* R3CSS:scale */
/* ---- Fig. 4 · The traverse -------------------------------------------
   One logarithmic length axis, 1 nm to 10 000 km, with the work pinned on
   it at true scale.

   Every position in this figure is authored in the markup as an inline
   custom property and drawn by CSS with top: calc(var(--u) * 100%). The
   axis is therefore PROPORTIONAL to whatever height the entry list needs,
   at any viewport, with no measurement and no script - which is why the
   figure is complete on first paint, complete with JS disabled, and
   cannot shift layout when effects.js arrives.

   The default fill state of a marker is --a: 1, i.e. ACQUIRED. The dimmed
   pre-acquisition state exists only inside .is-armed, a class JS adds when
   and only when it is actually going to animate.

   There is no transition and no animation declared in this block. Nothing
   here eases: the sweep is a constant-rate ramp and a latch is a step.
   -------------------------------------------------------------------- */
.trav {
  margin: var(--s-6) 0 var(--s-7);
  max-width: var(--page);
  /* markers punch a hole in the axis line, so they need the ground they
     actually sit on - section 04 is banded, not bare paper */
  --trav-ground: var(--g-range, var(--paper));
}

/* ---- head readout ---- */
.trav__read {
  display: flex; flex-wrap: wrap; gap: .25rem .9rem; align-items: baseline;
  margin: 0 0 var(--s-3);
  font-family: var(--font-meta); font-size: var(--t-mono-md);
  font-variant-numeric: tabular-nums; color: var(--ink-muted);
}
.trav__read span {
  font-size: var(--t-mono-xs); text-transform: uppercase; letter-spacing: .09em;
}
.trav__read b { color: var(--ink); font-weight: 500; }
/* pinned widths: the value swings between 6 and 9 characters and the count
   between 1 and 2, and neither is allowed to reflow the line */
.trav__rd { display: inline-block; min-width: 9ch; text-align: right; }
.trav__ct { display: inline-block; min-width: 2ch; text-align: right; }

/* ---- the four-column figure ---- */
.trav__grid {
  position: relative;
  display: grid;
  grid-template-columns: 6rem 4.5rem 2.75rem minmax(0, 1fr);
  /* NO align-items here. The default stretch is load-bearing: it is what
     gives the gutter, the ladder and the rail the full height of the entry
     list, and the whole proportional axis depends on it. */
}
.trav__gut, .trav__dec, .trav__rail, .trav__list { position: relative; }
.trav__rail { min-height: 26rem; }

/* leaders sit under everything: absolutely positioned and first in DOM */
.trav__lead {
  position: absolute; inset: 0; width: 100%; height: 100%;
  pointer-events: none; overflow: visible;
}
.trav__lead path { fill: none; stroke: var(--edge); stroke-width: 1; }

/* ---- the decade ladder ---- */
.trav-d {
  position: absolute; right: .5rem; top: calc(var(--u) * 100%);
  transform: translateY(-50%);
  font: 500 var(--t-mono-xs)/1 var(--font-meta);
  letter-spacing: .02em; font-variant-numeric: tabular-nums;
  color: var(--ink-muted); white-space: nowrap;
}
.trav-d--pre { color: var(--ink); }

/* ---- the axis, drawn as segments ----
   Three elements rather than one masked line, so the dashed open interval
   needs no background colour to hide anything and the figure is correct on
   any ground and in either theme. */
.trav-ax {
  position: absolute; left: 50%; margin-left: -.5px; width: 1px;
  top: calc(var(--u) * 100%);
  height: calc((var(--u2) - var(--u)) * 100%);
  background: var(--edge);
}
.trav-ax--gap { background: none; width: 0; border-left: 1px dashed var(--edge); }

.trav-t {
  position: absolute; left: 50%; top: calc(var(--u) * 100%);
  width: .85rem; margin-left: -.425rem; height: 1px;
  background: var(--edge);
}
.trav-t--pre { width: 1.2rem; margin-left: -.6rem; }

/* ---- markers. The glyph carries provenance, so a one-second glance at
   the rail cannot outrun the chips that qualify it:
     solid disc        a length of his
     half-filled disc  a parameter he chose, in a case file or a model
     hatched bar       computed here from one of his numbers
     ring + crossbar   a public reference value
     dashed ring       computed here, but on a premise he has not confirmed
     nothing at all    a number he has not checked
   -------------------------------------------------------------------- */
.trav-m {
  position: absolute; left: 50%; top: calc(var(--u) * 100%);
  width: .55rem; height: .55rem; margin: -.275rem 0 0 -.275rem;
  border: 1px solid var(--edge); border-radius: 50%;
  background: var(--trav-ground);
  transform: translateX(var(--x, 0px));
}
.trav-m::after {
  content: ""; position: absolute; inset: 1px; border-radius: 50%;
  opacity: var(--a, 1);
}
.trav-m--mine::after  { background: var(--accent); }
.trav-m--param::after {
  background: linear-gradient(to top, var(--accent) 0 50%, transparent 50%);
}
.trav-m--ref::after, .trav-m--geom::after {
  inset: auto 1px; top: 50%; height: 1px; margin-top: -.5px;
  border-radius: 0; background: var(--ink-muted);
}
.trav-m--geom { border-style: dashed; }

/* a range, not a point: the bar fills exactly as far as the edge has
   crossed it, which is coverage, not a detector response */
.trav-m--bar {
  width: .34rem; margin: 0 0 0 -.17rem; border-radius: 1px;
  height: calc((var(--u2) - var(--u)) * 100%);
}
.trav-m--bar::after {
  inset: 1px; border-radius: 0; opacity: 1;
  transform: scaleY(var(--a, 1)); transform-origin: 50% 0;
}
.trav-m--bar.trav-m--model { border-color: var(--mark); }
.trav-m--bar.trav-m--model::after {
  background: repeating-linear-gradient(45deg,
    var(--mark) 0 1px, transparent 1px 3px);
}
.trav-m--bar.trav-m--ref::after {
  left: 50%; right: auto; top: 0; height: auto; bottom: 0;
  width: 1px; margin: 0 0 0 -.5px; background: var(--ink-muted);
}

/* the tie: two markers five per cent apart, joined, because that near
   coincidence is where two rungs meet */
.trav-tie {
  position: absolute; left: 50%; top: calc(var(--u) * 100%);
  width: 1.05rem; margin-left: -.525rem; height: 1px; background: var(--edge);
}
.trav-tie--gut { left: var(--x, 0px); width: 1.4rem; margin-left: 0; }

/* ---- the reading edge: a bracket, not a dot ---- */
.trav__sweep {
  position: absolute; left: -.7rem; right: -.7rem; top: 100%; height: 0;
  border-top: 1px solid var(--mark); will-change: transform;
}
.trav__sweep::before, .trav__sweep::after {
  content: ""; position: absolute; top: -.3rem;
  width: 1px; height: .6rem; background: var(--mark);
}
.trav__sweep::before { left: 0; }
.trav__sweep::after  { right: 0; }
/* at rest it is an instrument that has finished, not one about to move */
.trav.is-done .trav__sweep { border-top-color: var(--rule-strong); }
.trav.is-done .trav__sweep::before,
.trav.is-done .trav__sweep::after { background: var(--rule-strong); }

/* ---- rung brackets, drawn from the items under them ---- */
.trav-b {
  position: absolute; left: var(--x, 0px); top: calc(var(--u) * 100%);
  height: calc((var(--u2) - var(--u)) * 100%);
  width: 0; border-left: 1px solid var(--edge);
}
.trav-b::before, .trav-b::after {
  content: ""; position: absolute; left: 0;
  width: .42rem; height: 1px; background: var(--edge);
}
.trav-b::before { top: 0; }
.trav-b::after  { bottom: 0; }
/* an open end is dashed and has no cap - that is what open means */
.trav-b--dash { border-left-style: dashed; }
.trav-b--dash::before, .trav-b--dash::after { display: none; }

.trav-b__k {
  position: absolute; left: calc(var(--x, 0px) + .38rem);
  top: calc(var(--um) * 100%);
  writing-mode: vertical-rl; transform: translateY(-50%) rotate(180deg);
  font: 500 var(--t-mono-xs)/1 var(--font-meta);
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-muted); white-space: nowrap;
}

/* ---- the entries: a real ordered list, smallest to largest ---- */
.trav__list { list-style: none; margin: 0; padding: 0; }
.trav-e {
  padding: .5rem 0 .55rem 1.5rem; max-width: 34rem;
  border-top: 1px solid var(--rule);
}
.trav-e:first-child { border-top: 0; padding-top: 0; }
.trav-e__k { font-size: var(--t-sm); color: var(--ink); }
.trav-e__v {
  font-family: var(--font-meta); font-size: var(--t-mono-sm);
  font-variant-numeric: tabular-nums; color: var(--ink);
  margin: 0 .35rem; white-space: nowrap;
}
.trav-e__n { font-size: .8125rem; line-height: 1.5; color: var(--ink-muted); }
.trav .chip { vertical-align: .08em; }

/* A row dims only once JS has taken charge, and a latch is a step, not a
   fade - there is deliberately no transition here. Both states are
   --ink (15.1:1) or --ink-muted (6.1:1) on this ground: AA at every frame. */
.trav.is-armed .trav-e:not(.on):not(.trav-e--open) .trav-e__k,
.trav.is-armed .trav-e:not(.on):not(.trav-e--open) .trav-e__v { color: var(--ink-muted); }

/* ---- provenance key ---- */
.trav__key {
  display: flex; flex-wrap: wrap; gap: .5rem 1.5rem;
  margin: var(--s-5) 0 0; padding-top: var(--s-3);
  border-top: 1px solid var(--rule);
  font-family: var(--font-meta); font-size: var(--t-mono-xs);
  letter-spacing: .06em; color: var(--ink-muted);
}
.trav__k { display: inline-flex; align-items: center; gap: .45rem; }
.trav__key .trav-m {
  position: static; margin: 0; transform: none; flex: 0 0 auto;
}
.trav__key .trav-m--bar { width: .34rem; height: .55rem; }
.trav-g--open {
  flex: 0 0 auto; width: .55rem; height: 0;
  border-top: 1px dashed var(--edge);
}

/* ---- notes and caption ---- */
.trav__gapnote {
  margin: var(--s-5) 0 0; max-width: var(--measure);
  padding-left: var(--s-4); border-left: 1px dashed var(--edge);
  font-size: var(--t-sm); line-height: 1.55; color: var(--ink-muted);
}
.trav__gapnote b {
  font-family: var(--font-meta); font-weight: 500; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.trav__cap {
  margin: var(--s-4) 0 0; max-width: var(--measure);
  font-size: .82rem; line-height: 1.55; color: var(--ink-muted);
}
.trav__cap p { margin: 0 0 .55rem; }
.trav__cap p:last-child { margin-bottom: 0; }
.trav__cap strong { color: var(--ink); }
.trav__cap b { color: var(--ink); font-variant-numeric: tabular-nums; }

/* the decade span written into the summary rail below the figure, by the
   same pass that draws the brackets */
.rail-s {
  display: block; margin-bottom: var(--s-1);
  font-family: var(--font-meta); font-size: var(--t-mono-xs);
  letter-spacing: .06em; text-transform: uppercase;
  font-variant-numeric: tabular-nums; color: var(--ink-muted);
}

/* ---- provenance chips. --mark now means "derived or live" in three
   places on the page: the first-author chip, the modelled chip, and the
   reading edge. The comment in style.css was updated to say so. ---- */
.chip--mine  { color: var(--accent); }
.chip--model { color: var(--mark); }
.chip--param { color: var(--ink-muted); border-style: dotted; }
.chip--ref   { color: var(--ink-muted); }
.chip--geom  { color: var(--ink-muted); }
.chip--open  { color: var(--ink-muted); border-style: dashed; }

/* ---- narrow ---- */
@media (max-width: 46rem) {
  /* the rung brackets go first: they are the summary, and the plain rail
     below the figure carries the same four spans in words */
  .trav__gut { display: none; }
  .trav__grid { grid-template-columns: 4.25rem 2.5rem minmax(0, 1fr); }
}
@media (max-width: 34rem) {
  /* leaders off: below this width every row prints its own length and the
     ladder is the map, so the marker-to-row mapping survives without them */
  .trav__lead { display: none; }
  .trav__grid { grid-template-columns: 3.2rem 2.1rem minmax(0, 1fr); }
  .trav-d:not(.trav-d--pre) { display: none; }
  .trav-e { padding-left: 1rem; }
}

@media print {
  .trav__sweep, .trav__read { display: none; }
  .trav-m::after { opacity: 1 !important; transform: none !important; }
  .trav-m--bar::after { transform: none !important; }
  .trav__cap, .trav__gapnote { color: inherit; }
}

@media (forced-colors: active) {
  .trav-ax, .trav-t, .trav-tie { background: CanvasText; forced-color-adjust: none; }
  .trav-ax--gap { background: none; border-left-color: CanvasText; }
  .trav-b, .trav-b--dash { border-left-color: CanvasText; }
  .trav-b::before, .trav-b::after { background: CanvasText; }
  .trav-m { border-color: CanvasText; background: Canvas; }
  .trav-m::after { background: CanvasText; forced-color-adjust: none; }
  .trav-m--param::after { background: CanvasText; }
  .trav__lead path { stroke: CanvasText; }
  .trav__sweep { border-top-color: CanvasText; }
  .trav__sweep::before, .trav__sweep::after { background: CanvasText; }
}


/* R3CSS:ammonia */
/* ---- Fig. 4 · The buffer — green ammonia ------------------------------
   Four registers on one day-of-year axis. Nothing in this block has a
   transition or an animation: the sweep is a clock owned by fx.js, and a
   re-solve is a different answer, not a state that eases into place. That
   is why there is no cubic-bezier here either.
   ---------------------------------------------------------------------- */
.nh3-wrap { padding-top: 0; }
.nh3 { margin: 1rem 0 3rem; }

/* readout strip. Real, selectable, in the accessibility tree — no number on
   this figure is hover-gated or announced only to a live region. */
.nh3__hud {
  margin: 0 0 .55rem; font: 500 .82rem/1.7 var(--mono);
  letter-spacing: .045em; color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}
.nh3__hud span { text-transform: uppercase; opacity: .72; margin-left: 1rem; }
.nh3__hud span:first-child { margin-left: 0; }
.nh3__hud b { color: var(--ink); font-weight: 600; }

.nh3__key {
  display: flex; flex-wrap: wrap; gap: .2rem 1.15rem;
  margin: 0 0 .55rem; padding: 0; list-style: none;
  font-size: var(--t-mono-xs); letter-spacing: .03em; color: var(--ink-2);
}
.nh3__key i {
  display: inline-block; width: 18px; height: 4px; margin-right: .45rem;
  vertical-align: .12em; border-radius: 1px;
}
.nk--seq   i { background: linear-gradient(90deg, var(--seq-1), var(--seq-6)); }
.nk--spill i { background: var(--s-loss); opacity: .7; }
.nk--store i { background: var(--accent); }
.nk--hb    i { background: repeating-linear-gradient(90deg, var(--s-heat) 0 7px, transparent 7px 11px); }
.nk--ship  i { background: repeating-linear-gradient(90deg, var(--s-cool) 0 4px, transparent 4px 8px); }

/* The box owns the geometry, so the canvas swap can never shift the page. */
.nh3__box { position: relative; aspect-ratio: 730 / 244; min-height: 15rem; }
.nh3__static, .nh3__cv {
  position: absolute; inset: 0; width: 100%; height: 100%; display: block;
}
/* the class above sets display:block, which would out-rank the UA rule for
   the hidden attribute — say it explicitly rather than relying on paint order */
.nh3__static[hidden], .nh3__cv[hidden] { display: none; }

.nh3s__frame path, .nh3s__rail path { fill: none; stroke: var(--rule-strong); stroke-width: 1; }
.nh3s__floor { fill: none; stroke: var(--rule-strong); stroke-width: 1; stroke-dasharray: 4 3; }
.nh3s__cf { fill: var(--accent); fill-opacity: .16; stroke: var(--accent); stroke-width: 1; }
.nh3s__hb { fill: none; stroke: var(--s-heat); stroke-width: 1.1; }
.nh3s__dep { fill: none; stroke: var(--s-cool); stroke-width: 1.1; }
.nh3s__mon text {
  font: 500 9px var(--mono); fill: var(--ink-muted); text-anchor: middle;
}

/* controls */
.nh3__ctl {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  gap: .75rem 1.75rem; align-items: start; margin-top: .85rem;
}
.nh3__row {
  display: flex; align-items: center; gap: .75rem; margin: 0 0 .55rem;
  font: 500 .78rem/1 var(--mono); letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-2);
}
.nh3__row label { flex: 0 0 10.5rem; }
.nh3__slider { flex: 1 1 auto; min-width: 8rem; max-width: 22rem; accent-color: var(--accent); }
.nh3__slider:focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; }

.nh3__again {
  margin-top: .35rem; padding: .35rem .7rem;
  font: 500 .75rem/1 var(--mono); letter-spacing: .04em;
  color: var(--ink-2); background: var(--surface-2);
  border: 1px solid var(--edge); border-radius: 3px; cursor: pointer;
}
.nh3__again:hover { color: var(--ink); }
.nh3__again:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.nh3__again[hidden] { display: none; }

.nh3__space { width: 190px; }
.nh3__inset { display: block; width: 190px; height: 140px; }
.nh3__inset[hidden] { display: none; }
.nh3__insetcap {
  margin: .3rem 0 0; font-size: var(--t-mono-xs); line-height: 1.45;
  color: var(--ink-2);
}

/* caption. The provenance line is not a footnote: it sits under the figure,
   its first four words are full-strength ink, and the code rewrites it if his
   data ever lands in the JSON block. */
.nh3__cap { margin: .8rem 0 0; max-width: 46rem;
  font-size: .84rem; line-height: 1.55; color: var(--ink-2); }
.nh3__cap b, .nh3__cap strong { color: var(--ink); font-variant-numeric: tabular-nums; }
.nh3__finding { display: block; margin-top: .5rem; }
.nh3__model {
  display: block; margin-top: .55rem;
  font-family: var(--font-meta); font-size: var(--t-mono-xs);
  line-height: 1.55; letter-spacing: .02em; color: var(--ink-muted);
}
.nh3__model em { font-style: normal; color: var(--ink); }
.nh3__prov { display: inline; }

@media (max-width: 720px) {
  .nh3__ctl { grid-template-columns: 1fr; }
  .nh3__space { display: none; }        /* the trade is in the caption too */
  .nh3__row { flex-wrap: wrap; gap: .35rem .75rem; }
  .nh3__row label { flex: 1 0 100%; }
  .nh3__slider { max-width: none; }
}

@media print {
  .nh3__ctl, .nh3__again { display: none; }
  .nh3__cv { display: none; }
  .nh3__static { display: block !important; }
}

/* Windows high contrast: the canvas cannot honour a forced palette, so hand
   the reader the SVG, which can. */
@media (forced-colors: active) {
  .nh3__cv { display: none !important; }
  .nh3__static { display: block !important; }
  .nh3s__cf { fill: Canvas; stroke: CanvasText; fill-opacity: 1; }
  .nh3s__hb, .nh3s__dep, .nh3s__floor { stroke: CanvasText; }
  .nh3s__frame path, .nh3s__rail path { stroke: CanvasText; }
  .nh3s__mon text { fill: CanvasText; }
}

/* ==========================================================================
   TRAVERSE - CONTAINMENT.

   A provenance marker (.trav-m, a 0.55rem disc) was rendering as an ~800px
   ellipse covering the whole of section 04 and making the prose unreadable.
   The markers are absolutely positioned with sizes derived from custom
   properties (--u, --u2, --a); if any of those resolves unexpectedly -
   inherited from the wrong ancestor, or lost when a container collapses -
   the element's computed size is unbounded.

   Rather than chase one arithmetic path, this makes the failure impossible:
   the figure clips its own paint, the rail clips its markers, and every
   marker carries a hard ceiling. A bad value now produces a clipped marker
   instead of a page-covering shape. Text cannot be occluded by construction.
   ========================================================================== */
.trav { contain: paint; }
.trav__rail, .trav__gut, .trav__dec { overflow: hidden; }
.trav-m {
  max-width: 1rem;
  max-height: 100%;
  box-sizing: border-box;
}
.trav-m--bar { max-width: .5rem; }
.trav-tie { max-width: 2rem; max-height: 2px; }

/* THE ACTUAL RUNAWAY, and the reason the first containment pass was not
   enough. The visible shape is .trav-m::after, not .trav-m. The pseudo is
   position:absolute with inset:1px, so it sizes against the nearest
   POSITIONED ANCESTOR - and if .trav-m ever fails to be that ancestor (a
   dropped declaration, an invalid custom property in its `top`, a cascade
   order change) the pseudo resolves against .trav__rail and fills the entire
   rail as one border-radius:50% ellipse. Capping .trav-m does nothing in that
   case, because the pseudo is no longer its child box.

   So: re-assert the parent as a containing block, and cap the pseudo itself.
   Now even total failure of the parent rule yields a 1rem dot. */
.trav-m { position: absolute; contain: layout paint; }
.trav-m::after { max-width: 1rem; max-height: 1rem; }
.trav-m--bar::after { max-width: .5rem; max-height: 100%; }
/* the sweep is a full-width rule by design, so it is exempt from the width
   ceiling but still cannot escape the rail's overflow clip */
.trav__sweep { max-height: 2px; }


/* SENATECSS */
/* ==========================================================================
   09 ON THE RECORD — THE DIVISION.

   A division is the parliamentary word for a recorded vote. It is also what
   happens in the last third of this section, where two people are quoted on
   opposite sides of the same question in the same article on the same day.

   There is exactly ONE moving quantity in the whole section: a seat index
   running 0 → 24 at the clerk's cadence, written by JS as `--seat` on the
   <ol>. Six strips share that one number and stop at their own tallies, so
   the shortest tally finishes first — and the shortest tally is the record
   he wrote alone. The geometry deflates his best item. That is the point.

   NOTHING here transitions, keyframes or eases. There is no cubic-bezier.
   ========================================================================== */

/* ---- 1. THE GROUND: a bill as printed --------------------------------- */
/* REPLACES the two --g-outside values at effects.css:458 and :467.
   Contrast on #F2F4F5, computed: --ink 15.28:1 · --ink-muted 6.33:1 ·
   --accent 8.20:1 · --mark 5.59:1.
   On #16181B: --ink 14.44:1 · --ink-muted 7.42:1 · --accent 8.52:1 ·
   --mark 6.64:1. Every pair clear of AA, and it holds at every frame,
   because no text in this section ever changes colour or opacity. */
:root { --g-outside: #F2F4F5; }                 /* cool official grey       */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --g-outside: #16181B; }
}
:root[data-theme="dark"] { --g-outside: #16181B; }

/* REPLACES the perfboard rule at effects.css:533–536 in its entirety.
   Ruled legislative draft paper: fine horizontal rules at 26px, full bleed.
   The band's horizontal rules are viewport-independent, so they live in the
   full-bleed layer; the bill's margin rule does NOT, because the letter
   column is centred with a clamp() padding and any 50%-based arithmetic
   drifts through the body text at real viewport widths. It is drawn as a
   real element instead — see .rec-rule. */
#outside::after {
  background-image: repeating-linear-gradient(to bottom,
    var(--rule) 0 1px, transparent 1px 26px);
  opacity: .26;
}

/* The bill's second margin rule. .letter::before already draws ONE rule for
   the whole letter at `left: var(--pad-x)`; the section box starts 1.25rem
   to the right of it, so a companion at -0.95rem sits 0.3rem inboard of the
   existing one and the pair reads as the double margin rule a bill is
   printed to the right of. No magic percentages, correct at every width. */
.rec-rule {
  position: absolute; z-index: 0; pointer-events: none;
  top: 0; bottom: 0; left: -.95rem; width: 1px;
  background: var(--mark); opacity: .55;
}

/* ---- 2. THE DOCKET ----------------------------------------------------- */
.dk {
  --pitch: .5rem;          /* one seat of axis                              */
  --seat: 24;              /* NO-JS / FINISHED STATE: every strip complete.
                              JS writes an inline --seat to override it and
                              the roll is the only thing that ever does.    */
  list-style: none;
  margin: var(--s-6) 0 0;
  padding: 0;
  border-bottom: 1px solid var(--rule);
}

.dk__i {
  display: grid;
  grid-template-columns: 6.2rem 5.6rem minmax(0, 1fr) 13rem;
  grid-template-areas:
    "no wt title tally"
    "no wt title cite";
  column-gap: 1.25rem;
  row-gap: .3rem;
  align-items: start;
  padding: .95rem 0;
  border-top: 1px solid var(--rule);
  /* selection state is a border, never padding — a padding swap would
     reflow five grid cells under the cursor */
  border-left: .3rem solid transparent;
  padding-left: 0;
}
.dk__i--mine { border-left-color: var(--accent); padding-left: .7rem; }

/* THE RULE. Numbers above it, a claim below it. */
.dk__i--claim {
  border-top: 2px solid var(--ink-muted);
  margin-top: .5rem;
  grid-template-areas: "no title title title";
}

.dk__no {
  grid-area: no;
  font-size: var(--t-mono-md); font-weight: 500;
  letter-spacing: .04em; color: var(--ink);
}
.dk__i--claim .dk__no { color: var(--ink-muted); }

.dk__t {
  grid-area: title;
  font-size: var(--t-sm); line-height: 1.4; color: var(--ink);
  text-wrap: pretty;
}
.dk__t em {
  display: block; margin-top: .28rem;
  font-style: normal; font-size: var(--t-sm);
  line-height: 1.5; color: var(--ink-muted);
}

/* ---- 3. THE TWO WEIGHT RAILS ------------------------------------------
   Authorship and seconding are NOT the same axis. Converting "a second" into
   a fraction of "an authorship" would require an exchange rate I do not have,
   so there are two rails and the label on each carries the fraction as text —
   which is also what a screen reader and a printed page get. The bar is
   redundant with its own label, by design.                                 */
.dk__w { grid-area: wt; display: grid; gap: .3rem; }
.dk__wt {
  display: block; width: 5rem; height: .95rem;
  border: 1px solid var(--edge);      /* 3.32:1 / 3.37:1 — SC 1.4.11 is met
                                         by the socket, which never changes */
  padding: 0;
}
.dk__wl {
  display: block; font-size: var(--t-mono-xs); line-height: 1;
  letter-spacing: .05em; color: var(--ink-muted);
  margin: 0 0 .18rem;
}
.dk__wt > b { display: block; height: 100%; width: calc(var(--w, 0) * 100%); }
.dk__wt          > b { background: var(--accent); }            /* authorship */
.dk__wt--s       > b { background: none;                       /* a second   */
  border-right: 2px solid var(--mark);
  background-image: repeating-linear-gradient(45deg,
    var(--mark) 0 1px, transparent 1px 5px); }
/* 1/18 of 5rem is 4.4px. The 2px right cap is what keeps the sliver visible
   at all — I am not hiding the weakest item, I am making sure it renders. */

/* the label sits above the socket, so the socket must not eat the row */
.dk__wt { position: relative; }
.dk__wl { position: static; }

/* ---- 4. THE DIVISION STRIP -------------------------------------------
   24 sockets at a FIXED pitch. The fill is a second gradient at the same
   fixed pitch, anchored at left:0, so widening the fill reveals whole seats
   one at a time and never rescales a tick. Width is a `calc(number × length)`
   — no percentages, nothing to resample.                                   */
.dk__tal { grid-area: tally; display: grid; gap: .3rem; justify-items: start; }

.dk__strip {
  position: relative;
  width: calc(24 * var(--pitch));
  height: .8rem;
  border-bottom: 1px solid var(--edge);
  background-image: repeating-linear-gradient(to right,
    var(--rule) 0 1px, transparent 1px var(--pitch));
  contain: layout paint;      /* the fill's width change cannot escape this box */
}
.dk__fill {
  position: absolute; left: 0; top: 0; bottom: 0;
  width: calc(min(var(--seat, 24), var(--n, 0)) * var(--pitch));
  background-image: repeating-linear-gradient(to right,
    var(--accent) 0 2px, transparent 2px var(--pitch));
}
.dk__i--nostrip .dk__strip { display: none; }

.dk__num {
  font-size: var(--t-mono-sm); letter-spacing: .06em; color: var(--ink-muted);
}
.dk__num b { font-weight: 500; color: var(--ink); }

/* ---- 5. THE CITATION --------------------------------------------------- */
.dk__cite {
  grid-area: cite;
  font-size: var(--t-mono-xs); letter-spacing: .04em;
  color: var(--accent); text-decoration-thickness: 1px;
  text-underline-offset: .2em;
  align-self: end;
}
.dk__cite:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

/* ---- 6. THE KEY — four labels, each next to what it qualifies ---------- */
.dk__key {
  display: grid;
  grid-template-columns: 6.5rem minmax(0, 1fr);
  column-gap: 1.25rem; row-gap: .5rem;
  margin: var(--s-5) 0 0;
  max-width: calc(var(--measure) + 6.5rem);
  font-size: var(--t-mono-xs); line-height: 1.6;
}
.dk__key dt {
  letter-spacing: .1em; color: var(--ink); font-weight: 500;
}
.dk__key dd { margin: 0; color: var(--ink-muted); }

/* ---- 7. THE SPREAD ----------------------------------------------------
   The only two-column break on the site. Every child is placed EXPLICITLY —
   with `1fr 1px 1fr` and auto-placement the second column lands in the 1px
   gutter track and the whole point of the composition renders 1px wide.    */
.sp {
  display: grid;
  grid-template-columns: 1fr 1px 1fr;
  column-gap: 2rem;
  margin: var(--s-7) 0 var(--s-6);
  padding-top: var(--s-4);
  border-top: 1px solid var(--rule-strong);
}
.sp__src            { grid-column: 1 / -1; grid-row: 1; margin: 0 0 var(--s-5); }
.sp__col--mine      { grid-column: 1;      grid-row: 2; }
.sp__rule           { grid-column: 2;      grid-row: 2; background: var(--rule-strong); }
.sp__col--held      { grid-column: 3;      grid-row: 2; }
.sp__after          { grid-column: 1 / -1; grid-row: 3; margin: var(--s-6) 0 0; }

.sp__src {
  font-size: var(--t-mono-xs); letter-spacing: .09em;
  color: var(--ink-muted); line-height: 1.6;
}
.sp__who {
  font-size: var(--t-mono-xs); letter-spacing: .09em; font-weight: 500;
  color: var(--ink); margin: 0 0 var(--s-3);
  padding-bottom: .45rem; border-bottom: 1px solid var(--rule-strong);
}
.sp__q { margin: 0; padding: 0; border: 0; }
.sp__q p {
  margin: 0; font-size: var(--t-base); line-height: 1.5;
  color: var(--ink); text-wrap: pretty;
}
/* The held column is not blank — a blank box is a bug. It is a ruled field
   with a stated refusal in it, which is a position. */
.sp__ruled {
  padding: .1rem 0 .6rem;
  background-image: repeating-linear-gradient(to bottom,
    transparent 0 1.55em, var(--rule) 1.55em calc(1.55em + 1px));
}
.sp__ruled p {
  margin: 0; font-size: var(--t-sm); line-height: 1.55em;
  color: var(--ink-muted); text-wrap: pretty;
}
.sp__cite {
  display: inline-block; margin-top: var(--s-3);
  font-size: var(--t-mono-xs); letter-spacing: .04em; color: var(--accent);
  text-underline-offset: .2em;
}
.sp__cite:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.sp__after {
  font-size: var(--t-sm); line-height: 1.55; color: var(--ink-muted);
  max-width: var(--measure);
  padding-top: var(--s-4); border-top: 1px solid var(--rule);
}
.sp__after .mono { font-size: var(--t-mono-xs); letter-spacing: .09em; }
.sp__after b { color: var(--ink); font-weight: 600; }

/* ---- 8. NARROW VIEWPORTS ---------------------------------------------
   28rem of fixed docket track does not fit a phone. Below 48rem the row
   stacks and the axis pitch shrinks so the strip stays inside 320px.      */
@media (max-width: 48rem) {
  .dk { --pitch: .42rem; }
  .dk__i {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "no" "wt" "title" "tally" "cite";
    row-gap: .5rem;
  }
  .dk__i--claim { grid-template-areas: "no" "title"; }
  .dk__w { grid-auto-flow: column; justify-content: start; gap: 1.25rem; }
  .dk__cite { align-self: start; }
  .rec-rule { display: none; }        /* the gutter is gone; so is the rule */
}
@media (max-width: 46rem) {
  .sp { grid-template-columns: minmax(0, 1fr); }
  .sp__src, .sp__after { grid-column: 1; }
  .sp__col--mine { grid-column: 1; grid-row: 2; }
  .sp__rule      { display: none; }
  .sp__col--held { grid-column: 1; grid-row: 3;
    margin-top: var(--s-6); padding-top: var(--s-4);
    border-top: 1px solid var(--rule-strong); }
}

/* ---- 9. FORCED COLORS -------------------------------------------------
   Gradients are dropped by the system, so the ticks go. Length is kept,
   because length is the information: a 1/18 sliver must still read as a
   sliver. Every fraction is also present as text in .dk__wl, so nothing
   here is the sole carrier of anything.                                   */
@media (forced-colors: active) {
  .rec-rule { display: none; }
  .dk, .dk__i { border-color: CanvasText; }
  .dk__i--mine { border-left-color: Highlight; }
  .dk__strip, .dk__wt { background-image: none; border-color: CanvasText; }
  .dk__fill  { background-image: none; background: Highlight;
               forced-color-adjust: none; }
  .dk__wt > b { background-image: none; background: CanvasText;
                border-right-color: CanvasText; forced-color-adjust: none; }
  .sp, .sp__who, .sp__after { border-color: CanvasText; }
  .sp__rule { background: CanvasText; forced-color-adjust: none; }
  .sp__ruled { background-image: none;
               border-left: 2px solid CanvasText; padding-left: .8rem; }
}

/* ---- 10. PRINT --------------------------------------------------------
   style.css:675 carries an unscoped `* { background: #fff !important }`,
   which would blank every bar on paper and delete the docket's best skim
   signal in the one medium a professor is most likely to use. Class
   selectors are (0,1,0) and beat the universal selector's (0,0,0), so each
   bar is re-asserted here explicitly. The strips are forced complete in
   case someone prints during the 2.6 s roll.                              */
@media print {
  .rec-rule { display: none !important; }
  .dk { --seat: 24 !important; break-inside: auto; }
  .dk__i { break-inside: avoid; }
  .dk__strip, .dk__wt { border: 1px solid #666 !important;
                        background: #fff !important; }
  .dk__fill   { background: #000 !important; }
  .dk__wt > b { background: #000 !important; border-right-color: #000 !important; }
  .dk__wt--s > b { background: #777 !important; }
  .dk__i--mine { border-left: .3rem solid #000 !important; }
  .sp__rule { background: #000 !important; }
  .sp__ruled { background: #fff !important;
               border-left: 2px solid #000 !important; padding-left: .8rem; }
  /* the section's whole claim is that this is checkable, so the links have
     to survive the paper. Scoped, never site-wide. */
  .dk__cite::after, .sp__cite::after {
    content: " (" attr(href) ")"; font-size: .8em; word-break: break-all;
  }
}


/* VG2CSS */
/* R3CSS:helm951 */
/* ---- Fig. 5 · 9:51:27 — the FCV Vanguard's helm ----------------------
   Replaces the whole .vg / .vg-wrap block.

   THE MOTION RULE holds here as everywhere in this file: there is not one
   transition, not one keyframe and not one cubic-bezier below. Everything
   that moves in this section moves on a canvas, because it is charge, and
   it moves at the rate a printed number sets. The only thing CSS changes
   is visibility, and visibility does not ease.

   The section has its own ground and its own texture — a recessed
   instrument bezel with a scanline — because it is the one section on the
   page that is a reconstruction of a real machine's own display, and it
   should not read as more page.
   -------------------------------------------------------------------- */

.ow-wrap {
  padding-top: clamp(1.5rem, 1rem + 2vw, 2.5rem);
  position: relative; isolation: isolate;

  /* the instrument's own ground. Both themes, defined once, here. */
  --ow-panel:  var(--paper-2);
  --ow-scan:   rgba(26, 29, 33, .035);
  --ow-glow:   rgba(20, 78, 99, .05);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .ow-wrap {
    --ow-scan: rgba(236, 231, 222, .045);
    --ow-glow: rgba(121, 189, 214, .06);
  }
}
:root[data-theme="dark"] .ow-wrap {
  --ow-scan: rgba(236, 231, 222, .045);
  --ow-glow: rgba(121, 189, 214, .06);
}

.ow { margin: var(--s-5) 0 0; max-width: var(--page); }

/* ---- the vessel, at the section's own width -------------------------- */
.ow__plate { margin: 0 0 var(--s-5); }
.ow__plate img {
  display: block; width: 100%; height: auto;
  border: 1px solid var(--edge);
  background: var(--paper-2);          /* the box is held even if the file is missing */
}
.ow__plate-cap {
  margin: .4rem 0 0; font-size: var(--t-mono-xs); line-height: 1.6;
  letter-spacing: .04em; color: var(--ink-muted);
}
.ow__plate-cap b { color: var(--ink); }

/* ---- the panel head -------------------------------------------------- */
.ow__head {
  background: var(--ow-panel);
  background-image:
    linear-gradient(to bottom, var(--ow-glow), transparent 40%),
    repeating-linear-gradient(to bottom, transparent 0 3px, var(--ow-scan) 3px 4px);
  border: 1px solid var(--edge);
  padding: var(--s-4) var(--s-4) var(--s-3);
}
.ow__ttl {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .4rem 1.1rem;
  margin: 0 0 var(--s-4); font-size: var(--t-mono-sm);
  letter-spacing: .1em; text-transform: uppercase; color: var(--ink-muted);
}
.ow__ttl b { color: var(--ink); }
.ow__clock {
  font-variant-numeric: tabular-nums; color: var(--ink);
  min-width: 11ch;                     /* --:--:-- and 9:51:27 AM occupy one box */
  border-bottom: 1px solid var(--edge);
}
.ow__froz { text-transform: none; letter-spacing: .03em; font-style: italic; }

.ow__pair {
  display: grid; gap: var(--s-3) var(--s-4); align-items: center;
  grid-template-columns: 1fr auto 1fr;
}
.ow__hl {
  display: block; font-size: var(--t-mono-xs); letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink-muted);
}
.ow__big {
  display: block; color: var(--ink);
  font-family: var(--font-meta); font-weight: 600;
  font-size: clamp(2.4rem, 1.5rem + 4.4vw, 4.25rem); line-height: .92;
  font-variant-numeric: tabular-nums; letter-spacing: -.03em;
}
.ow__big i { font-style: normal; font-size: .28em; letter-spacing: .08em;
  margin-left: .35em; color: var(--ink-muted); }
.ow__hero--idle .ow__big { color: var(--ink-muted); }   /* 6.64:1 — AA */
.ow__vs {
  display: grid; gap: .25rem; justify-items: center; text-align: center;
  font-size: var(--t-mono-xs); letter-spacing: .09em; color: var(--ink-muted);
  padding-inline: .5rem; border-inline: 1px solid var(--rule);
}
.ow__vs b { color: var(--ink); font-variant-numeric: tabular-nums; min-width: 9ch; }

.ow__leg {
  list-style: none; margin: var(--s-4) 0 0; padding: 0;
  display: grid; gap: .3rem; font-size: var(--t-mono-xs);
  line-height: 1.5; color: var(--ink-muted);
}
.ow__leg li { display: grid; grid-template-columns: .9rem 1.4ch 1fr; gap: .45rem;
  align-items: baseline; }
.ow__leg b { color: var(--ink); font-weight: 600; }
.ow__sw { display: block; width: .9rem; height: .55rem; border-left: 3px solid var(--rule); }

/* the four provenance inks. Colour is NEVER the only channel: every field
   also carries the letter itself, in --ink-muted at 6.64:1, which is real
   text and passes AA. --edge is used only as a boundary, never as text. */
.ow__sw[data-prov="M"], .ow__f[data-prov="M"] { border-left-color: var(--accent); }
.ow__sw[data-prov="D"], .ow__f[data-prov="D"] { border-left-color: var(--mark); }
.ow__sw[data-prov="c"], .ow__f[data-prov="c"] { border-left-color: var(--s-cool); }
.ow__sw[data-prov="x"], .ow__f[data-prov="x"] { border-left: 3px dashed var(--edge); }

/* ---- THE BUS -------------------------------------------------------
   A FIXED height, deliberately. The ribbon scale is one kilowatt per
   pixel, and that claim is only literally true if the box does not
   rescale with the viewport. The height is in CSS, so the box is reserved
   before a byte of script arrives and nothing can shift. */
.ow__stage {
  position: relative; height: 380px; margin-top: var(--s-2);
  background: var(--ow-panel);
  background-image: repeating-linear-gradient(to bottom, transparent 0 3px, var(--ow-scan) 3px 4px);
  border: 1px solid var(--edge); border-top: 0;
  contain: layout paint style;
}
@media (max-width: 46rem) { .ow__stage { height: 320px; } }
.ow__cv, .ow__frame {
  position: absolute; inset: 0; width: 100%; height: 100%; display: block;
}
.ow__cv[hidden] { display: none; }
.ow__baked[hidden] { display: none; }
.ow__frame { pointer-events: none; }

.ow__grip {
  position: absolute; z-index: 2; cursor: ns-resize;
  touch-action: none;                  /* the gesture IS vertical drag, and this
                                          box is 58 px wide, so the page still
                                          scrolls everywhere else */
  background: transparent;
}
.ow__grip[hidden] { display: none; }

/* the no-JS bus */
.ow-bus rect { fill: var(--paper); stroke: var(--edge); stroke-width: 1; }
.ow-rib .ow-elec { fill: var(--s-elec); fill-opacity: .16; stroke: var(--s-elec); stroke-width: 1; }
.ow-rib .ow-mark { fill: var(--mark);   fill-opacity: .16; stroke: var(--mark);   stroke-width: 1; }
.ow-rib .ow-loss { fill: var(--s-loss); fill-opacity: .16; stroke: var(--s-loss); stroke-width: 1; }
.ow-pipe path { fill: none; stroke-width: 1; stroke-dasharray: 2 4; }
.ow-pipe .ow-cool { stroke: var(--s-cool); }
.ow-pipe .ow-elec { stroke: var(--s-elec); }
.ow-t text {
  font-family: var(--font-meta); font-size: 11px; fill: var(--ink);
  font-variant-numeric: tabular-nums;
}
.ow-t .ow-sm { font-size: 9px; fill: var(--ink-muted); letter-spacing: .06em; }

/* ---- the balance, and it closes ------------------------------------- */
.ow__ledger {
  border: 1px solid var(--edge); border-top: 0;
  padding: var(--s-4); background: var(--paper);
}
.ow__lh {
  margin: 0 0 var(--s-3); font-size: var(--t-mono-xs); font-weight: 600;
  letter-spacing: .09em; text-transform: uppercase; color: var(--ink-muted);
}
.ow__bal { margin: 0; display: grid; gap: .1rem; font-size: var(--t-mono-sm); }
.ow__bal .ow__f {
  display: grid; grid-template-columns: minmax(9rem, 14ch) 7.5ch 1fr;
  gap: .2rem .8rem; align-items: baseline;
  padding: .3rem .5rem; border-left: 3px solid var(--rule);
}
.ow__bal dt { color: var(--ink); text-transform: uppercase; letter-spacing: .05em; }
.ow__bal dd { margin: 0; color: var(--ink); font-variant-numeric: tabular-nums;
  text-align: right; }
.ow__bal dd b { font-weight: 600; }
.ow__bal .ow__why {
  text-align: left; color: var(--ink-muted); font-size: var(--t-mono-xs);
  line-height: 1.55; letter-spacing: .01em;
}
.ow__f--tot { border-top: 1px solid var(--rule-strong); margin-top: .35rem; padding-top: .5rem; }
.ow__f--tot dt, .ow__f--tot dd { font-weight: 600; }
.ow__note {
  margin: var(--s-3) 0 0; max-width: var(--measure);
  font-size: .84rem; line-height: 1.6; color: var(--ink-muted);
}
.ow__note b { color: var(--ink); }

/* ---- the levers ------------------------------------------------------ */
.ow__ctl {
  border: 1px solid var(--edge); border-top: 0;
  padding: var(--s-3) var(--s-4) var(--s-4); background: var(--paper-2);
}
.ow__ctl[hidden] { display: none; }
.ow__ctl-h {
  margin: 0 0 var(--s-3); font-size: var(--t-mono-xs); letter-spacing: .09em;
  text-transform: uppercase; color: var(--ink-muted);
}
.ow__row {
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem 1rem;
  margin-bottom: .5rem;
}
.ow__cl { font-size: var(--t-mono-xs); letter-spacing: .09em;
  text-transform: uppercase; color: var(--ink-muted); flex: 0 0 auto; }
.ow__total, .ow__split { flex: 1 1 14rem; min-width: 10rem; min-height: 2.25rem;
  accent-color: var(--accent); }
.ow__val { font-size: var(--t-mono-sm); color: var(--ink-muted);
  font-variant-numeric: tabular-nums; }
.ow__val b { color: var(--ink); min-width: 5ch; display: inline-block;
  text-align: right; }
.ow__back {
  font: inherit; font-size: var(--t-mono-xs); letter-spacing: .07em;
  text-transform: uppercase; padding: .45rem .7rem;
  color: var(--ink-muted); background: var(--paper);
  border: 1px solid var(--edge); cursor: pointer;
}
.ow__back:hover { color: var(--ink); }
.ow__total:focus-visible, .ow__split:focus-visible, .ow__back:focus-visible {
  outline: 2px solid var(--focus); outline-offset: 2px;
}
.ow__refuse, .ow__bridge {
  margin: .4rem 0 0; font-size: var(--t-mono-xs); line-height: 1.6;
  letter-spacing: .04em; color: var(--mark);
}
.ow__refuse:empty, .ow__bridge:empty { display: none; }
.ow__cmp {
  margin: .6rem 0 0; font-size: var(--t-mono-xs); line-height: 1.7;
  color: var(--ink-muted); max-width: var(--measure);
}
.ow__cmp b { color: var(--ink); font-variant-numeric: tabular-nums; }
.ow__say {
  margin: .5rem 0 0; font-size: var(--t-mono-xs); letter-spacing: .04em;
  color: var(--ink-muted); min-height: 1.2em;
}

/* ---- the two measured planes ---------------------------------------- */
.ow__planes {
  display: grid; gap: var(--s-4); margin-top: var(--s-5);
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
}
.ow__plane { margin: 0; }
.ow__plane svg { display: block; width: 100%; height: auto; }
.ow__pc {
  margin: .4rem 0 0; font-size: .8rem; line-height: 1.55; color: var(--ink-muted);
}
.ow__pc em { font-style: italic; color: var(--ink); }
.ow-ax path { fill: none; stroke: var(--rule-strong); stroke-width: 1; vector-effect: non-scaling-stroke; }
.ow-gl path { fill: none; stroke: var(--rule); stroke-width: 1; vector-effect: non-scaling-stroke; }
.ow-tk text, .ow-ttl text {
  font-family: var(--font-meta); font-size: 8px; fill: var(--ink-muted);
  font-variant-numeric: tabular-nums;
}
.ow-ttl text { letter-spacing: .09em; text-transform: uppercase; }
.ow-p1 { fill: none; stroke: var(--s-elec); stroke-width: 1.8; vector-effect: non-scaling-stroke; }
.ow-p2 { fill: none; stroke: var(--s-cool); stroke-width: 1.4;
  stroke-dasharray: 6 2; vector-effect: non-scaling-stroke; }
.ow-d1 circle { fill: var(--s-elec); }
.ow-d2 circle { fill: none; stroke: var(--s-cool); stroke-width: 1.2; }
.ow-env { fill: var(--ink-muted); fill-opacity: .07; }
.ow-envl { fill: var(--ink-muted); }
.ow-gap path { stroke: var(--edge); stroke-width: 1; stroke-dasharray: 2 2; fill: none; }
.ow-gap text, .ow-inst text, .ow-sm {
  font-family: var(--font-meta); font-size: 7.5px; fill: var(--ink-muted);
  letter-spacing: .03em;
}
.ow-inst path { stroke: var(--ink); stroke-width: 1.3; fill: none; }
.ow-inst circle { fill: none; stroke: var(--ink); stroke-width: 1; }
.ow-live circle { fill: none; stroke: var(--s-elec); stroke-width: 1.6; }
.ow-live path { stroke: var(--s-elec); stroke-width: 1; }
.ow-live--2 circle { stroke: var(--s-cool); }
.ow-live--2 path { stroke: var(--s-cool); }

/* ---- THE PANEL. 47 fields, six groups, one dead row ------------------ */
.ow__panel {
  display: grid; gap: 1px; margin-top: var(--s-5);
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  background: var(--rule); border: 1px solid var(--edge);
}
.ow__blk {
  background: var(--ow-panel);
  background-image: repeating-linear-gradient(to bottom, transparent 0 3px, var(--ow-scan) 3px 4px);
  padding: var(--s-3);
  contain: layout style;               /* 47 fields must not relayout the page */
}
.ow__blk--idle { background-color: var(--paper); }
.ow__bh {
  margin: 0 0 .4rem; font-family: var(--font-meta); font-size: var(--t-mono-xs);
  font-weight: 600; letter-spacing: .11em; text-transform: uppercase;
  color: var(--ink); border-bottom: 1px solid var(--rule-strong);
  padding-bottom: .25rem;
}
.ow__fl { margin: 0; display: grid; gap: 1px; }
.ow__f {
  display: grid; grid-template-columns: 1fr auto; align-items: baseline;
  gap: .2rem .5rem; padding: .18rem .35rem .18rem .5rem;
  border-left: 3px solid var(--rule);
  font-family: var(--font-meta); font-size: var(--t-mono-xs); line-height: 1.5;
}
.ow__f dt { color: var(--ink-muted); letter-spacing: .05em; }
.ow__f dd {
  margin: 0; color: var(--ink); font-weight: 600;
  font-variant-numeric: tabular-nums; text-align: right;
  min-width: 8.5ch;                    /* the slot is reserved before the digits
                                          arrive, so the boot cannot reflow */
}
.ow__f dd i { font-style: normal; font-weight: 400; color: var(--ink-muted);
  margin-left: .3em; letter-spacing: .04em; }
/* the provenance letter. --ink-muted, 6.64:1, real text, passes AA.
   Never --edge: that token is a boundary, not type. */
.ow__p { color: var(--ink-muted); font-size: .82em; margin-left: .35em;
  font-weight: 400; vertical-align: super; line-height: 0; }
.ow__ann {
  margin: .55rem 0 0; font-size: .78rem; line-height: 1.6; color: var(--ink-muted);
  border-top: 1px solid var(--rule); padding-top: .45rem;
}
.ow__ann b { color: var(--ink); }
.ow__dead .ow__f dd, .ow__dead .ow__f dt { color: var(--ink-muted); font-weight: 400; }

/* THE BOOT. Absent, then present — no fade, no ease, no transition. The
   attribute only exists while JS is running, so a reader with no script
   is never shown a blank instrument. */
.ow[data-boot="1"] .ow__blk[data-grp]:not([data-up]) dd,
.ow[data-boot="1"] .ow__blk[data-grp]:not([data-up]) .ow__ann { visibility: hidden; }

/* off the frame, the load side is not modelled and says so by going quiet */
.ow[data-frame="0"] .ow__vs b { color: var(--ink-muted); }

/* ---- the source, under the reconstruction --------------------------- */
.ow__src { margin-top: var(--s-5); font-size: .84rem; color: var(--ink-muted); }
.ow__src summary {
  cursor: pointer; font-family: var(--font-meta); font-size: var(--t-mono-xs);
  letter-spacing: .07em; text-transform: uppercase; padding: .4rem 0;
  color: var(--ink);
}
.ow__src summary:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.ow__src figure { margin: .5rem 0 0; }
.ow__src img {
  display: block; width: 100%; height: auto;
  border: 1px solid var(--edge); background: var(--paper-2);
}
.ow__src figcaption {
  margin-top: .4rem; font-size: var(--t-mono-xs); line-height: 1.6;
  color: var(--ink-muted);
}
.ow__src figcaption b { color: var(--ink); }
.ow__src cite { font-style: normal; }

/* ---- caption and the provenance ledger ------------------------------ */
.ow__cap {
  margin: var(--s-5) 0 0; max-width: var(--measure);
  font-size: .84rem; line-height: 1.6; color: var(--ink-muted);
}
.ow__cap b, .ow__cap strong { color: var(--ink); font-variant-numeric: tabular-nums; }
.ow__cap cite { font-style: normal; }
.ow__cap code { font-family: var(--font-meta); font-size: .92em; }
.ow__p2 { display: block; margin-top: .7rem; }
.ow__model {
  display: block; margin-top: .9rem;
  font-family: var(--font-meta); font-size: var(--t-mono-xs);
  line-height: 1.75; letter-spacing: .02em; color: var(--ink-muted);
}
.ow__model b { color: var(--ink); }
.ow__model a { overflow-wrap: anywhere; }

/* ---- narrow: the two hero numbers stay side by side, because their
        asymmetry IS the argument and must never stack ----------------- */
@media (max-width: 40rem) {
  .ow__pair { grid-template-columns: 1fr 1fr; gap: var(--s-3); }
  .ow__vs { grid-column: 1 / -1; grid-row: 2; border-inline: 0;
    border-top: 1px solid var(--rule); padding-top: .4rem; }
  .ow__bal .ow__f { grid-template-columns: 1fr 7.5ch; }
  .ow__bal .ow__why { grid-column: 1 / -1; text-align: left; }
  .ow__panel { grid-template-columns: 1fr; }
}

/* ---- forced colours: drop the canvas, show the baked SVG ------------- */
@media (forced-colors: active) {
  .ow__cv { display: none !important; }
  .ow__baked { display: block !important; }
  .ow__grip { display: none !important; }
  .ow-rib rect { fill: Canvas; stroke: CanvasText; }
  .ow-bus rect { fill: Canvas; stroke: CanvasText; }
  .ow-pipe path, .ow-p1, .ow-p2, .ow-inst path { stroke: CanvasText; }
  .ow-d1 circle { fill: CanvasText; }
  .ow-d2 circle { stroke: CanvasText; }
  .ow__head, .ow__stage, .ow__blk { background-image: none; }
}

/* ---- print: the baked SVG is what the printer gets ------------------ */
@media print {
  .ow__ctl, .ow__say, .ow__grip { display: none !important; }
  .ow__cv { display: none !important; }
  .ow__baked { display: block !important; }
  .ow__src[open] img { max-height: 12cm; width: auto; }
  .ow__head, .ow__stage, .ow__blk { background-image: none; }
  .ow[data-boot="1"] .ow__blk[data-grp]:not([data-up]) dd,
  .ow[data-boot="1"] .ow__blk[data-grp]:not([data-up]) .ow__ann { visibility: visible; }
}


/* EVCSS */
/* R3CSS:evidence - append to the end of effects.css.
   02 The evidence: byline rails, name-authority card, two-voyage scale figure.
   No canvas, no FX.job, no rAF. Tokens only. */

/* 1. GROUND. The Oasis photograph goes. It is the one thing in the section
   that is not his and not checkable, in the section whose whole claim is that
   everything resolves - and it is the section's only network request (183 KB).
   PREFERRED: delete effects.css:579-585, the #evidence::before clause in the
   dark block at 597-601, #evidence::before from the reduced-data rule at 614,
   assets/ship.jpg, and its entry in assets/CREDITS.md. These two rules make it
   work either way. TO REVERT: delete them. */
#evidence::before { background-image: none; }
@media (prefers-color-scheme: dark) { #evidence::before { background-image: none; } }

/* ...and the vacated ground becomes ledger stock: a fine rule per line, a
   heavier one every fourth. ::after is the texture slot; ::before is the
   ground slot and is left alone. Distinct from #training's single-weight
   ticks so the two sections still read apart in peripheral vision. */
#evidence::after, .pubs-wrap::after {
  background-image:
    repeating-linear-gradient(to bottom, var(--rule) 0 1px, transparent 1px 1.6875rem),
    repeating-linear-gradient(to bottom, var(--rule-strong) 0 1px, transparent 1px 6.75rem);
  opacity: .26;
}

/* 2. THE RAIL. Authorship position drawn as a position: true author-count
   width, his slot filled. Sized in px, never in ems of a loading face, so a
   web-font swap cannot move it. */
.ev__rail {
  --slot-w: 14px; --slot-h: 20px; --slot-gap: 4px;
  display: inline-flex; gap: var(--slot-gap); flex: none;
}
.ev__rail i {
  display: block; width: var(--slot-w); height: var(--slot-h);
  border: 1px solid var(--edge);   /* 3.32:1 / 3.37:1 - SC 1.4.11 */
  border-radius: 1px; background: none;
}
/* solid --accent = a byline printed in a DOI-resolvable paper.
   45deg hatch in --mark = derived or self-reported, which is what --mark
   already means on the traverse rail (04) and the docket (06). */
.ev__me { background: var(--accent); border-color: var(--accent); }
.ev__me--rep {
  background: none; border-color: var(--mark);   /* 5.87:1 / 6.77:1 boundary */
  background-image: repeating-linear-gradient(45deg,
    var(--mark) 0 1px, transparent 1px 4px);
}

.ev__pos {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: .3rem .55rem; line-height: 1.25rem;   /* fixed: a mono swap cannot reflow */
}
.ev__ord { color: var(--ink); }
.ev__sep { color: var(--rule-strong); }

/* THE ANTI-DIVERGENCE MECHANISM. "of N" is not typed - it is counted from the
   byline printed above, in document order, so editing the author list edits
   the number. If a rail is later left stale it shows slots that disagree with
   its own printed count. */
.pub { counter-reset: evauth; }
.pub .authors > li { counter-increment: evauth; }
.ev__n::before { content: counter(evauth); }

.ev__prov {
  font-size: var(--t-sm); line-height: 1.55; color: var(--ink-muted);
  max-width: var(--measure); margin: calc(var(--s-2) * -1) 0 var(--s-3);
}

.ev__key { list-style: none; margin: var(--s-5) 0 0; display: grid; gap: var(--s-3); }
.ev__key li {
  display: flex; align-items: flex-start; gap: .6rem;
  font-size: var(--t-sm); line-height: 1.5; color: var(--ink-muted);
}
.ev__key b { color: var(--ink); font-weight: 600; }
.ev__key .ev__rail { --slot-w: 10px; --slot-h: 15px; --slot-gap: 3px; margin-top: .18rem; }

/* 3. THE NAME-AUTHORITY CARD. The papers say Mansourifilestan, the inbox says
   Mansouri, and a Scholar search on the wrong one returns nothing. A catalogue
   cross-reference is the form a librarian invented for exactly this. */
.ev__xref {
  border: 1px solid var(--edge); background: var(--paper-2);
  border-radius: 2px; padding: var(--s-4); margin: 0 0 var(--s-5);
}
.ev__xref .note-head { margin-bottom: var(--s-3); }
.ev__xref .ev__xh {
  font-family: var(--font-meta); font-size: var(--t-mono-md);
  letter-spacing: .06em; color: var(--ink); line-height: 1.25rem;
  margin: 0 0 var(--s-3); overflow-wrap: anywhere;
}
.ev__xl { list-style: none; margin: 0; display: grid; gap: var(--s-2); }
.ev__xl > li {
  display: grid; grid-template-columns: 4.75rem minmax(0, 1fr); gap: .5rem;
  font-family: var(--font-meta); font-size: var(--t-mono-sm); line-height: 1.15rem;
}
.ev__xk { color: var(--ink-muted); }
.ev__xv { color: var(--ink); overflow-wrap: anywhere; }
.ev__xn { margin: var(--s-4) 0 0; }

/* 4. TWO VOYAGES, ONE AXIS. */
.ev__sc { margin: 0; max-width: var(--page); }
.ev__sc-key {
  display: flex; flex-wrap: wrap; gap: .4rem 1.5rem; margin: 0 0 var(--s-5);
  font-size: var(--t-mono-sm); color: var(--ink-muted); line-height: 1.15rem;
}
.ev__sc-key span { display: inline-flex; align-items: center; gap: .5rem; }
.ev__k { display: inline-block; width: 26px; height: 11px; flex: none; border-radius: 1px; }
.ev__k--meas  { background: var(--accent); border: 1px solid var(--accent); }
.ev__k--model { border: 1px solid var(--mark);
  background-image: repeating-linear-gradient(45deg, var(--mark) 0 1px, transparent 1px 5px); }

.ev__sc-row { display: grid; gap: .5rem; margin: 0 0 var(--s-6); }
.ev__sc-l {
  font-size: var(--t-mono-sm); text-transform: uppercase; letter-spacing: .12em;
  color: var(--ink-muted); margin: 0; line-height: 1.15rem;
}
.ev__sc-track { display: block; width: 100%; border-bottom: 1px solid var(--rule); }
.ev__sc-bar {
  display: block; height: 22px; min-width: 3px;
  width: calc(100% * var(--n) / 1562);   /* arithmetic left in the sheet on purpose */
  border-radius: 1px 0 0 0;
}
.ev__sc-bar--meas  { background: var(--accent); border: 1px solid var(--accent); }
.ev__sc-bar--model { border: 1px solid var(--mark);
  background-image: repeating-linear-gradient(45deg, var(--mark) 0 1px, transparent 1px 5px); }
.ev__sc-v { margin: 0; font-size: var(--t-mono-md); color: var(--ink); line-height: 1.25rem; }
.ev__sc-d { margin: 0; font-size: var(--t-sm); line-height: 1.55; color: var(--ink-muted);
  max-width: var(--measure); }
.ev__sc-d b { color: var(--ink); font-weight: 600; }
.ev__sc-ax { font-size: var(--t-mono-xs); color: var(--ink-muted);
  line-height: 1.15rem; margin: 0 0 var(--s-5); }
.ev__sc-note { font-size: var(--t-sm); line-height: 1.55; color: var(--ink-muted);
  max-width: var(--measure); margin: 0 0 var(--s-4); }
.ev__sc-say { font-size: var(--t-base); line-height: 1.5; color: var(--ink);
  max-width: var(--measure); margin: 0; }

@media (min-width: 44rem) {
  .ev__sc-row { grid-template-columns: 8.5rem minmax(0, 1fr);
                column-gap: var(--s-4); row-gap: .55rem; }
  .ev__sc-l     { grid-column: 1; grid-row: 1; text-align: right; padding-top: .2rem; }
  .ev__sc-track { grid-column: 2; grid-row: 1; }
  .ev__sc-v     { grid-column: 2; grid-row: 2; }
  .ev__sc-d     { grid-column: 2; grid-row: 3; }
}
/* 11 slots at 13px pitch is 143px - fits a 320px viewport inside .pub. */
@media (max-width: 26rem) {
  .ev__rail { --slot-w: 10px; --slot-h: 16px; --slot-gap: 3px; }
  .ev__sc-bar { height: 18px; }
}

/* 5. PRINT. style.css:669 carries an unscoped `* { background:#fff !important }`
   which would blank the filled slot out of every rail and flatten both bars to
   identical empty boxes - in the one medium a professor is likeliest to use.
   Class selectors are (0,1,0) and beat the universal selector, so every mark is
   re-asserted, and provenance is carried on paper by SHAPE (filled block vs
   heavy outline) rather than by a background image a printer may drop. */
@media print {
  .ev__rail i, .ev__sc-bar, .ev__k { print-color-adjust: exact; -webkit-print-color-adjust: exact; }
  .ev__rail i { border: 1px solid #666 !important; background: #fff !important; }
  .ev__me { background: #000 !important; border-color: #000 !important; }
  .ev__me--rep { background: #fff !important; background-image: none !important;
                 border: 2px solid #000 !important; }
  .ev__sc-bar--meas, .ev__k--meas { background: #000 !important; border: 1px solid #000 !important; }
  .ev__sc-bar--model, .ev__k--model { background: #fff !important;
    background-image: none !important; border: 2px solid #000 !important; }
  .ev__sc-track { border-bottom: 1px solid #666 !important; }
  .ev__xref { background: #fff !important; border: 1px solid #000 !important; }
  .ev__ord, .ev__sc-v { font-weight: 600; }
  .ev__sc, .ev__sc-row { break-inside: avoid; }
  #evidence::after, .pubs-wrap::after { display: none; }
}

/* 6. FORCED COLOURS. Backgrounds and borders both flatten to system colours,
   so the filled slot would otherwise be indistinguishable from the empty ones.
   Same fix the docket already uses: fill in CanvasText with forced-color-adjust
   off for solid, a heavy outline for hatched. */
@media (forced-colors: active) {
  .ev__rail i { border-color: CanvasText; background: Canvas; }
  .ev__me { background: CanvasText; border-color: CanvasText; forced-color-adjust: none; }
  .ev__me--rep { background: Canvas; background-image: none;
                 border: 2px solid CanvasText; forced-color-adjust: none; }
  .ev__sc-bar--meas, .ev__k--meas { background: CanvasText; border-color: CanvasText;
                                    forced-color-adjust: none; }
  .ev__sc-bar--model, .ev__k--model { background: Canvas; background-image: none;
                                      border: 2px solid CanvasText; forced-color-adjust: none; }
  .ev__sc-track { border-bottom-color: CanvasText; }
  .ev__xref { border-color: CanvasText; background: Canvas; }
}


/* RPCSS */
/* --------------------------------------------------------------------------
   10 · REPLY — THE RUN ENDS
   Appended to style.css. Purely additive except the one :root line below.

   NO NEW GROUND TOKEN. The reply band is already --paper-2, which the token
   block at the top of this file reserves for exactly this ("recessed: reply
   band"). Every contrast ratio below is computed against it, both themes:

     LIGHT #F3EFE7   --ink 15.35 · --ink-muted 6.14 · --accent 7.96
                     --mark 5.42 · --edge 3.07 · --s-heat 4.05
     DARK  #1B1E23   --ink 13.42 · --ink-muted 6.90 · --accent 7.92
                     --mark 6.18 · --edge 3.11 · --s-heat 6.24

   --edge clears SC 1.4.11 on this ground but only just (3.07 / 3.11), so it
   is used ONLY for the bore hairline inside the figure. The one interactive
   control in this section is bounded by --accent at 7.96 / 7.92.
   -------------------------------------------------------------------------- */

/* ADD to the :root block in section 1 of style.css, after --t-h1: */
:root {
  --t-ask: clamp(2rem, 1.3rem + 3.4vw, 3.5rem);   /* the page's largest line */
}

/* The run sheet is torn off the same fanfold the 1403 is feeding in 08.
   One gradient, no JS. .reply already carries the solid rule above it. */
.reply { position: relative; }
.reply::before {
  content: ""; position: absolute; left: 0; right: 0; top: 3px; height: 1px;
  background: repeating-linear-gradient(90deg,
              var(--rule-strong) 0 3px, transparent 3px 8px);
}
.reply-lede { margin-bottom: var(--s-6); }

/* ---- the run sheet ------------------------------------------------------ */
.rs { margin: 0 0 var(--s-7); max-width: var(--page); }

.rs__head {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: var(--s-2) var(--s-3); padding-bottom: var(--s-3);
}
.rs__title {
  font-size: var(--t-mono-xs); text-transform: uppercase; letter-spacing: .09em;
  color: var(--ink);
}
.rs__mode {
  font-size: var(--t-mono-xs); color: var(--ink-muted); margin-left: auto;
}

/* Fixed height, and the canvas carries width/height attributes matching its
   box, so this reserves its final size before a byte of JS runs: no CLS in
   either direction, and none while the numbers walk. */
.rs-plot { position: relative; height: 128px; }
.rs-cv   { display: block; width: 100%; height: 128px; position: relative; z-index: 1; }

/* The no-JS plate. Discrete stops, never a gradient ramp — a hard-edged
   linear-gradient cannot band or dither on a 6-bit panel. Marks sit at the
   real fluxes: q"_CHF at 60.73 %, q"_max at 84.67 %, cursor at the design
   point, 52.52 %. The channel below is wet end to end, which is the truth at
   200 kW m^-2 (CHF margin 1.60). */
.rs-plot::before {
  content: ""; position: absolute; left: 0; right: 0; top: 20px; height: 10px;
  background: linear-gradient(to right,
    var(--accent)  0     52.52%,
    var(--rule)    52.52% 60.60%,
    var(--s-heat)  60.60% 60.90%,
    var(--rule)    60.90% 84.55%,
    var(--s-heat)  84.55% 84.80%,
    var(--rule)    84.80% 100%);
}
.rs-plot::after {
  content: ""; position: absolute; left: 0; right: 0; top: 69px; height: 38px;
  background: var(--accent);
  border-top: 1px solid var(--edge); border-bottom: 1px solid var(--edge);
}
.rs-ns { position: absolute; inset: 0; }
.rs-ns::before {                               /* you are here */
  content: ""; position: absolute; left: 52.52%; top: 12px;
  width: 3px; height: 22px; margin-left: -1px; background: var(--ink);
}
.rs-plot.is-live::before,
.rs-plot.is-live::after,
.rs-plot.is-live .rs-ns { display: none; }

/* Cells, not a pre-formatted string: every value is nowrap and tabular, so a
   number changing length can never reflow the prose beneath it. */
.rs-cells {
  display: grid; gap: var(--s-3) var(--s-5);
  grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr));
  margin: var(--s-4) 0 0; padding-top: var(--s-3);
  border-top: 1px solid var(--rule);
}
.rs-cells dt {
  font-size: var(--t-mono-xs); text-transform: uppercase; letter-spacing: .09em;
  color: var(--ink-muted); white-space: nowrap;
}
.rs-cells dd {
  margin: .25rem 0 0; font-size: var(--t-mono-md); color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* A stamp is down or it is not: three states, signalled by weight and by
   inversion, never by a transition and never by hue alone. */
.rs-stamp {
  display: inline-block; font-size: var(--t-mono-xs); letter-spacing: .16em;
  padding: .15rem .5rem; border: 1px solid var(--edge); color: var(--ink);
  transition: none;
}
.rs-stamp.is-lost { border-color: var(--ink); font-weight: 600; }
.rs-stamp.is-end  {
  background: var(--ink); color: var(--paper); border-color: var(--ink);
  font-weight: 600;
}

.rs__src {
  font-size: var(--t-mono-sm); line-height: 1.65; color: var(--ink-muted);
  margin: var(--s-4) 0 0; max-width: var(--measure);
}
.rs__src code { font-size: 1em; color: var(--ink); }
.rs__src b    { font-weight: 500; color: var(--ink); }

/* ---- the question ------------------------------------------------------- */
.ask {
  font-family: var(--font-prose); font-weight: 600;
  font-size: var(--t-ask); line-height: 1.08; letter-spacing: -.022em;
  max-width: var(--page); text-wrap: balance;
  margin: var(--s-8) 0;
  padding-block: var(--s-6);
  border-block: 1px solid var(--rule-strong);
}

/* ---- the relay ---------------------------------------------------------- */
/* Always its own line under the address: the oscillogram needs 304 px and the
   address is set at --t-lede, so sharing a line is a wrap waiting to happen. */
.relay {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-3);
  margin: var(--s-3) 0 0;
}
.relay__btn {
  font-size: var(--t-mono-sm); letter-spacing: .12em; text-transform: uppercase;
  min-height: 44px; padding: 0 var(--s-4);       /* SC 2.5.8, with room */
  color: var(--accent); background: transparent;
  border: 1px solid var(--accent); border-radius: 2px;
  cursor: pointer;
  transition: none;                              /* a contact does not ease */
}
.relay__btn.is-closed { background: var(--accent); color: var(--accent-ink); }
.relay__btn:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.relay__cv { display: block; width: 304px; max-width: 100%; height: 46px; }
.relay__cap {
  display: block; margin-top: var(--s-3); max-width: var(--measure);
  font-size: var(--t-mono-sm); line-height: 1.65; color: var(--ink-muted);
}
.relay__mod {
  color: var(--mark); text-transform: uppercase; letter-spacing: .09em;
}
.relay__cap b { color: var(--ink); font-weight: 600; }
.relay__cap i { color: var(--ink); font-style: italic; }

/* ---- the two link lines ------------------------------------------------- */
.reply-ids {
  font-size: var(--t-mono-md); line-height: 2; max-width: var(--page);
  margin-top: var(--s-6); padding-top: var(--s-4);
  border-top: 1px solid var(--rule); overflow-wrap: anywhere;
}
.reply-last {
  font-size: var(--t-mono-sm); color: var(--ink-muted);
  margin-top: var(--s-4); max-width: var(--measure);
}

/* ---- print -------------------------------------------------------------- */
@media print {
  /* the sheet's own `* { background: #fff !important }` would erase an
     inverted stamp, so on paper the run state signals by rule weight */
  .rs-plot, .relay, .reply::before { display: none !important; }
  .rs-stamp.is-end, .rs-stamp.is-lost {
    border: 2px solid #000 !important; font-weight: 700;
  }
  .ask { border-block-color: #000; }
}

/* ---- forced colours ------------------------------------------------------ */
@media (forced-colors: active) {
  /* canvas cannot follow a forced palette; the cells, the stamp and the
     visually-hidden sentence carry every number the figure draws */
  .rs-plot, .relay__cv { display: none; }
  .relay__btn { border: 1px solid ButtonBorder; color: ButtonText; background: ButtonFace; }
  .relay__btn.is-closed { border-color: Highlight; color: Highlight; text-decoration: underline; }
  .rs-stamp.is-end, .rs-stamp.is-lost {
    background: Canvas; color: CanvasText; border: 2px solid Highlight;
  }
}


/* BOPCSS:ship */
/* =========================================================================
   TWO DOORS, ONE CHANNEL — cruise-ship tri-generation balance of plant.
   Every colour is a custom property already defined in style.css /
   effects.css, in both themes.  There is no hex in this file, so a theme
   flip costs nothing and needs no JS at all.
   ========================================================================= */

.bop { margin: 4rem 0; }
.bop .standfirst--tight { margin-top: .6rem; }

.bop__fig { margin: 1.6rem 0 0; }

/* the frame reserves its space from first paint — no layout shift, ever */
.bop__frame {
  aspect-ratio: 1200 / 960;
  width: 100%;
  background: var(--paper-2);
  border: 1px solid var(--rule-strong);
  border-radius: 2px;
}
.bop__svg { display: block; width: 100%; height: 100%; }

/* ---------------------------------------------------------------- text --
   Every glyph sits on paper, never on a ribbon.  Where one must cross a
   ribbon it gets a paper halo through paint-order, so contrast is against
   --paper at every frame regardless of what is moving underneath.        */
.bop__svg text {
  font: 500 10px/1.2 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  fill: var(--ink);
  paint-order: stroke fill;
  stroke: var(--paper);
  stroke-width: 3px;
  stroke-linejoin: round;
}
.bop-bandlbl { font-size: 9.5px; font-weight: 600; letter-spacing: .06em;
               fill: var(--ink-muted); text-transform: uppercase; }
.bop-shiplbl { font-size: 11px; font-weight: 700; letter-spacing: .04em; }
.bop-shipsub, .bop-vollbl, .bop-volout { font-size: 9.5px; fill: var(--ink-muted); }
.bop-vollbl  { fill: var(--paper); stroke: none; font-weight: 600; }
.bop-tags text { font-size: 9.5px; font-weight: 700; letter-spacing: .05em; }
.bop-tags .tt  { font-weight: 500; letter-spacing: 0; fill: var(--ink-muted); }
.bop-chanlbl   { font-size: 8.5px; letter-spacing: .05em; }
.kl { font-size: 9.5px; fill: var(--ink-muted); }
.tk { font-size: 9px; font-weight: 600; letter-spacing: .04em; fill: var(--ink-muted); }
.bop-trknote { font-size: 9.5px; fill: var(--ink); }
.bop-sea-val { font-size: 10px; font-weight: 700; letter-spacing: .06em; fill: var(--ink); }
.bop-deflbl  { font-size: 10px; font-weight: 700; fill: var(--mark); }

/* ------------------------------------------------------------ the ship -- */
.bop-hullsea  { fill: var(--rule); opacity: .32; }
.bop-hull     { fill: var(--ink); }
.bop-super    { fill: var(--ink-muted); }
.bop-funnel   { fill: var(--ink); }
.bop-wl       { stroke: var(--edge); stroke-width: 1; stroke-dasharray: 5 4; fill: none; }
.bop-tk       { fill: var(--seq-6); stroke: var(--paper); stroke-width: 1.5; }
.bop-plant    { fill: var(--mark);  stroke: var(--paper); stroke-width: 1.5; }
.bop-lead     { stroke: var(--edge); stroke-width: 1; fill: none; }
.bop-detail   { stroke: var(--edge); stroke-width: 1; stroke-dasharray: 3 4; fill: none; }

/* ------------------------------------------------------- volume bar ---- */
.bop-vol { stroke: var(--paper); stroke-width: 1; }
.bop-vol--tk   { fill: var(--seq-6); }
.bop-vol--fc   { fill: var(--seq-4); }
.bop-vol--rest { fill: var(--rule-strong); }
.bop-vol--hx   { fill: var(--s-heat); }
.bop-vol--ts   { fill: var(--s-cool); }
.bop-vol--bt   { fill: var(--s-elec); }

/* ------------------------------------------------------------ ribbons --
   ONE rule the reader can check with a ruler: stroke-width is the power.
   Hue AND texture carry the carrier, so the plate survives greyscale,
   print and every form of colour vision.                                 */
.rb--fuel { stroke: var(--seq-6); }
.rb--slip { stroke: var(--seq-6); stroke-dasharray: 10 6; }  /* same substance, unconverted */
.rb--elec { stroke: var(--s-elec); }
.rb--heat { stroke: var(--s-heat); }
.rb--cool { stroke: var(--s-cool); }
.rb--loss { stroke: var(--s-loss); }

/* the advecting stipple: one dashed stroke per ribbon, 46 % of the body
   width so it can never leave it.  Direction you can watch. */
.st { opacity: .5; }
.st--fuel, .st--slip { stroke: var(--paper);   stroke-dasharray: 3 17; }
.st--elec { stroke: var(--paper); stroke-dasharray: 2 6; }
.st--heat { stroke: var(--paper); stroke-dasharray: 3 13; }
.st--cool { stroke: var(--paper); stroke-dasharray: 8 12; }
.st--loss { stroke: var(--paper); stroke-dasharray: 2 10; }

.bop-chevpath { fill: none; stroke: var(--paper); stroke-width: 1.6;
                stroke-linecap: round; stroke-linejoin: round; }

/* -------------------------------------------------------- the channel --
   The fuel ribbon narrowing as it is consumed.  The split IS the taper. */
.bop-chan { fill: var(--seq-6); stroke: var(--paper); stroke-width: 1; }
.bop-prof { fill: none; stroke: var(--mark); stroke-width: 1.6;
            stroke-linejoin: round; stroke-linecap: round; }

/* ------------------------------------------------------- components ----
   Provenance is drawn, not footnoted: a reader sees in one sweep which
   four blocks carry Table 4 numbers and which sixteen do not.           */
.nd            { fill: var(--paper); }
.nd--paper     { stroke: var(--ink);  stroke-width: 1.5; }
.nd--mine      { stroke: var(--edge); stroke-width: 1.2; stroke-dasharray: 4 3; }
.nd--load      { stroke: var(--edge); stroke-width: 1.2; fill: var(--paper-2); }
.bop-bus       { fill: var(--s-elec); stroke: none; }
.bop-fn        { fill: var(--paper-2); }
.bop-fcfill, .bop-hxfill { fill: var(--accent); }
.bop-soc       { fill: var(--s-elec); }
.bop-pframe    { fill: none; stroke: var(--rule); stroke-width: 1; }

.bop-waterline { stroke: var(--edge); stroke-width: 1.5; fill: none; }
.bop-sea       { fill: var(--s-loss); }

.bop-knobstem  { stroke: var(--mark); stroke-width: 1.5; fill: none; }
.bop-knobdot   { fill: var(--mark); stroke: var(--paper); stroke-width: 1.5; }

/* ----------------------------------------------------------- tracks ---- */
.bop-trkbg   { fill: var(--paper); stroke: var(--rule); stroke-width: 1; }
.bop-trkcap  { stroke: var(--s-heat); stroke-width: 1.5; stroke-dasharray: 6 4; fill: none; }
.bop-trkA    { fill: none; stroke: var(--s-heat); stroke-width: 2; stroke-linejoin: round; }
.bop-trkB    { fill: none; stroke: var(--seq-6);  stroke-width: 2; stroke-linejoin: round; }
.bop-cursline { stroke: var(--mark); stroke-width: 1.5; fill: none; }

/* --------------------------------------------------------- controls ---- */
.bop-ctl { margin-top: 1rem; display: grid; gap: .7rem; }
.bop-ctl__states { display: flex; flex-wrap: wrap; gap: .4rem; }
.bop-state {
  font: 600 12px/1 ui-sans-serif, system-ui, sans-serif;
  padding: .5rem .7rem; border: 1px solid var(--edge); border-radius: 2px;
  background: var(--paper); color: var(--ink); cursor: pointer;
}
.bop-state[aria-pressed="true"] { background: var(--accent); color: var(--paper);
                                  border-color: var(--accent); }
.bop-ctl__u { display: flex; align-items: center; gap: .7rem; flex-wrap: wrap; }
.bop-ctl__u label { font: 600 12px/1 ui-sans-serif, system-ui, sans-serif;
                    letter-spacing: .04em; color: var(--ink-muted); }
.bop-u { flex: 1 1 240px; min-width: 200px; accent-color: var(--mark); }
.bop-auto {
  font: 700 11px/1 ui-sans-serif, system-ui, sans-serif; letter-spacing: .08em;
  padding: .45rem .8rem; border: 1px solid var(--edge); border-radius: 2px;
  background: var(--paper); color: var(--ink); cursor: pointer;
}
.bop-auto[aria-pressed="true"] { background: var(--mark); color: var(--paper);
                                 border-color: var(--mark); }
.bop-state:focus-visible, .bop-auto:focus-visible, .bop-u:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
.bop-mode  { font: 600 12px/1.4 ui-sans-serif, system-ui, sans-serif;
             color: var(--ink-muted); margin: 0; }
.bop.is-manual .bop-mode { color: var(--mark); }
.bop-annun { font: 600 12px/1.4 ui-sans-serif, system-ui, sans-serif;
             color: var(--ink-muted); margin: 0; }
.bop-annun.is-hot { color: var(--mark); }

/* the no-JS notice steps aside once the module has actually started */
.bop-nojs { font-size: .85rem; color: var(--ink-muted); margin: 0; }
.bop.is-live .bop-nojs { display: none; }
.bop:not(.is-live) .bop-ctl__states,
.bop:not(.is-live) .bop-ctl__u { opacity: .45; pointer-events: none; }

/* --------------------------------------------------------- readouts ----
   Fixed grid, tabular numerals and reserved widths, so no value can move
   the layout by getting longer.                                          */
.bop-read {
  margin-top: 1rem; display: grid; gap: 1px;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  background: var(--rule); border: 1px solid var(--rule);
}
.bop-read > div { background: var(--paper-2); padding: .5rem .6rem; }
.bop-read span { display: block; font-size: .68rem; letter-spacing: .05em;
                 text-transform: uppercase; color: var(--ink-muted); }
.bop-read b { display: block; font-variant-numeric: tabular-nums;
              font-size: .95rem; color: var(--ink); }

.bop-audit, .bop-resid {
  margin: .7rem 0 0; font: 600 11.5px/1.5 ui-monospace, "SFMono-Regular", Menlo, monospace;
  letter-spacing: .02em; color: var(--ink-muted);
}
.bop-audit.is-open, .bop-resid.is-open { color: var(--mark); }
.bop-residnote { margin: .35rem 0 0; font-size: .82rem; color: var(--ink-muted);
                 max-width: 62ch; }

.bop-sr { position: absolute; width: 1px; height: 1px; overflow: hidden;
          clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }

.bop__fig figcaption { margin-top: .9rem; font-size: .85rem; color: var(--ink-muted);
                       max-width: 72ch; }

/* ---------------------------------------------------------- honesty ---- */
.bop-honest { margin-top: 2rem; padding: 1.1rem 1.2rem;
              background: var(--paper-2); border-left: 2px solid var(--rule-strong); }
.bop-honest h3 { margin: 0 0 .6rem; font-size: 1rem; }
.bop-honest p  { margin: 0 0 .7rem; font-size: .9rem; max-width: 74ch; }
.bop-honest p:last-child { margin-bottom: 0; }

/* ------------------------------------------------------------- data ---- */
.bop-data { margin-top: 1.4rem; }
.bop-data summary { cursor: pointer; font-weight: 600; font-size: .9rem; }
.bop-data table { width: 100%; border-collapse: collapse; margin: 1rem 0 1.6rem;
                  font-size: .8rem; font-variant-numeric: tabular-nums; }
.bop-data caption { text-align: left; font-weight: 600; padding-bottom: .4rem;
                    color: var(--ink-muted); }
.bop-data th, .bop-data td { border-bottom: 1px solid var(--rule);
                             padding: .3rem .5rem; text-align: left; vertical-align: top; }
.bop-data thead th { border-bottom: 1px solid var(--rule-strong); }
.bop-cite { font-size: .8rem; color: var(--ink-muted); }

/* ----------------------------------------------------------- mobile ----
   No horizontal scroll and no reflowed drawing.  The SVG scales, so the
   ship and the volume bar — the two-second payload, both pure shape —
   survive intact.  The fine text inside the plate is hidden below 760 px
   because 9 px type at a 0.3 scale is not text, and every number it was
   carrying is in the readout strip and the data table as real HTML.     */
@media (max-width: 760px) {
  .bop-fine, .bop-tags, .kl, .tk, .bop-trknote { display: none; }
  .bop__frame { aspect-ratio: 1200 / 720; }
  .bop-read { grid-template-columns: repeat(auto-fit, minmax(128px, 1fr)); }
}

/* --------------------------------------------------- reduced motion ----
   A finished plate, never a blank: the stipple stays, stationary, so every
   width is still readable and every chevron still shows direction.  The
   slider still works, because a control is not an animation.            */
@media (prefers-reduced-motion: reduce) {
  .bop .st { opacity: .38; }
}
.bop.is-still .st { opacity: .38; }

@media (forced-colors: active) {
  .rb, .st, .bop-trkA, .bop-trkB { forced-color-adjust: none; }
}


/* BOPCSS:boat */
/* ==========================================================================
   Fig. 12 - 09:51:27.  THE CONSOLE IS THE PLANT.

   Every colour on this plate is a token.  No hex anywhere.  Stream inks
   (--s-elec 4.16:1 light, --s-heat 4.38:1) FAIL AA for text and are
   therefore NEVER text here: they are ribbon fill and legend swatch only.
   Every word on the plate is --ink or --ink-muted, over --paper or
   --paper-2.  That makes the contrast check static - it cannot vary with
   the animation, and it passes for every frame by construction.

   NOTHING IN THIS FILE IS TRANSITIONED OR ANIMATED.  There is no
   @keyframes, no transition, and therefore no prefers-reduced-motion
   override: the JS kernel is the only motion authority on this site, as
   intended.
   ========================================================================== */

.bop {
  margin: var(--s-8) 0 0;
  padding: 0;
}
.bop__h {
  font-family: var(--font-meta);
  font-size: var(--t-mono-sm);
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ink-muted);
  margin: 0 0 var(--s-2);
}
.bop__kick {
  font-size: var(--t-sm);
  line-height: 1.55;
  color: var(--ink-muted);
  max-width: var(--measure);
  margin: 0 0 var(--s-5);
}

/* --------------------------------------------------------------------------
   THE KEY.  Permanent, never hover-gated.  It carries the width law, the
   width floor, and the four provenance marks.  The bar figure inside it is
   also the no-JavaScript picture: thirteen streams at their printed widths,
   to scale against each other.
   -------------------------------------------------------------------------- */
.bop__key {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s-4);
  border-top: 1px solid var(--rule-strong);
  border-bottom: 1px solid var(--rule);
  padding: var(--s-4) 0;
  margin: 0 0 var(--s-5);
}
@media (min-width: 46rem) { .bop__key { grid-template-columns: 20rem minmax(0, 1fr); gap: var(--s-6); } }
.bop__key h5 {
  font-family: var(--font-meta);
  font-size: var(--t-mono-xs);
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ink-muted);
  margin: 0 0 var(--s-2);
  font-weight: 400;
}
.bop__key p { font-size: var(--t-sm); line-height: 1.5; color: var(--ink-muted); margin: 0 0 var(--s-2); }
.bop__key .bop__law {
  font-family: var(--font-meta);
  font-size: var(--t-mono-md);
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.bop__bars { width: 100%; height: auto; display: block; }
.bop__bars text { font-family: var(--font-meta); font-size: 9.5px; fill: var(--ink-muted); }
.bop__bars .bl { fill: var(--ink); }
.bop__bars .r-h2 { fill: var(--ink);     fill-opacity: .24; }
.bop__bars .r-el { fill: var(--s-elec);  fill-opacity: .32; }
.bop__bars .r-th { fill: var(--s-heat);  fill-opacity: .32; }
.bop__bars .r-ls { fill: var(--s-loss);  fill-opacity: .32; }
.bop__bars .hair { stroke: var(--edge); stroke-width: 1; }

/* provenance key */
.bop__prov { list-style: none; margin: 0; padding: 0; font-family: var(--font-meta); font-size: var(--t-mono-xs); }
.bop__prov li { margin: 0 0 .28rem; color: var(--ink-muted); }
.bop__prov b { color: var(--ink); font-weight: 400; }

/* --------------------------------------------------------------------------
   THE PLATE.  Blocks are real HTML, so text is selectable, scalable,
   screen-readable and reflows - and NO TEXT IS EVER DRAWN TO CANVAS, which
   is why there is no per-frame text layout anywhere in this figure.
   -------------------------------------------------------------------------- */
.bop__plate {
  position: relative;
  /* asymmetric on a narrow screen on purpose: the west margin is the spine
     channel the skip routes run in, the east margin holds the thermal and
     loss taps.  Above 40rem the plate is symmetric and every route becomes a
     straight run in a gutter. */
  padding: 0 88px 58px 78px;
  background: var(--paper);
}
@media (min-width: 40rem) { .bop__plate { padding: 0 128px 70px; } }

.bop__cv {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  z-index: 0;
}
.bop__grid {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: 14px;
  row-gap: 54px;
}
.bop__labs { position: absolute; inset: 0; z-index: 2; pointer-events: none; }

.bop__b {
  grid-column: 1 / -1;
  background: var(--paper);
  border: 1px solid var(--edge);
  padding: .45rem .6rem .5rem;
  font-family: var(--font-meta);
  font-variant-numeric: tabular-nums;
  font-size: var(--t-mono-xs);
  line-height: 1.5;
  color: var(--ink);
}
.bop__b > h5 {
  font-size: var(--t-mono-sm);
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ink);
  font-weight: 400;
  margin: 0 0 .1rem;
}
/* exactly ONE field per block is set at the larger size - the block's
   rating - so the eye lands on nine anchors before it reads anything */
.bop__rate {
  display: block;
  font-size: var(--t-mono-md);
  color: var(--ink);
  border-bottom: 1px solid var(--rule);
  padding-bottom: .22rem;
  margin-bottom: .28rem;
}
.bop__b dl { display: grid; grid-template-columns: auto 1fr; gap: 0 .5rem; margin: 0; }
.bop__b dt { color: var(--ink-muted); white-space: nowrap; }
.bop__b dd { margin: 0; text-align: right; overflow-wrap: anywhere; }
@media (min-width: 40rem) { .bop__b dd { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } }
.bop__b--loss { border-style: dashed; }

/* wide layout: the plant reads top-down, sources to bus to sinks, and every
   route is then a straight run in a gutter.  A straight ribbon has no corner
   to get wrong. */
@media (min-width: 40rem) {
  .bop__b--h2  { grid-column: 4 / 11; grid-row: 1; }
  .bop__b--fc1 { grid-column: 1 / 7;  grid-row: 2; }
  .bop__b--cv1 { grid-column: 1 / 7;  grid-row: 3; }
  .bop__b--fc2 { grid-column: 7 / 13; grid-row: 2; }
  .bop__b--cv2 { grid-column: 7 / 13; grid-row: 3; }
  .bop__b--bus { grid-column: 1 / 13; grid-row: 4; }
  .bop__b--bat { grid-column: 1 / 5;  grid-row: 5; }
  .bop__b--drv { grid-column: 5 / 9;  grid-row: 5; }
  .bop__b--unc { grid-column: 9 / 13; grid-row: 5; }
  .bop__b--bus dl { grid-template-columns: repeat(4, auto 1fr); }
}

/* stream labels.  Two lines - name, then number - so a label is never wider
   than its gutter. */
.bop__lab {
  position: absolute;
  max-width: 84px;
  background: var(--paper);
  font-family: var(--font-meta);
  font-variant-numeric: tabular-nums;
  line-height: 1.25;
  text-align: center;
  padding: 1px 3px;
}
.bop__lab i { display: block; font-style: normal; font-size: var(--t-mono-xs); color: var(--ink-muted); }
.bop__lab b { display: block; font-size: var(--t-mono-sm); color: var(--ink); font-weight: 400; }
.bop__lab[data-anch="mid"]   { transform: translate(-50%, -50%); }
.bop__lab[data-anch="end"]   { transform: translate(-100%, -108%); text-align: right; }
.bop__lab[data-anch="start"] { transform: translate(0, -108%); text-align: left; }
.bop__lab[data-anch="below"] { transform: translate(10px, -58%); text-align: left; }
@media (min-width: 40rem) { .bop__lab { max-width: 124px; } }

/* THE SUB-FLOOR MARK.  A stream under the width floor is a declared state,
   not a small ribbon - so its number is set at the same size as a block's
   rating and boxed, and the canvas draws a bracket around the route. */
.bop__lab--sub { border: 1px solid var(--edge); padding: 2px 5px; }
.bop__lab--sub b { font-size: var(--t-mono-md); }
.bop__lab--zero b, .bop__lab--off b { color: var(--ink-muted); }
.bop__lab--off b { letter-spacing: .06em; }

/* --------------------------------------------------------------------------
   PROVENANCE.  Two channels on every mark, so colour is never load-bearing.
   -------------------------------------------------------------------------- */
.p-p { }                                                     /* printed  */
.p-l::before {                                               /* live     */
  content: ""; display: inline-block; width: .3em; height: .3em;
  background: var(--ink); margin-right: .3em; vertical-align: .12em;
}
.p-d { text-decoration: underline dotted var(--edge); text-underline-offset: .18em; }
.p-a { color: var(--ink-muted); }                            /* assumed  */
.p-a::before { content: "["; }
.p-a::after  { content: "]"; }
[data-prov="on"] .p-p::after { content: "\00A0P"; font-size: .78em; color: var(--ink-muted); }
[data-prov="on"] .p-l::after { content: "\00A0L"; font-size: .78em; color: var(--ink-muted); }
[data-prov="on"] .p-d::after { content: "\00A0D"; font-size: .78em; color: var(--ink-muted); }
[data-prov="on"] .p-a::after { content: "]\00A0A"; font-size: .78em; }

/* --------------------------------------------------------------------------
   CONTROLS, LEDGER, STRIP
   -------------------------------------------------------------------------- */
.bop__ctl {
  display: grid;
  gap: var(--s-3) var(--s-5);
  grid-template-columns: minmax(0, 1fr);
  border-top: 1px solid var(--rule-strong);
  margin-top: var(--s-5);
  padding-top: var(--s-4);
  font-family: var(--font-meta);
  font-size: var(--t-mono-sm);
}
@media (min-width: 46rem) { .bop__ctl { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); } }
.bop__ctl fieldset { border: 0; margin: 0; padding: 0; }
.bop__ctl legend {
  font-size: var(--t-mono-xs); letter-spacing: .09em; text-transform: uppercase;
  color: var(--ink-muted); padding: 0; margin-bottom: .3rem;
}
.bop__ctl label { display: inline-flex; align-items: center; gap: .3rem; margin-right: .9rem; color: var(--ink); }
.bop__ctl input[type="range"] { width: 100%; accent-color: var(--accent); }
.bop__ctl .bop__val { color: var(--ink); font-variant-numeric: tabular-nums; }
.bop__btn {
  font: inherit; font-family: var(--font-meta); font-size: var(--t-mono-sm);
  color: var(--ink); background: var(--paper-2);
  border: 1px solid var(--edge); padding: .3rem .7rem; cursor: pointer;
}
.bop__btn:hover { background: var(--select); }
.bop__latch {
  min-height: 1.7em; margin: var(--s-3) 0 0;
  font-family: var(--font-meta); font-size: var(--t-mono-sm);
  letter-spacing: .04em; color: var(--ink);
}
.bop__say { min-height: 1.7em; font-size: var(--t-sm); color: var(--ink-muted); margin: var(--s-3) 0 0; }

.bop__strip {
  display: grid; gap: var(--s-3);
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  margin-top: var(--s-5); padding-top: var(--s-4);
  border-top: 1px solid var(--rule);
  font-family: var(--font-meta); font-size: var(--t-mono-xs);
  font-variant-numeric: tabular-nums; color: var(--ink);
}
.bop__strip h5 {
  font-size: var(--t-mono-xs); letter-spacing: .09em; text-transform: uppercase;
  color: var(--ink-muted); margin: 0 0 .2rem; font-weight: 400;
}
.bop__lamp::before {
  content: ""; display: inline-block; width: .5em; height: .5em;
  border: 1px solid var(--edge); margin-right: .35em; vertical-align: .02em;
}

.bop__ledger {
  margin-top: var(--s-5); padding: var(--s-4) 0;
  border-top: 1px solid var(--rule-strong); border-bottom: 1px solid var(--rule-strong);
  font-family: var(--font-meta); font-variant-numeric: tabular-nums;
  font-size: var(--t-mono-sm); color: var(--ink);
}
.bop__ledger dl { display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); gap: var(--s-2) var(--s-4); margin: .4rem 0 0; }
.bop__ledger dt { font-size: var(--t-mono-xs); letter-spacing: .06em; text-transform: uppercase; color: var(--ink-muted); }
.bop__ledger dd { margin: 0; font-size: var(--t-mono-md); }
.bop__ledger .bop__verdict { font-family: var(--font-prose); font-size: var(--t-sm); line-height: 1.55; color: var(--ink-muted); margin: var(--s-3) 0 0; max-width: var(--measure); }

/* charts.  aspect-ratio reserves the box, so unhiding the canvas cannot
   shift the page. */
.bop__stage { position: relative; margin-top: var(--s-5); aspect-ratio: 900 / 250; }
@media (max-width: 46rem) { .bop__stage { aspect-ratio: 420 / 460; } }
.bop__cvc { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.bop__clegend {
  display: flex; flex-wrap: wrap; gap: var(--s-4);
  font-family: var(--font-meta); font-size: var(--t-mono-xs); color: var(--ink-muted);
  margin-top: var(--s-2);
}
.bop__clegend b { color: var(--ink); font-weight: 400; }
.bop__sw { display: inline-block; width: 1.7rem; height: .18rem; vertical-align: .18em; margin-right: .35rem; }
.bop__sw--1 { background: var(--seq-6); }
.bop__sw--2 { background: linear-gradient(to right, var(--seq-3) 0 60%, transparent 60% 100%); }
.bop__sw--s { background: var(--mark); height: .5rem; width: .5rem; }

.bop .fx-table h5 {
  font-family: var(--font-meta); font-size: var(--t-mono-sm);
  letter-spacing: .09em; text-transform: uppercase; color: var(--ink);
  font-weight: 400; margin: var(--s-5) 0 0;
}
.bop .fx-table ul { font-size: var(--t-sm); line-height: 1.55; color: var(--ink-muted); }
.bop .fx-table p  { font-size: var(--t-sm); line-height: 1.55; color: var(--ink-muted); max-width: var(--measure); }

.bop figcaption { font-size: var(--t-base); line-height: 1.6; max-width: var(--measure); margin-top: var(--s-5); }
.bop figcaption p { margin: 0 0 var(--s-4); }
.bop figcaption .bop__lede { font-size: var(--t-lede); line-height: 1.4; }

/* print: the plate's canvas carries no text, so print shows the blocks, the
   bar figure and the full table */
@media print {
  .bop__cv, .bop__cvc, .bop__ctl, .bop__labs { display: none !important; }
  .bop details { display: block; }
  .bop details > div { display: block !important; }
}


/* ATOMCSS:mea */
/* SECTIONBLOCK:mea */
/* ==================================================================== *
 * 04 - THE CELL, CUT OPEN.                          section + instrument
 * -------------------------------------------------------------------- *
 * GROUND. A cool cyan-teal - the one hue the six existing grounds leave
 * free (#question blue, #range green, #training violet) and far enough
 * from all three to tell apart in peripheral vision while scrolling.
 * Contrast computed, not asserted, against the section ground:
 *
 *                    #E9F2F4 light        #101A1D dark
 *   --ink              14.77 : 1  AAA       14.41 : 1  AAA
 *   --ink-muted         6.18 : 1  AA         7.30 : 1  AAA
 *   --accent            7.92 : 1  AAA        8.60 : 1  AAA
 *
 * TEXTURE. The layer stack itself as wallpaper - channel, diffusion
 * layer, catalyst, membrane, repeating. The subject at pattern scale.
 *
 * NO TEXT IS EVER PAINTED ON A CANVAS in this section. Every label and
 * every number is DOM on --paper, so AA holds at every frame by
 * construction rather than by testing, and a screen reader gets all of
 * it. It is also why the dimension rail is a CSS grid above the cut and
 * not type inside the drawing.
 * ==================================================================== */

:root { --g-mea: #E9F2F4; }
@media (prefers-color-scheme: dark) { :root { --g-mea: #101A1D; } }
:root[data-theme="dark"] { --g-mea: #101A1D; }
:root[data-theme="light"] { --g-mea: #E9F2F4; }

#mea::before { background-color: var(--g-mea); }
#mea::after {
  background-image: repeating-linear-gradient(to right,
    var(--rule-strong) 0 1px, transparent 1px 27px,
    var(--rule) 27px 28px, transparent 28px 46px,
    var(--rule-strong) 46px 47px, transparent 47px 53px,
    var(--rule) 53px 54px, transparent 54px 92px);
  opacity: .28;
}

.mea__fig { margin: var(--s-6) 0 0; }
.mea__how {
  font-size: var(--t-sm); color: var(--ink-muted);
  border-left: 2px solid var(--rule-strong); padding-left: var(--s-3);
}
.mea__how b { color: var(--ink); font-weight: 600; }

/* ---- dimension rail. Column widths are written by the script from the
   same array the canvas draws the bands from; the values below are the
   no-JS fallback and MUST match BW in the script. */
.mea__rail {
  display: grid;
  grid-template-columns: .1550fr .1350fr .0750fr .2700fr .0950fr .1350fr .1350fr;
  gap: 0; margin-bottom: .3rem;
  border-bottom: 1px solid var(--rule-strong);
}
.mea__band {
  padding: 0 .25rem .3rem; min-width: 0;
  border-left: 1px solid var(--rule);
  font-family: var(--font-meta); font-size: var(--t-mono-xs);
  line-height: 1.35; color: var(--ink-muted);
}
.mea__band:first-child { border-left: 0; }
.mea__band b {
  display: block; color: var(--ink); font-weight: 500;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mea__band i, .mea__band em {
  display: block; font-style: normal; font-variant-numeric: tabular-nums;
}
.mea__band em { color: var(--mark); }
.mea__band--mem b, .mea__band--pt b { color: var(--accent); }
.mea__brk { font-size: var(--t-mono-xs); color: var(--ink-muted); margin: 0 0 .6rem; }

.mea__stage { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s-4); }
@media (min-width: 60rem) {
  .mea__stage { grid-template-columns: minmax(0, 1fr) 15.5rem; }
}

/* THE PLATE. A fixed aspect-ratio box. The no-JS SVG and both canvases
   occupy the identical rectangle, absolutely positioned, so the JS
   upgrade, a quality change, a theme flip and a reduced-motion swap all
   resolve inside the same box and CLS is zero on every path. */
.mea__plate {
  position: relative; aspect-ratio: 16 / 11;
  border: 1px solid var(--edge); background: var(--paper);
}
@media (min-width: 46rem) { .mea__plate { aspect-ratio: 96 / 47; } }
.mea__skel, .mea__bg, .mea__fx {
  position: absolute; inset: 0; width: 100%; height: 100%; display: block;
}
.mea__skel { z-index: 0; }
.mea__bg   { z-index: 1; }
.mea__fx   { z-index: 2; }
[data-mode] .mea__skel { display: none; }

.mea__tag {
  position: absolute; z-index: 3;
  font-family: var(--font-meta); font-size: var(--t-mono-xs);
  letter-spacing: .04em; color: var(--ink-muted);
  background: var(--paper); padding: .1rem .3rem;
  border: 1px solid var(--rule); border-radius: 2px;
  pointer-events: none; white-space: nowrap;
}
.mea__tag--a    { left: .3rem;  top: 41%; }
.mea__tag--c    { right: .3rem; top: 41%; }
.mea__tag--load { left: 50%; transform: translateX(-50%); bottom: .3rem; }
.mea__tag--det  { left: .3rem; top: 53.5%; }
.mea__tag--pol  { right: .25rem; top: .25rem; color: var(--mark); }
@media (max-width: 46rem) {
  .mea__tag--det { font-size: 9px; }
  .mea__tag--a, .mea__tag--c { display: none; }
}

.mea__side { min-width: 0; }
.mea__ann {
  margin: 0 0 .5rem; padding: .3rem .45rem;
  font-family: var(--font-meta); font-size: var(--t-mono-xs);
  letter-spacing: .05em; color: var(--ink-muted);
  border: 1px solid var(--rule); background: var(--paper);
}
.mea__ann[data-on="1"] {
  color: var(--mark); border-color: var(--mark); font-weight: 600;
}
.mea__polbox {
  position: relative; aspect-ratio: 3 / 2;
  border: 1px solid var(--edge); background: var(--paper);
}
.mea__polbox .mea__skel, .mea__pol {
  position: absolute; inset: 0; width: 100%; height: 100%; display: block;
}
.mea__pol { z-index: 1; }
.mea__axes {
  display: flex; justify-content: space-between;
  font-size: var(--t-mono-xs); color: var(--ink-muted); margin: .2rem 0 .7rem;
}

.mea__hud { margin: 0 0 .7rem; font-size: var(--t-mono-xs); }
.mea__hud div {
  display: flex; justify-content: space-between; gap: .5rem;
  padding: .12rem 0; border-bottom: 1px dotted var(--rule);
}
.mea__hud--rule { border-top: 1px solid var(--rule-strong); margin-top: .3rem; padding-top: .3rem; }
.mea__hud dt { color: var(--ink-muted); }
.mea__hud dd {
  margin: 0; font-family: var(--font-meta); color: var(--ink-muted);
  white-space: nowrap;
}
.mea__hud dd b {
  color: var(--ink); font-weight: 500;
  font-variant-numeric: tabular-nums;
  display: inline-block; min-width: 4.2ch; text-align: right;
}

/* ---- the ledger. Putting a live conservation counter on a physics
   animation is a dare; it only survives because stoichiometry is the
   control flow. */
.mea__ledger {
  margin: 0; padding: .45rem .55rem;
  border: 1px solid var(--rule-strong); background: var(--paper-2);
  font-size: var(--t-mono-xs); color: var(--ink-muted);
}
.mea__ledger span {
  display: flex; justify-content: space-between; gap: .6rem;
  line-height: 1.9; border-bottom: 1px dotted var(--rule);
}
.mea__ledger span:last-child { border-bottom: 0; }
.mea__ledger i { font-style: normal; }
.mea__ledger b {
  color: var(--ink); font-weight: 500; font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.mea__bal { letter-spacing: .06em; }
.mea__bal[data-ok="1"] { color: var(--accent); }
.mea__bal[data-ok="0"] { color: var(--mark); font-weight: 700; }

/* ---- controls. touch-action keeps a vertical swipe scrolling the page
   instead of being captured by a slider, which is the single most likely
   thing to be annoying on a phone. */
.mea__ctl { display: none; }
[data-mode] .mea__ctl {
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem 1.1rem;
  margin-top: var(--s-4); padding-top: var(--s-3);
  border-top: 1px solid var(--rule-strong);
  font-family: var(--font-meta); font-size: var(--t-mono-sm);
  color: var(--ink-muted);
}
.mea__ctlrow { display: flex; align-items: center; gap: .5rem; }
.mea__slider {
  flex: 0 1 11rem; accent-color: var(--accent);
  touch-action: pan-y; min-height: 1.75rem;
}
.mea__slider:focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; }
.mea__val { color: var(--ink); white-space: nowrap; }
.mea__val b { font-weight: 500; font-variant-numeric: tabular-nums;
              display: inline-block; min-width: 3.5ch; text-align: right; }
.mea__btns { display: flex; gap: .5rem; flex-wrap: wrap; }
.mea__ctl button {
  font: inherit; color: var(--ink); background: var(--paper);
  border: 1px solid var(--edge); border-radius: 2px;
  padding: .3rem .6rem; min-height: 1.9rem; cursor: pointer;
}
.mea__ctl button:hover { border-color: var(--ink); }
.mea__ctl button:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.mea__rearm { color: var(--accent) !important; border-color: var(--accent) !important; }
[data-src="scroll"] #meaI { opacity: .75; }

/* ---- SAME PUSH. Cheapest object in the section and the highest
   spectacle per byte, so it is built first and cut last. */
.mea__push {
  margin-top: var(--s-5); padding: var(--s-4);
  border: 1px solid var(--rule-strong); background: var(--paper-2);
  max-width: var(--page);
}
.mea__push-h {
  margin: 0 0 .4rem; font-family: var(--font-meta);
  font-size: var(--t-mono-md); letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink); font-weight: 500;
}
.mea__push-i {
  margin: 0 0 .8rem; max-width: var(--measure);
  font-size: var(--t-sm); line-height: 1.55; color: var(--ink-muted);
}
.mea__push-i b { color: var(--ink); }
.mea__push-g { display: grid; gap: .5rem; }
@media (min-width: 42rem) { .mea__push-g { grid-template-columns: 1fr 1fr; } }
.mea__push-row {
  display: grid; grid-template-columns: 1.4rem 1fr; gap: .1rem .5rem;
  align-items: start;
  padding: .5rem .6rem; border: 1px solid var(--rule); background: var(--paper);
  font-size: var(--t-mono-xs); color: var(--ink-muted);
}
.mea__push-row > b, .mea__push-row > span:not(.mea__push-site) { grid-column: 2; }
.mea__push-row > b { color: var(--ink); font-weight: 500; font-family: var(--font-prose);
                     font-size: var(--t-sm); margin-bottom: .15rem; }
.mea__push-row b { font-variant-numeric: tabular-nums; }
.mea__push-site {
  grid-row: 1 / -1; width: 1.1rem; height: 1.1rem; border-radius: 50%;
  border: 2px solid var(--edge); margin-top: .15rem;
}
.mea__push-site--a { background: var(--accent); }
.mea__push-site--c { background: var(--mark); }
.mea__push-clock b {
  color: var(--mark); font-weight: 600; font-size: var(--t-mono-md);
  letter-spacing: .02em;
}
.mea__push-f { margin: .7rem 0 0; font-size: var(--t-mono-xs); color: var(--ink-muted); }
.mea__push-f b { color: var(--ink); }

/* ---- caption. With JS off the still text is the DEFAULT and the live
   one is opted into by the script - the same mechanism as .cook__cap. */
.mea__cap {
  margin: var(--s-5) 0 0; max-width: var(--measure);
  font-size: var(--t-sm); line-height: 1.55; color: var(--ink-muted);
}
.mea__cap b { color: var(--ink); font-variant-numeric: tabular-nums; }
.mea__cap--live { display: none; }
[data-mode="live"] .mea__cap--live  { display: inline; }
[data-mode="live"] .mea__cap--still { display: none; }
.mea__model {
  display: block; margin-top: .55rem;
  font-family: var(--font-meta); font-size: var(--t-mono-xs);
  line-height: 1.6; letter-spacing: .02em; color: var(--ink-muted);
}
.mea__model b { color: var(--ink); font-weight: 500; }

/* * THE NO-JS FIGURE. Inline SVG, so it is text, it scales, it is
   theme-aware through the same tokens, and a screen reader gets the
   description plus the tables. Not a raster. */
.mk-ch   { fill: var(--paper); }
.mk-gdl  { fill: var(--paper-2); }
.mk-cl   { fill: var(--seq-1); fill-opacity: .55; }
.mk-mem  { fill: var(--seq-0); fill-opacity: .72; }
.mk-detail { fill-opacity: .72; }
.mk-edge, .mk-frame, .mk-axis {
  fill: none; stroke: var(--edge); stroke-width: 1; vector-effect: non-scaling-stroke;
}
.mk-wire { fill: none; stroke: var(--edge); stroke-width: 2.2; vector-effect: non-scaling-stroke; }
.mk-tab  { fill: none; stroke: var(--rule-strong); stroke-width: 3.4; vector-effect: non-scaling-stroke; }
.mk-load { fill: var(--paper); stroke: var(--edge); stroke-width: 1.4; vector-effect: non-scaling-stroke; }
.mk-fil  { fill: none; stroke: var(--s-heat); stroke-width: 2; opacity: .55; vector-effect: non-scaling-stroke; }
.mk-pt circle    { r: 2.6px; fill: var(--ink); fill-opacity: .60; }
.mk-drop         { fill: var(--s-cool); fill-opacity: .52; }
.mk-h2 circle    { r: 3px; fill: var(--seq-2); }
.mk-h2 path      { stroke: var(--seq-2); stroke-width: 1.7; fill: none; vector-effect: non-scaling-stroke; }
.mk-o2 circle    { r: 3.9px; fill: var(--mark); }
.mk-o2 path      { stroke: var(--mark); stroke-width: 1.35; fill: none; vector-effect: non-scaling-stroke; }
.mk-vap circle   { r: 2.6px; fill: var(--s-cool); }
.mk-vap path     { stroke: var(--s-cool); stroke-width: 1.5; fill: none; vector-effect: non-scaling-stroke; }
.mk-veh circle   { fill: var(--accent); }
.mk-chg .dia     { fill: var(--accent); }
.mk-veh .pm, .mk-chg .pm, .mk-e path {
  stroke: var(--paper); stroke-width: 1.5; fill: none; stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}
.mk-chg--big .pm, .mk-veh--big .pm { stroke-width: 2; }
.mk-e circle     { fill: var(--s-elec); }
.mk-hop {
  fill: none; stroke: var(--accent); stroke-width: 1.3;
  stroke-dasharray: 2 2.4; opacity: .72; vector-effect: non-scaling-stroke;
}
.mk-ov   { fill: var(--s-loss); fill-opacity: .62; stroke: var(--edge); stroke-width: 1;
           vector-effect: non-scaling-stroke; }
.mk-lead { fill: none; stroke: var(--rule-strong); stroke-width: 1;
           stroke-dasharray: 3 3; vector-effect: non-scaling-stroke; }
.mk-net  { fill: none; stroke: var(--rule); stroke-width: 1; vector-effect: non-scaling-stroke; }
.mk-node circle { r: 1.9px; fill: var(--ink-muted); fill-opacity: .48; }
.mk-so3 circle  { r: 2.2px; fill: var(--mark); }
.mk-grid { fill: none; stroke: var(--rule); stroke-width: 1; vector-effect: non-scaling-stroke; }
.mk-erev { fill: none; stroke: var(--rule-strong); stroke-width: 1;
           stroke-dasharray: 4 3; vector-effect: non-scaling-stroke; }
.mk-pol  { fill: none; stroke: var(--accent); stroke-width: 1.6; vector-effect: non-scaling-stroke; }
.mk-cap  { fill: none; stroke: var(--s-heat); stroke-width: 1.2;
           stroke-dasharray: 3 3; vector-effect: non-scaling-stroke; }
.mk-dot  { fill: var(--ink); stroke: var(--paper); stroke-width: 1.4; }

@media (forced-colors: active) {
  .mea__bg, .mea__fx, .mea__pol { display: none !important; }
  .mea__skel { display: block !important; }
  .mk-edge, .mk-frame, .mk-axis, .mk-wire, .mk-tab, .mk-h2 path, .mk-o2 path,
  .mk-vap path, .mk-hop, .mk-lead, .mk-net, .mk-grid, .mk-erev, .mk-pol,
  .mk-cap, .mk-fil { stroke: CanvasText; }
  .mk-ch, .mk-gdl, .mk-cl, .mk-mem, .mk-load, .mk-drop { fill: Canvas; stroke: CanvasText; }
  .mk-pt circle, .mk-h2 circle, .mk-o2 circle, .mk-vap circle, .mk-veh circle,
  .mk-chg .dia, .mk-e circle, .mk-node circle, .mk-so3 circle, .mk-ov, .mk-dot { fill: CanvasText; }
  .mk-veh .pm, .mk-chg .pm, .mk-e path { stroke: Canvas; }
  .mea__tag, .mea__ann, .mea__ledger { border-color: CanvasText; }
}

@media print {
  .mea__ctl { display: none !important; }
  .mea__stage { grid-template-columns: minmax(0, 1fr); }
  .mea__push { break-inside: avoid; }
}


/* ATOMCSS:fission */
/* ==========================================================================
   SECTIONCSS:fission - 09 . The 0.65 per cent.  Appended to effects.css.
   Nothing here eases: not one transition and not one cubic-bezier.  Every
   motion in the section is a quantity changing at the rate its own physics
   sets, on the two canvases and on two SVG attributes.

   THE GROUND.  The darkest section on the site, and dark in BOTH themes - a
   deliberate single-look commitment, because it is a control room and a
   control room is not lit differently on a bright day.  The theme still
   moves it: light mode a cooler lifted graphite, dark mode a near-black.
   The palette is redefined on the section using the SITE'S OWN token names,
   so every rule here and every FX.token() call in the JS keeps working.

   CONTRAST, computed (WCAG 2.1 relative luminance).  Grounds: --paper
   #121820 light / #0B0E12 dark; --paper-2 #191E25 is the recessed ground
   the tanks sit on, and is the worst case.
     --ink 13.3:1  --ink-muted 7.3:1  --accent 8.6:1  --mark/--s-heat 6.8:1
     --s-cool 8.3:1  --s-loss 7.3:1   --edge 3.7:1 (MEETS SC 1.4.11)
     --seq-0 3.8:1 (ramp floor)       --seq-5 13.0:1
   --rule 1.3:1 and --rule-strong 1.9:1 are decorative hairlines and are
   never the sole boundary of anything interactive.

   The six --seq steps are 1.2-1.5:1 against EACH OTHER, which is correct:
   the tanks are ordered by decay constant, so the quantity is a magnitude
   and a single-hue sequential ramp is the honest encoding.  Colour is never
   the only channel - every tank carries its lambda, half-life, beta_i and
   share of beta as real text, a 1 px --rule-strong separator, and a fill
   height driven by its own C_i.

   No text is ever painted on a canvas here.  Every number a reader can read
   is DOM text on an opaque plate, so AA is structural, and pin flares
   (capped at 0.50 alpha, 1.25 pin radii, relaxing on 0.22 s) can never sit
   behind type.
   ========================================================================== */

#fission {
  /* --- grounds --- */
  --paper:       #121820;
  --paper-2:     #191E25;
  /* --- ink --- */
  --ink:         #ECE7DE;
  --ink-muted:   #A8AEB7;
  /* --- accents --- */
  --accent:      #7FC6DE;
  --accent-ink:  #0B0E12;
  --mark:        #E7936A;
  /* --- lines --- */
  --rule:        #2B3138;
  --rule-strong: #464D57;
  --edge:        #6E7681;
  --focus:       #7FC6DE;
  --select:      #21323B;
  /* --- streams --- */
  --s-elec:      #7FC6DE;
  --s-heat:      #E7936A;
  --s-cool:      #62CBAC;
  --s-loss:      #A8AEB7;
  /* --- the six precursor groups, ordered by lambda --- */
  --seq-0: #3A7CB4; --seq-1: #4A8FC6; --seq-2: #67A9DA;
  --seq-3: #88C2E8; --seq-4: #AAD8F2; --seq-5: #CFE9FA;
  --seq-6: #E6F3FC;

  /* reaches into the gutter without ever being able to overflow */
  --fs-bleed: max(0px, min(4rem, calc((100vw - 100%) / 2 - 1rem)));

  position: relative;
  isolation: isolate;
  color: var(--ink);
  margin-inline: calc(-1 * var(--fs-bleed));
  padding: clamp(2rem, 1.25rem + 3.5vw, 3.5rem) var(--fs-bleed);
  background-color: var(--paper);
  border-block: 1px solid var(--rule-strong);
  color-scheme: dark;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) #fission { --paper: #0B0E12; --paper-2: #161B22; }
}
:root[data-theme="dark"] #fission { --paper: #0B0E12; --paper-2: #161B22; }

/* --- TEXTURE.  A 34 px lattice, the pin pitch, plus a soft core wash.
   Two gradients, no image, no repaint cost.  Sits under everything. --- */
#fission::before {
  content: "";
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background-image:
    repeating-linear-gradient(0deg,
      color-mix(in srgb, var(--ink) 5%, transparent) 0 1px, transparent 1px 34px),
    repeating-linear-gradient(90deg,
      color-mix(in srgb, var(--ink) 5%, transparent) 0 1px, transparent 1px 34px),
    radial-gradient(120% 80% at 50% 0%,
      color-mix(in srgb, var(--accent) 7%, transparent), transparent 70%);
  mask-image: linear-gradient(180deg, #000 0 82%, transparent);
}
/* Security print.  The only disclaimer that survives a screenshot: no crop
   of the instrument can be taken without it.  DOM, not canvas, so it is
   there with JavaScript off too. */
#fission .fs__wm {
  position: absolute; inset: 0; z-index: 0; overflow: hidden;
  pointer-events: none; user-select: none;
}
#fission .fs__wm i {
  display: block;
  font: 400 var(--t-mono-sm)/2.9 var(--font-meta);
  letter-spacing: .34em; text-transform: uppercase; font-style: normal;
  color: color-mix(in srgb, var(--ink) 7%, transparent);
  white-space: nowrap; transform: rotate(-24deg) translateX(-6%);
}
#fission > *:not(.fs__wm) { position: relative; z-index: 1; }

/* --- copy ------------------------------------------------------------- */
.fs__in { max-width: var(--page); margin-inline: auto; }
.fs__in > * { margin-bottom: var(--s-5); }
.fs__in > *:last-child { margin-bottom: 0; }
.fs__in > .body { max-width: var(--measure); }
.fs__in .sec-h { color: var(--ink); }
.fs__in .chip { border-color: var(--edge); color: var(--ink-muted); }

/* --- the reactivity rail ---------------------------------------------- */
.fs__rail {
  position: relative; height: 2.6rem;
  border: 1px solid var(--edge); background: var(--paper-2);
}
.fs__rail-band {                       /* PROMPT CRITICAL, +100 to +110 c */
  position: absolute; inset-block: 0;
  left: 95.238%; right: 0;             /* (100+100)/210 .. (110+100)/210  */
  background: repeating-linear-gradient(-45deg,
    color-mix(in srgb, var(--s-heat) 42%, transparent) 0 3px,
    transparent 3px 7px);
  border-left: 1px solid var(--s-heat);
}
.fs__rail-zero { position: absolute; inset-block: 0; left: 47.619%; width: 1px;
  background: var(--rule-strong); }
.fs__rail-act, .fs__rail-cmd {
  position: absolute; top: -1px; bottom: -1px; width: 2px; margin-left: -1px;
}
.fs__rail-act { background: var(--accent); }
.fs__rail-cmd { background: var(--ink-muted); width: 1px;
  border-top: 5px solid var(--ink-muted); }
.fs__rail-tick {
  position: absolute; bottom: 2px; transform: translateX(-50%);
  font: 400 var(--t-mono-xs)/1 var(--font-meta); color: var(--ink-muted);
}
.fs__rail-lab {
  position: absolute; top: 3px; right: 4px;
  font: 400 var(--t-mono-xs)/1 var(--font-meta); letter-spacing: .08em;
  color: var(--s-heat);
}

/* --- THE CORE, the hero object.  Full width of the figure, and the tanks
   and the meters are a subordinate band beneath it. ---------------------- */
.fs__core {
  position: relative;
  aspect-ratio: 2.1 / 1;
  border: 1px solid var(--edge);
  background: var(--paper-2);
}
@media (max-width: 720px) { .fs__core { aspect-ratio: 1.35 / 1; } }
@media (max-width: 480px) { .fs__core { aspect-ratio: 1 / 1; } }
.fs__core > canvas,
.fs__core > .fs__static {
  position: absolute; inset: 0; width: 100%; height: 100%; display: block;
}
/* The canvas is NEVER given [hidden] or display:none — FX.fitCanvas reads
   getBoundingClientRect() and a zero box means onSize never fires and the
   whole effect silently never sizes itself.  It starts transparent over
   the SVG plate and is revealed once frame one has painted. */
.fs__cv { opacity: 0; }
.fs__fig.is-live .fs__cv { opacity: 1; }
.fs__fig.is-live .fs__static { visibility: hidden; }
.fs__cv--core { cursor: crosshair; touch-action: manipulation; }

/* legend plate, sitting on the core.  Opaque, so nothing behind it can
   take its contrast away. */
.fs__legend {
  position: absolute; left: 8px; bottom: 8px; z-index: 2;
  display: flex; flex-wrap: wrap; gap: .1rem .8rem;
  max-width: calc(100% - 16px);
  padding: .3rem .5rem;
  background: var(--paper); border: 1px solid var(--rule-strong);
  font: 400 var(--t-mono-xs)/1.5 var(--font-meta); color: var(--ink-muted);
}
.fs__legend b { font-weight: 400; color: var(--ink); }
.fs__key { display: inline-flex; align-items: center; gap: .3rem; }
.fs__key::before {
  content: ""; width: 14px; height: 2px; background: currentColor;
}
.fs__key--fast { color: var(--s-heat); }
.fs__key--epi  { color: var(--s-loss); }
.fs__key--th   { color: var(--s-cool); }
.fs__key--th::before { width: 4px; height: 4px; }
.fs__key--del  { color: var(--seq-3); }
.fs__key--del::before { width: 8px; height: 8px; border-radius: 50%;
  background: none; border: 1.5px solid currentColor; }
.fs__key--tr   { color: var(--accent); }
.fs__loupecap {
  position: absolute; right: 8px; top: 8px; z-index: 2; width: min(23%, 15rem);
  padding: .3rem .45rem;
  background: var(--paper); border: 1px solid var(--rule-strong);
  font: 400 var(--t-mono-xs)/1.45 var(--font-meta); color: var(--ink-muted);
}

/* --- THE BAND: six tanks, then the meters ----------------------------- */
.fs__band { display: grid; gap: var(--s-4); }
@media (min-width: 820px) { .fs__band { grid-template-columns: 1fr 19.5rem; } }

.fs__tankwrap { position: relative; aspect-ratio: 3.6 / 1; min-height: 8rem;
  border: 1px solid var(--edge); background: var(--paper-2); }
.fs__tankwrap > canvas { position: absolute; inset: 0; width: 100%; height: 100%;
  display: block; }
.fs__tanks {
  display: grid; grid-template-columns: repeat(6, 1fr);
  gap: 0; margin-top: .35rem;
}
.fs__tank {
  padding: .3rem .35rem;
  border-left: 1px solid var(--rule-strong);
  font: 400 var(--t-mono-xs)/1.45 var(--font-meta);
  color: var(--ink-muted);
}
.fs__tank:first-child { border-left: 0; }
.fs__tank b { display: block; font-weight: 400; color: var(--ink);
  letter-spacing: .04em; }
.fs__tank .fs__sw { display: inline-block; width: 9px; height: 9px;
  margin-right: .3rem; vertical-align: -1px; }
.fs__tank--0 .fs__sw { background: var(--seq-0); }
.fs__tank--1 .fs__sw { background: var(--seq-1); }
.fs__tank--2 .fs__sw { background: var(--seq-2); }
.fs__tank--3 .fs__sw { background: var(--seq-3); }
.fs__tank--4 .fs__sw { background: var(--seq-4); }
.fs__tank--5 .fs__sw { background: var(--seq-5); }
.fs__tank em { font-style: normal; color: var(--accent); }

/* --- meters ------------------------------------------------------------ */
.fs__meters { display: grid; gap: var(--s-3); align-content: start; }
.fs__dialbox { display: grid; grid-template-columns: 1fr 3.6rem; gap: .5rem;
  padding: .5rem; border: 1px solid var(--edge); background: var(--paper-2); }
.fs__dial, .fs__pow, .fs__ribbon { display: block; width: 100%; height: auto; }
.fs__dial-face { fill: none; stroke: var(--rule-strong); stroke-width: 1; }
.fs__dial-tick { stroke: var(--ink-muted); stroke-width: 1; }
.fs__dial-tick--maj { stroke: var(--ink); stroke-width: 1.4; }
.fs__dial text, .fs__pow text {
  font: 400 9px var(--font-meta); fill: var(--ink-muted);
}
.fs__dial .fs__inf { font-size: 13px; fill: var(--ink); }
.fs__needle { stroke: var(--accent); stroke-width: 2; stroke-linecap: round; }
.fs__needle-hub { fill: var(--accent); }
.fs__pow-frame { fill: none; stroke: var(--rule-strong); }
.fs__powbar { fill: var(--s-cool); }
.fs__ribbonbox { padding: .4rem .5rem; border: 1px solid var(--edge);
  background: var(--paper-2); }
.fs__ribbon .fs__line { fill: none; stroke: var(--accent); stroke-width: 1.3;
  vector-effect: non-scaling-stroke; }
.fs__ribbon .fs__base { fill: none; stroke: var(--rule-strong); stroke-width: 1;
  vector-effect: non-scaling-stroke; }

.fs__read { margin: 0; display: grid; grid-template-columns: auto 1fr;
  gap: .1rem .6rem; font: 400 var(--t-mono-sm)/1.6 var(--font-meta); }
.fs__read dt { color: var(--ink-muted); }
.fs__read dd { margin: 0; color: var(--ink); text-align: right;
  font-variant-numeric: tabular-nums; }
.fs__read dd.is-wide { text-align: left; grid-column: 1 / -1;
  color: var(--ink-muted); }

/* --- controls ---------------------------------------------------------- */
.fs__controls { display: grid; gap: var(--s-3);
  padding: var(--s-3); border: 1px solid var(--edge); background: var(--paper-2); }
.fs__rodrow { display: grid; gap: .35rem; }
.fs__rodrow label { font: 400 var(--t-mono-sm)/1.4 var(--font-meta);
  color: var(--ink-muted); letter-spacing: .04em; }
.fs__rod {
  -webkit-appearance: none; appearance: none;
  width: 100%; height: 1.6rem; margin: 0; background: transparent;
}
.fs__rod::-webkit-slider-runnable-track {
  height: 8px; background: var(--paper); border: 1px solid var(--edge);
}
.fs__rod::-moz-range-track {
  height: 8px; background: var(--paper); border: 1px solid var(--edge);
}
.fs__rod::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 14px; height: 24px; margin-top: -9px;
  background: var(--accent); border: 1px solid var(--accent-ink);
}
.fs__rod::-moz-range-thumb {
  width: 14px; height: 24px; border-radius: 0;
  background: var(--accent); border: 1px solid var(--accent-ink);
}
.fs__rod:focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; }

.fs__btns { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; }
.fs__btn {
  font: 400 var(--t-mono-sm)/1 var(--font-meta); letter-spacing: .03em;
  padding: .55rem .7rem; color: var(--ink);
  background: var(--paper); border: 1px solid var(--edge); cursor: pointer;
}
.fs__btn:hover { border-color: var(--accent); color: var(--accent); }
.fs__btn:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.fs__btn[aria-pressed="true"] { background: var(--accent); color: var(--accent-ink);
  border-color: var(--accent); }
.fs__bylabel { display: inline-flex; align-items: center; gap: .4rem;
  font: 400 var(--t-mono-xs)/1.3 var(--font-meta); color: var(--ink-muted);
  padding: .4rem .5rem; border: 1px dashed var(--edge); }
.fs__bypass { accent-color: var(--s-heat); width: 1rem; height: 1rem; }

/* --- plates ------------------------------------------------------------ */
.fs__plate {
  padding: var(--s-4); border: 1px solid var(--rule-strong);
  background: var(--paper-2);
}
.fs__plate p { margin: 0 0 var(--s-3); }
.fs__plate p:last-child { margin-bottom: 0; }
.fs__plate .mono { font: 400 var(--t-mono-md)/1.65 var(--font-meta);
  color: var(--ink-muted); }
.fs__plate .mono strong { color: var(--ink); font-weight: 400; }
.fs__stamp { border-color: var(--s-heat); }
.fs__stamp p { font: 400 var(--t-mono-md)/1.7 var(--font-meta); color: var(--ink); }

.fs__rmcap { display: none; }
@media (prefers-reduced-motion: reduce) { .fs__rmcap { display: block; } }

.fs__cap { font-size: var(--t-sm); color: var(--ink-muted);
  max-width: var(--measure); }
.fs__cap b { color: var(--ink); font-weight: 400; }

/* --- the data table.  Real text, no hover gating, always available. ---- */
#fission .fx-table summary { color: var(--ink-muted); }
#fission .fx-table summary:focus-visible { outline: 2px solid var(--focus); }
.fs__data { width: 100%; border-collapse: collapse; margin-top: .5rem;
  font: 400 var(--t-mono-xs)/1.5 var(--font-meta); }
.fs__data caption { text-align: left; color: var(--ink-muted);
  padding-bottom: .4rem; }
.fs__data th, .fs__data td { padding: .28rem .5rem; text-align: right;
  border-bottom: 1px solid var(--rule); font-variant-numeric: tabular-nums; }
.fs__data th[scope="row"], .fs__data td:first-child { text-align: left;
  font-weight: 400; }
.fs__data thead th { color: var(--ink); border-bottom-color: var(--rule-strong); }
.fs__data tbody th { color: var(--ink-muted); }
.fs__data td { color: var(--ink); }
.fs__scroll { overflow-x: auto; }

/* --- the JS-off plate -------------------------------------------------- */
.fs__static .st-mod  { fill: var(--paper-2); }
.fs__static .st-pin  { fill: var(--paper); fill-opacity: .14;
                       stroke: var(--rule-strong); }
.fs__static .st-hot  { fill: var(--s-heat); fill-opacity: .38; }
.fs__static .st-grid { stroke: var(--rule); stroke-opacity: .6; }
.fs__static .st-edge { fill: none; stroke: var(--edge); }
.fs__static .st-fast { stroke: var(--s-heat); stroke-width: 1.15; fill: none; }
.fs__static .st-epi  { stroke: var(--s-loss); stroke-width: 1; fill: none; }
.fs__static .st-th   { fill: var(--s-cool); }
.fs__static .st-del  { fill: none; stroke: var(--seq-3); stroke-width: 1.3; }
.fs__static .st-frag { fill: var(--s-heat); }
.fs__static .st-rule { stroke: var(--ink-muted); stroke-width: 1.4; }
.fs__static text { font: 400 9px var(--font-meta); fill: var(--ink-muted); }

/* Forced colours: keep every boundary, drop every decorative wash. */
@media (forced-colors: active) {
  #fission { border-color: CanvasText; }
  .fs__core, .fs__tankwrap, .fs__controls, .fs__plate, .fs__legend,
  .fs__loupecap, .fs__dialbox, .fs__ribbonbox { border-color: CanvasText; }
  #fission::before, .fs__wm { display: none; }
}
