/* ═══════════════════════════════════════════════════════════════
   KS3 — page styles.  MRB-183.

   This file is a TRANSLATION of Claude Design's KS3 reference set,
   frozen at docs/ks3/design-reference/ and specified in SPEC.md.
   Design's artifacts are React prototypes carrying 507 inline style
   attributes and zero CSS classes; inline styles cannot be shared
   across 185 lessons, so every value below arrives here as a class
   consuming a token.

   RULES OF THE ROAD

   1. No raw palette values in this file. Everything comes from the
      `.rd[data-mode="ks3"]` block in tokens.css. If KS3 needs a new
      colour, it is added THERE (architecture.md §8.5), not here.
   2. R1 — two tokens per hue. --ks3-accent (3.4:1) is for borders,
      rules, focus rings and text ABOVE 24px. --ks3-accent-text
      (6.0:1) is the only orange allowed at body size. Same split for
      green. Getting this wrong is, per R1, "the single most common way
      an accessible palette gets broken at integration".
   3. R2 — colour is never the only signal. Every state below also
      carries a word or a drawn mark.
   4. Hard offset shadows only. No blur, no gradient: it has to render
      identically on a five-year-old Chromebook.
   5. R15 — every control is a real control, and every one shows the
      same 3px accent focus ring.

   ⚠️ ✓ ✕ → ARE DRAWN, NOT TYPED.  See §9.3 of SPEC.md: Design's own
   latin woff2 subsets contain none of U+2713, U+2715, U+2192, so typing
   them drops to a system font mid-badge. `.ks3-mark` is the inline-SVG
   carrier. Do not "simplify" it back to a character.
   ═══════════════════════════════════════════════════════════════ */

/* ── page shell ─────────────────────────────────────────────────── */

/* The dotted ground is Design's, from the browse-layer artifact: a 26px
   grid of 1px dots in --ks3-dot. It is texture and nothing else — no
   information rides on it — so it needs no reduced-motion or contrast
   carve-out, and it sits UNDER every card because each card paints its
   own opaque --ks3-card ground. */
body.rd[data-mode="ks3"] {
  background: var(--ks3-ground);
  background-image: radial-gradient(var(--ks3-dot) 1px, transparent 1px);
  background-size: 26px 26px;
  color: var(--ks3-ink);
  font-family: var(--ks3-font-body);
  font-size: 19px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-wrap: pretty;
}

[data-mode="ks3"] h1,
[data-mode="ks3"] h2,
[data-mode="ks3"] h3 {
  font-family: var(--ks3-font-display);
  font-weight: 800;
}

/* R15 — one focus treatment, on everything, no exceptions. */
[data-mode="ks3"] :focus-visible {
  outline: 3px solid var(--ks3-accent);
  outline-offset: 2px;
  border-radius: var(--ks3-r-focus);
}

/* The drawn marks. currentColor so a mark inherits whatever the badge
   or the link is already using, and stroke-based so it stays crisp at
   any badge size. */
.ks3-mark {
  width: 1em;
  height: 1em;
  flex: 0 0 auto;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  vertical-align: -.12em;
}
.ks3-mark-arrow { stroke-width: 2.2; }

/* MRB-182 — Design's browse-layer header. Sticky, translucent over the
   dotted ground with a blur behind it, and one 72px rail carrying brand
   and breadcrumbs (⊕ MRB-330 — "and the KS3 pill" until 6 Sep 2026;
   the pill is cut). `z-index:60` clears the sticky season
   tiles and the sticky unit aside, both of which scroll under it. */
.ks3-nav {
  position: sticky;
  top: 0;
  z-index: 60;
  border-bottom: 2px solid var(--ks3-ink);
  background: rgba(251, 243, 230, .93);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
}
.ks3-nav-rail {
  max-width: var(--ks3-rail);
  margin: 0 auto;
  padding: 0 32px;
  min-height: 72px;
  display: flex;
  align-items: center;
  gap: 20px;
}
/* Brand → crumbs divider. Decoration: the crumb trail is already a
   labelled landmark, so this carries no meaning to announce. */
.ks3-nav-divider {
  width: 2px;
  height: 26px;
  background: var(--ks3-rule);
  flex: 0 0 auto;
}
/* Holds `.ks3-classlink` (below) at the right edge of the rail. Since MRB-330
   removed the pill that used to be the rail's last child, this is the ONLY
   thing keeping that link off the crumbs — it is not the dead element it now
   looks like. On a signed-out page it grows into the space the pill vacated,
   which is correct: at phone widths the rail is over-constrained, its
   `flex-basis: 0` resolves to zero, and the freed width goes to the crumbs. */
.ks3-nav-spacer { flex: 1; }

/* ⊖ REMOVED by MRB-330, 6 Sep 2026 — the KS3 pill. The rule read:

       .ks3-pill {
         flex: 0 0 auto;
         padding: 8px 16px;
         border-radius: var(--ks3-r-pill);
         background: var(--ks3-ink);
         color: var(--ks3-on-dark);
         font-size: 14px;
         font-weight: 700;
         letter-spacing: .1em;
         text-decoration: none;
       }

   described as "the KS3 pill, top right — an ink lozenge, cream text", and
   `build_ks3.py`'s shell emitted one on every page in the key stage. Both are
   gone; the ruling is written up at the rail markup in build_ks3.py.

   Kept as a record and not simply deleted because `.ks3-pill` is still NAMED
   in two places that a search will find and that must not be "fixed" back:
   `shared/class-entry.js` querySelects it as an insertion anchor (and handles
   null), and the comment below describes the lozenge pair it used to make.

   ⚠️ `.ks3-pills`, `.ks3-pill-badge` and `.ks3-pill-label` further down this
   file are a DIFFERENT component — the keyword pill list inside a lesson —
   and are untouched. */

/* ── the way in to a student's own class (MRB-259) ──
   Injected by shared/class-entry.js at the end of the nav rail, and
   ONLY for a signed-in viewer who is actually enrolled.

   ⊕ MRB-330, 6 Sep 2026 — this used to read "immediately before the KS3
   pill", and the paragraph below described the two as a deliberate pair:
   same lozenge geometry, inverted weight, "so the pair does not read as two
   equal destinations". The pill is cut, so this is now the only lozenge on
   the rail and there is no pair to balance. Its own geometry is complete and
   self-contained, so nothing here needs restyling — but the WHY below is now
   a historical reason rather than a live constraint, and a future change to
   these values no longer has a second element to stay in step with. It has no static
   presence in any generated page — that is what makes "no flash of the entry
   before auth resolves" structural rather than a CSS trick.

   The lozenge geometry is `.ks3-pill`'s exactly, so the two read as one pair
   at the end of the rail; the WEIGHT is inverted — outlined rather than
   filled — so the pair does not read as two equal destinations. KS3's
   signature 2px ink outline does that work, which is also why this needs no
   colour Design did not specify: every value below is an existing --ks3-*
   token, so the parity gate's provenance layer stays green.

   ⚠️ Not registered in ks3_parity.py's COMPONENTS or CONTRAST, and that is a
   real gap rather than an oversight. Those layers load a built page in
   headless Chrome with no session, so this element does not exist there and a
   registered row would fail on a missing selector rather than measure
   anything. Gating it needs the harness to be able to mount a session first.
   Ink on the nav ground is 14.2:1 by the token table, so the contrast is
   sound; it is the ASSERTION that is missing, not the property. */
.ks3-classlink {
  flex: 0 0 auto;
  padding: 6px 14px;
  border: 2px solid var(--ks3-ink);
  border-radius: var(--ks3-r-pill);
  background: var(--ks3-card);
  color: var(--ks3-ink);
  font-size: 14px;
  font-weight: 700;
  text-decoration: none;
}
.ks3-classlink:hover { background: var(--ks3-accent-tint); }

/* MRB-197: Design's mark — dimensions from the frozen reference's header
   (30px chevron, 22px/800 Bricolage wordmark, 10px gap, ink on ground).
   ⊕ MRB-208 rule 1 amends the chevron into a 34px accent-filled tile with a
   cream stroke; the wordmark is unchanged. */
.ks3-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 22px;
  letter-spacing: -0.02em;
  color: var(--ks3-ink);
  text-decoration: none;
}
.ks3-brand svg { flex: 0 0 auto; }
/* ── the header trail (⊕ MRB-208 rule 1) ──
   The lesson's trail, in the same header slot the mono `.ks3-crumbs` row uses
   on every other page type. Body font at 17px, NOT the row's mono 14px — they
   are two treatments of one landmark and only one of them is a lesson's. The
   divider and the spacer above are the browse layer's and are shared; the
   trail does its own wrapping, so the 72px rail grows with it. (⊕ MRB-330 —
   this listed "the KS3 pill" as a third shared piece; it is cut.) */

/* ── the header trail (⊕ MRB-208 rule 1) ──
   Body font at 17px, NOT the `.ks3-crumbs` row's mono 14px — they are two
   different components and only one of them is a lesson's. The row survives on
   unit indices, discipline hubs and the browse layer. */
.ks3-trail {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 9px;
  min-width: 0;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: 17px;
  line-height: 1.3;
}
/* ⊕ MRB-257 · audit 3.12 — 44px OF TAP, AND NOT ONE PIXEL OF LAYOUT.
   A crumb link measured 20px tall on every page. WCAG 2.2 SC 2.5.8 wants
   24×24 and is satisfied either way, so this is the comfort target and not a
   failure — but the trail is the only way back up out of a lesson and a 20px
   strip is a poor one on a phone. The padding buys the height and the matching
   negative margin gives it straight back to the layout, so `nav.ks3-nav` is
   the same height it was and no approved page moves. 22px of line box + 24px
   of padding = 46px. */
.ks3-trail a,
.ks3-trail [aria-current] {
  display: block;
  min-width: 0;
  padding-block: 12px;
  margin-block: -12px;
}
.ks3-trail a {
  color: var(--ks3-accent-text);
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
}
.ks3-trail a:hover { color: var(--ks3-accent-hover); text-decoration: underline; }
.ks3-trail [aria-current] { color: var(--ks3-ink-muted); font-weight: 500; }
.ks3-trail-sep { color: var(--ks3-rule-strong); }

/* Index pages run to the full page width; lesson pages are capped by
   .ks3-lesson (below) because prose has a measure and a browse grid
   does not. */
.ks3-main {
  max-width: var(--ks3-page);
  margin: 0 auto;
  padding: 44px 24px 90px;
}
/* The four browse screens take Design's narrower 1240px rail. The lesson
   tree deliberately does NOT: it is capped by .ks3-lesson at 60rem and
   changing .ks3-main under it would move every lesson page in a ticket
   that is about the browse layer. */
.ks3-main.is-browse { max-width: var(--ks3-rail); padding: 52px 32px 90px; }
.ks3-main.is-hub    { max-width: var(--ks3-rail); padding: 56px 32px 90px; }

/* R11 — 60rem for the lesson, 46rem for prose inside it. */
.ks3-lesson { max-width: var(--ks3-wide); margin: 0 auto; }
.ks3-lesson > .ks3-explainer,
.ks3-lesson > .ks3-prose { max-width: var(--ks3-measure); }

/* MRB-208: the crumb trail moved onto the header rail, so it carries no
   bottom margin of its own and `min-width:0` lets it shrink rather than push
   the rail wider.

   ⊕ MRB-330, 6 Sep 2026 — that sentence used to end "lets it shrink before the
   KS3 pill does on a narrow rail". The pill is cut, so there is no longer a
   fixed item downstream for it to yield to. `min-width:0` is still REQUIRED
   and removing it would be a regression: a flex item will not shrink below its
   content without it, at any depth, which is exactly what the ellipsis in the
   700px block depends on. Only the REASON changed — it now yields to
   `.ks3-brand`, which is the unshrinkable one (see the 700px block).

   ⚠️ THE TYPE STAYS MONO 14px. Design's browse-layer artifact draws these
   at 16px/600 in the body face, but the Reference Set — which is what
   SPEC.md §2 and Layer C of ks3_parity.py were written from — makes the
   breadcrumb the one mono line in the chrome. Two Design artifacts
   disagree; the gate is the arbiter, and a breadcrumb that changes face
   between a lesson page and a browse page would be worse than either
   choice. Only the POSITION moved. */
.ks3-crumbs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  min-width: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  color: var(--ks3-ink-muted);
}
.ks3-crumbs a { color: var(--ks3-accent-text); text-decoration: none; font-weight: 500; }
.ks3-crumbs a:hover { color: var(--ks3-accent-hover); text-decoration: underline; }
.ks3-crumb-sep { color: var(--ks3-rule-strong); }

/* ── type scale (SPEC.md §2, seven rows) ────────────────────────── */

/* row 6 */
.ks3-eyebrow {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
  margin: 0 0 .35rem;
}

/* row 1 — a 5-character title and a 49-character one both have to work */
/* ⊕ MRB-277, 21 Aug 2026 — `overflow-wrap` ON EVERY DISPLAY HEADING.
   At 320px the clamp floors at 44px, and a single long word then measures
   wider than the phone: `Chromatography` is fourteen characters and pushed
   chromatography.html sideways with the instrument CLOSED, so it was the
   heading and nothing on the page below it. It is not one page — the key
   stage also titles lessons `Photosynthesis`, `Evaporation and
   crystallisation`, `Variation: continuous and discontinuous` and `Gametes
   and fertilisation`, and every future unit is free to do the same.
   `break-word` rather than `anywhere`: it breaks a word ONLY when the word
   cannot fit on a line of its own, so every heading that already fits is
   laid out to the last pixel exactly as it was.
   320px is not a hypothetical — an iPhone SE and a budget Android are both
   320, and Rainford students are on what they have. */
.ks3-lesson-head h1 {
  margin: 12px 0 0;
  font-size: clamp(44px, 6vw, 74px);
  line-height: .94;
  letter-spacing: -.035em;
  overflow-wrap: break-word;
}
.ks3-unit-head h1,
.ks3-hub-head h1,
.ks3-landing-head h1 {
  margin: 0;
  font-size: clamp(40px, 5.4vw, 66px);
  line-height: .96;
  letter-spacing: -.035em;
  overflow-wrap: break-word;
}

/* row 2 */
.ks3-bigq {
  margin: 16px 0 0;
  font-size: 25px;
  font-weight: 600;
  line-height: 1.35;
  color: var(--ks3-accent-text);
  max-width: 24ch;
}

/* row 4 */
.ks3-intro { margin: 18px 0 0; font-size: 22px; line-height: 1.55; color: var(--ks3-ink-body); max-width: 64ch; }
.ks3-meta  { margin: 0; font-size: 18px; color: var(--ks3-ink-muted); }

.ks3-lesson-head {
  border-bottom: 3px solid var(--ks3-ink);
  padding-bottom: 28px;
}

/* ⊕ MRB-221 — `.ks3-review-flag` and its ::before dot are DELETED. §5.10.1 is
   revoked, no page emits the marker, and CSS for an element that cannot exist
   is dead weight a grep mistakes for evidence (contract §6 gate 9). */

/* ── blocks ─────────────────────────────────────────────────────── */

.ks3-block {
  margin: 28px 0 0;
  padding: 30px;
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
  border-radius: var(--ks3-r-block);
  box-shadow: var(--ks3-shadow-block);
}
.ks3-block > h2:first-of-type,
.ks3-block > h3:first-of-type { margin-top: 0; }

/* row 3 */
.ks3-block h2 { font-size: 30px; line-height: 1.2; letter-spacing: -.025em; }

/* An explainer is prose, so it carries no card at all — the reference
   sets it as bare text at the reading measure. */
.ks3-explainer,
.ks3-prose {
  margin: 28px 0 0;
  padding: 0;
  background: none;
  border: 0;
  box-shadow: none;
}
.ks3-explainer p,
.ks3-prose p { margin: 0; font-size: 20px; line-height: 1.75; }
.ks3-explainer p + p,
.ks3-prose p + p { margin-top: 18px; }

/* ── the ink-dark blocks ──
   Three blocks invert: the hook, the practical and the key note. Each
   takes a different shadow colour, which is what tells them apart at a
   glance while the ground stays constant. */
.ks3-dark {
  background: var(--ks3-ink);
  color: var(--ks3-on-dark);
  border: 0;
  border-radius: var(--ks3-r-dark);
  padding: 32px;
}
.ks3-dark .ks3-eyebrow { color: var(--ks3-alert); }
.ks3-dark p { color: var(--ks3-on-dark-body); }
.ks3-dark h2 { color: var(--ks3-on-dark); }

.ks3-hook      { box-shadow: 6px 6px 0 var(--ks3-accent); }
.ks3-practical { box-shadow: 6px 6px 0 var(--ks3-blue); }
.ks3-keynote   { box-shadow: 6px 6px 0 var(--ks3-alert); }

.ks3-practical .ks3-eyebrow { color: var(--ks3-blue-light); }

.ks3-hook h2 {
  margin: 0;
  font-size: 38px;
  line-height: 1.05;
  letter-spacing: -.03em;
  overflow-wrap: break-word;
}
.ks3-hook-prompt { margin: 16px 0 0; font-size: 19px; line-height: 1.65; }
.ks3-commit {
  margin: 16px 0 0;
  font-size: 22px;
  font-weight: 700;
  line-height: 1.35;
  color: var(--ks3-alert);
}
/* ⊕ GATE A — the amber above had never once landed. `.ks3-commit` is (0,1,0)
   and `.ks3-dark p` is (0,1,1), so on every ink block the commit line resolved
   to `--ks3-on-dark-body` and read as ordinary body copy. The reasoning at
   :6432 ("B2's three gates sit on ink where `.ks3-commit`'s amber is legible")
   was right about the ground and wrong about the cascade.

   Every `.ks3-commit` inside `.ks3-dark` was walked in a browser and all of
   them resolve to the same ground, `--ks3-ink` #221E1B: the hook's
   `.ks3-hook-commit` on 46 pages, the six bare `.ks3-benchgate`s (bal, dish,
   fb, joint, lever, muscle), and the removal / sabotage / test-bench predicts.
   None of those wrappers paints a background of its own. Amber #FFC53D on
   #221E1B measures 10.48:1 ✓ AAA, so the author's intent is both intended and
   legible and this makes it land.

   (0,2,0), so `.ks3-walk-block .ks3-benchgate .ks3-commit` and its `.ks3-sbench-block`
   twin at (0,3,0) still win — those two gates sit on CREAM inset panels, where
   amber would be 1.4:1 and Design paints `--ks3-accent-text` instead.) */
.ks3-dark .ks3-commit { color: var(--ks3-alert); }
.ks3-hook-commit {
  margin-top: 26px;
  padding-top: 22px;
  border-top: 2px solid var(--ks3-dark-rule);
}

/* ── the hook's media column and its Motion control (⊕ 14 Aug 2026) ──
   `phenomenon.art`, `.figures` and `.tiles` were read by nothing, so three of
   the six hooks drew nothing at all. This is the class Mide named: "the hook
   missing its candle artwork and Motion toggle entirely".

   ⚠️ CORRECTION TO DESIGN — the grid collapses. Design's b1-01 grid has no
   breakpoint, so at a 390px viewport it holds a 165px prose column beside a
   122px candle and the page scrolls sideways by 23px. That is measured on the
   approved page, and it is the only horizontal scroll anywhere in the
   delivery. Collapsing at 780px is the breakpoint already ruled for the bench
   grid (MRB-208), so the page gains no new threshold. */
.ks3-hook-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
  gap: 34px;
  align-items: center;
}
.ks3-hook-motion {
  grid-column: 2;
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: -14px;
}
@media (max-width: 780px) {
  .ks3-hook-grid { grid-template-columns: minmax(0, 1fr); gap: 22px; }
  .ks3-hook-motion { grid-column: 1; margin-top: 0; }
}
.ks3-hook-motion-label {
  font-size: 15px;
  font-weight: 700;
  color: var(--ks3-on-dark-muted);
}
/* ⚠️ CORRECTION TO DESIGN — `min-height` is the 44px tap token, not Design's
   34px. Every other control in the key stage honours `--ks3-tap`, the token
   exists so this judgement is not remade per control, and the inventory had
   already flagged the same class of defect on the two range sliders ("an
   accessibility regression if copied as drawn"). It costs 10px of height on
   one row. Nothing else about the control moves. */
.ks3-motion-btn {
  cursor: pointer;
  font: inherit;
  font-size: 15px;
  font-weight: 700;
  padding: 7px 14px;
  min-height: var(--ks3-tap);
  border-radius: var(--ks3-r-pill);
  border: 2px solid var(--ks3-on-dark-muted);
  background: transparent;
  color: var(--ks3-on-dark-body);
}
.ks3-motion-btn[aria-pressed="true"] {
  border-color: var(--ks3-accent);
  background: var(--ks3-dark-panel);
  color: var(--ks3-on-dark);
}

/* R7 / Law 9 — one switch turns every drawing's animation off, and the OS
   setting does it without asking. `!important` because each animation is set
   on the element itself. */
[data-motion="off"] [data-anim] { animation: none !important; }
@media (prefers-reduced-motion: reduce) {
  [data-anim] { animation: none !important; }
}

.ks3-hook-art {
  position: relative;
  height: 226px;
  border-radius: 22px;
  background: #17130F;
  border: 2px solid var(--ks3-dark-panel);
  overflow: hidden;
  display: grid;
  place-items: end center;
}

/* The candle. Wick, wax and night ground are Design's own literals; the flame
   gradient is tokens — alert → accent → accent-text, top to bottom. */
.ks3-candle {
  position: relative;
  width: 100%;
  display: grid;
  place-items: center;
}
.ks3-flame {
  position: absolute;
  transform-origin: 50% 100%;
  animation: ks3-flicker 1.7s ease-in-out infinite alternate;
}
.ks3-flame-outer {
  bottom: 86px;
  width: 46px;
  height: 92px;
  border-radius: 50% 50% 46% 46% / 66% 66% 34% 34%;
  background: linear-gradient(180deg, var(--ks3-alert) 0%, var(--ks3-accent) 62%, var(--ks3-accent-text) 100%);
}
.ks3-flame-inner {
  bottom: 90px;
  width: 20px;
  height: 42px;
  border-radius: 50% 50% 46% 46% / 70% 70% 30% 30%;
  background: var(--ks3-alert-tint);
  animation-duration: 1.15s;
}
.ks3-wick {
  position: absolute;
  bottom: 78px;
  width: 3px;
  height: 12px;
  background: #2A211B;
  border-radius: 2px;
}
.ks3-wax {
  width: 74px;
  height: 78px;
  background: linear-gradient(90deg, #E7DECE 0%, #FFFCF5 38%, #C6B9A7 100%);
  border-radius: 8px 8px 4px 4px;
}
.ks3-soot {
  position: absolute;
  border-radius: var(--ks3-r-pill);
  background: #6E6259;
  animation: ks3-soot 3.1s linear infinite;
}
.ks3-soot:nth-of-type(1) { left: 46%; bottom: 128px; width: 5px; height: 5px; }
.ks3-soot:nth-of-type(2) { left: 55%; bottom: 132px; width: 4px; height: 4px; animation-duration: 2.4s; animation-delay: .7s; }
.ks3-soot:nth-of-type(3) { left: 50%; bottom: 140px; width: 3px; height: 3px; animation-duration: 3.8s; animation-delay: 1.4s; }

@keyframes ks3-flicker {
  0%   { transform: scale(1) translateY(0) rotate(-1.4deg); }
  100% { transform: scale(1.06) translateY(-4px) rotate(1.8deg); }
}
@keyframes ks3-soot {
  0%   { transform: translateY(0); opacity: .55; }
  100% { transform: translateY(-58px); opacity: 0; }
}

/* row 3, at key-note weight — Design drops to 700 here because 800 at
   30px over four lines is a wall. */
.ks3-keynote p {
  margin: 14px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 30px;
  line-height: 1.3;
  letter-spacing: -.02em;
  color: var(--ks3-on-dark);
}

/* ── misconception ──
   Amber is a wrong IDEA being confronted. It is never "you got it
   wrong" — the student is not the error. */
.ks3-misconception {
  background: var(--ks3-alert-tint);
  border: 2px solid var(--ks3-ink);
}
.ks3-misconception .ks3-eyebrow { color: var(--ks3-alert-text); margin: 0; }
.ks3-mis-head { display: flex; align-items: center; gap: 12px; }
.ks3-mis-badge {
  flex: 0 0 32px;
  height: 32px;
  border-radius: 10px;
  background: var(--ks3-ink);
  color: var(--ks3-alert);
  display: grid;
  place-items: center;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 19px;
}
.ks3-mis-quote {
  margin: 15px 0 0;
  font-size: 22px;
  font-weight: 700;
  line-height: 1.4;
  font-style: italic;
}
.ks3-misconception > p { font-size: 19px; line-height: 1.6; }

/* ── the confrontation's own content (⊕ 14 Aug 2026) ──
   Design draws a misconception block as a SEQUENCE of confrontations: one
   wrong idea answered, then — behind a divider — another, inside one "Think
   again". Both b1-03 and b1-06 do this. The generator rendered the register's
   quote and stopped, so every authored answer on all six pages went nowhere
   and b1-03's block rendered as `! Think again` alone.

   The divider is `--ks3-alert-border` and that is the one place amber is
   correct: a wrong idea being confronted is exactly what §8 reserves it for.
   It is never a mark on a student (MRB-202) and never a key fact (MRB-208
   rule 3) — both of which is why neither of those grows an amber anything. */
.ks3-mis-body { font-size: 19px; line-height: 1.6; }
.ks3-mis-body + .ks3-mis-body { margin-top: 14px; }
.ks3-mis-next {
  margin-top: 22px;
  padding-top: 20px;
  border-top: 2px solid var(--ks3-alert-border);
}
.ks3-mis-next > .ks3-mis-quote { margin-top: 0; }

/* The scorecards themselves were already styled — see the
   `mono-numeral scorecards` section further down. The CSS was written by an
   earlier run and the RENDERER never was, which is the exact mirror of the
   seven-tests board, where the renderer exists and the stylesheet has nothing
   for any of its classes. Both halves of a component have to land or neither
   is built. */

/* ── activity options (R3) ──
   R3 is load-bearing and easy to lose: an activity button shows that it
   was CHOSEN, never whether it was right. No green, no red, and it stays
   enabled so the student can change their mind. Only the ladder marks
   correctness. */
.ks3-options {
  list-style: none;
  margin: 16px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 11px;
}
.ks3-option {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  cursor: pointer;
  padding: 16px 18px;
  border-radius: var(--ks3-r-option);
  font-family: inherit;
  font-size: 18px;
  font-weight: 600;
  min-height: var(--ks3-tap);
  text-align: left;
  background: var(--ks3-ground);
  border: 2px solid var(--ks3-option-border);
  color: var(--ks3-ink);
  transition: transform .14s, background-color .16s, border-color .16s;
}
.ks3-option:hover { transform: translateX(3px); border-color: var(--ks3-accent); }
.ks3-option[aria-pressed="true"] {
  background: var(--ks3-accent-tint);
  border-color: var(--ks3-accent);
}
.ks3-opt-mark {
  flex: 0 0 28px;
  height: 28px;
  border-radius: 9px;
  display: grid;
  place-items: center;
  font-size: 15px;
  font-weight: 800;
  background: var(--ks3-band);
  color: var(--ks3-ink-muted);
}
/* ⊕ MRB-257 · audit 3.19 — THE TOKEN'S OWN ANNOTATION, ENFORCED.
   `--ks3-accent` is "3.4:1 — LARGE TEXT ONLY. Never body size" and this is a
   15px/800 letter: cream on it measures 3.34:1 on seven pages, on a control
   verified NOT disabled. Ink on the same fill is 4.49:1, which is the pairing
   `.ks3-dark .ks3-option[aria-pressed="true"] .ks3-opt-mark` has always used —
   a fill and ink, never a fill and cream. The FILL does not move: the accent
   is what says "chosen", and R3 is untouched because the treatment is still
   identical whether the answer was right or wrong. */
.ks3-option[aria-pressed="true"] .ks3-opt-mark {
  background: var(--ks3-accent);
  color: var(--ks3-ink);
}
.ks3-opt-label { flex: 1; }

/* ⊕⊕ SELECTION MOVES TO THE ACCENT TOKEN — RULED BY MIDE, 30 Aug 2026
   (audit P10-26 / Mide-pile M6). THIS IS THE PLATFORM RULE. Every "you
   picked this" state on an ink-dark ground reads from here; the per-unit
   comments further down this file that still say "amber is chosen" are
   SUPERSEDED by it, and are kept only so the reasoning they record is not
   lost.

       Amber is reserved for warning and loss. A colour meaning "careful"
       must not also mean "you picked this".

   The light-ground branch had already expressed selection as accent — an
   accent border over an accent-tint fill (see `.ks3-option[aria-pressed]`
   above) — so this makes ONE rule across both grounds instead of two.

   ⚠️ THE DARK TREATMENT IS A BORDER, NOT A SLAB, AND THAT IS A CONTRAST
   FINDING RATHER THAN A PREFERENCE. `--ks3-accent` #E4572E is annotated
   "3.4:1 — LARGE TEXT ONLY", and `--ks3-ink` #221E1B measured ON an accent
   fill is 4.49:1 — a hair UNDER the 4.5:1 body-text floor. So the amber
   slab these controls used to paint CANNOT simply be recoloured: a solid
   accent fill with ink text would ship 16px labels below AA. What ships
   instead mirrors the light ground — `--ks3-dark-panel` #3E3730 as the
   fill, the accent as the 2px border (3.18:1 on the panel, 4.49:1 on the
   ink ground behind it, both over the 3:1 non-text floor of WCAG 1.4.11),
   and `--ks3-on-dark` cream as the label (10.63:1). Selection still reads
   on two channels, because the resting state of nearly every one of these
   controls is `background: transparent` with a muted border.

   ⚠️ WHAT THIS RULING DOES **NOT** MOVE. MRB-252 (19 Aug 2026) sent BOTH
   "category" AND "selection" to `--ks3-data`. Mide's ruling moves SELECTION
   ONLY. Every `--ks3-data` use meaning "this column, not that one" or "this
   is the field you are looking at" stays exactly where it is, and so does
   every amber that means a wrong idea being confronted, a caution, or a
   loss. Recolouring a CATEGORY under cover of this ruling is the one way to
   get it wrong.

   Three separate units stopped at this question before it was ruled. It is
   settled; do not re-raise it. */

/* options on an ink-dark block */
.ks3-dark .ks3-option {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-on-dark-muted);
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-option[aria-pressed="true"] {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-accent);
}
.ks3-dark .ks3-opt-mark { background: var(--ks3-on-dark-muted); color: var(--ks3-ink); }
.ks3-dark .ks3-option[aria-pressed="true"] .ks3-opt-mark {
  background: var(--ks3-dark-panel);
  border: 2px solid var(--ks3-accent);
  color: var(--ks3-on-dark);
}

.ks3-reveal {
  margin-top: 18px;
  padding: 18px 20px;
  border-radius: 18px;
  background: var(--ks3-accent-tint);
  border: 2px solid var(--ks3-accent);
  font-size: 19px;
  line-height: 1.6;
}
.ks3-dark .ks3-reveal {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-alert);
  color: var(--ks3-on-dark);
}

/* ── vocabulary (Law 7) ──
   The keywords block is SURFACE-LESS, like the explainer. In the reference
   it is a bare section holding a heading, a lead line and the card grid —
   the cards are the surfaces. Giving the block its own card ground would
   nest bordered cards inside a bordered card, which is the one thing this
   layout must not do. */
.ks3-keywords {
  background: none;
  border: 0;
  box-shadow: none;
  padding: 0;
}
.ks3-keywords-lead {
  margin: 0 0 18px;
  font-size: 18px;
  line-height: 1.55;
  color: var(--ks3-ink-body);
  max-width: 52ch;
}

/* ── vocabulary cards (R4) ──
   The dog-ear is the ONLY affordance. When the card opens the dog-ear
   goes, because there is no longer anything underneath. */
.ks3-cards {
  list-style: none;
  margin: 18px 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(268px, 1fr));
  gap: 16px;
}

/* ⊕ MRB-229 · 5 Sep 2026 — THE LONG-KEYWORD GRID BLOWOUT. Two declarations,
   and neither is cosmetic: together they are the whole reason
   `ks3/chemistry/mixtures-and-separation/chromatography.html` measured
   `scrollWidth` 326 against a 320px viewport. It was the only one of the 185,
   and the block ~5,700 lines below records it as unexplained. This is the
   explanation.

   ⚠️ It is NOT a chromatography bug, and it is not a c3.py bug. It is generic,
   and the page was simply the first content to cross the threshold:

     * At `max-width: 34rem` the rule further down sets
       `.ks3-cards { grid-template-columns: 1fr }`. `1fr` is `minmax(auto, 1fr)`,
       and a grid item's AUTOMATIC MINIMUM is its min-content size — so that
       track can never be narrower than the widest unbreakable thing in a card,
       no matter how narrow the container is.
     * A card's min-content is its longest single WORD. "Chromatography" sets
       218.1px at `.ks3-card-front`'s 27px/800 display face, plus that rule's
       `padding-right: 1.6rem` (25.6px, the dog-ear's clearance), plus the
       button's 22px padding either side and its 2px borders = 291.7px.
     * At 320px the keywords block's content box is 252px. The track blew out
       to 291.7, the `<li>` right edge landed at 326, and the DOCUMENT scrolled.

   So the trigger is a 14-letter word, and any longer one anywhere in KS3 would
   have done the same — "Chromatography" is merely the longest term the estate
   currently sets in that treatment. Renaming it would have hidden the bug and
   left the next long keyword to re-find it.

   `min-width: 0` releases the track from the min-content floor so it can fall
   back to the container's 252px. On its own that would only move the damage:
   `.ks3-card-btn` is `overflow: hidden`, so the word would be CLIPPED rather
   than pushed — silently losing the end of the very term the card teaches, and
   the 390px gate treats clipping as a failure precisely because it looks fixed.
   `overflow-wrap: break-word` is therefore load-bearing next to it: it lets the
   word wrap mid-token when — and only when — it would otherwise overflow, so
   nothing is lost and nothing moves at any width where the word already fits.

   Above 34rem the track is `minmax(268px, 1fr)`, min-content never binds, and
   both declarations are inert. Nothing about the card's drawing changes. */
.ks3-cards > li { min-width: 0; }
.ks3-card-front { overflow-wrap: break-word; }

.ks3-card-btn {
  position: relative;
  overflow: hidden;
  width: 100%;
  cursor: pointer;
  text-align: left;
  font: inherit;
  min-height: 150px;
  padding: 22px;
  display: flex;
  flex-direction: column;
  gap: 9px;
  background: var(--ks3-card);
  border: 2px solid var(--ks3-option-border);
  border-radius: var(--ks3-r-card);
  box-shadow: var(--ks3-shadow-card);
  color: var(--ks3-ink);
  transition: transform .15s, background-color .2s;
}
.ks3-card-btn:hover { transform: translate(-2px, -2px); }

/* the dog-ear */
.ks3-card-btn::after {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  width: 0;
  height: 0;
  border-top: 26px solid var(--ks3-accent);
  border-left: 26px solid transparent;
  border-top-right-radius: var(--ks3-r-panel);
}
.ks3-card-btn.is-flipped::after { border-top-color: transparent; }
.ks3-card-btn.is-flipped { background: var(--ks3-accent-tint); border-color: var(--ks3-accent); }

.ks3-card-front {
  display: block;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 27px;
  letter-spacing: -.02em;
  padding-right: 1.6rem;
}
.ks3-card-back { display: flex; flex-direction: column; gap: 8px; }
.ks3-card-def  { font-size: 19px; line-height: 1.5; }
.ks3-card-note { font-size: 16px; line-height: 1.45; color: var(--ks3-ink-muted); }
/* ⚠️ Load-bearing and it looks redundant. `hidden` is on the element and
   ks3.js toggles it, but an AUTHOR `display` beats the UA stylesheet's
   [hidden] rule regardless of specificity, so without this line every
   answer renders face-up. Previously caught in a screenshot, not by a
   DOM probe. Any future `display` on a hidden element needs the same. */
.ks3-card-back[hidden] { display: none; }

/* R4 requires the block to ASK for the declaration in words. Once the
   card is open the ask is spent, so the hint goes with the dog-ear. */
.ks3-card-hint {
  margin-top: auto;
  font-size: 16px;
  font-weight: 700;
  color: var(--ks3-accent-text);
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.ks3-card-btn.is-flipped .ks3-card-hint { display: none; }

/* ── check / worked example ─────────────────────────────────────── */

.ks3-check h2,
.ks3-worked h2 {
  margin: 9px 0 14px;
  font-size: 28px;
  line-height: 1.25;
  letter-spacing: -.02em;
}
.ks3-reveal-btn {
  cursor: pointer;
  padding: 14px 22px;
  border-radius: var(--ks3-r-control);
  background: var(--ks3-ink);
  color: var(--ks3-on-dark);
  border: 2px solid var(--ks3-ink);
  font: inherit;
  font-weight: 700;
  font-size: 17px;
  min-height: var(--ks3-tap);
  transition: transform .15s;
}
.ks3-reveal-btn:hover { transform: translateY(-2px); }

/* criteria: a numbered list on an inset ground, green badges */
.ks3-crit-wrap {
  margin-top: 18px;
  padding: 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-crit-lead {
  margin: 0 0 13px;
  font-size: 15px;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-crit-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 11px;
}
.ks3-crit {
  display: flex;
  gap: 12px;
  align-items: baseline;
  font-size: 19px;
  line-height: 1.55;
}
.ks3-crit-num {
  flex: 0 0 25px;
  height: 25px;
  border-radius: 8px;
  background: var(--ks3-ok-tint);
  border: 2px solid var(--ks3-ok);
  display: grid;
  place-items: center;
  font-size: 13px;
  font-weight: 800;
  color: var(--ks3-ok-text);
}

.ks3-fifa {
  display: grid;
  gap: .3rem;
  margin: 18px 0 0;
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
  font-size: 19px;
}
.ks3-fifa p { margin: 0; }
.ks3-fifa strong { font-family: var(--ks3-font-display); }

/* ── figures (§4.10) ────────────────────────────────────────────── */

.ks3-figure { margin: 28px 0 0; }
.ks3-figure img { max-width: 100%; height: auto; border-radius: 24px; border: 2px solid var(--ks3-ink); }
.ks3-figure figcaption {
  margin-top: 12px;
  font-size: 17px;
  line-height: 1.5;
  color: var(--ks3-ink-body);
}
/* ⊕ A DRAWN figure (Mide's diagram ruling, 18 Aug 2026). The frame is the
   `img` rule's frame — same 2px ink outline and same 24px radius — because a
   drawing and a photograph are the same object on the page and only one of
   them arrives as a file. It carries its own ground rather than inheriting
   the block's: a web drawn in ink needs the paper under it to be the paper,
   not whichever panel the figure happens to sit in.

   No motion rule below, and that is deliberate rather than an omission: the
   drawings are static by ruling, so there is no animation for
   `prefers-reduced-motion` to switch off. A drawer that wants motion has to
   come back through this comment. */
.ks3-figure-scroll {
  overflow-x: auto;
  /* ⊕ MRB-280, 21 Aug 2026 — CONTAINMENT, not clipping. A scroller whose
     `position` is static is NOT the containing block for an absolutely
     positioned descendant, so `overflow-x: auto` cannot clip one: the
     descendant resolves against an ancestor OUTSIDE the scroller and
     extends the DOCUMENT instead. That is what pushed c5-04 sideways by
     140px at 390px (see `.ks3-rgrid-scroll` below), and it was the
     invisible `.ks3-sr-only` labels doing it. Every horizontal scroller
     in the key stage now declares itself a containing block, so the
     shape cannot come back one figure at a time. `overflow: hidden` is
     NOT the fix — measured — because the problem is not clipping. */
  position: relative;
  border-radius: 24px;
  /* The focus ring belongs on the scroll box, because the scroll box is the
     thing that takes focus. Inset so it is not clipped by the rounding. */
  outline-offset: -3px;
}
/* ⊕ MRB-257 · audit 3.9 — A PANEL THAT ENDS AT A PANEL BOUNDARY LOOKS
   FINISHED. On `natural-selection` at 390px this box is 358px wide over 700px
   of drawing — 49% of the figure off screen — and the visible window ends at
   SVG x≈388, which is EXACTLY where the left panel's rounded frame and closing
   rule end. There is no scrollbar (overlay scrollbars), no shadow, no mask.
   Meanwhile the figcaption underneath reads "A bird hunting on the left panel
   finds the dark moth first; ON THE RIGHT it finds the pale one" — about a
   panel the student has no reason to believe exists. The key stage's three
   other figures cut mid-object ("Aphids", "Fu[ngi]"), and that ragged cut is
   itself the cue this one lacks.

   The audit offers "edge fade when scrollWidth > clientWidth, and/or stack the
   two panels vertically below ~700px". THE FADE, and here is why not the
   other: stacking is a change to the SVG drawer in `build_ks3.py`, which this
   pass does not own, and it would re-draw a figure Mide has approved.

   THE THRESHOLD IS MEASURED, NOT GUESSED. Swept in a browser on the one page
   that carries a figure: the box is 288/358/448/512/552/652/692px at
   320/390/480/544/600/700/740, and 712px at 760 — so it is scrollable at every
   width up to 747 and at none above it, because `.ks3-figure-svg` declares
   `min-width: 700px` and the box is the viewport less 48px of `.ks3-main`
   padding. A parity row asserts the mask IS there at 390 and is NOT at 1440,
   so the day that padding changes the gate says so rather than the fade
   quietly detaching from the condition it stands for.

   ⚠️ NAMED COST: the fade is on the trailing edge and does not know the scroll
   position, so a student who has scrolled to the far end still sees it. The
   defect being fixed is the state the page LOADS in — a complete-looking panel
   — and a soft edge at the end is a much smaller lie than a hard one at the
   start. Making it scroll-aware in CSS alone needs `animation-timeline:
   scroll()`, which is Chrome-only; doing it in JS is outside this pass.

   ⊕ MRB-254 — THE BREAKPOINT IS GONE, REPLACED BY THE MEASUREMENT IT STOOD
   FOR. 747 was derived above from ONE figure width (760) and one padding (48),
   and it was correct for exactly as long as every figure was 760 wide. The
   biology kit is 860 and 900 units wide, so a 900px plate would overflow all
   the way to a 948px viewport while the fade switched off at 747 — the figure
   ending in a hard edge two hundred pixels of viewport before the cue admits
   it, which is the audit-3.9 defect back again at a different width.

   There is no single number that is right for fourteen widths. So the
   condition is now the condition itself: `wireFigureCues()` in ks3.js sets
   `.is-overflowing` from `scrollWidth > clientWidth`, on load and on every
   resize, and this rule hangs off that. It cannot detach from what it stands
   for, because it IS what it stands for.

   ⚠️ WITHOUT JAVASCRIPT THERE IS NO FADE — deliberately, and it is the safe
   way round. The old media query's failure mode was a fade drawn where the
   figure fits, which teaches the reader that the cue means nothing. This
   one's is a missing cue on a figure that does scroll, which is where the key
   stage stood before audit 3.9 and is no worse than it. A `<noscript>`
   fallback would have to guess a width again. */
.ks3-figure-scroll.is-overflowing {
  -webkit-mask-image:
    linear-gradient(to right, #000 calc(100% - 56px), transparent);
  mask-image:
    linear-gradient(to right, #000 calc(100% - 56px), transparent);
}
.ks3-figure-svg {
  display: block;
  width: 100%;
  /* The FLOOR, not the answer: each drawer emits its own `min-width` inline
     from the width it was drawn at (see `_svg_open`), and an inline style
     beats this rule. This stays for a figure drawn narrower than 700, where
     the labels would stop being labels — see the note beside the scroll
     wrapper in build_ks3.py. */
  min-width: 700px;
  height: auto;
  border-radius: 24px;
  border: 2px solid var(--ks3-ink);
  background: var(--ks3-ground);
}

/* An honest placeholder, and it has to read as deliberate rather than
   broken — hence a drawn dashed frame and a labelled pill. */
.ks3-figure-slot {
  display: grid;
  place-items: center;
  padding: 52px 24px;
  border: 3px dashed var(--ks3-rule-strong);
  border-radius: 24px;
  background: var(--ks3-inset);
}
.ks3-figure-tag {
  padding: 8px 16px;
  border-radius: var(--ks3-r-pill);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-rule-strong);
  font-size: 14px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}

/* ── the mastery ladder (R8 / MRB-184) ──────────────────────────── */

.ks3-ladder {
  margin: 28px 0 0;
  max-width: var(--ks3-wide);
  background: var(--ks3-card);
  border: 3px solid var(--ks3-ink);
  border-radius: var(--ks3-r-dark);
  padding: 32px;
  box-shadow: 6px 6px 0 var(--ks3-ok);
}
.ks3-ladder-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
  border-bottom: 2px solid var(--ks3-rule);
  padding-bottom: 20px;
}
.ks3-ladder-head h2 { margin: 0; font-size: 36px; letter-spacing: -.03em;
                      overflow-wrap: break-word; }
.ks3-ladder-sub { margin: 6px 0 0; font-size: 18px; color: var(--ks3-ink-muted); }
.ks3-ladder-score { text-align: right; min-width: 220px; }
.ks3-score { margin: 0; font-size: 22px; font-weight: 700; }
.ks3-score-note { margin: 4px 0 0; font-size: 16px; color: var(--ks3-ink-muted); }

.ks3-rungs { margin-top: 26px; display: flex; flex-direction: column; gap: 28px; }

/* Two the page marks (accent), two the student marks (violet). The
   colour split is the same one the layers use: violet always means
   "this is yours". */
.ks3-rung {
  border-left: 4px solid var(--ks3-accent);
  padding: 0 0 0 22px;
}
.ks3-rung h3 { margin: 0; font-size: 23px; color: var(--ks3-accent-text); }
.ks3-rung-q { margin: 8px 0 16px; font-size: 21px; font-weight: 600; line-height: 1.4; }
.ks3-rung-self { border-left-color: var(--ks3-stretch); }
.ks3-rung-self h3 { color: var(--ks3-stretch-text); }

.ks3-ladder .ks3-options {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 11px;
}
.ks3-ladder .ks3-option {
  border-radius: 15px;
  padding: 15px 17px;
}
.ks3-ladder .ks3-option:hover { transform: translateY(-2px); }
.ks3-ladder .ks3-opt-mark { flex-basis: 27px; height: 27px; }

/* Ladder option states. Every one carries a drawn mark as well as a
   colour (R2), so the state survives greyscale. */
.ks3-option.is-correct {
  background: var(--ks3-ok-tint);
  border-color: var(--ks3-ok);
  animation: ks3-pop .35s both;
}
.ks3-option.is-correct .ks3-opt-mark { background: var(--ks3-ok); color: #FFFFFF; }
.ks3-option.is-wrong {
  background: var(--ks3-band);
  border-color: var(--ks3-ink);
}
.ks3-option.is-wrong .ks3-opt-mark { background: var(--ks3-ink); color: var(--ks3-on-dark); }
.ks3-option.is-spent {
  background: var(--ks3-row-dim);
  border-color: var(--ks3-option-spent);
  color: var(--ks3-ink-faint);
}
/* ⊕ MRB-257 · audit 3.21 — 2.63:1, AND NO OPACITY IS INVOLVED. This one is an
   explicit pair: `--ks3-ink-ghost` #9A8F86 on `--ks3-band` #F4E9D8. The glyph
   is the letter of an answer the student has already spent, on all 18 pages
   measured. `--ks3-ink-faint` #6E655D on the same band is 4.75:1 and is still
   plainly the quietest thing on the row — `--ks3-ink-muted` would have been
   5.97 and would have started competing with the live options. */
.ks3-option.is-spent .ks3-opt-mark { background: var(--ks3-band); color: var(--ks3-ink-faint); }
.ks3-option:disabled { cursor: default; }
.ks3-option:disabled:hover { transform: none; }

.ks3-feedback {
  margin: 15px 0 0;
  font-size: 19px;
  line-height: 1.6;
  padding: 14px 18px;
  border-radius: 15px;
  background: var(--ks3-card);
  border: 2px solid var(--ks3-rule);
}
.ks3-feedback.is-correct { background: var(--ks3-ok-tint); border-color: var(--ks3-ok); }
.ks3-feedback.is-correct .ks3-mark { color: var(--ks3-ok-text); }
.ks3-feedback.is-wrong   { background: var(--ks3-band);   border-color: var(--ks3-ink); }
.ks3-feedback-word { font-weight: 700; display: inline-flex; align-items: center; gap: 8px; }

/* R8 — write, then check, then mark.
   The textarea is alone on the page until "Check my answer" is pressed,
   because a visible checklist IS the answer. */
.ks3-answer-label {
  display: block;
  font-size: 16px;
  font-weight: 700;
  color: var(--ks3-ink-muted);
  margin: 0 0 8px;
}
.ks3-answer {
  display: block;
  width: 100%;
  min-height: 8.5rem;
  padding: 16px 18px;
  font: inherit;
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-ink);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-option-border);
  border-radius: var(--ks3-r-option);
  resize: vertical;
}
.ks3-answer:hover { border-color: var(--ks3-accent); }
.ks3-check-btn {
  margin-top: 14px;
  cursor: pointer;
  padding: 13px 20px;
  border-radius: var(--ks3-r-control);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
  font: inherit;
  font-weight: 700;
  font-size: 17px;
  min-height: var(--ks3-tap);
  color: var(--ks3-ink);
  transition: transform .14s;
}
.ks3-check-btn:hover { transform: translateY(-2px); }
/* ⊕ RULING (Mide, 19 Aug 2026) — the missing half of R8. The criteria are
   not reachable until the student has committed an answer, so the control
   that opens them is inactive until then. Routed straight to the MEASURED
   `--ks3-dim-spent` rather than to another by-eye .45 (see the note above
   `.ks3-sort-reveal[disabled]`), and `:hover` is cancelled with it — a lift
   on a dead button reads as "press me". No copy accompanies it: the lock
   explains itself by the empty box directly above. */
.ks3-check-btn[disabled] { opacity: var(--ks3-dim-spent); cursor: default; transform: none; }
.ks3-check-btn[disabled]:hover { transform: none; }

.ks3-ticks {
  margin: 16px 0 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
/* ⚠️ LOAD-BEARING, and it looks redundant — same trap as
   `.ks3-card-back[hidden]` above. An AUTHOR `display` beats the UA
   stylesheet's `[hidden] { display: none }` regardless of specificity, so
   without these two lines the success criteria and the tally render VISIBLE
   before "Check my answer" is pressed. That is not a cosmetic slip: R8's
   entire point is that the criteria are not on the page until the student has
   written an answer, because a visible checklist is the answer. It would break
   for exactly the readers who can least afford it — anyone arriving before
   ks3.js runs, and anyone with JS off. */
.ks3-ticks[hidden],
.ks3-tally[hidden] { display: none; }
.ks3-tick {
  display: flex;
  gap: 12px;
  align-items: baseline;
  font-size: 19px;
  line-height: 1.55;
}
/* A real checkbox (MRB-184: "the tick controls must be real
   checkboxes"), sized up to a comfortable target and accent-coloured. */
.ks3-tick input[type="checkbox"] {
  flex: 0 0 auto;
  width: 25px;
  height: 25px;
  margin: 0;
  accent-color: var(--ks3-ok);
  cursor: pointer;
}
.ks3-tick label { cursor: pointer; }
.ks3-tick-num {
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
  color: var(--ks3-ok-text);
}
/* Partial is honest, and it is NOT a failure — so it takes the neutral
   band, never the amber or a red. */
.ks3-tally {
  margin: 14px 0 0;
  font-size: 17px;
  font-weight: 700;
  padding: 11px 16px;
  border-radius: 13px;
  background: var(--ks3-band);
  border: 2px solid var(--ks3-rule-strong);
  color: var(--ks3-ink-body);
  display: inline-block;
}
.ks3-tally.is-met {
  background: var(--ks3-ok-tint);
  border-color: var(--ks3-ok);
  color: var(--ks3-ok-text);
}

.ks3-retry-wrap {
  margin-top: 26px;
  padding-top: 20px;
  border-top: 2px solid var(--ks3-rule);
}
.ks3-retry {
  cursor: pointer;
  padding: 14px 24px;
  border-radius: var(--ks3-r-control);
  background: var(--ks3-ink);
  color: var(--ks3-on-dark);
  border: 2px solid var(--ks3-ink);
  font: inherit;
  font-weight: 700;
  font-size: 18px;
  min-height: var(--ks3-tap);
  transition: transform .14s;
}
.ks3-retry:hover { transform: translateY(-2px); }
.ks3-retry-note { margin: 10px 0 0; font-size: 16px; color: var(--ks3-ink-muted); }

/* ── simulations (R5, R6, R18, R19) ─────────────────────────────── */

.ks3-sim { position: relative; margin: 20px 0 0; max-width: var(--ks3-wide); }
.ks3-sim-canvas {
  display: block;
  width: 100%;
  height: auto;
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
  border-radius: var(--ks3-r-panel);
}
.ks3-dark .ks3-sim-canvas { border-color: var(--ks3-on-dark-muted); }

.ks3-sim-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 14px 26px;
  margin: 16px 0 0;
}
.ks3-sim-control {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 17px;
  font-weight: 600;
  color: var(--ks3-ink-body);
}
.ks3-dark .ks3-sim-control { color: var(--ks3-on-dark-body); }
.ks3-sim-control input[type="range"] {
  accent-color: var(--ks3-accent);
  min-width: 11rem;
  max-width: 15rem;
  height: var(--ks3-tap);
  cursor: pointer;
}
.ks3-sim-control select {
  font: inherit;
  font-size: 17px;
  padding: 10px 12px;
  min-height: var(--ks3-tap);
  border-radius: var(--ks3-r-control);
  border: 2px solid var(--ks3-ink);
  background: var(--ks3-card);
  color: var(--ks3-ink);
  cursor: pointer;
}
.ks3-sim-value {
  font-family: var(--ks3-font-mono);
  font-weight: 500;
  font-size: 15px;
  color: var(--ks3-ink-muted);
}
.ks3-dark .ks3-sim-value { color: var(--ks3-on-dark-muted); }

/* MRB-211 — a control that is a ROW OF BUTTONS: the bench's "Move the slide
   to" and its movement toggle. The shell is `.ks3-sim-control` so the caption
   sits over the control exactly as it does for a select or a wheel; only the
   control itself differs, which is why this is a modifier and not a second
   layout.

   The caption is a <span>, not the <label> text, because `for` names one
   labelable element and a group has three. `role="group"` +
   `aria-labelledby` carries the same association. */
.ks3-sim-control-caption { display: block; }
.ks3-sim-seg { display: flex; flex-wrap: wrap; gap: 9px; }
/* ⊕ Drift 4 ruled ONE segmented control for the system — b1-06's variant, the
   only light branch whose geometry matches its own dark branch. `.ks3-seg-btn`
   is that control; `.ks3-sim-seg-btn` is the same declaration under the name
   the sim panels already use. Two classes, one set of values, because a second
   copy is exactly the drift the ruling exists to stop. */
.ks3-seg-btn,
.ks3-sim-seg-btn {
  cursor: pointer;
  font: inherit;
  font-size: 17px;
  font-weight: 700;
  padding: 11px 17px;
  min-height: var(--ks3-tap);
  border-radius: var(--ks3-r-control);
  border: 2px solid var(--ks3-rule-strong);
  background: transparent;
  color: var(--ks3-ink-body);
}
/* R2 — the chosen state carries a WORD (`aria-pressed`, and the toggle's own
   two words) and a border, never colour alone. R3 is untouched: nothing here
   is an answer, only where the slide is and whether it is moving. */
.ks3-seg-btn[aria-pressed="true"],
.ks3-sim-seg-btn[aria-pressed="true"] {
  background: var(--ks3-accent-tint);
  border-color: var(--ks3-accent);
  color: var(--ks3-accent-text);
}
/* On the ink-dark practical block, Design's own pair: the lit state is the
   alert yellow with ink text, the resting state transparent on the muted
   rule.
   ⊕ MRB-297 · 1 Sep 2026 — THE LIT STATE IS NO LONGER THE ALERT YELLOW.
   Mide ruled on 30 Aug 2026 that selection takes `--ks3-accent`; amber is
   kept for warning and loss. Design's pair is recorded above as drawn,
   because it is the provenance for the SHAPE — a lit half and a resting
   half, one segmented control — and the shape is what MRB-242 below is
   about. Only the lit colour moved, and it moved to a 2px accent border on
   the dark panel rather than to an accent slab: ink on the accent measures
   4.49:1, just under the 4.5:1 body floor.
   ⊕ MRB-242 — `.ks3-seg-btn` was missing from BOTH of these, so the ruling
   three comments up ("one segmented control, two class names, one set of
   values") held for the light branch and broke for the dark one. Every
   `.ks3-seg-btn` on an ink block fell through to the light accent branch:
   unpressed at 1.35:1, pressed in accent where Design draws amber. 16 buttons
   across b1-03, b1-04 and b1-05 — the fit tabs, the sabotage tabs, the zoom
   ticks and the removal tabs — all of them in `ks3-dark ks3-practical`
   instrument blocks, all of them corrected by adding the name back. */
.ks3-dark .ks3-seg-btn,
.ks3-dark .ks3-sim-seg-btn {
  border-color: var(--ks3-on-dark-muted);
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-seg-btn[aria-pressed="true"],
.ks3-dark .ks3-sim-seg-btn[aria-pressed="true"] {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-accent);
  color: var(--ks3-on-dark);
}

/* R19 — particle-states gets a band strip instead of a number, so the
   solid/liquid/gas changes are findable by dragging. No temperature is
   displayed, because naming one would imply a melting point for a model
   that names no substance. */
.ks3-sim-bands {
  display: grid;
  grid-template-columns: 33fr 33fr 34fr;
  gap: 3px;
  margin-top: 6px;
  min-width: 11rem;
  max-width: 15rem;
}
.ks3-sim-band {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  text-align: center;
  padding: 4px 2px;
  border-radius: 7px;
  background: var(--ks3-band);
  border: 2px solid var(--ks3-rule-strong);
  color: var(--ks3-ink-muted);
}
.ks3-sim-band.is-on {
  background: var(--ks3-accent-tint);
  border-color: var(--ks3-accent);
  color: var(--ks3-accent-text);
}

/* R18's two locked readings. DM Mono because they change. */
.ks3-sim-readout {
  margin: 16px 0 0;
  font-size: 19px;
  line-height: 1.5;
  color: var(--ks3-ink-body);
}
.ks3-dark .ks3-sim-readout { color: var(--ks3-on-dark-body); }
.ks3-sim-figure {
  font-family: var(--ks3-font-mono);
  font-weight: 500;
  font-size: 40px;
  line-height: 1;
  color: var(--ks3-ink);
}
.ks3-dark .ks3-sim-figure { color: var(--ks3-alert); }
.ks3-sim-units {
  font: inherit;
  font-size: 16px;
  padding: 8px 12px;
  min-height: var(--ks3-tap);
  border-radius: var(--ks3-r-control);
  border: 2px solid var(--ks3-ink);
  background: var(--ks3-card);
  color: var(--ks3-ink);
  cursor: pointer;
}
.ks3-sim-caption {
  margin: 12px 0 0;
  font-size: 17px;
  line-height: 1.5;
  color: var(--ks3-ink-muted);
}
.ks3-dark .ks3-sim-caption { color: var(--ks3-on-dark-muted); }

/* R5 — the locked simulation is BLURRED, not hidden. The veil covers the
   canvas only; the caption underneath stays fully readable because it
   holds the instructions for the prediction. */
.ks3-sim-cover {
  display: none;
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  aspect-ratio: 560 / 220;
  margin: 0;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  text-align: center;
  font-size: 18px;
  font-weight: 700;
  color: var(--ks3-ink);
  background: color-mix(in srgb, var(--ks3-card) 85%, transparent);
  border-radius: var(--ks3-r-panel);
}
/* ⚠️ The veil is LIGHT on every block, so its text is DARK on every block —
   including the ink-dark ones. `.ks3-dark p` is (0,1,1) and `.ks3-sim-cover`
   is a <p>, so without this the cover inherited on-dark cream text and
   rendered light-on-light at 1.06:1 inside the practical block: the "make
   your prediction first" instruction was invisible on precisely the lab R5
   exists to gate. Found in a screenshot, then measured. The parity gate now
   carries this pair on both block grounds so it cannot come back. */
.ks3-dark .ks3-sim-cover { color: var(--ks3-ink); }
.ks3-sim[data-locked="1"] .ks3-sim-cover { display: flex; }
.ks3-sim[data-locked="1"] .ks3-sim-canvas { filter: blur(2px) saturate(.65); }
.ks3-sim[data-locked="1"] .ks3-sim-controls { display: none; }
.ks3-sim[data-locked="1"] .ks3-sim-readout { display: none; }

/* ── layers (§5.6) ──
   Violet marks a layer the student CHOSE. Never a level they were put
   in — KS3 has no tier and must not grow one.
   R12: when the support layer is empty it renders NOTHING. The
   generator omits the section entirely; there is no empty-state rule
   here on purpose. */
.ks3-layer { margin: 34px 0 0; }
.ks3-layer-head {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 16px;
}
.ks3-layer-head .ks3-eyebrow { margin: 0; color: var(--ks3-stretch-text); }
.ks3-layer-rule { flex: 1; height: 2px; background: var(--ks3-stretch-rule); }
.ks3-layer-body {
  background: var(--ks3-stretch-tint);
  border: 2px solid var(--ks3-stretch);
  border-radius: 26px;
  padding: 26px 28px;
}
.ks3-layer-body > *:first-child { margin-top: 0; }
.ks3-layer-body p { margin: 0; font-size: 19px; line-height: 1.7; }
.ks3-support .ks3-layer-body {
  background: var(--ks3-stretch-wash);
  border-style: dashed;
  border-color: var(--ks3-stretch-dash);
}

/* ── end matter ─────────────────────────────────────────────────── */

.ks3-endmatter {
  margin: 34px 0 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 16px;
}
.ks3-endmatter section {
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
  border-radius: var(--ks3-r-card);
  padding: 22px;
  margin: 0;
  box-shadow: none;
}
.ks3-endmatter h2 {
  margin: 0 0 10px;
  font-size: 21px;
  line-height: 1.25;
  letter-spacing: -.01em;
}
/* ⊕ MRB-257 · audit 3.12 — the endmatter links measured 28.8px tall, 68+ of
   them, and this is the "what next" a student taps at the end of every lesson.
   The 8px row gap goes and the 44px comes out of the link itself: 28.8 + 8 =
   36.8px of pitch could not hold a 44px target without OVERLAPPING its
   neighbour, and two overlapping targets in a link list is a worse defect than
   a small one — you tap the second and get the third. So the block grows 7.2px
   per link instead, and the targets sit exactly adjacent. */
.ks3-endmatter ul { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 0; }
.ks3-endmatter a {
  font-size: 18px;
  font-weight: 600;
  color: var(--ks3-accent-text);
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  min-height: var(--ks3-tap);
  gap: 6px;
}
.ks3-endmatter a:hover { color: var(--ks3-accent-hover); text-decoration: underline; }
.ks3-endmatter p { margin: 0; font-size: 18px; line-height: 1.5; color: var(--ks3-ink-body); }
/* ⚠️ `.ks3-endmatter .ks3-tutor`, not `.ks3-tutor` — it has to outrank
   `.ks3-endmatter section` above, which is (0,1,1) and would otherwise keep
   the tutor card on the ordinary card ground no matter what came later in the
   file. Caught by the parity gate: the accent resolved to #FFFCF5. */
.ks3-endmatter .ks3-tutor { background: var(--ks3-accent); color: var(--ks3-ink); }

/* ⚠️ 19px/700 IS THE ACCESSIBILITY FIX, not a styling preference.
   Measured: ink #221E1B on accent #E4572E is 4.491:1 — it misses AA for
   body-size text by nine thousandths. Claude Design's reference ships this
   card at 17px, so the flaw is in the reference and the gate caught it.
   Nothing in Design's palette rescues it: on the accent ground, ink is the
   BEST available at 4.49, --ks3-card gives 3.59 and --ks3-on-dark 3.34.
   The two honest options were to abandon the accent ground — losing the one
   card on the page that shouts, which is the whole point of it — or to make
   the text genuinely large, where the WCAG threshold is 3:1 and 4.49 clears
   it with room. 19px at weight 700 is over the 18.66px-bold line, so this is
   a reclassification the text actually earns, not a waiver. Do not drop it
   back to 17px without also changing the ground. */
.ks3-endmatter .ks3-tutor p {
  color: var(--ks3-ink);
  font-size: 19px;
  font-weight: 700;
}
/* ⊕ MRB-257 · audit 6.2 — THIS IS A BUTTON NOW, not a link.
   The CTA is offered 58 times and used to be an in-page anchor that scrolled
   the student to a section they had already finished; on `the-menstrual-cycle`
   at 390px it scrolled 9,768px BACKWARDS. It now opens the tutor, which is an
   action, not an address — so it is a <button>, and a button needs the UA
   border, appearance and font-family cleared to keep the drawn shape.
   `display` is inline-FLEX rather than inline-block so `min-height` can carry
   the tap target: at 10px padding on a 16px label the control measured 40px,
   under audit 3.12's 44px. That was invisible while it was a link in a run of
   text; it is not invisible on the only control in the block. */
.ks3-tutor-cta {
  display: inline-flex;
  align-items: center;
  min-height: var(--ks3-tap);
  margin-top: 12px;
  padding: 10px 17px;
  border: 0;
  border-radius: 12px;
  appearance: none;
  -webkit-appearance: none;
  background: var(--ks3-card);
  color: var(--ks3-accent-text);
  font-family: inherit;
  font-weight: 700;
  font-size: 16px;
  text-decoration: none;
  cursor: pointer;
}

/* R14 — anything legal sits small, at the bottom edge, never a callout. */
.ks3-legal {
  margin: 34px 0 0;
  font-size: 15px;
  line-height: 1.5;
  color: var(--ks3-ink-muted);
  border-top: 1px solid var(--ks3-rule);
  padding-top: 16px;
}

.ks3-pending { color: var(--ks3-ink-muted); }
.ks3-pending em { font-style: normal; font-size: .9em; }

/* ── coming soon (§11 decision 8) ───────────────────────────────── */

.ks3-coming-soon { background: var(--ks3-inset); box-shadow: none; }
.ks3-tag {
  display: inline-block;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  padding: 8px 16px;
  border-radius: var(--ks3-r-pill);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-rule-strong);
  color: var(--ks3-ink-muted);
  margin: 0 0 16px;
}

/* ── index rows and cards ───────────────────────────────────────── */

.ks3-lesson-list {
  margin: 26px 0 0;
  padding: 0;
  list-style: none;
  border: 2px solid var(--ks3-ink);
  border-radius: var(--ks3-r-card);
  overflow: hidden;
  background: var(--ks3-card);
}
.ks3-lesson-row {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 15px 20px;
  border-bottom: 1px solid var(--ks3-rule);
  flex-wrap: wrap;
}
.ks3-lesson-row:last-child { border-bottom: 0; }
.ks3-lesson-row > a {
  flex: 1 1 240px;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 21px;
  letter-spacing: -.015em;
  color: var(--ks3-ink);
  text-decoration: none;
}
.ks3-lesson-row > a:hover { color: var(--ks3-accent-text); text-decoration: underline; }
.ks3-num {
  flex: 0 0 38px;
  height: 38px;
  border-radius: 12px;
  background: var(--ks3-band);
  border: 2px solid var(--ks3-rule);
  display: grid;
  place-items: center;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 17px;
  color: var(--ks3-ink);
}
.ks3-family {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .12em;
  padding: 4px 9px;
  border-radius: 7px;
  background: var(--ks3-band);
  color: var(--ks3-ink-muted);
}
.ks3-badge {
  font-size: 14px;
  font-weight: 700;
  padding: 6px 13px;
  border-radius: var(--ks3-r-pill);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-rule-strong);
  color: var(--ks3-ink-muted);
}
/* ⊕ MRB-221 — `.ks3-badge.is-draft` deleted with the marker it mirrored. */
.ks3-badge.is-soon {
  background: var(--ks3-band);
  border-color: var(--ks3-rule-strong);
  color: var(--ks3-ink-muted);
}

/* A coming-soon row dims; a cross-reference row goes blue and wraps so
   the pointer gets its own line. */
.ks3-lesson-row.is-soon { background: var(--ks3-row-dim); }
.ks3-lesson-row.is-soon > a { color: var(--ks3-ink-faint); cursor: default; }
.ks3-lesson-row.is-ref .ks3-num { background: var(--ks3-blue-tint); border-color: var(--ks3-blue); }
.ks3-lesson-row.is-ref .ks3-badge {
  background: var(--ks3-blue-tint);
  border-color: var(--ks3-blue);
  color: var(--ks3-blue-text);
}
/* R13 — says WHERE, never WHEN. Naming a year here would be false for
   any school teaching a different order, and would make lesson page
   text a function of the sequence. */
.ks3-ref-note {
  flex-basis: 100%;
  margin: 6px 0 0;
  font-size: 17px;
  line-height: 1.5;
  color: var(--ks3-ink-body);
}
.ks3-ref-note em { font-style: italic; }

.ks3-unit-grid, .ks3-disc-grid {
  list-style: none;
  margin: 26px 0 0;
  padding: 0;
  display: grid;
  gap: 18px;
  grid-template-columns: repeat(auto-fit, minmax(min(20rem, 100%), 1fr));
}
.ks3-unit-card > a, .ks3-disc-card > a {
  display: flex;
  flex-direction: column;
  gap: 9px;
  height: 100%;
  padding: 20px 22px;
  border: 2px solid var(--ks3-ink);
  border-radius: var(--ks3-r-card);
  background: var(--ks3-card);
  box-shadow: var(--ks3-shadow-block);
  text-decoration: none;
  color: var(--ks3-ink);
  transition: transform .16s;
}
.ks3-unit-card > a:hover, .ks3-disc-card > a:hover { transform: translate(-3px, -3px); }
.ks3-unit-card h2, .ks3-disc-card h2 {
  margin: 0;
  font-size: 26px;
  line-height: 1.15;
  letter-spacing: -.02em;
}
.ks3-code {
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ks3-accent-text);
}

/* ── browse layer ───────────────────────────────────────────────── */

/* Season tint, read off the half-term slug.
   MRB-182: Design recoloured the browse layer BY TERM. Autumn / spring /
   summer used to borrow the accent / success / blue hues, which meant a
   half-term tile and a subject dot could be the same colour and mean
   two different things. They are their own three hues now, each with a
   darkened ink for text at body size on the fill (R1 — ink on the
   autumn amber is only 3.68:1, so a 16px line on it takes --ks3-*-ink).
   Every card still names its half term as well (R2). */
[data-season="autumn"] { --ks3-season: var(--ks3-autumn); --ks3-season-ink: var(--ks3-autumn-ink); }
[data-season="spring"] { --ks3-season: var(--ks3-spring); --ks3-season-ink: var(--ks3-spring-ink); }
[data-season="summer"] { --ks3-season: var(--ks3-summer); --ks3-season-ink: var(--ks3-summer-ink); }

/* ── the hub ────────────────────────────────────────────────────── */

/* ⊕ MRB-257, found in passing while gating MRB-229's 390px assertion, and NOT
   in the audit — which swept lesson pages at 320px and browse pages only at
   390. `minmax(340px, 1fr)` is a floor, not a preference: below 340px of
   available width the track stays 340px and the grid overflows its own
   container. At the 320px reflow width the hub pushed the document to 356px.
   `min(340px, 100%)` is the standard repair and changes nothing at any width
   where 340px fits, which is every width the hub was designed at. Same shape
   one rule down at `minmax(320px, 1fr)`, repaired with it. */
.ks3-hub-top {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(340px, 100%), 1fr));
  gap: 56px;
  align-items: end;
}
.ks3-hub-title {
  margin: 14px 0 0;
  font-size: clamp(48px, 6.4vw, 86px);
  line-height: .92;
  letter-spacing: -.035em;
}
/* "poke at" — accent at 48px+, which is comfortably inside R1's
   large-text allowance for --ks3-accent. */
.ks3-hero-mark { color: var(--ks3-accent); position: relative; display: inline-block; }
.ks3-swash {
  position: absolute;
  left: 0;
  bottom: -6px;
  width: 100%;
  height: 16px;
  overflow: visible;
}
.ks3-swash path {
  fill: none;
  stroke: var(--ks3-alert);
  stroke-width: 9;
  stroke-linecap: round;
}
.ks3-hub-sub {
  margin: 30px 0 0;
  font-size: 22px;
  line-height: 1.55;
  max-width: 31ch;
  color: var(--ks3-ink-body);
}
.ks3-hub-actions { display: flex; gap: 14px; margin-top: 34px; flex-wrap: wrap; }

/* The two hero buttons, and the shape every button on the browse layer
   takes: hard 5px offset, 2px ink border, no blur. */
.ks3-btn {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 17px 28px;
  min-height: var(--ks3-tap);
  border-radius: 16px;
  border: 2px solid var(--ks3-ink);
  background: var(--ks3-card);
  box-shadow: 5px 5px 0 var(--ks3-ink);
  font: inherit;
  font-weight: 700;
  font-size: 19px;
  color: var(--ks3-ink);
  text-decoration: none;
  cursor: pointer;
  transition: transform .16s cubic-bezier(.34, 1.56, .64, 1), box-shadow .16s;
}
.ks3-btn:hover { transform: translate(-3px, -3px); box-shadow: 8px 8px 0 var(--ks3-ink); }
.ks3-btn:active { transform: translate(2px, 2px); box-shadow: 1px 1px 0 var(--ks3-ink); }
/* ⚠️ MRB-208 / MRB-206: the primary fills with --ks3-accent-text (6.0:1),
   NOT the raw --ks3-accent Design drew. Cream on the raw accent is 3.4:1,
   which fails AA for a 19px label. The accent stays legal for this
   button's BORDER and SHADOW, and for the hero word above at 48px+. */
.ks3-btn.is-primary { background: var(--ks3-accent-text); color: var(--ks3-on-dark); }

/* ── the hub's lesson picker (MRB-212) ──────────────────────────── */

.ks3-picker { position: relative; }
.ks3-mark-caret { stroke-width: 3; }
/* The panel hangs under the button. `position:absolute` keeps it out of
   the hero's flow so opening it does not shove the year cards down the
   page; z-index clears the "Live right now" panel beside it. */
.ks3-picker-panel {
  position: absolute;
  top: calc(100% + 12px);
  left: 0;
  z-index: 40;
  min-width: min(360px, 86vw);
  max-height: min(60vh, 460px);
  overflow-y: auto;
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
  border-radius: 22px;
  box-shadow: 5px 5px 0 var(--ks3-ink);
  padding: 18px;
}
.ks3-picker-panel[hidden] { display: none; }
.ks3-picker-group + .ks3-picker-group { margin-top: 18px; }
.ks3-picker-group .ks3-eyebrow { margin: 0 0 8px; }
.ks3-picker-list { list-style: none; margin: 0; padding: 0; }
.ks3-picker-list > li + li { border-top: 1px solid var(--ks3-rule); }
/* R15 — a real link, and a 44px one. Two lines: what it is, and which
   unit it belongs to, so a title alone never has to carry the context. */
.ks3-picker-list a {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 11px 12px;
  min-height: var(--ks3-tap);
  justify-content: center;
  border-radius: var(--ks3-r-control);
  text-decoration: none;
  color: var(--ks3-ink);
}
.ks3-picker-list a:hover { background: var(--ks3-band); }
.ks3-picker-title { font-weight: 700; font-size: 17px; line-height: 1.25; }
.ks3-picker-unit { font-size: 14px; color: var(--ks3-ink-muted); line-height: 1.25; }

/* ⊖ REMOVED by MRB-330, 6 Sep 2026 — the ink-dark "Live right now" panel.
   Fourteen rules lived here (`.ks3-live`, `-head`, `-count`, `-line`, `-bars`,
   `-label`, `-of`, `-track`, `-fill` and three `-row[data-discipline]` hue
   assignments), plus two overrides in the 34rem block. The only markup that
   ever carried them was `landing()`'s hub panel in build_ks3.py, which is cut;
   the ruling and the reasoning are written up there. Nothing else in the
   estate names `.ks3-live*` — grepped across build_ks3.py, ks3_art/, the
   gates, and every script and stylesheet under shared/, before removing.
   (Spelled out rather than globbed on purpose: a `shared/` glob written with
   a star is a stray comment opener inside a CSS comment.)

   ⚠️ `.ks3-hub-live` (the "· N live" fragment on a subject card, a little
   further up this file) is a different rule with a similar name and is NOT
   part of this cut.

   ⚠️ No token is orphaned by this removal — checked, because an orphaned one
   would drop out of ks3_parity Layer A's provenance sweep. `--ks3-dark-track`
   still has six readers further down this file (the instrument progress
   tracks), `--ks3-alert`, `--ks3-on-dark-muted` and the three subject hues are
   used throughout, and ks3_parity's MINTED_TOKENS note still cites
   `--ks3-dark-track` by name. Nothing in tokens.css needed touching. */

.ks3-hub-sec { margin-top: 84px; }
.ks3-sec-head {
  display: flex;
  align-items: end;
  justify-content: space-between;
  gap: 24px;
  margin-bottom: 26px;
  flex-wrap: wrap;
}
.ks3-sec-head h2 {
  margin: 0;
  font-size: 40px;
  letter-spacing: -.03em;
  overflow-wrap: break-word;
}
.ks3-sec-head p { margin: 0; font-size: 18px; color: var(--ks3-ink-muted); max-width: 44ch; }

/* Design's browse grid: a touch wider per card and a touch looser than
   the unit index's, so a year card's 52px numeral has room. */
.ks3-unit-grid.is-browse {
  gap: 22px;
  grid-template-columns: repeat(auto-fit, minmax(min(290px, 100%), 1fr));
}

.ks3-browse-year > a { box-shadow: 5px 5px 0 var(--ks3-ink); gap: 14px; padding: 26px 26px 22px; }
.ks3-year-top { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.ks3-browse-year h2 { font-size: 52px; line-height: 1; letter-spacing: -.04em; }
.ks3-year-tile {
  width: 44px;
  height: 44px;
  flex: 0 0 auto;
  border-radius: var(--ks3-r-control);
  border: 2px solid var(--ks3-ink);
  background: var(--ks3-year-tint, var(--ks3-band));
  display: grid;
  place-items: center;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 20px;
}
.ks3-browse-year[data-year="7"] { --ks3-year-tint: var(--ks3-year7-tint); }
.ks3-browse-year[data-year="8"] { --ks3-year-tint: var(--ks3-year8-tint); }
.ks3-browse-year[data-year="9"] { --ks3-year-tint: var(--ks3-year9-tint); }
.ks3-year-blurb { margin: 0; font-size: 17px; line-height: 1.5; color: var(--ks3-ink-body); }
.ks3-browse-cta {
  margin-top: auto;
  font-size: 17px;
  font-weight: 700;
  color: var(--ks3-accent-text);
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
/* the six-segment season strip — decoration, hidden from AT. The second
   half term of each season dims to .62, which is Design's way of showing
   the pairs without adding a third hue. */
.ks3-browse-strip { display: flex; gap: 6px; margin-top: 2px; }
.ks3-browse-strip > span {
  flex: 1;
  height: 10px;
  border-radius: 99px;
  background: var(--ks3-season, var(--ks3-rule));
}
.ks3-browse-strip > span[data-late] { opacity: .62; }

/* the hub's three tinted subject cards */
.ks3-hub-subject[data-discipline="biology"]   { --ks3-tint: var(--ks3-ok-tint); }
.ks3-hub-subject[data-discipline="chemistry"] { --ks3-tint: var(--ks3-accent-tint); }
.ks3-hub-subject[data-discipline="physics"]   { --ks3-tint: var(--ks3-blue-tint); }
.ks3-hub-subject > a {
  display: flex;
  flex-direction: column;
  gap: 12px;
  height: 100%;
  padding: 26px;
  border: 2px solid var(--ks3-ink);
  border-radius: 26px;
  background: var(--ks3-tint);
  box-shadow: 5px 5px 0 var(--ks3-hue, var(--ks3-ink));
  color: var(--ks3-ink);
  text-decoration: none;
  transition: transform .18s cubic-bezier(.34, 1.56, .64, 1), box-shadow .18s;
}
.ks3-hub-subject > a:hover {
  transform: translate(-4px, -4px);
  box-shadow: 9px 9px 0 var(--ks3-hue, var(--ks3-ink));
}
.ks3-hub-subject h3 { margin: 0; font-size: 30px; letter-spacing: -.02em; }
.ks3-hub-subject-meta { margin: 0; font-size: 17px; font-weight: 600; color: var(--ks3-ink-body); }
.ks3-hub-subject-units { margin: 0; font-size: 17px; line-height: 1.55; color: var(--ks3-ink-body); }
.ks3-hub-live { color: var(--ks3-accent-text); font-weight: 700; }

/* ── the year screen ────────────────────────────────────────────── */

/* Head and stat tiles. The 2-up head is shared with the half-term and
   subject screens; only what sits in the right-hand column differs. */
.ks3-browse-head {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
  gap: 52px;
  align-items: end;
}
/* Design drew the year head one step larger than the half-term and
   subject heads (46/74 against 44/70). Unified here on the smaller pair:
   the three screens sit one click apart in the same journey and a title
   that grows when you go UP a level reads as a bug, not a hierarchy. */
.ks3-browse-head h1 {
  margin: 12px 0 0;
  font-size: clamp(44px, 5.4vw, 70px);
  line-height: .95;
  letter-spacing: -.035em;
}
.ks3-browse-head .ks3-intro { font-size: 21px; max-width: 46ch; margin: 22px 0 0; }
.ks3-stat-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.ks3-stat {
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
  border-radius: var(--ks3-r-panel);
  padding: 18px 16px;
  text-align: center;
}
.ks3-stat-n {
  display: block;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 40px;
  line-height: 1;
}
.ks3-stat-l {
  display: block;
  font-size: 15px;
  font-weight: 600;
  color: var(--ks3-ink-muted);
  margin-top: 4px;
}

/* Three season rows, each a sticky term tile beside its two half terms. */
.ks3-season-rows { margin-top: 56px; display: flex; flex-direction: column; gap: 34px; }
.ks3-season-row {
  display: grid;
  grid-template-columns: 210px minmax(0, 1fr);
  gap: 26px;
  align-items: start;
}
/* top:96px clears the 72px sticky header plus Design's breathing room. */
.ks3-season-side { position: sticky; top: 96px; }
.ks3-season-tile {
  background: var(--ks3-season);
  border: 2px solid var(--ks3-ink);
  border-radius: 22px;
  padding: 20px;
  box-shadow: 4px 4px 0 var(--ks3-ink);
}
/* 32px/800 is large text, so ink on the season fill passes at 3:1 (R1). */
.ks3-season-name {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 32px;
  letter-spacing: -.02em;
  color: var(--ks3-ink);
}
/* …but this line is 16px, which is body size, so it MUST take the
   season's darkened ink. Ink on the autumn amber is only 3.68:1. */
.ks3-season-meta {
  margin: 6px 0 0;
  font-size: 16px;
  font-weight: 600;
  color: var(--ks3-season-ink, var(--ks3-ink));
}
.ks3-ht-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(290px, 100%), 1fr));
  gap: 20px;
}

.ks3-browse-ht > a {
  box-shadow: 5px 5px 0 var(--ks3-ink);
  border-radius: 24px;
  padding: 24px;
  gap: 14px;
}
.ks3-ht-head { display: flex; align-items: center; gap: 13px; }
.ks3-browse-ht .ks3-code {
  display: inline-grid;
  place-items: center;
  flex: 0 0 auto;
  width: 46px;
  height: 46px;
  border-radius: 15px;
  background: var(--ks3-season, var(--ks3-band));
  border: 2px solid var(--ks3-ink);
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 23px;
  color: var(--ks3-ink);
  letter-spacing: 0;
}
.ks3-ht-name {
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 26px;
  letter-spacing: -.02em;
  line-height: 1.1;
}
.ks3-browse-ht .ks3-meta { font-size: 16px; font-weight: 600; }
/* One row per unit taught in this half term, dot coloured by discipline.
   However many there are — a half term is not guaranteed three. */
.ks3-ht-units {
  list-style: none;
  margin: 0;
  padding: 14px 0 0;
  border-top: 2px dashed var(--ks3-rule);
  display: flex;
  flex-direction: column;
  gap: 9px;
}
.ks3-ht-units > li { display: flex; gap: 10px; align-items: baseline; font-size: 17px; line-height: 1.4; }
.ks3-ht-units > li[data-discipline="biology"]   { --ks3-hue: var(--ks3-biology); }
.ks3-ht-units > li[data-discipline="chemistry"] { --ks3-hue: var(--ks3-chemistry); }
.ks3-ht-units > li[data-discipline="physics"]   { --ks3-hue: var(--ks3-physics); }
.ks3-ht-dot {
  width: 11px;
  height: 11px;
  flex: 0 0 auto;
  border-radius: 99px;
  background: var(--ks3-hue, var(--ks3-accent));
  transform: translateY(-1px);
}
.ks3-browse-split { margin: 0; font-size: 16px; color: var(--ks3-ink-muted); }

/* ── the half-term screen ───────────────────────────────────────── */

/* The season-coloured stat panel. Design drew it amber because it drew
   half term 1; it takes the page's OWN season here, so half terms 3–6
   are green and teal. */
.ks3-ht-panel {
  background: var(--ks3-season, var(--ks3-band));
  border: 2px solid var(--ks3-ink);
  border-radius: 24px;
  padding: 24px;
  box-shadow: 5px 5px 0 var(--ks3-ink);
}
/* 56px/800 — large text, so ink on the season fill passes at 3:1. */
.ks3-ht-panel-n {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 56px;
  line-height: 1;
  letter-spacing: -.03em;
  color: var(--ks3-ink);
}
/* Both of these are body size on the fill, so both take the season ink. */
.ks3-ht-panel-l {
  margin: 6px 0 0;
  font-size: 18px;
  font-weight: 600;
  color: var(--ks3-season-ink, var(--ks3-ink));
}
.ks3-ht-panel-split {
  margin: 9px 0 0;
  font-size: 15px;
  font-weight: 600;
  color: var(--ks3-season-ink, var(--ks3-ink));
}
/* Each subject's flex-grow IS its lesson count — the bar is the split,
   not an illustration of it. */
.ks3-ht-bar { margin-top: 16px; display: flex; gap: 6px; }
.ks3-ht-bar > span { height: 12px; border-radius: 99px; background: var(--ks3-hue, var(--ks3-accent)); }
.ks3-ht-bar > span[data-discipline="biology"]   { --ks3-hue: var(--ks3-biology); }
.ks3-ht-bar > span[data-discipline="chemistry"] { --ks3-hue: var(--ks3-chemistry); }
.ks3-ht-bar > span[data-discipline="physics"]   { --ks3-hue: var(--ks3-physics); }

/* the three subject cards */
.ks3-browse-subject > a { border-radius: 26px; padding: 26px; gap: 16px; }
.ks3-ht-unit { display: flex; flex-direction: column; }
.ks3-ht-unit-title {
  margin: 5px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 23px;
  line-height: 1.15;
  letter-spacing: -.02em;
}
.ks3-ht-lessons {
  list-style: none;
  margin: 14px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 11px;
}
.ks3-ht-lessons > li { display: flex; gap: 12px; align-items: baseline; font-size: 18px; line-height: 1.4; }
/* R2 — an authored lesson is bolder AND the card's CTA says so in words. */
.ks3-ht-lessons > li.is-live { font-weight: 700; }
.ks3-ht-n {
  font-family: var(--ks3-font-mono);
  font-weight: 500;
  color: var(--ks3-hue, var(--ks3-accent-text));
  flex: 0 0 auto;
}
.ks3-ht-soon {
  margin-top: auto;
  font-size: 16px;
  font-weight: 600;
  color: var(--ks3-ink-muted);
}
.ks3-browse-subject .ks3-browse-cta { font-size: 17px; }
/* (0,2,0) beats the base .ks3-browse-dot regardless of source order — the
   hub keeps Design's 20px dot, the half-term card takes its 22px one.) */
.ks3-browse-subject .ks3-browse-dot { width: 22px; height: 22px; }

.ks3-browse-subject[data-discipline="biology"]   { --ks3-hue: var(--ks3-biology); }
.ks3-browse-subject[data-discipline="chemistry"] { --ks3-hue: var(--ks3-chemistry); }
.ks3-browse-subject[data-discipline="physics"]   { --ks3-hue: var(--ks3-physics); }
.ks3-browse-subject > a { box-shadow: 5px 5px 0 var(--ks3-hue, var(--ks3-ink)); }
/* The half-term subject card's heading. The hub's subject cards are
   `.ks3-hub-subject` and use an h3, so this selector is theirs alone. */
.ks3-browse-subject h2 { font-size: 32px; letter-spacing: -.025em; }
.ks3-browse-dot {
  width: 20px;
  height: 20px;
  border-radius: 99px;
  background: var(--ks3-hue, var(--ks3-accent));
  border: 2px solid var(--ks3-ink);
  flex: 0 0 auto;
}
.ks3-browse-subject-head { display: flex; align-items: center; gap: 11px; }

/* ── the subject screen ─────────────────────────────────────────── */

/* The h1 wears the subject dot. --subject is set inline on <body> by
   shell(); --ks3-hue is the browse layer's own name for the same idea. */
.ks3-h1-dotted { display: flex; align-items: center; gap: 18px; }
.ks3-subject-dot {
  width: 26px;
  height: 26px;
  flex: 0 0 auto;
  border-radius: 99px;
  background: var(--subject, var(--ks3-accent));
  border: 3px solid var(--ks3-ink);
}

/* the ink-dark unit panel, with Design's orange offset shadow */
.ks3-unit-panels { display: flex; flex-direction: column; gap: 18px; }
.ks3-unit-panel {
  background: var(--ks3-ink);
  color: var(--ks3-on-dark);
  border-radius: 24px;
  padding: 24px;
  box-shadow: 5px 5px 0 var(--ks3-accent);
}
.ks3-unit-panel .ks3-eyebrow { color: var(--ks3-on-dark-muted); margin: 0; }
.ks3-unit-panel-title {
  margin: 8px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 30px;
  line-height: 1.1;
  letter-spacing: -.02em;
}
.ks3-unit-panel-title a { color: inherit; text-decoration: none; }
.ks3-unit-panel-title a:hover { text-decoration: underline; }
.ks3-unit-panel-intro {
  margin: 14px 0 0;
  font-size: 17px;
  line-height: 1.55;
  color: var(--ks3-on-dark-body);
}

.ks3-unit-body {
  margin-top: 52px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
  gap: 36px;
  align-items: start;
}

/* The timeline. The dashed connector is drawn once, behind the nodes,
   as a repeating gradient rather than 12 border segments. It is pure
   decoration: the numbers carry the order. */
.ks3-timeline {
  list-style: none;
  margin: 0;
  padding: 0;
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 18px;
}
.ks3-timeline::before {
  content: "";
  position: absolute;
  left: 31px;
  top: 40px;
  bottom: 40px;
  width: 3px;
  background: repeating-linear-gradient(var(--ks3-ink) 0 9px, transparent 9px 18px);
  opacity: .35;
}
.ks3-tl-row { position: relative; }
.ks3-tl-row > a {
  display: grid;
  grid-template-columns: 64px minmax(0, 1fr);
  gap: 18px;
  align-items: start;
  text-decoration: none;
  color: var(--ks3-ink);
  transition: transform .18s cubic-bezier(.34, 1.56, .64, 1);
}
.ks3-tl-row > a:hover { transform: translateX(5px); }
/* R2 — the node's FILL says written, and the card's CTA says it in words
   as well. Neither signal is doing the job alone. */
.ks3-tl-node {
  width: 64px;
  height: 64px;
  border-radius: 20px;
  border: 3px solid var(--ks3-ink);
  box-shadow: 3px 3px 0 var(--ks3-ink);
  background: var(--ks3-card);
  color: var(--ks3-ink);
  display: grid;
  place-items: center;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 27px;
}
.ks3-tl-row[data-live] .ks3-tl-node {
  background: var(--ks3-accent-text);
  color: var(--ks3-on-dark);
}
.ks3-tl-card {
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
  border-radius: 22px;
  box-shadow: 4px 4px 0 var(--ks3-ink);
  padding: 20px 22px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.ks3-tl-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.ks3-tl-title {
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 26px;
  line-height: 1.1;
  letter-spacing: -.02em;
}
.ks3-tl-blurb { font-size: 18px; line-height: 1.5; color: var(--ks3-ink-body); }
.ks3-tl-soon { font-size: 17px; font-weight: 700; color: var(--ks3-ink-ghost); }
.ks3-tl-row .ks3-browse-cta { margin-top: 0; }
/* The family chip. `family_label()` sends it here title-cased and this
   uppercases it visually — a literal run of capitals in the bytes is
   read letter by letter by some screen readers. */
.ks3-family { text-transform: uppercase; letter-spacing: .12em; padding: 5px 10px; border-radius: 8px; background: var(--ks3-tag); }

/* the sticky aside */
.ks3-unit-aside { position: sticky; top: 96px; display: flex; flex-direction: column; gap: 18px; }
.ks3-aside-box {
  border: 2px solid var(--ks3-ink);
  border-radius: 22px;
  padding: 22px;
  background: var(--ks3-card);
}
.ks3-aside-words { background: var(--ks3-accent-tint); }
.ks3-aside-box h3 { margin: 0 0 12px; font-size: 22px; letter-spacing: -.02em; }
.ks3-terms { display: flex; flex-wrap: wrap; gap: 8px; }
.ks3-term {
  padding: 8px 13px;
  border-radius: 11px;
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
  font-size: 16px;
  font-weight: 600;
}
.ks3-aside-line { margin: 0 0 12px; font-size: 17px; line-height: 1.5; color: var(--ks3-ink-muted); }
.ks3-later-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.ks3-later-list > li { display: flex; gap: 12px; align-items: baseline; font-size: 18px; }
.ks3-tl-n { font-family: var(--ks3-font-mono); color: var(--ks3-ink-ghost); flex: 0 0 auto; }

.ks3-browse-unit { margin: 34px 0 0; }
.ks3-browse-unit h2 { margin: 10px 0 0; font-size: 30px; letter-spacing: -.03em; }
.ks3-browse-unit h2 a { color: var(--ks3-ink); text-decoration: none; }
.ks3-browse-unit h2 a:hover { color: var(--ks3-accent-text); text-decoration: underline; }

.ks3-browse-alt { margin: 26px 0 0; display: flex; gap: 20px; flex-wrap: wrap; }
.ks3-browse-alt a {
  font-size: 17px;
  font-weight: 700;
  color: var(--ks3-accent-text);
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.ks3-browse-alt a:hover { text-decoration: underline; }

/* R14 keeps this one: two routes are visible on the page and the reader
   is genuinely holding the question of whether the other leads
   somewhere different. */
.ks3-browse-secondary { margin: 52px 0 0; }
.ks3-browse-secondary h2 { margin: 0 0 6px; font-size: 32px; letter-spacing: -.025em; }

/* MRB-182 — Design's footer band: the tag ground, the wordmark at
   display weight, and the quick links back up the tree on the right. */
.ks3-footer {
  border-top: 2px solid var(--ks3-ink);
  background: var(--ks3-tag);
  color: var(--ks3-ink-muted);
}
.ks3-footer-rail {
  max-width: var(--ks3-rail);
  margin: 0 auto;
  padding: 34px 32px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
}
.ks3-footer-title {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 20px;
  letter-spacing: -.02em;
  color: var(--ks3-ink);
}
.ks3-footer-links { display: flex; gap: 22px; flex-wrap: wrap; }
.ks3-footer-links a {
  font-size: 17px;
  font-weight: 600;
  color: var(--ks3-ink-muted);
  text-decoration: none;
  /* R15 — a real control, and a thumb-sized one. */
  display: inline-flex;
  align-items: center;
  min-height: var(--ks3-tap);
}
.ks3-footer-links a:hover { color: var(--ks3-accent-text); text-decoration: underline; }

/* ── motion (R6, R7) ────────────────────────────────────────────── */

@keyframes ks3-reveal {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: none; }
}
@keyframes ks3-pop {
  0%   { transform: scale(.9); }
  60%  { transform: scale(1.03); }
  100% { transform: scale(1); }
}

@media (prefers-reduced-motion: no-preference) {
  .ks3-reveal[data-reveal],
  .ks3-crit-wrap,
  .ks3-ticks { animation: ks3-reveal .22s both; }
}

/* R6 — reduced motion is a COMPLETE experience, not a degraded one.
   Nothing here removes information: the simulations stop animating and
   their written readout carries the whole result instead. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}

/* ═══════════════════════════════════════════════════════════════
   B1 ROUND TWO — the progress rail and four new block types
   Every value below was measured on Design's approved B1 pages in a
   real browser at 1280 / 1340 / 820 / a true 390 layout viewport,
   with device metrics overridden so viewport queries actually fire.
   Shrinking a container does not fire a max-width query — the
   viewport is still wide (MRB-210 finding 3).
   ═══════════════════════════════════════════════════════════════ */

/* ── anchors (⊕ MRB-229 comment 2 · audit 3.3) ──
   EVERY id-bearing element in the lesson column, not only the rail's stages.
   A hash link from the trail, the endmatter, the tutor card or another lesson
   lands under the sticky bar without it.

   ⊖ THIS USED TO BE `.ks3-lesson [id] { scroll-margin-top: 92px }`, "measured
   by driving the rail's own links: #s-sort lands at top: 91.94px". It was
   measured on ONE page. `header.ks3-nav` is `position: sticky; top: 0` and its
   height varies from 86.3px to 214.8px, because `.ks3-trail` wraps to as many
   as four lines — so a single number was right on the three pages where the
   header stayed one line and wrong on the other 36 of 58, where a student
   taps a rail stop and arrives at a section whose heading is behind the
   header. Up to 123.2px of it: an entire `<h2>`, or "START HERE" on eleven
   pages in B8–B11. That breaks the rail, which is the page's primary
   navigation on a phone, and the tutor CTA with it.

   ANOTHER FIXED NUMBER IS NOT THE FIX. Chemistry and Physics have longer unit
   titles still to come, and this would drift again the day one lands. So:

     1. `scroll-padding-top` on the scrollport, not `scroll-margin-top` on the
        target — one declaration for the whole document instead of one per id,
        and it composes with the sticky bar rather than with each section.
     2. The value is `--ks3-sticky-h`, which `shared/ks3.js` REWRITES from a
        `ResizeObserver` on the header and the rail bar. Whatever those two
        actually measure, at whatever viewport, in whatever language, is what
        an anchor clears.
     3. The values below are the FALLBACK, for the moment before the observer
        first fires and for any engine without one. They are deliberately
        generous — landing a heading 30px low is a blemish, landing it behind
        the header is a broken link — and the narrow one accounts for the rail
        bar, which below 1340px is sticky and sits under the header.

   ⚠️ `scroll-margin-top` and `scroll-padding-top` ADD. The old rule is gone
   rather than kept "as a fallback": leaving it would have over-scrolled every
   anchor by 92px the moment this one started working. */
:root {
  --ks3-nav-h: 72px;      /* the sticky header alone */
  --ks3-sticky-h: 92px;   /* header + anything sticky under it */
}
@media (max-width: 1339px) {
  :root { --ks3-sticky-h: 140px; }   /* the rail bar is sticky down here */
}
html { scroll-padding-top: calc(var(--ks3-sticky-h) + 12px); }

/* ── the progress rail, both variants (MRB-208 rule 2) ──
   There is never a viewport with both and never one with neither. */
[data-rail="side"] { display: none; }
@media (min-width: 1340px) {
  [data-rail="side"] { display: block; }
  [data-rail="top"] { display: none; }
}

.ks3-rail {
  position: fixed;
  top: 150px;
  /* Sits in the gutter: .ks3-main caps at 1320px, so at 1340 the rail
     occupies x 38–142 and the lesson column starts at 190 — a 48px gap
     that only widens above 1340. */
  left: calc(50% - 632px);
  width: 104px;
  z-index: 20;
}
.ks3-rail ol {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin: 0;
  padding: 0;
  list-style: none;
}
.ks3-rail li { display: flex; flex-direction: column; align-items: center; }
.ks3-rail a {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
  padding: 2px 0;
  text-decoration: none;
  color: inherit;
}
/* ⊕ MRB-257 · audit 3.6 — THE RAIL'S RESTING STATE IS `--ks3-ink-muted`, NOT
   `--ks3-ink-ghost`, and this is on all 58 lesson pages.
   `--ks3-ink-ghost` #9A8F86 is documented in tokens.css as the SPENT OPTION
   BADGE GLYPH — a state nobody has to read. The rail is not spent, it is
   upcoming, and it is a live `<a>`: on desktop it is the only in-page
   navigation there is. Measured on the page ground it was 2.87:1 for the
   11px/500 label and 3.08:1 for the 16px/800 chip (bold under 18.66px is NOT
   large text, so the bar is 4.5 and not 3.0). `--ks3-ink-muted` #5F564F takes
   both to 6.5:1.
   Nothing else moves: `is-current`'s ring and ink and `is-done`'s accent fill
   both already passed and both still carry the state, so the rail still says
   where you are and how far you have got — it is only the stops you have not
   reached yet that stop being a rumour. */
.ks3-rail-chip {
  width: 32px;
  height: 32px;
  border-radius: 10px;
  border: 2px solid var(--ks3-rule-strong);
  background: var(--ks3-card);
  color: var(--ks3-ink-muted);
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 16px;
  display: grid;
  place-items: center;
}
.ks3-rail-label {
  font-family: var(--ks3-font-mono);
  font-size: 11px;
  font-weight: 500;
  line-height: 1.2;
  letter-spacing: .09em;
  text-transform: uppercase;
  text-align: center;
  color: var(--ks3-ink-muted);
}
/* CURRENT is scroll — "where am I". It is a ring and an ink border, never
   a tick, so it cannot be mistaken for progress. */
.ks3-rail li.is-current .ks3-rail-chip {
  border-color: var(--ks3-ink);
  color: var(--ks3-ink);
  box-shadow: 0 0 0 4px var(--ks3-accent-tint);
}
.ks3-rail li.is-current .ks3-rail-label { color: var(--ks3-ink); }
/* DONE is completion — the accent fill and the drawn mark. Never on load. */
/* ⚠️ ⊕ MRB-277, 21 Aug 2026 — `color` HERE IS A LOADED GUN WITH THE SAFETY ON,
   AND THE SAFETY IS IN ks3.js. `--ks3-on-dark` on `--ks3-accent` measures
   3.34:1. That PASSES as a non-text graphical object (WCAG 1.4.11, 3:1) and
   would FAIL as text (1.4.3, 4.5:1) — and this chip normally holds the lesson
   NUMBER, which is text.
   It is safe today for one reason only: `shared/ks3.js` swaps the numeral for
   `TICK_SVG` in this state, so the accent ground carries a drawn mark and
   never a numeral. The `color` declaration is what the numeral WOULD use.
   So this rule's accessibility depends on a substitution in a different file.
   If that swap is ever removed, or a rail reaches `is-done` before the runtime
   paints, a number ships at 3.34:1 on all 87 rails. Either keep the swap, or
   raise this colour before you let a glyph back onto this ground.
   Found by the MRB-277 chip sweep, which measured all 45 chip states across
   295 pages: every one passes, and this is the only one that passes for a
   reason that is not written down. Now it is. */
.ks3-rail li.is-done .ks3-rail-chip {
  background: var(--ks3-accent);
  border-color: var(--ks3-ink);
  color: var(--ks3-on-dark);
}
.ks3-rail li.is-done .ks3-rail-label { color: var(--ks3-ink-muted); }
.ks3-rail-line {
  width: 2px;
  height: 20px;
  margin: 7px 0;
  background: var(--ks3-rule);
}
.ks3-rail li.is-done .ks3-rail-line { background: var(--ks3-accent); }

/* ⊕ MRB-257 · audit 3.15 — `display: contents` IS WHAT MAKES THE STICKY BELOW
   REAL. `.ks3-railbar` has always declared `position: sticky; top: 0`, and it
   has never once stuck: its containing block `div.ks3-rails` is exactly as
   tall as the bar (46.6px), so the sticky range is zero. Measured at scrollY
   2500 the bar's rect top is −2414 and `visibleBelowHeader = 0`. The desktop
   `[data-rail="side"]` is `position: fixed` and does persist — so **a phone
   student never sees `data-rail-count` or `data-rail-fill` move at all**,
   which is the whole progress mechanism, on the viewport most of them use.

   Dropping the declaration was the audit's other option and it is the wrong
   one: it would make the file honest about a rail that does not work rather
   than making the rail work. `display: contents` removes `.ks3-rails`' box, so
   the bar's containing block becomes the document and the sticky range becomes
   the page. The div keeps its `data-rail-stages` payload and every JS
   `querySelector` still finds it — nothing about the element changes except
   that it stops generating a box, and it painted nothing to lose.

   `top` is the LIVE header height rather than 0. At `top: 0` the bar would
   share the edge with `.ks3-nav`, whose `z-index: 60` beats this one's 20, so
   a bar that finally stuck would have stuck UNDER the header. Same custom
   property the anchors use, written by the same observer. */
.ks3-rails { display: contents; }
.ks3-railbar {
  position: sticky;
  top: var(--ks3-nav-h, 72px);
  z-index: 20;
  background: var(--ks3-ground);
  border-bottom: 2px solid var(--ks3-rule);
  padding: 9px 16px 10px;
}
.ks3-railbar-row {
  display: flex;
  align-items: center;
  gap: 12px;
  max-width: var(--ks3-wide);
  margin: 0 auto;
}
.ks3-railbar-count {
  flex: 0 0 auto;
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
  color: var(--ks3-ink-muted);
}
.ks3-railbar-label {
  flex: 1 1 auto;
  font-size: 16px;
  font-weight: 700;
  color: var(--ks3-ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ks3-railbar-track {
  flex: 0 0 96px;
  height: 8px;
  border-radius: 99px;
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
  overflow: hidden;
}
.ks3-railbar-fill {
  display: block;
  height: 100%;
  width: 0;
  background: var(--ks3-accent);
}

/* ── KEY FACT (⊕ §4.8.1 B) ──
   The ACCENT offset shadow is what separates this from a .ks3-block, whose
   shadow is ink. Never amber: amber is a wrong idea being confronted, and a
   key fact must never be confusable with a misconception block. And it grows
   no badge, letter or mark — --ks3-band is also the chosen-WRONG ladder
   ground, so anything mark-like here reads as a verdict. */
.ks3-keyfact {
  margin-top: 24px;
  padding: 18px 22px;
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
  border-radius: var(--ks3-r-panel);
  box-shadow: 5px 5px 0 var(--ks3-accent);
}
.ks3-keyfact[data-ground="card"] { background: var(--ks3-card); }
.ks3-keyfact[data-ground="inset"] { background: var(--ks3-inset); }
.ks3-keyfact-label {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  line-height: 1.6;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ks3-accent-text);
}
.ks3-keyfact-body {
  margin: 6px 0 0;
  font-family: var(--ks3-font-display);
  font-size: 22px;
  font-weight: 700;
  line-height: 1.35;
  letter-spacing: -.015em;
  color: var(--ks3-ink);
}

/* ── the statement panel (⊕ §5.1.1 `rule`) ──
   3px border and no shadow is what separates it from a .ks3-block. The clamp
   is drift 3's RULED value, not any one page's own. */
.ks3-rule {
  margin-top: 28px;
  padding: 34px 32px;
  background: var(--ks3-band);
  border: 3px solid var(--ks3-ink);
  border-radius: var(--ks3-r-block);
}
.ks3-rule > .ks3-eyebrow { color: var(--ks3-accent-text); }
.ks3-rule-statement {
  margin: 10px 0 0;
  max-width: 20ch;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: clamp(28px, 3.9vw, 44px);
  line-height: 1.06;
  letter-spacing: -.03em;
  color: var(--ks3-ink);
}
.ks3-rule-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 14px;
  margin: 24px 0 0;
  padding: 0;
  list-style: none;
}
/* --ks3-option-border, NOT ink — that is what separates these from the
   misconception block's cards.

   ⚠️ `> li`, AND THE CHILD COMBINATOR IS LOAD-BEARING (⊕ MRB-245). This was a
   descendant selector, so at (0,1,1) it reached every `<li>` at any depth
   inside the grid — including the named-part PILLS a card can now carry, which
   are (0,1,0) and lost. Same cascade trap as `.ks3-dark p`, in the light half
   of the stylesheet: the pills shipped on the card ground with the option
   border, looking almost right and not being it. A card is a DIRECT child of
   the grid and always was, so every shipped card is byte-identical across the
   change. Caught by the parity row that pins the pill, on the first run after
   the pill existed. */
.ks3-rule-cards > li {
  padding: 17px 19px;
  background: var(--ks3-card);
  border: 2px solid var(--ks3-option-border);
  border-radius: var(--ks3-r-card);
}
.ks3-rule-term { margin: 0; font-size: 18px; font-weight: 700; }
.ks3-rule-gloss {
  margin: 6px 0 0;
  font-size: 17px;
  line-height: 1.5;
  color: var(--ks3-ink-body);
}
/* ⊕ MRB-245 — the three card parts that were AUTHORED AND NEVER DRAWN. See
   `_rule_card` for what shipped empty and on which pages. Every value here is
   measured off Design's own markup (b1-03 lines 230–248, b1-04 lines 218–223,
   b7-01 lines 173–178), and each rule reaches only a card that authors its
   key, so no shipped `term` + `gloss` card moves.

   ⚠️ THE ROLE LINE TAKES TWO TONES AND THE RECORD CHOOSES. b1-03 draws the
   "In both · 4" card's label in `--ks3-ink-muted` and the "Plant only · 3"
   card's in `--ks3-accent-text` — the difference IS the claim the panel makes,
   that the plant's list is the animal's plus three, so it is data
   (`label_tone`) and not a constant. Accent is the default, because b1-04's
   four problems and b7-01's four parts all take it. */
.ks3-rule-role {
  margin: 0 0 6px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-accent-text);
}
.ks3-rule-role[data-tone="muted"] { color: var(--ks3-ink-muted); }
.ks3-rule-chips {
  list-style: none;
  margin: 14px 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
/* Ink, not `--ks3-option-border`: these are not controls and never were, and
   the 2px ink outline is what makes a pill read as a named part rather than
   as something to press. */
.ks3-rule-chip {
  padding: 8px 14px;
  border-radius: var(--ks3-r-pill);
  border: 2px solid var(--ks3-ink);
  background: var(--ks3-inset);
  color: var(--ks3-ink);
  font-size: 17px;
  font-weight: 700;
}
.ks3-rule-chips[data-tone="accent-tint"] .ks3-rule-chip {
  background: var(--ks3-accent-tint);
}
/* The card's foot line: the cells that answer the problem it names. Mono and
   muted, so it reads as evidence for the card rather than as more of it. */
/* ⊕ MRB-248 / B11 — the card's fourth part: what the method CANNOT do.
   b11-04's bank cards say what each way of storing variation is, and then, in
   a second paragraph, what it cannot do — which is the argument of the whole
   panel rather than an aside. Measured off Design's own markup (page line
   161): 17px, line-height 1.5, `--ks3-ink-muted`, 10px above.

   ⚠️ NOT `.ks3-rule-eg`, WHICH IS MONO AT 15px. That treatment is right for
   "shrew · dormouse · hedgehog" and wrong for two sentences of prose, and
   without this rule the author's only options were a mono paragraph or one
   joined paragraph with the break and the tone lost. */
.ks3-rule-limit {
  margin: 10px 0 0;
  font-size: 17px;
  line-height: 1.5;
  color: var(--ks3-ink-muted);
}
.ks3-rule-eg {
  margin: 12px 0 0;
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
  color: var(--ks3-ink-muted);
}
/* A card carrying a role line and a display name is the THREE-part card
   Design draws on b1-04 and b7-01: the name is the headline, not a label. */
.ks3-rule-role + .ks3-rule-term {
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 23px;
  line-height: 1.2;
  letter-spacing: -.02em;
}
/* ⊕ MRB-248 / B10 — THE ACCENT BADGE, drawn twice at two sizes.
   b10-03's `#s-who` cards carry initials (`RF`, `WC`) at 44px/12px/16px,
   spanning three rows; b10-04's `#s-steps` cards carry a step number at
   38px/11px/19px, spanning two. Measured off Design's own markup, page lines
   158 and 172. A badged card is a two-column grid with the badge in the first
   column and every text row in the second — which is a different card from the
   flow one above, and is why the `<li>` carries `data-badge` rather than the
   badge being detected by presence.

   ⚠️ `grid-row: 1 / -1` RATHER THAN A COUNTED SPAN. Design writes `span 3` on
   b10-03 and `span 2` on b10-04 because those cards have three and two text
   rows; a card that authors a `role` on b10-04's shape, or drops one on
   b10-03's, would then leave the badge short or overrunning. `1 / -1` is the
   same drawing on both and cannot go out of step with the card's contents. */
/* ⚠️ A BADGED LIST IS ONE COLUMN OF FULL-WIDTH ROWS, not the auto-fit grid.
   Design draws both `#s-who` and `#s-steps` as a `flex-direction: column`
   list (b10-03 line 153, b10-04 line 169) and the reason is the shape of the
   card: badge on the left, three stacked rows on the right, each of them a
   sentence. Dropped into a 220px auto-fit track the body wraps to one or two
   words per line and the card becomes a column of fragments. Keyed on the
   LIST, because CSS cannot select a parent from a child's attribute. */
.ks3-rule-cards[data-badged] {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.ks3-rule-cards > li[data-badge] {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 8px 18px;
  padding: 20px 24px;
  align-items: start;
}
/* ⚠️ THE BADGE IS PINNED TO COLUMN 1 ROW 1 AND EVERY TEXT ROW TO COLUMN 2.
   Design writes `grid-row: span 3` / `span 2` on the badge and lets the rest
   auto-place, which only works while the card has exactly the rows she drew —
   a card with one row fewer leaves auto-placement dropping the ROLE line into
   column 1 under the badge, where the `auto` track is then sized by a long
   mono string and the badge stops being a badge. Seen in a browser on b10-03,
   where the role is the longest line on the card. Pinning both sides is the
   same drawing and cannot go out of step with the card's contents. */
.ks3-rule-badge {
  grid-column: 1;
  grid-row: 1;
  align-self: start;
  display: grid;
  place-items: center;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  background: var(--ks3-accent);
  /* ⊕ MRB-257 · audit 3.13 — INK ON THE ACCENT, NOT CREAM. `--ks3-accent`
     #E4572E is annotated "3.4:1 — LARGE TEXT ONLY. Never body size", and cream
     on it measures exactly that 3.34:1 at 16px/800. Ink on the same fill is
     4.49:1. Identical correction to audit 3.19's on the option badge, and the
     dark-block rule at `.ks3-dark .ks3-option[aria-pressed="true"]
     .ks3-opt-mark` has always paired a fill with ink — this is that pattern,
     applied where it was missed. */
  color: var(--ks3-ink);
}
.ks3-rule-badge[data-badge="initials"] {
  width: 44px;
  height: 44px;
  border-radius: 12px;
  font-size: 16px;
}
.ks3-rule-badge[data-badge="num"] {
  width: 38px;
  height: 38px;
  border-radius: 11px;
  font-size: 19px;
}
/* A badged card's rows sit in the second column and carry no top margin of
   their own — the grid's own row gap is the spacing, and a stacked margin
   would push the first row off the badge it is meant to sit beside. */
.ks3-rule-cards > li[data-badge] > .ks3-rule-role,
.ks3-rule-cards > li[data-badge] > .ks3-rule-term,
.ks3-rule-cards > li[data-badge] > .ks3-rule-gloss,
.ks3-rule-cards > li[data-badge] > .ks3-rule-chips,
.ks3-rule-cards > li[data-badge] > .ks3-rule-eg,
.ks3-rule-cards > li[data-badge] > .ks3-rule-limit {
  grid-column: 2;
  margin: 0;
}
/* ⚠️ ON A BADGED CARD THE ROLE IS NOT THE HEADLINE'S LABEL — it sits UNDER
   the name (page line 160: name, then role, then body). So the display-weight
   promotion that `.ks3-rule-role + .ks3-rule-term` gives a b1-04 card must not
   apply here, and the name takes it directly instead. */
.ks3-rule-cards > li[data-badge] > .ks3-rule-term {
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 22px;
  line-height: 1.2;
  letter-spacing: -.02em;
}
.ks3-rule-close {
  margin: 22px 0 0;
  max-width: var(--ks3-measure);
  font-size: 20px;
  line-height: 1.7;
}

/* ── the formula (⊕ §5.1.1 · MRB-204 step 1) ──
   Centred with NO max-width. That is the entire difference between this and
   `rule`'s left-aligned 20ch measure — the shells are otherwise identical and
   a future tidy-up will try to merge them. A parity pair keeps them apart. */
.ks3-formula { margin-top: 28px; }
.ks3-formula-statement {
  padding: 40px 32px;
  background: var(--ks3-band);
  border: 3px solid var(--ks3-ink);
  border-radius: var(--ks3-r-block);
  text-align: center;
}
.ks3-formula-statement p {
  margin: 0;
  max-width: none;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: clamp(26px, 3.6vw, 40px);
  line-height: 1.15;
  letter-spacing: -.03em;
  color: var(--ks3-ink);
}

/* ── the comparison rows (⊕ §5.1.1 · MRB-210) ──
   FLEX, NEVER GRID. A grid cannot produce the 820px stack without a second
   query. Fixed at the root rather than with a viewport query, so it also
   survives a narrow CONTAINER at a wide viewport. */
.ks3-compare {
  margin-top: 28px;
  padding: 30px;
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
  border-radius: var(--ks3-r-block);
  box-shadow: var(--ks3-shadow-block);
}
.ks3-compare[data-ground="card"] { background: var(--ks3-card); }
.ks3-compare > .ks3-eyebrow[data-tone="accent-text"] { color: var(--ks3-accent-text); }
.ks3-compare-statement {
  margin: 10px 0 22px;
  font-family: var(--ks3-font-display);
  font-size: 22px;
  font-weight: 700;
  line-height: 1.35;
  color: var(--ks3-ink);
}
.ks3-compare-rows { margin: 0; padding: 0; list-style: none; }
.ks3-compare-head,
.ks3-compare-row {
  display: flex;
  flex-wrap: wrap;
  border-top: 2px solid var(--ks3-rule);
}
.ks3-compare-head { background: var(--ks3-ink); color: var(--ks3-on-dark); }
/* ⊕ MRB-257 · audit 3.14 — A FIXED 118px BASIS IS A PHONE MEASUREMENT USED AT
   1440. Minus 32px of padding it leaves 86px of text width at EVERY width
   above 820, so "Job 2 · Deliver gametes — shared, but different" renders as
   six lines in 95px on a desktop with room to spare. The clamp keeps the
   narrow end exactly where Design put it and lets the column breathe as the
   row does; the 820px rule below still takes it to a full-width row. */
.ks3-compare-name {
  flex: 0 0 clamp(118px, 18%, 240px);
  padding: 14px 16px;
  font-weight: 700;
}
.ks3-compare-cell {
  flex: 1 1 250px;
  min-width: 0;
  padding: 14px 16px;
}
.ks3-compare-row[data-zebra="1"] { background: var(--ks3-inset); }
.ks3-compare-row[data-zebra="0"] { background: var(--ks3-card); }
.ks3-compare-cell[data-tone="ink-body"] .ks3-compare-text { color: var(--ks3-ink-body); }
.ks3-compare-head .ks3-compare-cell[data-tone="alert"] { color: var(--ks3-alert); }
.ks3-compare-head .ks3-compare-cell[data-tone="on-dark"] { color: var(--ks3-on-dark); }
/* Emitted unconditionally and hidden here, because emitting it conditionally
   would need JS and would break at a container width the query cannot see. */
.ks3-compare-caption { display: none; }
@media (max-width: 820px) {
  .ks3-compare-name { flex: 1 1 100%; }
  .ks3-compare-head { display: none; }
  .ks3-compare-caption {
    display: block;
    margin-bottom: 4px;
    font-family: var(--ks3-font-mono);
    font-size: 13px;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--ks3-ink-muted);
  }
}

/* ── CLASSIFY's two instruments (⊕ 14 Aug 2026) ─────────────────────
   The seven-tests board and the three-way sorter. `build_ks3.py` has emitted
   their markup since B1 round two and this stylesheet had **no rule for any of
   their 44 classes** and `shared/ks3.js` no wiring for any of their 13
   `data-*` attributes. So `<ul class="ks3-lamps">` rendered as a literal
   bullet list of bare default buttons — which is exactly what Mide opened
   b1-01 and found, and it is why "the seven-tests instrument rendering as bare
   buttons dumped into a bullet list" was a precise description rather than an
   exaggeration.

   Every value below is transcribed from Design's approved b1-01, most of it
   out of the `<script data-dc-script>` block: the page builds these controls
   at runtime from JS-computed style strings (`optStyleTab`, `lampStyle`,
   `badgeStyle`, `verdictStyle`, `noteStyle`, `rowStyle`), so the markup alone
   does not carry them.                                                     */

/* The specimen tabs. Pill radius, and the chosen state is INVERTED — ink
   ground, cream text — rather than the accent tint every other chosen control
   takes. Design draws it that way here and on b1-03's bench; reproduced as
   drawn. It is legible and it is not a mark: a tab says which specimen you are
   looking at, never whether you were right. */
.ks3-tabs {
  list-style: none;
  margin: 20px 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}
.ks3-tab {
  cursor: pointer;
  font: inherit;
  font-size: 17px;
  font-weight: 700;
  padding: 11px 17px;
  min-height: var(--ks3-tap);
  border-radius: var(--ks3-r-pill);
  border: 2px solid var(--ks3-option-border);
  background: var(--ks3-card);
  color: var(--ks3-ink);
  transition: transform .14s;
}
.ks3-tab[aria-pressed="true"] {
  border-color: var(--ks3-ink);
  background: var(--ks3-ink);
  color: var(--ks3-on-dark);
}

/* One specimen's whole instrument, on the inset ground. */
.ks3-board-panel {
  margin-top: 22px;
  padding: 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-board-name {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-board-blurb {
  margin: 6px 0 0;
  font-size: 19px;
  line-height: 1.55;
  color: var(--ks3-ink-body);
}

/* Law 4's gate: say what you think before the board opens. */
.ks3-board-predict {
  margin-top: 18px;
  padding-top: 18px;
  border-top: 2px solid var(--ks3-rule);
}
.ks3-board-ask { margin: 0 0 12px; font-size: 21px; font-weight: 700; }
.ks3-board-options {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}

.ks3-board-tests {
  margin-top: 20px;
  padding-top: 18px;
  border-top: 2px solid var(--ks3-rule);
}
.ks3-board-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.ks3-board-instruction {
  margin: 0;
  font-size: 17px;
  font-weight: 700;
  color: var(--ks3-ink-body);
}
.ks3-board-tally {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-weight: 500;
  font-size: 24px;
  color: var(--ks3-ink);
}

/* The lamps. THREE states, and the lamp is deliberately not a `.ks3-option`:
   it reports the SPECIMEN, not the student, so it is allowed to resolve
   differently for yes and no where an answer button is not (R3). `data-state`
   rather than `aria-pressed` alone, because "yes" and "no" are both pressed. */
.ks3-lamps {
  list-style: none;
  margin: 16px 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 11px;
}
.ks3-lamp {
  width: 100%;
  text-align: left;
  cursor: pointer;
  font: inherit;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 14px 16px;
  min-height: var(--ks3-tap);
  border-radius: var(--ks3-r-option);
  border: 2px solid var(--ks3-option-border);
  background: var(--ks3-card);
  color: var(--ks3-ink);
}
.ks3-lamp[data-state="yes"] {
  border-color: var(--ks3-ink);
  background: var(--ks3-alert-tint);
  color: var(--ks3-ink);
}
.ks3-lamp[data-state="no"] {
  border-color: var(--ks3-option-border);
  background: var(--ks3-row-dim);
  color: var(--ks3-ink-body);
}
.ks3-lamp-row { display: flex; align-items: center; gap: 10px; }
.ks3-lamp-badge {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  flex: 0 0 28px;
  border-radius: 9px;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 15px;
  background: var(--ks3-band);
  color: var(--ks3-ink);
}
.ks3-lamp[data-state="yes"] .ks3-lamp-badge { background: var(--ks3-alert); }
.ks3-lamp-name { font-size: 18px; font-weight: 700; }
.ks3-lamp-verdict {
  margin-left: auto;
  font-size: 16px;
  font-weight: 700;
  color: var(--ks3-accent-text);
}
.ks3-lamp[data-state="yes"] .ks3-lamp-verdict { color: var(--ks3-alert-text); }
.ks3-lamp[data-state="no"] .ks3-lamp-verdict { color: var(--ks3-ink-muted); }
/* The note is the finding. It cannot show before the tap — it IS what the tap
   produces — so it is display:none until the lamp has a resolved state. */
.ks3-lamp-note { display: none; font-size: 16px; line-height: 1.45; color: var(--ks3-ink-body); }
.ks3-lamp[data-state] .ks3-lamp-note { display: block; }

/* The verdict, on ink, once all seven are lit. */
.ks3-board-verdict {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ink);
  color: var(--ks3-on-dark);
}
.ks3-board-verdict-head {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 26px;
  line-height: 1.2;
  letter-spacing: -.02em;
  color: var(--ks3-on-dark);
}
.ks3-board-verdict-body {
  margin: 12px 0 0;
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-on-dark-body);
}
.ks3-board-extra {
  margin-top: 18px;
  padding-top: 16px;
  border-top: 2px solid var(--ks3-dark-panel);
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}
.ks3-board-extra-label {
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-board-extra-answer { font-size: 20px; font-weight: 700; color: var(--ks3-alert); }
.ks3-board-extra-note {
  font-size: 18px;
  color: var(--ks3-on-dark-body);
  flex: 1 1 240px;
}

/* ── the three-way sorter ──
   R3 twice over: a chip takes the same chosen treatment whichever category it
   names, and after the reveal a wrong row is pixel-identical to a right one.
   The page states what settles each item; it never says whether the student
   had it. The only thing a row's border records is whether it has been
   answered at all. */
.ks3-sortrows {
  list-style: none;
  margin: 22px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.ks3-sortrow {
  padding: 15px 18px;
  border-radius: var(--ks3-r-option);
  border: 2px solid var(--ks3-rule);
  background: var(--ks3-card);
}
.ks3-sortrow[data-sorted="1"] { border-color: var(--ks3-option-border); }
.ks3-sortrow-main { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.ks3-sortrow-name { flex: 1 1 190px; font-size: 19px; font-weight: 700; }
.ks3-sortrow-chips { display: flex; gap: 8px; flex-wrap: wrap; }
.ks3-sort-chip {
  cursor: pointer;
  font: inherit;
  font-size: 16px;
  font-weight: 700;
  padding: 9px 14px;
  min-height: var(--ks3-tap);
  border-radius: var(--ks3-r-control);
  border: 2px solid var(--ks3-option-border);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}
.ks3-sort-chip[aria-pressed="true"] {
  border-color: var(--ks3-accent);
  background: var(--ks3-accent-tint);
}
.ks3-sort-evidence {
  margin: 12px 0 0;
  padding-top: 12px;
  border-top: 2px solid var(--ks3-rule);
  font-size: 18px;
  line-height: 1.55;
  color: var(--ks3-ink-body);
  /* `.ks3-reveal`'s panel treatment is taken back off: the evidence is a line
     inside a row, not a card on the page. The class is kept only because it is
     the 220ms arrive animation's one hook — see F7 in build_ks3.py. */
  background: none;
  border-left: 0;
  border-right: 0;
  border-bottom: 0;
  border-radius: 0;
  padding-left: 0;
  padding-right: 0;
  padding-bottom: 0;
}
.ks3-sort-answer { font-family: var(--ks3-font-display); font-weight: 800; }
.ks3-sort-foot {
  margin-top: 20px;
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}
/* ⊕ MRB-257 · audit 3.21 / 3.8 — `--ks3-dim-spent`, AND WHAT IT IS AND IS NOT.
   Fourteen rules dimmed a locked or spent control by eye, at .45 or .5, and
   the labels landed between 2.63:1 and 4.11:1. WCAG 1.4.3 exempts an inactive
   component, and the audit says so in as many words — every locked button here
   sits beside a progress readout that explains the lock, so nothing is
   FAILING. This is not a compliance fix and it does not claim to be. It is
   that "you may not read this yet" and "you may not read this at all" are two
   different sentences, and .45 was saying the second one.
   .65 measures 4.86:1 on the reveal button's own ground (#FDF8EE on #9C9893 →
   #FCF6EB on #6F6C67) and stays visibly, obviously off.

   ⚠️ ABOUT THIRTY OTHER `[disabled] { opacity: .45 }` RULES IN THIS FILE ARE
   NOT ROUTED HERE — `.ks3-cl-up`, `.ks3-rs-next`, `.ks3-ba-up`, `.ks3-zb-in`,
   `.ks3-qb-truth`, `.ks3-vp-plot`, `.ks3-sc-check`, `.ks3-bb-run`,
   `.ks3-test-btn`, `.ks3-b5c-check` and the rest. That is deliberate and it is
   a known incompleteness, not an oversight: the audit measured the fourteen
   above and did not measure those, several are pinned by their own parity
   rows at 0.45, and changing a value nobody has measured is how a palette
   drifts. Route each to `var(--ks3-dim-spent)` when you next touch its
   instrument, and move its parity row in the same edit. */
.ks3-sort-reveal[disabled] { opacity: var(--ks3-dim-spent); cursor: default; transform: none; }
.ks3-sort-progress {
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
  color: var(--ks3-ink-muted);
}

/* MRB-196's self-check, ruled by Mide 13 Aug. Design's sorter reveals eight
   answers and never asks whether they matched, so the student commits and
   never finds out — which Law 4 forbids. It renders ONLY once the evidence is
   showing, because before that there is nothing to compare against. There is
   no correct option and there must never be one: only the student knows how
   many of their own eight matched. */
.ks3-selfcheck {
  margin-top: 20px;
  padding-top: 18px;
  border-top: 2px solid var(--ks3-rule);
}
.ks3-selfcheck-q { margin: 0 0 12px; font-size: 19px; font-weight: 700; }
.ks3-selfcheck-options {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}
.ks3-selfcheck-note {
  margin: 12px 0 0;
  font-size: 17px;
  line-height: 1.55;
  color: var(--ks3-ink-body);
}
/* ⊕ GATE A — and the one place in this sweep where the author's own colour
   could NOT simply be made to land. Two of the three self-checks sit in a
   `.ks3-check` block on cream, where `--ks3-ink-body` is right. The third,
   `when-diet-goes-wrong` #s-cases, sits inside `.ks3-clinic-block`, which is
   `.ks3-dark` — walked in a browser, `.ks3-selfcheck` paints nothing, so the
   note's real ground is the block's own `--ks3-ink` #221E1B. `--ks3-ink-body`
   #3B342E on that measures 1.35:1: unreadable. So the on-dark TWIN of the same
   token is the faithful translation of "softened body copy" — `--ks3-ink-body`
   and `--ks3-on-dark-body` are the same role on the two grounds — and it
   measures 12.40:1 ✓ AAA. It is also exactly what `.ks3-dark p` has been
   painting, so nothing on the page moves; what changes is that the colour is
   now chosen rather than inherited by accident. */
.ks3-dark .ks3-selfcheck-note { color: var(--ks3-on-dark-body); }

/* ── b1-02: MRB-204's four formula components + critique-steps ──────
   Ruled by Mide: wherever a KS3 lesson uses a formula it gets all four, in
   order — the formula stands alone, it is DRAWN as a triangle, the first
   worked example reveals one step at a time on tap, and then the student
   fills the steps themselves before meeting full FIFA questions. Only the
   first was built. */
.ks3-steps {
  list-style: none;
  margin: 22px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.ks3-step-btn {
  width: 100%;
  display: flex;
  align-items: flex-start;
  gap: 12px;
  text-align: left;
  cursor: pointer;
  font: inherit;
  padding: 15px 18px;
  min-height: var(--ks3-tap);
  border-radius: var(--ks3-r-option);
  border: 2px solid var(--ks3-option-border);
  background: var(--ks3-card);
  color: var(--ks3-ink);
}
/* A CHECKBOX SET, not a radio group: three of the six cost him, so the demand
   is "find all the faults" and a control that unpicks its siblings would make
   that impossible. */
.ks3-step-btn[aria-pressed="true"] { border-color: var(--ks3-accent); background: var(--ks3-accent-tint); }
.ks3-step-num {
  display: grid;
  place-items: center;
  flex: 0 0 32px;
  height: 32px;
  border-radius: 9px;
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 16px;
}
.ks3-step-text { font-size: 19px; line-height: 1.5; }
/* The verdict is keyed off the STEP's own fault — the method's property, not
   the student's — so it reads the same whether or not it was tapped. The
   46px indent is 32px of badge plus the 14px gap, derived rather than typed. */
.ks3-step-verdict {
  margin: 10px 0 0 46px;
  padding: 14px 16px;
  border-radius: var(--ks3-r-panel);
  font-size: 18px;
  line-height: 1.55;
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-rule-strong);
  color: var(--ks3-ink-body);
}
.ks3-step[data-fault="1"] .ks3-step-verdict {
  background: var(--ks3-alert-tint);
  border-color: var(--ks3-alert-border);
}
.ks3-step-word { font-family: var(--ks3-font-display); color: var(--ks3-ink); }
.ks3-steps-foot { margin-top: 20px; display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.ks3-steps-reveal[disabled] { opacity: var(--ks3-dim-spent); cursor: default; transform: none; }
.ks3-steps-progress {
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
  color: var(--ks3-ink-muted);
}

/* MRB-204 step 2 — the triangle. Cover the one you want. */
.ks3-triangle { margin-top: 26px; text-align: center; }
.ks3-tri-heading {
  margin: 4px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 24px;
  letter-spacing: -.02em;
}
.ks3-tri-svg { display: block; width: 260px; max-width: 100%; height: auto; margin: 16px auto 0; }
.ks3-tri-path { fill: var(--ks3-band); stroke: var(--ks3-ink); stroke-width: 3; stroke-linejoin: round; }
.ks3-tri-div { stroke: var(--ks3-ink); stroke-width: 3; stroke-linecap: round; }
.ks3-tri-label {
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 22px;
  fill: var(--ks3-ink);
}
/* The cover is the mechanism. It is invisible until its term is covered, and
   then it is a solid ink block with the word gone — which is what "cover the
   one you want" means physically. */
.ks3-tri-cover { fill: var(--ks3-ink); opacity: 0; pointer-events: none; transition: opacity .16s; }
.ks3-triangle[data-covered="top"] .ks3-tri-cover[data-cover="top"],
.ks3-triangle[data-covered="left"] .ks3-tri-cover[data-cover="left"],
.ks3-triangle[data-covered="right"] .ks3-tri-cover[data-cover="right"] { opacity: 1; }
.ks3-tri-btns { margin-top: 18px; display: flex; gap: 9px; flex-wrap: wrap; justify-content: center; }
.ks3-tri-note {
  margin: 16px auto 0;
  max-width: 46rem;
  padding: 16px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
  font-size: 19px;
  line-height: 1.6;
  text-align: left;
}
.ks3-tri-close { margin: 16px auto 0; max-width: 46rem; font-size: 18px; line-height: 1.6; color: var(--ks3-ink-body); }

/* MRB-204 step 3 — the staged stepper. One way: there is no collapse. */
.ks3-fifa-staged .ks3-step-next { margin-top: 14px; }
.ks3-fifa-staged .ks3-step-next[disabled] { opacity: .45; cursor: default; transform: none; }

/* MRB-204 step 4 — the student's own four steps. */
.ks3-construct { margin-top: 18px; }
.ks3-fifa-field { margin-top: 14px; }
.ks3-fifa-label { display: block; font-size: 17px; font-weight: 700; margin-bottom: 6px; }
.ks3-fifa-letter {
  display: inline-grid;
  place-items: center;
  width: 26px; height: 26px;
  margin-right: 8px;
  border-radius: 8px;
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 14px;
  vertical-align: middle;
}
.ks3-fifa-input {
  width: 100%;
  font: inherit;
  font-size: 18px;
  padding: 12px 14px;
  min-height: var(--ks3-tap);
  border-radius: var(--ks3-r-control);
  border: 2px solid var(--ks3-ink);
  background: var(--ks3-card);
  color: var(--ks3-ink);
}
.ks3-construct-foot { margin-top: 18px; display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.ks3-construct-check[disabled] { opacity: var(--ks3-dim-spent); cursor: default; transform: none; }
.ks3-construct-hint {
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
  color: var(--ks3-ink-muted);
}
.ks3-construct-out {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-model-lead {
  margin: 0 0 10px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-model { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 7px; }
.ks3-model-line {
  font-family: var(--ks3-font-mono);
  font-size: 17px;
  line-height: 1.5;
  padding: 10px 14px;
  border-radius: var(--ks3-r-control);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-rule);
  white-space: pre-wrap;
}
.ks3-construct-tally {
  margin: 14px 0 0;
  font-size: 18px;
  font-weight: 700;
  color: var(--ks3-ok-text);
}

/* ── b1-03's three instruments (⊕ 14 Aug 2026) ──────────────────────
   b1-03 is the approved reference screen for MODEL — 50 lesson slots, the
   largest family in the key stage — and all three of its instruments rendered
   as empty sections. `#s-bench` alone was 1266px of the missing height.

   `cell-bench`'s argument is the SECOND VIEW: a textbook drawing shows all
   seven parts, a school microscope shows three, and switching between them is
   how a student learns to tell "not there" from "there but you cannot see
   it". That is the misconception the lesson exists to break and a diagram
   alone cannot teach it. */
.ks3-bench-controls {
  margin-top: 22px;
  display: flex;
  flex-wrap: wrap;
  gap: 22px 34px;
}
.ks3-bench-control-label {
  margin: 0 0 9px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-bench-seg { display: flex; gap: 9px; flex-wrap: wrap; }
.ks3-bench-gate {
  margin-top: 20px;
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-bench-gate-q { margin: 0; font-size: 20px; font-weight: 700; }
.ks3-bench-gate .ks3-options {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}

.ks3-parts {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.ks3-part {
  width: 100%;
  display: flex;
  align-items: flex-start;
  gap: 11px;
  text-align: left;
  cursor: pointer;
  font: inherit;
  padding: 12px 14px;
  min-height: 56px;
  border-radius: var(--ks3-r-option);
  border: 2px solid var(--ks3-option-border);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}
.ks3-part[aria-pressed="true"] { border-color: var(--ks3-accent); background: var(--ks3-accent-tint); }
.ks3-part-num {
  display: grid;
  place-items: center;
  flex: 0 0 28px;
  height: 28px;
  border-radius: 9px;
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 15px;
}
/* ⊕ MRB-257 · audit 3.19 — the same pair, same reasoning, 15px/800. */
.ks3-part[aria-pressed="true"] .ks3-part-num { background: var(--ks3-accent); color: var(--ks3-ink); }
.ks3-part-name { display: block; font-size: 17px; font-weight: 700; line-height: 1.25; }
.ks3-part-tag {
  display: block;
  margin-top: 2px;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-readout {
  margin-top: 16px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-readout-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.ks3-readout-num {
  display: grid;
  place-items: center;
  width: 34px; height: 34px;
  border-radius: 10px;
  background: var(--ks3-accent);
  /* ⊕ MRB-257 · audit 3.19 — 18px/800 is still under the 18.66px bold
     threshold, so this is body size and takes ink, not cream. */
  color: var(--ks3-ink);
  border: 2px solid var(--ks3-ink);
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 18px;
}
.ks3-readout-name { font-family: var(--ks3-font-display); font-weight: 800; font-size: 25px; letter-spacing: -.02em; }
.ks3-readout-where {
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .06em;
  text-transform: uppercase;
  padding: 5px 11px;
  border-radius: var(--ks3-r-pill);
  border: 2px solid var(--ks3-ink);
  background: var(--ks3-band);
  color: var(--ks3-ink);
}
.ks3-readout-where[data-where="plant"] { background: var(--ks3-ok-tint); }
.ks3-readout-where[data-where="absent"] { background: var(--ks3-alert-tint); }
.ks3-readout-job { margin: 12px 0 0; font-size: 20px; line-height: 1.55; font-weight: 600; }
.ks3-readout-detail { margin: 8px 0 0; font-size: 18px; line-height: 1.6; color: var(--ks3-ink-body); }
/* "You can see this one." / "You cannot see this one." — the second view's
   whole point, so it is a stated line and not a colour. */
.ks3-readout-scope {
  margin: 14px 0 0;
  padding-top: 12px;
  border-top: 2px solid var(--ks3-rule);
  font-size: 18px;
  line-height: 1.55;
  color: var(--ks3-ink-body);
}
.ks3-readout-scope-word { font-family: var(--ks3-font-display); }
.ks3-bench-tally {
  margin: 14px 0 0;
  font-family: var(--ks3-font-mono);
  font-size: 16px;
  font-weight: 500;
  color: var(--ks3-ink-muted);
}

/* The two-way sorter. Structurally the three-way sorter's sibling and
   deliberately not built on it: this one SENDS a statement to one of two
   named things, and one component trying to be both is the "nearest shape
   that exists" failure again. */
.ks3-pairrows {
  list-style: none;
  margin: 22px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 11px;
}
.ks3-pairrow {
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-rule-strong);
}
.ks3-pairrow[data-sent="1"] { border-color: var(--ks3-option-border); }
.ks3-pairrow-text { margin: 0; font-size: 19px; line-height: 1.5; font-weight: 600; }
.ks3-pairrow-chips { margin-top: 12px; display: flex; gap: 9px; flex-wrap: wrap; }
.ks3-pairrow-note { margin: 12px 0 0; font-size: 18px; line-height: 1.55; color: var(--ks3-ink-body); }
.ks3-pairrow-word { font-family: var(--ks3-font-display); }
.ks3-pair-foot { margin-top: 20px; display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.ks3-pair-reveal[disabled] { opacity: var(--ks3-dim-spent); cursor: default; transform: none; }
.ks3-pair-progress {
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
  color: var(--ks3-ink-muted);
}
.ks3-pair-panel {
  margin-top: 18px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
  font-size: 19px;
  line-height: 1.6;
}

/* ⊕ MRB-257 · audit 5.3 — the answer panel is a headline and a body now.
   It used to publish the raw Python dict `{'headline': …, 'body': …}` to
   students, because the builder serialised the payload and `wirePairs` only
   un-hid it. Matches `.ks3-reveal-panel p + p`, which spaces the same shape
   in `#s-think`. */
.ks3-pair-panel p + p { margin-top: 14px; }

/* Build it. "Which parts" becomes a consequence of "what job". */
.ks3-fit-tabs { margin-top: 22px; display: flex; gap: 9px; flex-wrap: wrap; }
.ks3-fit-job {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-fit-job-label {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-fit-job-text { margin: 6px 0 0; font-size: 21px; line-height: 1.4; font-weight: 700; }
.ks3-fit-job-where { margin: 10px 0 0; font-size: 18px; line-height: 1.6; color: var(--ks3-ink-body); }
/* ⊕ MRB-242 — "Install the parts" is a plain bold line in Design (reference
   line 310), not the mono uppercase eyebrow this rule used to draw. It labels
   a row of controls; it is not a section marker. */
.ks3-fit-install-label {
  margin: 22px 0 0;
  font-size: 18px;
  font-weight: 700;
}
/* ⊕ MRB-242 — Design lays the chips out as a WRAPPING ROW of pills sized to
   their own words (reference line 309), not as a fixed-column grid. A grid
   stretches a pill to a 232px column, which is a rectangle with rounded ends
   rather than a chip. The parity row that asserted `display: grid` was written
   against this stylesheet rather than against the reference, and has been
   corrected with it. */
.ks3-fit-parts {
  list-style: none;
  margin: 12px 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}
.ks3-fit-part {
  display: flex;
  align-items: center;
  gap: 9px;
  text-align: left;
  cursor: pointer;
  font: inherit;
  font-size: 17px;
  font-weight: 700;
  padding: 11px 17px;
  /* 48px, Design's own value, and comfortably over `--ks3-tap`'s 44px floor. */
  min-height: 48px;
  border-radius: 999px;
  border: 2px solid var(--ks3-option-border);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}
.ks3-fit-part[aria-pressed="true"] { border-color: var(--ks3-accent); background: var(--ks3-accent-tint); }
/* ⊕ MRB-242 — the chip's number is the bench's badge component, reused. One
   badge, one set of values, a size modifier here and a dark branch below —
   the same discipline the segmented control takes, and the reason there is no
   second `.ks3-fit-part-num`. Design draws it at 24px/7px (reference 1213). */
.ks3-fit-part .ks3-part-num {
  flex: 0 0 24px;
  height: 24px;
  border: 0;
  border-radius: 7px;
  font-size: 14px;
}
.ks3-fit-foot { margin-top: 18px; display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.ks3-fit-run[disabled] { opacity: var(--ks3-dim-spent); cursor: default; transform: none; }
.ks3-fit-clear {
  cursor: pointer;
  font: inherit;
  font-size: 16px;
  font-weight: 700;
  padding: 11px 16px;
  min-height: var(--ks3-tap);
  border-radius: var(--ks3-r-control);
  border: 2px solid var(--ks3-option-border);
  background: transparent;
  color: var(--ks3-ink-body);
}
.ks3-fit-progress {
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
  color: var(--ks3-ink-muted);
}
.ks3-fit-out {
  margin-top: 20px;
  padding: 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
/* ⊕ MRB-242 — Design's verdict pill, `verdictBadgeStyle` at reference line
   1243: mono, 14px/500, tracked .08em, uppercased, on a 999px capsule with a
   2px ink border and a fill that carries the state. No badge element was ever
   emitted, so the three authored badge words had nowhere to land at all. */
.ks3-fit-badge {
  margin: 0;
  display: inline-block;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
  padding: 6px 13px;
  border-radius: 999px;
  border: 2px solid var(--ks3-ink);
  color: var(--ks3-ink);
}
.ks3-fit-badge[data-state="fails"] { background: var(--ks3-alert-tint); }
.ks3-fit-badge[data-state="waste"] { background: var(--ks3-band); }
.ks3-fit-badge[data-state="works"] { background: var(--ks3-accent-tint); }

/* 12px, not 0 — the headline now has the pill above it (reference line 329). */
.ks3-fit-verdict {
  margin: 12px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 26px;
  line-height: 1.18;
  letter-spacing: -.025em;
}
.ks3-fit-findings {
  list-style: none;
  margin: 14px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 9px;
}
.ks3-fit-finding {
  padding: 14px 16px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-rule-strong);
  font-size: 18px;
  line-height: 1.55;
}
.ks3-fit-finding[data-kind="missing"] { background: var(--ks3-alert-tint); border-color: var(--ks3-alert-border); }
.ks3-fit-finding strong { font-family: var(--ks3-font-display); }
.ks3-fit-note { margin: 16px 0 0; padding-top: 14px; border-top: 2px solid var(--ks3-rule); font-size: 19px; line-height: 1.6; }

/* ── #s-fit ON THE INK BLOCK (⊕ MRB-242) ───────────────────────────────────
   Everything above was written for a cream ground. The instrument only ever
   ships on `ks3-block ks3-dark ks3-practical`, so everything above was wrong
   everywhere it ran: measured on the built page, the job panel and the output
   text came out at 1.17:1, the clear button and the unpressed tabs at 1.35:1
   and the foot hint at 2.31:1. That is the "faded" report, and it is one
   stylesheet writing cream-on-cream and ink-on-ink.

   ⚠️ EVERY PARAGRAPH RULE HERE HAS TO BE SCOPED, and that is the whole trap.
   `.ks3-dark p` is (0,1,1); `.ks3-fit-job-label` and its five siblings are
   (0,1,0). Unscoped they lose every time and the block's own classes never
   paint a single <p>. `.ks3-dark p` is right for `#s-hook` and `#s-keynote`
   and is left exactly as it is — the fix is on this side. Same trap, same
   fix, as `.ks3-plate-note` and `.ks3-tbench-why` in B3. */

/* Design's h2 here is 34px, not `.ks3-block h2`'s 30px — measured and
   recorded in the lesson record's own notes long before the heading was
   emitted at all. Reference line 291. */
.ks3-fit-block .ks3-blockhead h2 {
  font-size: 34px;
  line-height: 1.12;
  letter-spacing: -.03em;
}

/* The nested job panel is `--ks3-dark-panel`, a step up from the block's own
   ink rather than a cream inset. Design draws it borderless; the border stays
   in the box so the geometry does not move, painted in the panel's colour. */
.ks3-dark .ks3-fit-job {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-dark-panel);
}
.ks3-dark .ks3-fit-job-label { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-fit-job-text { color: var(--ks3-on-dark); }
.ks3-dark .ks3-fit-job-where { color: var(--ks3-on-dark-body); }
.ks3-dark .ks3-fit-install-label { color: var(--ks3-on-dark); }

/* The chips: transparent on the muted rule, amber with ink text when
   installed — Design's own dark pair, the same one the segmented control
   takes. The pressed rule is (0,3,0) so it still wins over the light
   `[aria-pressed="true"]` rule above and over this block's unpressed rule. */
.ks3-dark .ks3-fit-part {
  border-color: var(--ks3-on-dark-muted);
  background: transparent;
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-fit-part[aria-pressed="true"] {
  border-color: var(--ks3-accent);
  background: var(--ks3-dark-panel);
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-fit-part .ks3-part-num {
  background: var(--ks3-dark-panel);
  color: var(--ks3-on-dark-muted);
}
/* ⊕ MRB-297 · 1 Sep 2026 — THE BADGE FOLLOWS ITS BUTTON. This read
   `color: var(--ks3-alert)` on the ink ground, which was right while a chosen
   option was amber. The button above moved to `--ks3-accent` on 30 Aug and
   the badge did not, so the two halves of one control disagreed: an accent
   border round an amber number. The amber is retired here, not the ink.
   ⚠️ The accent is NOT available as the glyph colour at 15px/800 — #E4572E
   on `--ks3-ink` is 4.49:1, under the 4.5:1 floor — so the selection is
   carried by the border, exactly as the platform's own migrated badge does
   at `.ks3-dark .ks3-option[aria-pressed="true"] .ks3-opt-mark`, and the
   glyph takes `--ks3-on-dark` at 15.0:1. The light-ground sibling at
   `.ks3-part[aria-pressed="true"] .ks3-part-num` is an accent slab with ink
   on it and stays as it is: ink on accent measures 8.6:1 the other way up. */
.ks3-dark .ks3-fit-part[aria-pressed="true"] .ks3-part-num {
  background: var(--ks3-ink);
  /* ⚠️ ⊕ CORRECTED 1 Sep 2026 — `border-color` ALONE PAINTED NOTHING, and the
     comment above asserted it was doing the work. `.ks3-fit-part
     .ks3-part-num` two hundred lines up sets `border: 0`, so a colour with no
     width is invisible: measured 0px on the chosen badge and the unchosen one
     alike. The state still READ, because the ink slab and the glyph colour
     both change — but not by the mechanism this block claims, and a comment
     that describes a mechanism the code does not have is how the next person
     "tidies away" the property that is actually carrying it.
     `border: 2px solid` now, which is the shorthand the platform's own
     migrated badge uses at `.ks3-dark .ks3-option[aria-pressed="true"]
     .ks3-opt-mark` — so the claim above is true, and the two badges are one
     treatment rather than two that resemble each other. Global `border-box`
     (`shared/styles.css`) means the 24px badge stays 24px. */
  border: 2px solid var(--ks3-accent);
  color: var(--ks3-on-dark);
}

/* ⊕ CORRECTED FROM THE SHIPPED STYLESHEET, and reported. `.ks3-reveal-btn` is
   ink on an ink border, which on a block whose ground IS `--ks3-ink` paints an
   invisible control — the same defect already corrected for `.ks3-plate-open`
   and `.ks3-ledger-clear`. Those two took `--ks3-on-dark`; Design draws THIS
   one in `--ks3-ground` (reference line 320), so this one takes `--ks3-ground`
   and the difference is Design's, not a third invention. */
.ks3-dark .ks3-fit-run {
  background: var(--ks3-ground);
  border-color: var(--ks3-ground);
  color: var(--ks3-ink);
}
.ks3-dark .ks3-fit-clear {
  border-color: var(--ks3-on-dark-muted);
  color: var(--ks3-on-dark-body);
}
.ks3-dark .ks3-fit-progress { color: var(--ks3-on-dark-muted); }

/* ⚠️ THE RESULTS CARD IS CREAM INSIDE THE INK BLOCK. Design sets `color:
   var(--ks3-ink)` on the card explicitly (reference line 328) and the omission
   of that one declaration is why the verdict and the note read
   `--ks3-on-dark-body` on cream at 1.17:1. A panel that reverses the ground
   must re-declare its own text colour; it cannot inherit it. */
.ks3-dark .ks3-fit-out {
  background: var(--ks3-ground);
  border-color: var(--ks3-ground);
  color: var(--ks3-ink);
}
.ks3-dark .ks3-fit-verdict { color: var(--ks3-ink); }
.ks3-dark .ks3-fit-note { color: var(--ks3-ink); }
/* ⊕ MRB-242 — the badge is a <p> inside that cream card, so it needs the
   card's ink and not the block's on-dark body colour. This rule is (0,2,0)
   and `.ks3-dark p` is (0,1,1); unscoped, `.ks3-fit-badge` would be (0,1,0)
   and lose — the same trap that painted the verdict and the note at 1.17:1.
   The three fills above are already (0,2,0) via their attribute selector and
   are not colour rules, so they need no dark twin. */
.ks3-dark .ks3-fit-badge { color: var(--ks3-ink); }

/* ── b1-05's three instruments (⊕ 14 Aug 2026) ──────────────────────
   `zoom-ladder`, `sort-task` and `removal-cases`. All three rendered as
   EMPTY sections — an eyebrow and 90.39px of nothing each — and between them
   they were 78% of this page's missing height. b1-05 is the page Mide
   rejected by name on 11 August. */
.ks3-visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%);
  overflow: hidden;
  white-space: nowrap;
}

.ks3-zoom-frame {
  margin-top: 24px;
  border-radius: var(--ks3-r-card);
  border: 2px solid var(--ks3-ink);
  background: var(--ks3-card);
  overflow: hidden;
}
.ks3-zoom-canvas { display: block; width: 100%; height: auto; }
.ks3-zoom-controls {
  padding: 14px 18px 18px;
  border-top: 2px solid var(--ks3-rule);
  background: var(--ks3-band);
}
.ks3-zoom-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.ks3-zoom-step {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-zoom-size { margin: 0; font-family: var(--ks3-font-mono); font-size: 17px; font-weight: 500; color: var(--ks3-ink); }
/* Design draws this slider at 40px. `--ks3-tap` is 44 and every other control
   honours it; the inventory already flagged this exact slider as "an
   accessibility regression if copied as drawn". Corrected, and stated. */
.ks3-zoom-range {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: var(--ks3-tap);
  background: transparent;
  cursor: pointer;
}
.ks3-zoom-range::-webkit-slider-runnable-track {
  height: 10px;
  border-radius: var(--ks3-r-pill);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
}
.ks3-zoom-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 30px; height: 30px;
  margin-top: -12px;
  border-radius: 50%;
  background: var(--ks3-accent);
  border: 3px solid var(--ks3-ink);
  cursor: pointer;
}
.ks3-zoom-range::-moz-range-track {
  height: 10px;
  border-radius: var(--ks3-r-pill);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
}
.ks3-zoom-range::-moz-range-thumb {
  width: 30px; height: 30px;
  border-radius: 50%;
  background: var(--ks3-accent);
  border: 3px solid var(--ks3-ink);
  cursor: pointer;
}
.ks3-zoom-ticks { display: flex; flex-wrap: wrap; gap: 7px; }
.ks3-zoom-panel {
  margin-top: 18px;
  padding: 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-zoom-name {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 30px;
  line-height: 1.15;
  letter-spacing: -.025em;
}
.ks3-zoom-what { margin: 10px 0 0; font-size: 19px; line-height: 1.6; color: var(--ks3-ink-body); }
.ks3-zoom-gain { margin-top: 18px; padding-top: 16px; border-top: 2px solid var(--ks3-rule); }
.ks3-zoom-gain-label {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-accent-text);
}
.ks3-zoom-gain-text { margin: 8px 0 0; font-size: 20px; line-height: 1.55; font-weight: 600; }
.ks3-zoom-human { margin: 16px 0 0; font-size: 18px; line-height: 1.6; color: var(--ks3-ink-body); }
.ks3-zoom-human strong { font-family: var(--ks3-font-display); }

/* The awkward eight. THE activity Mide rejected: it rendered as a four-option
   multiple choice with eight items in the prompt.

   ⚠️ The mark is on the ROW and never on a control. After the reveal a row
   the student had takes the inset ground on a full ink outline; one they did
   not takes alert-tint on the alert border. The chips are byte-identical
   before and after, and identical on a right row and a wrong one — measured
   by driving Design's page. That is what keeps a deferred, self-service,
   student-opened mark clear of R3, and it is the entire distinction. */
.ks3-hardrows {
  list-style: none;
  margin: 22px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 11px;
}
.ks3-hardrow {
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-rule-strong);
}
.ks3-hardrow[data-open="1"][data-right="1"] {
  background: var(--ks3-inset);
  border-color: var(--ks3-ink);
}
.ks3-hardrow[data-open="1"][data-right="0"] {
  background: var(--ks3-alert-tint);
  border-color: var(--ks3-alert-border);
}
.ks3-hardrow-item { margin: 0; font-size: 20px; font-weight: 700; }
.ks3-hardrow-chips { margin-top: 11px; display: flex; gap: 7px; flex-wrap: wrap; }
.ks3-hardrow-answer { margin: 12px 0 0; font-size: 18px; line-height: 1.55; color: var(--ks3-ink-body); }
.ks3-hardrow-word { font-family: var(--ks3-font-display); }
.ks3-hard-foot { margin-top: 20px; display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.ks3-hard-reveal[disabled] { opacity: var(--ks3-dim-spent); cursor: default; transform: none; }
.ks3-hard-progress {
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
  color: var(--ks3-ink-muted);
}

/* Take a level out. On the ink-dark practical ground, with the consequence
   landing on a light panel — the same "it leaves the cell" move the sabotage
   chain makes. */
.ks3-removal-progress {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
  color: var(--ks3-on-dark-muted);
}
.ks3-removal-lede { margin: 14px 0 0; font-size: 19px; line-height: 1.65; max-width: 58ch; }
.ks3-removal-tabs { margin-top: 22px; display: flex; gap: 9px; flex-wrap: wrap; }
.ks3-removal-what {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-dark-panel);
}
.ks3-removal-what-label {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-removal-what-text { margin: 8px 0 0; font-size: 21px; line-height: 1.5; font-weight: 700; color: var(--ks3-on-dark); }
.ks3-removal-intact { margin: 10px 0 0; font-size: 18px; line-height: 1.6; color: var(--ks3-on-dark-body); }
.ks3-removal-predict { margin-top: 22px; }
.ks3-removal-predict .ks3-commit { margin: 0 0 4px; }
.ks3-removal-out {
  margin-top: 22px;
  padding: 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}
.ks3-removal-lost {
  margin: 0;
  display: inline-block;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  padding: 6px 13px;
  border-radius: var(--ks3-r-pill);
  border: 2px solid var(--ks3-ink);
  background: var(--ks3-alert-tint);
  color: var(--ks3-ink);
}
.ks3-removal-headline {
  margin: 12px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 26px;
  line-height: 1.18;
  letter-spacing: -.025em;
  color: var(--ks3-ink);
}
.ks3-removal-body { margin: 12px 0 0; font-size: 19px; line-height: 1.6; color: var(--ks3-ink-body); }
.ks3-removal-principle {
  margin: 18px 0 0;
  padding-top: 16px;
  border-top: 2px solid var(--ks3-rule);
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-ink);
}
.ks3-removal-principle strong { font-family: var(--ks3-font-display); }

/* ⊕ GATE A — every colour above is (0,1,0) and this whole engine lives inside
   `.ks3-dark`, so `.ks3-dark p` (0,1,1) took all eight of them and painted the
   lot `--ks3-on-dark-body`. The block spans THREE grounds and each one was
   walked in a browser rather than guessed from the class name:

     · `.ks3-removal` itself paints nothing → the ink block, `--ks3-ink` #221E1B
       — `.ks3-removal-progress`, on-dark-muted #C6B9A7 → 8.58:1 ✓
     · `.ks3-removal-what` is `--ks3-dark-panel` #3E3730, a panel nested in the
       ink block — label on-dark-muted 6.08:1 ✓, text on-dark 10.63:1 ✓,
       intact on-dark-body 8.77:1 ✓
     · `.ks3-removal-out` is `--ks3-ground` #FBF3E6 — the consequence LEAVES the
       cell and lands on cream, so ink is right here and re-declaring ink is the
       fix: headline 15.02:1 ✓, principle 15.02:1 ✓, body ink-body 11.11:1 ✓
     · `.ks3-removal-lost` paints its OWN `--ks3-alert-tint` #FFF3D4 pill on that
       cream panel — ink on the tint, 14.98:1 ✓ */
.ks3-dark .ks3-removal-progress   { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-removal-what-label { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-removal-what-text  { color: var(--ks3-on-dark); }
.ks3-dark .ks3-removal-intact     { color: var(--ks3-on-dark-body); }
.ks3-dark .ks3-removal-lost       { color: var(--ks3-ink); }
.ks3-dark .ks3-removal-headline   { color: var(--ks3-ink); }
.ks3-dark .ks3-removal-body       { color: var(--ks3-ink-body); }
.ks3-dark .ks3-removal-principle  { color: var(--ks3-ink); }

/* ── SYSTEM's bench and its sabotage engine (⊕ 14 Aug 2026) ─────────
   b1-04 is the approved reference screen for SYSTEM, 32 slots, and both of
   its instruments rendered as empty sections. Together they were 58% of the
   page's missing height.

   ⚠️ The cell picker is NOT the segmented control. Drift 4 records that
   b1-04's light `seg()` branch is a full-width option ROW — `width:100%`,
   `text-align:left`, `min-height:56px`, `--ks3-r-option` — that shares a
   helper name with the segment and nothing else. Generating it as a segment
   would produce the wrong component; generating the segment from it would
   produce the wrong control everywhere else. */
.ks3-bench-cells {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.ks3-bench-cell {
  width: 100%;
  text-align: left;
  cursor: pointer;
  font: inherit;
  padding: 12px 14px;
  min-height: 56px;
  border-radius: var(--ks3-r-option);
  border: 2px solid var(--ks3-option-border);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}
.ks3-bench-cell[aria-pressed="true"] {
  border-color: var(--ks3-accent);
  background: var(--ks3-accent-tint);
}
.ks3-bench-cell-name { display: block; font-size: 17px; font-weight: 700; line-height: 1.25; }
.ks3-bench-cell-tag {
  display: block;
  margin-top: 2px;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-bench-cell[aria-pressed="true"] .ks3-bench-cell-tag { color: var(--ks3-accent-text); }

.ks3-bench-figure {
  border-radius: var(--ks3-r-card);
  border: 2px solid var(--ks3-ink);
  background: var(--ks3-card);
  overflow: hidden;
}
.ks3-bench-canvas { display: block; width: 100%; height: auto; }
.ks3-bench-caption {
  margin: 0;
  padding: 11px 16px;
  border-top: 2px solid var(--ks3-rule);
  background: var(--ks3-band);
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  color: var(--ks3-ink-muted);
}
.ks3-bench-panel {
  margin-top: 16px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-bench-eyebrow {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-bench-job { margin: 6px 0 0; font-size: 21px; line-height: 1.4; font-weight: 700; }
.ks3-bench-where { margin: 12px 0 0; font-size: 18px; line-height: 1.6; color: var(--ks3-ink-body); }
.ks3-tuning {
  list-style: none;
  margin: 20px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 9px;
}
.ks3-tune {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 14px 16px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-rule-strong);
}
/* The dial reads what was DONE to the part — turned off, turned up, reshaped.
   Amber for taken away, accent for added to, band for reshaped. It is not a
   mark on a student, so amber is right here in a way it never is on an
   option. */
.ks3-tune-dial {
  display: grid;
  place-items: center;
  flex: 0 0 74px;
  padding: 5px 0;
  border-radius: 8px;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .08em;
  border: 2px solid var(--ks3-ink);
  color: var(--ks3-ink);
  background: var(--ks3-band);
}
.ks3-tune-dial[data-dial="GONE"],
.ks3-tune-dial[data-dial="NONE"],
.ks3-tune-dial[data-dial="HALF"] { background: var(--ks3-alert-tint); }
.ks3-tune-dial[data-dial="MORE"],
.ks3-tune-dial[data-dial="EXTRA"] { background: var(--ks3-accent-tint); }
.ks3-tune-part { display: block; font-size: 18px; font-weight: 700; }
.ks3-tune-why { display: block; margin-top: 3px; font-size: 18px; line-height: 1.55; color: var(--ks3-ink-body); }
.ks3-bench-problem {
  margin: 18px 0 0;
  padding-top: 16px;
  border-top: 2px solid var(--ks3-rule);
  font-size: 19px;
  line-height: 1.6;
}
.ks3-bench-problem strong { font-family: var(--ks3-font-display); }

/* The sabotage engine, on the ink-dark practical ground. */
.ks3-sab-progress {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
  color: var(--ks3-on-dark-muted);
}
.ks3-sab-lede { margin: 14px 0 0; font-size: 19px; line-height: 1.65; max-width: 58ch; }
.ks3-sab-lede strong { color: var(--ks3-on-dark); }
.ks3-sab-tabs { margin-top: 22px; display: flex; gap: 9px; flex-wrap: wrap; }
.ks3-sab-what {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-dark-panel);
}
.ks3-sab-what-label {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-sab-what-text { margin: 8px 0 0; font-size: 21px; line-height: 1.5; font-weight: 700; color: var(--ks3-on-dark); }
.ks3-sab-predict { margin-top: 22px; }
.ks3-sab-predict .ks3-commit { margin: 0 0 4px; }
.ks3-sab-chain { margin-top: 22px; }
.ks3-sab-figure {
  border-radius: var(--ks3-r-card);
  border: 2px solid var(--ks3-on-dark-muted);
  overflow: hidden;
}
.ks3-sab-canvas { display: block; width: 100%; height: auto; }
.ks3-sab-caption {
  margin: 0;
  padding: 11px 16px;
  border-top: 2px solid var(--ks3-on-dark-muted);
  background: var(--ks3-dark-panel);
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  color: var(--ks3-on-dark-muted);
}
.ks3-sab-pick {
  margin: 18px 0 0;
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
  color: var(--ks3-on-dark-muted);
}
.ks3-chain {
  list-style: none;
  margin: 14px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
/* Every link is a light card ON the dark block — the consequence leaves the
   cell and lands in the world. The FIRST link is the cell itself and takes
   the band ground and the full ink outline; the rest step outward. */
.ks3-chain-link {
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ground);
  border: 2px solid var(--ks3-rule-strong);
}
.ks3-chain-link:first-child { background: var(--ks3-band); border-color: var(--ks3-ink); }
.ks3-chain-scale {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-chain-text { margin: 6px 0 0; font-size: 19px; line-height: 1.55; color: var(--ks3-ink); }
.ks3-sab-close {
  margin: 18px 0 0;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-dark-panel);
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-on-dark-body);
}

/* ⊕ GATE A — the same trap as the removal engine, and the same three grounds.
   Eight bare (0,1,0) colours above, all of them inside `.ks3-dark`, all of them
   taken by `.ks3-dark p` (0,1,1). Grounds walked in a browser:

     · the ink block, `--ks3-ink` #221E1B — `.ks3-sab-progress` and
       `.ks3-sab-pick` sit on it directly, on-dark-muted → 8.58:1 ✓
     · `--ks3-dark-panel` #3E3730 — `.ks3-sab-what` carries it (label
       on-dark-muted 6.08:1 ✓, text on-dark 10.63:1 ✓), and `.ks3-sab-caption`
       and `.ks3-sab-close` each paint it themselves (caption on-dark-muted
       6.08:1 ✓, close on-dark-body 8.77:1 ✓)
     · `.ks3-chain-link` is a LIGHT card on the ink block — `--ks3-ground`
       #FBF3E6, and `--ks3-band` #F4E9D8 for the first link, which is the cell
       itself. So the chain's INK is correct and re-declaring ink is the fix:
       scale ink-muted 6.51:1 / 5.97:1 ✓, text ink 15.02:1 / 13.78:1 ✓ */
.ks3-dark .ks3-sab-progress   { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-sab-what-label { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-sab-what-text  { color: var(--ks3-on-dark); }
.ks3-dark .ks3-sab-caption    { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-sab-pick       { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-sab-close      { color: var(--ks3-on-dark-body); }
.ks3-dark .ks3-chain-scale    { color: var(--ks3-ink-muted); }
.ks3-dark .ks3-chain-text     { color: var(--ks3-ink); }

/* ── CONTRAST's flagship: settles-it (⊕ 14 Aug 2026) ────────────────
   Four mystery cells, four true facts each, sixteen judgements. Inherited by
   18 CONTRAST lessons. It rendered as an EMPTY section: the generic shell
   reads prompt/options/reveal and this payload declares
   instruction/choice_labels/cases.

   The row's ground records whether the FACT settles it — the page's own
   answer, revealed to everyone identically — and never whether the student
   said so. That is what keeps it clear of R3 while looking close to the line.
   Design's `agreed` computation is gone under MRB-196; see the renderer. */
.ks3-case-tabs {
  margin-top: 22px;
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
.ks3-case-head {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-case-label {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-case-desc { margin: 8px 0 0; font-size: 21px; line-height: 1.45; font-weight: 700; }

.ks3-features {
  list-style: none;
  margin: 18px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 11px;
}
.ks3-feature {
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-rule-strong);
}
/* After the reveal the row says what the FACT does. A discriminating fact
   takes the band ground and the full ink outline; one that settles nothing
   dims back. Identical for every student. */
.ks3-case-panel[data-open="1"] .ks3-feature[data-settles="1"] {
  background: var(--ks3-band);
  border-color: var(--ks3-ink);
}
.ks3-case-panel[data-open="1"] .ks3-feature[data-settles="0"] {
  background: var(--ks3-row-dim);
  border-color: var(--ks3-rule);
}
.ks3-feature-text { margin: 0; font-size: 19px; line-height: 1.5; font-weight: 600; }
.ks3-feature-choices { margin-top: 12px; display: flex; gap: 9px; flex-wrap: wrap; }
.ks3-settle-choice {
  cursor: pointer;
  font: inherit;
  font-size: 16px;
  font-weight: 700;
  padding: 10px 16px;
  min-height: var(--ks3-tap);
  border-radius: var(--ks3-r-control);
  border: 2px solid var(--ks3-option-border);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}
.ks3-settle-choice[aria-pressed="true"] {
  border-color: var(--ks3-accent);
  background: var(--ks3-accent-tint);
}
.ks3-settle-choice[disabled] { cursor: default; }
.ks3-settle-choice[disabled][aria-pressed="false"] { opacity: var(--ks3-dim-spent); }
/* ⚖️ MRB-196 — ONE tone. Design paints this `--ks3-ink` when the student
   agreed and `--ks3-ink-body` when they did not: about 6 ΔL* apart, a mark
   nobody can read, and a mark all the same. The self-check asks instead. */
.ks3-feature-why {
  margin: 12px 0 0;
  font-size: 18px;
  line-height: 1.55;
  color: var(--ks3-ink-body);
}
.ks3-why-word { font-family: var(--ks3-font-display); }

.ks3-settle-foot {
  margin-top: 20px;
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}
.ks3-settle-reveal[disabled] { opacity: var(--ks3-dim-spent); cursor: default; transform: none; }
.ks3-settle-progress {
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
  color: var(--ks3-ink-muted);
}
.ks3-case-verdict {
  margin-top: 20px;
  padding: 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ink);
  color: var(--ks3-on-dark);
}
.ks3-case-verdict-label {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ks3-alert);
}
.ks3-case-answer {
  margin: 10px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 27px;
  line-height: 1.18;
  letter-spacing: -.025em;
  color: var(--ks3-on-dark);
}
.ks3-case-why { margin: 12px 0 0; font-size: 19px; line-height: 1.6; color: var(--ks3-on-dark-body); }

/* ── the bench grid (drift 1 + drift 2, ruled on MRB-208 13 Aug) ──
   232px, and the collapse at 780px. The tie was 1:1 once b1-06's declarations
   proved to match no element on that page — dead CSS — and it broke toward
   b1-04, which is also SYSTEM's reference screen and carries 32 slots. */
[data-bench-grid] {
  display: grid;
  grid-template-columns: minmax(0, 232px) minmax(0, 1fr);
  gap: 22px;
}
@media (max-width: 780px) {
  [data-bench-grid] { grid-template-columns: minmax(0, 1fr); }
}

/* ═══════════════════════════════════════════════════════════════════
   B2 · Movement: skeleton and muscles  (⊕ 16 Aug 2026, MRB-220)

   Four instruments and two shared pieces. Everything here is measured off
   Design's four frozen pages in `docs/ks3/design-reference/b2/`; where a
   value differs from one already shipped, the SHIPPED one wins and the
   difference is reported as drift (contract R3) — one stylesheet serves all
   183 lesson slots, and adopting a page's numbers restyles six lessons Mide
   has already approved to match four he has not seen.
   ═══════════════════════════════════════════════════════════════════ */

/* ── the block head row ──
   Eyebrow + <h2> on the left, a live mono counter right-aligned on the same
   baseline. Only a block that authors `head_counter` takes it. */
.ks3-blockhead {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
}
.ks3-blockhead > div { min-width: 0; }
.ks3-blockhead h2 { margin: 6px 0 0; }
.ks3-blockhead-count {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
  color: var(--ks3-ink-muted);
  flex: 0 0 auto;
}
.ks3-dark .ks3-blockhead-count { color: var(--ks3-on-dark-muted); }

/* An activity block may name its own ground (three of B2's sorters sit on
   inset rather than the shell's card). */
.ks3-check[data-ground="inset"],
.ks3-worked[data-ground="inset"] { background: var(--ks3-inset); }
.ks3-check[data-ground="band"] { background: var(--ks3-band); }
.ks3-check[data-ground="ground"] { background: var(--ks3-ground); }

/* ── the commit gate (C6) ──
   Gating by ABSENCE: answered, the whole block is gone and the instrument
   arrives in the space the question was occupying. */
.ks3-benchgate { margin-top: 22px; }
.ks3-benchgate .ks3-commit { margin: 0 0 4px; }

/* ── the two-paragraph reveal ──
   Design's `#s-think` reveal is a CARD panel on a 2px ink border, not the
   accent tint a one-line reveal takes. A new class, not a change to
   `.ks3-reveal`: that class is what the hook uses on these same pages and
   what C1's seven live confrontations use. */
.ks3-reveal-panel {
  padding: 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
}
.ks3-reveal-panel p { margin: 0; font-size: 19px; line-height: 1.6; }
.ks3-reveal-panel p + p { margin-top: 14px; }

/* ── the range control (b2-02, b2-04) ──
   No `shared/ks3.css` rule styled `input[type=range]` for KS3 before this;
   the chrome was page-local on both pages, so the sliders would have
   rendered as browser default. A 28px alert thumb on a 3px ink ring, on a
   10px dark track — big enough to hit, and the disabled state is a READOUT
   (a pivot joint does not bend) rather than decoration. */
.ks3-slider {
  -webkit-appearance: none;
  appearance: none;
  display: block;
  width: 100%;
  margin: 12px 0 0;
  height: 28px;
  background: transparent;
  cursor: pointer;
}
.ks3-slider::-webkit-slider-runnable-track {
  height: 10px;
  border-radius: var(--ks3-r-pill);
  background: var(--ks3-dark-track);
  border: 2px solid var(--ks3-on-dark-muted);
}
.ks3-slider::-moz-range-track {
  height: 10px;
  border-radius: var(--ks3-r-pill);
  background: var(--ks3-dark-track);
  border: 2px solid var(--ks3-on-dark-muted);
}
.ks3-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 28px;
  height: 28px;
  margin-top: -11px;
  border-radius: 50%;
  background: var(--ks3-alert);
  border: 3px solid var(--ks3-ink);
}
.ks3-slider::-moz-range-thumb {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--ks3-alert);
  border: 3px solid var(--ks3-ink);
}
.ks3-slider[disabled] { cursor: default; }
.ks3-slider[disabled]::-webkit-slider-thumb { background: var(--ks3-ink-ghost); }
.ks3-slider[disabled]::-moz-range-thumb { background: var(--ks3-ink-ghost); }
.ks3-slider:focus-visible {
  outline: var(--ks3-r-focus) solid var(--ks3-accent);
  outline-offset: 3px;
}

/* ── job-sort · the per-item sorter (b2-01, b2-02, b2-03) ──
   Reveals EACH ROW the instant that row is decided. The row's border going
   to ink is the only thing that changes on commitment — R3 and MRB-196 R10:
   nothing here marks correctness, and the why paragraph is one tone whether
   the student had it or not. */
.ks3-jobsort { margin-top: 22px; }
.ks3-jobsort-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.ks3-jobsort-item {
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-option-border);
}
.ks3-jobsort-item[data-open="1"] { border-color: var(--ks3-ink); }
.ks3-jobsort-text {
  margin: 0;
  font-size: 19px;
  line-height: 1.5;
  font-weight: 600;
}
.ks3-jobsort-opts {
  margin-top: 12px;
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
.ks3-jobsort-opt {
  cursor: pointer;
  font: inherit;
  font-size: 16px;
  font-weight: 700;
  padding: 10px 15px;
  min-height: var(--ks3-tap);
  border-radius: var(--ks3-r-control);
  border: 2px solid var(--ks3-option-border);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}
.ks3-jobsort-opt[aria-pressed="true"] {
  border-color: var(--ks3-accent);
  background: var(--ks3-accent-tint);
}
.ks3-jobsort-opt[disabled] { cursor: default; }
.ks3-jobsort-opt[disabled][aria-pressed="false"] { opacity: var(--ks3-dim-spent); }
.ks3-jobsort-why {
  margin: 12px 0 0;
  font-size: 18px;
  line-height: 1.55;
  color: var(--ks3-ink-body);
}
.ks3-jobsort-answer { font-family: var(--ks3-font-display); }
.ks3-jobsort-close {
  margin-top: 20px;
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
}
.ks3-jobsort-close p { margin: 0; font-size: 19px; line-height: 1.6; }

/* ── system-switch (b2-01) ──
   Emit-all-show-one. The chain panel is ink-dark INSIDE a light block —
   which is the whole reason this is not `sabotage`, whose shell is dark. */
.ks3-switch-tabs {
  margin-top: 20px;
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
.ks3-switch-what {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-switch-name {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-switch-does {
  margin: 8px 0 0;
  font-size: 20px;
  line-height: 1.5;
  max-width: 58ch;
}
.ks3-switch-predict { margin-top: 20px; }
/* ⊕ MRB-257 · audit 3.18 — AMBER ON CREAM AT 1.54:1, and the correction
   already existed twice in this file. `.ks3-commit` carries `--ks3-alert`,
   which is right on the ink grounds where nearly every gate sits and wrong
   here: this panel is `--ks3-card`, and #FFC53D on #FFFCF5 measures 1.54:1 at
   22px/700 against a 3:1 bar. `.ks3-walk-block .ks3-benchgate .ks3-commit` and
   its `.ks3-sbench-block` twin both route to `--ks3-accent-text` for exactly
   this reason; `.ks3-switch-predict` is the third cream-ground gate and was
   simply missed. 6.43:1. The string is "Switch it off. What is the first
   thing that goes wrong?" — the instruction the whole block asks the student
   to act on, unreadable at every viewport and in every interaction state. */
.ks3-switch-predict .ks3-commit {
  margin: 0 0 12px;
  color: var(--ks3-accent-text);
}
.ks3-switch-foot {
  margin-top: 18px;
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}
.ks3-switch-btn[disabled] { opacity: var(--ks3-dim-spent); cursor: default; transform: none; }
.ks3-switch-hint {
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
  color: var(--ks3-ink-muted);
}
.ks3-switch-chain {
  margin-top: 22px;
  padding: 26px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ink);
  color: var(--ks3-on-dark);
}
.ks3-switch-title {
  margin: 0 0 18px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ks3-alert);
}
.ks3-switch-rows { display: flex; flex-direction: column; gap: 16px; }
.ks3-switch-row {
  display: grid;
  grid-template-columns: minmax(0, 104px) minmax(0, 1fr);
  gap: 16px;
  align-items: start;
}
/* `show_levels: false` omits the chip AND collapses the column. Design's own
   page keeps an empty pill in a 104px column that holds nothing. */
.ks3-switch-row[data-nolevel="1"] { grid-template-columns: minmax(0, 1fr); }
.ks3-switch-row + .ks3-switch-row {
  padding-top: 16px;
  border-top: 2px solid var(--ks3-dark-rule);
}
.ks3-switch-chip {
  display: inline-block;
  padding: 5px 11px;
  border-radius: 9px;
  background: var(--ks3-dark-panel);
  color: var(--ks3-on-dark-muted);
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
  text-align: center;
}
/* Colour is a function of the LEVEL STRING, never of position: the chains do
   not all climb, so a chip keyed on index would be a claim about direction
   the data does not make. */
.ks3-switch-chip[data-level="cell"] { color: var(--ks3-alert); }
.ks3-switch-step {
  margin: 0;
  font-size: 19px;
  line-height: 1.55;
  color: var(--ks3-on-dark-body);
}
.ks3-switch-close {
  margin: 20px 0 0;
  padding-top: 18px;
  border-top: 2px solid var(--ks3-dark-rule);
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-on-dark);
}
.ks3-switch-all {
  margin-top: 20px;
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
}
.ks3-switch-all p { margin: 0; font-size: 19px; line-height: 1.6; }
@media (max-width: 620px) {
  .ks3-switch-row { grid-template-columns: minmax(0, 1fr); gap: 8px; }
  /* Design's own page rule (c2-01 style block, line 253): the observation row
     collapses to one column, so a 150px verdict column never squeezes the
     sentence to nothing. */
  .ks3-obs-row { grid-template-columns: minmax(0, 1fr); }
  .ks3-obs-verdict { text-align: left; }
}

/* ── the two canvas benches (b2-02 joint-bench, b2-03 muscle-pair) ──
   One set of chrome for both: they are drawn differently and framed
   identically, and two copies of the frame is exactly the drift a shared
   stylesheet exists to stop.

   ⚠️ EVERY <p> RULE BELOW IS SCOPED `.ks3-dark …`, AND IT HAS TO BE.
   `.ks3-dark p { color: var(--ks3-on-dark-body) }` is (0,1,1) and a bare
   class is (0,1,0), so an unscoped `.ks3-joint-trade { color: … }` LOSES and
   the verdict panel renders on-dark body copy on a cream ground. That is the
   exact defect B1 shipped — the zoom instrument's accent label resolving to
   `--ks3-on-dark-body` because `.ks3-dark p` was winning — and it was found
   here by the layer-C assertion rather than by reading, which is what the
   assertion is for. */
.ks3-joint-tabs,
.ks3-muscle-btns {
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
.ks3-joint-tabs { margin-top: 24px; }
.ks3-muscle-groups {
  margin-top: 24px;
  display: flex;
  flex-wrap: wrap;
  gap: 20px 30px;
}
.ks3-dark .ks3-muscle-grouplabel {
  margin: 0 0 9px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-joint-stage {
  margin-top: 20px;
  border-radius: var(--ks3-r-card);
  border: 2px solid var(--ks3-on-dark-muted);
  overflow: hidden;
}
.ks3-joint-canvas { display: block; width: 100%; height: auto; }
.ks3-joint-controls {
  padding: 16px 18px 18px;
  border-top: 2px solid var(--ks3-on-dark-muted);
  background: var(--ks3-dark-panel);
}
.ks3-joint-anglerow {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}
.ks3-dark .ks3-joint-anglelabel {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-dark .ks3-joint-anglevalue {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 17px;
  font-weight: 500;
  color: var(--ks3-alert);
}
.ks3-joint-twistrow {
  margin-top: 10px;
  display: flex;
  gap: 12px;
  align-items: center;
  flex-wrap: wrap;
}
.ks3-dark .ks3-joint-twistnote,
.ks3-dark .ks3-muscle-status {
  margin: 0;
  font-size: 18px;
  font-weight: 700;
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-muscle-status { font-size: 19px; }
.ks3-joint-tiles {
  margin-top: 18px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr));
  gap: 12px;
}
.ks3-joint-tile {
  padding: 15px 17px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-dark-panel);
}
.ks3-dark .ks3-joint-tile-label {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-dark .ks3-joint-tile-value {
  margin: 5px 0 0;
  font-size: 19px;
  font-weight: 700;
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-joint-tile-value.ks3-joint-tile-mono {
  font-family: var(--ks3-font-mono);
  font-size: 27px;
  font-weight: 500;
  color: var(--ks3-alert);
}
/* The verdict lands on a LIGHT panel inside the ink-dark block — the same
   move `removal-cases` makes, and for the same reason: the sentence a
   student is meant to take away must not read as one more dark readout. */
.ks3-dark .ks3-joint-trade {
  margin: 18px 0 0;
  padding: 17px 19px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
  font-size: 19px;
  line-height: 1.6;
}
.ks3-dark .ks3-joint-trade strong { font-family: var(--ks3-font-display); }

/* ── the initial-pill row (⊕ §4.8.2 · explainer.pills) ──
   MRS GREN and its successors. DECORATIVE REFERENCE, NOT A CONTROL — no
   aria-pressed, no cursor, no hover. If it ever grows one a student will press
   it, and it does nothing. */
.ks3-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 18px 0 0;
  padding: 0;
  list-style: none;
}
.ks3-pills li {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 9px 15px 9px 9px;
  background: var(--ks3-card);
  border: 2px solid var(--ks3-option-border);
  border-radius: var(--ks3-r-pill);
}
.ks3-pill-badge {
  width: 26px;
  height: 26px;
  border-radius: 8px;
  background: var(--ks3-band);
  display: grid;
  place-items: center;
  font-family: var(--ks3-font-display);
  font-size: 15px;
  font-weight: 800;
  color: var(--ks3-accent-text);
}
.ks3-pill-label { font-size: 17px; font-weight: 600; }

/* ── mono-numeral scorecards (⊕ §4.8.2 · misconception.scorecards) ──
   The figure is MONO because it is a live-number role. The card sits on the
   amber misconception ground without becoming a mark. */
.ks3-scorecards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(232px, 1fr));
  gap: 14px;
  /* 18px, not 20px, and the title's 8px, not 10px — both taken off Design's
     approved b1-01 now that the renderer exists to be measured against it. */
  margin: 18px 0 0;
  padding: 0;
  list-style: none;
}
.ks3-scorecards li {
  padding: 18px 20px;
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
  border-radius: var(--ks3-r-card);
}
.ks3-scorecard-fig {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 32px;
  font-weight: 500;
  line-height: 1;
  color: var(--ks3-ink);
}
.ks3-scorecard-title { margin: 8px 0 0; font-size: 18px; font-weight: 700; }
.ks3-scorecard-note {
  margin: 6px 0 0;
  font-size: 17px;
  line-height: 1.5;
  color: var(--ks3-ink-body);
}

/* Prose standing in for a list in an end-matter card (⊕ §4.8.1 D). */
.ks3-endmatter-prose { color: var(--ks3-ink-body); }
.ks3-safety { border-top: 0; margin-top: 18px; }

/* ═══════════════════════════════════════════════════════════════════
   C2 · Atoms, elements and compounds  (⊕ 16 Aug 2026, MRB-220)

   Nine instruments across six lessons, measured off Design's six frozen
   pages in `docs/ks3/design-reference/c2/`. Same standing rule as B2's:
   where a value differs from one already shipped, the SHIPPED one wins and
   the difference is reported as drift (contract R3).

   ⚠️ SPECIFICITY. `.ks3-dark p` is (0,1,1) and beats a bare instrument
   class (0,1,0), so every rule below that paints a <p> inside an ink-dark
   block is scoped `.ks3-dark …`. That is the defect the B2 pass was bitten
   by and the one B1 shipped with the zoom instrument; it is invisible to a
   grep and obvious in a browser.
   ═══════════════════════════════════════════════════════════════════ */

/* ── the dark-canvas frame (map N12) ──
   One wrapper shape, four instruments: a `--ks3-r-card` box with a 2px
   on-dark-muted outline and a dark-panel strip under the canvas carrying the
   controls. Design inlines it four times; it is one component here. */
.ks3-canvas-frame {
  margin-top: 20px;
  border-radius: var(--ks3-r-card);
  border: 2px solid var(--ks3-on-dark-muted);
  overflow: hidden;
}
.ks3-canvas-frame canvas { display: block; width: 100%; height: auto; }
.ks3-canvas-foot {
  padding: 16px 18px 18px;
  border-top: 2px solid var(--ks3-on-dark-muted);
  background: var(--ks3-dark-panel);
}

/* ── claim-switch (c2-01 #s-model) ──
   A LIGHT block. Nothing here is scoped `.ks3-dark`, and it must not be —
   the instrument is a card-ground panel inside a card-ground block, and the
   whole readability of the off state depends on ink-on-cream. */
.ks3-claimswitch { margin-top: 22px; }
.ks3-claims-label {
  margin: 0 0 12px;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-obs-label { margin-top: 22px; }
.ks3-claims,
.ks3-obs { display: flex; flex-direction: column; gap: 10px; }

/* A claim is a full-width toggle carrying its own state word. The chip is a
   READOUT, not decoration: "OFF" in words is what makes the state legible to
   a student who cannot rely on the ground going dim. */
.ks3-claim {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  cursor: pointer;
  padding: 16px 18px;
  border-radius: var(--ks3-r-option);
  font: inherit;
  font-size: 18px;
  font-weight: 600;
  min-height: var(--ks3-tap);
  text-align: left;
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
  color: var(--ks3-ink);
}
.ks3-claim[aria-pressed="false"] {
  background: var(--ks3-row-dim);
  border-color: var(--ks3-rule);
  color: var(--ks3-ink-faint);
}
.ks3-claim-chip {
  flex: 0 0 52px;
  display: inline-grid;
  place-items: center;
  padding: 5px 0;
  border-radius: 9px;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  background: var(--ks3-ink);
  color: var(--ks3-on-dark);
}
.ks3-claim[aria-pressed="false"] .ks3-claim-chip {
  background: var(--ks3-band);
  color: var(--ks3-ink-muted);
}
.ks3-claim-text { flex: 1; text-align: left; }

/* An observation row: the sentence on the left, a mono verdict right. Under
   620px it stacks, which is Design's own page rule. */
.ks3-obs-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 150px);
  gap: 14px;
  align-items: center;
  padding: 16px 18px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-option-border);
}
/* ⚖️ The dead state is the BAND ground on a 2px ink border — the same
   treatment a KEY FACT takes, and deliberately not a red or a dim. An
   observation that stops being explained is not an error the student made. */
.ks3-obs-row[data-dead="1"] {
  background: var(--ks3-band);
  border-color: var(--ks3-ink);
}
.ks3-obs-text { margin: 0; font-size: 18px; line-height: 1.5; }
.ks3-obs-verdict {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .05em;
  text-transform: uppercase;
  text-align: right;
  color: var(--ks3-ink-muted);
}
.ks3-obs-row[data-dead="1"] .ks3-obs-verdict { color: var(--ks3-accent-text); }
.ks3-claim-note {
  margin: 18px 0 0;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
  font-size: 19px;
  line-height: 1.6;
}

/* ── test-budget-bench (c2-02 #s-bench) ──
   A LIGHT block. The budget line lives in the block head and is the only
   counter in the key stage painted in `--ks3-accent-text` rather than
   ink-muted — because it is a resource running down, not a tally going up. */
.ks3-budget { margin-top: 20px; }
.ks3-blockhead-count[data-tone="accent"] { color: var(--ks3-accent-text); }
.ks3-sample-tabs,
.ks3-sample-tests,
.ks3-verdict-btns { display: flex; gap: 9px; flex-wrap: wrap; }
.ks3-sample {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-sample-name {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-sample-look { margin: 8px 0 0; font-size: 20px; line-height: 1.5; }
.ks3-sample-tests { margin-top: 18px; }
/* ⚖️ A SPENT test button stays legible. Design dims it to .45, which is the
   budget being visible: a student must be able to read what they already
   bought while deciding what to buy next. */
.ks3-test-btn[disabled] { opacity: .45; cursor: default; }
/* ⊕ MRB-242 — the SEVENTH instance of the trap, and the first found by a gate
   rather than by someone looking at a page. c2-02's results list ships
   `hidden` and gave itself `display: flex`, which beats the UA rule.
   ⚖️ Scope, stated honestly: every `.ks3-result` INSIDE it is separately
   `hidden` and correctly stays so, so no bought-test result was ever exposed
   — the pedagogy held. What shipped was an empty flex box with an 18px top
   margin on all six benches. Cosmetic, not a leak. It is fixed here because
   it is the same defect one layer out, and because the next element to be
   added to that list would not have been hidden at all. */
.ks3-results[hidden] { display: none; }
.ks3-results {
  list-style: none;
  margin: 18px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.ks3-result {
  padding: 14px 16px;
  border-radius: 14px;
  background: var(--ks3-card);
  border: 2px solid var(--ks3-rule);
}
.ks3-result-test {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-result-body { margin: 5px 0 0; font-size: 18px; line-height: 1.5; }
.ks3-sample-verdict {
  margin-top: 20px;
  padding-top: 18px;
  border-top: 2px solid var(--ks3-rule);
}
.ks3-sample-ask { margin: 0 0 10px; font-size: 19px; font-weight: 700; }
/* ⚖️ The verdict panel is INK inside a light block, and it fires on the
   student's verdict whether or not that verdict was right. It is a reveal,
   never a mark — the instrument does not know who was correct and must not
   look as though it does. */
.ks3-verdict-panel {
  margin-top: 16px;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ink);
  color: var(--ks3-on-dark);
}
.ks3-verdict-panel .ks3-verdict-name {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 24px;
  line-height: 1.2;
  letter-spacing: -.02em;
  color: var(--ks3-on-dark);
}
.ks3-verdict-panel .ks3-verdict-why {
  margin: 10px 0 0;
  font-size: 18px;
  line-height: 1.6;
  color: var(--ks3-on-dark-body);
}
.ks3-bench-close {
  margin-top: 20px;
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
}
.ks3-bench-close p { margin: 0; font-size: 19px; line-height: 1.6; }

/* ── mixture-compound-dish (c2-03 #s-bench) ──
   Ink-dark. Every <p> rule is scoped `.ks3-dark …` or lives on the LIGHT
   result panel nested inside, where the scoping is the other way round. */
.ks3-dish { margin-top: 4px; }
.ks3-dish-groups { display: flex; flex-wrap: wrap; gap: 20px 30px; }
.ks3-dark .ks3-dish-grouplabel {
  margin: 0 0 9px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-dish-btns,
.ks3-dish-tests { display: flex; gap: 9px; flex-wrap: wrap; }
.ks3-dish-tests { margin-top: 18px; }
/* ⚖️ THE REFUSAL IS THE LESSON. A proportion control that has been heated is
   disabled and SAYS SO by dimming, rather than vanishing — a student has to
   see the control they can no longer use, or the lesson is that it was never
   there. Same reasoning as the joint bench's locked slider. */
.ks3-dish-ratio[disabled] { opacity: .45; cursor: default; }
.ks3-dark .ks3-dish-note {
  margin: 0;
  font-size: 18px;
  font-weight: 700;
  color: var(--ks3-on-dark);
}
/* The result card is the CONTRAST spine, and it lands on a LIGHT ground
   inside the ink block — the same move `removal-cases` and the joint bench's
   verdict make, and for the same reason: the sentence a student takes away
   must not read as one more dark readout. */
.ks3-dish-result {
  margin-top: 16px;
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}
.ks3-dark .ks3-dish-testname {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-dish-cols {
  margin-top: 12px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
  gap: 14px;
}
.ks3-dish-col { padding: 16px 18px; border-radius: 16px; }
.ks3-dish-before { background: var(--ks3-inset); border: 2px solid var(--ks3-rule); }
.ks3-dish-after { background: var(--ks3-card); border: 2px solid var(--ks3-ink); }
.ks3-dark .ks3-dish-collabel {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
/* The AFTER column's label is the one accent in the pair — it is the state
   that changed, and the colour is what says so before the words do. */
.ks3-dark .ks3-dish-after .ks3-dish-collabel { color: var(--ks3-accent-text); }
.ks3-dark .ks3-dish-body { margin: 6px 0 0; font-size: 18px; line-height: 1.5; }
.ks3-dark .ks3-dish-verdict {
  margin: 14px 0 0;
  font-size: 18px;
  line-height: 1.55;
  color: var(--ks3-ink-body);
}
.ks3-dish-verdict strong { font-family: var(--ks3-font-display); }

/* ── verdict-cards (c2-03 #s-sort · c2-04 #s-sort · c2-04 #s-read) ──
   One component, two layouts, three headline types. LIGHT blocks in all
   three instances, so nothing here is scoped `.ks3-dark`. */
.ks3-vcards { margin-top: 22px; }
.ks3-vcards[data-layout="column"] {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
/* Design's own page rule (c2-04 style block, line 23). */
.ks3-vcards[data-layout="grid"] {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(210px, 100%), 1fr));
  gap: 14px;
}
.ks3-vcard {
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-option-border);
}
.ks3-vcards[data-layout="grid"] .ks3-vcard { padding: 18px 20px; }
/* ⚖️ The only mark the card makes is its OWN border going to ink. It marks
   the card as decided, never the button as right — which is what keeps a
   per-card reveal clear of R3. */
.ks3-vcard[data-open="1"] { border-color: var(--ks3-ink); }
.ks3-vcard-head { margin: 0; font-size: 19px; line-height: 1.5; font-weight: 600; }
.ks3-vcard-head[data-headline="formula"] {
  font-family: var(--ks3-font-mono);
  font-size: 26px;
  font-weight: 500;
}
.ks3-vcard-head[data-headline="symbol"] {
  font-family: var(--ks3-font-display);
  font-size: 42px;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -.02em;
}
.ks3-vcard-sub {
  margin: 6px 0 0;
  font-size: 19px;
  font-weight: 600;
  color: var(--ks3-ink-body);
}
.ks3-vcards[data-layout="grid"] .ks3-vcard-sub { font-size: 19px; }
.ks3-vcard-opts { margin-top: 12px; display: flex; gap: 8px; flex-wrap: wrap; }
.ks3-vcard-opt[disabled] { cursor: default; }
.ks3-vcard-opt[disabled][aria-pressed="false"] { opacity: .5; }
.ks3-vcard-why {
  margin: 12px 0 0;
  font-size: 18px;
  line-height: 1.55;
  color: var(--ks3-ink-body);
}
.ks3-vcards[data-layout="grid"] .ks3-vcard-why { font-size: 17px; }
.ks3-vcard-answer { font-family: var(--ks3-font-display); }
.ks3-vcards-close {
  margin-top: 20px;
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
}
.ks3-vcards-close p { margin: 0; font-size: 19px; line-height: 1.6; }

/* ── formula-builder (c2-05 #s-builder) ──
   Ink-dark. Three control groups, two of which carry a LIVE label — the
   count group's caption is the element's name and changes with the pair. */
.ks3-fb { margin-top: 4px; }
.ks3-fb-groups { display: flex; flex-wrap: wrap; gap: 20px 30px; }
.ks3-dark .ks3-fb-grouplabel {
  margin: 0 0 9px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-fb-btns { display: flex; gap: 9px; flex-wrap: wrap; align-items: center; }
/* ⚖️ The name is 30px display 800 whether or not the formula is a real
   substance — "H3O2 — not a substance" gets the same weight as "Water". The
   refusal is an answer, not an error state, and dimming it would teach that
   the student did something wrong. */
.ks3-dark .ks3-fb-name {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 30px;
  line-height: 1.1;
  letter-spacing: -.02em;
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-fb-note {
  margin: 8px 0 0;
  font-size: 18px;
  line-height: 1.55;
  color: var(--ks3-on-dark-body);
}

/* ── model-limit (c2-05 #s-limit) ──
   A LIGHT block on inset. ⚖️ The light/ink asymmetry IS the argument: the
   molecule sits on a card and the giant structure sits on ink, so the thing
   the model does not cover looks different before a word of it is read. */
.ks3-limit { margin-top: 20px; }
.ks3-limit-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
  gap: 16px;
}
.ks3-limit-card { padding: 22px 24px; border-radius: var(--ks3-r-panel); }
.ks3-limit-card[data-ground="card"] {
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
}
.ks3-limit-card[data-ground="ink"] {
  background: var(--ks3-ink);
  color: var(--ks3-on-dark);
  border: 0;
}
.ks3-limit-caption {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
/* On the ink card the caption goes ALERT — the one place in this block amber
   appears, and it is marking the boundary of a model rather than a mistake. */
.ks3-limit-card[data-ground="ink"] .ks3-limit-caption { color: var(--ks3-alert); }
.ks3-limit-body { margin: 8px 0 0; font-size: 18px; line-height: 1.55; }
.ks3-limit-card[data-ground="ink"] .ks3-limit-body { color: var(--ks3-on-dark-body); }
.ks3-limit-commit { margin: 18px 0 12px; font-size: 19px; font-weight: 700; }

/* ── balance-bench (c2-06 #s-balance) ── */
.ks3-bal { margin-top: 4px; }
.ks3-bal-groups { display: flex; flex-wrap: wrap; gap: 20px 30px; }
.ks3-dark .ks3-bal-grouplabel {
  margin: 0 0 9px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-bal-btns { display: flex; gap: 9px; flex-wrap: wrap; }
.ks3-canvas-foot[data-row="1"] {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}
/* The run button is the one control in the unit on the ALERT ground: it is
   the thing that makes the reaction happen, and it is spent afterwards. */
.ks3-bal-run {
  font: inherit;
  font-size: 18px;
  font-weight: 700;
  padding: 13px 22px;
  min-height: var(--ks3-tap);
  border-radius: var(--ks3-r-control);
  border: 2px solid var(--ks3-ink);
  background: var(--ks3-alert);
  color: var(--ks3-ink);
  cursor: pointer;
}
.ks3-bal-run[disabled] { opacity: .55; cursor: default; }
.ks3-dark .ks3-bal-status {
  margin: 0;
  font-size: 18px;
  font-weight: 700;
  color: var(--ks3-on-dark);
}
.ks3-bal-tiles {
  margin-top: 18px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr));
  gap: 12px;
}
.ks3-bal-tile {
  padding: 15px 17px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-dark-panel);
}
.ks3-dark .ks3-bal-tile-label {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
/* ⚖️ THE THIRD TILE NEVER MEASURES. Two tiles are mono readouts; the third is
   a 19px/700 sentence that reads "not measured — you work it out" for ever.
   The type difference is what says it is a refusal and not a pending value. */
.ks3-dark .ks3-bal-tile-value {
  margin: 5px 0 0;
  font-size: 19px;
  font-weight: 700;
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-bal-tile-value.ks3-bal-tile-mono {
  font-family: var(--ks3-font-mono);
  font-size: 25px;
  font-weight: 500;
}
/* The AFTER reading is the one that changes, so it is the one in alert. */
.ks3-dark .ks3-bal-tile-value[data-tile="after"] { color: var(--ks3-alert); }
.ks3-dark .ks3-bal-note {
  margin: 18px 0 0;
  padding: 17px 19px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
  font-size: 19px;
  line-height: 1.6;
}

/* ── the SUM's formula block: a balance beam and a part–whole bar ──
   MRB-204 as amended. A triangle encodes a product or a quotient; a sum gets
   a beam and a bar, because drawing a sum as a triangle would teach a false
   relationship to make a rule fit. The triangle path is untouched. */
.ks3-formula[data-shape="sum"] {
  background: var(--ks3-band);
  border: 3px solid var(--ks3-ink);
  border-radius: var(--ks3-r-block);
  padding: 32px;
}
.ks3-formula-eyebrow { text-align: center; color: var(--ks3-accent-text); }
.ks3-formula-figure {
  display: flex;
  justify-content: center;
  margin-top: 18px;
}
.ks3-beam { width: 100%; max-width: 470px; height: auto; }
.ks3-beam-stand { fill: var(--ks3-ink); }
.ks3-beam-post,
.ks3-beam-arm {
  stroke: var(--ks3-ink);
  stroke-width: 7;
  stroke-linecap: round;
}
.ks3-beam-pivot { fill: var(--ks3-accent); stroke: var(--ks3-ink); stroke-width: 5; }
.ks3-beam-hanger { stroke: var(--ks3-ink); stroke-width: 4; }
.ks3-beam-pan { fill: var(--ks3-card); stroke: var(--ks3-ink); stroke-width: 5; }
.ks3-beam-label,
.ks3-beam-caption {
  font-family: var(--ks3-font-display);
  font-weight: 800;
  fill: var(--ks3-ink);
}
.ks3-beam-label { font-size: 27px; }
.ks3-beam-caption { font-size: 30px; }
.ks3-formula-support {
  margin: 14px 0 0;
  text-align: center;
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
  line-height: 1.9;
  color: var(--ks3-ink-body);
}

/* ⊕ PHASE 3, 25 Aug 2026 · the beam's own two sentences.
   `caption` sits above the drawing and says what it shows; `note` sits
   below and says why the shape is a beam rather than a triangle. Both are
   Design's, both were being dropped on the floor, and both are engine-level
   rather than any one unit's — which is why they live here beside
   `.ks3-formula-figure` and not inside a BEGIN P4 block. The flex switch is
   scoped with `:has()` so a figure without them keeps the centred row it
   has always had, byte for byte. */
.ks3-formula-figure:has(.ks3-formula-figcaption),
.ks3-formula-figure:has(.ks3-formula-fignote) {
  flex-direction: column;
  align-items: center;
}
.ks3-formula-figcaption {
  margin: 0 0 14px;
  max-width: 46ch;
  text-align: center;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 20px;
  line-height: 1.3;
  letter-spacing: -.01em;
  color: var(--ks3-ink);
}
.ks3-formula-fignote {
  margin: 16px 0 0;
  max-width: 60ch;
  text-align: center;
  font-size: 16px;
  line-height: 1.6;
  color: var(--ks3-ink-body);
}

/* ── the part–whole bar with cover plates ── */
.ks3-bar-block { margin-top: 30px; padding-top: 28px; border-top: 3px solid var(--ks3-ink); }
.ks3-bar-head .ks3-eyebrow { color: var(--ks3-accent-text); }
.ks3-bar-head h2 {
  margin: 6px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 32px;
  line-height: 1.1;
  letter-spacing: -.03em;
}
.ks3-bar-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 30px 44px;
  margin-top: 22px;
}
.ks3-bar { flex: 1 1 300px; min-width: 280px; max-width: 470px; height: auto; }
.ks3-bar-cell { fill: var(--ks3-card); stroke: var(--ks3-ink); stroke-width: 4; }
.ks3-bar-label {
  font-family: var(--ks3-font-display);
  font-weight: 800;
  fill: var(--ks3-ink);
}
.ks3-bar-split { stroke: var(--ks3-rule-strong); stroke-width: 3; stroke-dasharray: 8 7; }
.ks3-bar-plate { fill: var(--ks3-ink); }
/* ⚖️ THE GHOST LABEL. The covered quantity stays faintly readable on top of
   the plate, so a student can see what they covered. The triangle's cover is
   a plain opaque rect and this is the measured difference. */
.ks3-bar-ghost {
  font-family: var(--ks3-font-display);
  font-weight: 800;
  fill: var(--ks3-ink-ghost);
}
.ks3-bar-side { flex: 1 1 300px; min-width: 260px; }
.ks3-bar-btns { display: flex; gap: 9px; flex-wrap: wrap; }
/* Its own control style — neither `seg` nor `segLight`. The 2px ink border is
   ALWAYS there and only the ground swaps, because all three buttons are live
   at all times: this is a radio, and there is no spent state. */
.ks3-bar-btn {
  font: inherit;
  font-size: 17px;
  font-weight: 700;
  padding: 11px 18px;
  min-height: var(--ks3-tap);
  border-radius: var(--ks3-r-control);
  border: 2px solid var(--ks3-ink);
  background: var(--ks3-card);
  color: var(--ks3-ink);
  cursor: pointer;
}
.ks3-bar-btn[aria-pressed="true"] {
  background: var(--ks3-ink);
  color: var(--ks3-on-dark);
}
.ks3-bar-result {
  margin: 18px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 28px;
  line-height: 1.2;
  letter-spacing: -.02em;
}
.ks3-bar-sentence { margin: 12px 0 0; font-size: 19px; line-height: 1.55; }
.ks3-bar-close {
  margin: 14px 0 0;
  font-size: 18px;
  line-height: 1.55;
  color: var(--ks3-ink-body);
}

/* ── the chipped worked-example step (map N5) ── */
.ks3-fifa-chips { display: flex; flex-direction: column; gap: 12px; }
/* ⊕ MRB-242 — the SIXTH time this trap has been laid in this stylesheet (see
   `.ks3-card-back[hidden]`, `.ks3-ticks[hidden]`, `.ks3-picker-panel[hidden]`,
   `.ks3-hb [hidden]`, `.ks3-sortcards [hidden]`). An AUTHOR `display` beats
   the UA stylesheet's `[hidden] { display: none }` at ANY specificity, so the
   `display: flex` below silently un-hid every staged step the generator wrote
   `hidden` on: b2-04 shipped its worked example with all four FIFA steps
   already showing, above a counter reading "Step 0 of 4" and a button with
   nothing left to do. verify_ks3.py now fails the build on any element that
   ships `hidden` and still computes a display, so there is no seventh. */
.ks3-fifa-chipped[hidden] { display: none; }
.ks3-fifa-chipped {
  display: flex;
  gap: 16px;
  align-items: flex-start;
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-option-border);
}
.ks3-fifa-chip {
  flex: 0 0 38px;
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: 11px;
  background: var(--ks3-accent-text);
  color: var(--ks3-ground);
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 20px;
}
.ks3-fifa-body { flex: 1 1 auto; min-width: 0; }
.ks3-fifa-label {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-fifa-line {
  margin: 5px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 26px;
  line-height: 1.25;
  letter-spacing: -.02em;
  color: var(--ks3-ink);
}
.ks3-fifa-stepnote { margin: 6px 0 0; font-size: 18px; line-height: 1.55; color: var(--ks3-ink-body); }

/* ⊕ MRB-242 — the SUM LEDGER. B3's three running totals ("4 slices toast and
   butter · 4 × 780 = 3120 kJ") author a name and a line and no letter, because
   they are sums and not F-I-F-A. MRB-242 rules one FIFA layout, so they take
   the same markup as every other step — but a five-row sum rendered as five
   bordered panels of 26px display type is not a sum any more, it is five
   worked examples. This gives those rows back the compact metrics they read
   with as a paragraph list, without a second markup shape existing to drift. */
.ks3-fifa-sum { gap: 2px; }
.ks3-fifa-sum .ks3-fifa-chipped {
  padding: 0;
  border: 0;
  background: none;
  border-radius: 0;
  gap: 0;
}
.ks3-fifa-sum .ks3-fifa-body {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 .6ch;
}
/* The label is the row's name, not a step label: display type at body size,
   not a mono uppercase eyebrow. Shouting "4 SLICES TOAST AND BUTTER" at a
   student adding up a day's food would be the eyebrow treatment misapplied. */
.ks3-fifa-sum .ks3-fifa-label {
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 19px;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ks3-ink);
}
.ks3-fifa-sum .ks3-fifa-line {
  margin: 0;
  font-size: 19px;
  font-weight: 400;
  letter-spacing: 0;
}
.ks3-fifa-foot {
  margin-top: 18px;
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}
.ks3-step-donenote { font-size: 18px; font-weight: 700; color: var(--ks3-ink-body); }

/* ── fifa-pick (c2-06 #s-build) — MRB-204 part 4 ── */
/* ⊕ N10. Design inlines `position:absolute; left:-9999px` on two <label>s and
   `shared/ks3.css` had no helper. One class now. */
.ks3-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}
.ks3-pick { margin-top: 22px; }
.ks3-pick-panels { display: flex; flex-direction: column; gap: 18px; }
.ks3-pick-panel {
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-option-border);
}
.ks3-pick-label {
  margin: 0 0 12px;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
/* ⚖️ STACKED and LEFT-ALIGNED — the only left-aligned control set in the key
   stage, and it has to be: these are candidate EQUATIONS, and centring them
   would make three lines of mono maths unscannable. */
.ks3-pick-opts { display: flex; flex-direction: column; gap: 9px; }
.ks3-pick-opt {
  font: inherit;
  font-family: var(--ks3-font-mono);
  font-size: 16px;
  font-weight: 500;
  text-align: left;
  padding: 11px 16px;
  min-height: var(--ks3-tap);
  border-radius: var(--ks3-r-control);
  border: 2px solid var(--ks3-option-border);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
  cursor: pointer;
}
.ks3-pick-opt[aria-pressed="true"] {
  border-color: var(--ks3-accent);
  background: var(--ks3-accent-tint);
}
.ks3-pick-opt[disabled] { opacity: .45; cursor: default; }
.ks3-pick-answer { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; }
.ks3-pick-input {
  font: inherit;
  font-family: var(--ks3-font-mono);
  font-size: 22px;
  font-weight: 500;
  width: 8.5rem;
  padding: 12px 14px;
  min-height: var(--ks3-tap);
  border-radius: var(--ks3-r-option);
  border: 2px solid var(--ks3-option-border);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}
.ks3-pick-unit { font-size: 18px; }
.ks3-pick-input[disabled],
.ks3-pick-unit[disabled] { opacity: .55; }
.ks3-pick-foot {
  margin-top: 20px;
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}
.ks3-pick-btn[disabled] { opacity: .45; cursor: default; }
.ks3-pick-progress {
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
  color: var(--ks3-ink-muted);
}
/* The model answer lands on INK inside a light block — the same inversion the
   verdict panel and the switch chain make, and for the same reason. */
.ks3-pick-reveal {
  margin-top: 20px;
  padding: 26px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ink);
  color: var(--ks3-on-dark);
}
.ks3-pick-reveal .ks3-pick-revealhead {
  margin: 0 0 16px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ks3-alert);
}
.ks3-pick-step { display: flex; gap: 14px; align-items: flex-start; }
.ks3-pick-step + .ks3-pick-step {
  margin-top: 16px;
  padding-top: 16px;
  border-top: 2px solid var(--ks3-dark-rule);
}
.ks3-pick-chip {
  flex: 0 0 34px;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 10px;
  background: var(--ks3-alert);
  color: var(--ks3-ink);
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 18px;
}
.ks3-pick-stepbody { flex: 1 1 auto; min-width: 0; }
.ks3-pick-reveal .ks3-pick-steplabel {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-pick-reveal .ks3-pick-stepline {
  margin: 5px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 25px;
  line-height: 1.25;
  letter-spacing: -.02em;
  color: var(--ks3-on-dark);
}
.ks3-pick-reveal .ks3-pick-stepnote {
  margin: 6px 0 0;
  font-size: 18px;
  line-height: 1.55;
  color: var(--ks3-on-dark-body);
}
.ks3-pick-reveal .ks3-pick-close {
  margin: 18px 0 0;
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-on-dark-body);
}

/* ── scale-zoom (c2-01 #s-scale) ──
   Ink-dark, so every <p> below is scoped past `.ks3-dark p`. */
.ks3-scale { margin-top: 4px; }
.ks3-scale-controls {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}
.ks3-scale-btn[disabled] { opacity: .45; cursor: default; }
.ks3-dark .ks3-scale-readout {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 17px;
  font-weight: 500;
  color: var(--ks3-alert);
}
.ks3-dark .ks3-scale-note {
  margin: 16px 0 0;
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-on-dark-body);
}

/* ── narrow screens ────────────────────────────────────────────────
   A real 390px phone. The reference set is desktop-first, so these are
   translation decisions rather than transcriptions: single-column
   grids, tighter block padding, and a title that stops at its clamp
   floor rather than overflowing. */
@media (max-width: 34rem) {
  .ks3-rule { padding: 26px 20px; }
  .ks3-formula-statement { padding: 30px 20px; }
  .ks3-compare { padding: 22px 18px; }
  .ks3-main { padding: 28px 16px 64px; }

  /* ── the browse layer on a phone (MRB-182) ──
     These need their own rules rather than inheriting `.ks3-main`'s,
     because `.ks3-main.is-browse` is (0,2,0) and wins on specificity
     whatever the source order. Everything two-up collapses to one
     column and everything sticky goes static — a sticky season tile on
     a 390px screen is a banner eating a third of the viewport. */
  .ks3-main.is-browse,
  .ks3-main.is-hub { padding: 28px 16px 64px; }
  .ks3-nav-rail { padding: 0 16px; gap: 12px; min-height: 64px; }
  /* ⊖ SUPERSEDED by MRB-229's ruling — see the ≤700px block below, which is
     later in the file and wins. This rule read
       `.ks3-trail a { white-space: normal; overflow-wrap: break-word; }`
     and its reasoning was that letting a long unit name wrap is "the least
     visible departure". It was the wrong trade and it is the direct cause of
     two live defects: the wrapped trail grew the sticky header to as much as
     214.8px (25% of a phone viewport, 37.6% at the 320px reflow width), and
     the height it grew to is what put in-page anchors behind the header on 36
     of 58 pages. Wrapping also did not stop the sideways scroll — `nowrap` on
     the anchor kept the crumb itself unbreakable at the word level, so B9's
     longest trail still pushed the document to 577px against a 390 viewport.
     Kept as a record rather than deleted: the reasoning is the interesting
     part, and the next person to reach for wrapping should meet it here. */
  .ks3-footer-rail { padding: 24px 16px; gap: 12px; }

  .ks3-hub-top { gap: 34px; }
  /* ⊕ MRB-257, also found in passing. "Ecosystems and interdependence" cannot
     break at display size in 288px of content width, so a unit index pushed
     the document to 324px at the 320 reflow width. Only engages when a word
     genuinely cannot fit — at 390 there is room and nothing breaks. */
  .ks3-unit-head h1,
  .ks3-hub-title { overflow-wrap: break-word; }
  .ks3-hub-actions { gap: 10px; }
  .ks3-btn { padding: 15px 20px; font-size: 17px; }
  /* ⊖ MRB-330 — `.ks3-live { padding: 22px 20px 20px; }` and
     `.ks3-live-line { font-size: 22px; }` removed with the panel itself. */
  .ks3-hub-sec { margin-top: 52px; }
  .ks3-sec-head { display: block; }
  .ks3-sec-head h2 { font-size: 32px; }
  .ks3-sec-head p { margin-top: 8px; max-width: none; }
  .ks3-browse-year h2 { font-size: 42px; }
  .ks3-picker-panel { min-width: 0; width: min(340px, calc(100vw - 44px)); }

  .ks3-browse-head { gap: 28px; }
  .ks3-stat-row { gap: 8px; }
  .ks3-stat { padding: 14px 8px; }
  .ks3-stat-n { font-size: 30px; }
  .ks3-stat-l { font-size: 13px; }

  .ks3-season-rows { margin-top: 36px; gap: 26px; }
  .ks3-season-row { grid-template-columns: 1fr; gap: 14px; }
  .ks3-season-side { position: static; }
  .ks3-season-tile { padding: 16px 18px; }
  .ks3-season-name { font-size: 27px; }

  .ks3-ht-panel { padding: 20px; }
  .ks3-ht-panel-n { font-size: 44px; }

  .ks3-unit-body { margin-top: 36px; gap: 24px; }
  .ks3-unit-aside { position: static; }
  /* A 64px node plus an 18px gutter is a quarter of the screen. */
  .ks3-tl-row > a { grid-template-columns: 48px minmax(0, 1fr); gap: 12px; }
  .ks3-tl-node { width: 48px; height: 48px; font-size: 21px; border-radius: 15px; }
  .ks3-timeline::before { left: 23px; }
  .ks3-tl-card { padding: 16px 18px; }
  .ks3-tl-title { font-size: 22px; }
  .ks3-h1-dotted { gap: 12px; }
  .ks3-block, .ks3-dark, .ks3-ladder { padding: 22px 18px; }
  .ks3-cards { grid-template-columns: 1fr; }
  .ks3-card-btn { min-height: 0; }
  .ks3-ladder .ks3-options { grid-template-columns: 1fr; }
  .ks3-endmatter { grid-template-columns: 1fr; }
  .ks3-ladder-head { flex-direction: column; align-items: flex-start; }
  .ks3-ladder-score { text-align: left; min-width: 0; }
  .ks3-sim-control input[type="range"] { max-width: none; width: 100%; }
  .ks3-keynote p { font-size: 24px; }
  .ks3-hook h2 { font-size: 30px; }
  .ks3-rung { padding-left: 16px; }
}

/* ═══════════════════════════════════════════════════════════════════════
   THE HEADER ON A NARROW SCREEN — ⊕ MRB-229, RULED 18 Aug 2026

   Every one of the 294 KS3 pages scrolled sideways on a 390px phone, and the
   header trail was the whole cause. Measured with device metrics overridden
   to a true 390px (NOT `--window-size`, which headless Chrome floors at about
   500 and hides this entirely):

       b8-05  document 397px   trail  91px
       b5-05  document 416px   trail 110px
       b3-07  document 490px   trail 184px
       b9-04  document 577px   trail 271px   ← the longest trail in Biology

   Nothing else on any of those pages overflowed — the shelf instrument on
   b9-04 measures scrollWidth == clientWidth == 322 at 390 — so this is one
   shell defect fixed once, not per-page layout debt. It also took the "KS3"
   pill entirely off-screen on 33 pages (left edge 468 in a 390 viewport), and
   because the header is sticky the pill never came back on a vertical scroll:
   a nav link that could only be reached by swiping sideways to maximum.
   (⊕ MRB-330 — the pill is cut. That half of the motivation is now history;
   the wrapping-header half below is still live and still the reason.)

   THE RULING: below 700px the trail shows UNIT AND PAGE ONLY, elides what
   does not fit, and the page never scrolls sideways.

   Written as "keep the last three children" rather than "hide the first four"
   so it cannot go wrong the day a trail is a different depth — `<li>`s come in
   crumb/separator pairs, so the last three are always [unit][›][this page].
   The same selector serves `.ks3-crumbs`, whose children are bare spans and
   anchors rather than list items: a unit index at 390 was overflowing for
   exactly the same reason, one component along, and the hub's single-crumb
   row matches nothing here and is left alone.

   What is NOT lost: the hidden crumbs are `display: none`, so a screen reader
   announces the two that are shown — the same two a sighted student sees —
   and every level above them is one tap away through the unit index the first
   shown crumb points at. `.ks3-brand` still goes to the site root, and since
   MRB-330 it is the only chrome link to it — the KS3 pill that used to sit at
   the right of the rail and point at the key-stage index is cut, and the first
   SHOWN crumb is what reaches that now.

   ⊕ MRB-330, 6 Sep 2026 — REVISITED here, because removing the pill hands this
   block back real width and the question was whether the rules above actually
   let the crumbs have it. They do, and no change was needed. Written down so
   the next reader does not have to re-derive it:

       390px viewport, 34rem block active (rail padding 0 16px, gap 12px)
       content width                                    358.0px
       before   brand 140.6 + divider 2 + pill 63 + 4 gaps 48 = 253.6
                left for crumbs + spacer                 104.4px
       after    brand 140.6 + divider 2        + 3 gaps 36 = 178.6
                left for crumbs + spacer                 179.4px

   `.ks3-nav-spacer` does NOT take that 75px. `flex: 1` is `1 1 0%`, so its
   base size is zero: in an over-constrained rail — which is every phone width
   here — shrink resolution is scaled by base size, the spacer contributes
   nothing, and it resolves to 0px. The freed width lands on `.ks3-crumbs`,
   which is the one item with `min-width: 0`. Where the rail is NOT
   over-constrained (a short trail nearer 700px) the crumbs are already at
   content width and the spacer growing is correct — it is what holds a
   signed-in student's `.ks3-classlink` at the right edge now that the pill is
   not there to do it.

   ⚠️ The "keep the last three children" elision STAYS, and relaxing it would
   undo the point of the cut. Showing a fourth and fifth crumb would spend the
   75px on more rungs instead of on making the two that matter legible, which
   is the opposite of what was asked for. The ruling above is unchanged: unit
   and page, elide the rest, never scroll sideways.

   This is also the first half of audit 3.3's fix. A header that no longer
   wraps is a header that no longer varies from 86px to 215px, which fixes most
   of the anchor occlusion for free — the ResizeObserver in `shared/ks3.js`
   fixes the rest, and it is the half that holds when Chemistry's and Physics'
   unit titles turn out longer than Biology's.
   ═══════════════════════════════════════════════════════════════════════ */
@media (max-width: 700px) {
  .ks3-trail > li:not(:nth-last-child(-n + 3)),
  .ks3-crumbs > *:not(:nth-last-child(-n + 3)) { display: none; }

  /* `nowrap` on the row is what turns "too long" into "elide" rather than
     "wrap to a fourth line". Everything from here down exists so the elision
     has somewhere to happen: a flex item will not shrink below its content
     without `min-width: 0`, at any depth. */
  .ks3-trail,
  .ks3-crumbs { flex-wrap: nowrap; }
  .ks3-trail > li { min-width: 0; }
  .ks3-trail-sep,
  .ks3-crumb-sep { flex: 0 0 auto; }
  .ks3-trail a,
  .ks3-trail [aria-current],
  .ks3-crumbs a,
  .ks3-crumbs [aria-current] {
    display: block;
    min-width: 0;
    max-width: 100%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  /* ⊕ MRB-330, 6 Sep 2026 — AT PHONE WIDTHS THE TRAIL WRAPS INSTEAD OF ELIDING.
     Removing the "KS3" pill above gave the trail back the 63px it was holding,
     and that nearly doubled what the crumbs get at 390px — "Acids and alkalis"
     went from 46px to 90px. It still did not FIT: the last two crumbs want
     about 240px between them and there are about 179px to give, so the header
     still read "Acids and a… › Acid + m…". Two ellipses instead of two shorter
     ellipses is not the ruling being satisfied; the ruling was to let the
     breadcrumb breathe, and a breadcrumb that names nothing has not.

     So below 420px the elision is lifted and the row is allowed a second line.
     Nothing is dropped, both names are readable, and the cost is one line of
     header height on phones only. Above 420px the elision above is unchanged —
     there the trail fits, and wrapping a rail that fits would be worse. */
  @media (max-width: 420px) {
    .ks3-trail,
    .ks3-crumbs { flex-wrap: wrap; row-gap: 2px; }
    .ks3-trail a,
    .ks3-trail [aria-current],
    .ks3-crumbs a,
    .ks3-crumbs [aria-current] {
      white-space: normal;
      /* ⚠️ `overflow: hidden` STAYS. Lifting it to `visible` here read as the
         natural partner of `white-space: normal` and cost a 320px regression:
         two lesson pages began scrolling the document sideways by 1px and 11px.
         Wrapping is what buys the legibility; the clip is what guarantees
         nothing can spill out of the rail on a narrow phone, and the two are
         independent. Measured at 320 and 390 both ways before this was
         written — see ks3_overflow.py, which is the gate that caught it. */
      text-overflow: clip;
    }
  }
  /* ⊖ SUPERSEDED by MRB-297 — the rule below replaces it. It read
       `.ks3-brand { min-width: 0; }`
     and its reasoning was that the brand "can stop being allowed to push the
     trail off the edge, which is what `flex-shrink: 0` on a 177px item does
     at 390". The reasoning was right about the trail and wrong about what
     shrinking a brand actually does.

     ⚠️ `min-width: 0` LET THE BOX SHRINK. IT DID NOT SHRINK THE WORDMARK.
     `.ks3-brand` is `overflow: visible`, so the flex algorithm squeezed the
     BOX to 52–137px while the text went on needing 177px — and painted the
     difference straight out through the divider and over the first crumb.
     Measured on all 70 physics lessons at a true 390px device-metrics
     override: 70 of 70 overprint, by +13.6px to +98.4px, median +60. A
     reader sees `MrBadm[Describi]usAI`. The same rule serves biology and
     chemistry, so it is 185 of 185 lessons, and it is chemistry's C3-03.

     ⚠️ THE TEST THAT MISSES IT, recorded so it is not repeated. The layout
     boxes never intersect, so `getBoundingClientRect()` overlap returns
     **0 of 70** and the defect reads as absent; `document.scrollWidth` is
     exactly 390 and no per-element overflow sweep fires either. Three
     auditors reported this ABSENT on that evidence and all three were
     struck. The only valid test is a `Range` over the wordmark's own TEXT
     NODE against the trail's left edge — text ink, not the box.

     THE FIX, and why it is a scale and not a hide. The brand stops being
     the thing that gives way (`flex-shrink: 0` + `min-width: max-content`,
     so overprinting is impossible by construction rather than by arithmetic)
     and instead comes down to the trail's own 17px on a phone.

     ⊕ CORRECTED 1 Sep 2026 — this paragraph used to read "the trail keeps
     the width it had — 104.4px against 107.6px before". Re-measured on
     `temperature-and-internal-energy` at 390px, from the built pages either
     side of the fix, and 107.6 does not reproduce. It is not a trail width
     at all: 107.6 and 104.4 are two rows of the audit's own overprint table
     (`records/p10.md`, "ink overflows box by") for `magnets-and-poles` and
     `magnetic-fields` — two different lessons, one column, neither of them
     before-and-after. The 104.4 the fix genuinely produces here is a
     coincidence with the second row, which is what made the misreading
     survive. What the trail actually does, and it is worth stating honestly
     because it is the cost of this rule:

         as shipped   trail BOX 192.4px, of which the wordmark paints over
                      the first 98.4px — so ~94px is legible, the parent
                      crumb is destroyed outright and the leaf crumb
                      (104.5px) is half buried
         after        trail BOX 104.4px, ALL of it legible; the parent crumb
                      renders at 30.7px and the leaf at 50.0px, both
                      ellipsised

     So the box does shrink, by 88px, and the leaf crumb halves. What goes UP
     is the part a reader can actually read: 94px to 104.4px, with nothing
     overprinted. That is the trade, stated as it measures rather than as it
     was first written.

     ⚠️ AND IT WAS MEASURED AT ONE VIEWPORT, ON ONE SUBJECT. Re-driven over
     all 185 KS3 lesson pages at three widths:

         viewport   overprint   brand    trail
           390px      0/185     140.6px  104.4px
           360px      0/185     140.6px   74.4px
           320px      0/185     140.6px   34.4px

     The overprint is gone at every width, on all three subjects — that part
     of the claim is stronger than it was stated, not weaker. But the brand is
     now a fixed, unshrinkable 140.6px at EVERY width, which is 44% of a 320px
     viewport, and the trail is whatever is left. That is why the trail is now
     identical on all 185 pages: it no longer depends on the title at all.

     ⚠️ **34.4px at 320px is about two glyphs and an ellipsis for the whole
     trail**, and nobody looked at 320 or 360 before this shipped. It is not a
     REGRESSION — the pre-fix rule overprinted at those widths too — but it is
     a real cost and it is unruled. The audit's own fallback (hide the wordmark
     below 420px) would give the trail ~140px back at 320. That is Mide's
     call, not a second unilateral design decision on top of the first: this
     block already picks 17px over 22px on his behalf, and one such choice per
     run is enough. **OPEN ITEM FOR MIDE.**

     Also measured and NOT caused here: `ks3/chemistry/mixtures-and-separation/
     chromatography.html` has `scrollWidth` 326 at a 320px viewport, before
     and after, which breaks MRB-229's "the page never scrolls sideways". It
     is the only one of the 185.

     Holding the brand at 22px and making it unshrinkable also fixes the
     overprint, but leaves the trail 68.0px — 36px less than this rule — and
     cuts the last crumb to 15px, which is the trade this rule declines.

     Design's mark is unchanged: same chevron, same wordmark, same weight,
     scaled — MRB-197 governs the composition of the mark, not its size at
     every viewport.

     ⚠️ `ks3_parity`'s row "nav brand wordmark (MRB-197)" pins 22px. Verified
     it still passes: that harness pins no viewport and lands at 756px, where
     this block does not apply and the brand computes 22px. */
  .ks3-brand {
    flex-shrink: 0;
    min-width: max-content;
    font-size: 17px;
    gap: 8px;
  }
  .ks3-brand svg { flex: 0 0 auto; width: 26px; height: 26px; }
}

/* ═══ BEGIN C1 ═══ */
/* ═══ collision-counter (c1-04 #s-bench) ═══════════════════════════════
   The LIGHT canvas frame. `.ks3-canvas-frame` is C2's DARK one — a 2px
   `--ks3-on-dark-muted` rule over a `--ks3-dark-panel` foot — and this
   block is a plain `.ks3-block`, so it takes a 2px INK rule over an
   `--ks3-inset` control strip instead. Two frames, because they sit on
   two different grounds; sharing one would put a muted rule on cream,
   where it is nearly invisible.

   ⚠️ EVERY TEXT RULE IS SELF-SCOPED `.ks3-counter …`, at (0,2,0). A bare
   `.ks3-counter-grouplabel` is (0,1,0) and loses to `.ks3-dark p` at
   (0,1,1) the day anything puts this instrument on ink. It is a LIGHT
   block today and the guard costs nothing; two separate builds have
   been bitten by exactly this.

   The three segmented groups use `.ks3-sim-seg-btn` and NOT a private
   control. Drift 4 ruled one segmented control for the key stage; a
   second copy at Design's 16px / 11px 16px is the drift the ruling
   exists to stop. Reported. */

.ks3-counter-stage {
  margin-top: 22px;
  border-radius: var(--ks3-r-card);
  border: 2px solid var(--ks3-ink);
  overflow: hidden;
  background: var(--ks3-card);
}
.ks3-counter-canvas { display: block; width: 100%; height: auto; }

.ks3-counter-controls {
  padding: 18px;
  border-top: 2px solid var(--ks3-ink);
  background: var(--ks3-inset);
}

/* Design's `[data-ctl]` (page CSS line 23). Three captioned groups that
   drop to one column below ~510px rather than squeezing three 3-way
   button rows into a phone width. */
.ks3-counter-groups {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(230px, 100%), 1fr));
  gap: 18px;
}
.ks3-counter .ks3-counter-grouplabel {
  margin: 0 0 10px;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-counter-btns {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

/* The bumps toggle takes the FULL WIDTH under the three groups, with its
   caption beside it. It is not a fourth control group and must not read
   as one: the groups change the gas, and this changes what is drawn on
   top of it. */
.ks3-counter-bumps {
  margin-top: 18px;
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  align-items: center;
}
.ks3-counter .ks3-counter-bumpnote {
  margin: 0;
  font-size: 17px;
  line-height: 1.5;
  color: var(--ks3-ink-muted);
  flex: 1 1 16rem;
}

/* The live note — a BAND panel on a 2px ink rule, which is the KEY FACT
   treatment and deliberately so: it is the sentence the bench just
   proved, not a verdict on anything the student did. */
.ks3-counter .ks3-counter-note {
  margin: 18px 0 0;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-ink);
}

/* ── evidence-bench (c1-06 #s-bench) ──
   Seven observations, judged one at a time, on a LIGHT block. Nothing here is
   scoped `.ks3-dark` and nothing may be: the two verdict grounds are cream
   tints (`--ks3-inset` and `--ks3-band`) and both resolve to mud on ink.

   ⚖️ The two-tone verdict panel is a fact about THE MODEL, never about the
   student. `--ks3-inset` behind a `--ks3-rule-strong` edge when the model
   handles it; `--ks3-band` behind `--ks3-accent` when it fails — the KEY FACT
   ground, deliberately not a red. A student who called all seven wrong sees
   exactly these panels. R3 lives in that. */
.ks3-ebench { margin-top: 20px; }
.ks3-ebench-list { display: flex; flex-direction: column; gap: 12px; }

.ks3-ebench-case {
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-option-border);
}
/* The border going to ink is the only thing commitment changes on the case,
   and it marks the CASE rather than the button — the same move `job-sort`
   makes, and for the same reason. */
.ks3-ebench-case[data-open="1"] { border-color: var(--ks3-ink); }

/* Statement left, the two calls right; one column under 720px, which is
   Design's own page rule (line 23–24). `minmax(0, 1fr)` and not `1fr`: a long
   observation must be allowed to shrink rather than push the buttons out. */
.ks3-ebench-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 16px;
  align-items: start;
}
@media (max-width: 720px) {
  .ks3-ebench-row { grid-template-columns: minmax(0, 1fr); }
}

.ks3-ebench-tag {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-ebench-text {
  margin: 6px 0 0;
  font-size: 19px;
  line-height: 1.5;
  font-weight: 600;
}

.ks3-ebench-btns { display: flex; gap: 8px; flex-wrap: wrap; }
/* C1's segmented geometry: 16px / 11px 16px in BOTH branches on all six pages.
   Drift 4 ruled 17px / 11px 17px on b1-06's single variant; six pages against
   one, and re-ruling it is §1.6 (d)'s job, not this component's. Written to
   C1's number so the bench matches the page it is on. */
.ks3-ebench-btn {
  cursor: pointer;
  font: inherit;
  font-size: 16px;
  font-weight: 700;
  padding: 11px 16px;
  min-height: var(--ks3-tap);
  border-radius: var(--ks3-r-control);
  border: 2px solid var(--ks3-option-border);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}
.ks3-ebench-btn[aria-pressed="true"] {
  border-color: var(--ks3-accent);
  background: var(--ks3-accent-tint);
}

/* 6px on the leading edge, not a full outline: the verdict reads as a
   continuation of the case it sits under rather than as a second card. */
.ks3-ebench-verdict {
  margin-top: 14px;
  padding: 16px 18px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border-left: 6px solid var(--ks3-accent);
}
.ks3-ebench-case[data-ok="1"] .ks3-ebench-verdict {
  background: var(--ks3-inset);
  border-left-color: var(--ks3-rule-strong);
}
.ks3-ebench-vlabel {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-accent-text);
}
.ks3-ebench-case[data-ok="1"] .ks3-ebench-vlabel { color: var(--ks3-ink-muted); }
.ks3-ebench-vtext { margin: 8px 0 0; font-size: 18px; line-height: 1.55; }

/* The whole-set close: the counted line in display type, then the static
   shared-cause paragraph that is the actual bridge into C2. */
.ks3-ebench-tally {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
}
.ks3-ebench-tallyline {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 24px;
  line-height: 1.25;
  letter-spacing: -.015em;
}
.ks3-ebench-cause { margin: 10px 0 0; font-size: 19px; line-height: 1.6; }

/* ── gap-test-rig (c1-01 #s-gap) ──
   ⚠️ INK-DARK, and `.ks3-dark p` is (0,1,1). Every text rule below is scoped
   to at least (0,2,0) so it wins — the defect that shipped with the zoom
   instrument on B1 and bit B2 again is exactly an unscoped instrument caption
   losing to the block it sits in.

   The drawing reuses `.ks3-canvas-frame` / `.ks3-canvas-foot` (map N12) rather
   than growing a fifth copy: Design's frame here is the same 2px muted rule on
   a card radius over a dark-panel foot, measured property for property. */

.ks3-gap { margin-top: 4px; }
/* Design's own measure on the choice list — four short answers at full column
   width read as a form, not as a question. */
.ks3-gap .ks3-options { max-width: 34rem; }

.ks3-gap-rig { margin-top: 20px; }

.ks3-gap .ks3-gap-caption {
  margin: 0 0 12px;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-gap-btns { display: flex; gap: 12px; flex-wrap: wrap; }

/* The outcome paragraph sits OUTSIDE the frame, on the block's own ink — it is
   the sentence the student takes away, not another readout on the instrument.
   All eight branches are in the document and seven are hidden. */
.ks3-gap-note { margin: 16px 0 0; }
.ks3-gap .ks3-gap-note p {
  margin: 0;
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-on-dark-body);
}

/* ══════════════════════════════════════════════════════════════════
   C1 · Particles and their behaviour (⊕ MRB-228)

   ── halving-bench (c1-01 #s-cut) ──
   ⚠️ A LIGHT instrument. Its sibling on the same page (`gap-test-rig`) is
   ink-dark, so nothing here may assume `.ks3-dark`; the frame is the light
   counterpart of `.ks3-canvas-frame` — 2px INK on the card ground, with an
   INSET foot — and it is its own class for that reason rather than a
   `[data-light]` modifier on a dark component.
   ══════════════════════════════════════════════════════════════════ */

.ks3-cut { margin-top: 22px; }

.ks3-cut-frame {
  border-radius: var(--ks3-r-card);
  border: 2px solid var(--ks3-ink);
  overflow: hidden;
  background: var(--ks3-card);
}
.ks3-cut-frame canvas { display: block; width: 100%; height: auto; }
.ks3-cut-foot {
  padding: 18px;
  border-top: 2px solid var(--ks3-ink);
  background: var(--ks3-inset);
}

/* Three readouts on one row, one per row on a phone. Design's own breakpoint:
   at 620px the third column has 100px of space and "Edge of the piece" wraps
   to three lines under a 30px number. */
.ks3-cut-readouts {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
}
@media (max-width: 620px) {
  .ks3-cut-readouts { grid-template-columns: minmax(0, 1fr); }
}

/* Both text rules are scoped past `.ks3-check p` (0,1,1) at (0,2,0) — the
   block is light today, but a shell can be re-measured and an unscoped
   instrument is how the last two builds lost their captions. */
.ks3-cut .ks3-cut-label {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-cut .ks3-cut-value {
  margin: 4px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 30px;
  line-height: 1.1;
  color: var(--ks3-ink);
}
/* ⚖️ The one accent in the readout row, and it is not a mark. "Floor" is a
   MEASUREMENT that has stopped moving, not a verdict on the student — the
   colour rides on the word, which is in the document from the start with its
   sibling hidden beside it, so no state is ever colour alone (R2). */
.ks3-cut .ks3-cut-value [data-verdict="floor"] { color: var(--ks3-accent-text); }

.ks3-cut-btns { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 18px; }
/* Spent, not gone: a control at an end of the ladder stays readable at .45,
   which is the same treatment `.ks3-scale-btn` and the budget bench take. */
.ks3-cut-btn[disabled] { opacity: .45; cursor: default; }

/* The running note. BAND on a 2px ink border — the panel treatment
   `claim-switch` uses for the same job, and deliberately not amber: this is
   the state of the model, never a wrong idea. It carries no badge, letter or
   mark, because band is also the chosen-wrong ladder ground (MRB-202) and
   anything mark-like here would read as a verdict. */
.ks3-cut-note {
  margin: 18px 0 0;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
}
.ks3-cut .ks3-cut-note p {
  margin: 0;
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-ink);
}

/* ── heating-bench (c1-03 #s-curve) ──
   A LIGHT bench. Every rule below paints on cream, because the block is a
   plain `.ks3-block` on Design's page and not a practical — the canvas fills
   itself with paper, and on an ink ground that fill reads as a hole.

   Two things here are not in the stylesheet yet and are the reason this is a
   component rather than four inline styles:

   1. A LIGHT canvas frame. `.ks3-canvas-frame` is the ink-dark wrapper the
      four C2 instruments share (2px `--ks3-on-dark-muted` on a dark foot);
      on cream, both of those resolve as a smudge. Same shape, light tokens.
   2. A LIGHT range control. `.ks3-slider` is b2-02's, and it is a dark-ground
      control: an alert thumb on a `--ks3-dark-track`. The GEOMETRY is
      deliberately identical — a 28px thumb on a 10px track — so the key
      stage has one slider in two palettes rather than two sliders; only the
      paint changes, and the 44px height is Design's own (page CSS line 25).

   ⚠️ Specificity. The paragraph rules are written two classes deep. This
   block is light so `.ks3-dark p` (0,1,1) does not reach it today — but a
   bench is exactly the component someone later drops into a practical, and
   at (0,1,0) every note in it would silently repaint to on-dark body copy.
   That has bitten two builds already. */

.ks3-hb { margin-top: 22px; }

/* `hidden` is the source of truth and it is backed up here for the same
   reason ks3.js backs it up inline: an author `display` beats the UA
   stylesheet's `[hidden] { display: none }` at any specificity, and this
   instrument holds five notes and five phase words of which exactly one of
   each may ever be readable. */
.ks3-hb [hidden] { display: none; }

.ks3-hb-frame {
  border-radius: var(--ks3-r-card);
  border: 2px solid var(--ks3-ink);
  background: var(--ks3-card);
  overflow: hidden;
}
.ks3-hb-canvas { display: block; width: 100%; height: auto; }
.ks3-hb-foot {
  padding: 18px;
  border-top: 2px solid var(--ks3-ink);
  background: var(--ks3-inset);
}

.ks3-hb .ks3-hb-scrub-label {
  display: block;
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}

/* The scrub. 44px of control around a 10px track, so the whole thing is a
   tap target and not just the thumb. `accent-color` is Design's own line and
   is kept as the paint for any engine that ignores the pseudo-elements. */
.ks3-hb-scrub {
  -webkit-appearance: none;
  appearance: none;
  display: block;
  width: 100%;
  height: var(--ks3-tap);
  margin: 6px 0 0;
  background: transparent;
  accent-color: var(--ks3-accent);
  cursor: pointer;
}
.ks3-hb-scrub::-webkit-slider-runnable-track {
  height: 10px;
  border-radius: var(--ks3-r-pill);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
}
.ks3-hb-scrub::-moz-range-track {
  height: 10px;
  border-radius: var(--ks3-r-pill);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
}
.ks3-hb-scrub::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 28px;
  height: 28px;
  margin-top: -11px;
  border-radius: 50%;
  background: var(--ks3-accent);
  border: 3px solid var(--ks3-ink);
}
.ks3-hb-scrub::-moz-range-thumb {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--ks3-accent);
  border: 3px solid var(--ks3-ink);
}
.ks3-hb-scrub:focus-visible {
  outline: var(--ks3-r-focus) solid var(--ks3-accent);
  outline-offset: 3px;
}

/* Three readouts across, one down on a phone — Design's own breakpoint
   (page CSS line 24). */
.ks3-hb-tiles {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
  margin-top: 6px;
}
@media (max-width: 620px) {
  .ks3-hb-tiles { grid-template-columns: minmax(0, 1fr); }
}
.ks3-hb .ks3-hb-tile-label {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-hb .ks3-hb-tile-value {
  margin: 4px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 30px;
  line-height: 1.1;
  color: var(--ks3-ink);
}
/* The state word takes the accent only while the state is CHANGING. It is
   `--ks3-accent-text` and not `--ks3-accent`: this is a 30px word, but the
   readout is the kind of thing a student reads at a glance from a metre
   away, and R1's large-text allowance is not an excuse to spend it. */
.ks3-hb .ks3-hb-phase[data-tone="accent"] { color: var(--ks3-accent-text); }

.ks3-hb-jumps {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 16px;
}

/* The live note under the bench: a BAND panel on a 2px ink rule — the KEY
   FACT treatment, because what it says while the thermometer is stuck is the
   fact the lesson exists to deliver. */
.ks3-hb-notes { margin-top: 18px; }
.ks3-hb .ks3-hb-note {
  margin: 0;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-ink);
}

/* ── keyed-commit (c1-06 #s-verdict, c1-03 #s-bubble) ──
   Four options, each carrying its own answer, then one panel whose first
   paragraph is the student's and whose rest is everyone's.

   ⚠️ SPECIFICITY, AND IT IS LOAD-BEARING. `.ks3-dark p` is (0,1,1); a bare
   `.ks3-keyed-reply` is (0,1,0) and LOSES, so an unscoped colour would leave
   the reply rendering in on-dark body copy — the same defect the zoom
   instrument shipped with in B1 and the muscle bench nearly repeated in B2.
   Every text rule below is scoped `.ks3-dark …`.

   The light rules under them are the fallback, not dead weight: the component
   is shared, both drawn instances happen to be ink-dark, and a third instance
   on a light block must not render a `--ks3-dark-panel` box with no readable
   text in it. */
.ks3-keyed { margin-top: 0; }
/* 36rem, which is Design's own measure on both pages. The options are answers
   rather than prose and a full-width answer button is a target the eye has to
   travel. */
.ks3-keyed-options { max-width: 36rem; }

.ks3-keyed-reveal {
  margin-top: 20px;
  padding: 24px;
  border-radius: var(--ks3-r-panel);
  /* Light fallback: the card-on-ink panel, matching `.ks3-reveal-panel`. */
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
}
.ks3-keyed-reply,
.ks3-keyed-static {
  margin: 0;
  font-size: 19px;
  line-height: 1.6;
}
/* The gaps are between PARAGRAPHS, and the sibling selectors still match a
   hidden reply — three of the four replies are `hidden` and the first static
   paragraph follows the last of them in document order, so it takes its
   14px whether or not any reply is showing. The visible reply is first in
   flow and keeps `margin: 0`. */
.ks3-keyed-reply + .ks3-keyed-static,
.ks3-keyed-static + .ks3-keyed-static { margin-top: 14px; }

/* The drawn state, on ink. */
.ks3-dark .ks3-keyed-reveal {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-on-dark-muted);
}
.ks3-dark .ks3-keyed-reply,
.ks3-dark .ks3-keyed-static { color: var(--ks3-on-dark-body); }

/* ── model-timeline (c1-06 #s-history) ──
   Five models as a wrapping row, one detail card under them. A LIGHT block;
   nothing here is scoped `.ks3-dark` and nothing may be.

   ⚠️ A THIRD CONTROL GEOMETRY, registered as its own rather than folded into
   the segmented control. `.ks3-seg-btn` and `.ks3-sim-seg-btn` are both one
   centred line at one weight; this is left-aligned, `10px 14px`, and stacks a
   mono year over a 700 name. Folding it into `seg()` would centre the two
   lines against each other and lose the left edge the row is read down. */
.ks3-mtl { margin-top: 20px; }
.ks3-mtl-steps {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 18px;
}
.ks3-mtl-step {
  cursor: pointer;
  font: inherit;
  text-align: left;
  padding: 10px 14px;
  min-height: var(--ks3-tap);
  border-radius: var(--ks3-r-control);
  border: 2px solid var(--ks3-option-border);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}
.ks3-mtl-step[aria-pressed="true"] {
  border-color: var(--ks3-accent);
  background: var(--ks3-accent-tint);
}
/* `display: block` on both spans is what makes the button two lines. Without
   it the year and the name run together and the row reads as five long
   labels rather than five dated positions. */
.ks3-mtl-year {
  display: block;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .06em;
}
.ks3-mtl-who {
  display: block;
  font-size: 16px;
  font-weight: 700;
  margin-top: 2px;
}

/* One card, five states, emit-all-show-one. Card ground on a 2px ink border —
   the ordinary panel, because the claim inside it is not a verdict on anyone. */
.ks3-mtl-card {
  padding: 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
}
.ks3-mtl-label {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ks3-accent-text);
}
/* 26px display. The claim is the model in one sentence and it is meant to be
   readable from the scroll position, which is the same argument `formula`
   makes for standing alone. */
.ks3-mtl-claim {
  margin: 8px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 26px;
  line-height: 1.25;
  letter-spacing: -.02em;
}
.ks3-mtl-body { margin: 12px 0 0; font-size: 19px; line-height: 1.6; }
/* The rule is what separates the model from the evidence that killed it. The
   line is muted and the label is not: "What broke it:" has to survive being
   skimmed. */
.ks3-mtl-broke {
  margin: 14px 0 0;
  padding-top: 14px;
  border-top: 2px solid var(--ks3-rule);
  font-size: 18px;
  line-height: 1.55;
  color: var(--ks3-ink-muted);
}
.ks3-mtl-broke strong { color: var(--ks3-ink); }

/* ═══ prediction-stack (c1-04 #s-predict) ══════════════════════════════
   Three panels on the ink-dark practical block, each a `--ks3-dark-panel`
   card on a 2px muted rule that goes to `--ks3-alert` once the student
   has it right.

   ⚠️ EVERY TEXT RULE IS SELF-SCOPED `.ks3-predict …` AT (0,2,0), AND IT
   HAS TO BE. `.ks3-dark p { color: var(--ks3-on-dark-body) }` is (0,1,1)
   and a bare `.ks3-predict-q` is (0,1,0), so unscoped the question would
   render in on-dark BODY copy instead of on-dark, and the wrong-answer
   note would lose its alert entirely. This block IS ink-dark, so it is
   not a hypothetical — it is the exact defect B1 shipped with the zoom
   instrument and B2 was bitten by again.

   The three-way buttons are `.ks3-sim-seg-btn`, whose dark branch is
   already Design's `segDark`: alert fill with ink text when pressed,
   transparent on the muted rule when not. No private control. */

.ks3-predicts {
  margin-top: 20px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.ks3-predict {
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-dark-panel);
  border: 2px solid var(--ks3-on-dark-muted);
}
/* The panel — not the option — carries the verdict, so the chosen button
   keeps the ordinary chosen treatment and nothing inside the option list
   marks the student. */
.ks3-predict[data-right="1"] { border-color: var(--ks3-alert); }

.ks3-predict .ks3-predict-q {
  margin: 0 0 12px;
  font-size: 18px;
  line-height: 1.5;
  font-weight: 600;
  color: var(--ks3-on-dark);
}

.ks3-predict-btns {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.ks3-predict .ks3-predict-note {
  margin: 12px 0 0;
  font-size: 17px;
  line-height: 1.5;
  color: var(--ks3-on-dark-body);
}
/* The one shared fallback. It sends the student back to the bench and
   gives nothing away, so it is a prompt rather than a correction — and
   the alert is the ink-dark palette's lit colour, not a red. */
.ks3-predict .ks3-predict-note[data-tone="wrong"] {
  color: var(--ks3-alert);
}

/* ── random-walk-bench (c1-05 #s-walk) ──
   A LIGHT block, and every rule below depends on that. Design draws the tank
   on cream inside a `--ks3-card` frame with a 2px INK rule; nothing here is
   scoped `.ks3-dark` and nothing here may be, or the dye goes purple-on-black
   and the two counters lose their contrast.

   ⚠️ This frame is NOT `.ks3-canvas-frame`. That one is the dark twin — a 2px
   `--ks3-on-dark-muted` rule over a `--ks3-dark-panel` foot, shared by four C2
   instruments. Same shape, two grounds, two components. */

.ks3-walk { margin-top: 22px; }

.ks3-walk-frame {
  border-radius: var(--ks3-r-card);
  border: 2px solid var(--ks3-ink);
  overflow: hidden;
  background: var(--ks3-card);
}
.ks3-walk-canvas { display: block; width: 100%; height: auto; }
.ks3-walk-foot {
  padding: 18px;
  border-top: 2px solid var(--ks3-ink);
  background: var(--ks3-inset);
}

/* Three readouts across, one under the other on a phone. The single-column
   fallback is Design's own query (page line 24), not a translation. */
.ks3-walk-readouts {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
}
@media (max-width: 620px) {
  .ks3-walk-readouts { grid-template-columns: minmax(0, 1fr); }
}
.ks3-walk-readout-label {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-walk-readout-value {
  margin: 4px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 30px;
  line-height: 1.1;
  color: var(--ks3-ink);
  /* ⊕ An addition. The two counters climb into four and five digits while a
     student is reading them, and proportional figures make the number jump
     sideways on every repaint — which reads as the instrument being unsteady
     rather than the count going up. */
  font-variant-numeric: tabular-nums;
}
/* ⚖️ THE ONE STATE CHANGE IN THE READOUT ROW. "Spread out? Yes" goes to
   accent-TEXT — the 6:1 orange, never `--ks3-accent`, which is large-text-only
   and would fail at 30px display weight on inset. It is not a verdict on the
   student and carries no mark: the tank evened out, that is all. */
.ks3-walk-readout-value[data-walk-even="1"] { color: var(--ks3-accent-text); }

.ks3-walk-controls {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 18px;
}
/* The four controls ARE `.ks3-sim-seg-btn` — the key stage's segmented
   control, reused rather than re-declared. Design's C1 draws it 1px smaller
   than the shipped rule in both branches (16px / 11px 16px against 17px /
   11px 17px) and the map reopens drift 4 over exactly that, on all six pages.
   Until it is re-ruled, a fourth private copy of this button would make the
   ruling a four-place change instead of a one-place one. `.ks3-walk-btn` adds
   nothing but a selector to measure and to hang any future correction on. */
.ks3-walk-btn { flex: 0 0 auto; }

/* The live note. `--ks3-band` on a 2px ink border — the KEY FACT treatment,
   which is right: this is the bench telling the student what they are looking
   at, not marking anything. All four branches live in here and one is shown. */
.ks3-walk-note {
  margin: 18px 0 0;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
}
.ks3-walk-note p {
  margin: 0;
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-ink);
}

/* The commit gate, scoped to THIS block and no further. `r_bench_gate` emits
   the same markup on every bench in the key stage and `.ks3-benchgate` sets
   only a margin, because B2's three gates sit on ink where `.ks3-commit`'s
   amber is legible. This one sits on cream, where amber is not: Design paints
   it `--ks3-accent-text` and draws the panel that carries it (page 121–122).
   Scoping to `.ks3-walk-block` keeps the shared component shared. */
.ks3-walk-block .ks3-benchgate {
  margin-top: 20px;
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-walk-block .ks3-benchgate .ks3-commit {
  margin: 0 0 12px;
  color: var(--ks3-accent-text);
}

@media (max-width: 34rem) {
  .ks3-walk-foot { padding: 16px 14px; }
  /* Four controls at 44px tall stack rather than shrink — a tap target that
     has been squeezed to fit is not a tap target. */
  .ks3-walk-controls { gap: 10px; }
}

/* ── scale-cards (c1-05 #s-scale) ──
   Ink-dark and STATIC. Three cards, each a distance, a time and what that
   costs biology, closed by the rule they add up to.

   ⚠️ SPECIFICITY. `.ks3-dark p { color: var(--ks3-on-dark-body) }` is (0,1,1)
   and a bare `.ks3-scard-time` is (0,1,0), so every <p> rule below is scoped
   `.ks3-dark`. The one that would visibly break is the 28px display TIME:
   unscoped it loses its `--ks3-on-dark` and drops to body colour, which reads
   as a caption rather than the answer. This has bitten two separate builds. */

.ks3-scards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 12px;
  margin-top: 20px;
}

/* A panel nested inside an ink-dark block: `--ks3-dark-panel` on the muted
   rule, never the ground the block is already painted in. */
.ks3-scard {
  padding: 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-dark-panel);
  border: 2px solid var(--ks3-on-dark-muted);
}

/* ⚑ AMBER FOR A BODY LABEL, and the map flags it rather than resolving it
   (§5.5.2). Amber on ink has meant "control" since B1 and "misconception" as
   a block treatment; this is neither. Design drew it, the page wins, and the
   parity row pins the value so a re-ruling shows up as a failure rather than
   as a quiet repaint. */
.ks3-dark .ks3-scard-distance {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ks3-alert);
}

/* The answer to the question the label asks. Display type, full on-dark. */
.ks3-dark .ks3-scard-time {
  margin: 8px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 28px;
  line-height: 1.15;
  color: var(--ks3-on-dark);
}

.ks3-dark .ks3-scard-text {
  margin: 10px 0 0;
  font-size: 17px;
  line-height: 1.5;
  color: var(--ks3-on-dark-body);
}

/* The closing line — the rule the three cards add up to. Body size, not card
   size: it is the paragraph the panel is FOR. */
.ks3-dark .ks3-scards-close {
  margin: 18px 0 0;
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-on-dark-body);
}

@media (max-width: 34rem) {
  .ks3-scard { padding: 18px 16px; }
}

/* ── sort-cards (c1-03 #s-think) ──
   The sorter lives INSIDE the amber misconception shell, so every card is a
   cream card on an amber ground. Nothing here is amber: amber is the wrong
   IDEA being confronted, and these cards are the student's working.

   ⚠️ Specificity, twice over. `.ks3-misconception > p` is (0,1,1) and only
   reaches DIRECT children, so it does not touch the card's paragraphs today
   — but the text rules below are written two classes deep anyway, because a
   descendant rule on this shell, or the day someone drops this sorter into a
   dark block, would repaint every note in it and nothing would fail.

   ⚖️ THE ONE MARKED CARD IN C1. Design draws the card's border in the accent
   when the word fits and in ink when it does not (page lines 767–768), and
   the note in ink or accent-text to match. It is carried because the page
   wins over the engine, and it is expressed against ONE attribute so a
   ruling that R3 reaches this component is a two-line change here and
   nowhere else. Note what it is NOT: never the ok family, never a tick,
   never a cross — it cannot be mistaken for the ladder's verdict, and the
   "wrong" state is exactly the neutral ink border a decided `job-sort` row
   already takes. */

.ks3-sortcards { margin-top: 18px; }
.ks3-sortcards [hidden] { display: none; }

.ks3-sortcards-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 12px;
}

.ks3-sortcards-card {
  padding: 18px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-option-border);
}
.ks3-sortcards-card[data-verdict="right"] { border-color: var(--ks3-accent); }
.ks3-sortcards-card[data-verdict="wrong"] { border-color: var(--ks3-ink); }

.ks3-sortcards .ks3-sortcards-text {
  margin: 0 0 12px;
  font-size: 18px;
  line-height: 1.45;
  font-weight: 600;
  color: var(--ks3-ink);
}

.ks3-sortcards-opts { display: flex; gap: 8px; flex-wrap: wrap; }

.ks3-sortcards .ks3-sortcards-note {
  margin: 12px 0 0;
  font-size: 16px;
  line-height: 1.5;
  color: var(--ks3-ink);
}
/* The correction reads in accent-text — 6.0:1, the only orange the key stage
   allows under 24px. */
.ks3-sortcards .ks3-sortcards-note[data-note="wrong"] {
  color: var(--ks3-accent-text);
}

/* The whole-set summary: a card panel on a 2px ink rule, the same treatment
   as the two-paragraph reveal, because that is what it is. */
.ks3-sortcards-close {
  margin-top: 20px;
  padding: 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
}
.ks3-sortcards .ks3-sortcards-close p {
  margin: 0;
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-ink);
}
.ks3-sortcards .ks3-sortcards-close p + p { margin-top: 14px; }

/* ── state-bench (c1-02 #s-bench) ──
   A LIGHT block, and it must stay one. c1-02 is the only C1 lesson with no
   dark `practical` (map §2.3): its only ink grounds are the hook and the
   keynote, and the particle drawing is cream-on-cream. Nothing below is
   scoped `.ks3-dark` because nothing here may ever land on ink.

   ⚠️ SPECIFICITY. The note's text rule is `.ks3-sbench .ks3-sbench-note-text`
   at (0,2,0) rather than a bare class at (0,1,0). `.ks3-dark p` is (0,1,1) and
   has already beaten a bare instrument class twice in this key stage; the
   block cannot sit on ink today, and this is what makes that still true if
   somebody moves it. */

.ks3-sbench { margin-top: 22px; }

/* Design draws the gate as a real INSET PANEL on a 2px ink border with the
   commit line in accent-text (page lines 122–123). The shipped `.ks3-benchgate`
   is a bare margin, which is right for B2's and C2's gates — so this is scoped
   to this block rather than pushed onto the shared class, and no page Mide has
   already approved moves. */
.ks3-sbench-block .ks3-benchgate {
  margin-top: 20px;
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-sbench-block .ks3-benchgate .ks3-commit {
  margin: 0 0 12px;
  color: var(--ks3-accent-text);
}

/* The frame is the LIGHT twin of `.ks3-canvas-frame`: card ground, 2px INK
   outline, inset control strip. It is not that component with a token swapped
   — `.ks3-canvas-frame` is 2px `--ks3-on-dark-muted` over `--ks3-dark-panel`
   and exists to sit inside an ink block. Two frames, two grounds, and neither
   is a variant of the other. */
.ks3-sbench-frame {
  border-radius: var(--ks3-r-card);
  border: 2px solid var(--ks3-ink);
  overflow: hidden;
  background: var(--ks3-card);
}
.ks3-sbench-frame canvas { display: block; width: 100%; height: auto; }

.ks3-sbench-foot {
  padding: 18px;
  border-top: 2px solid var(--ks3-ink);
  background: var(--ks3-inset);
}
.ks3-sbench-group + .ks3-sbench-group { margin-top: 18px; }
.ks3-sbench-grouplabel {
  margin: 0 0 10px;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-sbench-row {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  align-items: center;
}

/* Design's own `seg()` (page lines 605–609), reproduced rather than routed
   through `.ks3-seg-btn`, and the difference is not cosmetic in either
   dimension:

     * SIZE — C1 draws 16px / 11px 16px in BOTH branches on all six pages;
       `.ks3-seg-btn` is 17px / 11px 17px under drift 4's ruling, which was
       taken on b1-06's single variant. §1.6 (d) reopens that ruling on a
       six-pages-against-one count and it is not this build's to settle, so
       this block carries C1's own numbers under its own class and nothing
       shipped moves.
     * COLOUR — C1's chosen state keeps `--ks3-ink` text; `.ks3-seg-btn` goes
       to `--ks3-accent-text`. On this bench the state buttons are a picker
       and not an answer, and accent text on the chosen one would read as a
       verdict (R3 / MRB-196 R10).

   R2: the chosen state carries `aria-pressed` and, on the two toggles, its
   own pair of words — never colour alone. */
.ks3-sbench-seg {
  cursor: pointer;
  font: inherit;
  font-size: 16px;
  font-weight: 700;
  padding: 11px 16px;
  min-height: var(--ks3-tap);
  border-radius: var(--ks3-r-control);
  border: 2px solid var(--ks3-option-border);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}
.ks3-sbench-seg[aria-pressed="true"] {
  border-color: var(--ks3-accent);
  background: var(--ks3-accent-tint);
}

/* The band panel under the frame carrying whichever of the eight notes is
   live. All eight are in the document; seven are `hidden`. */
.ks3-sbench-note {
  margin: 18px 0 0;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
}
.ks3-sbench .ks3-sbench-note-text {
  margin: 0;
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-ink);
}

/* R7 / Law 9 — the canvas animates, so the student needs a stop that is not an
   OS setting. It has one, in words, in the Instruments group ("Freeze the
   motion"), and `wireStateBench` also scales the rate under
   `prefers-reduced-motion` rather than stopping — asked every frame, so this
   rule is documentation of a JS behaviour rather than a duplicate of it. */

/* ── state-matrix (c1-02 #s-matrix) ──
   Design carries this in a page-local <style> (lines 23–26) because its tool
   has nowhere else to put it; here it is a component, so the 700px breakpoint
   and the mono column heads live with the table rather than with the page.

   A LIGHT block. Nothing is scoped `.ks3-dark` — c1-02's only ink grounds are
   the hook and the keynote — and the row text rule is scoped `.ks3-smatrix …`
   at (0,2,0) for the same reason the bench's note is: `.ks3-dark p` is (0,1,1)
   and beats a bare instrument class. */

.ks3-smatrix { margin-top: 18px; }

/* The table can be wider than a phone, so it scrolls INSIDE its own box and
   the page body never scrolls sideways. The border and the radius are on the
   scroller, so the rounded outline survives the overflow. */
.ks3-smatrix-scroll {
  overflow-x: auto;
  /* ⊕ MRB-280, 21 Aug 2026 — CONTAINMENT, not clipping. A scroller whose
     `position` is static is NOT the containing block for an absolutely
     positioned descendant, so `overflow-x: auto` cannot clip one: the
     descendant resolves against an ancestor OUTSIDE the scroller and
     extends the DOCUMENT instead. That is what pushed c5-04 sideways by
     140px at 390px (see `.ks3-rgrid-scroll` below), and it was the
     invisible `.ks3-sr-only` labels doing it. Every horizontal scroller
     in the key stage now declares itself a containing block, so the
     shape cannot come back one figure at a time. `overflow: hidden` is
     NOT the fix — measured — because the problem is not clipping. */
  position: relative;
  border-radius: var(--ks3-r-panel);
  border: 2px solid var(--ks3-ink);
}
.ks3-smatrix-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 18px;
}
.ks3-smatrix-table th,
.ks3-smatrix-table td {
  border: 2px solid var(--ks3-ink);
  padding: 12px 14px;
  text-align: left;
}
/* Column heads and row heads take the same mono uppercase treatment on band —
   this is a matrix, so the first column is a head as much as the first row is. */
.ks3-smatrix-table thead th,
.ks3-smatrix-row > th {
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  background: var(--ks3-band);
}
.ks3-smatrix-row { background: var(--ks3-card); }
.ks3-smatrix .ks3-smatrix-table td { color: var(--ks3-ink); }

/* ⚖️ THE LIT ROW. Accent TINT, on the row and on its head, and never the
   accent fill — this is "the bench is showing you this one", not a verdict and
   not an answer (R3 / MRB-196 R10). It is also never amber: amber is a wrong
   idea being confronted.

   The tint is not the only signal. `r_state_matrix` puts `aria-current="true"`
   on the lit row's head, because Design signals this with colour alone and the
   footnote under the table promises the student a highlight (R2). */
.ks3-smatrix-row[data-lit="1"],
.ks3-smatrix-row[data-lit="1"] > th { background: var(--ks3-accent-tint); }

.ks3-smatrix .ks3-smatrix-foot {
  margin: 16px 0 0;
  font-size: 18px;
  color: var(--ks3-ink-muted);
}

@media (max-width: 700px) {
  .ks3-smatrix-table { font-size: 16px; }
  .ks3-smatrix-table th,
  .ks3-smatrix-table td { padding: 9px 10px; }
}
/* ═══ END C1 ═══ */

/* ═══ BEGIN B2 ═══ */
/* ── arm-lever (b2-04 #s-bench) — the forearm rig ──
   Append to the B2 group in shared/ks3.css, after the two canvas benches
   (`.ks3-joint-*` / `.ks3-muscle-*`, ~line 4430).

   ⚠️ EVERY <p> AND <label> RULE BELOW IS SCOPED `.ks3-dark …`, AND IT HAS TO
   BE. This instrument's only drawn instance sits on `ks3-block ks3-dark
   ks3-practical`. `.ks3-dark p { color: var(--ks3-on-dark-body) }` is (0,1,1)
   and a bare `.ks3-lever-tile-label` is (0,1,0), so an unscoped colour LOSES
   and the mono uppercase caption renders in on-dark body copy — legible, so
   nobody reads it as a bug, and wrong, so the three measured tiles stop
   reading as captions over values. That is the exact defect B1 shipped with
   the zoom instrument and B2 was bitten by again on the joint bench, and the
   layer-C assertions are what found it both times.

   The frame, the stage and the tiles deliberately match `.ks3-joint-*` value
   for value. They are drawn differently and framed identically, and a third
   copy of the frame is the drift a shared stylesheet exists to stop — but
   they are NOT merged into one selector list, because the joint bench is a
   shipped, approved page and a shared rule makes every future change to this
   rig a change to that one. */

.ks3-lever-controls {
  margin-top: 24px;
  display: flex;
  flex-wrap: wrap;
  gap: 20px 30px;
}
/* 240px basis with a 220px floor: two controls sit side by side on a tablet
   and all three do on a laptop, and none of them ever gets narrower than the
   slider's own usable travel. */
.ks3-lever-control { flex: 1 1 240px; min-width: 220px; }
.ks3-lever-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.ks3-dark .ks3-lever-label {
  display: block;
  margin: 0 0 9px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
/* The live value beside its own label, in the alert yellow. The same treatment
   the joint bench gives its angle readout, because they are the same thing:
   the number the control is currently set to. */
.ks3-dark .ks3-lever-value {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 17px;
  font-weight: 500;
  color: var(--ks3-alert);
}
/* `.ks3-slider` is full-width by default, which is right inside the joint
   bench's own control strip and wrong in a three-column row: a 60rem block
   would give the mass slider a 20rem travel and make 0.5 kg a pixel. */
.ks3-lever-slider { max-width: 18rem; }
.ks3-lever-tabs { display: flex; gap: 9px; flex-wrap: wrap; }

.ks3-lever-stage {
  margin-top: 20px;
  border-radius: var(--ks3-r-card);
  border: 2px solid var(--ks3-on-dark-muted);
  overflow: hidden;
}
.ks3-lever-canvas { display: block; width: 100%; height: auto; }

.ks3-lever-tiles {
  margin-top: 18px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(190px, 100%), 1fr));
  gap: 12px;
}
.ks3-lever-tile {
  padding: 15px 17px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-dark-panel);
}
.ks3-dark .ks3-lever-tile-label {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
/* ⚖️ TWO TILE TREATMENTS, AND THE SPLIT IS THE GATE. The three MEASURED tiles
   are mono 25px — they hold numbers the rig is giving you. The fourth is
   19px/700 prose type, because until the meter is fitted it holds a sentence
   ("not measured — you work it out") and a sentence set in a 25px mono
   readout face reads as a broken number. It keeps the prose treatment after
   the meter is fitted too: the force is the student's own answer being
   confirmed, not a fourth thing the rig measured for them. */
.ks3-dark .ks3-lever-tile-value {
  margin: 5px 0 0;
  font-size: 19px;
  font-weight: 700;
  line-height: 1.35;
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-lever-tile-value.ks3-lever-tile-mono {
  font-family: var(--ks3-font-mono);
  font-size: 25px;
  font-weight: 500;
  line-height: 1.2;
}

.ks3-lever-foot {
  margin-top: 18px;
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}
/* One-way, and the spent state says so: the button carries its own second
   word ("Meter reading shown") as well as the dim, because R2 forbids colour
   alone as a state. */
.ks3-lever-meter[disabled] { opacity: .5; cursor: default; }
.ks3-dark .ks3-lever-note {
  margin: 0;
  font-size: 18px;
  line-height: 1.55;
  color: var(--ks3-on-dark-body);
}

/* ── cover-triangle · TRIANGLE variant, widened for b2-04 ──
   Append below the existing `MRB-204 step 2 — the triangle` group in
   shared/ks3.css (~line 2990). Nothing above it changes.

   ⚠️ EVERY RULE HERE IS SCOPED `[data-tri-layout="row"]` OR NAMES A CLASS
   THAT DID NOT EXIST. That is the byte-identity guarantee for b1-02, made
   structurally rather than by care: b1-02's triangle emits no
   `data-tri-layout` attribute and no `.ks3-tri-result`, `.ks3-tri-units` or
   `.ks3-tri-condition` element, so not one declaration below can reach it.

   The triangle sits on the CARD ground inside a `.ks3-formula` block, never
   on ink, so none of this needs the `.ks3-dark …` scoping the benches do. */

/* Design draws the figure and the reading side by side and lets them stack on
   a narrow screen — the reading is a sentence about the figure, and putting a
   paragraph 260px under a drawing loses that. FLEX, not grid: the stack point
   is the SVG's own 260px plus the side panel's 260px minimum, which a grid
   would need a query to express. */
.ks3-triangle[data-tri-layout="row"] {
  /* The head stays centred with the block; only the body becomes a row. */
  text-align: center;
}
.ks3-triangle[data-tri-layout="row"] .ks3-tri-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 34px 52px;
  margin-top: 22px;
}
.ks3-triangle[data-tri-layout="row"] .ks3-tri-svg {
  flex: 0 0 auto;
  margin: 0;
}
.ks3-triangle[data-tri-layout="row"] .ks3-tri-side {
  flex: 1 1 320px;
  min-width: 260px;
  text-align: left;
}
/* The buttons belong to the reading, not to the figure, so they lead the
   side column left-aligned rather than sitting centred under the triangle. */
.ks3-triangle[data-tri-layout="row"] .ks3-tri-btns {
  margin-top: 0;
  justify-content: flex-start;
}

/* ⊕ WIDENED, and it is a correction as much as an addition. `.ks3-tri-label`
   is 22px, which is right for b1-02's three- to five-character words
   ("total", "eye", "obj") and far too small for a single letter: a 22px "T"
   inside a 260px triangle reads as a stray mark rather than as the subject of
   the block. Design draws these at 62px in a 300-wide box — about 54px here —
   and 44px is what fits inside the derived 52px cover box with its corner
   radius, so the cover still hides the letter completely. */
.ks3-triangle[data-tri-layout="row"] .ks3-tri-label { font-size: 44px; }

/* The ARRANGEMENT, in display type. This is the line a student reads off the
   page and writes down; the sentence under it says why it is that line. */
.ks3-tri-result {
  margin: 18px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 30px;
  line-height: 1.2;
  letter-spacing: -.02em;
  color: var(--ks3-ink);
}
/* ⚠️ The row variant's note is a BARE PARAGRAPH, not the inset panel b1-02
   takes. A boxed panel is right when the sentence is the only thing under the
   figure; here it is the second of five stacked blocks in a column and a box
   around one of them would read as a callout. Overrides only inside the row. */
.ks3-triangle[data-tri-layout="row"] .ks3-tri-note {
  margin: 12px 0 0;
  max-width: none;
  padding: 0;
  background: none;
  border: 0;
  border-radius: 0;
  font-size: 19px;
  line-height: 1.55;
}
.ks3-triangle[data-tri-layout="row"] .ks3-tri-close {
  margin: 14px 0 0;
  max-width: none;
}
/* The unit legend. Mono, because these are symbols and their units, and the
   1.9 line-height is what keeps three `<br>`-joined lines readable as a list
   without being one. */
.ks3-tri-units {
  margin: 16px 0 0;
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
  line-height: 1.9;
  color: var(--ks3-ink-body);
}
/* ⚖️ The balanced condition, set apart in display type because it is not a
   fourth arrangement of T, F and d — it is the statement that makes every
   question on the page solvable. Subscript ₁ and ₂ land here; both are in the
   display face's subset, and the font gate is what keeps that true. */
.ks3-tri-condition {
  margin: 16px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 21px;
  line-height: 1.4;
  color: var(--ks3-ink);
}

/* ── lever-steps (b2-04 #s-build) — MRB-204 step 4, on the student's own rig ──
   Append to the B2 group in shared/ks3.css, after `.ks3-lever-*`.

   The block sits on the INSET ground inside a light `check` shell, so nothing
   here needs the `.ks3-dark …` scoping the rig above it does — with ONE
   exception, marked below: the reveal panel inverts to ink inside a light
   block, and everything inside it is scoped through `.ks3-lstep-reveal`
   rather than through `.ks3-dark`, because that class is not on this section.

   ⚠️ The panels, the option buttons, the number field and the unit select are
   deliberately the same values as `.ks3-pick-*` (c2-06's `#s-build`). Two
   MRB-204 step-4 blocks that look different would teach a student that the
   two are different kinds of task, and they are the same task on two
   subjects. They are NOT merged into one selector list: c2-06 is a shipped,
   approved page and a shared rule makes every future change here a change
   there. `.ks3-sr-only` and `.ks3-sim-units` ARE reused, because those are
   already shared helpers rather than one block's chrome. */

.ks3-lstep { margin-top: 22px; }
.ks3-lstep-panels { display: flex; flex-direction: column; gap: 18px; }
/* Card on the option border, standing off the inset ground the block sits on
   — the three commitments are foreground and the block is their backing. */
.ks3-lstep-panel {
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-option-border);
}
.ks3-lstep-label {
  margin: 0 0 12px;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
/* ⊕ The question. c2-06's panels carry only the mono step label; Design gives
   each of these three a sentence saying what to do — "Which line says that?",
   "Put your rig's numbers in, with the distances in metres." — and without a
   slot for it the panels would be three unexplained option lists. */
.ks3-lstep-q { margin: 0 0 12px; font-size: 19px; font-weight: 600; }

/* ⚖️ STACKED AND LEFT-ALIGNED. These are candidate EQUATIONS, and centring
   three lines of mono maths makes them unscannable — the eye needs the `F ×`
   of every option to start in the same column to see what differs. */
.ks3-lstep-opts { display: flex; flex-direction: column; gap: 9px; }
.ks3-lstep-opt {
  font: inherit;
  font-family: var(--ks3-font-mono);
  font-size: 16px;
  font-weight: 500;
  text-align: left;
  padding: 11px 16px;
  min-height: var(--ks3-tap);
  border-radius: var(--ks3-r-control);
  border: 2px solid var(--ks3-option-border);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
  cursor: pointer;
}
/* R2/R3 — the chosen state carries `aria-pressed` as well as the border, and
   it is CHOSEN and not correct: no green, no mark, and nothing here is ever
   `is-correct`. Only the mastery ladder marks. */
.ks3-lstep-opt[aria-pressed="true"] {
  border-color: var(--ks3-accent);
  background: var(--ks3-accent-tint);
}
/* Locked once the model is on screen: a pick changed after reading the answer
   is not a pick. */
.ks3-lstep-opt[disabled] { opacity: .45; cursor: default; }

.ks3-lstep-answer {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  align-items: center;
}
.ks3-lstep-input {
  font: inherit;
  font-family: var(--ks3-font-mono);
  font-size: 22px;
  font-weight: 500;
  width: 8.5rem;
  padding: 12px 14px;
  min-height: var(--ks3-tap);
  border-radius: var(--ks3-r-option);
  border: 2px solid var(--ks3-option-border);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}
.ks3-lstep-unit { font-size: 18px; }
.ks3-lstep-input[disabled],
.ks3-lstep-unit[disabled] { opacity: .55; }

.ks3-lstep-foot {
  margin-top: 20px;
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}
.ks3-lstep-btn[disabled] { opacity: var(--ks3-dim-spent); cursor: default; }
.ks3-lstep-progress {
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
  color: var(--ks3-ink-muted);
}

/* ── the model answer, on INK inside a light block ──
   The same inversion the joint bench's verdict and c2-06's model take: the
   student's own working has just been committed on cream, and the worked
   version arrives on a different ground so the two cannot be confused for
   each other at a glance. */
.ks3-lstep-reveal {
  margin-top: 20px;
  padding: 26px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ink);
  color: var(--ks3-on-dark);
}
.ks3-lstep-reveal .ks3-lstep-revealhead {
  margin: 0 0 16px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ks3-alert);
}
.ks3-lstep-step { display: flex; gap: 14px; align-items: flex-start; }
/* The rule between steps belongs to the step BELOW it, so the first step has
   none — otherwise the panel opens with a hairline under its own heading. */
.ks3-lstep-step + .ks3-lstep-step {
  margin-top: 16px;
  padding-top: 16px;
  border-top: 2px solid var(--ks3-dark-rule);
}
/* ⚠️ ALERT ON INK, not accent. The worked example upstairs chips its four
   steps in `--ks3-accent-text` on the inset ground; this panel is ink, where
   the accent does not carry, and the alert yellow is the pair Design uses for
   every emphasis inside an ink panel in this unit. Same four letters, same
   four labels, two grounds. */
.ks3-lstep-chip {
  flex: 0 0 34px;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 10px;
  background: var(--ks3-alert);
  color: var(--ks3-ink);
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 18px;
}
.ks3-lstep-stepbody { flex: 1 1 auto; min-width: 0; }
.ks3-lstep-reveal .ks3-lstep-steplabel {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-lstep-reveal .ks3-lstep-stepline {
  margin: 5px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 25px;
  line-height: 1.25;
  letter-spacing: -.02em;
  color: var(--ks3-on-dark);
  /* The lines are equations and can outrun a narrow column; they scroll
     inside themselves rather than pushing the panel wide. */
  overflow-wrap: break-word;
}
.ks3-lstep-reveal .ks3-lstep-stepnote {
  margin: 6px 0 0;
  font-size: 18px;
  line-height: 1.55;
  color: var(--ks3-on-dark-body);
}
/* ⚖️ The closing line quotes the student's own answer back beside the worked
   one. It takes the same body treatment as a step note and NOT a verdict
   colour, because it is a comparison the student makes and never a mark the
   page makes. */
.ks3-lstep-reveal .ks3-lstep-close {
  margin: 18px 0 0;
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-on-dark-body);
}

/* ── meter-compare (b2-04 #s-meters) — three groups, three readings each ──
   Append to the B2 group in shared/ks3.css, after `.ks3-lstep-*`.

   A light `check` block on the DEFAULT card ground, so nothing here needs the
   `.ks3-dark …` scoping the rig does — and it must not acquire it: `#s-build`
   directly above sits on the inset ground and this one does not, which is
   Design's own measured difference between the two light blocks. */

.ks3-meters { margin-top: 0; }
/* 34rem, Design's own measure. These options are three candidate ORDERINGS
   read against each other, and a full-width answer button on a 60rem column
   is a target the eye has to travel to compare with the one above it. */
.ks3-meters-commit .ks3-options { max-width: 34rem; }

.ks3-meters-reveal { margin-top: 20px; }
/* Auto-fit rather than three fixed columns: at 1450 N the leg-press card
   carries the widest readings row on the page, and a fixed grid would either
   wrap that line or squeeze the other two. The `min()` keeps a single card
   from overflowing a phone. */
.ks3-meters-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(230px, 100%), 1fr));
  gap: 14px;
}
/* Card on a 2px INK border, not the option border the commit buttons take.
   These are not answers — they are the measurements, and the heavier rule is
   what separates data from a control. */
.ks3-meters-card {
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
}
.ks3-meters-name {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
/* ⚖️ THE THREE READINGS ARE MONO AND THEY ARE NOT SMALL PRINT. They are the
   evidence the mean is drawn from, and setting them as a caption under the
   headline number would teach that the mean is the measurement and the
   spread is a footnote — which is the misconception the closing band exists
   to break. */
.ks3-meters-readings {
  margin: 8px 0 0;
  font-family: var(--ks3-font-mono);
  font-size: 17px;
  font-weight: 500;
  line-height: 1.5;
  color: var(--ks3-ink-body);
}
.ks3-meters-mean {
  margin: 10px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 30px;
  letter-spacing: -.02em;
  color: var(--ks3-ink);
}
/* The label under the headline says what the headline IS. Without it a 305
   in display type reads as "the strength of a hand grip" rather than as one
   summary of three disagreeing pulls. */
.ks3-meters-meanlabel {
  margin: 4px 0 0;
  font-size: 16px;
  color: var(--ks3-ink-muted);
}
/* The band closes the block, on the same ground a key fact takes but with no
   shadow — it is the conclusion drawn from the three cards above it rather
   than a standing law, and the accent shadow is what marks the difference. */
.ks3-meters-close {
  margin: 18px 0 0;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-ink);
}
/* ═══ END B2 ═══ */

/* ═══ BEGIN B3 ═══ */
/* ── band-commit (b3-01 #s-plate) — seven commitments, one reveal ──
   Append to shared/ks3.css in a new "B3 · Nutrition and digestion" group,
   after the B2 rules.

   ⚠️ SPECIFICITY, AND IT IS LOAD-BEARING ON EVERY RULE BELOW.
   `.ks3-dark p` is (0,1,1) and `.ks3-dark .ks3-eyebrow` is (0,2,0); a bare
   `.ks3-plate-note` is (0,1,0) and LOSES to the first of those. This block is
   on ink on Design's page and so is every other flagship in B3, so an unscoped
   colour here does not fail loudly — it renders the note in `--ks3-on-dark-body`
   on a CREAM panel, which is a pale grey sentence on cream and is invisible to
   anyone reading the stylesheet. That is the defect B1's zoom instrument
   shipped with and B2's muscle bench nearly repeated. Every text rule below is
   scoped `.ks3-dark …` or tighter.

   The light rules under each are the fallback and are not dead weight: the
   component is shared, the one drawn instance is ink-dark, and a second
   instance on a light block must not render a `--ks3-dark-panel` row with no
   readable text in it. */

.ks3-plate { margin-top: 22px; }
.ks3-plate-rows {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 11px;
}
/* The resting row carries a TRANSPARENT border of the same width as the lit
   one. Adding the border on reveal instead would move all seven rows down by
   4px at the moment the answers arrive, which reads as the page flinching. */
.ks3-plate-row {
  padding: 17px 19px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid transparent;
}
.ks3-plate-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
}
.ks3-plate-name { margin: 0; font-size: 19px; font-weight: 700; }
.ks3-plate-hint {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
  color: var(--ks3-ink-muted);
}
.ks3-plate-bands { margin-top: 11px; display: flex; gap: 9px; flex-wrap: wrap; }
/* Design's own segmented control, at the sizes she drew for this block:
   16px/700 in a 44px tap target. Same shape as `.ks3-sim-seg-btn`, deliberately
   NOT that class — these are seven independent three-way pickers inside one
   list, not a sim's control strip, and sharing the class would tie the two
   together for a resemblance rather than for a reason. */
.ks3-plate-band {
  cursor: pointer;
  font: inherit;
  font-size: 16px;
  font-weight: 700;
  padding: 10px 15px;
  min-height: var(--ks3-tap);
  border-radius: var(--ks3-r-control);
  border: 2px solid var(--ks3-option-border);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}
.ks3-plate-band[aria-pressed="true"] {
  background: var(--ks3-accent-tint);
  border-color: var(--ks3-accent);
}
/* Locked, not marked. Once the answers are open every band button stops
   responding; the CHOSEN one keeps exactly the treatment it had and the other
   two dim. Nothing here says whether the choice was right — that is the row's
   job and the why panel's, and it is the whole of R3 in this instrument. */
.ks3-plate-band[disabled] { cursor: default; }
.ks3-plate-band[disabled][aria-pressed="false"] { opacity: var(--ks3-dim-spent); }
.ks3-plate-why {
  margin-top: 12px;
  padding: 14px 16px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
}
.ks3-plate-real {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ks3-accent-text);
}
.ks3-plate-note { margin: 7px 0 0; font-size: 18px; line-height: 1.55; }
.ks3-plate-foot {
  margin-top: 20px;
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}
.ks3-plate-open[disabled] { opacity: var(--ks3-dim-spent); cursor: default; transform: none; }
.ks3-plate-count {
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
  color: var(--ks3-ink-muted);
}
.ks3-plate-verdict {
  margin-top: 22px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
}
.ks3-plate-vlabel {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ks3-accent-text);
}
.ks3-plate-vhead {
  margin: 10px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 27px;
  line-height: 1.18;
  letter-spacing: -.025em;
}
.ks3-plate-vwhy { margin: 12px 0 0; font-size: 19px; line-height: 1.6; }

/* ── the drawn state: on ink ──────────────────────────────────────── */

.ks3-dark .ks3-plate-row { background: var(--ks3-dark-panel); }
.ks3-dark .ks3-plate-name { color: var(--ks3-on-dark); }
.ks3-dark .ks3-plate-hint { color: var(--ks3-on-dark-muted); }
/* Design's dark pair, identical to `.ks3-sim-seg-btn`'s: the lit state is the
   alert yellow with ink text, the resting state transparent on the muted rule.
   ⚖️ AMBER HERE IS "CHOSEN", NOT "WRONG". ⊕ SUPERSEDED 30 Aug 2026 — selection is now the ACCENT (see the platform block at `.ks3-dark .ks3-option`); this paragraph is kept for its reasoning, not its colour.

   On an ink ground it is the only
   colour in the system with enough lift to read as a pressed control, and
   Design uses it that way on all three B3 dark blocks. Nothing in this
   instrument uses it to mark a mistake. */
.ks3-dark .ks3-plate-band {
  border-color: var(--ks3-on-dark-muted);
  background: transparent;
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-plate-band[aria-pressed="true"] {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-accent);
  color: var(--ks3-on-dark);
}
/* ⊕ CORRECTED FROM THE SHIPPED STYLESHEET, and reported. `.ks3-reveal-btn` is
   `background: var(--ks3-ink)` on a `border: 2px solid var(--ks3-ink)`, which
   on a `.ks3-dark` block whose own ground IS `--ks3-ink` paints an invisible
   button — Design's page reads as a line of floating text where the control
   should be. The inverse of the light treatment is the only reading that keeps
   the contrast the light button has, so it is what the dark one takes. */
.ks3-dark .ks3-plate-open {
  background: var(--ks3-on-dark);
  border-color: var(--ks3-on-dark);
  color: var(--ks3-ink);
}
.ks3-dark .ks3-plate-count { color: var(--ks3-on-dark-muted); }
/* ⚠️ THE WHY PANEL IS CREAM INSIDE AN INK BLOCK, which is why its two text
   rules below are the ones that must be scoped. Unscoped they lose to
   `.ks3-dark p` and render `--ks3-on-dark-body` on `--ks3-ground`. */
.ks3-dark .ks3-plate-why { background: var(--ks3-ground); }
.ks3-dark .ks3-plate-real { color: var(--ks3-accent-text); }
.ks3-dark .ks3-plate-note { color: var(--ks3-ink); }
/* The row's own answer state, and the only marking anywhere in the block.
   `hit` keeps the panel ground it already had and gains the lit rule; `miss`
   drops to the block ground behind a muted rule. Read together they say "these
   were the ones you placed correctly" without a colour that means correct —
   no `--ks3-ok`, no tint, no glyph. */
.ks3-dark .ks3-plate-row[data-state="hit"] {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-alert);
}
.ks3-dark .ks3-plate-row[data-state="miss"] {
  background: transparent;
  border-color: var(--ks3-on-dark-muted);
}
.ks3-dark .ks3-plate-verdict { background: var(--ks3-dark-panel); }
.ks3-dark .ks3-plate-vlabel { color: var(--ks3-alert); }
.ks3-dark .ks3-plate-vhead { color: var(--ks3-on-dark); }
.ks3-dark .ks3-plate-vwhy { color: var(--ks3-on-dark-body); }

/* ── clinic-cases (b3-04 #s-cases) — five clinics, two with two answers ──
   Append to shared/ks3.css in a new B3 group.

   ⚠️ SPECIFICITY, AND IT IS LOAD-BEARING. `.ks3-dark p` is (0,1,1) and every
   bare class below is (0,1,0), so an unscoped `color` LOSES and the whole
   panel renders in on-dark body copy: the mono clinic label, the amber intake
   line and the ink verdict panel would all read as one undifferentiated
   paragraph. Every colour rule is therefore scoped `.ks3-dark …` at (0,2,0).
   Geometry is safe unscoped — `.ks3-dark p` sets colour and nothing else.

   The light rules are the fallback rather than dead weight: the component is
   registered for the key stage, this instance happens to be ink-dark, and a
   future light instance must not paint a `--ks3-dark-panel` box with no
   readable text in it. Same construction as `keyed-commit`.

   ⚠️ The tab and pick buttons take `.ks3-sim-seg-btn`, which drift 4 already
   ruled as THE segmented control and which already carries the ink-dark pair
   (`--ks3-alert` fill, `--ks3-ink` text, `--ks3-on-dark-muted` resting
   border). Design's own `seg(on, true)` is that declaration value for value,
   so nothing here re-declares it. What IS declared is the SPENT state, which
   the shared control has no notion of. */

.ks3-clinic { margin-top: 22px; }

.ks3-clinic-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 9px;
  margin-bottom: 20px;
}

/* The brief — one clinic's whole evidence, on the nested panel ground. */
.ks3-clinic-brief {
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
}
.ks3-clinic-label {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-clinic-desc { margin: 8px 0 0; font-size: 20px; line-height: 1.5; }
/* ⚖️ THE INTAKE LINE IS THE MEASUREMENT AND IT IS SET APART FROM THE STORY.
   Mono, and in the one colour nothing else in the panel uses, because the
   whole lesson is "reason from the measurement, not from the description" —
   a student who cannot find the number in three lines of clinical prose
   cannot do the exercise. */
.ks3-clinic-intake {
  margin: 12px 0 0;
  font-family: var(--ks3-font-mono);
  font-size: 16px;
  font-weight: 500;
  color: var(--ks3-accent-text);
}

.ks3-clinic-picklabel {
  margin: 20px 0 9px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-clinic-picks { display: flex; flex-wrap: wrap; gap: 9px; }

/* ⚖️ THE SPENT STATE DIMS WHAT WAS *NOT* CHOSEN, and that is the only thing
   this block ever paints about the student's answer. It records what has been
   spent; it says nothing about whether it was right. There is deliberately no
   rule keyed on correctness anywhere in this file — R3, and MRB-196 R10 for
   the verdict that used to carry it. */
.ks3-clinic-panel[data-open="1"] .ks3-clinic-pick[aria-pressed="false"] {
  opacity: .45;
  cursor: default;
}

.ks3-clinic-foot {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  margin-top: 18px;
}
.ks3-clinic-count {
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
  color: var(--ks3-ink-muted);
}

/* The verdict lands on the PAGE GROUND inside an ink block — Design's own
   inversion, and the reason it is legible as an answer rather than as more
   bench. Everything inside it therefore takes ink and must NOT be scoped
   `.ks3-dark`: that is the one panel in this instrument where the dark
   branch would be wrong. */
.ks3-clinic-verdict {
  margin-top: 20px;
  padding: 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}
.ks3-clinic-verdict-label {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ks3-accent-text);
}
.ks3-clinic-answer {
  margin: 10px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 26px;
  line-height: 1.18;
  letter-spacing: -.025em;
  color: var(--ks3-ink);
}
.ks3-clinic-why {
  margin: 12px 0 0;
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-ink);
}

/* ── the drawn state, on ink ───────────────────────────────────────────── */
.ks3-dark .ks3-clinic-brief { background: var(--ks3-dark-panel); }
.ks3-dark .ks3-clinic-label,
.ks3-dark .ks3-clinic-picklabel,
.ks3-dark .ks3-clinic-count { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-clinic-desc { color: var(--ks3-on-dark); }
.ks3-dark .ks3-clinic-intake { color: var(--ks3-alert); }
/* ⚠️ (0,2,0) BOTH TIMES, and the second is the one that is easy to miss: the
   verdict sits on the CREAM ground inside an ink block, so its three
   paragraphs have to be pulled back to ink explicitly or `.ks3-dark p` paints
   `--ks3-on-dark-body` on cream at 1.2:1 — invisible text on the one panel
   that carries the answer. */
.ks3-dark .ks3-clinic-answer,
.ks3-dark .ks3-clinic-why { color: var(--ks3-ink); }
.ks3-dark .ks3-clinic-verdict-label { color: var(--ks3-accent-text); }

/* ── enzyme-run (b3-06 #s-bench) — three counters, one of which never moves ──
   Append to shared/ks3.css in the B3 group, after `.ks3-gut-*`.

   ⚠️ SPECIFICITY, AND IT IS LOAD-BEARING. `.ks3-dark p` is (0,1,1) and every
   bare class below is (0,1,0), so an unscoped `color` LOSES. On this block the
   rate readout and the temperature figure are what would visibly break: amber
   mono is how a student reads a dial, and left to `.ks3-dark p` they resolve
   to on-dark body copy and stop looking like numbers at all. Every colour rule
   is scoped `.ks3-dark …` at (0,2,0). Geometry is safe unscoped —
   `.ks3-dark p` sets colour and nothing else.

   The light rules under them are the fallback, not dead weight: the component
   is registered for the key stage and a future light instance must not paint a
   `--ks3-dark-panel` box with no readable text in it.

   ⚠️ The two dial rows take `.ks3-sim-seg-btn`, which drift 4 already ruled as
   THE segmented control and which already carries the ink-dark pair. Design's
   own `seg()` on this page draws 16px / 10px 15px against the shipped control's
   17px / 11px 17px; under contract R3 the SHIPPED value wins and the difference
   is reported as drift rather than given a fourth control class. */

.ks3-erun { margin-top: 22px; }

/* ── the two dials ────────────────────────────────────────────────────── */
.ks3-erun-dials { display: flex; flex-wrap: wrap; gap: 20px 30px; }
.ks3-erun-diallabel {
  margin: 0 0 9px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-erun-seg { display: flex; flex-wrap: wrap; gap: 9px; }

/* ── the temperature panel ────────────────────────────────────────────── */
.ks3-erun-temp {
  margin-top: 20px;
  padding: 20px 22px;
  border-radius: var(--ks3-r-card);
  background: var(--ks3-inset);
}
.ks3-erun-temphead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.ks3-erun-temphead .ks3-erun-diallabel { margin: 0; }
.ks3-erun-tempvalue {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 21px;
  font-weight: 500;
  color: var(--ks3-accent-text);
}
/* The slider is labelled for a screen reader and captioned for everyone else
   by the figure above it, which is Design's own arrangement. Off-screen rather
   than `display:none`, because a hidden label labels nothing. */
.ks3-erun-srlabel {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}
.ks3-erun-slider {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 38px;
  background: transparent;
}
.ks3-erun-slider::-webkit-slider-runnable-track {
  height: 10px;
  border-radius: var(--ks3-r-pill);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-rule-strong);
}
.ks3-erun-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 30px;
  height: 30px;
  margin-top: -12px;
  border-radius: 50%;
  background: var(--ks3-accent);
  border: 3px solid var(--ks3-ink);
  cursor: pointer;
}
.ks3-erun-slider::-moz-range-track {
  height: 10px;
  border-radius: var(--ks3-r-pill);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-rule-strong);
}
.ks3-erun-slider::-moz-range-thumb {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--ks3-accent);
  border: 3px solid var(--ks3-ink);
  cursor: pointer;
}
.ks3-erun-tempnotes {
  margin: 4px 0 0;
  font-size: 18px;
  line-height: 1.55;
  color: var(--ks3-ink-body);
}

/* ── the tube ─────────────────────────────────────────────────────────── */
.ks3-erun-tube {
  margin-top: 18px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-card);
  background: var(--ks3-inset);
}
.ks3-erun-tubehead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.ks3-erun-reaction {
  margin: 0;
  font-size: 20px;
  font-weight: 700;
  color: var(--ks3-ink);
}
.ks3-erun-rate {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 16px;
  font-weight: 500;
  color: var(--ks3-accent-text);
}

.ks3-erun-counters {
  list-style: none;
  margin: 18px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 13px;
}
.ks3-erun-counterhead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.ks3-erun-counterlabel {
  margin: 0;
  font-size: 17px;
  font-weight: 600;
  color: var(--ks3-ink);
}
.ks3-erun-countervalue {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 17px;
  font-weight: 500;
  color: var(--ks3-ink-muted);
}
.ks3-erun-track {
  display: block;
  margin-top: 6px;
  height: 18px;
  border-radius: var(--ks3-r-pill);
  background: var(--ks3-rule);
  overflow: hidden;
}
.ks3-erun-bar {
  display: block;
  height: 100%;
  background: var(--ks3-rule-strong);
  transition: width .3s linear;
}
.ks3-erun-counter[data-counter="product"] .ks3-erun-bar {
  background: var(--ks3-accent);
}
/* ⚑ FOR MIDE — `--ks3-ok` outside the ladder. The token's own comment reserves
   green for the ladder marking correctness, and this is a bar meaning
   "unchanged" on a block that marks nothing. Design drew it; it is reproduced
   as drawn, flagged in the lesson module, and asserted in a parity row so the
   day the palette question is ruled the gate names the value.

   `-fixed` also carries no `transition`, because nothing about this bar ever
   changes — it is the one counter with no handle for the runtime at all. */
.ks3-erun-bar-fixed {
  background: var(--ks3-ok);
  transition: none;
}

.ks3-erun-controls {
  display: flex;
  align-items: center;
  gap: 13px;
  flex-wrap: wrap;
  margin-top: 20px;
}
.ks3-erun-clock {
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
  color: var(--ks3-ink-muted);
}
.ks3-erun-run[disabled],
.ks3-erun-reset[disabled] { opacity: .45; cursor: default; }

/* The verdict inverts to the page ground inside the ink block — Design's own
   move, and what makes it read as the answer rather than as more bench. */
.ks3-erun-verdict {
  margin: 18px 0 0;
  padding: 17px 19px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
  font-size: 18px;
  line-height: 1.6;
}

@media (prefers-reduced-motion: reduce) {
  .ks3-erun-bar { transition: none; }
}

/* ── the drawn state, on ink ───────────────────────────────────────────── */
.ks3-dark .ks3-erun-diallabel,
.ks3-dark .ks3-erun-countervalue,
.ks3-dark .ks3-erun-clock { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-erun-temp,
.ks3-dark .ks3-erun-tube { background: var(--ks3-dark-panel); }
/* ⚠️ THE TWO DIAL FIGURES. Unscoped these are (0,1,0), lose to `.ks3-dark p`
   and render in on-dark body copy — the temperature and the rate stop reading
   as instrument readings and become two more sentences. */
.ks3-dark .ks3-erun-tempvalue,
.ks3-dark .ks3-erun-rate { color: var(--ks3-alert); }
.ks3-dark .ks3-erun-tempnotes { color: var(--ks3-on-dark-body); }
.ks3-dark .ks3-erun-reaction,
.ks3-dark .ks3-erun-counterlabel { color: var(--ks3-on-dark); }
.ks3-dark .ks3-erun-slider::-webkit-slider-runnable-track {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-on-dark-muted);
}
.ks3-dark .ks3-erun-slider::-webkit-slider-thumb { background: var(--ks3-alert); }
.ks3-dark .ks3-erun-slider::-moz-range-track {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-on-dark-muted);
}
.ks3-dark .ks3-erun-slider::-moz-range-thumb { background: var(--ks3-alert); }
.ks3-dark .ks3-erun-track { background: rgba(255, 255, 255, .08); }
.ks3-dark .ks3-erun-bar { background: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-erun-counter[data-counter="product"] .ks3-erun-bar {
  background: var(--ks3-alert);
}
/* ⚠️ THE SAME SPECIFICITY TRAP, ONE LEVEL IN, AND IT BITES THE ONE BAR THE
   LESSON IS ABOUT. The fixed bar carries BOTH classes, so `.ks3-dark
   .ks3-erun-bar` at (0,2,0) beats a bare `.ks3-erun-bar-fixed` at (0,1,0) and
   the enzyme counter would render in the same muted grey as the substrate —
   three identical bars, and the counter that never moves would stop being
   visibly different from the one that does. Scoped to (0,2,0) and placed after
   its sibling so source order settles the tie. */
.ks3-dark .ks3-erun-bar-fixed { background: var(--ks3-ok); }
/* ⚠️ (0,2,0), and the one that catches people: the verdict sits on the CREAM
   ground inside an ink block, so it has to be pulled back to ink explicitly or
   `.ks3-dark p` paints #E7DECE on #FBF3E6 at 1.2:1 — the answer rendered
   invisible on the one panel that carries it. */
.ks3-dark .ks3-erun-verdict { color: var(--ks3-ink); }

/* ══════════════════════════════════════════════════════════════════
   B3 · Nutrition and digestion (⊕ MRB-220)

   ── fold-builder (b3-07 #s-fold) — three levels, one area ──
   Three toggles that multiply, and a readout that says what the multiplying
   came to. Append to shared/ks3.css in the B3 group.

   ⚠️ INK-DARK, AND THE SCOPING IS LOAD-BEARING. `.ks3-dark p` is (0,1,1);
   a bare `.ks3-fold-note` is (0,1,0) and LOSES, so an unscoped colour would
   leave the readout note rendering in the block's own on-dark body copy
   rather than in the panel's. Every text rule below is scoped to at least
   (0,2,0). This is the defect B1 shipped with the zoom instrument, B2 was
   bitten by again on the muscle bench, and B3 would have hit on all eight
   pages at once.

   There is NO light fallback under these rules, and that is deliberate rather
   than an omission: `ks3_data/b3/__init__.py` records that every one of B3's
   eight flagship instruments sits on `ks3-block ks3-dark ks3-practical`, so a
   light instance of this component does not exist and a speculative fallback
   would be a second treatment nobody has drawn or measured.
   ══════════════════════════════════════════════════════════════════ */

.ks3-fold { margin-top: 22px; }

.ks3-fold-levels {
  display: flex;
  flex-direction: column;
  gap: 11px;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* ⚖️ THE ROW LIGHTS WHEN THE LEVEL IS ON, and the lit state is the panel
   ground plus the alert rule. Amber here is a level the student CHOSE to
   add, never a mistake — nothing in this instrument marks anything, and
   there is no right number of levels to have on. */
.ks3-fold-level {
  padding: 19px 21px;
  border-radius: var(--ks3-r-panel);
  background: transparent;
  border: 2px solid var(--ks3-on-dark-muted);
}
.ks3-fold-level[data-on="1"] {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-alert);
}

.ks3-fold-levelmain {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.ks3-fold-levelwhat { flex: 1 1 260px; min-width: 0; }

.ks3-fold .ks3-fold-name {
  margin: 0;
  font-size: 19px;
  font-weight: 700;
  color: var(--ks3-on-dark);
}
.ks3-fold .ks3-fold-what {
  margin: 5px 0 0;
  font-size: 18px;
  line-height: 1.55;
  color: var(--ks3-on-dark-body);
}
/* ⚖️ THE SCALE LINE IS MONO AND IT IS NOT DECORATION. "About 1 mm — visible
   with a hand lens" against "About 0.001 mm — needs an electron microscope"
   is what makes the three levels three SIZES of one trick rather than three
   separate facts, and mono is what makes the two measurements line up to be
   compared. */
.ks3-fold .ks3-fold-scale {
  margin: 7px 0 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  color: var(--ks3-on-dark-muted);
}

/* A toggle, not an answer button: 44px minimum target, and the pressed state
   is the alert fill carrying INK text — the same dark segmented pair the sims
   use, so a control means the same thing everywhere on an ink block. */
.ks3-fold-toggle {
  flex: 0 0 auto;
  cursor: pointer;
  font: inherit;
  font-size: 16px;
  font-weight: 700;
  padding: 11px 17px;
  min-height: 44px;
  border-radius: var(--ks3-r-control);
  border: 2px solid var(--ks3-on-dark-muted);
  background: transparent;
  color: var(--ks3-on-dark);
}
.ks3-fold-toggle[aria-pressed="true"] {
  border-color: var(--ks3-accent);
  background: var(--ks3-dark-panel);
  color: var(--ks3-on-dark);
}

.ks3-fold-readout {
  margin-top: 22px;
  padding: 24px 26px;
  border-radius: var(--ks3-r-card);
  background: var(--ks3-dark-panel);
}
.ks3-fold-readhead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.ks3-fold .ks3-fold-readlabel {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
/* The number is the headline of the block and it is MONO, not display type:
   it changes six times while a student watches, and a proportional display
   face would make the readout jump on every toggle. */
.ks3-fold .ks3-fold-area {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 26px;
  font-weight: 500;
  color: var(--ks3-alert);
}

.ks3-fold-track {
  display: block;
  margin-top: 14px;
  height: 26px;
  border-radius: var(--ks3-r-pill);
  border: 2px solid var(--ks3-on-dark-muted);
  background: rgba(255, 255, 255, .06);
  overflow: hidden;
}
/* ⚖️ THE BAR GOES GREEN ONLY WHEN ALL THREE LEVELS ARE ON, and green here is
   NOT a mark. It is the completed model: the state the real small intestine
   is actually in. Nothing about the bar says the student was right — there
   was no question — and `--ks3-ok` is being used as "this is the finished
   thing", which is the one reading R3 leaves open to it. */
.ks3-fold-bar {
  display: block;
  height: 100%;
  width: 2%;
  background: var(--ks3-alert);
  transition: width .45s ease;
}
.ks3-fold-bar[data-full="1"] { background: var(--ks3-ok); }

.ks3-fold-noteline { margin: 14px 0 0; }
.ks3-fold .ks3-fold-note {
  margin: 0;
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-on-dark-body);
}
.ks3-fold .ks3-fold-multiple {
  margin: 12px 0 0;
  font-family: var(--ks3-font-mono);
  font-size: 16px;
  font-weight: 500;
  color: var(--ks3-on-dark-muted);
}

/* The bar is the only thing in this instrument that moves, and it moves
   because the growth is the point — half a square metre to thirty is a
   distance, not a jump. Under reduced motion the width still changes; it
   simply arrives at once. Nothing is lost, because the number, the note and
   the multiple all state the same fact in words. */
@media (prefers-reduced-motion: reduce) {
  .ks3-fold-bar { transition: none; }
}

/* ── gut-journey (b3-05 #s-journey) — seven stops and a chart that argues ──
   Append to shared/ks3.css in the B3 group, after `.ks3-clinic-*`.

   ⚠️ SPECIFICITY, AND IT IS LOAD-BEARING. `.ks3-dark p` is (0,1,1) and every
   bare class below is (0,1,0), so an unscoped `color` LOSES. The tiles are
   what would visibly break: three labels and three values resolving to one
   on-dark body colour is a panel that has quietly lost its structure while
   still looking tidy. Every colour rule is scoped `.ks3-dark …` at (0,2,0).
   Geometry is safe unscoped — `.ks3-dark p` sets colour and nothing else.

   ⚠️ A THIRD CONTROL GEOMETRY, registered as its own rather than folded into
   `.ks3-seg-btn`. That control and `.ks3-sim-seg-btn` are one centred line at
   one weight; this stacks a mono two-digit number over a 700 name and is read
   DOWN its left edge, because the seven are a sequence and not a set. The
   precedent for a stacked, left-aligned step button is `.ks3-mtl-step`. */

.ks3-gut { margin-top: 22px; }

.ks3-gut-tabs {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 22px;
  padding: 0;
}
.ks3-gut-tab {
  cursor: pointer;
  font: inherit;
  text-align: left;
  font-size: 16px;
  font-weight: 700;
  padding: 9px 15px;
  min-height: var(--ks3-tap);
  border-radius: var(--ks3-r-control);
  border: 2px solid var(--ks3-option-border);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}
.ks3-gut-tabnum {
  display: block;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .08em;
  opacity: .75;
}
.ks3-gut-tablabel { display: block; }
.ks3-gut-tab[aria-pressed="true"] {
  background: var(--ks3-accent-tint);
  border-color: var(--ks3-accent);
}

/* ── one stop ─────────────────────────────────────────────────────────── */
.ks3-gut-stop {
  padding: 26px 28px;
  border-radius: var(--ks3-r-card);
  background: var(--ks3-inset);
}
.ks3-gut-stophead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.ks3-gut-name {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 29px;
  line-height: 1.15;
  letter-spacing: -.025em;
  color: var(--ks3-ink);
}
/* ⚖️ WHAT KIND OF STOP THIS IS, in the one colour nothing else in the panel
   uses. Four of the seven do mechanical or chemical work, one does transport
   only, one secretes without food passing through and one stores — and a
   student who cannot tell those apart at a glance reads the journey as seven
   equal organs, which is exactly the flat picture the chart then contradicts. */
.ks3-gut-kind {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 16px;
  font-weight: 500;
  color: var(--ks3-accent-text);
}
.ks3-gut-what {
  margin: 14px 0 0;
  max-width: 62ch;
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-ink-body);
}

.ks3-gut-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr));
  gap: 12px;
  margin-top: 20px;
}
.ks3-gut-tile {
  padding: 15px 17px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
}
.ks3-gut-tilelabel {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-gut-tilevalue {
  margin: 5px 0 0;
  font-size: 18px;
  font-weight: 700;
  color: var(--ks3-ink);
}
/* The TIME tile is mono and larger than the other two. It is the quantity the
   chart under it is drawn from, and reading it as one of three equal facts is
   how the four-hours-against-sixteen argument gets missed. */
.ks3-gut-tile[data-tile="time"] .ks3-gut-tilevalue {
  font-family: var(--ks3-font-mono);
  font-size: 23px;
  font-weight: 500;
}

/* The note inverts to the page ground inside the ink block — Design's own
   move, and what makes it read as an aside rather than as more panel. */
.ks3-gut-note {
  margin: 18px 0 0;
  padding: 17px 19px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
  font-size: 18px;
  line-height: 1.6;
}
.ks3-gut-note strong { font-family: var(--ks3-font-display); }

/* ── the chart ────────────────────────────────────────────────────────── */
.ks3-gut-chart {
  margin-top: 22px;
  padding: 24px 26px;
  border-radius: var(--ks3-r-card);
  background: var(--ks3-inset);
}
.ks3-gut-chartlabel {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-gut-rows {
  list-style: none;
  margin: 16px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 9px;
}
.ks3-gut-row { display: flex; align-items: center; gap: 13px; }
.ks3-gut-rowname {
  flex: 0 0 132px;
  font-size: 16px;
  font-weight: 600;
  color: var(--ks3-ink);
}
.ks3-gut-track {
  flex: 1 1 auto;
  height: 20px;
  border-radius: var(--ks3-r-pill);
  background: var(--ks3-rule);
  overflow: hidden;
}
/* ⚠️ THE WIDTH IS INLINE AND COMES FROM THE PYTHON, computed from the same
   `hours` the printed figure is authored beside. Nothing here or in the
   wiring may set a width — the bar and the number would then have two
   sources and one of them would eventually be stale. */
.ks3-gut-bar {
  display: block;
  height: 100%;
  background: var(--ks3-rule-strong);
  transition: background-color .3s ease;
}
.ks3-gut-row[data-lit="1"] .ks3-gut-bar { background: var(--ks3-accent); }
.ks3-gut-rowhours {
  flex: 0 0 74px;
  text-align: right;
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
  color: var(--ks3-ink-muted);
}
.ks3-gut-chartclose {
  margin: 16px 0 0;
  font-size: 18px;
  line-height: 1.6;
  color: var(--ks3-ink-body);
}
@media (prefers-reduced-motion: reduce) {
  .ks3-gut-bar { transition: none; }
}

/* ── the drawn state, on ink ───────────────────────────────────────────── */
.ks3-dark .ks3-gut-tab {
  border-color: var(--ks3-on-dark-muted);
  background: transparent;
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-gut-tab[aria-pressed="true"] {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-accent);
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-gut-stop,
.ks3-dark .ks3-gut-chart { background: var(--ks3-dark-panel); }
.ks3-dark .ks3-gut-name { color: var(--ks3-on-dark); }
.ks3-dark .ks3-gut-kind { color: var(--ks3-alert); }
.ks3-dark .ks3-gut-what,
.ks3-dark .ks3-gut-chartclose { color: var(--ks3-on-dark-body); }
.ks3-dark .ks3-gut-tile { background: rgba(255, 255, 255, .06); }
.ks3-dark .ks3-gut-tilelabel,
.ks3-dark .ks3-gut-rowhours,
.ks3-dark .ks3-gut-chartlabel { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-gut-tilevalue,
.ks3-dark .ks3-gut-rowname { color: var(--ks3-on-dark); }
.ks3-dark .ks3-gut-track { background: rgba(255, 255, 255, .08); }
.ks3-dark .ks3-gut-bar { background: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-gut-row[data-lit="1"] .ks3-gut-bar { background: var(--ks3-alert); }
/* ⚠️ (0,2,0), and this is the one that catches people: the note sits on the
   CREAM ground inside an ink block, so it has to be pulled back to ink
   explicitly or `.ks3-dark p` paints #E7DECE on #FBF3E6 at 1.2:1 — seven
   invisible paragraphs, one per stop. */
.ks3-dark .ks3-gut-note { color: var(--ks3-ink); }

/* ── job-switch (b3-08 #s-jobs) — five jobs, switched off one at a time ──
   Append to shared/ks3.css in the B3 group, after `.ks3-fold-*`.

   ⚠️ INK-DARK, AND ONE RULE HERE IS THE DIFFERENCE BETWEEN A SENTENCE AND A
   BLANK. `.ks3-dark p` is (0,1,1) and a bare `.ks3-jobsw-without` is (0,1,0),
   so unscoped the consequence paragraph paints `--ks3-on-dark-body` #E7DECE
   on the cream `--ks3-ground` #FBF3E6 it is drawn on: contrast 1.1:1, present
   in the DOM, invisible on screen, and invisible to anyone reading the
   stylesheet. Every text rule below is scoped to at least (0,2,0). This is
   the same defect class as B1's zoom instrument and B2's muscle bench, and it
   is the one row of the parity fragment that must never be dropped.

   No light fallback, deliberately: `ks3_data/b3/__init__.py` records that all
   eight B3 flagships sit on `ks3-block ks3-dark ks3-practical`, so a light
   instance of this component does not exist and a speculative second
   treatment would be one nobody has drawn or measured. */

.ks3-jobsw { margin-top: 22px; }

.ks3-jobsw-list {
  display: flex;
  flex-direction: column;
  gap: 11px;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* ⚖️ THE GROUND INVERTS, and it is the opposite way round from
   `fold-builder`. A job that is still being done sits on the nested panel —
   it is a working part of the system. Switching it off drops the row back to
   the block's bare ink with an alert rule around it, so a switched-off job
   visibly stops being part of anything. Amber marks a part that has been
   REMOVED, never a student who was wrong. */
.ks3-jobsw-job {
  padding: 19px 21px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-dark-panel);
  border: 2px solid transparent;
}
.ks3-jobsw-job[data-off="1"] {
  background: transparent;
  border-color: var(--ks3-alert);
}

.ks3-jobsw-main {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.ks3-jobsw-what { flex: 1 1 280px; min-width: 0; }

.ks3-jobsw .ks3-jobsw-tag {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-jobsw .ks3-jobsw-name {
  margin: 5px 0 0;
  font-size: 19px;
  font-weight: 700;
  color: var(--ks3-on-dark);
}
.ks3-jobsw .ks3-jobsw-does {
  margin: 6px 0 0;
  font-size: 18px;
  line-height: 1.55;
  color: var(--ks3-on-dark-body);
}

/* The same dark toggle the fold builder takes, because it is the same control
   doing the same thing on the same ground. 44px minimum target; the pressed
   state is the alert fill carrying ink. */
.ks3-jobsw-toggle {
  flex: 0 0 auto;
  cursor: pointer;
  font: inherit;
  font-size: 16px;
  font-weight: 700;
  padding: 11px 17px;
  min-height: 44px;
  border-radius: var(--ks3-r-control);
  border: 2px solid var(--ks3-on-dark-muted);
  background: transparent;
  color: var(--ks3-on-dark);
}
.ks3-jobsw-toggle[aria-pressed="true"] {
  border-color: var(--ks3-accent);
  background: var(--ks3-dark-panel);
  color: var(--ks3-on-dark);
}

/* ⚖️ CREAM INSIDE THE INK BLOCK, and this is the rule the whole file warns
   about. The consequence is what the student came for, and Design lifts it
   out of the dark onto the page's own ground so it reads as the answer rather
   than as more of the row. `--ks3-ink` on `--ks3-ground` is 14.2:1; the
   unscoped version is 1.1:1. */
.ks3-jobsw .ks3-jobsw-without {
  margin: 13px 0 0;
  padding: 15px 17px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
  font-size: 18px;
  line-height: 1.6;
}
.ks3-jobsw .ks3-jobsw-without strong { font-family: var(--ks3-font-display); }

.ks3-jobsw-all {
  margin-top: 20px;
  padding: 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-dark-panel);
}
.ks3-jobsw .ks3-jobsw-alltag {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ks3-alert);
}
/* The payoff is a HEADLINE, in display type. Set as body copy it would read
   as one more consequence in a list of five, and it is the conclusion drawn
   from all five at once. */
.ks3-jobsw .ks3-jobsw-allhead {
  margin: 10px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 26px;
  line-height: 1.18;
  letter-spacing: -.025em;
  color: var(--ks3-on-dark);
}
.ks3-jobsw .ks3-jobsw-allbody {
  margin: 12px 0 0;
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-on-dark-body);
}

/* Nothing in this instrument animates: rows appear and disappear, they do not
   travel. `prefers-reduced-motion` therefore has nothing to degrade here and
   the reduced-motion experience is the complete one — the same standing as
   `meter-compare`. Design's page adds a 340ms arrive keyframe to the
   consequence paragraph and guards it; the shipped stylesheet has no such
   keyframe and one is not invented for two pages. */

/* ── person-ledger (b3-03 #s-ledger) — the person is the control ──
   Append to the B3 group in shared/ks3.css, after `.ks3-tbench-*`.

   ⚠️ SPECIFICITY. `.ks3-dark p` is (0,1,1) and a bare `.ks3-ledger-mwhy` is
   (0,1,0). The MATCH PANEL is cream (`--ks3-ground`) inside an ink-dark block,
   so its three paragraphs are the ones that lose — and the sentence at risk is
   *"Now switch person without changing the food"*, which NOTES-B3 §3.3 names
   as the thing that must not be lost. Unscoped it would still be there, in
   #E7DECE on #FBF3E6, and nobody would ever read it. Every text rule below is
   scoped `.ks3-dark …` or tighter.

   ⚖️ THE BAR HAS THREE STATES AND NONE OF THEM IS A MARK. Short, matched and
   over are readings of a measurement, not verdicts on a plate: there is no
   correct day in this block, which is why the copy under a match tells the
   student to keep the food and change the person. */

.ks3-ledger { margin-top: 20px; }
.ks3-ledger-grouplabel {
  margin: 0 0 9px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-ledger-tabs { display: flex; gap: 9px; flex-wrap: wrap; }
.ks3-ledger-tab {
  cursor: pointer;
  font: inherit;
  font-size: 17px;
  font-weight: 700;
  padding: 11px 17px;
  min-height: var(--ks3-tap);
  border-radius: var(--ks3-r-control);
  border: 2px solid var(--ks3-option-border);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}
.ks3-ledger-tab[aria-pressed="true"] {
  background: var(--ks3-accent-tint);
  border-color: var(--ks3-accent);
}
.ks3-ledger-panel {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-card);
  background: var(--ks3-inset);
}
.ks3-ledger-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.ks3-ledger-name { margin: 0; font-size: 21px; font-weight: 700; }
.ks3-ledger-need {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 17px;
  font-weight: 500;
  color: var(--ks3-accent-text);
}
.ks3-ledger-why { margin: 9px 0 0; font-size: 18px; line-height: 1.55; }
.ks3-ledger-bar {
  margin-top: 18px;
  height: 30px;
  border-radius: var(--ks3-r-pill);
  border: 2px solid var(--ks3-rule-strong);
  overflow: hidden;
  position: relative;
}
.ks3-ledger-fill {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  display: block;
  width: 0;
  transition: width .4s ease, background .4s ease;
}
@media (prefers-reduced-motion: reduce) {
  .ks3-ledger-fill { transition: none; }
}
/* ⚖️ THREE READINGS, NOT THREE VERDICTS. `--ks3-ok` is deliberately NOT used
   here even though the middle state is a match: green is the ladder's colour
   for a correct answer, and a plate is not an answer. `--ks3-blue` is the
   practical block's own shadow colour and reads as "on target" without
   claiming anyone scored. */
.ks3-ledger-fill[data-state="short"]   { background: var(--ks3-alert); }
.ks3-ledger-fill[data-state="matched"] { background: var(--ks3-blue); }
.ks3-ledger-fill[data-state="over"]    { background: var(--ks3-accent); }
.ks3-ledger-figures {
  margin-top: 11px;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.ks3-ledger-total {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 22px;
  font-weight: 500;
}
.ks3-ledger-balance {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 16px;
  font-weight: 500;
  color: var(--ks3-ink-muted);
}
.ks3-ledger-foodlabel {
  margin: 20px 0 9px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-ledger-foods {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(210px, 100%), 1fr));
  gap: 11px;
}
.ks3-ledger-food {
  width: 100%;
  text-align: left;
  cursor: pointer;
  font: inherit;
  padding: 15px 17px;
  min-height: var(--ks3-tap);
  border-radius: var(--ks3-r-control);
  border: 2px solid var(--ks3-option-border);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}
.ks3-ledger-food[data-count]:not([data-count="0"]) {
  background: var(--ks3-accent-tint);
  border-color: var(--ks3-accent);
}
.ks3-ledger-foodrow {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}
.ks3-ledger-foodname { font-size: 18px; font-weight: 700; }
.ks3-ledger-foodcount {
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
}
.ks3-ledger-foodkj {
  display: block;
  margin-top: 5px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  opacity: .82;
}
.ks3-ledger-foot {
  margin-top: 18px;
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}
.ks3-ledger-portions {
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
  color: var(--ks3-ink-muted);
}
.ks3-ledger-match {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
}
.ks3-ledger-mlabel {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ks3-accent-text);
}
.ks3-ledger-mhead {
  margin: 9px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 25px;
  line-height: 1.2;
  letter-spacing: -.02em;
}
.ks3-ledger-mwhy { margin: 11px 0 0; font-size: 18px; line-height: 1.6; }

/* ── the drawn state: on ink ──────────────────────────────────────── */

.ks3-dark .ks3-ledger-grouplabel,
.ks3-dark .ks3-ledger-foodlabel,
.ks3-dark .ks3-ledger-portions,
.ks3-dark .ks3-ledger-balance { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-ledger-tab,
.ks3-dark .ks3-ledger-food {
  border-color: var(--ks3-on-dark-muted);
  background: transparent;
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-ledger-tab[aria-pressed="true"],
.ks3-dark .ks3-ledger-food[data-count]:not([data-count="0"]) {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-accent);
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-ledger-panel { background: var(--ks3-dark-panel); }
.ks3-dark .ks3-ledger-name,
.ks3-dark .ks3-ledger-total { color: var(--ks3-on-dark); }
.ks3-dark .ks3-ledger-need { color: var(--ks3-alert); }
.ks3-dark .ks3-ledger-why { color: var(--ks3-on-dark-body); }
.ks3-dark .ks3-ledger-bar {
  border-color: var(--ks3-on-dark-muted);
  background: rgba(255, 255, 255, .06);
}
/* ⊕ CORRECTED FROM THE SHIPPED STYLESHEET, and reported. `.ks3-reveal-btn` is
   ink on an ink border, which on a `.ks3-dark` block whose ground IS
   `--ks3-ink` paints an invisible control. The inverse keeps the contrast the
   light button has. Same correction as `.ks3-plate-open`. */
.ks3-dark .ks3-ledger-clear {
  background: var(--ks3-on-dark);
  border-color: var(--ks3-on-dark);
  color: var(--ks3-ink);
}
/* ⚠️ THE MATCH PANEL IS CREAM INSIDE THE INK BLOCK. These are the three rules
   that would ship broken unscoped — see the header. */
.ks3-dark .ks3-ledger-match {
  background: var(--ks3-ground);
  border-color: var(--ks3-ground);
}
.ks3-dark .ks3-ledger-mlabel { color: var(--ks3-accent-text); }
.ks3-dark .ks3-ledger-mhead,
.ks3-dark .ks3-ledger-mwhy { color: var(--ks3-ink); }

/* ── test-bench (b3-02 #s-bench) — predict, and the prediction runs it ──
   Append to the B3 group in shared/ks3.css, after `.ks3-plate-*`.

   ⚠️ SPECIFICITY. `.ks3-dark p` is (0,1,1) and a bare `.ks3-tbench-why` is
   (0,1,0). The RESULT PANEL is cream (`--ks3-ground`) inside an ink-dark
   block, so its four paragraphs are the ones that lose: unscoped they paint
   `--ks3-on-dark-body` #E7DECE on #FBF3E6 and the honest note — the sentence
   the whole lesson turns on — becomes invisible while still being present.
   Every text rule below is scoped `.ks3-dark …` or tighter.

   ⚖️ THE TUBE'S COLOUR IS NOT IN THIS FILE, and must not move into it. It is
   the reagent's own colour, authored on the test and set inline on the fill.
   A token here would tint a real observation. */

.ks3-tbench { margin-top: 22px; }
.ks3-tbench-picks { display: flex; flex-wrap: wrap; gap: 20px 30px; }
.ks3-tbench-grouplabel {
  margin: 0 0 9px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-tbench-tabs { display: flex; gap: 9px; flex-wrap: wrap; }
.ks3-tbench-tab {
  cursor: pointer;
  font: inherit;
  font-size: 17px;
  font-weight: 700;
  padding: 11px 17px;
  min-height: var(--ks3-tap);
  border-radius: var(--ks3-r-control);
  border: 2px solid var(--ks3-option-border);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}
.ks3-tbench-tab[aria-pressed="true"] {
  background: var(--ks3-accent-tint);
  border-color: var(--ks3-accent);
}
.ks3-tbench-readout {
  margin-top: 22px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(230px, 100%), 1fr));
  gap: 18px;
  align-items: start;
}
.ks3-tbench-tubecard,
.ks3-tbench-methodcard {
  padding: 20px;
  border-radius: var(--ks3-r-card);
  background: var(--ks3-inset);
}
.ks3-tbench-tubecard { display: flex; gap: 20px; align-items: center; }
/* A test tube drawn in CSS: square shoulders, round bottom. `aria-hidden`,
   because the state line beside it says in words what the colour is — R2, and
   the reason a colour change is never the only signal in this block. */
.ks3-tbench-tube {
  flex: 0 0 62px;
  height: 168px;
  border-radius: 8px 8px 30px 30px;
  border: 3px solid var(--ks3-rule-strong);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}
.ks3-tbench-fill {
  display: block;
  height: 62%;
  border-top: 2px solid rgba(0, 0, 0, .22);
  transition: background .55s ease, height .55s ease;
}
/* Running the test adds reagent as well as colour, so the level rises. */
.ks3-tbench-fill[data-run="1"] { height: 74%; }
@media (prefers-reduced-motion: reduce) {
  .ks3-tbench-fill { transition: none; }
}
.ks3-tbench-cap {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-tbench-tubelabel { margin: 6px 0 0; font-size: 19px; font-weight: 700; line-height: 1.35; }
.ks3-tbench-state {
  margin: 8px 0 0;
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
  color: var(--ks3-accent-text);
}
.ks3-tbench-method { margin: 6px 0 0; font-size: 18px; line-height: 1.5; }
.ks3-tbench-detects {
  margin: 12px 0 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  color: var(--ks3-ink-muted);
}
.ks3-tbench-predict { margin-top: 22px; }
.ks3-tbench-predict .ks3-options { max-width: 36rem; }
.ks3-tbench-prompt { margin: 0 0 4px; }
.ks3-tbench-result {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
}
.ks3-tbench-verdict {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ks3-accent-text);
}
.ks3-tbench-head {
  margin: 9px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 25px;
  line-height: 1.2;
  letter-spacing: -.02em;
}
.ks3-tbench-why { margin: 11px 0 0; font-size: 18px; line-height: 1.6; }
/* ⚖️ THE CLAIM LINE IS RULED OFF FROM THE EXPLANATION, and that is not
   decoration. Everything above the rule is what happened; everything below it
   is what the student is licensed to write down. Running the two together is
   exactly the slip the lesson exists to stop. */
.ks3-tbench-claim {
  margin: 12px 0 0;
  padding-top: 12px;
  border-top: 2px solid var(--ks3-rule);
  font-size: 18px;
  line-height: 1.6;
}
.ks3-tbench-claim strong { font-family: var(--ks3-font-display); }

/* ── the drawn state: on ink ──────────────────────────────────────── */

.ks3-dark .ks3-tbench-grouplabel { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-tbench-tab {
  border-color: var(--ks3-on-dark-muted);
  background: transparent;
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-tbench-tab[aria-pressed="true"] {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-accent);
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-tbench-tubecard,
.ks3-dark .ks3-tbench-methodcard { background: var(--ks3-dark-panel); }
.ks3-dark .ks3-tbench-tube { border-color: var(--ks3-on-dark-muted); background: rgba(255, 255, 255, .05); }
.ks3-dark .ks3-tbench-cap,
.ks3-dark .ks3-tbench-detects { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-tbench-tubelabel { color: var(--ks3-on-dark); }
.ks3-dark .ks3-tbench-state { color: var(--ks3-alert); }
.ks3-dark .ks3-tbench-method { color: var(--ks3-on-dark-body); }
/* ⚠️ THE RESULT PANEL IS CREAM INSIDE THE INK BLOCK. These four rules are the
   ones that would ship broken unscoped — see the header. */
.ks3-dark .ks3-tbench-result {
  background: var(--ks3-ground);
  border-color: var(--ks3-ground);
}
.ks3-dark .ks3-tbench-verdict { color: var(--ks3-accent-text); }
.ks3-dark .ks3-tbench-head,
.ks3-dark .ks3-tbench-why,
.ks3-dark .ks3-tbench-claim { color: var(--ks3-ink); }
/* ═══ END B3 ═══ */

/* ═══ BEGIN B4 ═══ */

/* ── B4 · Breathing and gas exchange (⊕ MRB-244) ──
   Five instruments, and every one of them on ink.

   ⚠️ SPECIFICITY, AND ON THIS UNIT IT IS LOAD-BEARING FIVE TIMES OVER.
   `.ks3-dark p` is (0,1,1) and every bare class below is (0,1,0), so an
   unscoped `color` LOSES. Every B4 practical is
   `ks3-block ks3-dark ks3-practical` — measured on all five pages, no
   exceptions — so unlike B2, where the trap could catch one block, here it
   would catch the whole unit in one pass and each page would still look tidy.
   Every colour rule is written under `.ks3-dark …` at (0,2,0); geometry is
   safe unscoped, because `.ks3-dark p` sets colour and nothing else.

   The five panels that INVERT to the cream ground inside the ink block are the
   dangerous ones — gas-compare's closing paragraph, bell-jar's chain,
   crossing-counter's note, fault-bench's reveal and two-process-ledger's
   verdict. Each has to be pulled back to ink explicitly or `.ks3-dark p`
   paints #E7DECE on #FBF3E6 at about 1.2:1: text that is present, correct,
   and invisible.

   ⊕ SIX ELEMENTS, NOT FIVE PANELS, AND THE DIFFERENCE COST A DEFECT. Counting
   PANELS is what let `.ks3-bell-chainlabel` ship invisible: the chain panel was
   on the list, its steps and its note were rescued, and its LABEL — a seventh
   `<p>` in the same cream panel — was not. `.ks3-dark p` beat it at (0,1,1)
   and it rendered #E7DECE on #FBF3E6 at 1.21:1 in all three phases. The list
   that matters is therefore of ELEMENTS:

     `.ks3-gas-close` · `.ks3-bell-chainlabel` · `.ks3-bell-step` ·
     `.ks3-bell-note` · `.ks3-cross-note` · `.ks3-fault-verdict` ·
     `.ks3-fault-answer` · `.ks3-fault-row dt` · `.ks3-fault-row dd` ·
     `.ks3-tpl-vtag` · `.ks3-tpl-vhead` · `.ks3-tpl-vwhy`

   Every one has a `.ks3-dark …` rule below at (0,2,0). Add a text element to
   any cream panel in this unit and it joins this list in the same commit.

   ⚠️ NO `display` ON ANYTHING THAT ALSO TAKES `[hidden]`. An author `display`
   beats the UA stylesheet's `[hidden] { display: none }` regardless of
   specificity — the defect that has now cost seven builds. The elements that
   are hidden at rest in this unit are: `.ks3-gas-table`, `.ks3-gas-close`,
   `.ks3-bell-phaseval` / `-airval` / `-diaval`, `.ks3-bell-chain`,
   `.ks3-bell-note`, `.ks3-cross-note`, `.ks3-fault-scenario`,
   `.ks3-fault-reveal`, `.ks3-fault-verdict span`, `.ks3-tpl-flownote[data-note]`
   and `.ks3-tpl-verdict`. Not one of them is given a `display` here, and the
   two places where a hidden element genuinely needs one when shown carry an
   explicit `[hidden] { display: none }` companion. */

/* ── the B4 range control (b4-02, b4-05) ──
   ⚠️ NOT `.ks3-slider`, and the difference is measured rather than a
   preference. B2's control is a 28px thumb, 28px tall, on a
   `--ks3-dark-track`. Design draws B4's at 30px, 38px tall, on a
   `--ks3-dark-panel` track. Three properties, and reusing B2's would silently
   redraw a control Design specified twice, identically, on two pages. This is
   the same call `.ks3-gut-tab` made against `.ks3-seg-btn`. */
.ks3-b4slider {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 38px;
  margin: 12px 0 0;
  background: transparent;
  cursor: pointer;
}
.ks3-b4slider::-webkit-slider-runnable-track {
  height: 10px;
  border-radius: var(--ks3-r-pill);
  background: var(--ks3-dark-panel);
  border: 2px solid var(--ks3-on-dark-muted);
}
.ks3-b4slider::-moz-range-track {
  height: 10px;
  border-radius: var(--ks3-r-pill);
  background: var(--ks3-dark-panel);
  border: 2px solid var(--ks3-on-dark-muted);
}
.ks3-b4slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 30px;
  height: 30px;
  margin-top: -12px;
  border-radius: 50%;
  background: var(--ks3-alert);
  border: 3px solid var(--ks3-ink);
  cursor: pointer;
}
.ks3-b4slider::-moz-range-thumb {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--ks3-alert);
  border: 3px solid var(--ks3-ink);
  cursor: pointer;
}
.ks3-b4slider:focus-visible {
  outline: var(--ks3-r-focus) solid var(--ks3-accent);
  outline-offset: 3px;
}

/* ═══ gas-compare (b4-01 #s-air) ══════════════════════════════════════════
   Four gases, a prediction on each, then both bags side by side.

   ⚖️ THE BAR IS CLAMPED AND THE NUMERAL IS NOT. Carbon dioxide inhaled is
   0.04% and its bar is drawn at the payload's `min_bar_pct`, about thirty-
   seven times too wide, because a truthful bar there is zero pixels. The
   figure beside it is the correction, which is why `.ks3-gas-num` sits
   ABOVE the track in every cell and is never smaller than the bar it
   corrects. Do not move it below, and do not drop it on a narrow screen. */
.ks3-gas { margin-top: 22px; }

.ks3-gas-rows {
  list-style: none;
  margin: 22px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 11px;
}
.ks3-gas-row {
  padding: 17px 19px;
  border-radius: var(--ks3-r-panel);
  border: 2px solid transparent;
}
.ks3-gas-rowname { margin: 0; font-size: 19px; font-weight: 700; }
.ks3-gas-choices {
  margin-top: 11px;
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
.ks3-gas-choice {
  cursor: pointer;
  font: inherit;
  font-size: 16px;
  font-weight: 700;
  padding: 10px 15px;
  min-height: var(--ks3-tap);
  border-radius: var(--ks3-r-control);
  border: 2px solid var(--ks3-option-border);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}
/* ⚖️ THE ROW LOCKS AT THE REVEAL, and the lock is drawn on the buttons that
   were NOT chosen. R3: nothing marks correctness while the prediction is
   being made, so this state cannot exist before `[data-open="1"]`. */
.ks3-gas[data-open="1"] .ks3-gas-choice { cursor: default; }
.ks3-gas[data-open="1"] .ks3-gas-choice[aria-pressed="false"] { opacity: .45; }

.ks3-gas-foot {
  margin-top: 20px;
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}
.ks3-gas-open[disabled] { opacity: .45; cursor: default; }
.ks3-gas-open[disabled]:hover { transform: none; }
.ks3-gas-count {
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
  color: var(--ks3-ink-muted);
}

/* ⚠️ NO `display` HERE. `.ks3-gas-table` is emitted `hidden`. */
.ks3-gas-table {
  margin-top: 22px;
  border-radius: var(--ks3-r-card);
  border: 2px solid var(--ks3-rule-strong);
  background: var(--ks3-inset);
  overflow: hidden;
}
/* ⚠️ FLEX, NOT GRID, and that is Design's own choice rather than a
   simplification: the name column is fixed at 152px and the two data cells
   flex from 190px, so on a narrow screen the name takes a full row and the
   two bags sit side by side underneath — which is the comparison the block
   is for. A three-column grid would stack all three and put the bags on
   different screens. */
.ks3-gas-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 14px;
  align-items: flex-start;
}
.ks3-gas-head { padding: 13px 16px; background: rgba(0, 0, 0, .28); }
.ks3-gas-body {
  padding: 15px 16px;
  align-items: start;
  border-top: 2px solid rgba(255, 255, 255, .12);
}
.ks3-gas-body[data-band="1"] { background: rgba(255, 255, 255, .03); }
.ks3-gas-hname,
.ks3-gas-name { flex: 0 0 152px; }
.ks3-gas-hcell,
.ks3-gas-cell { flex: 1 1 190px; min-width: 0; }
.ks3-gas-hname {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
}
.ks3-gas-hcell {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 17px;
}
.ks3-gas-gname { margin: 0; font-size: 18px; font-weight: 700; }
.ks3-gas-verdict {
  margin: 3px 0 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
}
/* The per-cell caption is a NARROW-SCREEN affordance: the head row goes away
   below 880px and each figure has to say for itself which bag it came from.
   It carries `display` and never carries `[hidden]`. */
.ks3-gas-cap { display: none; }
.ks3-gas-num {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 17px;
}
.ks3-gas-track {
  display: block;
  margin-top: 5px;
  height: 12px;
  border-radius: var(--ks3-r-pill);
  background: var(--ks3-rule);
  overflow: hidden;
}
.ks3-gas-bar {
  display: block;
  height: 100%;
  background: var(--ks3-rule-strong);
  transition: width .4s ease;
}
/* ⚠️ NO `display`. `.ks3-gas-close` is emitted `hidden`. */
.ks3-gas-close {
  margin: 18px 0 0;
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
  font-size: 19px;
  line-height: 1.6;
}
.ks3-gas-close strong { font-family: var(--ks3-font-display); }

/* ⚠️ A MEDIA QUERY ADDS NO SPECIFICITY, and this block is where that bit me.
   `.ks3-gas-cap` in here is (0,1,0) whatever it is wrapped in, so `.ks3-dark p`
   at (0,1,1) still wins — which meant the cap was rendering `--ks3-on-dark-body`
   and was READABLE BY ACCIDENT at 8.77:1, with the authored colour never
   applied and therefore never checked.

   ⚠️ AND THE COLOUR IT WAS TRYING TO APPLY WAS THE WRONG TOKEN, which is why
   the cascade bug had to be fixed with it rather than before it.
   `--ks3-accent-text` #A93411 is the CREAM-GROUND orange (tokens.css says so
   in as many words); on the `--ks3-dark-panel` this cap actually sits on it
   measures 1.78:1. Scoping the rule without changing the token would have
   turned a lucky pass into a real failure, and the fix would have looked
   exactly like the defect.

   The cap REPLACES the head row below 880px, so it takes the head row's own
   colour: `--ks3-on-dark-muted`, as `.ks3-gas-hname` does, at 6.2:1. */
@media (max-width: 880px) {
  .ks3-gas-name { flex: 1 1 100%; }
  .ks3-gas-head { display: none; }
  .ks3-gas-cap {
    display: block;
    margin: 0 0 3px;
    font-family: var(--ks3-font-mono);
    font-size: 12px;
    font-weight: 500;
    letter-spacing: .07em;
    text-transform: uppercase;
    color: var(--ks3-ink-muted);
  }
  .ks3-dark .ks3-gas-cap { color: var(--ks3-on-dark-muted); }
}

/* ── gas-compare on ink ────────────────────────────────────────────────── */
.ks3-dark .ks3-gas-row { background: var(--ks3-dark-panel); }
.ks3-dark .ks3-gas-rowname,
.ks3-dark .ks3-gas-gname,
.ks3-dark .ks3-gas-num { color: var(--ks3-on-dark); }
.ks3-dark .ks3-gas-choice {
  border-color: var(--ks3-on-dark-muted);
  background: transparent;
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-gas-choice[aria-pressed="true"] {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-accent);
  color: var(--ks3-on-dark);
}
/* ⚖️ RIGHT KEEPS ITS PANEL, WRONG LOSES IT. This is not marking — the reveal
   has already happened and the answer is on screen either way. It is the row
   saying which of the four predictions the student got, in the one place they
   are looking. */
.ks3-dark .ks3-gas-row[data-verdict="right"] {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-alert);
}
.ks3-dark .ks3-gas-row[data-verdict="wrong"] {
  background: transparent;
  border-color: var(--ks3-on-dark-muted);
}
.ks3-dark .ks3-gas-count,
.ks3-dark .ks3-gas-hname,
.ks3-dark .ks3-gas-verdict { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-gas-table {
  border-color: var(--ks3-on-dark-muted);
  background: var(--ks3-dark-panel);
}
.ks3-dark .ks3-gas-hcell { color: var(--ks3-on-dark); }
/* The exhaled column is the one that changed, and it is alert in the heading,
   in the figure and in the bar — three places, one colour, so a student can
   follow one bag down the table. */
.ks3-dark .ks3-gas-hcell[data-side="out"],
.ks3-dark .ks3-gas-cell[data-side="out"] .ks3-gas-num { color: var(--ks3-alert); }
.ks3-dark .ks3-gas-track { background: rgba(255, 255, 255, .08); }
.ks3-dark .ks3-gas-bar { background: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-gas-cell[data-side="out"] .ks3-gas-bar { background: var(--ks3-alert); }
/* ⚠️ (0,2,0). The closing paragraph sits on the CREAM ground inside the ink
   block; unscoped, `.ks3-dark p` paints it #E7DECE on #FBF3E6. */
.ks3-dark .ks3-gas-close { color: var(--ks3-ink); }

/* ═══ bell-jar (b4-02 #s-model) ═══════════════════════════════════════════
   ⚖️ THE CHAIN IS THE INSTRUMENT, NOT THE PICTURE. The jar is a rectangle
   whose height scales and a circle that scales with it; the chain is four
   ordered lines whose first is always the muscle and whose last is always the
   air. That is why the chain gets the cream panel, the largest type in the
   block and the whole width, and the jar gets a 250px column. */
.ks3-bell { margin-top: 22px; }

.ks3-bell-panels {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(250px, 100%), 1fr));
  gap: 18px;
  align-items: start;
}
.ks3-bell-card {
  padding: 22px;
  border-radius: var(--ks3-r-card);
  background: var(--ks3-inset);
}
.ks3-bell-cap {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-bell-jar {
  height: 220px;
  margin-top: 14px;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  border: 3px solid var(--ks3-rule-strong);
  border-radius: 6px 6px 14px 14px;
  overflow: hidden;
  background: rgba(0, 0, 0, .04);
}
/* The chest's HEIGHT is the volume and its border-top is the diaphragm. Both
   are set inline by the wiring, because the control is continuous and there
   are 101 of them. */
.ks3-bell-chest {
  position: relative;
  display: block;
  height: 40%;
  background: rgba(255, 255, 255, .07);
  border-top: 4px solid var(--ks3-alert);
  transition: height .35s ease, background-color .35s ease;
}
.ks3-bell-lung {
  position: absolute;
  left: 50%;
  top: 12px;
  width: 54px;
  height: 54px;
  margin-left: -27px;
  border-radius: 50%;
  border: 3px solid var(--ks3-on-dark);
  background: rgba(255, 255, 255, .10);
  transition: transform .35s ease;
}
.ks3-bell-phase {
  margin: 12px 0 0;
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
  color: var(--ks3-accent-text);
}
.ks3-bell-reads { margin: 12px 0 0; }
.ks3-bell-read {
  padding: 11px 0;
  border-top: 2px solid var(--ks3-rule);
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.ks3-bell-read dt { margin: 0; font-size: 17px; }
.ks3-bell-read dd {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 18px;
  font-weight: 500;
  text-align: right;
}
.ks3-bell-control {
  margin-top: 18px;
  padding: 20px 22px;
  border-radius: var(--ks3-r-card);
  background: var(--ks3-inset);
}
.ks3-bell-controlhead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.ks3-bell-controlhead .ks3-bell-cap { font-size: 14px; }
.ks3-bell-dia {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 17px;
  font-weight: 500;
  color: var(--ks3-accent-text);
}
.ks3-bell-presets {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 4px;
}
.ks3-bell-chainpanel {
  margin-top: 18px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}
.ks3-bell-chainlabel {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ks3-accent-text);
}
/* ⚠️ NO `display`. Two of the three chains are emitted `hidden`, and an
   `<ol>` is `display: block` from the UA stylesheet anyway. */
.ks3-bell-chain {
  margin: 12px 0 0;
  padding-left: 24px;
  font-size: 18px;
  line-height: 1.65;
}
.ks3-bell-step { margin: 0 0 7px; color: var(--ks3-ink); }
/* ⚖️ THE RESTING CHAIN IS MUTED, and it is the only one that is. Design greys
   the whole list at rest because at rest the chain is not describing anything
   that is happening — "No net air movement in either direction." The moment
   the slider moves off `rest` the lines go to full ink. */
.ks3-bell-chain[data-chain="rest"] .ks3-bell-step { color: var(--ks3-ink-muted); }
/* ⚠️ NO `display`. Two of the three notes are emitted `hidden`. */
.ks3-bell-note {
  margin: 14px 0 0;
  font-size: 18px;
  line-height: 1.6;
  color: var(--ks3-ink);
}

/* ── bell-jar on ink ───────────────────────────────────────────────────── */
.ks3-dark .ks3-bell-card,
.ks3-dark .ks3-bell-control { background: var(--ks3-dark-panel); }
.ks3-dark .ks3-bell-cap { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-bell-jar {
  border-color: var(--ks3-on-dark-muted);
  background: rgba(255, 255, 255, .04);
}
.ks3-dark .ks3-bell-phase,
.ks3-dark .ks3-bell-dia { color: var(--ks3-alert); }
/* ⚖️ THE CHEST WARMS ON THE WAY IN. The tint is `--ks3-accent` at 20% and it
   is the only place in B4 the orange appears inside an instrument: the jar has
   no numbers on it, so the fill is the only thing saying which half of the
   breath the picture is showing. */
.ks3-dark .ks3-bell-chest[data-phase-now="in"] {
  background: rgba(228, 87, 46, .20);
}
.ks3-dark .ks3-bell-read { border-top-color: rgba(255, 255, 255, .12); }
.ks3-dark .ks3-bell-read dt { color: var(--ks3-on-dark-body); }
.ks3-dark .ks3-bell-read dd { color: var(--ks3-on-dark); }
/* ⚠️ (0,2,0), and the four lines this protects are the lesson. The chain sits
   on the CREAM ground inside the ink block; unscoped it is #E7DECE on
   #FBF3E6 — four invisible steps and an invisible note, in the one component
   NOTES-B4 §3.2 says the lesson dies without. */
/* ⊕ THE LABEL, AND IT IS THE ONE THAT SHIPPED WRONG. `.ks3-bell-chainlabel` is
   a `<p>` in the same cream panel as the steps it heads, so it loses to
   `.ks3-dark p` (0,1,1) exactly as they do — but it was missing from this
   block while they were in it, and rendered #E7DECE on #FBF3E6 at 1.21:1 in
   all three phases: `THE ORDER OF EVENTS` invisible above four visible steps.
   It is not the steps' rule because it does not take their colour: it is the
   accent eyebrow, the same treatment `.ks3-tpl-vtag` takes on b4-05, at
   5.98:1. */
.ks3-dark .ks3-bell-chainlabel { color: var(--ks3-accent-text); }
.ks3-dark .ks3-bell-step { color: var(--ks3-ink); }
.ks3-dark .ks3-bell-chain[data-chain="rest"] .ks3-bell-step {
  color: var(--ks3-ink-muted);
}
.ks3-dark .ks3-bell-note { color: var(--ks3-ink); }

/* ═══ crossing-counter (b4-03 #s-gradient) ════════════════════════════════
   ⚖️ TWO BARS, AND THE OUTWARD ONE IS DRAWN AS SERIOUSLY AS THE INWARD ONE.
   It is `--ks3-on-dark-muted` against the inward bar's alert, which is a
   figure/ground distinction and not a demotion: the same pairing the gas
   table uses for inhaled against exhaled. What it must never become is
   absent. */
.ks3-cross { margin-top: 22px; }

.ks3-cross-switches {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}
.ks3-cross-switch {
  cursor: pointer;
  font: inherit;
  font-size: 17px;
  font-weight: 700;
  padding: 12px 18px;
  min-height: var(--ks3-tap);
  border-radius: var(--ks3-r-control);
  border: 2px solid var(--ks3-option-border);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}
.ks3-cross-panel {
  margin-top: 20px;
  padding: 24px 26px;
  border-radius: var(--ks3-r-card);
  background: var(--ks3-inset);
}
.ks3-cross-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(190px, 100%), 1fr));
  gap: 14px;
}
.ks3-cross-tile {
  padding: 16px 18px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
}
.ks3-cross-tilelabel {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-cross-tileval {
  margin: 5px 0 0;
  font-family: var(--ks3-font-mono);
  font-size: 24px;
  font-weight: 500;
  color: var(--ks3-ink);
}
.ks3-cross-bars {
  list-style: none;
  margin: 20px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.ks3-cross-barhead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.ks3-cross-barname { margin: 0; font-size: 17px; font-weight: 600; }
.ks3-cross-barval {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 17px;
  font-weight: 500;
}
.ks3-cross-track {
  display: block;
  margin-top: 6px;
  height: 20px;
  border-radius: var(--ks3-r-pill);
  background: var(--ks3-rule);
  overflow: hidden;
}
.ks3-cross-fill {
  display: block;
  height: 100%;
  background: var(--ks3-rule-strong);
  transition: width .4s ease;
}
/* ⚠️ NO `display`. Three of the four notes are emitted `hidden`. */
.ks3-cross-note {
  margin: 20px 0 0;
  padding: 17px 19px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
  font-size: 18px;
  line-height: 1.6;
}

/* ⊕ MRB-257 (6.15) — the partial pressures, moved off the tiles and into a
   footnote. It is a caption, not a readout: the tiles now carry the derived
   comparison a Year 7 can read, and this is the figure a curious student (or
   a teacher) can still check. Muted, small, and never the thing the eye
   lands on first. */
.ks3-cross-kpa {
  margin: 14px 0 0;
  color: var(--ks3-muted);
  font-size: 14px;
  line-height: 1.5;
}

/* ── crossing-counter on ink ───────────────────────────────────────────── */
.ks3-dark .ks3-cross-switch {
  border-color: var(--ks3-on-dark-muted);
  background: transparent;
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-cross-switch[aria-pressed="true"] {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-accent);
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-cross-panel { background: var(--ks3-dark-panel); }
.ks3-dark .ks3-cross-tile { background: rgba(255, 255, 255, .06); }
.ks3-dark .ks3-cross-tilelabel { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-cross-tileval { color: var(--ks3-on-dark); }
/* The NET tile is the only alert-coloured figure in the panel, because it is
   the only one that is a difference rather than a reading. */
.ks3-dark .ks3-cross-tile[data-tone="net"] .ks3-cross-tileval {
  color: var(--ks3-alert);
}
.ks3-dark .ks3-cross-barname { color: var(--ks3-on-dark); }
.ks3-dark .ks3-cross-barval { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-cross-track { background: rgba(255, 255, 255, .08); }
.ks3-dark .ks3-cross-fill[data-fill="in"] { background: var(--ks3-alert); }
.ks3-dark .ks3-cross-fill[data-fill="out"] { background: var(--ks3-on-dark-muted); }
/* ⚠️ (0,2,0) — cream panel inside the ink block. */
.ks3-dark .ks3-cross-note { color: var(--ks3-ink); }
/* The footnote sits on the ink block itself, not inside the cream note, so it
   takes the on-dark muted ink rather than `--ks3-muted` (6.15). */
.ks3-dark .ks3-cross-kpa { color: var(--ks3-on-dark-muted); }

/* ═══ fault-bench (b4-04 #s-bench) ════════════════════════════════════════
   ⚖️ THE REVEAL IS NEVER WITHHELD FOR A WRONG ANSWER, so there is no
   "wrong" treatment on the panel — only a verdict line saying which of the
   two happened, in the same mono eyebrow either way. Nothing here is red and
   nothing is green: this is a diagnosis, not a mark. */
.ks3-fault { margin-top: 22px; }

.ks3-fault-tabs {
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
.ks3-fault-tab {
  cursor: pointer;
  font: inherit;
  font-size: 17px;
  font-weight: 700;
  padding: 11px 17px;
  min-height: var(--ks3-tap);
  border-radius: var(--ks3-r-control);
  border: 2px solid var(--ks3-option-border);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}
.ks3-fault-scenarios { margin-top: 20px; }
/* ⚠️ NO `display`. All but one scenario is emitted `hidden`. */
.ks3-fault-scenario {
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
}
.ks3-fault-tag {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-fault-text { margin: 8px 0 0; font-size: 20px; line-height: 1.5; }
.ks3-fault-q {
  margin: 20px 0 9px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-fault-foot {
  margin-top: 18px;
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}
.ks3-fault-open[disabled] { opacity: .45; cursor: default; }
.ks3-fault-open[disabled]:hover { transform: none; }
.ks3-fault-hint {
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
  color: var(--ks3-ink-muted);
}
/* ⚠️ NO `display`. Every reveal is emitted `hidden`, and stays hidden until
   this factor's own button is pressed. */
.ks3-fault-reveal {
  margin-top: 20px;
  padding: 24px 26px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}
.ks3-fault-verdict {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ks3-accent-text);
}
.ks3-fault-answer {
  margin: 9px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 25px;
  line-height: 1.2;
  letter-spacing: -.02em;
  color: var(--ks3-ink);
}
.ks3-fault-rows { margin: 16px 0 0; }
.ks3-fault-row { padding: 13px 0; border-top: 2px solid var(--ks3-rule); }
.ks3-fault-row dt {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-fault-row dd {
  margin: 5px 0 0;
  font-size: 18px;
  line-height: 1.55;
  color: var(--ks3-ink-body);
}

/* ── fault-bench on ink ────────────────────────────────────────────────── */
.ks3-dark .ks3-fault-tab {
  border-color: var(--ks3-on-dark-muted);
  background: transparent;
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-fault-tab[aria-pressed="true"] {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-accent);
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-fault-scenario { background: var(--ks3-dark-panel); }
.ks3-dark .ks3-fault-tag,
.ks3-dark .ks3-fault-q,
.ks3-dark .ks3-fault-hint { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-fault-text { color: var(--ks3-on-dark); }
/* ⚠️ (0,2,0). The whole reveal — verdict, headline and eight paragraphs
   across four rows — is on the CREAM ground inside the ink block. This is the
   largest run of text in the unit that `.ks3-dark p` would swallow. */
.ks3-dark .ks3-fault-answer { color: var(--ks3-ink); }
.ks3-dark .ks3-fault-row dt { color: var(--ks3-ink-muted); }
.ks3-dark .ks3-fault-row dd { color: var(--ks3-ink-body); }
.ks3-dark .ks3-fault-verdict { color: var(--ks3-accent-text); }

/* ═══ two-process-ledger (b4-05 #s-ledger) ════════════════════════════════
   ⚖️ THE TOP BAR NEVER MOVES. `[data-fill="resp"]` has its width written once,
   at build time, from `resp_rate` — nothing in `shared/ks3.js` touches it, and
   nothing here transitions it, because it has nothing to transition to. The
   whole lesson is a student dragging the light from one end to the other and
   watching it refuse to move.

   ⚖️ THE NET BAR IS THE ONLY THREE-COLOUR ELEMENT IN THE KEY STAGE, and the
   three are a sign rather than a judgement: `--ks3-ok` for uptake,
   `--ks3-alert` for release, `--ks3-blue` at the compensation point. Blue is
   used nowhere else in B4, which is what makes the balanced state read as a
   third thing rather than as a weak version of one of the other two. */
.ks3-tpl { margin-top: 22px; }

.ks3-tpl-control {
  padding: 20px 22px;
  border-radius: var(--ks3-r-card);
  background: var(--ks3-inset);
}
.ks3-tpl-controlhead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.ks3-tpl-cap {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-tpl-light {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 19px;
  font-weight: 500;
  color: var(--ks3-accent-text);
}
.ks3-tpl-presets {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 4px;
}
.ks3-tpl-preset {
  cursor: pointer;
  font: inherit;
  font-size: 16px;
  font-weight: 700;
  padding: 10px 15px;
  min-height: var(--ks3-tap);
  border-radius: var(--ks3-r-control);
  border: 2px solid var(--ks3-option-border);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}
.ks3-tpl-panel {
  margin-top: 18px;
  padding: 24px 26px;
  border-radius: var(--ks3-r-card);
  background: var(--ks3-inset);
}
.ks3-tpl-flows {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 15px;
}
.ks3-tpl-flowhead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}
.ks3-tpl-flowname { margin: 0; font-size: 18px; font-weight: 700; }
.ks3-tpl-flowval {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 17px;
  font-weight: 500;
}
.ks3-tpl-track {
  display: block;
  margin-top: 6px;
  height: 22px;
  border-radius: var(--ks3-r-pill);
  background: var(--ks3-rule);
  overflow: hidden;
}
.ks3-tpl-fill {
  display: block;
  height: 100%;
  background: var(--ks3-rule-strong);
  transition: width .35s ease, background-color .35s ease;
}
/* ⚠️ NO `display`. One of the photosynthesis notes is always `hidden`. */
.ks3-tpl-flownote { margin: 6px 0 0; font-size: 17px; line-height: 1.5; }
.ks3-tpl-verdicts {
  margin-top: 20px;
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}
/* ⚠️ NO `display`. Two of the three verdicts are emitted `hidden`. */
.ks3-tpl-vtag {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ks3-accent-text);
}
.ks3-tpl-vhead {
  margin: 8px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 24px;
  line-height: 1.2;
  letter-spacing: -.02em;
  color: var(--ks3-ink);
}
.ks3-tpl-vwhy {
  margin: 11px 0 0;
  font-size: 18px;
  line-height: 1.6;
  color: var(--ks3-ink-body);
}

/* ── two-process-ledger on ink ─────────────────────────────────────────── */
.ks3-dark .ks3-tpl-control,
.ks3-dark .ks3-tpl-panel { background: var(--ks3-dark-panel); }
.ks3-dark .ks3-tpl-cap { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-tpl-light { color: var(--ks3-alert); }
.ks3-dark .ks3-tpl-preset {
  border-color: var(--ks3-on-dark-muted);
  background: transparent;
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-tpl-preset[aria-pressed="true"] {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-accent);
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-tpl-flowname { color: var(--ks3-on-dark); }
.ks3-dark .ks3-tpl-flowval { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-tpl-flownote { color: var(--ks3-on-dark-body); }
.ks3-dark .ks3-tpl-track { background: rgba(255, 255, 255, .08); }
.ks3-dark .ks3-tpl-fill[data-fill="resp"] { background: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-tpl-fill[data-fill="photo"] { background: var(--ks3-ok); }
.ks3-dark .ks3-tpl-fill[data-tone="uptake"] { background: var(--ks3-ok); }
.ks3-dark .ks3-tpl-fill[data-tone="release"] { background: var(--ks3-alert); }
.ks3-dark .ks3-tpl-fill[data-tone="balanced"] { background: var(--ks3-blue); }
/* ⚠️ (0,2,0) — the verdict panel is cream inside the ink block, and its three
   paragraphs are where the compensation point is explained. */
.ks3-dark .ks3-tpl-vhead { color: var(--ks3-ink); }
.ks3-dark .ks3-tpl-vwhy { color: var(--ks3-ink-body); }
.ks3-dark .ks3-tpl-vtag { color: var(--ks3-accent-text); }

/* ── B4 · reduced motion ───────────────────────────────────────────────────
   ⊖ THERE IS NO B4 REDUCED-MOTION BLOCK, DELIBERATELY, and this comment is
   here so the next pass does not add one back.

   The first cut of this file listed B4's five transitioning elements under
   `@media (prefers-reduced-motion: reduce) { … transition: none }`. It was
   dead: R6's platform rule above (`*, *::before, *::after {
   transition-duration: .001ms !important }`) already covers every transition
   on every page, and `!important` beats a later bare declaration, so the B4
   block could never win and never needed to. Measured in a browser under
   emulated `prefers-reduced-motion: reduce`: the five elements compute to
   1e-06s from the platform rule with the B4 block present and without it.
   Dead CSS is evidence to a grep and nothing to a browser.

   What makes the unit safe under reduced motion is not CSS anyway. NOTES-B4
   §6 records that nothing in B4 animates and nothing uses a timer; there is no
   rAF loop in any of the five wire functions, so there is no tick that would
   have to test the query inside itself (MRB-220 R4). The bars and the jar jump
   to their new values instead of easing to them, every figure and every
   sentence is unchanged, and the reduced-motion experience is the complete
   one. */

/* ═══ END B4 ═══ */

/* ═══ BEGIN B6 ═══ */

/* ── B6 · Health and drugs (⊕ MRB-244) ──
   Three instruments, and every one of them on ink.

   ⚠️ SPECIFICITY, AND ON THIS UNIT IT IS LOAD-BEARING THREE TIMES OVER.
   `.ks3-dark p` is (0,1,1) and every bare class below is (0,1,0), so an
   unscoped `color` LOSES. All three B6 practicals are
   `ks3-block ks3-dark ks3-practical` — measured off Design's own markup on
   b6-01 `#s-dose`, b6-02 `#s-clock` and b6-03 `#s-claims`, no exceptions — so
   the trap takes the whole unit in one pass and each page still looks tidy.
   Every colour rule below is written under `.ks3-dark …` at (0,2,0); geometry
   is safe unscoped, because `.ks3-dark p` sets colour and nothing else.

   ⊕ THE LIST BELOW IS OF ELEMENTS, NOT OF PANELS, and B4 is why. Counting
   panels is what let `.ks3-bell-chainlabel` ship at 1.21:1: the panel was on
   the list, its steps and its note were rescued, and a seventh `<p>` inside
   the same panel taking a different colour was not. So — the CREAM-INSIDE-INK
   ELEMENTS in B6, each with its own `.ks3-dark …` rule at (0,2,0):

     `.ks3-route-verdict` · `.ks3-clock-verdict` · `.ks3-ccheck-word` ·
     `.ks3-ccheck-answer` · `.ks3-ccheck-why` · `.ks3-ccheck-settle`

   Add a text element to any cream panel in this unit and it joins this list in
   the same commit. Adding it to the panel's rule is not the same thing.

   ⚠️ AND RE-MEASURE THE TOKEN YOU CHOSE ON THE GROUND IT LANDS ON. B4 also
   proved the cascade bug can MASK a wrong token: `.ks3-gas-cap` measured a
   healthy 8.77:1 only because `.ks3-dark p` was winning, and the authored
   colour would have been 1.78:1. Fixing the specificity alone would have
   turned a lucky pass into a real failure and the fix would have looked
   exactly like the defect.

   ⚠️ NO `display` ON ANYTHING THAT ALSO TAKES `[hidden]`. An author `display`
   beats the UA stylesheet's `[hidden] { display: none }` regardless of
   specificity — the defect that has now cost eight builds. The elements hidden
   at rest in this unit are: `.ks3-route-stepbody`, `.ks3-route-else`, the
   per-drug `.ks3-route-name` / `-class` / `-where` / `.ks3-route-steps`,
   `.ks3-clock-note`, `.ks3-clock-verdict`, `.ks3-ccheck-claim`,
   `.ks3-ccheck-evidence`, `.ks3-ccheck-verdict` and the two
   `.ks3-ccheck-word span`s. Not
   one is given a `display` here — except `.ks3-ccheck-evlabel`, which carries
   one and never carries `[hidden]`: it is a caption INSIDE a paragraph that
   is hidden, so the attribute is on its parent and this rule can never
   collide with it. `.ks3-route-stepbody` needs to be a block
   when it is shown and gets it WITHOUT a `display` declaration, by being a
   flex item of `.ks3-route-stepmain` — a flex item is blockified, and an
   element the UA has set to `display: none` is not a flex item at all. */

/* ═══ route-tracer (b6-01 #s-dose) ════════════════════════════════════════
   Four drugs, one route, five stages, and a closing panel that is a
   consequence rather than a list.

   ⚖️ THE FIVE STAGE ROWS ARE THE PICTURE. There is no diagram in this unit
   (NOTES-B6 flag 14: measured, not omitted) — the ordered rows, the numeral
   chip filling in as each is reached and the alert border sitting on exactly
   one of them ARE the drawing. Which is why the resting rows are drawn at all
   rather than hidden: a student reads the whole journey before taking a single
   step, and stage 3 is visible from the start. */
.ks3-route { margin-top: 22px; }

.ks3-route-doselabel {
  margin: 0 0 9px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
}
.ks3-route-tabs {
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
.ks3-route-tab {
  cursor: pointer;
  font: inherit;
  font-size: 16px;
  font-weight: 700;
  padding: 10px 15px;
  min-height: var(--ks3-tap);
  border-radius: var(--ks3-r-control);
  border: 2px solid var(--ks3-option-border);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}

.ks3-route-panel {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-card);
}
.ks3-route-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.ks3-route-name { margin: 0; font-size: 21px; font-weight: 700; }
.ks3-route-class {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
}
.ks3-route-where { margin: 8px 0 0; font-size: 18px; line-height: 1.55; }

/* ⊕ MRB-242 — the same trap again (see `.ks3-card-back[hidden]`,
   `.ks3-ticks[hidden]`, `.ks3-picker-panel[hidden]`, `.ks3-hb [hidden]`,
   `.ks3-sortcards [hidden]`, `.ks3-fifa-chipped[hidden]`). An AUTHOR `display`
   beats the UA stylesheet's `[hidden] { display: none }` at ANY specificity,
   so the `display: flex` below un-hid all three of
   `what-drugs-do-to-the-body`'s route-step lists. `ks3.js` writes an inline
   `display:none` over them on load, which is the only reason nobody saw it —
   with JS off, or in the moment before it runs, every drug's route stacked
   open. The guard goes FIRST so the attribute keeps winning. */
.ks3-route-steps[hidden] { display: none; }
.ks3-route-steps {
  list-style: none;
  margin: 20px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.ks3-route-step {
  display: flex;
  gap: 14px;
  align-items: flex-start;
  padding: 14px 16px;
  border-radius: var(--ks3-r-panel);
  border: 2px solid transparent;
}
.ks3-route-num {
  flex: 0 0 30px;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 9px;
  border: 2px solid transparent;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 15px;
}
/* ⚠️ A FLEX CONTAINER, AND THAT IS WHAT KEEPS `[hidden]` WORKING ON THE BODY.
   Design draws the title and the body as two blocks stacked in the row's
   right-hand column. Writing that as `display: block` on the body would beat
   the UA's `[hidden] { display: none }` at any specificity and the unreached
   stages would ship visible — the eight-build defect. Making the parent a
   column flex container blockifies both children for free, and a child the UA
   has set to `display: none` never becomes a flex item. */
.ks3-route-stepmain {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.ks3-route-steptitle { font-size: 19px; font-weight: 700; }
.ks3-route-stepbody { font-size: 18px; line-height: 1.55; }

.ks3-route-foot {
  margin-top: 20px;
  display: flex;
  align-items: center;
  gap: 13px;
  flex-wrap: wrap;
}
.ks3-route-next[disabled] { opacity: .45; cursor: default; }
.ks3-route-next[disabled]:hover { transform: none; }

/* ⚠️ NO `display` HERE. `.ks3-route-else` is emitted `hidden`. */
.ks3-route-else {
  margin-top: 18px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-card);
}
.ks3-route-elselabel {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
}
.ks3-route-organs {
  list-style: none;
  margin: 16px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.ks3-route-organrow {
  display: grid;
  grid-template-columns: minmax(120px, 170px) 1fr;
  gap: 6px 18px;
}
.ks3-route-organ { margin: 0; font-size: 18px; font-weight: 700; }
.ks3-route-effect { margin: 0; font-size: 18px; line-height: 1.55; }
.ks3-route-verdict {
  margin: 18px 0 0;
  padding: 16px 18px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ground);
  font-size: 18px;
  line-height: 1.6;
}

/* The organ column stops being a column when there is no room for two. Below
   this the effect would be reading in a 90px gutter beside a 170px name. */
@media (max-width: 560px) {
  .ks3-route-organrow { grid-template-columns: 1fr; }
}

/* ── route-tracer on ink ───────────────────────────────────────────────── */
.ks3-dark .ks3-route-doselabel { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-route-tab {
  border-color: var(--ks3-on-dark-muted);
  background: transparent;
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-route-tab[aria-pressed="true"] {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-accent);
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-route-panel,
.ks3-dark .ks3-route-else { background: var(--ks3-dark-panel); }
.ks3-dark .ks3-route-name,
.ks3-dark .ks3-route-organ { color: var(--ks3-on-dark); }
.ks3-dark .ks3-route-class,
.ks3-dark .ks3-route-elselabel { color: var(--ks3-alert); }
.ks3-dark .ks3-route-where { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-route-num {
  border-color: var(--ks3-on-dark-muted);
  color: var(--ks3-on-dark-muted);
}
.ks3-dark .ks3-route-steptitle { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-route-stepbody,
.ks3-dark .ks3-route-effect { color: var(--ks3-on-dark-body); }
/* ⚖️ A REACHED STAGE KEEPS ITS CHIP LIT AND ITS TITLE FULL-STRENGTH; the
   CURRENT one additionally takes the alert border and the lifted ground. Two
   states, not one, because a student at stage 4 has to be able to see that
   stages 1–3 happened and that stage 4 is where they are. */
.ks3-dark .ks3-route-step[data-state] { border-color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-route-step[data-state] .ks3-route-steptitle {
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-route-step[data-state] .ks3-route-num {
  background: var(--ks3-alert);
  border-color: var(--ks3-alert);
  color: var(--ks3-ink);
}
.ks3-dark .ks3-route-step[data-state="current"] {
  background: rgba(255, 255, 255, .10);
  border-color: var(--ks3-alert);
}
/* ⚠️ (0,2,0). The verdict sits on the CREAM ground inside the ink block;
   unscoped, `.ks3-dark p` paints it #E7DECE on #FBF3E6 at about 1.2:1 —
   present, correct, spelt right and invisible. It is also the one sentence in
   the block that says what the whole route was for. */
.ks3-dark .ks3-route-verdict { color: var(--ks3-ink); }

/* ═══ clearance-clock (b6-02 #s-clock) ════════════════════════════════════
   Five drinks, six things people believe sober you up, and a clock that does
   not care about any of them.

   ⚖️ THE HOURS READOUT IS THE ONE ALERT FIGURE IN THE PANEL, because it is
   the number the block is about and the number that refuses to move. The
   units beside it are on-dark and the elapsed line under the bar is muted —
   levelling the three would turn the readout into three facts rather than one
   claim with two supports.

   ⚠️ THE NOTE PANEL IS NOT A VERDICT PANEL. The chosen fix's note sits on the
   lifted white wash inside the dark panel and reads in on-dark body; the
   verdict sits on the CREAM ground and reads in ink. They are different
   surfaces because they are different claims: the note explains what that one
   trick does, the verdict states where the clock actually is. */
.ks3-clock { margin-top: 22px; }

.ks3-clock-group { margin-top: 20px; }
.ks3-clock-grouplabel {
  margin: 0 0 9px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
}
.ks3-clock-btns {
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
.ks3-clock-drink,
.ks3-clock-fix {
  cursor: pointer;
  font: inherit;
  font-size: 16px;
  font-weight: 700;
  padding: 10px 15px;
  min-height: var(--ks3-tap);
  border-radius: var(--ks3-r-control);
  border: 2px solid var(--ks3-option-border);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}

.ks3-clock-panel {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-card);
}
.ks3-clock-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.ks3-clock-units { margin: 0; font-size: 21px; font-weight: 700; }
.ks3-clock-hours {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 16px;
  font-weight: 500;
}
.ks3-clock-bloodlabel {
  margin: 16px 0 6px;
  font-size: 17px;
  font-weight: 600;
}
.ks3-clock-track {
  display: block;
  height: 22px;
  border-radius: var(--ks3-r-pill);
  overflow: hidden;
}
.ks3-clock-fill {
  display: block;
  height: 100%;
  background: var(--ks3-alert);
  transition: width .4s ease;
}
.ks3-clock-remaining {
  margin: 8px 0 0;
  font-family: var(--ks3-font-mono);
  font-size: 16px;
  font-weight: 500;
}
/* ⚠️ NO `display` HERE. Five of the six `.ks3-clock-note` elements are
   emitted `hidden`, and a `<p>` needs nothing from this rule to be a block. */
.ks3-clock-note {
  margin: 16px 0 0;
  padding: 15px 17px;
  border-radius: var(--ks3-r-panel);
  font-size: 18px;
  line-height: 1.55;
}
.ks3-clock-foot {
  margin-top: 20px;
  display: flex;
  align-items: center;
  gap: 13px;
  flex-wrap: wrap;
}
.ks3-clock-wait[disabled] { opacity: .45; cursor: default; }
.ks3-clock-wait[disabled]:hover { transform: none; }
/* ⚠️ NO `display` HERE EITHER. `.ks3-clock-verdict` is emitted `hidden`. */
.ks3-clock-verdict {
  margin: 18px 0 0;
  padding: 17px 19px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ground);
  font-size: 18px;
  line-height: 1.6;
}

/* ── clearance-clock on ink ────────────────────────────────────────────── */
.ks3-dark .ks3-clock-grouplabel { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-clock-drink,
.ks3-dark .ks3-clock-fix {
  border-color: var(--ks3-on-dark-muted);
  background: transparent;
  color: var(--ks3-on-dark);
}
/* ⚖️ A CHOSEN FIX IS LIT AND A DRINK NEVER IS. Design draws the drinks with
   `seg(false)` on every one of them — they are an action, not a selection,
   and each press pours another one. A drink that latched would read as a
   choice a student had made rather than a thing they had done. */
.ks3-dark .ks3-clock-fix[aria-pressed="true"] {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-accent);
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-clock-panel { background: var(--ks3-dark-panel); }
.ks3-dark .ks3-clock-units,
.ks3-dark .ks3-clock-bloodlabel { color: var(--ks3-on-dark); }
.ks3-dark .ks3-clock-hours { color: var(--ks3-alert); }
.ks3-dark .ks3-clock-remaining { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-clock-track { background: rgba(255, 255, 255, .08); }
.ks3-dark .ks3-clock-note {
  background: rgba(255, 255, 255, .06);
  color: var(--ks3-on-dark-body);
}
/* ⚠️ (0,2,0). The verdict sits on the CREAM ground inside the ink block;
   unscoped, `.ks3-dark p` paints it #E7DECE on #FBF3E6 at about 1.2:1. It is
   the sentence that names how many hours it actually took. */
.ks3-dark .ks3-clock-verdict { color: var(--ks3-ink); }

/* ⊕ NO `prefers-reduced-motion` BLOCK FOR B6, AND THAT IS CHECKED RATHER THAN
   FORGOTTEN. The unit carries exactly one transition — `.ks3-clock-fill`'s
   width — and the platform-wide rule at the top of this file
   (`*,*::before,*::after { transition-duration: .001ms !important }`) already
   removes it. A per-instrument block here would be dead code, which is what
   B4 removed after adding one. Nothing in B6 animates and nothing uses a
   timer (NOTES-B6 §4), so there is no rAF loop to test the query inside
   either, and the reduced-motion experience is the complete one: the blood
   bar jumps to its new width and every figure and sentence is unchanged. */

/* ═══ claim-check (b6-03 #s-claims) ═══════════════════════════════════════
   Five claims, five faults, one-to-one.

   ⚖️ NOTHING IN THIS BLOCK IS GREEN OR RED, AND THAT IS A RULE RATHER THAN A
   PALETTE CHOICE (MRB-208 R10, restated on Design's own page: *the bench does
   not mark right and wrong; it highlights the pick, and the verdict panel
   names the answer*). There is no `is-correct` or `is-wrong` rule anywhere in
   this component, and there must never be one. A chosen fault takes the alert
   border and the alert letter — the same treatment a chosen anything takes
   across the key stage — and once the claim is checked the four that were not
   chosen drop to .5. That is a record of what was picked, not a mark on it.

   ⚠️ FOUR CREAM-INSIDE-INK ELEMENTS IN ONE PANEL, which is the largest count
   in the unit and exactly the shape of the B4 defect: `.ks3-ccheck-word`,
   `-answer`, `-why` and `-settle` all sit on `--ks3-ground` inside the ink
   block, and every one of them needs its own `.ks3-dark …` rule. Rescuing the
   panel is not rescuing the paragraphs. */
.ks3-ccheck { margin-top: 22px; }

.ks3-ccheck-tabsgroup { margin-top: 22px; }
.ks3-ccheck-tabslabel {
  margin: 0 0 9px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
}
.ks3-ccheck-tabs {
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
.ks3-ccheck-tab {
  cursor: pointer;
  font: inherit;
  font-size: 16px;
  font-weight: 700;
  padding: 10px 15px;
  min-height: var(--ks3-tap);
  border-radius: var(--ks3-r-control);
  border: 2px solid var(--ks3-option-border);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}

.ks3-ccheck-panel {
  margin-top: 20px;
  padding: 24px 26px;
  border-radius: var(--ks3-r-card);
}
/* ⚠️ NO `display` ON `.ks3-ccheck-claim` OR `-evidence`: four of each ship
   `hidden`, and a `<p>` is a block without help. */
.ks3-ccheck-claim {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 26px;
  line-height: 1.2;
  letter-spacing: -.02em;
}
.ks3-ccheck-evidence {
  margin: 14px 0 0;
  padding: 15px 17px;
  border-radius: var(--ks3-r-panel);
  font-size: 18px;
  line-height: 1.55;
}
/* A caption INSIDE the evidence paragraph, so it needs a block of its own —
   and it never carries `[hidden]`, its parent does. */
.ks3-ccheck-evlabel {
  display: block;
  margin-bottom: 6px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
}
.ks3-ccheck-ask {
  margin: 20px 0 10px;
  font-size: 17px;
  font-weight: 600;
}
.ks3-ccheck-faults {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 9px;
}
.ks3-ccheck-fault {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  text-align: left;
  width: 100%;
  cursor: pointer;
  font: inherit;
  font-size: 18px;
  line-height: 1.5;
  padding: 14px 16px;
  min-height: var(--ks3-tap);
  border-radius: var(--ks3-r-panel);
  border: 2px solid var(--ks3-option-border);
  background: transparent;
  color: var(--ks3-ink);
}
.ks3-ccheck-fault[disabled] { cursor: default; }
.ks3-ccheck-fault[disabled][aria-pressed="false"] { opacity: var(--ks3-dim-spent); }
.ks3-ccheck-mark {
  flex: 0 0 26px;
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: 8px;
  border: 2px solid var(--ks3-option-border);
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 14px;
}
.ks3-ccheck-faulttext { flex: 1 1 auto; min-width: 0; }
.ks3-ccheck-foot {
  margin-top: 18px;
  display: flex;
  align-items: center;
  gap: 13px;
  flex-wrap: wrap;
}
.ks3-ccheck-check[disabled] { opacity: .45; cursor: default; }
.ks3-ccheck-check[disabled]:hover { transform: none; }
.ks3-ccheck-tally {
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
}
/* ⚠️ NO `display` HERE. `.ks3-ccheck-verdict` is emitted `hidden`, and so is
   each of the two words inside `.ks3-ccheck-word`. */
.ks3-ccheck-verdict {
  margin-top: 18px;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ground);
}
.ks3-ccheck-word {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.ks3-ccheck-answer {
  margin: 9px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 22px;
  line-height: 1.25;
  letter-spacing: -.02em;
}
.ks3-ccheck-why { margin: 11px 0 0; font-size: 18px; line-height: 1.6; }
.ks3-ccheck-settle {
  margin: 14px 0 0;
  padding-top: 14px;
  border-top: 2px solid var(--ks3-rule);
  font-size: 18px;
  line-height: 1.6;
}

/* ── claim-check on ink ────────────────────────────────────────────────── */
.ks3-dark .ks3-ccheck-tabslabel { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-ccheck-tab {
  border-color: var(--ks3-on-dark-muted);
  background: transparent;
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-ccheck-tab[aria-pressed="true"] {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-accent);
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-ccheck-panel { background: var(--ks3-dark-panel); }
.ks3-dark .ks3-ccheck-claim,
.ks3-dark .ks3-ccheck-ask { color: var(--ks3-on-dark); }
.ks3-dark .ks3-ccheck-evidence {
  background: rgba(255, 255, 255, .06);
  color: var(--ks3-on-dark-body);
}
.ks3-dark .ks3-ccheck-evlabel { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-ccheck-tally { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-ccheck-fault {
  border-color: var(--ks3-on-dark-muted);
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-ccheck-mark {
  border-color: var(--ks3-on-dark-muted);
  color: var(--ks3-on-dark-muted);
}
/* ⊕ MRB-257 · audit 3.8 — THE LETTER GOES ON-DARK WHEN THE BUTTON IS SPENT,
   and the dim alone could not have carried this. After "Check it" the fault
   buttons lock; the unpicked ones dim, and this is the state where the student
   reads what was wrong. At .5 the 18px fault TEXT measured 3.96:1 and the
   14px/800 A/B/C/D mark measured 2.70:1 — two different numbers because they
   are two different colours under one opacity. `--ks3-dim-spent` .65 takes the
   text to ~5.5:1 on its own; the mark, starting from `--ks3-on-dark-muted`,
   would only reach ~3.5. So the mark takes `--ks3-on-dark` in this state and
   lands at ~5.5:1 with it. The PICKED button's alert mark below is untouched
   — it is not dimmed and it was never the problem. */
.ks3-dark .ks3-ccheck-fault[disabled][aria-pressed="false"] .ks3-ccheck-mark {
  color: var(--ks3-on-dark);
}
/* ⚖️ THE ONLY STATE A FAULT BUTTON HAS. Alert border, alert letter, a lifted
   ground — and the label colour does not move, because a chosen option is not
   a right one. If a rule below this line ever resolves to `--ks3-ok` #12A150
   or `--ks3-danger` #FF6B6B, the bench has started marking.

   ⊕ MRB-297 · 1 Sep 2026 — "ALERT BORDER, ALERT LETTER" IS NOW ACCENT
   BORDER, ACCENT-BORDERED LETTER. Mide's 30 Aug ruling moves selection to
   the accent; the paragraph above is kept because the part that matters —
   the label colour does not move, and nothing here marks — is untouched by
   it. The 30 Aug pass moved the BUTTON and left the LETTER on amber, so the
   block ran for two days with two different "on" colours in it, which is the
   precise thing `ks3_parity`'s own note on these rows exists to stop.
   ⚠️ The accent is not available as a 14px/800 glyph colour (4.49:1 on ink,
   3.18:1 on the panel), so the letter carries the accent as its BORDER and
   takes `--ks3-on-dark` as its glyph — the platform's migrated-badge shape
   at `.ks3-dark .ks3-option[aria-pressed="true"] .ks3-opt-mark`. */
.ks3-dark .ks3-ccheck-fault[aria-pressed="true"] {
  border-color: var(--ks3-accent);
  background: rgba(255, 255, 255, .10);
}
.ks3-dark .ks3-ccheck-fault[aria-pressed="true"] .ks3-ccheck-mark {
  border-color: var(--ks3-accent);
  color: var(--ks3-on-dark);
}
/* ⚠️ (0,2,0), FOUR TIMES. All four sit on the CREAM ground inside the ink
   block; unscoped, `.ks3-dark p` paints each of them #E7DECE on #FBF3E6 at
   about 1.2:1. The word is the accent eyebrow — never `--ks3-ok`, never
   `--ks3-danger` — because the reveal opens whichever way the pick went. */
.ks3-dark .ks3-ccheck-word { color: var(--ks3-accent-text); }
.ks3-dark .ks3-ccheck-answer { color: var(--ks3-ink); }
.ks3-dark .ks3-ccheck-why,
.ks3-dark .ks3-ccheck-settle { color: var(--ks3-ink-body); }

/* ═══ END B6 ═══ */

/* ═══ BEGIN B5 ═══ */

/* ── B5 · Reproduction (⊕ MRB-244) ──
   Eight instruments, and every one of them on ink.

   ⚠️ SPECIFICITY, AND ON THIS UNIT IT IS LOAD-BEARING EIGHT TIMES OVER.
   `.ks3-dark p` is (0,1,1) and every bare class below is (0,1,0), so an
   unscoped `color` LOSES. All eight B5 practicals are
   `ks3-block ks3-dark ks3-practical` — measured off Design's own markup on
   b5-01 `#s-jobs`, b5-02 `#s-compare`, b5-03 `#s-dial`, b5-04 `#s-cross`,
   b5-05 `#s-cross`, b5-06 `#s-parts`, b5-07 `#s-becomes` and b5-08 `#s-sort`,
   no exceptions — so the trap takes the whole unit in one pass and each page
   still looks tidy. Every colour rule below is written under `.ks3-dark …` at
   (0,2,0); geometry is safe unscoped, because `.ks3-dark p` sets colour and
   nothing else. This has now bitten NINE builds; on B4 it shipped a chain
   label at 1.21:1 — invisible — past a green build, a green kinds gate and a
   green key audit.

   ⊕ THE LIST BELOW IS OF ELEMENTS, NOT OF PANELS, and B4 is why. Counting
   panels is what let `.ks3-bell-chainlabel` ship at 1.21:1: the panel was on
   the list, its steps and its note were rescued, and a seventh `<p>` inside
   the same panel taking a different colour was not. So — the CREAM-INSIDE-INK
   ELEMENTS in B5, each with its own `.ks3-dark …` rule at (0,2,0), and all
   seven of them inside `.ks3-b5c-reveal`, which is the ONE cream panel this
   unit draws (five times, because five blocks share it):

     `.ks3-b5c-word` · `.ks3-b5c-answer` · `.ks3-b5c-why` ·
     `.ks3-b5c-tell` · `.ks3-b5c-telllabel` · `.ks3-b5c-winlabel` ·
     `.ks3-b5c-wintext` · `.ks3-b5c-winticks span`

   Add a text element to that panel and it joins this list in the same commit.
   Adding it to the panel's own rule is not the same thing.

   ⚠️ AND RE-MEASURE THE TOKEN YOU CHOSE ON THE GROUND IT LANDS ON. B4 proved
   the cascade bug can MASK a wrong token: `.ks3-gas-cap` measured a healthy
   8.77:1 only because `.ks3-dark p` was winning, and the authored colour would
   have been 1.78:1. Fixing the specificity alone would have turned a lucky
   pass into a real failure, and the fix would have looked exactly like the
   defect. Every colour below was read out of `shared/tokens.css` and each
   cream-panel row is pinned on colour AND background in `ks3_parity.py` —
   either alone passes with the panel painted the wrong way round.

   ⚠️ NO `display` ON ANYTHING THAT ALSO TAKES `[hidden]`. An author `display`
   beats the UA stylesheet's `[hidden] { display: none }` regardless of
   specificity — the defect that has now cost eight builds (⊕ MRB-242). The
   elements hidden at rest in this unit are: `.ks3-b5c-item`, `.ks3-b5c-opts`,
   `.ks3-b5c-reveal`, the two `.ks3-b5c-word span`s, `.ks3-cmp-why` and
   `.ks3-dial-phasedata`. Not one is given a `display` here.

   Three of them are worth saying out loud, because each was a near miss:

     · `.ks3-b5c-opts` exists ONLY so that the `<ul class="ks3-options">`
       inside it does not have to be the hidden element. `.ks3-options` IS
       `display: flex` — shipped, shared, and used by every predict in the key
       stage — so hiding the list directly would have been the MRB-242 defect
       with a component nobody would think to check.
     · `.ks3-b5c-headrow` is the flex row and it NEVER carries `[hidden]`; its
       parent `.ks3-b5c-item` does. Same arrangement as `.ks3-ccheck-evlabel`.
     · `.ks3-cmp-cap` and `.ks3-cmp-head` swap `display` in a media query and
       neither ever carries the attribute. The captions are real elements
       rather than `content:` on a pseudo-element, because a screen reader
       reads them at every width and generated content is not reliably
       announced.

   ⚠️ AND NOTHING IN THIS SECTION ANIMATES OR TRANSITIONS. NOTES-B5 §2 says it
   of the unit — all DOM-only, no timer, no canvas — so there is no rAF tick
   to consult `prefers-reduced-motion` inside (MRB-220 R4, the b2-03 slip), and
   deliberately nothing here to degrade either. Design's pages carry two motion
   rules: `[data-arrive]` on a panel the runtime already unhides, and
   `[data-scalebar]` on a bar whose width never changes after load. Adopting
   either would create a reduced-motion obligation in order to interpolate
   something no student can see move. */

/* ═══ the commit family ═══════════════════════════════════════════════════
   b5-01 `#s-jobs`, b5-04 `#s-cross`, b5-05 `#s-cross`, b5-06 `#s-parts` and
   b5-08 `#s-sort` are ONE component drawn five times, and NOTES-B5 §6 makes
   the repetition load-bearing: "b5-05 reuses b5-04's instrument shape
   deliberately … If Code refactors either one, keep them identical — the
   repetition is the argument." One namespace is what makes drifting apart
   impossible rather than merely discouraged.

   ⊕ ONE TAB TREATMENT, AND IT IS THE MAJORITY ONE — reported as drift, not
   silently reconciled. Design draws the tab chip with `seg()` on b5-03,
   b5-04, b5-05, b5-06 and b5-08 (mono 15px/500, 11px radius, ink-panel text
   when chosen) and with a bespoke inline style on b5-01 alone (17px/700,
   `--ks3-r-control`, ink text when chosen). Five blocks that must stay
   identical cannot carry two chip designs, and one stylesheet serves 183
   lesson slots, so the five-page treatment wins and b5-01's is the drift. */
.ks3-b5c { margin-top: 22px; }
.ks3-b5c-tabs {
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
.ks3-b5c-tab {
  cursor: pointer;
  font: inherit;
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
  padding: 10px 16px;
  min-height: var(--ks3-tap);
  border-radius: 11px;
  border: 2px solid transparent;
  background: transparent;
}
.ks3-b5c-panel {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
}
/* ⚠️ NO `display` ON `.ks3-b5c-item`: all but one ship `hidden`, and a `<div>`
   is a block without help. The flex row is the child, and it is never hidden. */
.ks3-b5c-headrow {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
}
.ks3-b5c-name {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 26px;
  line-height: 1.15;
  letter-spacing: -.025em;
}
.ks3-b5c-meta {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
}
.ks3-b5c-context {
  margin: 10px 0 0;
  font-size: 18px;
  line-height: 1.55;
}
.ks3-b5c-ask {
  margin: 20px 0 9px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
}
/* ⚠️ NO `display` ON `.ks3-b5c-opts` — that is its entire reason for
   existing. See the header. */
.ks3-b5c-foot {
  margin-top: 18px;
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}
.ks3-b5c-check[disabled] { opacity: .45; cursor: default; }
.ks3-b5c-check[disabled]:hover { transform: none; }
.ks3-b5c-hint {
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
}
/* ⚠️ NO `display` HERE. `.ks3-b5c-reveal` is emitted `hidden` five or eight
   times per page, and so is each of the two words inside `.ks3-b5c-word`. */
.ks3-b5c-reveal {
  margin-top: 20px;
  padding: 24px 26px;
  border-radius: var(--ks3-r-panel);
}
.ks3-b5c-word {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.ks3-b5c-answer {
  margin: 9px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 23px;
  line-height: 1.22;
  letter-spacing: -.02em;
}
.ks3-b5c-why { margin: 11px 0 0; font-size: 18px; line-height: 1.6; }
/* b5-08 only — the observable that settles it, on a line of its own because
   it is the thing being taught (NOTES-B5 §2.6). */
.ks3-b5c-tell {
  margin: 12px 0 0;
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
}
/* A caption INSIDE the deciding-feature line, so it needs a block of its own —
   and it never carries `[hidden]`, its ancestor does. */
.ks3-b5c-telllabel {
  display: block;
  margin-bottom: 4px;
  font-size: 12px;
  letter-spacing: .08em;
  text-transform: uppercase;
}
/* b5-05 only — the 0–40 week window under the why. A SECOND claim, and not
   the verdict: it says when an exposure matters most, which is a different
   question from whether it crosses. */
.ks3-b5c-window {
  margin-top: 16px;
  padding-top: 14px;
  border-top: 2px solid var(--ks3-rule);
}
.ks3-b5c-winlabel {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.ks3-b5c-wintrack {
  position: relative;
  height: 22px;
  margin-top: 8px;
  border-radius: 8px;
  overflow: hidden;
}
.ks3-b5c-winfill {
  position: absolute;
  top: 0;
  bottom: 0;
}
.ks3-b5c-winticks {
  display: flex;
  justify-content: space-between;
  margin-top: 5px;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
}
.ks3-b5c-wintext { margin: 9px 0 0; font-size: 17px; line-height: 1.5; }

/* ── the commit family on ink ──────────────────────────────────────────── */
.ks3-dark .ks3-b5c-tab {
  border-color: var(--ks3-on-dark-muted);
  color: var(--ks3-on-dark-body);
}
/* ⚖️ A CHOSEN TAB IS ALERT AND NOTHING ELSE (MRB-196 R10). ⊕ SUPERSEDED 30 Aug 2026 — selection is now the ACCENT (see the platform block at `.ks3-dark .ks3-option`); this paragraph is kept for its reasoning, not its colour.

   Choosing which
   structure to look at is not answering anything, so if this rule ever
   resolves to `--ks3-ok` #12A150 or `--ks3-danger` #FF6B6B the bench has
   started marking a control that has no right answer. */
.ks3-dark .ks3-b5c-tab[aria-pressed="true"] {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-accent);
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-b5c-panel { background: var(--ks3-dark-panel); }
.ks3-dark .ks3-b5c-name { color: var(--ks3-on-dark); }
.ks3-dark .ks3-b5c-meta { color: var(--ks3-alert); }
.ks3-dark .ks3-b5c-context { color: var(--ks3-on-dark-body); }
.ks3-dark .ks3-b5c-ask,
.ks3-dark .ks3-b5c-hint { color: var(--ks3-on-dark-muted); }
/* ⊕ CORRECTED FROM THE SHIPPED STYLESHEET, and reported — the same correction
   `.ks3-plate-open` and `.ks3-ledger-clear` already carry. `.ks3-reveal-btn`
   is ink on an ink border, which on a `.ks3-dark` block whose ground IS
   `--ks3-ink` paints an invisible control. Design's B5 pages inherit that
   defect from `_ds`, which re-ships this very stylesheet; the inverse keeps
   the contrast the light button has. */
.ks3-dark .ks3-b5c-check {
  background: var(--ks3-on-dark);
  border-color: var(--ks3-on-dark);
  color: var(--ks3-ink);
}
/* ⚠️ THE REVEAL IS CREAM INSIDE THE INK BLOCK, and these are the eight rules
   that would ship broken unscoped — see the header. Pinned on colour AND
   background in `ks3_parity.py`, because either alone passes with the panel
   painted the wrong way round. */
.ks3-dark .ks3-b5c-reveal { background: var(--ks3-ground); }
/* ⚖️ THE VERDICT WORD IS NOT A MARK. It is the accent eyebrow whether the
   student had it or not — never `--ks3-ok`, never `--ks3-danger` — because
   the reveal opens either way and names the right answer in full. Only the
   mastery ladder marks correctness. */
.ks3-dark .ks3-b5c-word { color: var(--ks3-accent-text); }
.ks3-dark .ks3-b5c-answer,
.ks3-dark .ks3-b5c-why,
.ks3-dark .ks3-b5c-wintext { color: var(--ks3-ink); }
.ks3-dark .ks3-b5c-tell,
.ks3-dark .ks3-b5c-telllabel,
.ks3-dark .ks3-b5c-winlabel { color: var(--ks3-ink-muted); }
.ks3-dark .ks3-b5c-winticks span { color: var(--ks3-ink-muted); }
.ks3-dark .ks3-b5c-wintrack { background: var(--ks3-band); }
.ks3-dark .ks3-b5c-winfill { background: var(--ks3-accent); }

/* ═══ the comparison-row family ══════════════════════════════════════════
   b5-02 `#s-compare` and b5-07 `#s-becomes`: one table drawn twice, so the
   plant and the animal sit in the same shape (NOTES-B5 §1).

   ⚖️ THE WHOLE ROW IS THE BUTTON — NOTES-B5 §2.5, in terms: "the whole row is
   the button, as in `gamete-compare`. No separate chevron control." So the
   `<button>` wraps all three cells and the tap target is the row's full
   width. A chevron would be a 44px target inside a 700px row.

   ⚖️ AND THE LEAD COLUMN IS NOT ALWAYS THE FIRST. `[data-lead]` marks the
   column Design paints in the alert: the SPERM on b5-02, and AFTER
   FERTILISATION on b5-07. Hard-coding the first data column would put b5-07's
   emphasis on the flower that no longer exists. */
.ks3-cmp { margin-top: 22px; }
.ks3-cmp-table {
  border-radius: var(--ks3-r-card);
  border: 2px solid var(--ks3-on-dark-muted);
  overflow: hidden;
}
.ks3-cmp-head { padding: 13px 16px; }
.ks3-cmp-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 14px;
  align-items: flex-start;
  width: 100%;
}
.ks3-cmp-name {
  flex: 0 0 158px;
  display: block;
  font-size: 17px;
  font-weight: 700;
}
.ks3-cmp-head .ks3-cmp-name {
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
}
.ks3-cmp-cell {
  flex: 1 1 210px;
  min-width: 0;
  display: block;
}
.ks3-cmp-val { display: block; font-size: 17px; line-height: 1.45; }
.ks3-cmp-head .ks3-cmp-val {
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 17px;
}
/* Below 880px the header row goes and these take over. Neither element ever
   carries `[hidden]`, so the swap cannot collide with MRB-242. */
.ks3-cmp-cap { display: none; }
.ks3-cmp-btn {
  display: block;
  width: 100%;
  text-align: left;
  cursor: pointer;
  font: inherit;
  padding: 15px 16px;
  min-height: var(--ks3-tap);
  border: 0;
  background: transparent;
  color: inherit;
}
/* ⚠️ NO `display` ON `.ks3-cmp-why`: every one of them ships `hidden`, and a
   `<p>` is a block without help. */
.ks3-cmp-why { margin: 0; padding: 15px 18px 18px; font-size: 18px; line-height: 1.6; }
.ks3-cmp-whylabel { font-family: var(--ks3-font-display); }
.ks3-cmp-scale {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-card);
}
.ks3-cmp-scalelabel {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
}
.ks3-cmp-scalelist {
  list-style: none;
  margin: 14px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.ks3-cmp-scalehead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.ks3-cmp-scalename { margin: 0; font-size: 17px; font-weight: 600; }
.ks3-cmp-scalesize {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 16px;
  font-weight: 500;
}
.ks3-cmp-scaletrack {
  display: block;
  margin-top: 6px;
  height: 20px;
  border-radius: var(--ks3-r-pill);
  overflow: hidden;
}
.ks3-cmp-scalebar { display: block; height: 100%; }
.ks3-cmp-scalenote { margin: 14px 0 0; font-size: 18px; line-height: 1.6; }

/* ── the comparison rows on ink ────────────────────────────────────────── */
.ks3-dark .ks3-cmp-table { background: var(--ks3-dark-panel); }
.ks3-dark .ks3-cmp-head { background: rgba(0, 0, 0, .28); }
.ks3-dark .ks3-cmp-head .ks3-cmp-name { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-cmp-row { border-top: 2px solid rgba(255, 255, 255, .12); }
.ks3-dark .ks3-cmp-row[data-alt] { background: rgba(255, 255, 255, .03); }
/* ⚖️ AN OPEN ROW IS TINTED, NOT MARKED. Opening a row is not answering
   anything — every row's `why` is true — so this is the accent's own wash and
   never `--ks3-ok` or `--ks3-danger`. Last, so it beats the zebra. */
.ks3-dark .ks3-cmp-row[data-open] { background: rgba(228, 87, 46, .10); }
.ks3-dark .ks3-cmp-name { color: var(--ks3-on-dark); }
.ks3-dark .ks3-cmp-val { color: var(--ks3-on-dark-body); }
.ks3-dark .ks3-cmp-cell[data-lead] .ks3-cmp-val { color: var(--ks3-alert); }
/* ⊕ MRB-257 · audit 3.2 — NO ACCENT-TEXT TOKEN ON A DARK GROUND, EVER.
   `--ks3-accent-text` #A93411 is specified as the small-text partner ON CREAM
   ("the only orange under 24px"); landed on `--ks3-dark-panel` it measures
   1.78:1, and 1.62:1 on the zebra row. That is the same class of error as the
   on-dark green MRB-252 rules on, one token along.

   It matters more here than a contrast number suggests. Below 880px the
   header row above is `display: none` and THESE CAPTIONS ARE THE ONLY THING
   SAYING WHICH COLUMN IS WHICH — "SPERM CELL" / "EGG CELL", "BEFORE" /
   "AFTER FERTILISATION". On a 390px phone the block collapses to stacked
   pairs and its entire teaching point rests on four strings at 1.78:1. */
.ks3-dark .ks3-cmp-cap { color: var(--ks3-on-dark); }
.ks3-dark .ks3-cmp-why { color: var(--ks3-on-dark-body); }
.ks3-dark .ks3-cmp-whylabel { color: var(--ks3-on-dark); }
.ks3-dark .ks3-cmp-scale { background: var(--ks3-dark-panel); }
.ks3-dark .ks3-cmp-scalelabel,
.ks3-dark .ks3-cmp-scalesize { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-cmp-scalename { color: var(--ks3-on-dark); }
.ks3-dark .ks3-cmp-scaletrack { background: rgba(255, 255, 255, .08); }
.ks3-dark .ks3-cmp-scalebar { background: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-cmp-scalebar[data-lead] { background: var(--ks3-alert); }
.ks3-dark .ks3-cmp-scalenote { color: var(--ks3-on-dark-body); }

@media (max-width: 880px) {
  .ks3-cmp-head { display: none; }
  .ks3-cmp-grid > .ks3-cmp-name { flex: 1 1 100%; }
  .ks3-cmp-cap {
    display: block;
    margin: 0 0 3px;
    font-family: var(--ks3-font-mono);
    font-size: 12px;
    font-weight: 500;
    letter-spacing: .07em;
    text-transform: uppercase;
  }
}

/* ═══ cycle-dial (b5-03 #s-dial) ═════════════════════════════════════════
   ⚖️ THE RELEASE MARKER IS THE INSTRUMENT. Everything else on this bench is
   scaffolding for one observation: change the cycle length and the alert line
   moves, while the fortnight after it does not. The marker is 2px of
   `--ks3-alert` on a lifted track and it is the only alert figure inside the
   panel, because it is the only thing that answers the lesson's question.

   ⚠️ THE SHED BAND IS DRAWN IN THE ACCENT AT 30%, NOT IN THE ALERT. Two
   alert-coloured regions on one 46px track and the eye cannot tell which one
   is being asked about. */
.ks3-dial { margin-top: 22px; }
.ks3-dial-lenlabel {
  margin: 0 0 9px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
}
.ks3-dial-lens { display: flex; gap: 9px; flex-wrap: wrap; }
.ks3-dial-len {
  cursor: pointer;
  font: inherit;
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
  padding: 10px 16px;
  min-height: var(--ks3-tap);
  border-radius: 11px;
  border: 2px solid transparent;
  background: transparent;
}
.ks3-dial-panel {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-card);
}
.ks3-dial-track {
  position: relative;
  height: 46px;
  border-radius: 12px;
  overflow: hidden;
}
.ks3-dial-shed { position: absolute; top: 0; bottom: 0; left: 0; }
.ks3-dial-release { position: absolute; top: 0; bottom: 0; width: 2px; }
.ks3-dial-marker {
  position: absolute;
  top: 4px;
  bottom: 4px;
  width: 12px;
  margin-left: -6px;
  border-radius: 6px;
}
.ks3-dial-ticks {
  display: flex;
  justify-content: space-between;
  margin-top: 7px;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
}
.ks3-dial-controls {
  margin-top: 16px;
  display: flex;
  align-items: center;
  gap: 12px;
}
.ks3-dial-step {
  flex: 0 0 var(--ks3-tap);
  width: var(--ks3-tap);
  height: var(--ks3-tap);
  display: grid;
  place-items: center;
  cursor: pointer;
  font: inherit;
  font-size: 22px;
  font-weight: 700;
  border-radius: 12px;
  border: 2px solid transparent;
  background: transparent;
}
.ks3-dial-slider { flex: 1 1 auto; min-width: 0; margin: 0; }
.ks3-dial-readrow {
  margin-top: 18px;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
}
.ks3-dial-day {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 30px;
  line-height: 1.1;
  letter-spacing: -.03em;
}
.ks3-dial-phase {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
}
.ks3-dial-cells {
  margin-top: 16px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 14px;
}
.ks3-dial-cell { padding: 18px 20px; border-radius: var(--ks3-r-panel); }
.ks3-dial-celllabel {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.ks3-dial-celltext { margin: 7px 0 0; font-size: 18px; line-height: 1.55; }
.ks3-dial-note { margin: 16px 0 0; font-size: 18px; line-height: 1.6; }

/* ── the dial on ink ───────────────────────────────────────────────────── */
.ks3-dark .ks3-dial-lenlabel { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-dial-len {
  border-color: var(--ks3-on-dark-muted);
  color: var(--ks3-on-dark-body);
}
/* ⚖️ CHOOSING A CYCLE LENGTH IS NOT ANSWERING ANYTHING (MRB-196 R10). Alert,
   and never `--ks3-ok` or `--ks3-danger`: there is no right length. ⊕ SUPERSEDED 30 Aug 2026 — selection is now the ACCENT (see the platform block at `.ks3-dark .ks3-option`); this paragraph is kept for its reasoning, not its colour. */
.ks3-dark .ks3-dial-len[aria-pressed="true"] {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-accent);
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-dial-panel { background: var(--ks3-dark-panel); }
.ks3-dark .ks3-dial-track { background: rgba(255, 255, 255, .07); }
.ks3-dark .ks3-dial-shed { background: rgba(228, 87, 46, .30); }
/* ⚖️ THE ONE ALERT LINE ON THE TRACK, and the whole instrument's argument.
   If this ever resolves to the track colour the release day has become
   invisible and the block is a slider with two paragraphs under it. */
.ks3-dark .ks3-dial-release { background: var(--ks3-alert); }
.ks3-dark .ks3-dial-marker {
  background: var(--ks3-on-dark);
  box-shadow: 0 0 0 3px rgba(0, 0, 0, .35);
}
.ks3-dark .ks3-dial-ticks { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-dial-step {
  border-color: var(--ks3-on-dark-muted);
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-dial-day { color: var(--ks3-on-dark); }
.ks3-dark .ks3-dial-phase { color: var(--ks3-alert); }
.ks3-dark .ks3-dial-cell { background: rgba(0, 0, 0, .24); }
.ks3-dark .ks3-dial-celllabel { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-dial-celltext { color: var(--ks3-on-dark-body); }
.ks3-dark .ks3-dial-note { color: var(--ks3-on-dark-muted); }

/* ═══ END B5 ═══ */


/* ═══ BEGIN B7 ═══ */

/* ── B7 · Photosynthesis (⊕ MRB-245) ──
   Four instruments, and every one of them on ink.

   ⚠️ SPECIFICITY, AND ON THIS UNIT IT IS LOAD-BEARING FOUR TIMES OVER.
   `.ks3-dark p` is (0,1,1) and every bare class below is (0,1,0), so an
   unscoped `color` LOSES. All four B7 practicals are
   `ks3-block ks3-dark ks3-practical` — measured off Design's own markup on
   b7-01 `#s-bench`, b7-02 `#s-tuner`, b7-03 `#s-bench` and b7-04 `#s-trace`,
   no exceptions — so the trap takes the whole unit in one pass. Every colour
   rule below is written under `.ks3-dark …` at (0,2,0); geometry is safe
   unscoped, because `.ks3-dark p` sets colour and nothing else.

   ⊕ THE LIST BELOW IS OF ELEMENTS, NOT OF PANELS, and B4 is why. Counting
   panels is what let `.ks3-bell-chainlabel` ship at 1.21:1: the panel was on
   the list, its steps and its note were rescued, and a seventh `<p>` inside
   the same panel taking a different colour was not. So — the CREAM-INSIDE-INK
   ELEMENTS in B7, each with its own `.ks3-dark …` rule at (0,2,0):

     `.ks3-rr-tag` · `.ks3-rr-head` · `.ks3-rr-why` ·
     `.ks3-lt-verdictlabel` · `.ks3-lt-head` · `.ks3-lt-why` ·
     `.ks3-mb-tag` · `.ks3-mb-head` · `.ks3-mb-why` · `.ks3-mb-conclude` ·
     `.ks3-tb-verdict`

   Add a text element to any cream panel in this unit and it joins this list in
   the same commit. Adding it to the panel's rule is not the same thing.

   ⚠️ AND RE-MEASURE THE TOKEN YOU CHOSE ON THE GROUND IT LANDS ON. B4 proved
   the cascade bug can MASK a wrong token: `.ks3-gas-cap` measured a healthy
   8.77:1 only because `.ks3-dark p` was winning, and the authored colour would
   have been 1.78:1. Fixing the specificity alone would have turned a lucky
   pass into a real failure, and the fix would have looked exactly like the
   defect.

   ⚠️ NO `display` ON ANYTHING THAT ALSO TAKES `[hidden]`. An author `display`
   beats the UA stylesheet's `[hidden] { display: none }` regardless of
   specificity — the defect that has now cost eight builds, and which
   `_JS_HIDDEN_AUDIT` now catches by dropping the inline `display` ks3.js
   writes and asking the stylesheet directly. The elements hidden at rest in
   this unit are `.ks3-rr-verdict`, `.ks3-lt-verdict`, `.ks3-mb-verdict`,
   `.ks3-tb-food`, `.ks3-tb-verdict` and `.ks3-tb-note`. Three of them need a
   `display` when they ARE shown and take a `[hidden]` guard first;
   `.ks3-tb-note` needs one and gets it for free, by being a flex item of
   `.ks3-tb-linkmain` — a flex item is blockified, and an element the UA has
   set to `display: none` is not a flex item at all.

   ⚠️ NOTHING HERE TRANSITIONS OR ANIMATES. NOTES-B7 §3 says it of the unit and
   all four pages bear it out, so there is no reduced-motion obligation created
   by this section. `.ks3-option`'s own 160ms background transition is the
   platform's and is inherited by the dials; gate 7 exists because of it. */

/* The dial rows shared by the remover and the tuner, and the step rows on the
   method breaker. `.ks3-options` is a COLUMN by default, because an answer
   list is a column; a dial is a segmented control and reads across. */
.ks3-rr-dials,
.ks3-lt-dials {
  margin-top: 22px;
  display: flex;
  flex-wrap: wrap;
  gap: 20px 30px;
}
.ks3-rr-dialname,
.ks3-lt-dialname {
  margin: 0 0 9px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
}
.ks3-rr-opts,
.ks3-lt-opts,
.ks3-mb-opts,
.ks3-tb-tabs {
  margin-top: 0;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 9px;
}
.ks3-rr-opt,
.ks3-lt-opt,
.ks3-mb-opt,
.ks3-tb-tab {
  width: auto;
  padding: 10px 15px;
  font-size: 16px;
  font-weight: 700;
}
/* ⚖️ A CHOSEN SETTING IS THE ALERT GROUND, NOT THE ALERT BORDER, and that is
   Design's measurement rather than a preference: her `seg()` paints the
   chosen segment `background: var(--ks3-alert); color: var(--ks3-ink)`. The
   platform's `.ks3-dark .ks3-option[aria-pressed="true"]` gives an alert
   BORDER on the dark panel, which is right for an answer that has been
   committed to and wrong for a switch that is currently on: four dials with
   four thin borders do not read as a state at a glance. Same specificity
   (0,3,0), later in the file, so source order decides — deliberately, because
   this is a narrowing of the platform rule to one unit and not a replacement
   of it.

   ⚠️ AND IT IS STILL NOT A MARK (MRB-196 R10). Alert is "this is the setting
   you have chosen", never "this is correct". Nothing on these benches is
   right or wrong; only the ladder marks.

   ⊕ SUPERSEDED 30 Aug 2026 — THE COLOUR, NOT THE ARGUMENT. Mide's ruling
   moves selection to `--ks3-accent`; amber is warning and loss. Both
   paragraphs above are kept for their reasoning, which still holds, and not
   for their colour, which does not.

   ⊕ MRB-297 · 1 Sep 2026 — AND THE ARGUMENT WENT WITH THE COLOUR. The
   30 Aug recolour dropped the narrowing as well as the amber, so these four
   dials fell through to the platform rule and shipped as the very thing the
   first paragraph rejects: a thin outline on the same ground as its unchosen
   siblings, chosen and unchosen both `--ks3-dark-panel`. Driven at 1280px
   and screenshotted before and after.

   What is restored is the NARROWING, at the ruled colour. The fill is the
   accent at .22 over the ink block — an alpha the file already carries at
   `.ks3-oflow-body.is-hot`, so no colour is introduced. Measured on the
   composite (#4D2B1F): the `--ks3-on-dark` label lands 11.3:1, and the 2px
   accent border 3.4:1 on the fill and 4.5:1 on the ink ground behind, both
   clear of the 3:1 non-text floor (WCAG 1.4.11).

   ⚠️ AN ACCENT SLAB IS NOT AVAILABLE HERE, and that is measurement rather
   than taste: `--ks3-ink` on `--ks3-accent` is 4.49:1, under the 4.5:1 floor
   for a 16px/700 label. See the amendment on `--ks3-alert` in
   `shared/tokens.css`. The estate's lifted-ground idiom
   (`rgba(255, 255, 255, .06)`, the #4A433C tile) was tried first, as the
   cheaper fix, and rendered indistinguishable from the outline: a 1.17:1
   lift is not a state at a glance. .30 was tried too and dropped — it takes
   the border to 3.03:1, which passes with no margin at all. */
.ks3-dark .ks3-rr-opt[aria-pressed="true"],
.ks3-dark .ks3-lt-opt[aria-pressed="true"],
.ks3-dark .ks3-mb-opt[aria-pressed="true"],
.ks3-dark .ks3-tb-tab[aria-pressed="true"] {
  background: rgba(228, 87, 46, .22);
  border-color: var(--ks3-accent);
  color: var(--ks3-on-dark);
}
/* The four bench buttons are `.ks3-reveal-btn`, which is INK ON INK — on a
   `ks3-dark` block that paints an invisible control. Inverted here, exactly as
   `.ks3-dark .ks3-b5c-check` is. */
.ks3-dark .ks3-rr-test,
.ks3-dark .ks3-rr-reset,
.ks3-dark .ks3-lt-oak,
.ks3-dark .ks3-lt-reset,
.ks3-dark .ks3-mb-run,
.ks3-dark .ks3-mb-reset,
.ks3-dark .ks3-tb-back,
.ks3-dark .ks3-tb-reset {
  background: var(--ks3-on-dark);
  border-color: var(--ks3-on-dark);
  color: var(--ks3-ink);
}

/* The nested dark panel every one of the four benches puts its readouts in. */
.ks3-rr-panel,
.ks3-lt-panel,
.ks3-mb-panel,
.ks3-tb-panel {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-card);
}
.ks3-dark .ks3-rr-panel,
.ks3-dark .ks3-lt-panel,
.ks3-dark .ks3-mb-panel,
.ks3-dark .ks3-tb-panel { background: var(--ks3-dark-panel); }

.ks3-rr-foot,
.ks3-lt-foot,
.ks3-mb-foot,
.ks3-tb-foot {
  margin-top: 20px;
  display: flex;
  align-items: center;
  gap: 13px;
  flex-wrap: wrap;
}

/* ⚖️ THE THREE BAR TRACKS ARE ONE COMPONENT. Design draws them at 18px on the
   remover and 20px on the tuner; one height is taken, because a 2px difference
   between two pages nobody sees side by side is not worth a second component,
   and the two would drift. The track is a wash of the block's own ink rather
   than a token, which is Design's `rgba(255,255,255,.08)` — measured. */
/* ⊕ MRB-277, 21 Aug 2026 — THE TRACK, NOT THE FILL, AND THE TOKEN ALREADY
   EXISTED. These bars measured 2.72:1 against their track: `--ks3-ok` on a
   `rgba(255,255,255,.08)` veil over `--ks3-dark-panel`. That is under the
   3:1 bar WCAG 1.4.11 sets for a graphical object that carries meaning —
   and on b7-01, b7-02 and b8-03 these bars ARE the measurement the lesson
   is about, so a student who cannot separate the bar from its track cannot
   read the lesson.

   THE FILL COULD NOT MOVE. Token law: `--ks3-ok` is marks and fills only,
   `--ks3-ok-text` / `--ks3-ok-dark` are the body-size greens and are not
   fills, and amber is reserved for warning and loss — so it is not
   available to mean "healthy" here. That leaves the track.

   `--ks3-dark-track` (#3A332E) is the token drawn for precisely this job —
   "progress-bar track on an ink-dark panel" — and these four tracks were
   the ones not using it. So this is not a new value invented to clear a
   threshold; it is four ad-hoc veils joining the system. Measured after:
   3.69:1 for the ok fill, and every other fill on the same track improves
   too, because the track got darker and all three fills are lighter. */
.ks3-rr-track,
.ks3-lt-track {
  display: block;
  margin-top: 6px;
  height: 19px;
  border-radius: 99px;
  background: var(--ks3-dark-track);
  overflow: hidden;
}
.ks3-rr-fill,
.ks3-lt-fill { display: block; height: 100%; }

/* ═══ reactant-remover (b7-01 #s-bench) ══════════════════════════════════
   Four dials, three readouts, one iodine verdict, and rate is the PRODUCT of
   the four — so removing any one takes every readout to its zero string at
   once. The three bars are always the same width as each other, which is the
   whole picture: they are three views of one number. */
.ks3-rr-setuprow {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.ks3-rr-setup { margin: 0; font-size: 20px; font-weight: 700; }
.ks3-rr-rate {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 16px;
  font-weight: 500;
}
.ks3-rr-readouts {
  list-style: none;
  margin: 18px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 13px;
}
.ks3-rr-readrow,
.ks3-lt-readrow {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.ks3-rr-rolabel { margin: 0; font-size: 17px; font-weight: 600; }
.ks3-rr-rovalue {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 17px;
  font-weight: 500;
}
/* ⚖️ THREE BARS, THREE COLOURS, AND THE DISTINCTION IS THE SCIENCE. Design
   gives the oxygen bar `--ks3-ok` because it is the readout you can actually
   watch — bubbles coming off pondweed — and the other two the alert and the
   muted. Three identical bars would say the three readouts are one thing. */
.ks3-rr-readout[data-tone="alert"] .ks3-rr-fill { background: var(--ks3-alert); }
.ks3-rr-readout[data-tone="ok"] .ks3-rr-fill { background: var(--ks3-ok); }
.ks3-rr-readout[data-tone="muted"] .ks3-rr-fill {
  background: var(--ks3-on-dark-muted);
}

/* ⚠️ NO `display` HERE — the verdict panels are emitted `hidden` and the guard
   goes first, so the attribute keeps winning. */
.ks3-rr-verdict[hidden] { display: none; }
.ks3-rr-verdict {
  margin-top: 18px;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ground);
}
.ks3-rr-tag {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.ks3-rr-head {
  margin: 9px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 23px;
  line-height: 1.22;
  letter-spacing: -.02em;
}
.ks3-rr-why { margin: 11px 0 0; font-size: 18px; line-height: 1.6; }

/* ── reactant-remover on ink ───────────────────────────────────────────── */
.ks3-dark .ks3-rr-dialname { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-rr-setup { color: var(--ks3-on-dark); }
.ks3-dark .ks3-rr-rate { color: var(--ks3-alert); }
.ks3-dark .ks3-rr-rolabel { color: var(--ks3-on-dark); }
.ks3-dark .ks3-rr-rovalue { color: var(--ks3-on-dark-muted); }
/* The cream-inside-ink elements. Each one separately, never the panel alone. */
.ks3-dark .ks3-rr-tag { color: var(--ks3-accent-text); }
.ks3-dark .ks3-rr-head { color: var(--ks3-ink); }
.ks3-dark .ks3-rr-why { color: var(--ks3-ink); }

/* ═══ leaf-tuner (b7-02 #s-tuner) ════════════════════════════════════════
   ⚖️ THE BAR IS THE PERCENTAGE HALVED AND CLAMPED AT 100, so a full bar means
   200% of an oak leaf and the OPENING leaf's water bar is already hard against
   the end of its track while its rate bar sits near the middle. That picture is
   the trade-off, drawn, before a word of the verdict is read — which is why the
   two bars take different colours and why neither of them is the accent. */
.ks3-lt-readouts {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 15px;
}
.ks3-lt-rolabel { margin: 0; font-size: 18px; font-weight: 700; }
.ks3-lt-rovalue {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 17px;
  font-weight: 500;
}
.ks3-lt-readout[data-tone="ok"] .ks3-lt-fill { background: var(--ks3-ok); }
.ks3-lt-readout[data-tone="alert"] .ks3-lt-fill { background: var(--ks3-alert); }

/* ⚠️ NO `display` — five of the six verdicts ship `hidden`, and the sixth is
   the one the opening leaf earns. */
.ks3-lt-verdict[hidden] { display: none; }
.ks3-lt-verdict {
  margin-top: 20px;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ground);
}
.ks3-lt-verdictlabel {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.ks3-lt-head {
  margin: 9px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 23px;
  line-height: 1.22;
  letter-spacing: -.02em;
}
.ks3-lt-why { margin: 11px 0 0; font-size: 18px; line-height: 1.6; }

/* ── leaf-tuner on ink ─────────────────────────────────────────────────── */
.ks3-dark .ks3-lt-dialname { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-lt-rolabel { color: var(--ks3-on-dark); }
.ks3-dark .ks3-lt-rovalue { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-lt-verdictlabel { color: var(--ks3-accent-text); }
.ks3-dark .ks3-lt-head { color: var(--ks3-ink); }
.ks3-dark .ks3-lt-why { color: var(--ks3-ink); }

/* ═══ method-breaker (b7-03 #s-bench) ════════════════════════════════════
   Five steps, six outcomes, and an ordered precedence that is the pedagogy:
   safety first, then the faults that DESTROY the result, then the ones that
   only OBSCURE it.

   ⚖️ THE SUB-STEP DIMS WITH ITS PARENT. Step 3b is how the ethanol is heated,
   and skipping step 3 leaves nothing to heat — Design dims the row to `.45`
   and this does the same, from the same fact (`data-parent`, derived from the
   authored `num`). It is not disabled: a student may still press it, and the
   bench simply has no fire to report. */
.ks3-mb-steps {
  list-style: none;
  margin: 22px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.ks3-mb-step {
  display: flex;
  flex-wrap: wrap;
  gap: 12px 16px;
  align-items: center;
  padding: 14px 16px;
  border-radius: var(--ks3-r-panel);
  background: rgba(255, 255, 255, .05);
}
.ks3-mb-step[data-dim] { opacity: .45; }
.ks3-mb-num {
  flex: 0 0 34px;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 10px;
  border: 2px solid transparent;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 15px;
}
.ks3-mb-stepmain {
  flex: 1 1 240px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.ks3-mb-steptitle { font-size: 19px; font-weight: 700; }
.ks3-mb-stepdetail { font-size: 17px; line-height: 1.5; }
/* ⊕ MRB-257 · audit 3.17 — `flex: 0 0 auto` CANNOT SHRINK, and at 320px
   (WCAG 1.4.10 reflow) the step's option row reached right=335 against a 320
   viewport — the only content-level overflow in the key stage. `0 1 auto`
   lets it give way; `min-width: 0` lets the buttons inside it wrap, which
   they already declare. Below 34rem it takes its own row outright rather than
   being squeezed beside the step text. */
.ks3-mb-opts { flex: 0 1 auto; min-width: 0; }

/* ⚠️ NO `display` — every verdict ships `hidden`. */
.ks3-mb-verdict[hidden] { display: none; }
.ks3-mb-verdict {
  margin-top: 18px;
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ground);
  border: 2px solid transparent;
}
/* ⚖️ THE SAFETY BRANCH IS DRAWN AS A SAFETY BRANCH. Its own words say the
   practical has ended and the test never happened; giving it the same panel as
   "the leaf crumbled" would file a fire at head height alongside a spoiled
   pattern. The accent outline is the only per-branch treatment on this bench,
   and it is on the one branch that is not about the quality of the data.
   NOTES-B7 flag 14, MRB-233. */
.ks3-mb-verdict[data-kind="safety"] {
  border-color: var(--ks3-accent);
  box-shadow: var(--ks3-shadow-block);
}
.ks3-mb-tag {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.ks3-mb-head {
  margin: 9px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 23px;
  line-height: 1.22;
  letter-spacing: -.02em;
}
.ks3-mb-why { margin: 11px 0 0; font-size: 18px; line-height: 1.6; }
.ks3-mb-conclude {
  margin: 14px 0 0;
  padding-top: 14px;
  border-top: 2px solid var(--ks3-rule);
  font-size: 18px;
  line-height: 1.6;
}

/* ── method-breaker on ink ─────────────────────────────────────────────── */
.ks3-dark .ks3-mb-num {
  border-color: var(--ks3-on-dark-muted);
  color: var(--ks3-on-dark-muted);
}
.ks3-dark .ks3-mb-steptitle { color: var(--ks3-on-dark); }
.ks3-dark .ks3-mb-stepdetail { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-mb-tag { color: var(--ks3-accent-text); }
.ks3-dark .ks3-mb-head { color: var(--ks3-ink); }
.ks3-dark .ks3-mb-why { color: var(--ks3-ink); }
.ks3-dark .ks3-mb-conclude { color: var(--ks3-ink); }

/* ═══ trace-it-back (b7-04 #s-trace) ═════════════════════════════════════
   Six foods, six chains of different lengths, one destination. The chain is
   revealed BACKWARDS, one link per press.

   ⚖️ EVERY LINK IS DRAWN FROM THE START and only the NOTE arrives on a press.
   A student reads how far there is to go before taking a step, which is the
   sentence the prompt makes to them — the number of steps changes and the
   destination does not — and it is why an unreached link dims rather than
   being absent. */
.ks3-tb-tabsgroup { margin-top: 22px; }
.ks3-tb-tabslabel {
  margin: 0 0 9px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
}
/* ⚠️ NO `display` — five of the six food panels ship `hidden`. */
.ks3-tb-food[hidden] { display: none; }
.ks3-tb-headrow {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.ks3-tb-name { margin: 0; font-size: 21px; font-weight: 700; }
.ks3-tb-steps {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 16px;
  font-weight: 500;
}
.ks3-tb-chain {
  list-style: none;
  margin: 18px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
/* ⊕ MRB-257 · audit 3.5 / 3.7 — `--ks3-dim-ahead`, AND THE AUDIT'S OWN NUMBER
   IS WRONG. It says "raise to ≈opacity: .72 (lands ≈4.5:1)". Measured, .72
   composites `--ks3-on-dark-muted` #C6B9A7 to #A09586 on `--ks3-dark-panel` =
   3.98:1, which still fails the bar the fix was written to clear. .78 lands
   4.35; .80 lands 4.51. The value shipped is the one that does the thing the
   audit asked for, not the one it wrote down.
   The state survives the change: an unreached row is ALSO borderless, and its
   name is muted where a reached one is on-dark, so "not yet" is carried by
   three treatments and not by the dim alone. */
.ks3-tb-link {
  display: flex;
  gap: 14px;
  align-items: flex-start;
  padding: 14px 16px;
  border-radius: var(--ks3-r-panel);
  border: 2px solid transparent;
  opacity: var(--ks3-dim-ahead);
}
.ks3-tb-link[data-shown] { opacity: 1; }
.ks3-tb-num {
  flex: 0 0 30px;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 9px;
  border: 2px solid transparent;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 15px;
}
/* ⚠️ A FLEX CONTAINER, AND THAT IS WHAT KEEPS `[hidden]` WORKING ON THE NOTE.
   Design draws the link name and its note as two blocks stacked in the row's
   right-hand column. Writing that as `display: block` on the note would beat
   the UA's `[hidden] { display: none }` at any specificity and every unrevealed
   note would ship visible — the eight-build defect, and on this instrument it
   would give away every answer on the page. Making the parent a column flex
   container blockifies both children for free, and a child the UA has set to
   `display: none` never becomes a flex item. */
.ks3-tb-linkmain {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.ks3-tb-linkname { font-size: 19px; font-weight: 700; }
.ks3-tb-note { font-size: 18px; line-height: 1.55; }
.ks3-tb-back[disabled] { opacity: .45; cursor: default; }
.ks3-tb-back[disabled]:hover { transform: none; }
/* ⚠️ NO `display` — the verdict lands only when the chain is complete. */
.ks3-tb-verdict[hidden] { display: none; }
.ks3-tb-verdict {
  margin: 18px 0 0;
  padding: 17px 19px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ground);
  font-size: 18px;
  line-height: 1.6;
}

/* ── trace-it-back on ink ──────────────────────────────────────────────── */
.ks3-dark .ks3-tb-tabslabel { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-tb-name { color: var(--ks3-on-dark); }
.ks3-dark .ks3-tb-steps { color: var(--ks3-alert); }
.ks3-dark .ks3-tb-num {
  border-color: var(--ks3-on-dark-muted);
  color: var(--ks3-on-dark-muted);
}
.ks3-dark .ks3-tb-linkname { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-tb-note { color: var(--ks3-on-dark-body); }
/* ⚖️ A REVEALED LINK LIGHTS ITS CHIP AND ITS NAME; the LAST one additionally
   takes the alert border and the lifted ground, because arriving at the
   producer is the thing the instrument is for. Two states, not one. */
.ks3-dark .ks3-tb-link[data-shown] { border-color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-tb-link[data-shown] .ks3-tb-linkname { color: var(--ks3-on-dark); }
.ks3-dark .ks3-tb-link[data-shown] .ks3-tb-num {
  background: var(--ks3-alert);
  border-color: var(--ks3-alert);
  color: var(--ks3-ink);
}
.ks3-dark .ks3-tb-link[data-shown][data-last] {
  background: rgba(255, 255, 255, .10);
  border-color: var(--ks3-alert);
}
.ks3-dark .ks3-tb-verdict { color: var(--ks3-ink); }

/* ═══ the word summary (b7-01 #s-summary) ════════════════════════════════
   ⚠️ THE ARROW IS DRAWN AND `currentColor` IS WHAT COLOURS IT. The `<svg>` is
   stroked `currentColor`, so this one rule is the whole of Design's
   `style="color: var(--ks3-accent-text)"` on the element. A typed U+2192 is
   refused by `r_equation` — the design system's fonts have no glyph for it. */
.ks3-eqn {
  margin-top: 24px;
  padding: 26px 28px;
  border-radius: var(--ks3-r-card);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px 18px;
}
.ks3-eqn-side {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: clamp(20px, 2.4vw, 27px);
  letter-spacing: -.02em;
  color: var(--ks3-ink);
}
.ks3-eqn-arrowwrap {
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;
  color: var(--ks3-accent-text);
}
.ks3-eqn-arrow { display: block; }
/* ⊕ MRB-254 — the arrow with a condition over it and a condition under it.
   A column, so the two labels are centred ON the arrow rather than beside the
   row: the condition is a property of the CHANGE, and the change is the arrow.
   `min-width` on the stack rather than a width on the labels, because the
   longer of the two is what has to fit and which one that is depends on the
   lesson. Mono and uppercase, matching `.ks3-eqn-condition` exactly — it is
   the same voice saying the same kind of thing, moved to where it applies. */
.ks3-eqn-arrowstack {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  gap: 6px;
  color: var(--ks3-accent-text);
}
.ks3-eqn-cond {
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  line-height: 1.25;
  letter-spacing: .04em;
  text-transform: uppercase;
  text-align: center;
  color: var(--ks3-ink-muted);
  max-width: 15ch;
}
/* ⚠️ THE STACK MUST NOT BE THE THING THAT WRAPS. `.ks3-eqn` is a wrapping flex
   row and the two sides are display type; on a 390px phone the row breaks
   between reactants, arrow and products, and an arrow that has left its
   reactants behind still has to carry its own two conditions with it. Which it
   does — they are inside it. This rule only stops the stack being squeezed
   narrower than its own text and pushing a label to four lines. */
@media (max-width: 560px) {
  .ks3-eqn-split { gap: 10px 14px; }
  .ks3-eqn-cond { max-width: 18ch; }
}
/* Full width beneath the row, which is what makes the condition a CONDITION
   rather than a third term in the summary. */
.ks3-eqn-condition {
  margin: 0;
  flex: 1 1 100%;
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}

/* The two dial columns stop being columns when there is no room for two, and
   the method breaker's option pair drops under its own step. Below this the
   settings would be reading in a 90px gutter beside a 240px title. */
@media (max-width: 560px) {
  .ks3-rr-dials,
  .ks3-lt-dials { gap: 18px; }
  .ks3-mb-step { align-items: flex-start; }
}

/* ═══ END B7 ═══ */

/* ═══ BEGIN B8 ═══ */

/* ── B8 · Respiration (⊕ MRB-248) ──
   Five instruments, and every one of them on ink.

   ⚠️ SPECIFICITY, AND ON THIS UNIT IT IS LOAD-BEARING FIVE TIMES OVER.
   `.ks3-dark p` is (0,1,1) and every bare class below is (0,1,0), so an
   unscoped `color` LOSES to the dark ground's default and the component's own
   colour never reaches the page. All five B8 practicals are
   `ks3-block ks3-dark ks3-practical` — measured off Design's own `#s-bench`
   markup on all five pages, character for character, no exceptions.

   ⛔ AND DO NOT TRUST THIS COMMENT. B7's stylesheet carried a section note
   claiming every rule in it was scoped, and the gate found the trap re-laid
   inside that very section hours later. Every colour rule below is written
   under `.ks3-dark …` at (0,2,0) AND `ks3_parity.check_dark_text_specificity()`
   resolves the real cascade winner on every element on every ink ground. The
   comment is a statement of intent; the gate is the evidence.

   ⊕ THE LIST BELOW IS OF ELEMENTS, NOT OF PANELS, and B4 is why. Counting
   panels is what let `.ks3-bell-chainlabel` ship at 1.21:1 — the panel was on
   the list, its steps and its note were rescued, and a seventh `<p>` inside the
   same panel taking a different colour was not. So: the CREAM-INSIDE-INK
   elements in B8, each with its own `.ks3-dark …` rule at (0,2,0):

     `.ks3-ml-exitslabel` · `.ks3-ml-exitname` · `.ks3-ml-exitroute` ·
     `.ks3-ml-close`

   Add a text element to any cream panel in this unit and it joins this list in
   the same commit. Adding it to the panel's rule is not the same thing.

   ⚠️ AND RE-MEASURE THE TOKEN YOU CHOSE ON THE GROUND IT LANDS ON. B4 proved
   the cascade bug can MASK a wrong token: `.ks3-gas-cap` measured a healthy
   8.77:1 only because `.ks3-dark p` was winning, and the authored colour would
   have been 1.78:1. Fixing the specificity alone would have turned a lucky pass
   into a real failure, and the fix would have looked exactly like the defect.

   ⚠️ NO `display` ON ANYTHING THAT ALSO TAKES `[hidden]`. An author `display`
   beats the UA stylesheet's `[hidden] { display: none }` regardless of
   specificity — the defect that has now cost eight builds. Every element
   hidden at rest in this unit takes its `[hidden]` guard FIRST, on its own
   line, before any rule that could give it a box.

   ⚠️ NOTHING HERE TRANSITIONS OR ANIMATES. All five B8 instruments are
   DOM-only with no canvas, no rAF and no timer, so this section creates no
   reduced-motion obligation. `.ks3-option`'s own 160ms background transition is
   the platform's and is inherited by the tabs; parity gate 7 exists because of
   it. */

/* The tab rows shared across the unit. `.ks3-options` is a COLUMN by default,
   because an answer list is a column; a tab strip is a segmented control and
   reads across. */
.ks3-ml-tabs {
  margin-top: 0;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 9px;
}
.ks3-ml-tab {
  width: auto;
  padding: 10px 15px;
  font-size: 16px;
  font-weight: 700;
}
/* ⚖️ A CHOSEN TAB IS THE ALERT GROUND, NOT THE ALERT BORDER, and that is
   Design's measurement rather than a preference: her `seg()` paints the chosen
   segment `background: var(--ks3-alert); color: var(--ks3-ink)`. The platform's
   `.ks3-dark .ks3-option[aria-pressed="true"]` gives an alert BORDER on the dark
   panel, which is right for an answer committed to and wrong for a switch that
   is currently on. Same specificity (0,3,0) as the platform rule and later in
   the file, so source order decides — deliberately, because this is a narrowing
   to one unit and not a replacement.

   ⚠️ AND IT IS STILL NOT A MARK (MRB-196 R10). Alert is "this is the setting
   you have chosen", never "this is correct". ⊕ SUPERSEDED 30 Aug 2026 — selection is now the ACCENT (see the platform block at `.ks3-dark .ks3-option`); this paragraph is kept for its reasoning, not its colour. Nothing on these benches is right
   or wrong; only the mastery ladder marks. */
.ks3-dark .ks3-ml-tab[aria-pressed="true"] {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-accent);
  color: var(--ks3-on-dark);
}
/* The bench buttons are `.ks3-reveal-btn`, which is INK ON INK — on a
   `ks3-dark` block that paints an invisible control. Inverted here, exactly as
   `.ks3-dark .ks3-rr-test` is. */
.ks3-dark .ks3-ml-run {
  background: var(--ks3-on-dark);
  border-color: var(--ks3-on-dark);
  color: var(--ks3-ink);
}
.ks3-ml-run[disabled] { opacity: .45; cursor: default; }
.ks3-ml-run[disabled]:hover { transform: none; }

/* The nested dark panel the bench puts its ledger in. */
.ks3-ml-panel {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-card);
}
.ks3-dark .ks3-ml-panel { background: var(--ks3-dark-panel); }

.ks3-ml-tabsgroup { margin-top: 22px; }
.ks3-ml-tabslabel {
  margin: 0 0 9px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
}

/* ═══ mass-ledger (b8-01 #s-bench) ═══════════════════════════════════════
   Four amounts, two columns, two totals that always agree — and one figure
   that sits outside both of them on purpose.

   ⚖️ THE ENERGY ROW IS NOT A THIRD TOTAL AND IS NOT DRAWN AS ONE. It shares
   the totals row, and it takes the ALERT where the two totals take on-dark,
   because the whole argument of the block is that it is a different kind of
   quantity. If this ever resolves to the same colour as its two neighbours the
   page has started saying that energy is a substance with a mass, which is the
   belief rung 2 exists to break. */
.ks3-ml-name { margin: 0; font-size: 20px; font-weight: 700; }
.ks3-ml-note { margin: 6px 0 0; font-size: 18px; line-height: 1.55; }
.ks3-ml-cols {
  margin-top: 20px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 16px;
}
.ks3-ml-colhead {
  margin: 0 0 10px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
}
.ks3-ml-rows {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.ks3-ml-row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 14px;
  border-radius: var(--ks3-r-panel);
  background: rgba(255, 255, 255, .06);
}
.ks3-ml-rowname { font-size: 18px; font-weight: 600; }
.ks3-ml-rowvalue {
  font-family: var(--ks3-font-mono);
  font-size: 17px;
  white-space: nowrap;
}
.ks3-ml-totals {
  margin-top: 18px;
  display: flex;
  flex-wrap: wrap;
  gap: 12px 24px;
  padding: 16px 18px;
  border-radius: var(--ks3-r-panel);
  background: rgba(255, 255, 255, .06);
}
.ks3-ml-total,
.ks3-ml-energy {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 17px;
  font-weight: 500;
}
.ks3-ml-foot {
  margin-top: 18px;
  display: flex;
  align-items: center;
  gap: 13px;
  flex-wrap: wrap;
}

/* ⚠️ NO `display` HERE — the exits panel is emitted `hidden` and the guard goes
   first, so the attribute keeps winning. */
.ks3-ml-exits[hidden] { display: none; }
.ks3-ml-exits {
  margin-top: 18px;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ground);
}
.ks3-ml-exitslabel {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.ks3-ml-exitlist {
  list-style: none;
  margin: 12px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.ks3-ml-exit {
  display: grid;
  grid-template-columns: minmax(110px, 150px) 1fr;
  gap: 4px 16px;
}
.ks3-ml-exitname { margin: 0; font-size: 18px; font-weight: 700; }
.ks3-ml-exitroute { margin: 0; font-size: 18px; line-height: 1.55; }
.ks3-ml-close {
  margin: 14px 0 0;
  padding-top: 14px;
  border-top: 2px solid var(--ks3-rule);
  font-size: 18px;
  line-height: 1.6;
}

/* ── mass-ledger on ink ────────────────────────────────────────────────── */
.ks3-dark .ks3-ml-tabslabel { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-ml-name { color: var(--ks3-on-dark); }
.ks3-dark .ks3-ml-note { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-ml-colhead { color: var(--ks3-alert); }
.ks3-dark .ks3-ml-rowname { color: var(--ks3-on-dark); }
.ks3-dark .ks3-ml-rowvalue { color: var(--ks3-on-dark-body); }
.ks3-dark .ks3-ml-total { color: var(--ks3-on-dark); }
/* ⚖️ THE ONE THAT IS NOT A TOTAL. Alert, beside two on-dark neighbours. */
.ks3-dark .ks3-ml-energy { color: var(--ks3-alert); }
/* The cream-inside-ink elements. Each one separately, never the panel alone. */
.ks3-dark .ks3-ml-exitslabel { color: var(--ks3-accent-text); }
.ks3-dark .ks3-ml-exitname { color: var(--ks3-ink); }
.ks3-dark .ks3-ml-exitroute { color: var(--ks3-ink); }
.ks3-dark .ks3-ml-close { color: var(--ks3-ink); }

/* ═══ cell-demand (b8-02 #s-bench) ═══════════════════════════════════════
   Five cells, one reaction, and a cut that is per cell and one-way.

   ⚖️ THE SPEND BARS ARE ALL ONE COLOUR, DELIBERATELY. They are shares of a
   single budget rather than different kinds of quantity — unlike b8-01's
   totals row, where the third figure takes the alert precisely because it is
   NOT the same kind of thing as its neighbours. Colouring these three
   differently would say the cell has three energy supplies. */
.ks3-cd-tabsgroup { margin-top: 22px; }
.ks3-cd-tabslabel {
  margin: 0 0 9px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
}
.ks3-cd-tabs {
  margin-top: 0;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 9px;
}
.ks3-cd-tab {
  width: auto;
  padding: 10px 15px;
  font-size: 16px;
  font-weight: 700;
}
.ks3-dark .ks3-cd-tab[aria-pressed="true"] {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-accent);
  color: var(--ks3-on-dark);
}
.ks3-cd-panel {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-card);
}
.ks3-dark .ks3-cd-panel { background: var(--ks3-dark-panel); }
/* ⚠️ NO `display` — four of the five cell panels ship `hidden`. */
.ks3-cd-cell[hidden] { display: none; }
.ks3-cd-headrow {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.ks3-cd-name { margin: 0; font-size: 21px; font-weight: 700; }
.ks3-cd-origin {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
}
.ks3-cd-job { margin: 8px 0 0; font-size: 18px; line-height: 1.55; }
.ks3-cd-spendlabel {
  margin: 20px 0 8px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
}
.ks3-cd-spend {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 11px;
}
.ks3-cd-spendhead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.ks3-cd-spendname { margin: 0; font-size: 17px; font-weight: 600; }
.ks3-cd-spendpct {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 16px;
}
.ks3-cd-track {
  display: block;
  margin-top: 5px;
  height: 16px;
  border-radius: 99px;
  background: rgba(255, 255, 255, .08);
  overflow: hidden;
}
.ks3-cd-fill { display: block; height: 100%; background: var(--ks3-alert); }
.ks3-cd-mito {
  margin-top: 18px;
  padding: 16px 18px;
  border-radius: var(--ks3-r-panel);
  background: rgba(255, 255, 255, .06);
}
.ks3-cd-mitorow { margin: 0; font-size: 18px; line-height: 1.55; }
.ks3-cd-mitolabel {
  display: block;
  margin-bottom: 6px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
}
.ks3-cd-foot {
  margin-top: 18px;
  display: flex;
  align-items: center;
  gap: 13px;
  flex-wrap: wrap;
}
.ks3-dark .ks3-cd-cut {
  background: var(--ks3-on-dark);
  border-color: var(--ks3-on-dark);
  color: var(--ks3-ink);
}
.ks3-cd-cut[disabled] { opacity: .45; cursor: default; }
.ks3-cd-cut[disabled]:hover { transform: none; }
/* ⚠️ NO `display` — the `fails` line is the reveal and ships `hidden` on every
   cell. If this ever gets a `display` it beats the UA's `[hidden]` at any
   specificity and every cell's answer ships visible. */
.ks3-cd-fails[hidden] { display: none; }
.ks3-cd-fails {
  margin: 18px 0 0;
  padding: 17px 19px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ground);
  font-size: 18px;
  line-height: 1.6;
}

/* ── cell-demand on ink ────────────────────────────────────────────────── */
.ks3-dark .ks3-cd-tabslabel { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-cd-name { color: var(--ks3-on-dark); }
.ks3-dark .ks3-cd-origin { color: var(--ks3-alert); }
.ks3-dark .ks3-cd-job { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-cd-spendlabel { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-cd-spendname { color: var(--ks3-on-dark); }
.ks3-dark .ks3-cd-spendpct { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-cd-mitorow { color: var(--ks3-on-dark-body); }
.ks3-dark .ks3-cd-mitolabel { color: var(--ks3-on-dark-muted); }
/* The cream-inside-ink element. Its own rule, never the panel's. */
.ks3-dark .ks3-cd-fails { color: var(--ks3-ink); }

/* ═══ oxygen-debt (b8-03 #s-bench) ═══════════════════════════════════════
   Four bars, and the lesson is what the fourth does when the first collapses.

   ⚖️ THE FOUR BARS TAKE THREE COLOURS AND THE DISTINCTION IS THE SCIENCE.
   `aerobic` is the ok green — it is the oxygen that arrived. `lactate` is the
   alert — it is the thing accumulating that the student is being taught to
   watch. `demand` and `breathing` are both muted, deliberately, because they
   are the two the lesson asks you to COMPARE: when the runner stops, the muted
   bar at the top collapses and the muted bar at the bottom does not, and
   painting them differently would break the pair.

   ⚠️ THE BAR WIDTHS ARE NOT ALL ON ONE SCALE and the stylesheet must not
   assume they are — `bar_divisor` applies to `demand` and `aerobic` only,
   because those two run past 100 in arbitrary units. `lactate` and `breathing`
   are already 0–100. The widths are computed in `build_ks3.py` and in
   `shared/ks3.js` from the same three rules; nothing here scales anything. */
.ks3-od-tabsgroup { margin-top: 22px; }
.ks3-od-tabslabel {
  margin: 0 0 9px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
}
.ks3-od-tabs {
  margin-top: 0;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 9px;
}
.ks3-od-tab {
  width: auto;
  padding: 10px 15px;
  font-size: 16px;
  font-weight: 700;
}
.ks3-dark .ks3-od-tab[aria-pressed="true"] {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-accent);
  color: var(--ks3-on-dark);
}
.ks3-od-panel {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-card);
}
.ks3-dark .ks3-od-panel { background: var(--ks3-dark-panel); }
.ks3-od-headrow {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.ks3-od-phase { margin: 0; font-size: 20px; font-weight: 700; }
.ks3-od-shortfall {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 16px;
  font-weight: 500;
}
.ks3-od-bars {
  list-style: none;
  margin: 18px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 13px;
}
.ks3-od-barhead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.ks3-od-barname { margin: 0; font-size: 17px; font-weight: 600; }
.ks3-od-barvalue {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 16px;
}
.ks3-od-track {
  display: block;
  margin-top: 6px;
  height: 18px;
  border-radius: 99px;
  background: var(--ks3-dark-track);
  overflow: hidden;
}
.ks3-od-fill { display: block; height: 100%; }
.ks3-od-bar[data-tone="ok"] .ks3-od-fill { background: var(--ks3-ok); }
.ks3-od-bar[data-tone="alert"] .ks3-od-fill { background: var(--ks3-alert); }
.ks3-od-bar[data-tone="muted"] .ks3-od-fill {
  background: var(--ks3-on-dark-muted);
}
.ks3-od-note {
  margin: 18px 0 0;
  padding: 15px 17px;
  border-radius: var(--ks3-r-panel);
  background: rgba(255, 255, 255, .06);
  font-size: 18px;
  line-height: 1.55;
}
.ks3-od-foot {
  margin-top: 18px;
  display: flex;
  align-items: center;
  gap: 13px;
  flex-wrap: wrap;
}
.ks3-dark .ks3-od-run,
.ks3-dark .ks3-od-stop,
.ks3-dark .ks3-od-reset {
  background: var(--ks3-on-dark);
  border-color: var(--ks3-on-dark);
  color: var(--ks3-ink);
}
/* The stop button ships `disabled` — there is nothing to recover from on the
   start line — and the runtime clears it on the first run. */
.ks3-od-stop[disabled] { opacity: .45; cursor: default; }
.ks3-od-stop[disabled]:hover { transform: none; }

/* ── oxygen-debt on ink ────────────────────────────────────────────────── */
.ks3-dark .ks3-od-tabslabel { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-od-phase { color: var(--ks3-on-dark); }
.ks3-dark .ks3-od-shortfall { color: var(--ks3-alert); }
.ks3-dark .ks3-od-barname { color: var(--ks3-on-dark); }
.ks3-dark .ks3-od-barvalue { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-od-note { color: var(--ks3-on-dark-body); }

/* ═══ fermenter (b8-04 #s-bench) ═════════════════════════════════════════
   Four dials, ten branches, eight outcome texts — and every branch drawn in
   full and hidden rather than written in by the runtime, because the reaction
   lines carry a DRAWN arrow that only `t()` can produce.

   ⚖️ NO BRANCH IS STYLED AS AN ERROR, AND THAT IS A RULING, NOT AN OVERSIGHT.
   The yeast open-and-stirred branch is how yeast is MANUFACTURED — open
   stirred tanks, and the branch that teaches why a brewer seals the vessel.
   It takes the same panel, the same tones and the same weight as every other
   outcome. There is deliberately no `[data-kind="error"]`, no amber outline
   and no red anywhere in this component: the bench shows a CONSEQUENCE, never
   a verdict on the student (MRB-196 R10).

   ⚖️ THE TWO PRODUCT TONES ARE THE TWO PRODUCTS, not right and wrong. Alert is
   the substance the outcome is named for; ok is its partner. A branch making
   nothing draws both bars empty and says so in words. */
.ks3-fm-dials {
  margin-top: 22px;
  display: flex;
  flex-wrap: wrap;
  gap: 20px 30px;
}
.ks3-fm-dialname {
  margin: 0 0 9px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
}
.ks3-fm-opts {
  margin-top: 0;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 9px;
}
.ks3-fm-opt {
  width: auto;
  padding: 10px 15px;
  font-size: 16px;
  font-weight: 700;
}
.ks3-dark .ks3-fm-opt[aria-pressed="true"] {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-accent);
  color: var(--ks3-on-dark);
}
.ks3-fm-panel {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-card);
}
.ks3-dark .ks3-fm-panel { background: var(--ks3-dark-panel); }
/* ⚠️ NO `display` — nine of the ten branch blocks ship `hidden`. An author
   `display` here would beat the UA's `[hidden] { display: none }` at any
   specificity and the bench would show all ten outcomes at once. */
.ks3-fm-branch[hidden] { display: none; }
.ks3-fm-headrow {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
/* The reaction line carries the drawn arrow, which is `.ks3-mark` at 1em
   stroked `currentColor` — so it takes this rule's colour and size for free. */
.ks3-fm-line { margin: 0; font-size: 20px; font-weight: 700; }
.ks3-fm-rate {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 16px;
  font-weight: 500;
}
.ks3-fm-products {
  list-style: none;
  margin: 18px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.ks3-fm-prodhead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.ks3-fm-prodname { margin: 0; font-size: 17px; font-weight: 600; }
.ks3-fm-prodvalue {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 16px;
}
.ks3-fm-track {
  display: block;
  margin-top: 5px;
  height: 16px;
  border-radius: 99px;
  background: var(--ks3-dark-track);
  overflow: hidden;
}
.ks3-fm-fill { display: block; height: 100%; }
.ks3-fm-product[data-tone="alert"] .ks3-fm-fill { background: var(--ks3-alert); }
.ks3-fm-product[data-tone="ok"] .ks3-fm-fill { background: var(--ks3-ok); }
.ks3-fm-outcome {
  margin-top: 20px;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ground);
}
.ks3-fm-outcomelabel {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.ks3-fm-title {
  margin: 9px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 23px;
  line-height: 1.22;
  letter-spacing: -.02em;
}
.ks3-fm-body { margin: 11px 0 0; font-size: 18px; line-height: 1.6; }
.ks3-fm-foot {
  margin-top: 18px;
  display: flex;
  align-items: center;
  gap: 13px;
  flex-wrap: wrap;
}
.ks3-dark .ks3-fm-preset {
  background: var(--ks3-on-dark);
  border-color: var(--ks3-on-dark);
  color: var(--ks3-ink);
}

/* ── fermenter on ink ──────────────────────────────────────────────────── */
.ks3-dark .ks3-fm-dialname { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-fm-line { color: var(--ks3-on-dark); }
.ks3-dark .ks3-fm-rate { color: var(--ks3-alert); }
.ks3-dark .ks3-fm-prodname { color: var(--ks3-on-dark); }
.ks3-dark .ks3-fm-prodvalue { color: var(--ks3-on-dark-muted); }
/* The cream-inside-ink elements. Each one separately, never the panel alone. */
.ks3-dark .ks3-fm-outcomelabel { color: var(--ks3-accent-text); }
.ks3-dark .ks3-fm-title { color: var(--ks3-ink); }
.ks3-dark .ks3-fm-body { color: var(--ks3-ink); }

/* ═══ route-decider (b8-05 #s-bench) ═════════════════════════════════════
   Five cases, three routes, and a verdict made of WORDS.

   ⚖️ THERE IS NO CORRECT-ANSWER STYLING IN THIS COMPONENT, AND THAT IS THE
   RULING RATHER THAN AN OMISSION. A settled case's route button looks exactly
   like any other pressed option whether the student had it or not; the verdict
   panel is where the answer is named, in a sentence. MRB-196 R10 and the house
   rule: only the ladder marks correctness, and amber is a wrong IDEA being
   confronted, never the student. If a later pass wants a tone here, that is a
   ruling to take, not a gap to fill.

   ⚠️ EVERY TEXT RULE IS SCOPED `.ks3-dark .ks3-rd-*`, which is (0,2,0). The
   block is on ink, `.ks3-dark p` is (0,1,1), and a bare `.ks3-rd-*` at (0,1,0)
   LOSES to it — shipping a 1.21:1 label that no grep would ever find and only
   a computed-style read catches. */
.ks3-rd-cases {
  margin-top: 0;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 9px;
}
.ks3-rd-case {
  width: auto;
  padding: 10px 15px;
  font-size: 16px;
  font-weight: 700;
}
.ks3-rd-caseslabel,
.ks3-rd-routeslabel {
  margin: 0 0 9px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
}
.ks3-rd-panel {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-card);
}
.ks3-rd-text {
  margin: 0 0 18px;
  font-size: 18px;
  line-height: 1.55;
}
.ks3-rd-routeslabel { margin-top: 0; }
.ks3-rd-routes {
  margin-top: 0;
  flex-direction: column;
  gap: 9px;
}
.ks3-rd-foot {
  margin-top: 18px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.ks3-rd-progress,
.ks3-rd-tally {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 15px;
}
.ks3-rd-tally { margin-top: 14px; }
/* ⚠️ NO `display` ON THE VERDICT BOX ITSELF. It ships `hidden`, and an author
   `display` would beat the UA's `[hidden] { display: none }` at any
   specificity — putting the answer to every case on screen at load, which is
   the one thing this bench cannot survive. The layout hangs off
   `:not([hidden])` for exactly that reason. */
.ks3-rd-verdict:not([hidden]) {
  margin-top: 18px;
  padding-top: 16px;
  border-top: 2px solid var(--ks3-dark-rule);
}
.ks3-rd-word {
  margin: 0 0 8px;
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
}
.ks3-rd-why {
  margin: 0;
  font-size: 17px;
  line-height: 1.55;
}

/* ── route-decider on ink ──────────────────────────────────────────────── */
.ks3-dark .ks3-rd-caseslabel { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-rd-routeslabel { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-rd-panel { background: var(--ks3-dark-panel); }
.ks3-dark .ks3-rd-text { color: var(--ks3-on-dark); }
.ks3-dark .ks3-rd-progress { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-rd-tally { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-rd-word { color: var(--ks3-on-dark); }
.ks3-dark .ks3-rd-why { color: var(--ks3-on-dark-body); }

/* Below this the exit's route would be reading in a 110px gutter beside a
   150px name, so the two stack; and four dial columns stop being columns. */
@media (max-width: 560px) {
  .ks3-ml-exit { grid-template-columns: 1fr; }
  .ks3-fm-dials { gap: 18px; }
}

/* ═══ END B8 ═══ */

/* ═══ BEGIN B9 ═══ */

/* ── B9 · Ecosystems and interdependence (⊕ MRB-250) ──
   Six instruments, and every one of them on ink.

   ⚠️ SPECIFICITY, AND ON THIS UNIT IT IS LOAD-BEARING SIX TIMES OVER.
   `.ks3-dark p` is (0,1,1) and every bare class below would be (0,1,0), so an
   unscoped `color` LOSES to the dark ground's default and the component's own
   colour never reaches the page. All six B9 practicals are
   `ks3-block ks3-dark ks3-practical` — measured off Design's own `#s-bench`
   markup on all six pages, character for character, no exceptions.

   ⛔ AND DO NOT TRUST THIS COMMENT. B7's stylesheet carried a section note
   claiming every rule in it was scoped, and the gate found the trap re-laid
   inside that very section hours later. Every colour rule below is written
   under `.ks3-dark …` at (0,2,0) or tighter AND
   `ks3_parity.check_dark_text_specificity()` resolves the real cascade winner
   on every element on every ink ground. The comment is intent; the gate is
   evidence.

   ⊕ THE LIST IS OF ELEMENTS, NOT OF PANELS, and B4 is why: counting panels is
   what let a seventh `<p>` inside a rescued panel ship at 1.21:1. The
   CREAM-INSIDE-INK elements in B9 — each with its own rule, never the panel's:

     `.ks3-cl-verdict` · `.ks3-rs-verdict` · `.ks3-ba-verdict` ·
     `.ks3-qb-verdict`

   Four closing panels on `--ks3-ground`, one per bench that has one. Add a
   text element inside any of them and it joins this list in the same commit.

   ⚠️ NO `display` ON ANYTHING THAT ALSO TAKES `[hidden]`. An author `display`
   beats the UA stylesheet's `[hidden] { display: none }` regardless of
   specificity — the defect that has now cost nine builds. Every element hidden
   at rest in this unit takes its `[hidden]` guard FIRST, on its own line,
   before any rule that could give it a box.

   ⚠️ NOTHING HERE TRANSITIONS OR ANIMATES. All six B9 instruments are DOM-only
   with no canvas, no rAF and no timer, so this section creates no
   reduced-motion obligation. `.ks3-option`'s own 160ms background transition
   is the platform's and is inherited by the tabs; parity gate 7 exists because
   of it. */

/* The tab rows, shared across the unit. `.ks3-options` is a COLUMN by default,
   because an answer list is a column; a tab strip is a segmented control and
   reads across. Design's own `seg()` on all six pages: 16px/700, 10px 15px,
   44px minimum, `--ks3-r-control`. */
.ks3-cl-tabs,
.ks3-rs-tabs,
.ks3-ba-tabs,
.ks3-qb-tabs {
  margin-top: 0;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 9px;
}
.ks3-cl-tab,
.ks3-rs-tab,
.ks3-ba-tab,
.ks3-qb-tab {
  width: auto;
  padding: 10px 15px;
  font-size: 16px;
  font-weight: 700;
}
/* ⚖️ A CHOSEN TAB IS THE ALERT GROUND, NOT THE ALERT BORDER — Design's
   measurement, not a preference: her `seg()` paints the chosen segment
   `background: var(--ks3-alert); color: var(--ks3-ink)`. The platform's
   `.ks3-dark .ks3-option[aria-pressed="true"]` gives an alert BORDER, which is
   right for an answer committed to and wrong for a switch that is currently
   on. Same specificity (0,3,0) as the platform rule and later in the file, so
   source order decides — deliberately, because this narrows one unit rather
   than replacing the platform rule.

   ⚠️ AND IT IS STILL NOT A MARK (MRB-196 R10). Alert is "this is the setting
   you have chosen", never "this is correct". ⊕ SUPERSEDED 30 Aug 2026 — selection is now the ACCENT (see the platform block at `.ks3-dark .ks3-option`); this paragraph is kept for its reasoning, not its colour. Nothing on these six benches is
   right or wrong; only the mastery ladder marks. */
.ks3-dark .ks3-cl-tab[aria-pressed="true"],
.ks3-dark .ks3-rs-tab[aria-pressed="true"],
.ks3-dark .ks3-ba-tab[aria-pressed="true"],
.ks3-dark .ks3-qb-tab[aria-pressed="true"] {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-accent);
  color: var(--ks3-on-dark);
}
/* The bench buttons are `.ks3-reveal-btn`, which is INK ON INK — on a
   `ks3-dark` block that paints an invisible control. Inverted here, exactly as
   `.ks3-dark .ks3-ml-run` is. */
.ks3-dark .ks3-cl-up,
.ks3-dark .ks3-cl-reset,
.ks3-dark .ks3-cy-btn,
.ks3-dark .ks3-rs-next,
.ks3-dark .ks3-rs-reset,
.ks3-dark .ks3-ss-toggle,
.ks3-dark .ks3-ss-half,
.ks3-dark .ks3-ba-up,
.ks3-dark .ks3-ba-reset,
.ks3-dark .ks3-qb-sample,
.ks3-dark .ks3-qb-truth {
  background: var(--ks3-on-dark);
  border-color: var(--ks3-on-dark);
  color: var(--ks3-ink);
}
.ks3-cl-up[disabled],
.ks3-rs-next[disabled],
.ks3-ba-up[disabled],
.ks3-qb-truth[disabled] { opacity: .45; cursor: default; }
.ks3-cl-up[disabled]:hover,
.ks3-rs-next[disabled]:hover,
.ks3-ba-up[disabled]:hover,
.ks3-qb-truth[disabled]:hover { transform: none; }

/* The mono label above a tab row or a dial, on all six. */
.ks3-cl-tabslabel,
.ks3-rs-tabslabel,
.ks3-rs-weblabel,
.ks3-ba-tabslabel,
.ks3-qb-diallabel {
  margin: 0 0 9px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
}
.ks3-cl-tabsgroup,
.ks3-ba-tabsgroup { margin-top: 22px; }
.ks3-rs-tabsgroup { margin-top: 20px; }
.ks3-dark .ks3-cl-tabslabel,
.ks3-dark .ks3-rs-tabslabel,
.ks3-dark .ks3-rs-weblabel,
.ks3-dark .ks3-ba-tabslabel,
.ks3-dark .ks3-qb-diallabel { color: var(--ks3-on-dark-muted); }

/* The nested dark panel each bench puts its working in. */
.ks3-cl-panel,
.ks3-rs-body,
.ks3-ba-panel,
.ks3-qb-panel,
.ks3-ss {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-card);
}
.ks3-cy-panel {
  margin-top: 22px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-card);
}
.ks3-dark .ks3-cl-panel,
.ks3-dark .ks3-cy-panel,
.ks3-dark .ks3-rs-body,
.ks3-dark .ks3-ss,
.ks3-dark .ks3-ba-panel,
.ks3-dark .ks3-qb-panel { background: var(--ks3-dark-panel); }

/* The control row at the foot of a bench, on all six. */
.ks3-cl-foot,
.ks3-cy-foot,
.ks3-rs-foot,
.ks3-ss-foot,
.ks3-ba-foot,
.ks3-qb-foot {
  margin-top: 18px;
  display: flex;
  align-items: center;
  gap: 13px;
  flex-wrap: wrap;
}
.ks3-cl-foot,
.ks3-ba-foot { margin-top: 20px; }

/* ⚠️ THE CLOSING PANELS: `[hidden]` FIRST, ON ITS OWN LINE. Each is emitted
   hidden and each takes a background, and a `display` here would beat the UA
   rule and ship the verdict visible before the student had reached it. */
.ks3-cl-verdict[hidden] { display: none; }
.ks3-rs-verdict[hidden] { display: none; }
.ks3-ba-verdict[hidden] { display: none; }
.ks3-qb-verdict[hidden] { display: none; }
.ks3-cl-verdict,
.ks3-rs-verdict,
.ks3-ba-verdict,
.ks3-qb-verdict {
  margin: 18px 0 0;
  padding: 17px 19px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ground);
  font-size: 18px;
  line-height: 1.6;
}
/* The cream-inside-ink elements. Each one separately, never the panel alone. */
.ks3-dark .ks3-cl-verdict,
.ks3-dark .ks3-rs-verdict,
.ks3-dark .ks3-ba-verdict,
.ks3-dark .ks3-qb-verdict { color: var(--ks3-ink); }

/* ═══ chain-ledger (b9-01 #s-bench) ══════════════════════════════════════
   Three chains, four or five levels each, and a tenth of the energy surviving
   every step.

   ⚖️ `column-reverse` IS THE CLAIM THE LESSON MAKES, NOT A LAYOUT CHOICE. The
   producer is `data-i="0"` and is drawn at the BOTTOM, so the student reads
   the chain the way the energy travels — in at the ground, up one step at a
   time. `#s-think`'s first quote is that arrows point at what the animal eats,
   and it is the single most-marked error in the topic; a list flipped to
   ordinary document order would draw the same figures making the opposite
   claim. There is a parity row on this property. */
.ks3-cl-levels[hidden] { display: none; }
.ks3-cl-levels {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column-reverse;
  gap: 10px;
}
.ks3-cl-level {
  padding: 14px 16px;
  border-radius: var(--ks3-r-panel);
  background: transparent;
  border: 2px solid transparent;
  opacity: var(--ks3-dim-ahead);
}
/* ⚖️ AN UNREACHED LEVEL DIMS AND STAYS ON-DARK. It is not a wrong answer and
   it is not absent: the whole chain is drawn from the start, so a student
   reads how far there is to go before taking a step. If the opacity ever
   resolves to 1 the bench has given away where the chain ends. */
.ks3-cl-level[data-shown] {
  opacity: 1;
  border-color: var(--ks3-on-dark-muted);
}
.ks3-cl-level[data-top] {
  background: rgba(255, 255, 255, .06);
  border-color: var(--ks3-alert);
}
.ks3-cl-levelhead {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px 16px;
}
.ks3-cl-levelname { margin: 0; font-size: 19px; font-weight: 700; }
.ks3-cl-levelrole {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 16px;
}
.ks3-cl-readout[hidden] { display: none; }
.ks3-cl-figs {
  margin-top: 8px;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.ks3-cl-energy {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 17px;
  font-weight: 500;
}
.ks3-cl-pct {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 15px;
}
.ks3-cl-track,
.ks3-ba-track {
  display: block;
  margin-top: 6px;
  height: 16px;
  border-radius: 99px;
  background: rgba(255, 255, 255, .08);
  overflow: hidden;
}
.ks3-cl-bar,
.ks3-ba-bar { display: block; height: 100%; }
.ks3-cl-note { margin: 8px 0 0; font-size: 17px; line-height: 1.5; }

/* ── chain-ledger on ink ───────────────────────────────────────────────── */
.ks3-dark .ks3-cl-levelname { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-cl-level[data-shown] .ks3-cl-levelname {
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-cl-levelrole { color: var(--ks3-on-dark-muted); }
/* ⚖️ THE ENERGY FIGURE TAKES THE ALERT and the percentage beside it does not.
   The number that is falling by a factor of ten is the one the eye should
   land on; the percentage is its restatement. */
.ks3-dark .ks3-cl-energy { color: var(--ks3-alert); }
.ks3-dark .ks3-cl-pct { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-cl-note { color: var(--ks3-on-dark-body); }
.ks3-cl-bar { background: var(--ks3-alert); }

/* ═══ cycle-runner (b9-02 #s-bench) ══════════════════════════════════════
   One field, two populations, twenty-six years of history.

   ⚖️ THE TWO SERIES ARE SCALED INDEPENDENTLY AND THE BARS ARE DRAWN IN PAIRS.
   Each year is one `.ks3-cy-year` column holding one amber bar and one green
   bar; the heights come from two separate maxima computed in
   `wireCycleRunner`, each with its own floor. On a shared scale the fox series
   flattens into the axis and the LAG — the whole lesson, and what both marked
   rungs test — becomes unreadable. The caption says so in words because the
   chart cannot. */
.ks3-cy-now {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 12px 28px;
}
.ks3-cy-live { margin: 0; font-size: 20px; font-weight: 700; }
.ks3-cy-chart {
  margin-top: 18px;
  display: flex;
  align-items: flex-end;
  gap: 3px;
  height: 150px;
  padding: 10px 12px;
  border-radius: var(--ks3-r-panel);
  background: rgba(255, 255, 255, .06);
}
.ks3-cy-year {
  flex: 1 1 0;
  display: flex;
  align-items: flex-end;
  gap: 2px;
  height: 100%;
  min-width: 0;
}
.ks3-cy-bar {
  display: block;
  flex: 1 1 0;
  border-radius: 2px 2px 0 0;
  background: var(--cy-fill, var(--ks3-alert));
}
.ks3-cy-caption {
  margin: 8px 0 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  letter-spacing: .06em;
  text-transform: uppercase;
}
.ks3-cy-note,
.ks3-ss-note {
  margin: 18px 0 0;
  padding: 15px 17px;
  border-radius: var(--ks3-r-panel);
  background: rgba(255, 255, 255, .06);
  font-size: 18px;
  line-height: 1.55;
}

/* ── cycle-runner on ink ───────────────────────────────────────────────── */
/* ⚖️ THE TWO LIVE READOUTS TAKE THE SERIES' OWN COLOURS, because the caption
   names the two colours rather than the two names — "amber = rabbits, green =
   foxes" — and a student matches the chart to the numbers by colour. Both are
   20px/700, which is large text, and both clear 3:1 on the nested panel. */
.ks3-dark .ks3-cy-live[data-series="prey"] { color: var(--ks3-alert); }
/* ⊕ MRB-252 (RULED) · audit 3.4 — the on-dark GREEN, not the mark green.
   `--ks3-ok` measures 3.48:1 at this size on `--ks3-dark-panel` and its own
   annotation forbids it as text. The caption beside this chart names the
   series BY COLOUR ("amber = rabbits · green = foxes"), so the hue has to
   survive the swap and it does: `--ks3-ok-dark` is the same 146° hue at
   6.6:1. Six sites moved together; the FILLS below did not move. */
.ks3-dark .ks3-cy-live[data-series="pred"] { color: var(--ks3-ok-dark); }
.ks3-dark .ks3-cy-caption { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-cy-note { color: var(--ks3-on-dark-body); }

/* ═══ remove-a-species (b9-03 #s-bench) ══════════════════════════════════
   One oak wood, eight species, six removals and three rounds each.

   ⚖️ THE WEB IS PROSE AND IT IS DRAWN AS PROSE. Eight who-eats-whom lines in a
   two-column grid; no adjacency structure is authored and none is drawn. That
   is Design's, measured, and it is what makes the bees' line — they pollinate
   the wildflowers, and nothing eats them — sit in the list as an equal rather
   than as a missing edge. */
.ks3-rs-web {
  margin-top: 18px;
  padding: 18px 20px;
  border-radius: var(--ks3-r-card);
  background: rgba(255, 255, 255, .06);
}
.ks3-rs-weblines {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 8px 20px;
}
.ks3-rs-webline { font-size: 17px; line-height: 1.5; }
.ks3-rs-panel[hidden] { display: none; }
.ks3-rs-headline { margin: 0; font-size: 21px; font-weight: 700; }
.ks3-rs-why { margin: 6px 0 0; font-size: 18px; line-height: 1.55; }
.ks3-rs-rounds {
  list-style: none;
  margin: 20px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.ks3-rs-round {
  display: flex;
  gap: 14px;
  align-items: flex-start;
  padding: 14px 16px;
  border-radius: var(--ks3-r-panel);
  background: transparent;
  border: 2px solid transparent;
  opacity: var(--ks3-dim-ahead);
}
.ks3-rs-round[data-shown] {
  opacity: 1;
  border-color: var(--ks3-on-dark-muted);
}
.ks3-rs-round[data-cur] {
  background: rgba(255, 255, 255, .06);
  border-color: var(--ks3-alert);
}
.ks3-rs-num {
  flex: 0 0 30px;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 9px;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 15px;
  background: transparent;
  border: 2px solid var(--ks3-on-dark-muted);
}
.ks3-rs-roundmain { flex: 1 1 auto; }
.ks3-rs-roundtitle { display: block; font-size: 19px; font-weight: 700; }
.ks3-rs-roundbody[hidden] { display: none; }
.ks3-rs-roundbody {
  display: block;
  margin-top: 6px;
  font-size: 18px;
  line-height: 1.55;
}

/* ── remove-a-species on ink ───────────────────────────────────────────── */
.ks3-dark .ks3-rs-webline { color: var(--ks3-on-dark-body); }
.ks3-dark .ks3-rs-headline { color: var(--ks3-on-dark); }
.ks3-dark .ks3-rs-why { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-rs-num { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-rs-round[data-shown] .ks3-rs-num {
  background: var(--ks3-alert);
  border-color: var(--ks3-alert);
  color: var(--ks3-ink);
}
.ks3-dark .ks3-rs-roundtitle { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-rs-round[data-shown] .ks3-rs-roundtitle {
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-rs-roundbody { color: var(--ks3-on-dark-body); }

/* ═══ supermarket-shelf (b9-04 #s-bench) ═════════════════════════════════
   Twelve foods, and two numbers that do not fall by the same amount.

   ⚖️⚖️ THE GAP BETWEEN THE TWO BARS IS THE ENTIRE LESSON, AND THIS GRID IS
   WHAT KEEPS THEM APART. `repeat(auto-fit, minmax(220px, 1fr))` wraps them to
   two ROWS on a narrow screen rather than merging or dropping one — the `none`
   note reads the gap aloud and rung 2 marks a student for knowing which of the
   two falls further. A revision that stacks them, combines them into one
   "food" bar, or hides one at a breakpoint deletes the lesson. There is a
   parity row on this container measured at a narrow viewport, because
   shrinking a container in a headless browser does not fire a media query and
   would pass silently over exactly that. */
.ks3-ss-shelf {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 10px;
}
.ks3-ss-food {
  padding: 13px 15px;
  border-radius: var(--ks3-r-panel);
  border: 2px solid var(--ks3-on-dark-muted);
  background: rgba(255, 255, 255, .08);
  opacity: 1;
  text-decoration: none;
}
/* ⚖️ A FOOD THAT IS GONE IS STRUCK THROUGH AND OUTLINED IN ALERT, and it is
   still not a mark: nothing here is right or wrong, and what the amber says is
   that this crop has failed. The strike-through is the second signal, so
   colour is never carrying the state alone (R2). */
.ks3-ss-food[data-gone] {
  border-color: var(--ks3-alert);
  background: rgba(255, 255, 255, .04);
  opacity: .6;
  text-decoration: line-through;
}
.ks3-ss-foodname { margin: 0; font-size: 18px; font-weight: 700; }
.ks3-ss-foodstatus {
  margin: 3px 0 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  letter-spacing: .05em;
  text-transform: uppercase;
  opacity: .85;
}
.ks3-ss-bars {
  margin-top: 20px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 14px;
}
.ks3-ss-barhead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.ks3-ss-barlabel { margin: 0; font-size: 17px; font-weight: 600; }
.ks3-ss-barvalue {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 17px;
}
.ks3-ss-track {
  display: block;
  margin-top: 6px;
  height: 18px;
  border-radius: 99px;
  background: rgba(255, 255, 255, .08);
  overflow: hidden;
}
.ks3-ss-fill {
  display: block;
  height: 100%;
  background: var(--ss-fill, var(--ks3-alert));
}

/* ── supermarket-shelf on ink ──────────────────────────────────────────── */
.ks3-dark .ks3-ss-foodname { color: var(--ks3-on-dark); }
.ks3-dark .ks3-ss-foodstatus { color: var(--ks3-on-dark); }
.ks3-dark .ks3-ss-food[data-gone] .ks3-ss-foodname,
.ks3-dark .ks3-ss-food[data-gone] .ks3-ss-foodstatus {
  color: var(--ks3-on-dark-muted);
}
.ks3-dark .ks3-ss-barlabel { color: var(--ks3-on-dark); }
.ks3-dark .ks3-ss-barvalue { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-ss-note { color: var(--ks3-on-dark-body); }

/* ═══ bioaccumulation (b9-05 #s-bench) ═══════════════════════════════════
   One lake, six levels, three persistence settings — and the third is a
   control.

   ⚖️ `column-reverse` AGAIN, AND FOR THE SAME REASON AS b9-01. The lake water
   is `data-i="0"` and sits at the bottom; the ospreys are at the top. The two
   benches are deliberately the same shape, because b9-05 is b9-01's arithmetic
   run in the other direction — the energy falls by a tenth going up and the
   concentration multiplies by ten going up, on the same ladder. */
.ks3-ba-chemnote { margin: 0; font-size: 18px; line-height: 1.55; }
.ks3-ba-levels {
  list-style: none;
  margin: 20px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column-reverse;
  gap: 10px;
}
.ks3-ba-level {
  padding: 14px 16px;
  border-radius: var(--ks3-r-panel);
  background: transparent;
  border: 2px solid transparent;
  opacity: var(--ks3-dim-ahead);
}
.ks3-ba-level[data-shown] {
  opacity: 1;
  border-color: var(--ks3-on-dark-muted);
}
.ks3-ba-level[data-cur] {
  background: rgba(255, 255, 255, .06);
  border-color: var(--ks3-alert);
}
.ks3-ba-levelhead {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 6px 16px;
}
.ks3-ba-name { margin: 0; font-size: 19px; font-weight: 700; }
.ks3-ba-eats {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 16px;
}
.ks3-ba-readout[hidden] { display: none; }
.ks3-ba-figs {
  margin-top: 8px;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.ks3-ba-ppm {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 18px;
  font-weight: 500;
}
.ks3-ba-lvlverdict {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 15px;
}

/* ── bioaccumulation on ink ────────────────────────────────────────────── */
.ks3-dark .ks3-ba-chemnote { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-ba-name { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-ba-level[data-shown] .ks3-ba-name {
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-ba-eats { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-ba-ppm { color: var(--ks3-alert); }
/* ⚖️ THE HARM FLAG IS AMBER AND IT IS NOT A MARK. It says a quantity has
   crossed a threshold, on a bench where nothing the student does is right or
   wrong. The safe rows stay muted, so the amber arrives as an event rather
   than as a colour scheme — and on the ×1 control it never arrives at all,
   which is the whole point of the control. */
.ks3-dark .ks3-ba-lvlverdict { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-ba-level[data-harmful] .ks3-ba-lvlverdict {
  color: var(--ks3-alert);
}
.ks3-ba-bar { background: var(--ks3-on-dark-muted); }
.ks3-ba-level[data-harmful] .ks3-ba-bar { background: var(--ks3-alert); }

/* ═══ quadrat-bench (b9-06 #s-bench) ═════════════════════════════════════
   A hundred squares, a hidden answer, and two failures that need two
   different fixes.

   ⚠️ THE GRID SHIPS EMPTY AND THAT IS THE RESTING STATE, NOT A PLACEHOLDER.
   The field is generated unseeded in `wireQuadratBench` at page load — the
   only `Math.random()` in B9 — so no count can be rendered at build time and
   none should be: an unsurveyed field has nothing on show. */
.ks3-qb-dials {
  margin-top: 22px;
  display: flex;
  flex-wrap: wrap;
  gap: 20px 30px;
}
.ks3-qb-grid {
  display: grid;
  grid-template-columns: repeat(var(--qb-cols, 10), 1fr);
  gap: 3px;
  max-width: 460px;
}
.ks3-qb-cell {
  display: grid;
  place-items: center;
  aspect-ratio: 1;
  border-radius: 4px;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  background: rgba(228, 87, 46, .08);
  border: 2px solid transparent;
}
/* ⚖️ A COUNTED SQUARE IS OUTLINED, and the outline is the second signal beside
   the shade so the sample is readable without relying on colour (R2). It is
   the only thing on the grid that says WHERE you looked — which is the whole
   difference between the flowery corner and a random spread. */
.ks3-qb-cell[data-in-sample] { border-color: var(--ks3-on-dark); }
.ks3-qb-caption {
  margin: 10px 0 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  letter-spacing: .06em;
  text-transform: uppercase;
}
.ks3-qb-figures[hidden] { display: none; }
.ks3-qb-figures {
  list-style: none;
  margin: 18px 0 0;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: rgba(255, 255, 255, .06);
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 12px 20px;
}
.ks3-qb-figlabel {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  letter-spacing: .07em;
  text-transform: uppercase;
}
.ks3-qb-figvalue {
  margin: 3px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 26px;
  letter-spacing: -.02em;
}

/* ── quadrat-bench on ink ──────────────────────────────────────────────── */
.ks3-dark .ks3-qb-cell { color: var(--ks3-on-dark); }
.ks3-dark .ks3-qb-caption { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-qb-figlabel { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-qb-figvalue { color: var(--ks3-on-dark); }
/* ⚖️ THE ESTIMATE IS AMBER AND THE REAL TOTAL IS GREEN, AND ONLY AFTER IT IS
   REVEALED. Until then it is the muted `hidden`, in the same slot and the same
   type — so the student can see there IS an answer being withheld, which is
   what makes committing to an estimate feel like committing. */
.ks3-dark .ks3-qb-figure[data-qb-figure="estimate"] .ks3-qb-figvalue {
  color: var(--ks3-alert);
}
.ks3-dark .ks3-qb-figure[data-qb-figure="real"] .ks3-qb-figvalue {
  color: var(--ks3-on-dark-muted);
}
/* ⊕ MRB-252 (RULED) · audit 3.4 — THE WORST OF THE SIX, and the reason the
   ruling could not be "use it at 24px and take the 3:1 bar". This figure is
   26px/800, which IS large text — and it still measured 2.89:1, because it
   sits on `.ks3-qb-figures`' rgba(255,255,255,.06) well, which composites
   to #4A433C rather than the #3E3730 the token was rated against. A ground
   one step lighter than the one a token is measured on is enough to take it
   under the LARGE bar. `--ks3-ok-dark` is measured on that tile: 5.51:1. */
.ks3-dark .ks3-qb-figure[data-revealed] .ks3-qb-figvalue {
  color: var(--ks3-ok-dark);
}

/* ═══ END B9 ═══ */

/* ═══ BEGIN B10 ═══════════════════════════════════════════════════════════
   B10 · Inheritance and DNA (⊕ MRB-248)

   Five instruments, one per lesson, every one of them on
   `ks3-block ks3-dark ks3-practical` — measured off Design's own `#s-bench`
   class attribute on all five delivered pages, not inferred from the kind
   name (schema §0.2).

   ⚠️ SO EVERY COLOUR RULE HERE IS WRITTEN UNDER `.ks3-dark …`. `.ks3-dark p`
   is (0,1,1) and a bare component class is (0,1,0), so a rule written as
   `.ks3-vp-axis { color: … }` LOSES to the dark-ground default and ships a
   caption at roughly 1.2:1 that no grep will ever find, because the CSS is
   right there saying the correct thing. Only a computed read catches it, and
   `ks3_parity.check_dark_text_specificity()` does exactly that.

   ⚠️ AND NOTHING HERE IS A MARK (MRB-196 R10). An alert ground on a tab says
   "this is the characteristic you are looking at", never "this is correct".
   Three B10 benches DO adjudicate a student's commitment (schema §0.6) and
   every one of them does it in WORDS on a cream verdict panel — the same
   tone whichever way it went. No green, no red, no badge, and never the
   amber `is-wrong` ladder treatment on a bench.

   ⚠️ AND `--ks3-accent` IS 3.4:1 — LARGE TEXT ONLY. Every mono caption,
   tag and label below 24px takes `--ks3-accent-text` (6.0:1) instead. On ink
   the accent is unusable at any size and the alert takes its place, which is
   what the `.ks3-dark` blocks below do.

   ⊖ AND THERE IS NO B10 REDUCED-MOTION BLOCK, DELIBERATELY. The unit's one
   piece of motion is the `[data-fill]` height transition on the bars, and
   R6's platform rule (`*, *::before, *::after { transition-duration: .001ms
   !important }`) already covers every transition on every page — `!important`
   beats a later bare declaration, so a B10 block could never win and never
   needed to. Verified in a browser under emulated `prefers-reduced-motion:
   reduce` rather than asserted. Nothing in this unit animates on a timer:
   there is no canvas, no rAF and no `setTimeout` in any of the five wire
   functions, so there is no tick that would have to test the query inside
   itself (contract R4, the b2-03 slip). */

/* ── b10-01 `#s-bench` · variation-plotter ─────────────────────────────── */

.ks3-vp { margin-top: 22px; }
.ks3-vp-tabslabel {
  margin: 0 0 9px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
}
.ks3-vp-tabs {
  margin-top: 0;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 9px;
}
.ks3-vp-tab,
.ks3-vp-pred {
  width: auto;
  padding: 10px 15px;
  font-size: 16px;
  font-weight: 700;
}
.ks3-vp-panel {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-card);
}
.ks3-vp-name {
  margin: 0;
  font-size: 20px;
  font-weight: 700;
}
.ks3-vp-predictlabel {
  margin: 14px 0 10px;
  font-size: 17px;
  font-weight: 600;
}
.ks3-vp-preds {
  margin-top: 0;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 9px;
}

/* ⚖️⚖️ THE CHART IS SIX EQUAL COLUMNS AND THE BAR DOES NOT FILL ITS COLUMN
   UNLESS THE DATA IS CONTINUOUS. This pair of rules IS the histogram /
   bar-chart convention, and it is a pure function of `data_type` — schema §2
   is explicit that there is no `gap` key, no `spacing` key and no
   `chart_type` key, because an authored one would let a record ship touching
   bars for blood group. The column is `flex: 1 1 0`, so `width: 100%` on the
   bar makes neighbouring bars MEET and `calc(100% - 6px)` makes them stand
   apart. If the discontinuous rule ever resolves to 100% the bench is drawing
   a histogram of blood groups and telling the student that is what one looks
   like. */
/* ⚑ THE COLUMN GAP IS ZERO, AND THAT IS A DELIBERATE DEPARTURE FROM DESIGN'S
   MEASUREMENT — reported, not taken quietly. Her chart row carries `gap: 4px`
   of its own, on top of the derived bar width. Measured in a browser at
   1280px: a continuous characteristic's bars then stand 4px apart while her
   own axis caption underneath reads "bars touch because the categories are
   ranges", and the verdict line above says "a histogram, bars touching". The
   page contradicts itself, so "follow the page" has no single answer — and the
   half that is wrong is the half a student takes away as a rule, because
   touching bars are WHAT MAKES A HISTOGRAM A HISTOGRAM.
   With the row gap at zero, every separation on this chart comes from the one
   place schema §2 says it may come from: `data_type`. Continuous bars meet at
   0px; discontinuous bars stand exactly 6px apart. The caption becomes true,
   the derived rule becomes the only rule, and `b10-plot-both` asserts both
   numbers on painted geometry. */
.ks3-vp-chart {
  margin-top: 18px;
  display: flex;
  align-items: flex-end;
  gap: 0;
  height: 170px;
  padding: 12px;
  border-radius: var(--ks3-r-panel);
}
/* ⊕ MRB-257 · audit 5.4 — THE BARS WERE FLEX-SHRUNK TO A COMMON CAP, so a
   histogram drew a plateau where its own verdict said "a smooth hump".
   This column was a column flex container holding [count][bar][bin label].
   The bar's height is an authored percentage, which resolved against the
   column's full 146px — but the two labels take 42.4px of that column, and a
   flex item shrinks. So EVERY bar authored above 70.96% rendered at exactly
   103.6px. Verified live on Height: 81.25% / 100% / 75% → 103.6 / 103.6 /
   103.6px, with the modal bin indistinguishable from its neighbours, under a
   verdict reading "most people are near the middle". Eye colour drew a true
   2.0 ratio as 1.42. At 390px the cap fell to 90.4px and it was worse.

   GRID, NOT FLEX, IS "TAKE THE LABELS OUT OF THE FLEX FLOW". Three rows —
   `auto` for the count, `minmax(0, 1fr)` for the bar, `auto` for the label —
   and the bar's percentage now resolves against a track that is what is left
   after the labels, so it cannot be asked for more space than exists and
   cannot be shrunk out of what it asked for. A 100% bar fills the track and a
   75% bar is three quarters of it, at every viewport and every bin count.

   `align-self: end` keeps the bar sitting on the axis the way `flex-end` did;
   `justify-items: center` keeps the labels centred while the bar's own width
   rule (100%, or 100% − 6px for a bar chart) still decides whether
   neighbouring bars meet. That width rule is the lesson's other half and it
   does not move — `b10-plot-both` measures it on painted geometry, and it now
   measures the heights the same way. */
.ks3-vp-col {
  flex: 1 1 0;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  justify-items: center;
  height: 100%;
  min-width: 0;
  gap: 5px;
}
.ks3-vp-bar {
  display: block;
  align-self: end;
  border-radius: 3px 3px 0 0;
  transition: height .3s;
}
.ks3-vp-chart[data-vp-type="continuous"] .ks3-vp-bar { width: 100%; }
.ks3-vp-chart[data-vp-type="discontinuous"] .ks3-vp-bar {
  width: calc(100% - 6px);
}
.ks3-vp-n {
  font-family: var(--ks3-font-mono);
  font-size: 12px;
}
.ks3-vp-binlabel {
  font-family: var(--ks3-font-mono);
  font-size: 11px;
  letter-spacing: .02em;
  text-align: center;
  line-height: 1.2;
}
.ks3-vp-axis {
  margin: 10px 0 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  letter-spacing: .06em;
  text-transform: uppercase;
}

/* ⚖️ THE VERDICT PANEL IS CREAM INSIDE INK — the one place in this block
   where the text has to be INK and not on-dark. It is what the whole
   commitment was for, and it is the element that carries the lesson's SECOND
   question under a rule. */
.ks3-vp-verdict {
  margin-top: 16px;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
}
.ks3-vp-tag {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.ks3-vp-kind {
  margin: 9px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 22px;
  line-height: 1.25;
  letter-spacing: -.02em;
}
.ks3-vp-shape {
  margin: 11px 0 0;
  font-size: 18px;
  line-height: 1.6;
}
/* ⚖️ THE RULE ABOVE `What causes it:` IS THE LESSON'S ARGUMENT DRAWN AS A
   LINE. Shape and cause are two questions and the panel asks them separately;
   a merge of these two paragraphs would delete `#s-think`, whose whole
   confrontation is that a smooth curve says nothing about the cause. */
.ks3-vp-cause {
  margin: 14px 0 0;
  padding-top: 14px;
  border-top: 2px solid var(--ks3-rule);
  font-size: 18px;
  line-height: 1.6;
}
.ks3-vp-foot {
  margin-top: 18px;
  display: flex;
  align-items: center;
  gap: 13px;
  flex-wrap: wrap;
}
.ks3-vp-plot[disabled] { opacity: .45; cursor: default; }
.ks3-vp-plot[disabled]:hover { transform: none; }

/* ── variation-plotter on ink ──────────────────────────────────────────── */
.ks3-dark .ks3-vp-tabslabel { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-vp-panel { background: var(--ks3-dark-panel); }
.ks3-dark .ks3-vp-name { color: var(--ks3-on-dark); }
.ks3-dark .ks3-vp-predictlabel { color: var(--ks3-on-dark); }
.ks3-dark .ks3-vp-chart { background: rgba(255, 255, 255, .06); }
.ks3-dark .ks3-vp-bar { background: var(--ks3-alert); }
.ks3-dark .ks3-vp-n { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-vp-binlabel { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-vp-axis { color: var(--ks3-on-dark-muted); }
/* ⚖️ A CHOSEN TAB IS THE ALERT GROUND, NOT THE ALERT BORDER — Design's `seg()`
   paints `background: var(--ks3-alert); color: var(--ks3-ink)`. The platform's
   `.ks3-dark .ks3-option[aria-pressed="true"]` gives an alert BORDER, which is
   right for an answer committed to and wrong for a switch that is currently
   on. Same specificity (0,3,0) and later in the file, so source order decides
   — deliberately, because this narrows one unit rather than replacing the
   platform rule. Still not a mark: it says which characteristic is on the
   bench, never that the student is right. */
.ks3-dark .ks3-vp-tab[aria-pressed="true"],
.ks3-dark .ks3-vp-pred[aria-pressed="true"] {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-accent);
  color: var(--ks3-on-dark);
}
/* `.ks3-reveal-btn` is INK ON INK, which on a `ks3-dark` block paints an
   invisible control. Inverted, exactly as B9's bench buttons are. */
.ks3-dark .ks3-vp-plot {
  background: var(--ks3-on-dark);
  border-color: var(--ks3-on-dark);
  color: var(--ks3-ink);
}
.ks3-dark .ks3-vp-verdict {
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}
/* ⚠️ (0,2,0) — the verdict is cream inside ink, so these four re-declare INK
   against `.ks3-dark p` rather than inheriting the on-dark body colour. */
.ks3-dark .ks3-vp-kind { color: var(--ks3-ink); }
.ks3-dark .ks3-vp-shape { color: var(--ks3-ink-body); }
.ks3-dark .ks3-vp-cause { color: var(--ks3-ink-body); }
/* ⛔ ACCENT-TEXT, NOT ACCENT. The tag is 14px mono — `--ks3-accent` is 3.4:1
   and legal for large text only. And it is ONE TONE for both verdicts: the
   bench says whether the prediction held, and says it the same way whichever
   way it went (schema §0.6). Green for right and red for wrong would make a
   bench into a marker, which only the mastery ladder is. */
.ks3-dark .ks3-vp-tag { color: var(--ks3-accent-text); }

/* ── b10-02 `#s-bench` · zoom-bench ────────────────────────────────────── */

.ks3-zb { margin-top: 22px; }
.ks3-zb-panel {
  padding: 22px 24px;
  border-radius: var(--ks3-r-card);
}
.ks3-zb-levels {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
/* ⚖️⚖️ AN UNREACHED LEVEL IS DIMMED AND STILL DRAWN. It is not absent and it
   is not a wrong answer: the whole ladder is on screen from the first paint so
   a student can see how far down there is to go, and — the part that matters —
   can read the SCALE COLUMN as a column. That column is the lesson's argument.
   If this opacity ever resolves to 1 the bench has given away every body
   paragraph before the first press; if the row is hidden instead, the column
   stops existing and the bench becomes six facts in a list. */
.ks3-zb-level {
  display: flex;
  gap: 14px;
  align-items: flex-start;
  padding: 14px 16px;
  border-radius: var(--ks3-r-panel);
  border: 2px solid transparent;
  background: transparent;
  opacity: var(--ks3-dim-ahead);
}
.ks3-zb-level[data-shown] { opacity: 1; }
.ks3-zb-num {
  flex: 0 0 30px;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 9px;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 15px;
  border: 2px solid transparent;
}
.ks3-zb-cell { flex: 1 1 auto; }
.ks3-zb-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 14px;
}
.ks3-zb-name {
  font-size: 19px;
  font-weight: 700;
}
.ks3-zb-scale {
  font-family: var(--ks3-font-mono);
  font-size: 15px;
}
/* ⚠️ `:not([hidden])` ON THE `display`, AND IT IS NOT TIDINESS. The body is a
   `<span>`, so it needs `display: block` to sit under the name — and a bare
   `.ks3-zb-body { display: block }` BEATS the UA's `[hidden] { display: none }`
   at (0,1,0) against (0,1,0) on source order, which would put all six body
   paragraphs on screen for a reader with JS off and for anything that quotes
   the page. `setHidden` also writes an inline `display:none`, so the live page
   would have looked perfect. MRB-242, found here by `b10-zoom-bottomed`. */
.ks3-zb-body:not([hidden]) {
  display: block;
  margin-top: 6px;
  font-size: 18px;
  line-height: 1.55;
}
.ks3-zb-foot {
  margin-top: 20px;
  display: flex;
  align-items: center;
  gap: 13px;
  flex-wrap: wrap;
}
.ks3-zb-in[disabled] { opacity: .45; cursor: default; }
.ks3-zb-in[disabled]:hover { transform: none; }
.ks3-zb-close {
  margin: 18px 0 0;
  padding: 17px 19px;
  border-radius: var(--ks3-r-panel);
  font-size: 18px;
  line-height: 1.6;
}
/* ⚠️ THE SAY-IT-BACK PANEL IS PART OF THE BENCH, not a block of its own —
   measured, it is inside `<section id="s-bench">`. It sits on the translucent
   well rather than on the nested panel, which is what tells the eye it is a
   second thing on the same bench and not a second bench. */
.ks3-zb-say {
  margin-top: 18px;
  padding: 20px 22px;
  border-radius: var(--ks3-r-card);
}
.ks3-zb-saylabel {
  margin: 0 0 10px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
}
.ks3-zb-qtabs {
  margin-top: 0;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 9px;
}
.ks3-zb-qtab {
  width: auto;
  padding: 10px 15px;
  font-size: 16px;
  font-weight: 700;
}
.ks3-zb-answer {
  margin: 14px 0 0;
  font-size: 18px;
  line-height: 1.55;
}

/* ── zoom-bench on ink ─────────────────────────────────────────────────── */
.ks3-dark .ks3-zb-panel { background: var(--ks3-dark-panel); }
.ks3-dark .ks3-zb-name { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-zb-level[data-shown] .ks3-zb-name { color: var(--ks3-on-dark); }
/* ⚖️ THE SCALE FIGURE IS THE ALERT, ON EVERY ROW, REACHED OR NOT. It is the
   column the lesson is about, and dimming it per row would leave the journey
   unreadable before it starts. The row's own 45% opacity is what says
   "not yet" — a second treatment on the figure would say "not important". */
.ks3-dark .ks3-zb-scale { color: var(--ks3-alert); }
.ks3-dark .ks3-zb-body { color: var(--ks3-on-dark-body); }
.ks3-dark .ks3-zb-num {
  border-color: var(--ks3-on-dark-muted);
  color: var(--ks3-on-dark-muted);
}
.ks3-dark .ks3-zb-level[data-shown] .ks3-zb-num {
  background: var(--ks3-alert);
  border-color: var(--ks3-alert);
  color: var(--ks3-ink);
}
/* ⚖️ THE LEVEL JUST REACHED TAKES THE ALERT OUTLINE AND A LIT GROUND. Still
   not a mark (MRB-196 R10): it says where the student is standing, and there
   is nothing on this bench to be right or wrong about. */
.ks3-dark .ks3-zb-level[data-here] {
  border-color: var(--ks3-alert);
  background: rgba(255, 255, 255, .06);
}
.ks3-dark .ks3-zb-level[data-shown]:not([data-here]) {
  border-color: var(--ks3-on-dark-muted);
}
.ks3-dark .ks3-zb-in,
.ks3-dark .ks3-zb-out {
  background: var(--ks3-on-dark);
  border-color: var(--ks3-on-dark);
  color: var(--ks3-ink);
}
/* ⚖️ THE BOTTOM-OUT PARAGRAPH IS THE PAGE GROUND ON AN INK BLOCK — cream
   inside ink, the one element on this bench whose text is INK. It is what the
   whole climb down exists to reach, and it is the sentence that says nothing
   was swapped for anything else on the way. */
.ks3-dark .ks3-zb-close {
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}
.ks3-dark .ks3-zb-say { background: rgba(255, 255, 255, .06); }
.ks3-dark .ks3-zb-saylabel { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-zb-answer { color: var(--ks3-on-dark-body); }
.ks3-dark .ks3-zb-qtab[aria-pressed="true"] {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-accent);
  color: var(--ks3-on-dark);
}

/* ── b10-03 `#s-bench` · model-builder ─────────────────────────────────── */

.ks3-dh { margin-top: 22px; }
.ks3-dh-dials {
  display: flex;
  flex-wrap: wrap;
  gap: 20px 30px;
}
.ks3-dh-dialname {
  margin: 0 0 9px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
}
.ks3-dh-opts {
  margin-top: 0;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 9px;
}
.ks3-dh-opt {
  width: auto;
  padding: 10px 15px;
  font-size: 16px;
  font-weight: 700;
}
.ks3-dh-panel {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-card);
}
.ks3-dh-modelline {
  margin: 0;
  font-size: 20px;
  font-weight: 700;
}
.ks3-dh-cards {
  list-style: none;
  margin: 18px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
/* ⚖️⚖️ THE CARD OUTLINE IS THE VERDICT, AND IT IS ON THE MODEL. This is one of
   the three B10 benches that adjudicate a commitment (schema §0.6), shipped as
   Design drew it: green when the evidence is consistent with the model on the
   bench, alert when it rules that model out. The DIAL BUTTONS never take a
   mark — amber there means "this is the model you have built", and amber here
   means "this piece of evidence says no". A wrong IDEA is what is being
   confronted, never the student. */
.ks3-dh-card {
  padding: 15px 17px;
  border-radius: var(--ks3-r-panel);
  border: 2px solid transparent;
}
.ks3-dh-cardhead {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 6px 16px;
}
.ks3-dh-cardname {
  margin: 0;
  font-size: 18px;
  font-weight: 700;
}
.ks3-dh-tag {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  letter-spacing: .05em;
  text-transform: uppercase;
}
.ks3-dh-what {
  margin: 6px 0 0;
  font-size: 17px;
  line-height: 1.5;
}
/* The ELIMINATION TEXT — the line that names what this evidence rules out, and
   the only thing on the bench that tells a student which decision to change.
   Amber because it is a wrong idea being confronted. */
.ks3-dh-why {
  margin: 6px 0 0;
  font-size: 17px;
  line-height: 1.5;
}
.ks3-dh-verdict {
  margin-top: 20px;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
}
.ks3-dh-verdicttag {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.ks3-dh-verdictbody {
  margin: 11px 0 0;
  font-size: 18px;
  line-height: 1.6;
}

/* ── model-builder on ink ──────────────────────────────────────────────── */
.ks3-dark .ks3-dh-dialname { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-dh-panel { background: var(--ks3-dark-panel); }
.ks3-dark .ks3-dh-modelline { color: var(--ks3-on-dark); }
.ks3-dark .ks3-dh-card { background: rgba(255, 255, 255, .06); }
.ks3-dark .ks3-dh-card[data-pass] { border-color: var(--ks3-ok); }
.ks3-dark .ks3-dh-card:not([data-pass]) { border-color: var(--ks3-alert); }
.ks3-dark .ks3-dh-cardname { color: var(--ks3-on-dark); }
/* ⊕ MRB-252 (RULED) · audit 3.4 — 14px mono. The card OUTLINE above stays
   `--ks3-ok`: an outline is a mark and marks are what the token is for. */
.ks3-dark .ks3-dh-tag[data-dh-tag="pass"] { color: var(--ks3-ok-dark); }
.ks3-dark .ks3-dh-tag[data-dh-tag="fail"] { color: var(--ks3-alert); }
.ks3-dark .ks3-dh-what { color: var(--ks3-on-dark-body); }
.ks3-dark .ks3-dh-why { color: var(--ks3-alert); }
.ks3-dark .ks3-dh-opt[aria-pressed="true"] {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-accent);
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-dh-verdict {
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}
.ks3-dark .ks3-dh-verdicttag { color: var(--ks3-accent-text); }
.ks3-dark .ks3-dh-verdictbody { color: var(--ks3-ink-body); }

/* ── b10-04 `#s-bench` · pea-cross ─────────────────────────────────────── */

.ks3-pc { margin-top: 22px; }
.ks3-pc-parents {
  display: flex;
  flex-wrap: wrap;
  gap: 20px 30px;
}
.ks3-pc-parentname {
  margin: 0 0 9px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
}
.ks3-pc-genos {
  margin-top: 0;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 9px;
}
.ks3-pc-geno {
  width: auto;
  padding: 10px 15px;
  font-size: 16px;
  font-weight: 700;
}
.ks3-pc-panel {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-card);
}
.ks3-pc-crossline {
  margin: 0;
  font-size: 20px;
  font-weight: 700;
}
/* ⚠️ THE SINGLE-SEED CARD AND THE HUNDRED-SEED TALLY ARE NEVER ON SCREEN
   TOGETHER, and that is the instrument's argument rather than a layout
   choice: one seed is the "chance decides each one" story, a hundred is the
   "only totals show the pattern" story. Growing a hundred CLEARS this card
   (Design's `last: n === 1 ? last : null`). */
.ks3-pc-last {
  margin-top: 16px;
  padding: 16px 18px;
  border-radius: var(--ks3-r-panel);
}
.ks3-pc-lastlabel {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  letter-spacing: .06em;
  text-transform: uppercase;
}
.ks3-pc-lastline {
  margin: 8px 0 0;
  font-size: 19px;
  line-height: 1.5;
}
.ks3-pc-tally { margin-top: 18px; }
.ks3-pc-rows {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.ks3-pc-rowhead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.ks3-pc-rowname {
  margin: 0;
  font-size: 17px;
  font-weight: 600;
}
.ks3-pc-rowvalue {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 16px;
}
.ks3-pc-track {
  display: block;
  margin-top: 5px;
  height: 16px;
  border-radius: 99px;
  overflow: hidden;
}
.ks3-pc-bar {
  display: block;
  height: 100%;
  transition: width .3s;
}
.ks3-pc-ratio {
  margin: 12px 0 0;
  font-family: var(--ks3-font-mono);
  font-size: 16px;
  font-weight: 500;
}
.ks3-pc-note {
  margin: 18px 0 0;
  padding: 15px 17px;
  border-radius: var(--ks3-r-panel);
  font-size: 18px;
  line-height: 1.55;
}
.ks3-pc-foot {
  margin-top: 18px;
  display: flex;
  align-items: center;
  gap: 13px;
  flex-wrap: wrap;
}

/* ── pea-cross on ink ──────────────────────────────────────────────────── */
.ks3-dark .ks3-pc-parentname { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-pc-panel { background: var(--ks3-dark-panel); }
.ks3-dark .ks3-pc-crossline { color: var(--ks3-on-dark); }
.ks3-dark .ks3-pc-last { background: rgba(255, 255, 255, .06); }
.ks3-dark .ks3-pc-lastlabel { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-pc-lastline { color: var(--ks3-on-dark-body); }
.ks3-dark .ks3-pc-rowname { color: var(--ks3-on-dark); }
.ks3-dark .ks3-pc-rowvalue { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-pc-track { background: rgba(255, 255, 255, .08); }
/* ⚖️ THE TWO BARS ARE TWO COLOURS AND NEITHER IS A MARK. Purple-flowered takes
   the alert and white-flowered the muted, exactly as Design paints them —
   which is a quantity being distinguished from another quantity, not a right
   answer being distinguished from a wrong one. There is nothing on this bench
   to be right about: chance decides each seed. */
.ks3-dark .ks3-pc-bar[data-pc-bar] { background: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-pc-row[data-pc-row="dominant"] .ks3-pc-bar {
  background: var(--ks3-alert);
}
.ks3-dark .ks3-pc-ratio { color: var(--ks3-alert); }
.ks3-dark .ks3-pc-note {
  background: rgba(255, 255, 255, .06);
  color: var(--ks3-on-dark-body);
}
.ks3-dark .ks3-pc-geno[aria-pressed="true"] {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-accent);
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-pc-one,
.ks3-dark .ks3-pc-many,
.ks3-dark .ks3-pc-clear {
  background: var(--ks3-on-dark);
  border-color: var(--ks3-on-dark);
  color: var(--ks3-ink);
}

/* ── b10-05 `#s-bench` · species-cases ─────────────────────────────────── */

.ks3-sc { margin-top: 22px; }
.ks3-sc-tabslabel {
  margin: 0 0 9px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
}
.ks3-sc-tabs {
  margin-top: 0;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 9px;
}
.ks3-sc-tab {
  width: auto;
  padding: 10px 15px;
  font-size: 16px;
  font-weight: 700;
}
.ks3-sc-panel {
  margin-top: 20px;
  padding: 24px 26px;
  border-radius: var(--ks3-r-card);
}
.ks3-sc-title {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 25px;
  line-height: 1.2;
  letter-spacing: -.02em;
}
.ks3-sc-facts {
  margin: 12px 0 0;
  font-size: 18px;
  line-height: 1.55;
}
.ks3-sc-commit {
  margin: 20px 0 10px;
  font-size: 17px;
  font-weight: 600;
}
.ks3-sc-verdicts {
  margin-top: 0;
  flex-direction: column;
  gap: 9px;
}
.ks3-sc-verdict {
  gap: 12px;
  align-items: flex-start;
  font-size: 18px;
  line-height: 1.5;
  padding: 14px 16px;
  border-radius: var(--ks3-r-panel);
}
.ks3-sc-letter {
  flex: 0 0 26px;
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: 8px;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 14px;
  border: 2px solid transparent;
}
/* ⛔ ONCE THE CASE IS OPENED THE UNCHOSEN VERDICTS DIM AND THE CHOSEN ONE
   KEEPS EXACTLY THE GROUND IT HAD. That is the whole treatment: no green on
   the right one, no red on the wrong one, no badge. The bench says whether the
   commitment held in WORDS, on the cream panel below (schema §0.6), and the
   student's own button is never marked (MRB-196 R10). */
.ks3-sc-case[data-sc-opened] .ks3-sc-verdict { cursor: default; }
.ks3-sc-case[data-sc-opened] .ks3-sc-verdict[aria-pressed="false"] {
  opacity: .5;
}
.ks3-sc-check[disabled] { opacity: .45; cursor: default; }
.ks3-sc-check[disabled]:hover { transform: none; }
.ks3-sc-foot {
  margin-top: 18px;
  display: flex;
  align-items: center;
  gap: 13px;
  flex-wrap: wrap;
}
.ks3-sc-tally {
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
}
.ks3-sc-out {
  margin-top: 18px;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
}
.ks3-sc-tag {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.ks3-sc-answer {
  margin: 9px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 22px;
  line-height: 1.25;
  letter-spacing: -.02em;
}
.ks3-sc-why {
  margin: 11px 0 0;
  font-size: 18px;
  line-height: 1.6;
}

/* ── species-cases on ink ──────────────────────────────────────────────── */
.ks3-dark .ks3-sc-tabslabel { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-sc-panel { background: var(--ks3-dark-panel); }
.ks3-dark .ks3-sc-title { color: var(--ks3-on-dark); }
.ks3-dark .ks3-sc-facts { color: var(--ks3-on-dark-body); }
.ks3-dark .ks3-sc-commit { color: var(--ks3-on-dark); }
.ks3-dark .ks3-sc-tab[aria-pressed="true"] {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-accent);
  color: var(--ks3-on-dark);
}
/* A verdict is a full-width ROW on the panel ground, not a segment — three
   sentences that have to be read, not three chips to be scanned. */
.ks3-dark .ks3-sc-verdict {
  background: transparent;
  border-color: var(--ks3-on-dark-muted);
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-sc-verdict[aria-pressed="true"] {
  background: rgba(255, 255, 255, .1);
  border-color: var(--ks3-accent);
}
.ks3-dark .ks3-sc-letter {
  border-color: var(--ks3-on-dark-muted);
  color: var(--ks3-on-dark-muted);
}
/* ⊕ MRB-297 · 1 Sep 2026 — THE LETTER FOLLOWS ITS VERDICT BUTTON, which
   moved to `--ks3-accent` on 30 Aug while this did not. Same correction and
   same reason as the fault letter in B6: the accent cannot be the glyph at
   14px/800, so it is the border, and the glyph takes `--ks3-on-dark`. The
   unchosen letter stays muted on both counts, so chosen and unchosen still
   differ in two properties and not one. */
.ks3-dark .ks3-sc-verdict[aria-pressed="true"] .ks3-sc-letter {
  border-color: var(--ks3-accent);
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-sc-check {
  background: var(--ks3-on-dark);
  border-color: var(--ks3-on-dark);
  color: var(--ks3-ink);
}
.ks3-dark .ks3-sc-tally { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-sc-out {
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}
/* ⛔ ACCENT-TEXT AT 6.0:1, NEVER `--ks3-accent` AT 3.4:1 — the tag is 14px
   mono. And ONE TONE for both verdicts: the bench says whether the commitment
   held and says it the same way whichever way it went. */
.ks3-dark .ks3-sc-tag { color: var(--ks3-accent-text); }
.ks3-dark .ks3-sc-answer { color: var(--ks3-ink); }
.ks3-dark .ks3-sc-why { color: var(--ks3-ink-body); }

/* ═══ END B10 ═══ */

/* ═══ BEGIN B11 ═══════════════════════════════════════════════════════════
   B11 · Evolution, extinction and biodiversity (⊕ MRB-248)

   KS3 Biology's last unit. Four instruments, one per lesson, every one of
   them on `ks3-block ks3-dark ks3-practical` — measured off Design's own
   `#s-bench` class attribute on all four delivered pages, not inferred from
   the kind name (schema §0.3).

   ⚠️ SO EVERY COLOUR RULE HERE IS WRITTEN UNDER `.ks3-dark …`. `.ks3-dark p`
   is (0,1,1) and a bare component class is (0,1,0), so a rule written as
   `.ks3-ab-why { color: … }` LOSES to the dark-ground default and ships a
   sentence at roughly 1.2:1 that no grep will ever find, because the CSS is
   right there saying the correct thing. Only a computed read catches it, and
   `ks3_parity.check_dark_text_specificity()` does exactly that.

   ⚠️ AND NOTHING HERE IS A MARK (MRB-196 R10, schema §0.7). An alert ground
   on a tab says "this is the world the bench is standing in", never "this is
   correct". These four benches take no prediction and adjudicate nothing;
   each gives its verdict in WORDS, on cream, in one tone. No option button
   takes a verdict class anywhere in this unit.

   ⚑ AMBER IS DOING TWO JOBS IN THIS UNIT AND IT IS FLAGGED, NOT REPAINTED.
   `--ks3-alert` is the platform's "a wrong IDEA is being confronted" ink, and
   B11 also uses it as a DATA colour throughout — the worst-here bar and
   figure here, the dark-moth stack on b11-02, the sub-40 risk band on
   b11-03, the zero-survivors bars on b11-04. Design drew all four that way
   and none of them marks a student, so the rule is not breached; it is the
   same ink carrying two meanings on four pages, and it is reproduced as drawn
   with a parity row on every instance so the day the palette question is
   ruled the gate names every value.

   ⚠️ AND `--ks3-accent` IS 3.4:1 — LARGE TEXT ONLY. Every mono caption, tag
   and label below 24px takes `--ks3-accent-text` (6.0:1) instead. On ink the
   accent is unusable at any size and the alert takes its place, which is what
   the `.ks3-dark` blocks below do.

   ⊖ AND THERE IS NO B11 REDUCED-MOTION BLOCK, DELIBERATELY, for B10's reason
   exactly. The unit has no canvas, no rAF and no timer in any wire function,
   so there is no tick that would have to test the query inside itself
   (contract R4, the b2-03 slip). Any transition declared below is already
   beaten by R6's platform rule (`*, *::before, *::after {
   transition-duration: .001ms !important }`), whose `!important` no later
   bare declaration can win against, so a B11 block would be noise. Verified
   in a
   browser under emulated `prefers-reduced-motion: reduce` rather than
   asserted. */

/* ── b11-01 `#s-bench` · advantage-bench ───────────────────────────────── */

.ks3-ab { margin-top: 22px; }
.ks3-ab-tabslabel {
  margin: 0 0 9px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
}
.ks3-ab-tabs {
  margin-top: 0;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 9px;
}
.ks3-ab-tab {
  width: auto;
  padding: 10px 15px;
  font-size: 16px;
  font-weight: 700;
}
.ks3-ab-panel {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-card);
}
.ks3-ab-envname {
  margin: 0;
  font-size: 20px;
  font-weight: 700;
}
.ks3-ab-envnote {
  margin: 6px 0 0;
  font-size: 18px;
  line-height: 1.55;
}
.ks3-ab-rows {
  list-style: none;
  margin: 20px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 13px;
}
/* ⚖️ NAME LEFT, FIGURE RIGHT, ON ONE BASELINE — and it WRAPS. The figure is
   the longest string on the row once " · best here" is on the end of it, and
   a narrow screen has to be able to drop it under the name rather than
   squeezing the animal's name to three characters. */
.ks3-ab-rowhead {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 4px 12px;
}
.ks3-ab-name {
  margin: 0;
  font-size: 18px;
  font-weight: 700;
}
.ks3-ab-chance {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 16px;
}
/* The track is the 100% the bar is a fraction of, so it is drawn whether the
   bar fills it or not — a 20% row has to READ as a fifth of something, not as
   a short bar floating on the panel. */
.ks3-ab-track {
  display: block;
  margin-top: 5px;
  height: 16px;
  border-radius: 99px;
  overflow: hidden;
}
.ks3-ab-bar {
  display: block;
  height: 100%;
}
.ks3-ab-why {
  margin: 6px 0 0;
  font-size: 17px;
  line-height: 1.5;
}
.ks3-ab-verdict {
  margin: 18px 0 0;
  padding: 16px 18px;
  border-radius: var(--ks3-r-panel);
  font-size: 18px;
  line-height: 1.6;
}

/* ── advantage-bench on ink ────────────────────────────────────────────── */
.ks3-dark .ks3-ab-tabslabel { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-ab-panel { background: var(--ks3-dark-panel); }
.ks3-dark .ks3-ab-envname { color: var(--ks3-on-dark); }
.ks3-dark .ks3-ab-envnote { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-ab-name { color: var(--ks3-on-dark); }
.ks3-dark .ks3-ab-why { color: var(--ks3-on-dark-body); }
.ks3-dark .ks3-ab-track { background: rgba(255, 255, 255, .08); }
/* ⚖️ A CHOSEN TAB IS THE ALERT GROUND, NOT THE ALERT BORDER — Design's `seg()`
   paints `background: var(--ks3-alert); color: var(--ks3-ink)`. The platform's
   `.ks3-dark .ks3-option[aria-pressed="true"]` gives an alert BORDER, which is
   right for an answer committed to and wrong for a switch that is currently
   on. Same specificity (0,3,0) and later in the file, so source order decides
   — deliberately, because this narrows one unit rather than replacing the
   platform rule. Still not a mark: it says which world the bench is standing
   in, never that the student is right. */
.ks3-dark .ks3-ab-tab[aria-pressed="true"] {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-accent);
  color: var(--ks3-on-dark);
}
/* ⚖️⚖️ THE COLUMN'S TWO ENDS, AND EVERY OTHER ROW IS MUTED. The default is
   the muted tone on BOTH the figure and the bar, and `data-ab-rank` is only
   emitted where the column has a unique maximum AND a unique minimum
   (`_b11_ab_extremes`). That is the disease-panel fix in the stylesheet's
   half of the job: with no attribute there is no green and no amber, and
   every one of the five rows is muted under a verdict that says none of the
   variations helps.

   ⚑ AND THE TWO COLOURS ARE DATA HERE, NOT A MARK. Amber is the bottom of a
   column, not a wrong answer; green is the top of one, not a right answer.
   The student has predicted nothing on this bench. Both carry the WORD as
   well as the colour — " · best here", " · worst here" — so nothing here is
   signalled by colour alone (R2).

   ⊕ RULED, 19 Aug 2026 (MRB-252 comment 2). This comment used to end "both
   are drawn as Design drew them", flag the palette question as OPEN and hand
   it to Mide. It is no longer open, and the reasoning above is exactly why it
   went the way it did: THE COLOURS ARE DATA, AND AMBER IS NOT THE DATA
   COLOUR. Amber is reserved for warning and confrontation — the `#s-think`
   register and genuine caution or loss — so "the bottom of a column" is a
   category and moves to `--ks3-data`. The green moves for a different reason,
   under the same ruling: `--ks3-ok` is a mark and this is a 16px figure, so
   the FIGURE takes `--ks3-ok-dark` while the BAR keeps `--ks3-ok`.

   Design's drawing is not overruled here so much as re-tokenised: the bench
   still reads as one colour at the top of a column and a different one at the
   bottom, still with the word beside each. What changed is which colour, and
   that is a palette decision the ticket has now taken. */
.ks3-dark .ks3-ab-chance { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-ab-bar { background: var(--ks3-on-dark-muted); }
/* ⊕ MRB-252 (RULED) · audit 3.4 + the amber ruling, on one pair of rows.
   The FIGURES are text and take the text greens/blues; the BARS are fills
   and keep the mark tokens. That split is the whole of the ruling in four
   lines: `--ks3-ok` never paints a letter again, and amber never labels a
   column again. */
.ks3-dark .ks3-ab-chance[data-ab-rank="best"] { color: var(--ks3-ok-dark); }
.ks3-dark .ks3-ab-chance[data-ab-rank="worst"] { color: var(--ks3-data); }
.ks3-dark .ks3-ab-bar[data-ab-rank="best"] { background: var(--ks3-ok); }
.ks3-dark .ks3-ab-bar[data-ab-rank="worst"] { background: var(--ks3-data); }
/* ⚠️ (0,2,0) — the verdict is cream inside ink, so it re-declares INK against
   `.ks3-dark p` rather than inheriting the on-dark body colour. It is the
   element the whole switch exists to reach, and it is ONE TONE: the bench
   says what just happened, not whether anybody was right. */
.ks3-dark .ks3-ab-verdict {
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}

/* ── b11-02 `#s-bench` · selection-runner ──────────────────────────────── */

.ks3-nr { margin-top: 22px; }
.ks3-nr-tabslabel {
  margin: 0 0 9px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
}
.ks3-nr-tabs {
  margin-top: 0;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 9px;
}
.ks3-nr-tab {
  width: auto;
  padding: 10px 15px;
  font-size: 16px;
  font-weight: 700;
}
.ks3-nr-panel {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-card);
}
.ks3-nr-barknote {
  margin: 0;
  font-size: 18px;
  line-height: 1.55;
}
/* ⚖️ ONE COLUMN PER GENERATION, OLDEST ON THE LEFT, and the columns FLEX so
   that one column fills the chart and twenty-four share it. That is what makes
   the run read as a run rather than as a widening pile: the chart is always
   the same width and the history gets finer inside it. `align-items:
   flex-end` sits every column on the floor. */
.ks3-nr-chart {
  margin-top: 18px;
  display: flex;
  align-items: flex-end;
  gap: 4px;
  height: 150px;
  padding: 12px;
  border-radius: var(--ks3-r-panel);
}
.ks3-nr-col {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  height: 100%;
  min-width: 0;
  gap: 2px;
}
/* ⚠️ `display: flex` HAS TO BE RE-DECLARED UNDER `:not([hidden])` OR THE
   COLUMN RULE ABOVE BEATS THE UA'S `[hidden] { display: none }` and all
   twenty-four columns paint at once from the first frame (MRB-242). The
   stylesheet, not the runtime, is what would have shipped that. */
.ks3-nr-col[hidden] { display: none; }
.ks3-nr-pale,
.ks3-nr-dark {
  display: block;
  width: 100%;
}
.ks3-nr-pale { border-radius: 3px 3px 0 0; }
.ks3-nr-dark { border-radius: 0 0 3px 3px; }
.ks3-nr-axis {
  margin: 8px 0 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  letter-spacing: .06em;
  text-transform: uppercase;
}
.ks3-nr-live {
  margin-top: 16px;
  display: flex;
  flex-wrap: wrap;
  gap: 12px 28px;
}
.ks3-nr-figure {
  margin: 0;
  font-size: 19px;
  font-weight: 700;
}
.ks3-nr-note {
  margin: 0;
  padding: 15px 17px;
  border-radius: var(--ks3-r-panel);
  font-size: 18px;
  line-height: 1.55;
}
.ks3-nr-notes { margin-top: 16px; }
.ks3-nr-foot {
  margin-top: 18px;
  display: flex;
  align-items: center;
  gap: 13px;
  flex-wrap: wrap;
}

/* ── selection-runner on ink ───────────────────────────────────────────── */
.ks3-dark .ks3-nr-tabslabel { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-nr-panel { background: var(--ks3-dark-panel); }
.ks3-dark .ks3-nr-barknote { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-nr-chart { background: rgba(255, 255, 255, .06); }
.ks3-dark .ks3-nr-axis { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-nr-note {
  background: rgba(255, 255, 255, .06);
  color: var(--ks3-on-dark-body);
}
/* ⚖️ A CHOSEN BARK IS THE ALERT GROUND — Design's `seg()` again. And a bark
   tab is emphatically NOT a mark: it says which world the moths are resting
   on, and pressing it does not reset the population, which is the best thing
   on this bench. */
.ks3-dark .ks3-nr-tab[aria-pressed="true"] {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-accent);
  color: var(--ks3-on-dark);
}
.ks3-dark .ks3-nr-one,
.ks3-dark .ks3-nr-ten,
.ks3-dark .ks3-nr-reset {
  background: var(--ks3-on-dark);
  border-color: var(--ks3-on-dark);
  color: var(--ks3-ink);
}
/* ⚑⚑ THE TWO MOTH COLOURS, AND AMBER IS DATA HERE. `--ks3-alert` is the DARK
   MOTH — the stack at the bottom of every column and the figure beside it —
   and the muted tone is the pale moth. Neither marks anybody: there is no
   prediction on this bench, and what the amber means is "this many of them
   are the dark form". Design drew it; it is reproduced as drawn and asserted
   in parity rows so the day the palette question is ruled the gate names the
   value. `data-label` on each figure carries the WORD, which is on screen
   beside the number, so the two stacks are never told apart by colour alone
   (R2). */
.ks3-dark .ks3-nr-pale { background: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-nr-dark { background: var(--ks3-alert); }
.ks3-dark .ks3-nr-figure[data-nr-series="pale"] {
  color: var(--ks3-on-dark-muted);
}
.ks3-dark .ks3-nr-figure[data-nr-series="dark"] { color: var(--ks3-alert); }

/* ── b11-03 `#s-bench` · pressure-bench ────────────────────────────────── */

.ks3-pb { margin-top: 22px; }
/* Two tab groups side by side, wrapping to a stack when they will not fit —
   the two axes are independent and reading them as one row of nine buttons is
   the one misreading the layout has to prevent. */
.ks3-pb-tabsets {
  display: flex;
  flex-wrap: wrap;
  gap: 20px 30px;
}
.ks3-pb-tabslabel {
  margin: 0 0 9px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
}
.ks3-pb-tabs {
  margin-top: 0;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 9px;
}
.ks3-pb-tab {
  width: auto;
  padding: 10px 15px;
  font-size: 16px;
  font-weight: 700;
}
.ks3-pb-panel {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-card);
}
.ks3-pb-name {
  margin: 0;
  font-size: 21px;
  font-weight: 700;
}
.ks3-pb-traits {
  list-style: none;
  margin: 12px 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 8px 20px;
}
.ks3-pb-trait {
  font-size: 17px;
  line-height: 1.5;
}
.ks3-pb-traitlabel {
  display: block;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  letter-spacing: .06em;
  text-transform: uppercase;
}
/* ⚖️ THE DIVIDER IS THE ARGUMENT'S HINGE. Above it, what the species IS;
   below it, what happens to it. A rule rather than a gap, because the two
   halves have to read as one panel — the traits are the explanation of the
   number, not a separate card beside it. */
.ks3-pb-under {
  margin-top: 20px;
  padding-top: 18px;
  border-top: 2px solid rgba(255, 255, 255, .12);
}
.ks3-pb-pname {
  margin: 0;
  font-size: 19px;
  font-weight: 700;
}
.ks3-pb-pnote {
  margin: 6px 0 0;
  font-size: 18px;
  line-height: 1.55;
}
.ks3-pb-outrow {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-top: 16px;
  flex-wrap: wrap;
}
.ks3-pb-outlabel {
  margin: 0;
  font-size: 17px;
  font-weight: 600;
}
.ks3-pb-outpct {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 17px;
}
.ks3-pb-track {
  display: block;
  margin-top: 6px;
  height: 18px;
  border-radius: 99px;
  overflow: hidden;
}
.ks3-pb-bar {
  display: block;
  height: 100%;
}
.ks3-pb-why {
  margin: 16px 0 0;
  padding: 16px 18px;
  border-radius: var(--ks3-r-panel);
  font-size: 18px;
  line-height: 1.6;
}

/* ── pressure-bench on ink ─────────────────────────────────────────────── */
.ks3-dark .ks3-pb-tabslabel { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-pb-panel { background: var(--ks3-dark-panel); }
.ks3-dark .ks3-pb-name { color: var(--ks3-on-dark); }
.ks3-dark .ks3-pb-trait { color: var(--ks3-on-dark-body); }
.ks3-dark .ks3-pb-traitlabel { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-pb-pname { color: var(--ks3-on-dark); }
.ks3-dark .ks3-pb-pnote { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-pb-outlabel { color: var(--ks3-on-dark); }
.ks3-dark .ks3-pb-track { background: rgba(255, 255, 255, .08); }
.ks3-dark .ks3-pb-tab[aria-pressed="true"] {
  background: var(--ks3-dark-panel);
  border-color: var(--ks3-accent);
  color: var(--ks3-on-dark);
}
/* ⚑⚑ THREE BANDS, AND AMBER IS THE BOTTOM ONE AS DATA. `--ks3-alert` here
   means "this population is in trouble fifty years on", not "you are wrong":
   nothing on this bench is predicted and nothing is marked. The sentence
   underneath says which trouble, in words, so the band is never the only
   signal (R2). Design drew all three; they are reproduced as drawn and
   asserted in parity rows so the day the palette question is ruled the gate
   names every value. */
/* ⊕ MRB-252 (RULED) · audit 3.4 — 17px mono, 3.48:1 as it shipped. */
.ks3-dark .ks3-pb-outpct[data-pb-band="ok"] { color: var(--ks3-ok-dark); }
.ks3-dark .ks3-pb-outpct[data-pb-band="mid"] { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-pb-outpct[data-pb-band="bad"] { color: var(--ks3-alert); }
.ks3-dark .ks3-pb-bar[data-pb-band="ok"] { background: var(--ks3-ok); }
.ks3-dark .ks3-pb-bar[data-pb-band="mid"] { background: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-pb-bar[data-pb-band="bad"] { background: var(--ks3-alert); }
/* ⚠️ (0,2,0) — cream inside ink, so it re-declares INK against `.ks3-dark p`.
   Twenty individually-written sentences land here and this is the only place
   the bench says anything at all. */
.ks3-dark .ks3-pb-why {
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}

/* ── b11-04 `#s-bench` · blight-bench ──────────────────────────────────── */

.ks3-bb { margin-top: 22px; }
.ks3-bb-tabslabel {
  margin: 0 0 9px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
}
.ks3-bb-tabs {
  margin-top: 0;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 9px;
}
.ks3-bb-tab {
  width: auto;
  padding: 10px 15px;
  font-size: 16px;
  font-weight: 700;
}
.ks3-bb-panel {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-card);
}
.ks3-bb-name {
  margin: 0;
  font-size: 20px;
  font-weight: 700;
}
.ks3-bb-note {
  margin: 6px 0 0;
  font-size: 18px;
  line-height: 1.55;
}
.ks3-bb-rows {
  list-style: none;
  margin: 20px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 13px;
}
.ks3-bb-rowhead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}
.ks3-bb-barname {
  margin: 0;
  font-size: 17px;
  font-weight: 600;
}
.ks3-bb-value {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 16px;
}
.ks3-bb-track {
  display: block;
  margin-top: 5px;
  height: 16px;
  border-radius: 99px;
  overflow: hidden;
}
.ks3-bb-bar {
  display: block;
  height: 100%;
}
.ks3-bb-foot {
  margin-top: 18px;
  display: flex;
  align-items: center;
  gap: 13px;
  flex-wrap: wrap;
}
.ks3-bb-run[disabled] { opacity: .45; cursor: default; }
.ks3-bb-run[disabled]:hover { transform: none; }
.ks3-bb-verdict {
  margin: 18px 0 0;
  padding: 17px 19px;
  border-radius: var(--ks3-r-panel);
  font-size: 18px;
  line-height: 1.6;
}

/* ── blight-bench on ink ───────────────────────────────────────────────── */
.ks3-dark .ks3-bb-tabslabel { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-bb-panel { background: var(--ks3-dark-panel); }
.ks3-dark .ks3-bb-name { color: var(--ks3-on-dark); }
.ks3-dark .ks3-bb-note { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-bb-barname { color: var(--ks3-on-dark); }
.ks3-dark .ks3-bb-value { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-bb-track { background: rgba(255, 255, 255, .08); }
/* ⊕ MRB-252 (RULED, 19 Aug 2026) — THIS IS A SELECTION, NOT A WARNING, so it
   is the one amber on this bench that moves. "Which field am I looking at" is
   the plainest category use in the key stage: four tabs, one of them on, and
   nothing about it is a caution. The ruling's line is "amber warns; it never
   merely labels", and this only labels.

   The `color: var(--ks3-ink)` does not move — ink on `--ks3-data` measures
   8.2:1, better than ink on the amber it replaces was obliged to be, and the
   tab keeps its 44px target. The two LOSS states below stay amber, which is
   the same ruling read the other way: they are caution, and caution is what
   amber is now for.

   ⊕ MRB-297 · 1 Sep 2026 — RE-EXAMINED UNDER THE 30 AUG RULING AND KEPT AT
   `--ks3-data`. That ruling moves SELECTION to `--ks3-accent` and leaves
   CATEGORY on `--ks3-data`, and a review of this file read the headline
   above — "THIS IS A SELECTION, NOT A WARNING" — as putting this tab in the
   half that moves. It does not. That headline was written against the amber
   question of 19 Aug, where the only thing being asked was "is this a
   caution?", and the paragraph under it then answers the later question
   ahead of time, in the amendment's own words: "which field am I looking
   at". These four tabs SHOW AND HIDE PANELS — one field's varieties at a
   time, `data-bb-field` against a hidden sibling — so nothing is committed
   to by pressing one, and the next tab press undoes it with no cost. That
   is a view switch, which is category.
   ⚠️ `shared/tokens.css` states the trap in as many words: "Recolouring a
   CATEGORY under cover of this ruling is the one way to get it wrong." Left
   deliberately. Raised for Mide in the MRB-297 report rather than changed,
   because a tab strip is the one shape where the two halves of his ruling
   genuinely touch. */
.ks3-dark .ks3-bb-tab[aria-pressed="true"] {
  background: var(--ks3-data);
  border-color: var(--ks3-data);
  color: var(--ks3-ink);
}
.ks3-dark .ks3-bb-run,
.ks3-dark .ks3-bb-clear {
  background: var(--ks3-on-dark);
  border-color: var(--ks3-on-dark);
  color: var(--ks3-ink);
}
/* ⚑⚑ ZERO IS ITS OWN BAND AND IT IS AMBER, AND IT STAYS AMBER. Design's rule
   is `pct === 0 ? alert : (pct >= 50 ? ok : muted)` — the clone field does not
   do badly, it returns NOTHING, and that is the one number this lesson exists
   to show. The row prints "0 of 1000" beside the bar so the band is never the
   only signal (R2).

   ⊕ RULED, 19 Aug 2026 (MRB-252 comment 2). The line above used to say "amber
   here is DATA, not a mark" and to park the palette question for Mide. The
   ruling splits that sentence: amber is no longer available as a category
   colour — but "nothing survived" is not a category, it is a LOSS, and loss
   is caution-adjacent, which is precisely what amber is now reserved for.
   So this one does not move, and the field TAB above it does. The `ok` band
   in the next rule moves to `--ks3-ok-dark` for the other half of the ruling:
   it is a 16px figure, and `--ks3-ok` is a fill. */
.ks3-dark .ks3-bb-value[data-bb-band="none"] { color: var(--ks3-alert); }
.ks3-dark .ks3-bb-value[data-bb-band="ok"] { color: var(--ks3-ok-dark); }
.ks3-dark .ks3-bb-value[data-bb-band="mid"] { color: var(--ks3-on-dark-muted); }
.ks3-dark .ks3-bb-bar[data-bb-band="none"] { background: var(--ks3-alert); }
.ks3-dark .ks3-bb-bar[data-bb-band="ok"] { background: var(--ks3-ok); }
.ks3-dark .ks3-bb-bar[data-bb-band="mid"] {
  background: var(--ks3-on-dark-muted);
}
/* ⚑ AND THE YIELD BAR IS AMBER TOO, WHICH IS DESIGN'S. It is the COST of
   variation — the reason a farmer plants a clone in the first place — and it
   is drawn in the same ink as "nothing survived" two rows above it. The
   variation bar beside it stays muted, as drawn.

   ⊕ RULED, 19 Aug 2026 (MRB-252 comment 2). This was flagged rather than
   repainted, on the grounds that the palette question was Mide's. It has been
   answered, and it answers this one as KEEP: "what you gave up" is a cost, a
   cost is a loss, and loss is inside the warning register the ruling reserves
   amber for. Sharing the ink with "nothing survived" is now correct rather
   than merely drawn — the two ARE the same kind of statement. After this run
   the page carries amber twice, for one meaning, instead of four times for
   three. */
.ks3-dark .ks3-bb-bar[data-bb-tone="muted"] {
  background: var(--ks3-on-dark-muted);
}
.ks3-dark .ks3-bb-bar[data-bb-tone="cost"] { background: var(--ks3-alert); }
/* ⚠️ (0,2,0) — cream inside ink, and the element the whole release exists to
   reach. One tone: the bench says what happened to the crop, not whether
   anybody was right. */
.ks3-dark .ks3-bb-verdict {
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}

/* ═══ END B11 ═══ */

/* ═══ BEGIN C3 ═══════════════════════════════════════════════════════════
   C3 · Mixtures and separation (⊕ MRB-272)

   NINE instrument families, all DOM, no canvas anywhere in the unit —
   `purity-sorter`, `dissolve-lab`, `sequence-rebuild` (two phases, one
   family), `crystal-bench`, `method-choice`, `still-run`, `chroma-run`,
   `plan-critique`, `melting-point-bench`. Every value below is measured off
   Design's seven approved pages in `docs/ks3/design-reference/c3/`, which
   are React prototypes carrying their real design in inline `style`
   attributes and in the style strings their component script composes.
   Where a page and the engine disagreed, the page won.

   ⚠️ EVERY ONE OF THE NINE SITS ON A LIGHT `ks3-block ks3-check`. There is
   no ink-dark practical block anywhere in C3 — checked against all seven
   built pages, not inferred from the kind names. So this whole block is
   written for cream, and the ONLY ink grounds below are nested REVEAL
   PANELS that Design paints `--ks3-ink` herself (the sorter's verdict, the
   jobs answer, the critique answer, the rebuild report, the still result,
   the chromatography and melting-point verdicts). Those take
   `--ks3-on-dark` / `--ks3-on-dark-body` and NEVER `--ks3-accent-text`.

   ⚠️ AND EVERY TEXT RULE IS SELF-SCOPED AT (0,2,0) — `.ks3-psort
   .ks3-psort-why`, not a bare `.ks3-psort-why`. A bare component class is
   (0,1,0) and loses to `.ks3-dark p` at (0,1,1) the day anything moves one
   of these instruments onto ink, and it loses SILENTLY: the CSS reads
   correctly and the sentence ships at ~1.2:1. C1's block says the same
   thing for the same reason; two builds have been bitten by it.

   ⚑ AMBER, AND WHY IT STAYS ON FOUR PANELS. `--ks3-alert-tint` grounds the
   predict gates on c3-03 (`.ks3-seq-gate`), c3-04 (`.ks3-cryst-gate`) and
   c3-05 (`.ks3-still-predict`) — Design draws all three that way, and
   c3-02's `#s-gate` the same, which is the shell's `.ks3-misconception` and
   not mine. That is amber carrying its RULED meaning and not drifting into
   category: each of those gates exists to elicit a named misconception
   (MIX-07, MIX-09, MIX-10) a paragraph before the bench confronts it. It is
   never a selection and never a reading. Every live value and every
   category hue in this unit is `--ks3-accent-text`, which is what Design
   paints them; `--ks3-data` is `#8FB7FF` and is an on-ink token, unusable
   on these cream grounds, so there is nothing to route to it here.

   ⚠️ AND NOTHING BELOW IS A MARK (R3, MRB-196 R10). Only the mastery ladder
   marks correctness. No option in any of these nine takes green or red at
   any size: a committed control keeps the platform's ordinary accent-tint
   pressed treatment, its spent siblings dim to `--ks3-dim-spent`, and every
   verdict is a PANEL OF WORDS. `--ks3-ok` does not appear in this block at
   all, and neither does `--ks3-danger`.

   ⚠️ `--ks3-accent` IS 3.4:1 — LARGE TEXT ONLY, and it appears below on
   exactly two things, neither of them text: the 6px offset shadow on the
   two "you have seen it all" summaries, and the fill behind an opened
   step's aria-hidden number chip. Every readout, every wide-range cell and
   every reason line takes `--ks3-accent-text` (6.0:1) instead.

   ⊖ AND THERE IS NO C3 REDUCED-MOTION BLOCK, DELIBERATELY — B10's and
   B11's reason exactly, and NOTES-C3 §6 says it out loud: no canvas, no
   rAF, no timer, so there is no tick that would have to re-test the query
   inside itself (contract R4). This block also declares NO transition and
   NO animation of its own, which is the other half of it: the gate that
   freezes transitions before reading computed style has nothing here to
   freeze, and the shared arrive treatment is the shell's.

   ⚠️ NO `display` ON ANYTHING THAT ALSO TAKES `[hidden]`. An author
   `display` beats the UA stylesheet's `[hidden] { display: none }` at ANY
   specificity, and eleven things in this unit are emitted hidden — the
   whole `.ks3-dlab` while the bench is locked, every reveal, every
   emit-both-show-one panel, and the melting-point rows beyond the opening
   repeat count. The guard is the LAST rule in this block so source order
   settles the (0,2,0) ties in its favour. */

/* ── the dial rows, shared by five benches ─────────────────────────────
   Design draws ONE control strip on c3-02, c3-04, c3-05, c3-06 and c3-07:
   a column of captioned groups, caption in mono over a wrapping row of
   segmented buttons. Five families, one set of values, grouped rather than
   copied — a sixth copy is the drift Drift 4's ruling exists to stop.

   The BUTTONS are `.ks3-seg-btn` and take nothing from here. Design's own
   `seg()` is 16px / 11px 16px on `--ks3-option-border`; the platform's is
   17px / 11px 17px on `--ks3-rule-strong`, and Drift 4 ruled the platform's
   one the key stage's only segmented control. Reported rather than
   re-declared. */
.ks3-dlab-dials,
.ks3-cryst-dials,
.ks3-still-dials,
.ks3-chroma-dials,
.ks3-mpb-dials {
  margin-top: 20px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.ks3-dlab .ks3-dlab-diallabel,
.ks3-cryst .ks3-cryst-diallabel,
.ks3-still .ks3-still-diallabel,
.ks3-chroma .ks3-chroma-diallabel,
.ks3-mpb .ks3-mpb-diallabel {
  margin: 0 0 8px;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-dlab-btns,
.ks3-cryst-btns,
.ks3-still-btns,
.ks3-chroma-btns,
.ks3-mpb-btns {
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}

/* ── the one-shot commit banks ─────────────────────────────────────────
   Six controls in this unit commit ONCE and then disable: the sorter's two
   verdicts, the jobs sorter, the plan critique, the pen identification, the
   batch identification and the rebuild's chips. Design dims a disabled
   button that was NOT the one pressed and leaves the pressed one at full
   strength, so the card still says what the student chose.

   ⚖️ `--ks3-dim-spent` (.65), not Design's .45. MRB-257 audit 3.21 ruled
   that value for exactly this state: "you may not press this any more" and
   "you may not read this" are two different sentences and .45 says the
   second. Design's .45 is the value this block deliberately does not
   reproduce, and it is the only such divergence in C3. */
.ks3-psort-opt[disabled],
.ks3-mchoice-opt[disabled],
.ks3-critiq-opt[disabled],
.ks3-chroma-pen[disabled],
.ks3-mpb-verdict-btn[disabled],
.ks3-seq-chip[disabled] { cursor: default; }
.ks3-psort-opt[disabled][aria-pressed="false"],
.ks3-mchoice-opt[disabled][aria-pressed="false"],
.ks3-critiq-opt[disabled][aria-pressed="false"],
.ks3-chroma-pen[disabled][aria-pressed="false"],
.ks3-mpb-verdict-btn[disabled][aria-pressed="false"] { opacity: var(--ks3-dim-spent); }

/* ── the predict gates (c3-03, c3-04, c3-05) ───────────────────────────
   ⚑ THE AMBER GROUND IS DESIGN'S AND IT STAYS. See the block header.

   ⚠️ AND THE PROMPT INSIDE IT IS INK, NOT `.ks3-commit`'s AMBER.
   `ks3_art/c3.py` emits `<p class="ks3-commit">` in all three gates, and
   `.ks3-commit` carries `--ks3-alert` — right on the ink grounds where most
   of the key stage's gates sit, and 1.5:1 here, on cream and on
   `--ks3-alert-tint` alike. That is the identical defect MRB-257 audit 3.18
   found on `.ks3-switch-predict`, and `.ks3-walk-block .ks3-benchgate
   .ks3-commit` and its `.ks3-sbench-block` twin are the two standing
   precedents. Design draws these three prompts as plain 19px/700 ink inside
   the amber panel — 13:1 — so that is what they get, at (0,2,0), which
   beats both `.ks3-commit` (0,1,0) and `.ks3-dark .ks3-commit` (0,2,0,
   earlier). Scoped to the three gates so the shared component stays
   shared. */
.ks3-seq-gate,
.ks3-cryst-gate,
.ks3-still-predict {
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-alert-tint);
  border: 2px solid var(--ks3-ink);
}
.ks3-cryst-gate,
.ks3-still-predict { margin-top: 22px; }
.ks3-seq-gate { margin-top: 14px; padding: 18px 20px; }
.ks3-seq-gate .ks3-commit,
.ks3-cryst-gate .ks3-commit,
.ks3-still-predict .ks3-commit {
  margin: 0;
  font-size: 19px;
  line-height: 1.45;
  color: var(--ks3-ink);
}
/* Design caps the lettered list rather than letting four options run the
   full width of a 1240px rail. */
.ks3-seq-gate .ks3-options { max-width: 30rem; }
.ks3-cryst-gate .ks3-options,
.ks3-still-predict .ks3-options { max-width: 32rem; }

/* ═══ c3-01 `#s-sorter` · purity-sorter ═════════════════════════════════
   Eight independent commitments in a reflowing grid. Design's own track is
   `repeat(auto-fit, minmax(272px, 1fr))`; the `min(272px, 100%)` is the
   390px translation — at 390px a `ks3-block`'s content box is 330px wide,
   so a bare 272px minimum fits, but it stops fitting the moment the block
   padding or the rail changes, and a grid track wider than its container is
   a page that scrolls sideways.

   ⚠️ THE CARD IS `--ks3-inset` ON A `--ks3-card` BLOCK and the reveal
   inside it is `--ks3-ink`. Three grounds stacked, which is Design's
   drawing: the block is the page, the card is a sample, and the answer is
   the thing that arrives on top of it. */
.ks3-psort-grid {
  margin-top: 22px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(272px, 100%), 1fr));
  gap: 16px;
}
.ks3-psort-card {
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
  display: flex;
  flex-direction: column;
}
.ks3-psort .ks3-psort-num {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-psort .ks3-psort-name {
  margin: 6px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 22px;
  line-height: 1.25;
  letter-spacing: -.02em;
}
.ks3-psort .ks3-psort-look {
  margin: 8px 0 0;
  font-size: 17px;
  line-height: 1.5;
  color: var(--ks3-ink-body);
}
/* The particle strip. It is a `role="img"` carrying a per-sample
   `aria-label`, so everything here is the picture and nothing here is
   information: eight strips, eight different sentences, authored in the
   payload. Design caps it at 200px so a fourteen-particle strip reads as a
   sample of a liquid and not as a rule across the card. */
.ks3-psort-dots {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-top: 14px;
  max-width: 200px;
}
/* Each dot's whole appearance — diameter, colour, radius, hairline — is an
   AUTHORED inline declaration passed through by `r_purity_sorter`, so the
   only thing left for the stylesheet is to stop flexbox resizing it. */
.ks3-psort-dot { flex: 0 0 auto; }
.ks3-psort .ks3-psort-ingredients {
  margin: 12px 0 0;
  font-size: 16px;
  line-height: 1.5;
  color: var(--ks3-ink-muted);
}
.ks3-psort-opts {
  margin-top: 16px;
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
.ks3-psort-reveal {
  margin-top: 14px;
  padding: 16px 18px;
  border-radius: 16px;
  background: var(--ks3-ink);
  color: var(--ks3-on-dark);
}
.ks3-psort .ks3-psort-verdict {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 20px;
  line-height: 1.25;
  color: var(--ks3-on-dark);
}
.ks3-psort .ks3-psort-why {
  margin: 8px 0 0;
  font-size: 17px;
  line-height: 1.55;
  color: var(--ks3-on-dark-body);
}
/* The all-eight close. A BAND panel, which is this unit's "here is what the
   bench just showed you" treatment and not a verdict on the student — the
   same panel c3-02's verdict, c3-03's close, c3-04's summary bar, c3-06's
   run report and c3-07's data note take. */
.ks3-psort-close,
.ks3-seq-close,
.ks3-critiq-close,
.ks3-dlab-verdict,
.ks3-cryst-verdict,
.ks3-chroma-outcome,
.ks3-mpb-notes {
  margin-top: 20px;
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
}
.ks3-psort .ks3-psort-close p,
.ks3-seq .ks3-seq-close p,
.ks3-critiq .ks3-critiq-close p,
.ks3-dlab .ks3-dlab-verdict p,
.ks3-mpb .ks3-mpb-note {
  margin: 0;
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-ink);
}

/* ═══ c3-02 `#s-lab` · dissolve-lab ═════════════════════════════════════
   ⚖️ THE RATE/AMOUNT SPLIT IS THE LESSON, AND THE THREE READOUTS ARE HOW A
   STUDENT SEES IT. They are one row of equal tiles on purpose: grams,
   seconds and appearance sit at the same weight and the same size, so that
   moving the stir dial and watching ONE of the three numbers change is the
   whole argument. Nothing here may emphasise one tile over another — a
   larger or brighter "how much" tile would answer the question the bench is
   asking.

   ⚠️ The whole instrument is emitted `hidden` (`locked_by:
   "gate-which-dial"`) and `wireDissolveLab` unhides the section. So
   `.ks3-dlab` takes no `display` — see the guard at the foot. */
.ks3-dlab-readouts {
  margin-top: 24px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(230px, 100%), 1fr));
  gap: 16px;
}
.ks3-dlab-readout,
.ks3-cryst-tile,
.ks3-still-gauge {
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-dlab .ks3-dlab-rlabel,
.ks3-cryst .ks3-cryst-tilelabel,
.ks3-still .ks3-still-glabel,
.ks3-dlab .ks3-dlab-caption,
.ks3-cryst .ks3-cryst-caption {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
/* ⚠️ `--ks3-accent-text`, NOT `--ks3-accent`. Design paints these readouts
   in the orange and they are 26–30px, so the large-text branch would be
   legal — but they are the one thing on the bench that CHANGES, they are
   read as a value rather than as a heading, and `--ks3-accent-text` is
   6.0:1 against `--ks3-accent`'s 3.4:1 on the same inset. Same ink, one
   step darker, no visual change worth the name. */
.ks3-dlab .ks3-dlab-rvalue,
.ks3-cryst .ks3-cryst-value,
.ks3-still .ks3-still-gvalue {
  margin: 8px 0 0;
  font-family: var(--ks3-font-mono);
  font-weight: 500;
  line-height: 1.1;
  color: var(--ks3-accent-text);
}
.ks3-dlab .ks3-dlab-rvalue { font-size: 30px; }
.ks3-cryst .ks3-cryst-value { font-size: 27px; }
.ks3-still .ks3-still-gvalue { font-size: 26px; line-height: 1.15; }
/* ⚖️ `.ks3-cryst-mass` IS DELIBERATELY GIVEN NOTHING OF ITS OWN. It is the
   third tile on c3-04's bench, it carries the one authored `recovered_mass`
   in all nine states, and it is the only readout in C3 with no data hook —
   there is nothing for the runtime to write into. Painting it differently
   from the two tiles beside it would argue that the mass is the special
   one, and the teaching is the exact opposite: the mass is the thing that
   never moves while everything around it does. */
.ks3-dlab .ks3-dlab-rnote {
  margin: 8px 0 0;
  font-size: 16px;
  line-height: 1.45;
  color: var(--ks3-ink-body);
}
/* The beaker. An inset frame carrying the mono caption, and inside it the
   jar: a THREE-SIDED 2px ink box, open at the top, rounded only at the
   bottom two corners. That is Design's drawing of a vessel seen from the
   front and the reason `border-top: 0` is load-bearing rather than tidy. */
.ks3-dlab-beaker {
  margin-top: 22px;
  padding: 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-dlab .ks3-dlab-caption { margin: 0 0 14px; }
.ks3-dlab-jar {
  border: 2px solid var(--ks3-ink);
  border-top: 0;
  border-radius: 0 0 20px 20px;
  background: var(--ks3-card);
  padding: 16px;
  min-height: 132px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 10px;
}
.ks3-dlab-dots {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-content: flex-start;
}
/* Unlike the sorter's, these dots are COMPOSED by `_dlab_dots`, which emits
   only what varies between two states — the diameter and the colour. The
   hairline, the pill radius and the refusal to be flex-resized live here,
   which is what keeps the runtime from having to restate them. */
.ks3-dlab-dot {
  display: block;
  flex: 0 0 auto;
  border-radius: 99px;
  border: 1.5px solid var(--ks3-ink);
}
/* Undissolved solid on the floor of the beaker, under a dashed rule. Two of
   the four solutes are insoluble, so this is a reachable, honest state and
   not an error — nothing here is amber and nothing here is red. */
.ks3-dlab-bottom {
  border-top: 2px dashed var(--ks3-rule-strong);
  padding-top: 10px;
}
.ks3-dlab-bottomdots {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.ks3-dlab .ks3-dlab-bottomnote {
  margin: 8px 0 0;
  font-size: 16px;
  line-height: 1.45;
  color: var(--ks3-ink-muted);
}
/* The two "you have now seen the whole thing" summaries — c3-02's after all
   three temperatures, c3-04's after all three methods. A CARD panel lifted
   off the block by Design's 6px accent offset, which is the KEY FACT
   treatment: the hardest-earned sentence on the page, in the same voice as
   the page's own key fact and not as a verdict on the student. */
.ks3-dlab-summary,
.ks3-cryst-summary {
  margin-top: 16px;
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
  box-shadow: 6px 6px 0 var(--ks3-accent);
}
.ks3-dlab .ks3-dlab-summary p,
.ks3-cryst .ks3-cryst-summary p {
  margin: 0;
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-ink);
}

/* ═══ c3-03 `#s-steps` / `#s-build` · sequence-rebuild ══════════════════
   ONE FAMILY, TWO PHASES, ONE VISUAL SYSTEM. `.ks3-seq-watch` and
   `.ks3-seq-rebuild` are modifiers this block deliberately gives no
   declarations of their own: they are the same five records rendered two
   ways, and a phase that looked different from its own other half would
   invite the student to read them as two exercises.

   ⚠️ THE STEP'S OPEN AND NEXT TREATMENTS ARE A `:has()` ENHANCEMENT, and
   the base rule below is a complete, correct resting card without them.
   Design keys them off component state (`open ? inset : card`, `isNext ?
   accent : rule`); the built markup carries that state only as `[hidden]`
   on a DESCENDANT — the body, and the reveal button — and this file uses
   `:has()` nowhere else and does not introduce a hard dependency on it. A
   browser without `:has()` gets every step on card with a rule border,
   which is exactly what steps 2–5 look like in Design's own opening state.
   Nothing is lost but emphasis; no text and no state word depends on it. */
.ks3-seq-steps {
  list-style: none;
  margin: 22px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.ks3-seq-step {
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-rule);
  display: flex;
  gap: 14px;
  align-items: flex-start;
}
.ks3-seq-step:has([data-seq-body]:not([hidden])) { background: var(--ks3-inset); }
.ks3-seq-step:has(.ks3-seq-open:not([hidden])) { border-color: var(--ks3-accent); }
/* Numbering, aria-hidden: the `<ol>` already carries the position for a
   screen reader, so this is the drawn version of a fact that is stated
   twice. Accent fill under `--ks3-on-dark` is 3.4:1 — an identifying mark
   against a 3:1 bar, and it is Design's own pairing. */
.ks3-seq-num,
.ks3-still-num {
  flex: 0 0 34px;
  height: 34px;
  border-radius: 11px;
  display: grid;
  place-items: center;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 17px;
  background: var(--ks3-band);
  color: var(--ks3-ink-muted);
  border: 2px solid var(--ks3-ink);
}
.ks3-seq-step:has([data-seq-body]:not([hidden])) .ks3-seq-num {
  background: var(--ks3-accent);
  color: var(--ks3-on-dark);
}
.ks3-seq-stepbody,
.ks3-still-stagebody {
  flex: 1 1 auto;
  min-width: 0;
}
.ks3-seq .ks3-seq-title {
  margin: 0;
  font-size: 20px;
  line-height: 1.45;
  font-weight: 700;
  color: var(--ks3-ink);
}
.ks3-seq .ks3-seq-detail {
  margin: 10px 0 0;
  font-size: 18px;
  line-height: 1.6;
  color: var(--ks3-ink-body);
}
/* The reason. `--ks3-accent-text` at 17px/600 — body size, so this is the
   6.0:1 token and never `--ks3-accent`. It is a category of sentence ("why
   this step exists"), not a mark on anything the student did. */
.ks3-seq .ks3-seq-why {
  margin: 10px 0 0;
  font-size: 17px;
  line-height: 1.55;
  font-weight: 600;
  color: var(--ks3-accent-text);
}
.ks3-seq-open { margin-top: 12px; }

/* The rebuild half. */
.ks3-seq-bank {
  margin-top: 20px;
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
/* ⚠️ THE ORDER IS AN `<ol>` AND IT KEEPS ITS NUMBERS. Design's own list is
   `display: flex; flex-direction: column; gap: 8px` with `padding-left:
   26px` — and blockification strips `display: list-item` off a flex item,
   so her markers never render and the 26px of gutter she reserved for them
   sits empty. The shape she drew is an indented numbered list with 8px
   between rows; it is reproduced with the numbers, because the whole
   exercise is "put them in order" and an unnumbered list of five sentences
   is the one thing that cannot show an order. Reported. */
.ks3-seq-order {
  margin: 20px 0 0;
  padding: 0 0 0 26px;
  font-size: 19px;
}
.ks3-seq .ks3-seq-slot { line-height: 1.5; }
.ks3-seq .ks3-seq-slot + .ks3-seq-slot { margin-top: 8px; }
.ks3-seq-foot,
.ks3-still-controls,
.ks3-chroma-foot {
  margin-top: 18px;
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}
.ks3-chroma-foot { margin-top: 20px; }
/* ⚖️ THE REPORT IS INK AND IT IS NOT A MARK. A wrong order is answered with
   what happened on the bench — the offending step's own `tooSoon` string —
   and a right one with the same panel in the same tone. Nothing green,
   nothing red, no score. It is ink for the same reason the sorter's reveal
   is ink: it is the consequence arriving, not a judgement being handed
   down. */
.ks3-seq-report,
.ks3-still-result {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ink);
  color: var(--ks3-on-dark);
}
.ks3-seq .ks3-seq-reporttitle,
.ks3-still .ks3-still-resulttitle {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  line-height: 1.25;
  color: var(--ks3-on-dark);
}
.ks3-seq .ks3-seq-reporttitle { font-size: 23px; }
.ks3-still .ks3-still-resulttitle { font-size: 22px; }
.ks3-seq .ks3-seq-reporttext,
.ks3-still .ks3-still-resulttext {
  margin: 12px 0 0;
  font-size: 18px;
  line-height: 1.6;
  color: var(--ks3-on-dark-body);
}

/* ═══ c3-04 `#s-bench` · crystal-bench ══════════════════════════════════
   ⚖️ NINE STATES, ONE MASS. The three tiles below share their whole
   treatment with c3-02's readouts — see there — and `.ks3-cryst-mass` gets
   nothing of its own on purpose.

   ⚠️ THE GATE IS PER-RUN AND DOES NOT GO AWAY. It is styled above with the
   other two amber gates; the run button lives INSIDE it because Design only
   shows the button once a prediction is in, which is what makes the gate
   unskippable on all nine states rather than only the first. */
.ks3-cryst-run { margin-top: 14px; }
.ks3-cryst-panel { margin-top: 20px; }
.ks3-cryst-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
  gap: 16px;
}
/* The evaporating dish. A CARD ground inside an inset-tiled bench, because
   the dish is the thing being looked into and the tiles are the instruments
   reading it. The 74px floor keeps the dish the same size whether it holds
   three big crystals or forty small ones — a dish that shrank with its
   contents would say the yield had changed, and the yield never changes. */
.ks3-cryst-dish {
  margin-top: 18px;
  padding: 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
}
.ks3-cryst .ks3-cryst-caption { margin: 0 0 16px; }
.ks3-cryst-crystals {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  min-height: 74px;
}
/* ⚠️ 18px, not the band group's 20px. Design lifts this panel two pixels
   tighter to the dish above it than the other five band panels sit to what
   precedes them — the verdict is what the dish just showed, not a separate
   remark about it. */
.ks3-cryst-verdict { margin-top: 18px; }
.ks3-cryst .ks3-cryst-verdict p {
  margin: 0;
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-ink);
}
/* The hazard line is the second paragraph of the same panel, one step
   quieter — it is what the method costs you, not what the run produced. */
.ks3-cryst .ks3-cryst-verdict p + p {
  margin-top: 12px;
  font-size: 18px;
  color: var(--ks3-ink-body);
}

/* ═══ c3-04 `#s-jobs` · method-choice · and c3-07 `#s-critique` ═════════
   Two instruments, one drawing: a column of inset cards, each a statement,
   a row of one-shot buttons and an ink reveal. Design draws them
   identically on two different pages, so they are grouped rather than
   copied. They differ in exactly one place — the critique quotes somebody
   else's plan and sets it in italic, because it is speech and not the
   platform's own instruction.

   ⚠️ NEITHER MARKS, and neither payload carries a `correct` key to mark
   with. A student who chose the other button reads the same panel in the
   same tone and works out the difference. */
.ks3-mchoice,
.ks3-critiq {
  margin-top: 20px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.ks3-mchoice-item,
.ks3-critiq-item,
.ks3-mpb-sample {
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-mchoice .ks3-mchoice-task {
  margin: 0;
  font-size: 20px;
  line-height: 1.5;
  font-weight: 700;
  color: var(--ks3-ink);
}
/* ⚠️ 4px, because the flex column above already supplies 16 of Design's 20.
   The close is a direct child of the same `display: flex` list the four
   judgements sit in, so the container's `gap: 16px` is already between it
   and the last card; the band group's own 20px would put 36px there. */
.ks3-critiq .ks3-critiq-close { margin-top: 4px; }
.ks3-critiq .ks3-critiq-step {
  margin: 0;
  font-size: 19px;
  line-height: 1.55;
  font-style: italic;
  color: var(--ks3-ink);
}
.ks3-mchoice-opts,
.ks3-critiq-opts,
.ks3-chroma-pens,
.ks3-mpb-verdicts {
  margin-top: 14px;
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
.ks3-mchoice-reveal,
.ks3-critiq-reveal {
  margin-top: 14px;
  padding: 16px 18px;
  border-radius: 16px;
  background: var(--ks3-ink);
  color: var(--ks3-on-dark);
}
.ks3-mchoice .ks3-mchoice-answer,
.ks3-critiq .ks3-critiq-answer {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 19px;
  line-height: 1.3;
  color: var(--ks3-on-dark);
}
.ks3-mchoice .ks3-mchoice-why,
.ks3-critiq .ks3-critiq-why {
  margin: 8px 0 0;
  font-size: 17px;
  line-height: 1.55;
  color: var(--ks3-on-dark-body);
}

/* ═══ c3-05 `#s-still` · still-run ══════════════════════════════════════
   ⚖️ THE NO-COOLING BRANCH IS DRAWN AT FULL WEIGHT. Turning the condenser
   off is a reachable state that separates the mixture perfectly and
   collects nothing, and it is the argument of the lesson — so
   `[data-still-warm]` is a `.ks3-still-stagetext` like any other and takes
   no warning treatment of any kind. It is not an error and it must not look
   like one. Amber here would say the student had done something wrong; what
   they have done is exactly one of distillation's two jobs.

   The stage list is the same idiom as c3-03's stepper at one size down —
   Design's own 30px chip against 34px, 16px/18px padding against 20px/22px,
   16px radius against `--ks3-r-panel`. Same `:has()` note applies. */
.ks3-still-body { margin-top: 22px; }
.ks3-still-gauges {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr));
  gap: 14px;
}
.ks3-still-gauge { padding: 18px 20px; }
.ks3-still-stages {
  list-style: none;
  margin: 18px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.ks3-still-stage {
  padding: 16px 18px;
  border-radius: 16px;
  background: var(--ks3-card);
  border: 2px solid var(--ks3-rule);
  display: flex;
  gap: 14px;
  align-items: flex-start;
}
.ks3-still-stage:has([data-still-stagebody]:not([hidden])) { background: var(--ks3-inset); }
.ks3-still-num {
  flex: 0 0 30px;
  height: 30px;
  border-radius: 10px;
  font-size: 15px;
}
.ks3-still-stage:has([data-still-stagebody]:not([hidden])) .ks3-still-num {
  background: var(--ks3-accent);
  color: var(--ks3-on-dark);
}
.ks3-still .ks3-still-stagetitle {
  margin: 0;
  font-size: 19px;
  line-height: 1.5;
  font-weight: 700;
  color: var(--ks3-ink);
}
.ks3-still .ks3-still-stagetext {
  margin: 8px 0 0;
  font-size: 18px;
  line-height: 1.6;
  color: var(--ks3-ink-body);
}

/* ═══ c3-06 `#s-lab` · chroma-run ═══════════════════════════════════════
   ⚖️ THE LANES ARE PROPORTIONAL AND THE SCROLLER IS THE 390px TRANSLATION.
   Every spot is placed by `bottom: <baseline + rf × span>%` off
   `lane_geometry`, so a photographed chromatogram can replace the drawing
   without a payload change; the only absolute number in the figure is the
   strip's authored `height_px`, which arrives inline.

   Design's five lanes are `flex: 1` in a row, which at 390px gives each one
   about 50px — narrower than a spot's own label and too narrow to compare
   heights across, which is the entire task. `min-width: 64px` plus
   `overflow-x: auto` ON THE LANE ROW keeps the comparison readable and
   keeps the scrolling inside the figure: the PAGE never scrolls sideways,
   which is the requirement. */
.ks3-chroma-paper {
  margin-top: 22px;
  padding: 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
}
.ks3-chroma-lanes {
  display: flex;
  gap: 14px;
  align-items: flex-end;
  overflow-x: auto;
  /* ⊕ MRB-280, 21 Aug 2026 — CONTAINMENT, not clipping. A scroller whose
     `position` is static is NOT the containing block for an absolutely
     positioned descendant, so `overflow-x: auto` cannot clip one: the
     descendant resolves against an ancestor OUTSIDE the scroller and
     extends the DOCUMENT instead. That is what pushed c5-04 sideways by
     140px at 390px (see `.ks3-rgrid-scroll` below), and it was the
     invisible `.ks3-sr-only` labels doing it. Every horizontal scroller
     in the key stage now declares itself a containing block, so the
     shape cannot come back one figure at a time. `overflow: hidden` is
     NOT the fix — measured — because the problem is not clipping. */
  position: relative;
  padding-bottom: 4px;
}
.ks3-chroma-lane {
  flex: 1 1 0;
  min-width: 64px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}
/* ⚠️ Design paints the strip `var(--ks3-paper-strip, #FFFDF8)` and
   `--ks3-paper-strip` does not exist in `shared/tokens.css` — every page
   she delivered renders the literal fallback. Rule 1 of this file forbids a
   raw palette value here, and #FFFDF8 against `--ks3-card` #FFFCF5 is a
   difference of two units on one channel, invisible on any screen. The
   strip is defined by its 2px ink outline in Design's drawing and it still
   is. Reported rather than minting a token for a value nobody chose. */
.ks3-chroma-strip {
  position: relative;
  width: 100%;
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
  border-radius: 6px;
}
/* The pencil baseline. `--ks3-rule-strong` on `--ks3-card` measures 2.05:1
   and it STAYS THAT WAY, deliberately and against the 3:1 bar for an
   identifying mark, for two reasons that are both the lesson's.

   A pencil line on paper IS faint — that is why pencil is used: graphite is
   insoluble, so it stays where you drew it and it barely shows. And the
   FAULT state is the contrast: Design draws the pen baseline 3px and dark,
   because a pen line climbs the paper and smears its own dyes across every
   lane. Darkening the pencil line towards the 3:1 bar would make the clean
   run look like the ruined one, which is the one comparison this figure
   exists to make.

   Nothing rides on it: the span is `aria-hidden` inside a `role="img"`
   whose label is swapped per outcome and describes the whole chromatogram
   in words, and the outcome panel below names which of the three decisions
   ruined the run. R2 is satisfied by the words, not by this. */
.ks3-chroma-baseline {
  position: absolute;
  left: 0;
  right: 0;
  height: 2px;
  background: var(--ks3-rule-strong);
}
/* A spot. The `bottom` and the `background` are the payload's, inline; the
   geometry is Design's clean-run spot and lives here so the runtime has one
   thing to change per state rather than six. */
.ks3-chroma-spot {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  width: 58%;
  height: 16px;
  border-radius: 99px;
  opacity: .85;
}
.ks3-chroma .ks3-chroma-lanelabel {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .06em;
  text-transform: uppercase;
  text-align: center;
  color: var(--ks3-ink-muted);
}
/* ⚖️ THE FOUR OUTCOMES ARE ONE PANEL IN ONE TONE. Three of them are ruined
   runs and one is a clean one, and they take the same band ground and the
   same type — because each fault names WHICH DECISION caused it, and none
   of them says the student was wrong. A ruined run painted in amber would
   turn a diagnosis into a mark. */
.ks3-chroma-outcomes { margin-top: 18px; }
.ks3-chroma-outcome { margin-top: 0; }
.ks3-chroma .ks3-chroma-outtitle {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 21px;
  line-height: 1.3;
  color: var(--ks3-ink);
}
.ks3-chroma .ks3-chroma-outtext {
  margin: 10px 0 0;
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-ink);
}
/* The forensic question. An INSET panel — a second question inside the
   bench rather than a result of it — and its answer arrives on ink like
   every other reveal in the unit. */
.ks3-chroma-verdict {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-chroma .ks3-chroma-ask,
.ks3-mpb .ks3-mpb-ask {
  margin: 0;
  font-size: 20px;
  font-weight: 700;
  line-height: 1.4;
  color: var(--ks3-ink);
}
.ks3-chroma .ks3-chroma-note {
  margin: 8px 0 0;
  font-size: 18px;
  line-height: 1.6;
  color: var(--ks3-ink-body);
}
.ks3-chroma-say,
.ks3-mpb-say {
  margin-top: 16px;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ink);
  color: var(--ks3-on-dark);
}
.ks3-chroma .ks3-chroma-saytitle,
.ks3-mpb .ks3-mpb-saytitle {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 21px;
  line-height: 1.25;
  color: var(--ks3-on-dark);
}
.ks3-chroma .ks3-chroma-saytext,
.ks3-mpb .ks3-mpb-saytext {
  margin: 10px 0 0;
  font-size: 18px;
  line-height: 1.6;
  color: var(--ks3-on-dark-body);
}

/* ═══ c3-07 `#s-bench` · melting-point-bench ════════════════════════════
   ⚖️ THE ANOMALY IS REPORTED, NOT DELETED, AND THE TABLE IS WHERE THAT
   HAPPENS. Batch 3's odd run sits in the table at full weight, in the same
   type as its two neighbours, and only its RANGE cell is marked wide. There
   is no strike-through, no dimming and no colour that would let a student
   read "ignore this row" off the page.

   ⚖️ AND A WIDE RANGE IS `--ks3-accent-text` AT 700, NEVER AMBER AND NEVER
   RED. It is the reading the whole lesson turns on — a pure sample melts
   sharply, an impure one melts over a range — so it is a DATUM being
   emphasised, not a warning and not a mark. #A93411 on `--ks3-inset`
   measures 5.9:1, and the emphasis carries a second signal in the weight as
   well as the hue (R2). */
.ks3-mpb-foot { margin-top: 18px; }
.ks3-mpb-data { margin-top: 22px; }
.ks3-mpb-samples {
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.ks3-mpb .ks3-mpb-name {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 21px;
  line-height: 1.25;
  color: var(--ks3-ink);
}
.ks3-mpb-table {
  margin-top: 14px;
  border-collapse: collapse;
  width: 100%;
  max-width: 32rem;
  font-family: var(--ks3-font-mono);
  font-size: 17px;
}
.ks3-mpb .ks3-mpb-table th {
  text-align: left;
  padding: 8px 10px;
  border-bottom: 2px solid var(--ks3-ink);
  font-weight: 500;
  font-size: 13px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
  white-space: nowrap;
}
.ks3-mpb .ks3-mpb-table td {
  padding: 8px 10px;
  border-bottom: 2px solid var(--ks3-rule);
  color: var(--ks3-ink);
  white-space: nowrap;
}
.ks3-mpb .ks3-mpb-range[data-mpb-wide="1"] {
  color: var(--ks3-accent-text);
  font-weight: 700;
}
/* The verdict question sits on CARD inside an inset-carded bench — Design's
   own inversion, and it is doing a job: the three sample panels above are
   the data, and this is the page asking you to read it. */
.ks3-mpb-verdict {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
}

/* ── 390px ─────────────────────────────────────────────────────────────
   The reference set is desktop-first and every rule here is a translation
   decision, stated as one.

   ⚠️ THE MELTING-POINT TABLE IS THE ONLY THING IN C3 THAT CANNOT FIT, and
   the breakpoint is MEASURED rather than guessed. Four columns of 17px DM
   Mono, `white-space: nowrap`, need 433px of content; the sample panel it
   sits in offers viewport − 168px once the lesson rail stops being capped.
   433 + 168 = 601, so the table overflows every viewport under about 600px
   and 34rem (544px) would have left a 56px band where it still spilled.
   40rem (640px) clears it with room, and is the honest number.

   Below it the table becomes its own scroller — GitHub's proven form,
   which keeps the cells laid out as a table while the block around them
   scrolls — rather than wrapping "52.5 °C" onto two lines or shrinking a
   readable measurement to 12px. The PAGE still does not scroll sideways.
   The cost, between 544px and 640px, is that the table sizes to its
   content (433px) instead of stretching to Design's `width: 100%`; a
   39-pixel-narrower data table on a small tablet is the right side of that
   trade.

   The grids above need nothing here: every one is `auto-fit` over
   `minmax(min(Npx, 100%), 1fr)` and collapses to one column on its own,
   and the chromatography lanes carry their own `overflow-x: auto` at every
   width. */
@media (max-width: 40rem) {
  .ks3-mpb-table {
    display: block;
    width: max-content;
    max-width: 100%;
    overflow-x: auto;
    /* ⊕ MRB-280, 21 Aug 2026 — see the note on `.ks3-figure-scroll`: a
       static scroller cannot clip an absolutely positioned descendant,
       because it is not that descendant's containing block. */
    position: relative;
  }
  /* A 44px tap target that has been squeezed to fit is not a tap target, so
     the control rows keep their gap and wrap instead. */
  .ks3-psort-opts,
  .ks3-mchoice-opts,
  .ks3-critiq-opts,
  .ks3-chroma-pens,
  .ks3-mpb-verdicts,
  .ks3-seq-bank { gap: 10px; }
}

/* ── the `[hidden]` guard, and it is LAST on purpose ───────────────────
   An author `display` beats the UA stylesheet's `[hidden] { display: none }`
   at ANY specificity, and this unit is built on emit-both-show-one: the
   whole locked bench, every reveal, every per-mixture panel, every outcome,
   every verdict and the melting-point rows beyond the opening repeat count
   are all in the document and hidden. Several of the rules above set
   `display` on elements that also take `[hidden]` — `.ks3-cryst-panel`'s
   descendants, `.ks3-still-stages`, `.ks3-mpb-samples`, `.ks3-critiq`'s
   items — so the guard is written at (0,2,0) and placed at the END of this
   block, where source order settles the ties in its favour.

   `.ks3-dlab[hidden]` is called out separately because it is the WRAPPER
   that is hidden, not a descendant: the bench does not open until
   `gate-which-dial` is answered, and `demo_mode` is authored `false`. */
.ks3-psort [hidden],
.ks3-dlab [hidden],
.ks3-seq [hidden],
.ks3-cryst [hidden],
.ks3-mchoice [hidden],
.ks3-still [hidden],
.ks3-chroma [hidden],
.ks3-critiq [hidden],
.ks3-mpb [hidden] { display: none; }
.ks3-dlab[hidden] { display: none; }

/* ═══ END C3 ═══ */

/* ═══ BEGIN C4 ═══════════════════════════════════════════════════════════
   C4's instrument styling. A PURE APPEND at EOF — every byte above this
   marker is unchanged, which is the one conflict shape that resolves
   mechanically when another lane merges.
   ═══ */

/* ── from css_01.css ── */
/* ═══ c4-01 `#s-pairs` · change-pairs ═══════════════════════════════════
   Three pair panels, two side cards each, and the pairing is the argument:
   `.ks3-cpair-pairs` is a COLUMN of pairs and `.ks3-cpair-sides` is the row
   inside one — never one six-up grid, because a student has to be able to
   see the two halves of a pair beside each other to see that the clue they
   share settles nothing.

   ⚠️ THREE GROUNDS STACKED, and that is Design's drawing: the block is the
   page (`ks3-block`, cream), the pair is a band, the side is a card, and the
   verdict that arrives on top of a decided side is INK. The verdict panel is
   the only ink in this block, so `--ks3-on-dark` / `--ks3-on-dark-body` live
   there and `--ks3-accent-text` appears nowhere near it.

   ⚠️ Design's grid track is `repeat(auto-fit, minmax(266px, 1fr))`; the
   `min(266px, 100%)` is the 390px translation — at 390px a `ks3-block`'s
   content box is narrower than 266px once the pair panel's own padding is
   taken off, and a grid track wider than its container is a page that
   scrolls sideways.

   ⚠️ AND NOTHING HERE IS A MARK (R3). No option in this instrument takes
   green or red at any size: the committed button keeps the segmented
   control's ordinary pressed treatment, its spent sibling dims to
   `--ks3-dim-spent`, and the verdict is a PANEL OF WORDS. `--ks3-ok` and
   `--ks3-danger` do not appear in this block at all. */
.ks3-cpair-pairs {
  margin-top: 22px;
  display: flex;
  flex-direction: column;
  gap: 20px;
}
.ks3-cpair-pair {
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
}
/* ⚠️ (0,2,0), self-scoped — a bare component class is (0,1,0) and loses to
   `.ks3-dark p` at (0,1,1) the day anything moves this instrument onto ink,
   and it loses silently: the CSS reads correctly and the sentence ships at
   ~1.2:1. The whole C3 block says the same thing for the same reason. */
.ks3-cpair .ks3-cpair-label {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-cpair-sides {
  margin-top: 14px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(266px, 100%), 1fr));
  gap: 16px;
}
.ks3-cpair-side {
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
  display: flex;
  flex-direction: column;
}
.ks3-cpair .ks3-cpair-name {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 20px;
  line-height: 1.3;
}
.ks3-cpair .ks3-cpair-what {
  margin: 8px 0 0;
  font-size: 17px;
  line-height: 1.5;
  color: var(--ks3-ink-body);
}
.ks3-cpair-opts {
  margin-top: 14px;
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
/* The one-shot commit bank. Design dims a disabled button that was NOT the
   one pressed and leaves the pressed one at full strength, so the card still
   says what the student chose.

   ⚖️ `--ks3-dim-spent` (.65), not Design's .45 — MRB-257 audit 3.21 ruled
   that value for exactly this state: "you may not press this any more" and
   "you may not read this" are two different sentences, and .45 says the
   second. Same divergence C3 makes, for the same reason. */
.ks3-cpair-opt[disabled] { cursor: default; }
.ks3-cpair-opt[disabled][aria-pressed="false"] { opacity: var(--ks3-dim-spent); }

.ks3-cpair-reveal { margin-top: 14px; }
.ks3-cpair-tests {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 9px;
}
.ks3-cpair-test {
  padding: 12px 14px;
  border-radius: 14px;
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-rule);
}
.ks3-cpair .ks3-cpair-testname {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-cpair .ks3-cpair-testresult {
  margin: 5px 0 0;
  font-size: 17px;
  line-height: 1.5;
}
/* The verdict — the only ink ground in this block, and it arrives AFTER the
   commitment. It is a statement about the substance, never about whether the
   student was right, so it carries no tick, no cross and no second tone. */
.ks3-cpair-verdict {
  margin-top: 12px;
  padding: 14px 16px;
  border-radius: 14px;
  background: var(--ks3-ink);
  color: var(--ks3-on-dark);
}
.ks3-cpair .ks3-cpair-verdict-head {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 18px;
  line-height: 1.3;
}
.ks3-cpair .ks3-cpair-why {
  margin: 7px 0 0;
  font-size: 16px;
  line-height: 1.55;
  color: var(--ks3-on-dark-body);
}
/* "You have seen it all" — six of six decided. `--ks3-accent` is 3.4:1 and
   appears here on a 6px offset shadow, which is not text. */
.ks3-cpair-close {
  margin-top: 20px;
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
  box-shadow: 6px 6px 0 var(--ks3-accent);
}
.ks3-cpair .ks3-cpair-close p {
  margin: 0;
  font-size: 19px;
  line-height: 1.6;
}

/* ═══ c4-01 `#s-chain` · chain-build ═════════════════════════════════════
   Two clause banks and one panel. Everything in the panel is authored text
   in `--ks3-inset` on ink rule; the sentence the student built sits at 21px
   and the full-marks sentence at 20px/600, which is Design's own hierarchy —
   the model answer is the heavier of the two because it is the thing to copy.

   ⚠️ The two notes are EMIT-BOTH-SHOW-ONE and share one class, one ground
   and one measure. Neither is a verdict on the student and neither takes a
   tone: every clause on offer is true, and what the note is about is whether
   the two halves answer each other.

   ⚠️ NOT `.ks3-chain` — that class already exists in this stylesheet for a
   different component (`.ks3-chain-link` / `.ks3-chain-scale`), so the
   wrapper here is `.ks3-chain-wrap`. A shared class would inherit another
   unit's box silently. */
.ks3-chain-slots {
  margin-top: 20px;
  display: flex;
  flex-direction: column;
  gap: 18px;
}
.ks3-chain-wrap .ks3-chain-slotlabel {
  margin: 0 0 8px;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
/* Design caps the clause list rather than letting a 24-word clause run the
   full width of a 1240px rail — a clause you have to track across the page
   is a clause you cannot compare with the one under it. */
.ks3-chain-slot .ks3-options { margin-top: 0; max-width: 40rem; }
.ks3-chain-reveal {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-chain-wrap .ks3-chain-eyebrow,
.ks3-chain-wrap .ks3-chain-modelhead {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-chain-wrap .ks3-chain-modelhead { margin: 14px 0 0; }
.ks3-chain-wrap .ks3-chain-sentence {
  margin: 10px 0 0;
  font-size: 21px;
  line-height: 1.5;
}
.ks3-chain-wrap .ks3-chain-note {
  margin: 18px 0 0;
  font-size: 19px;
  line-height: 1.65;
}
.ks3-chain-wrap .ks3-chain-model {
  margin: 10px 0 0;
  font-size: 20px;
  line-height: 1.55;
  font-weight: 600;
}

/* ⚠️ NO `display` ON ANYTHING THAT ALSO TAKES `[hidden]`, and this is the
   LAST rule of the c4-01 group so source order settles the (0,2,0) ties in
   its favour. An author `display` beats the UA stylesheet's
   `[hidden] { display: none }` at ANY specificity, and eight things here
   ship hidden: six side reveals, the close panel, the chain panel — and both
   chain notes inside it, which are the emit-both-show-one pair and would
   otherwise both be on screen at once, contradicting each other.

   Nothing in the c4-01 rules above sets `display` on an element that also
   takes `[hidden]` — checked, element by element — so this is insurance
   against the next rule added to this group rather than a fix for one
   already here. Written at (0,2,0) and placed last, the way C3's is. */
.ks3-cpair [hidden],
.ks3-chain-wrap [hidden] { display: none; }

/* ── from css_02.css ── */
/* ═══ c4-02 · atom-rearranger + impossible-ask ═══════════════════════════
   Two DOM instruments, no canvas. Every value below is measured off Design's
   approved page, `docs/ks3/design-reference/c4/c4-02-reactions-rearrange-
   atoms.dc.html`, which carries its real design in inline `style` attributes
   and in the style strings `atomSpan()` and `seg()` compose. Where the page
   and the engine disagreed, the page won.

   ⚠️ BOTH INSTRUMENTS SIT ON A LIGHT `ks3-block ks3-check`. Measured, not
   inferred from the kind names: `#s-rearr` and `#s-impossible` both carry
   `class="ks3-block"` and nothing else. So this whole block is written for
   cream, and the ONLY ink ground in it is the ask bench's verdict panel,
   which Design paints `--ks3-ink` herself. That panel takes `--ks3-on-dark`
   and `--ks3-on-dark-body` and NEVER `--ks3-accent-text`.

   ⚠️ EVERY TEXT RULE IS SELF-SCOPED AT (0,2,0) — `.ks3-arr .ks3-arr-label`,
   never a bare `.ks3-arr-label`. A bare component class is (0,1,0) and loses
   to `.ks3-dark p` at (0,1,1) the day anything moves one of these onto ink,
   and it loses SILENTLY: the CSS reads correctly and the sentence ships at
   about 1.2:1. C1's and C3's blocks say the same thing for the same reason.

   ⚑ AMBER, AND WHY IT IS ON THE GATE. `--ks3-alert-tint` grounds the
   rearranger's commitment panel, which is what Design draws. That is amber
   carrying its ruled meaning: every wrong option in that gate is a wrong
   idea about how many atoms there are ("Four — one for each hydrogen"),
   offered one press before the products take it apart. It is never a
   category and never a selection. `--ks3-data` is an on-ink token and both
   of these instruments live on cream, so there is nothing to route to it
   here; every live value in this block — the atom counts — is
   `--ks3-accent-text` at 6.0:1, which is what Design paints it.

   ⚠️ NOTHING BELOW IS A MARK (R3). Only the mastery ladder marks. No option
   in either instrument takes green or red at any size: a committed gate
   option keeps the platform's ordinary pressed treatment and the verdict is
   a PANEL OF WORDS. `--ks3-ok` does not appear in this block and neither
   does `--ks3-danger`.

   ⚠️ `--ks3-accent` IS 3.4:1 — LARGE TEXT ONLY, and it appears below on
   exactly one thing and it is not text: the 2px border round a product
   particle at stage 2.

   ⊖ AND THERE IS NO C4 REDUCED-MOTION BLOCK, deliberately, on B10's, B11's
   and C3's reasoning: no canvas, no rAF, no timer, and this block declares
   no transition and no animation of its own, so there is nothing here for
   the gate that freezes transitions to freeze. What motion these sections
   have is the shell's arrive treatment.

   ⚠️ NO `display` ON ANYTHING THAT ALSO TAKES `[hidden]`. An author
   `display` beats the UA stylesheet's `[hidden] { display: none }` at ANY
   specificity, and a great deal in these two instruments ships hidden: eight
   of the nine stage views, two of the three word equations, two of the three
   stage headings, four of the five commentary lines, two of the three count
   bodies, all three gates, one of the two advance labels, every "after"
   number, the summary and all four verdict panels. Everything that needs a
   `display` is therefore an element that is NEVER hidden — `.ks3-arr-row`
   inside a view, `.ks3-arr-group` inside a row, `.ks3-arr-atoms` inside a
   group — and the guard is the LAST rule in this block so source order
   settles the (0,2,0) ties in its favour.

   ⊖ AND ONE THING THIS BLOCK DELIBERATELY DOES NOT DO. The dial label / dial
   row pair below is the same three declarations C3 groups across five
   families (`.ks3-dlab-diallabel, .ks3-cryst-diallabel, …`) under Drift 4's
   ruling. It is written out again here rather than added to that selector,
   because C3's block belongs to another lane and a lane that edits another
   lane's file stops being a lane. Reported to the commander: when C4 and C5
   are both in, that grouped selector wants the six new families adding to it
   in one pass. */

/* ── the reaction dial ─────────────────────────────────────────────────
   The BUTTONS are `.ks3-seg-btn` and take nothing from here — Drift 4 ruled
   the platform's segmented control the key stage's only one, and Design's
   own `seg()` (16px / 11px 16px on `--ks3-option-border`) is reported rather
   than re-declared. */
.ks3-arr-dial { margin-top: 20px; }
.ks3-arr .ks3-arr-diallabel {
  margin: 0 0 8px;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-arr-tabs {
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}

/* ── the word equation ─────────────────────────────────────────────────
   "hydrogen + oxygen makes water" — the reaction in words, above the
   picture of it. Three of these are in the document and one is shown. */
.ks3-arr .ks3-arr-words {
  margin: 18px 0 0;
  font-size: 20px;
  line-height: 1.5;
  font-weight: 600;
  color: var(--ks3-ink);
}

/* ── the plate, and the nine views inside it ───────────────────────────
   `min-height` so the box does not jump between a three-particle stage and
   a nine-atom one. */
.ks3-arr-plate {
  margin-top: 18px;
  padding: 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
  min-height: 128px;
}
.ks3-arr .ks3-arr-stagename {
  margin: 0 0 16px;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
/* NEVER hidden — the view around it is what hides. This is where the
   `display` lives, and that is the whole arrangement. */
.ks3-arr-row {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  align-items: flex-start;
}
.ks3-arr-group {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 12px 14px;
  border-radius: 18px;
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
}
/* Stage 2 · the new substances. The accent border is the only use of
   `--ks3-accent` in this block and it is a 2px rule, not text. */
.ks3-arr-group.is-product {
  background: var(--ks3-accent-tint);
  border-color: var(--ks3-accent);
}
/* Stage 1 · the joins are broken. No outline, because an outline round a
   single atom would be a join put back. */
.ks3-arr-group.is-loose {
  padding: 0;
  border: 0;
  background: none;
}
.ks3-arr-atoms {
  display: flex;
  gap: 4px;
  align-items: center;
}
/* One labelled circle. The FILL and the INK on it are inline, from the one
   element table (NOTES §3); everything that is the same for every atom on
   the page is here. */
.ks3-arr .ks3-arr-atom {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border-radius: 99px;
  border: 2px solid var(--ks3-ink);
  font-family: var(--ks3-font-mono);
  font-weight: 500;
  font-size: 15px;
  line-height: 1;
}
.ks3-arr .ks3-arr-atom.is-small {
  width: 30px;
  height: 30px;
  font-size: 13px;
}
.ks3-arr .ks3-arr-label {
  margin: 8px 0 0;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  text-align: center;
  color: var(--ks3-ink-muted);
}

/* ── the two panels under the plate ────────────────────────────────────
   `auto-fit` at a 240px floor, so they sit side by side on a desk and stack
   on a phone without a second breakpoint. */
.ks3-arr-panels {
  margin-top: 18px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 16px;
}
.ks3-arr-panel {
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
}
.ks3-arr .ks3-arr-panelhead {
  margin: 0 0 12px;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}

/* ── the atom-count table ──────────────────────────────────────────────
   Mono, because every reading in the key stage is mono. This is the only
   place on the page where the budget is readable AS A NUMBER (§5A), which
   is why it is a table and not a sentence. */
.ks3-arr-table {
  border-collapse: collapse;
  width: 100%;
  font-family: var(--ks3-font-mono);
  font-size: 17px;
}
.ks3-arr .ks3-arr-table th[scope="col"] {
  text-align: left;
  padding: 7px 8px;
  border-bottom: 2px solid var(--ks3-ink);
  font-weight: 500;
  font-size: 12px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-arr .ks3-arr-el,
.ks3-arr .ks3-arr-before,
.ks3-arr .ks3-arr-after {
  padding: 7px 8px;
  border-bottom: 2px solid var(--ks3-rule);
  text-align: left;
  font-weight: 400;
  color: var(--ks3-ink);
}
/* The two readings of one cell. Which one shows is the stage; neither is
   ever written by JavaScript, so the treatment is a class and not a colour
   assembled at run time. */
.ks3-arr .ks3-arr-pending {
  color: var(--ks3-ink-ghost);
  font-weight: 400;
}
.ks3-arr .ks3-arr-num {
  color: var(--ks3-accent-text);
  font-weight: 700;
}

/* ── the stage commentary ──────────────────────────────────────────────
   Stage 1's is the honest one — "This stage is not real — no chemist ever
   sees it" — and it is the longest of the five, which is why the panel is
   sized off it rather than off stage 0's. */
.ks3-arr .ks3-arr-stagetext {
  margin: 0;
  font-size: 18px;
  line-height: 1.6;
  color: var(--ks3-ink);
}

/* ── the commitment gate ───────────────────────────────────────────────
   Amber, and see the block header for why that is the ruled meaning here
   and not a category. The options are the platform's `.ks3-options` and
   take their pressed treatment from there: no mark, no green, no red. */
.ks3-arr-gate {
  margin-top: 18px;
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-alert-tint);
  border: 2px solid var(--ks3-ink);
}
.ks3-arr .ks3-arr-gate .ks3-commit {
  margin: 0;
  font-size: 19px;
  font-weight: 700;
  color: var(--ks3-ink);
}
.ks3-arr-gate .ks3-options { max-width: 30rem; }

/* ── the two controls ──────────────────────────────────────────────────
   `.ks3-reveal-btn` and `.ks3-retry` are the platform's, and neither takes
   anything from here beyond the row that holds them. */
.ks3-arr-controls {
  margin-top: 18px;
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

/* ── all three reactions taken apart ───────────────────────────────────
   The band ground, which the key stage uses for "you have seen it all". */
.ks3-arr-summary {
  margin-top: 20px;
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
}
.ks3-arr .ks3-arr-summary p {
  margin: 0;
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-ink);
}

/* ── impossible-ask ────────────────────────────────────────────────────
   Four asks and one verdict slot. The panel is the same treatment whether
   the bench built it or refused it, which is right: a verdict says in WORDS
   what happened, and painting a refusal differently would make it a mark. */
.ks3-iask-asks {
  margin-top: 18px;
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
/* ⚠️ THE ONE INK GROUND IN THIS BLOCK. Both rules below are self-scoped at
   (0,2,0) so a generic on-dark type rule cannot win the colour, and neither
   takes `--ks3-accent-text`, which is a cream-ground token. */
.ks3-iask-verdict {
  margin-top: 18px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ink);
  color: var(--ks3-on-dark);
}
.ks3-iask .ks3-iask-title {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 22px;
  line-height: 1.25;
  color: var(--ks3-on-dark);
}
.ks3-iask .ks3-iask-text {
  margin: 12px 0 0;
  font-size: 18px;
  line-height: 1.6;
  color: var(--ks3-on-dark-body);
}

/* ── the hidden guard, LAST ────────────────────────────────────────────
   Everything in these two instruments that ships `hidden` is listed here, so
   that a later rule giving one of them a `display` is undone by source order
   rather than shipping a nine-view plate with all nine views on it.

   Written the way C3's guard is written — (0,2,0), no `!important`, last in
   its own block — so it ties with every component rule above it and wins on
   source order, which is the same mechanism and not a second one. */
.ks3-arr [hidden],
.ks3-iask [hidden] { display: none; }

/* ── from css_03.css ── */
/* ═══ c4-03 · equation-builder (#s-builder) and equation-read (#s-read) ══
   Two families, one page, every value measured off Design's approved page
   `docs/ks3/design-reference/c4/c4-03-word-equations.dc.html` — a React
   prototype carrying its real design in inline `style` attributes and in
   the style strings its component script composes. Where the page and the
   engine disagreed, the page won.

   ⚠️ BOTH SIT ON A LIGHT `ks3-block ks3-check`. Measured, not inferred:
   `#s-builder` and `#s-read` are both `class="ks3-block"` and nothing
   else. There is no ink-dark practical block on this page, so everything
   below is written for cream, and the ONLY ink ground is the check panel
   Design paints `--ks3-ink` herself. It takes `--ks3-on-dark` /
   `--ks3-on-dark-body` and never `--ks3-accent-text`.

   ⚠️ EVERY TEXT RULE IS SELF-SCOPED AT (0,2,0) — `.ks3-eqb
   .ks3-eqb-story`, not a bare `.ks3-eqb-story`. A bare component class is
   (0,1,0) and loses to `.ks3-dark p` at (0,1,1) the day anything moves one
   of these onto ink, and it loses SILENTLY: the CSS reads correctly and
   the sentence ships at ~1.2:1. C1's block and C3's say the same thing for
   the same reason.

   ⚠️ NOTHING BELOW IS A MARK (R3). Only the mastery ladder marks. A placed
   chip takes the accent BORDER Design draws on it and no fill; a committed
   reading keeps the platform's ordinary pressed treatment; its spent
   sibling dims. `--ks3-ok` and `--ks3-danger` do not appear in this block
   at all, and neither does amber: there is no predict gate on this page,
   because the bench IS the elicitation and the check is the confrontation.

   ⊖ NO `--ks3-data` HERE, and no reading to route to it. It is an on-ink
   token and the one ink panel on this page carries sentences, not values.

   ⊖ AND NO REDUCED-MOTION BLOCK, deliberately: no canvas, no rAF, no
   timer, and this block declares no transition and no animation of its
   own. The arrive treatment on the check panel is the shell's.

   ⚠️ NO `display` ON ANYTHING THAT ALSO TAKES `[hidden]` — and a great
   deal here is emitted hidden, because both instruments are built on
   emit-both-show-one: three stories, three benches, three equations, three
   check panels, four verdict branches per panel, six distractor
   corrections, every plus sign, both "nothing yet" ghosts, the check
   button and every reply. An author `display` beats the UA stylesheet's
   `[hidden] { display: none }` at ANY specificity, so the guard is the
   LAST rule in this block and source order settles the (0,2,0) ties in its
   favour. */

/* ── the case tabs ────────────────────────────────────────────────────
   The BUTTONS are `.ks3-seg-btn` and take nothing from here (Drift 4: one
   segmented control for the key stage). Design's own `seg()` is 16px /
   11px 16px on `--ks3-option-border`; the platform's is 17px / 11px 17px
   on `--ks3-rule-strong`, and the platform's is the ruled one. Reported
   rather than re-declared. */
.ks3-eqb-tabs {
  margin-top: 18px;
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}

.ks3-eqb .ks3-eqb-story {
  margin: 18px 0 0;
  font-size: 19px;
  line-height: 1.65;
  max-width: 56ch;
}

/* ── the bench ────────────────────────────────────────────────────────
   `--ks3-inset` on the block's own card: the block is the page, and the
   bench is the tray of things standing on it. This panel carries
   `id="builder-distractor"` — it is where a student can commit to heat
   being a reactant, and it is the elicitation half of REACT-06's join. */
.ks3-eqb-bench {
  margin-top: 20px;
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-eqb .ks3-eqb-benchlabel,
.ks3-eqb .ks3-eqb-eqlabel {
  margin: 0 0 12px;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-eqb .ks3-eqb-eqlabel { margin-bottom: 14px; }
.ks3-eqb-bank {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.ks3-eqb-row {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
.ks3-eqb-rowbtns {
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
/* ⚖️ THE BORDER IS THE WHOLE STATE, AND IT IS NOT A MARK. A chip that has
   been given a place takes the accent border Design draws on it — it says
   "you have put this somewhere", which is true whether the somewhere is
   right or wrong, and it says nothing else. No fill, no tint, no tick. */
.ks3-eqb .ks3-eqb-chip {
  display: inline-block;
  min-width: 11rem;
  max-width: 100%;
  padding: 10px 14px;
  border-radius: var(--ks3-r-control);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-rule);
  font-size: 18px;
  font-weight: 600;
}
.ks3-eqb .ks3-eqb-chip[data-eqb-placed="1"] { border-color: var(--ks3-accent); }

/* ── the equation the student is building ─────────────────────────────
   `min-height: 44px` is Design's and it is load-bearing: the line holds
   its height while both sides read "nothing yet", so placing the first
   chip does not shove the bench up the screen. */
.ks3-eqb-eq {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
}
.ks3-eqb .ks3-eqb-line {
  margin: 0;
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 22px;
  line-height: 1.3;
  min-height: 44px;
}
.ks3-eqb-side {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
.ks3-eqb-term {
  display: flex;
  align-items: center;
  gap: 12px;
}
.ks3-eqb .ks3-eqb-plus { color: var(--ks3-ink-muted); }
.ks3-eqb .ks3-eqb-ghost { color: var(--ks3-ink-ghost); }

/* The drawn arrow, on both grounds. `currentColor` is what lets one path
   serve the cream equation and the ink check panel without a second copy,
   and `flex: 0 0 auto` stops the flex row squeezing it out of shape. */
.ks3-eqb-arrow { flex: 0 0 auto; }

.ks3-eqb-btns {
  margin-top: 18px;
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

/* ── the check ────────────────────────────────────────────────────────
   The one ink ground on the page. It is a PANEL OF WORDS in all four
   branches and it is the same panel whichever branch it lands on — a right
   equation and a substance on the wrong side arrive in the same box, in
   the same colours, and what differs is the sentence. */
.ks3-eqb-check {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ink);
  color: var(--ks3-on-dark);
}
.ks3-eqb .ks3-eqb-checktitle {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 22px;
  line-height: 1.25;
  color: var(--ks3-on-dark);
}
.ks3-eqb .ks3-eqb-checktext {
  margin: 12px 0 0;
  font-size: 18px;
  line-height: 1.6;
  color: var(--ks3-on-dark-body);
}
.ks3-eqb .ks3-eqb-model {
  margin: 16px 0 0;
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 20px;
  color: var(--ks3-on-dark);
}

/* ═══ #s-read · equation-read ═══════════════════════════════════════════
   Three cards, one commitment each. `--ks3-inset` for the card and
   `--ks3-card` for the reply that arrives on top of it — the same two-tone
   stack the sorter uses, and the reply is deliberately NOT ink: it is the
   card answering itself, not a verdict arriving from somewhere else. */
.ks3-eqr {
  margin-top: 18px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.ks3-eqr-card {
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-eqr .ks3-eqr-eq {
  margin: 0;
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 21px;
}
.ks3-eqr-opts {
  margin-top: 14px;
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
.ks3-eqr .ks3-eqr-reply {
  margin: 14px 0 0;
  padding: 16px 18px;
  border-radius: 16px;
  background: var(--ks3-card);
  border: 2px solid var(--ks3-rule);
  font-size: 18px;
  line-height: 1.6;
}
/* ⚖️ `--ks3-dim-spent` (.65), not Design's .45 — MRB-257 audit 3.21 ruled
   that value for exactly this state, and C3 carries the same divergence
   for the same reason: "you may not press this any more" and "you may not
   read this" are two different sentences, and .45 says the second. The
   card still has to say what the student chose. */
.ks3-eqr-opt[disabled] { cursor: default; }
.ks3-eqr-opt[disabled][aria-pressed="false"] { opacity: var(--ks3-dim-spent); }

/* ── narrow ───────────────────────────────────────────────────────────
   A 44px tap target that has been squeezed to fit is not a tap target, so
   the control rows keep their gap and wrap instead. The chip drops its
   11rem floor at the same point: at 390px a `ks3-block`'s content box is
   about 330px, and a 176px chip beside three buttons is a row that wraps
   into a column anyway — full width reads as the label of the row it
   heads rather than as a stranded pill. */
@media (max-width: 480px) {
  .ks3-eqb-tabs,
  .ks3-eqb-rowbtns,
  .ks3-eqr-opts { gap: 10px; }
  .ks3-eqb .ks3-eqb-chip {
    min-width: 0;
    width: 100%;
  }
}

/* ── the `[hidden]` guard, and it is LAST on purpose ───────────────────
   See the block header: an author `display` beats `[hidden]` at any
   specificity, and several rules above set `display` on elements that are
   emitted hidden — every bank, every term, every side, every plus sign and
   the check button among them. */
.ks3-eqb [hidden],
.ks3-eqr [hidden] { display: none; }

/* ── from css_04.css ── */
/* ═══ c4-04 · mass-in-a-reaction ═══════════════════════════════════════════
   Four instruments — `mass-bench`, `mass-cover`, `mass-worked`,
   `mass-check` — and every one of them sits in a LIGHT `ks3-block`. Measured
   off Design's own markup (page lines 105, 169, 230, 259): there is no
   ink-dark practical block anywhere on this page, and none anywhere in C4 or
   C5. A later pass that "restores" a practical shell here would be painting an
   instrument on a ground Design did not draw it on and resolving every text
   token inside it wrong.

   ⚠️ EVERY TEXT RULE IS SELF-SCOPED AT (0,2,0) — `.ks3-mwork .ks3-mwork-note`,
   never a bare `.ks3-mwork-note`. A bare component class is (0,1,0) and loses
   to `.ks3-dark p` at (0,1,1) the day anything moves one of these onto ink,
   and it loses SILENTLY: the CSS reads correctly and the sentence ships at
   about 1.2:1. C1's and C3's blocks say the same thing for the same reason and
   two builds have been bitten by it.

   ⚑ AMBER, ON ONE PANEL, CARRYING ITS RULED MEANING. `--ks3-alert-tint`
   grounds the bench's prediction gate, which is exactly where c3-03, c3-04 and
   c3-05 spend it: a gate that exists to ELICIT a named misconception a few
   sections before the page confronts it. Here that is `REACT-07` — a student
   who believes a gas has no mass predicts "go down" for the sealed flask too,
   and `#s-think` picks that up by name. It is never a selection and never a
   reading.

   ⊕ AND `--ks3-data` IS SPENT ONCE, ON THE COVER PLATE'S "?", WHICH IS A
   CORRECTION TO DESIGN. She paints it `--ks3-alert`. MRB-252 reserved amber
   for WARNING AND CONFRONTATION — the `#s-think` register, genuine caution and
   loss — and moved CATEGORY AND SELECTION to `--ks3-data`. A plate saying
   *this is the cell you covered, this is the unknown you are solving for* is
   selection: it is neither a caution nor a loss, and spending amber on it
   wears the meaning away one panel at a time. `--ks3-data` is an ON-INK token
   and the plate's ground is `--ks3-ink` at .9 — measured, the glyph lands at
   6.4:1 there, and it is 26px display type. This is the first light-ground
   unit that has had an ink surface to spend it on; C3 correctly spent none.

   ⚠️ EVERY LIVE VALUE ON A CREAM GROUND IS `--ks3-accent-text` (6.0:1), never
   `--ks3-accent` (3.4:1) and never `--ks3-data` (an on-ink token, unusable
   here). `--ks3-accent` appears in this block on exactly two things, neither
   of them text: the 6px offset shadow on the bench's closing summary, and the
   fill behind an OPENED step's `aria-hidden` F/I/F/A badge.

   ⚠️ NOTHING BELOW IS A MARK (R3, MRB-196 R10). Only the mastery ladder marks
   correctness. `--ks3-ok` and `--ks3-danger` do not appear in this block at
   all: a chosen control keeps the platform's ordinary pressed treatment and
   every verdict is a PANEL OF WORDS.

   ⊖ NO REDUCED-MOTION BLOCK, DELIBERATELY. No canvas, no rAF, no timer, and
   this block declares no transition and no animation of its own, so there is
   nothing here for `prefers-reduced-motion` to degrade. What motion the page
   has is the shell's arrive treatment, which `shared/ks3.css` degrades inside
   its own media query.

   ⚠️ NO `display` ON ANYTHING THAT ALSO TAKES `[hidden]` — except through the
   guard at the very bottom of this block. An author `display` beats the UA
   stylesheet's `[hidden] { display: none }` at ANY specificity, and fourteen
   things on this page are emitted hidden: every run panel, the Run button, the
   two closing summaries, all three cover plates, all three cover results, the
   result wrapper, all eight step bodies and three of the four compare buttons.
   The guard is LAST so source order settles it at equal specificity. */


/* ═══ #s-bench · mass-bench ═══════════════════════════════════════════════
   Two dials, a prediction gate, a three-tile readout and a verdict. Design's
   own measurements throughout; the segmented buttons themselves are
   `.ks3-seg-btn`, the system's ONE segmented control (the Drift-4 ruling), and
   nothing here restyles it. */

.ks3-bbench .ks3-bbench-dials {
  margin-top: 20px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.ks3-bbench .ks3-bbench-diallabel {
  margin: 0 0 8px;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-bbench .ks3-bbench-dialrow {
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}

/* The gate. Amber, and see the block header for why that is the ruled meaning
   and not a drift into category. */
.ks3-bbench .ks3-bbench-gate {
  margin-top: 20px;
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-alert-tint);
  border: 2px solid var(--ks3-ink);
}
/* ⚠️ `.ks3-commit` is 22px in `--ks3-alert`, which is right on the ink grounds
   where nearly every gate in the key stage sits and about 1.1:1 here. Design
   draws this prompt as plain 19px/700 ink inside the amber panel — 13:1 — so
   that is what it gets, at (0,2,0), which beats `.ks3-commit` at (0,1,0).
   Scoped to this gate so the shared component stays shared. The three C3
   gates are the standing precedent. */
.ks3-bbench .ks3-commit {
  margin: 0;
  font-size: 19px;
  font-weight: 700;
  line-height: 1.45;
  color: var(--ks3-ink);
}
/* Design caps the lettered list rather than letting three options run the full
   width of the rail. */
.ks3-bbench .ks3-options { max-width: 30rem; }
.ks3-bbench .ks3-bbench-run { margin-top: 14px; }

/* The readout. `auto-fit` at 210px so the three tiles reflow to one column on
   a phone rather than squeezing a mono 26px reading into 90px. */
.ks3-bbench .ks3-bbench-tiles {
  margin-top: 20px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(210px, 100%), 1fr));
  gap: 14px;
}
.ks3-bbench .ks3-bbench-tile {
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
/* ⚠️ THE DASHED BORDER IS THE THIRD TILE'S WHOLE POINT, AND IT IS THE SAME ON
   ALL FOUR RUNS. On the open runs it holds "not measured — you work it out";
   on the sealed runs it holds a real `0.00 g`. Both are the same slot asking
   the same question, so the treatment must not fork on which one is showing —
   a solid border on the sealed runs would read as "this one is a measurement
   and that one is not", which is backwards. */
.ks3-bbench .ks3-bbench-tile.is-gas {
  background: var(--ks3-band);
  border: 2px dashed var(--ks3-ink);
}
.ks3-bbench .ks3-bbench-tilelabel {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-bbench .ks3-bbench-value {
  margin: 8px 0 0;
  font-family: var(--ks3-font-mono);
  font-weight: 500;
  font-size: 26px;
  line-height: 1.15;
  color: var(--ks3-accent-text);
}
/* The unmeasured reading is quieter and smaller, because it is a sentence
   rather than a number and 26px mono prose is a wall. */
.ks3-bbench .ks3-bbench-value.is-soft {
  font-size: 18px;
  line-height: 1.35;
  color: var(--ks3-ink-muted);
}
.ks3-bbench .ks3-bbench-tilenote {
  margin: 8px 0 0;
  font-size: 16px;
  line-height: 1.45;
  color: var(--ks3-ink-body);
}
.ks3-bbench .ks3-bbench-note {
  margin-top: 18px;
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
}
.ks3-bbench .ks3-bbench-note p,
.ks3-bbench .ks3-bbench-close p {
  margin: 0;
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-ink);
}
/* The "you have run all four" summary. `--ks3-accent` as a 6px offset shadow
   is not text and is legal at any size. */
.ks3-bbench .ks3-bbench-close {
  margin-top: 16px;
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
  box-shadow: 6px 6px 0 var(--ks3-accent);
}


/* ═══ #s-cover · mass-cover ═══════════════════════════════════════════════
   MRB-204 part 2 — the rule DRAWN, in the shape the relationship has.

   ⚖️ A PART–WHOLE BAR, NEVER A TRIANGLE. Conservation of mass is a SUM. A
   triangle encodes `A = B × C` and would tell a student two masses multiply
   together to give a third, which is not a relationship that exists. Ruled at
   MRB-246; `c2-06` is the same ruling one unit earlier. There is no
   `.ks3-tri-*` anywhere in this block and there must never be one. */

.ks3-mcov .ks3-mcov-rule {
  padding: 20px 24px;
  border-radius: var(--ks3-r-card);
  background: var(--ks3-card);
  border: 3px solid var(--ks3-ink);
}
.ks3-mcov .ks3-mcov-rule p {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: clamp(24px, 3.2vw, 36px);
  line-height: 1.14;
  letter-spacing: -.03em;
  text-align: center;
  color: var(--ks3-ink);
}

.ks3-mcov .ks3-mcov-bar {
  margin-top: 20px;
  padding: 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
/* ⚠️ THE TWO PART CELLS TAKE THEIR WIDTHS FROM `flex-grow`, WRITTEN BY THE
   RENDERER FROM THE PAYLOAD'S WEIGHTS. The ratio is authored in one place and
   drawn from that one place: a row with the proportions hard-coded here would
   be free to drift from the labels above it, and a bar whose parts disagree
   with its own numbers is a bar model that lies.

   `min-width: 0` because a flex item's default `min-width: auto` refuses to
   shrink below its content, and the gas cell's label is longer than its share
   of a 390px screen. Without it the 68/32 silently becomes about 55/45 on a
   phone — the one place the proportions would be worst wrong. */
.ks3-mcov .ks3-mcov-parts {
  margin-top: 10px;
  display: flex;
  gap: 10px;
}
.ks3-mcov .ks3-mcov-parts > .ks3-mcov-cell { min-width: 0; }
.ks3-mcov .ks3-mcov-cell {
  position: relative;
  padding: 18px 20px;
  border-radius: 14px;
  background: var(--ks3-blue-tint);
  border: 2px solid var(--ks3-ink);
}
.ks3-mcov .ks3-mcov-celllabel {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-mcov .ks3-mcov-cellvalue {
  margin: 6px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 24px;
  line-height: 1.2;
  color: var(--ks3-ink);
}
/* The plate. `--ks3-data`, not `--ks3-alert` — see the block header. It is
   `aria-hidden`, so it is reinforcement for a sighted reader and never the
   only route to which cell is covered; the cell's own label stays underneath
   it and the bar's `aria-label` names all three. */
.ks3-mcov .ks3-mcov-plate {
  position: absolute;
  inset: 0;
  border-radius: 12px;
  background: var(--ks3-ink);
  opacity: .9;
  display: grid;
  place-items: center;
  color: var(--ks3-data);
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 26px;
}
/* ⚠️ NEW ELEMENT, AND IT IS A SCIENCE-HONESTY LINE RATHER THAN A CAPTION.
   The cells are Design's readable 68/32 and 2.20 g is really about one part in
   seventy of 152.00 g. §5A asks drawn geometry to express the ratio its label
   claims; drawn true the gas cell would be a six-pixel sliver holding a 24px
   number, so the resolution is to say so — quietly, and with the real
   proportion in it, so the disclaimer teaches something. */
.ks3-mcov .ks3-mcov-scale {
  margin: 12px 0 0;
  font-size: 16px;
  line-height: 1.5;
  color: var(--ks3-ink-muted);
}
.ks3-mcov .ks3-mcov-btns {
  margin-top: 18px;
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
.ks3-mcov .ks3-mcov-out {
  margin-top: 18px;
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
}
.ks3-mcov .ks3-mcov-result {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: clamp(24px, 3.2vw, 30px);
  line-height: 1.2;
  letter-spacing: -.02em;
  color: var(--ks3-ink);
}
/* The sentence that NAMES THE OPERATION. Design authored one per cover and her
   markup drops all three; NOTES-C4 §4 asks for them, so they render. */
.ks3-mcov .ks3-mcov-sentence {
  margin: 10px 0 0;
  font-size: 18px;
  line-height: 1.55;
  color: var(--ks3-ink-body);
}
.ks3-mcov .ks3-mcov-close {
  margin: 18px 0 0;
  font-size: 18px;
  line-height: 1.5;
  color: var(--ks3-ink-body);
}

/* The three unit rows — the unit belongs to the quantity, said three times
   before the worked example's Answer step says it once more. */
.ks3-mcov .ks3-mcov-units {
  margin-top: 20px;
  display: flex;
  flex-direction: column;
  gap: 9px;
}
.ks3-mcov .ks3-mcov-unitrow {
  display: flex;
  align-items: center;
  gap: 14px;
}
.ks3-mcov .ks3-mcov-quantity {
  flex: 1 1 auto;
  font-size: 19px;
  font-weight: 600;
  line-height: 1.35;
  color: var(--ks3-ink);
}
/* `--ks3-ground` on `--ks3-accent-text` is 6.0:1 the other way up — the token
   is measured as a contrast partner for the ground in both directions. */
.ks3-mcov .ks3-mcov-unit {
  flex: 0 0 auto;
  min-width: 4.6rem;
  padding: 5px 16px;
  border-radius: 99px;
  background: var(--ks3-accent-text);
  color: var(--ks3-ground);
  font-family: var(--ks3-font-mono);
  font-size: 18px;
  font-weight: 500;
  white-space: nowrap;
  text-align: center;
}


/* ═══ #s-worked and #s-check · the same four steps, twice ═════════════════
   MRB-204 parts 3 and 4. ONE SET OF RULES FOR BOTH, because the thing that
   must not drift between them is precisely that they ARE the same four steps:
   a student who meets a different badge, a different box or a different rhythm
   on the second one has been shown a second method, which is the opposite of
   what the scaffolding is for. The two differ in what OPENS a step and in
   nothing else.

   The step's state lives in `data-open` and `data-next` on the `<li>`, so
   "an open step" and "the step you are standing on" each have exactly one
   definition instead of one per language. */

.ks3-mwork .ks3-mwork-steps,
.ks3-mchk .ks3-mchk-steps {
  list-style: none;
  margin: 20px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.ks3-mwork .ks3-mwork-step,
.ks3-mchk .ks3-mchk-step {
  display: flex;
  gap: 14px;
  align-items: flex-start;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-rule);
}
/* An opened step, and the step the stepper is standing on. NEITHER IS A MARK:
   an opened step is opened, not correct, and the student has not answered
   anything on `#s-worked` at all. */
.ks3-mwork .ks3-mwork-step[data-open="1"],
.ks3-mchk .ks3-mchk-step[data-open="1"] { background: var(--ks3-inset); }
.ks3-mwork .ks3-mwork-step[data-next="1"],
.ks3-mchk .ks3-mchk-step[data-next="1"] { border-color: var(--ks3-accent); }

.ks3-mwork .ks3-mwork-body,
.ks3-mchk .ks3-mchk-body { flex: 1 1 auto; min-width: 0; }

/* The F / I / F / A badge. `aria-hidden`, so it is decoration reinforcing a
   name that is real text beside it — which is why `--ks3-accent` as its fill
   is legal here and would not be as a text colour at 17px. */
.ks3-mwork .ks3-mwork-badge,
.ks3-mchk .ks3-mchk-badge {
  flex: 0 0 34px;
  height: 34px;
  border-radius: 11px;
  display: grid;
  place-items: center;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 17px;
  background: var(--ks3-band);
  color: var(--ks3-ink-muted);
  border: 2px solid var(--ks3-ink);
}
.ks3-mwork .ks3-mwork-step[data-open="1"] .ks3-mwork-badge,
.ks3-mchk .ks3-mchk-step[data-open="1"] .ks3-mchk-badge {
  background: var(--ks3-accent);
  color: var(--ks3-on-dark);
}

.ks3-mwork .ks3-mwork-name,
.ks3-mchk .ks3-mchk-name {
  margin: 0;
  font-size: 19px;
  font-weight: 700;
  color: var(--ks3-ink);
}
/* `#s-check` only: the instruction is on the page from the start and the
   answer is not. That is the difference between scaffolding and a solution. */
.ks3-mchk .ks3-mchk-prompt {
  margin: 6px 0 0;
  font-size: 18px;
  line-height: 1.55;
  color: var(--ks3-ink-body);
}
/* Mono for the arithmetic, `--ks3-accent-text` at 6.0:1 — never
   `--ks3-accent`, which is 3.4:1 and illegal under 24px. */
.ks3-mwork .ks3-mwork-maths,
.ks3-mchk .ks3-mchk-maths {
  margin: 8px 0 0;
  font-family: var(--ks3-font-mono);
  font-size: 19px;
  line-height: 1.5;
  color: var(--ks3-accent-text);
  overflow-wrap: anywhere;
}
.ks3-mchk .ks3-mchk-maths { margin-top: 10px; }
.ks3-mwork .ks3-mwork-note,
.ks3-mchk .ks3-mchk-note {
  margin: 8px 0 0;
  font-size: 18px;
  line-height: 1.6;
  color: var(--ks3-ink-body);
}

.ks3-mwork .ks3-mwork-next { margin-top: 16px; }
.ks3-mchk .ks3-mchk-btn { margin-top: 12px; }

.ks3-mchk .ks3-mchk-close {
  margin-top: 18px;
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
}
.ks3-mchk .ks3-mchk-close p {
  margin: 0;
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-ink);
}


/* ── the guard, and it is LAST on purpose ────────────────────────────────
   An author `display` beats the UA stylesheet's `[hidden] { display: none }`
   at ANY specificity, and fourteen things on this page are emitted hidden. The
   rules above give `display` to the step rows, the tile grid, the parts row,
   the cover plate and the badges, several of which are inside — or are —
   hidden nodes. These four are (0,2,0), the same as the component rules they
   have to beat, so they win on source order and must stay at the bottom of the
   block. `setHidden()` also writes an inline `display:none`, but only once JS
   has run: this is what holds between first paint and then, which is the
   window a slow phone makes long. */
.ks3-bbench [hidden],
.ks3-mcov [hidden],
.ks3-mwork [hidden],
.ks3-mchk [hidden] { display: none; }

/* ── from css_05.css ── */
/* ═══ c4-05 · coefficient-balancer + forbidden-move ══════════════════════
   Fragment for `shared/ks3.css`. Every value below is measured off Design's
   approved page `docs/ks3/design-reference/c4/c4-05-symbol-equations-and-
   balancing.dc.html`, which carries its real design in inline `style`
   attributes and in the style strings `lessonVals()` composes. Where the page
   and the engine disagreed, the page won — with the two exceptions written
   out below, both of which are rulings rather than preferences.

   ⚠️ BOTH SECTIONS ARE LIGHT. `#s-balance` is a plain `ks3-block ks3-check`
   and `#s-forbidden` is `ks3-block ks3-misconception`; there is no ink-dark
   practical block anywhere in C4 or C5, measured rather than assumed. So this
   whole block is written for cream and there is no `.ks3-dark` branch in it.
   A future pass that moves either instrument onto ink has to write one.

   ⚠️ AND EVERY TEXT RULE IS SELF-SCOPED AT (0,2,0) — `.ks3-cbal
   .ks3-cbal-words`, never a bare `.ks3-cbal-words`. A bare component class is
   (0,1,0) and loses to `.ks3-dark p` at (0,1,1) the day anything moves one of
   these onto ink, and it loses SILENTLY: the CSS reads correctly and the
   sentence ships at about 1.2:1. C1's and C3's blocks say the same thing for
   the same reason; two builds have already been bitten by it.

   ══ RULING 1 — A MATCHED COUNTER IS BLUE, NOT GREEN ══════════════════════

   ⚖️ Design paints a counter whose two sides agree with `--ks3-ok-tint` on a
   `--ks3-ok` border. It is reproduced here as `--ks3-blue-tint` on
   `--ks3-blue`, and this is the one place in the unit where the page did not
   win. Three reasons, and the third is the one that decides it:

     1. §11 / R3 reserve green for the mastery ladder. `--ks3-ok` is marks and
        fills; nothing green reaches an instrument. C3's whole block ships
        without a single `--ks3-ok` for this reason.
     2. C2's `mass-ledger` already ruled the identical case — a readout with
        short / matched / over states — and refused green in as many words:
        *"green is the ladder's colour for a correct answer, and a plate is
        not an answer."* This is the same readout one lesson later.
     3. ⚖️ **ON THIS PAGE IT WOULD TEACH THE MISCONCEPTION THE PAGE EXISTS TO
        CONFRONT.** `REACT-09` is "a balanced equation is a correct equation".
        `#s-think` is forty lines below these tiles and takes it apart:
        H₂ + O₂ → H₂O₂ is perfectly balanced and completely false. A tile that
        goes GREEN — the platform's own colour for "you got it right" — the
        instant the counts agree is that misconception rendered in CSS, and it
        would be teaching it in the same scroll that refutes it.

   `--ks3-blue-tint` / `--ks3-blue` is an existing light-ground pair (it is
   what `.ks3-lesson-row.is-ref` uses) and ink reads on it. `--ks3-data` was
   the other candidate and is wrong here: it is `#8FB7FF`, an ON-INK token,
   and these tiles are on cream — the same finding C3's block records.

   ⚠️ R2 — AND THE COLOUR IS NEVER THE ONLY SIGNAL. Every tile carries one of
   three sentences underneath it ("Matched." / "Too few on the right." / "Too
   few on the left."), which is the state, in words, whatever the palette
   does. All three are reachable on load: the opening methane state reads C
   matched, H short on the right and O short on the left.

   ══ RULING 2 — THE STEPPER IS 34px, AND WHY THAT IS SAID OUT LOUD ════════

   Design draws the `+` / `−` chips at 34 × 34 with a 6px gap, which is below
   the system's 44px `--ks3-tap` and above WCAG 2.5.8's 24px floor. Kept as
   drawn (MRB-205), because a 44px chip in front of a 26px formula is a
   control that outweighs the equation it is attached to — and because the
   gap between the pair is what a mis-tap actually turns on. The gap is
   widened from Design's 6px to 8px, which is the whole of the change and
   costs nothing. Flagged to the commander rather than adjusted silently.

   ⚠️ AND THE BOUND IS `aria-disabled`, NOT `disabled`. See `js_05.js`: a real
   `disabled` attribute drops keyboard focus the moment a student steps up to
   the cap. The dim below is attached to the ARIA state so that the drawn
   state and the announced state cannot come apart.

   ══ NOTHING BELOW IS A MARK ══════════════════════════════════════════════

   ⚖️ Only the mastery ladder marks correctness. No control here takes green
   or red at any size, no option carries `data-correct`, and every verdict is
   a PANEL OF WORDS. `--ks3-ok` and `--ks3-danger` do not appear in this block
   at all. If a rule below ever resolves to `--ks3-ok` #12A150 or
   `--ks3-danger` #FF6B6B, this instrument has started marking.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── the subscript, which is the unit's convention made visible ────────── */

/* ⚖️ A REAL `<sub>` ELEMENT, PINNED. `ks3.css` has no `sub` rule of its own
   and the UA default is `font-size: smaller` + `vertical-align: sub`, which
   is a different size in every engine and drifts badly inside a 26px
   Bricolage-700 formula. Pinned here so the 2 in H₂O sits the same way on
   every machine — because on THIS page the difference between a small number
   and a big one is the entire lesson, and a subscript that is only slightly
   smaller than the coefficient beside it is a lesson that does not land.
   `line-height: 0` keeps the raised baseline from opening the line box. */
.ks3-cbal .ks3-cbal-sym sub,
.ks3-forbid .ks3-cbal-sym sub {
  font-size: .64em;
  line-height: 0;
  vertical-align: -.22em;
  font-weight: inherit;
}

/* ── #s-balance · the coefficient balancer ─────────────────────────────── */

.ks3-cbal-tabs {
  margin: 18px 0 0;
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
/* The solved dot. Design appends " ·" to a tab whose equation has been driven
   to its target; the clipped word beside it is what a screen reader gets,
   because a bare middle dot is read as punctuation or skipped. */
.ks3-cbal .ks3-cbal-tick { font-weight: 700; }

.ks3-cbal .ks3-cbal-words {
  margin: 18px 0 0;
  font-size: 19px;
  line-height: 1.6;
  max-width: 54ch;
  color: var(--ks3-ink);
}

.ks3-cbal-panel {
  margin: 20px 0 0;
  padding: 24px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
  display: flex;
  align-items: flex-start;
  gap: 14px;
  flex-wrap: wrap;
}
.ks3-cbal-side {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  flex-wrap: wrap;
}
.ks3-cbal-term {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}
.ks3-cbal .ks3-cbal-formula {
  margin: 0;
  display: flex;
  align-items: baseline;
  gap: 3px;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 26px;
  line-height: 1.2;
  color: var(--ks3-ink);
}
/* THE COEFFICIENT — a LIVE VALUE, and the one number the student may move.
   Mono, because every live readout in the key stage is mono, and lit in
   accent-text once it leaves 1. R1: 24px is not above the 24px threshold, so
   it is `--ks3-accent-text` (6.0:1) and never `--ks3-accent`. It is not a
   mark and it never goes green: it says "you changed this", not "this is
   right". */
.ks3-cbal .ks3-cbal-coeff,
.ks3-forbid .ks3-cbal-coeff {
  font-family: var(--ks3-font-mono);
  font-weight: 500;
  font-size: 24px;
  color: var(--ks3-ink-ghost);
}
.ks3-cbal .ks3-cbal-coeff[data-cbal-lit="1"],
.ks3-forbid .ks3-cbal-coeff[data-cbal-lit="1"] {
  color: var(--ks3-accent-text);
}
.ks3-cbal .ks3-cbal-plus,
.ks3-forbid .ks3-cbal-plus {
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 26px;
  line-height: 1.6;
  color: var(--ks3-ink-muted);
}
.ks3-cbal-arrow,
.ks3-forbid .ks3-cbal-arrow {
  flex: 0 0 auto;
  align-self: center;
  color: var(--ks3-ink);
}

/* The stepper. See RULING 2 above for the 34px and for `aria-disabled`. */
.ks3-cbal-steps {
  display: flex;
  gap: 8px;
}
.ks3-cbal .ks3-cbal-step {
  cursor: pointer;
  font: inherit;
  font-family: var(--ks3-font-mono);
  font-size: 16px;
  font-weight: 500;
  width: 34px;
  height: 34px;
  border-radius: 10px;
  border: 2px solid var(--ks3-ink);
  background: var(--ks3-card);
  color: var(--ks3-ink);
  display: grid;
  place-items: center;
  line-height: 1;
}
.ks3-cbal .ks3-cbal-step[aria-disabled="true"] {
  cursor: default;
  opacity: .38;
}

/* ── the counters ─────────────────────────────────────────────────────── */

.ks3-cbal-counters {
  margin: 18px 0 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 14px;
}
.ks3-cbal-counter {
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-rule-strong);
}
/* RULING 1 — blue, not green. The reason is at the top of this block and it
   is the lesson's own argument, not a palette preference. */
.ks3-cbal-counter[data-cbal-match="1"] {
  background: var(--ks3-blue-tint);
  border-color: var(--ks3-blue);
}
.ks3-cbal .ks3-cbal-el {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
/* THE READING. Mono at 24px because it is an instrument readout, which is the
   same reason every live value in the key stage is mono. The two numbers live
   in their own spans and nothing else is ever written into them. */
.ks3-cbal .ks3-cbal-line {
  margin: 8px 0 0;
  font-family: var(--ks3-font-mono);
  font-weight: 500;
  font-size: 24px;
  line-height: 1.15;
  color: var(--ks3-ink);
}
.ks3-cbal .ks3-cbal-state {
  margin: 6px 0 0;
  font-size: 16px;
  color: var(--ks3-ink-body);
}
/* The matched tile's own word takes the blue text token, which is the AA half
   of the pair (R1). The two short states stay body ink: they are not a
   warning and the student is not the error. */
.ks3-cbal-counter[data-cbal-match="1"] .ks3-cbal-state {
  color: var(--ks3-blue-text);
}

/* ── the "Balanced." panel, the reset and the summary ─────────────────── */

.ks3-cbal-balanced {
  margin: 18px 0 0;
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
}
.ks3-cbal .ks3-cbal-balanced-h {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 22px;
  line-height: 1.25;
  color: var(--ks3-ink);
}
.ks3-cbal .ks3-cbal-note {
  margin: 10px 0 0;
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-ink);
}
/* The derived multiple. Mono like every other live number on the page, so a
   student reads it as a reading off the instrument rather than as a word. */
.ks3-cbal .ks3-cbal-note [data-cbal-k] {
  font-family: var(--ks3-font-mono);
  font-weight: 500;
}
.ks3-cbal-foot {
  margin: 16px 0 0;
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}
.ks3-cbal-summary {
  margin: 18px 0 0;
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
  box-shadow: 6px 6px 0 var(--ks3-accent);
}
.ks3-cbal .ks3-cbal-summary p {
  margin: 0;
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-ink);
}

/* ── #s-forbidden · the move that looks like it works ─────────────────── */

/* ⚠️ THE BUTTONS ARE ORDINARY. No warning border, no amber, no red, nothing
   that marks one of the two as the wrong one before it is pressed — the
   whole mechanism is that the student is ALLOWED to make the move and is then
   shown what they wrote. Both take `.ks3-seg-btn`, the system's one segmented
   control, and the amber on this section is the SHELL's `.ks3-misconception`
   eyebrow and badge, which is amber carrying its ruled meaning: a wrong idea
   being confronted. */
.ks3-forbid .ks3-mis-body { max-width: 56ch; }
.ks3-forbid-btns {
  margin: 16px 0 0;
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
.ks3-forbid-reveal {
  margin: 18px 0 0;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
}
/* R15's focus ring reaches this because `focusReveal` gives the panel a
   `tabindex="-1"` and the stylesheet's one focus treatment is unscoped. */
.ks3-forbid .ks3-forbid-eq {
  margin: 0;
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 24px;
  line-height: 1.2;
  color: var(--ks3-ink);
}
.ks3-forbid-product {
  display: inline-flex;
  align-items: baseline;
  gap: 3px;
}
/* ⚠️ AN AUTHOR `display` BEATS THE UA'S `[hidden] { display: none }`
   REGARDLESS OF SPECIFICITY, so declaring one on a node that ships hidden is
   how EMIT-BOTH-SHOW-ONE turns into show-both. Both products are in the
   document and both are `hidden` at rest; without this rule the resting page
   would print `H₂ + O₂ → H₂O₂ 2H₂O` and give away the section before the
   student has pressed anything. `setHidden` backs the attribute up with an
   inline display at runtime — this is the half that has to be right in the
   SHIPPED BYTES, which is where a crawler and a no-JS reader stop. It is the
   same trap `.ks3-card-back[hidden]` documents elsewhere in this file, and it
   is the only node in this fragment that needs it: everything else that ships
   hidden declares no display at all. */
.ks3-forbid-product[hidden] { display: none; }
.ks3-forbid .ks3-forbid-text {
  margin: 14px 0 0;
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-ink);
  max-width: 60ch;
}

/* ── narrow readers ───────────────────────────────────────────────────── */
/* The equation panel already wraps (`flex-wrap`), so a four-term equation
   folds onto two rows on a phone rather than scrolling sideways. The counter
   grid collapses to one column at 200px on its own. Nothing here needs a
   media query, and one that only restated the flex behaviour would be a rule
   that can drift away from it. */

/* ═══ END C4 ═══ */

/* ═══ BEGIN C5 ═══════════════════════════════════════════════════════════
   C5's instrument styling. A PURE APPEND at EOF — every byte above this
   marker is unchanged, which is the one conflict shape that resolves
   mechanically when another lane merges.
   ═══ */

/* ── from css_01.css ── */
/* ═══ c5-01 · burner-bench + fuel-cards ════════════════════════════════════
   Fragment for `shared/ks3.css`. Every value below is measured off Design's
   approved page `docs/ks3/design-reference/c5/c5-01-combustion.dc.html`, which
   carries its real design in inline `style` attributes and in the style
   strings `lessonVals()` composes. Where the page and the engine disagreed,
   the page won, except where a disagreement is written out below as a ruling.

   BOTH SECTIONS ARE LIGHT. `#s-burner` (page line 106) and `#s-fuels` (page
   line 178) are each `class="ks3-block"` with no second class. There is no
   ink-dark practical block anywhere in C5, exactly as there is none in C3 or
   C4. A later pass that "restores" a practical shell here would paint an
   instrument on a ground Design did not draw it on and resolve every text
   token inside it wrong.

   ⚠️ EVERY TEXT RULE IS SELF-SCOPED AT (0,2,0) — `.ks3-burner
   .ks3-burner-tilenote`, never a bare `.ks3-burner-tilenote`. A bare component
   class is (0,1,0) and loses to `.ks3-dark p` at (0,1,1) the day anything
   moves one of these instruments onto ink, and it loses SILENTLY: the CSS
   reads correctly and the sentence ships at about 1.2:1. C1's, C3's and C4's
   blocks all say the same thing for the same reason and two builds have been
   bitten by it.

   ⚑ AMBER, ON TWO THINGS, AND BOTH CARRY A MEANING RATHER THAN A CATEGORY.

     1. THE PREDICTION GATE. `--ks3-alert-tint` grounds it exactly as it
        grounds c3-03's, c3-04's, c3-05's and c4-04's: a gate that exists to
        ELICIT a named misconception a section before the page confronts it.
        Here that is `REACT-11` — shutting the air off makes a flame burn
        hotter — and both of the gate's distractors are that belief, from its
        two sides.

     2. THE "In the air around it" CELL, ON THE RUNS THAT MAKE CARBON
        MONOXIDE. This is a HAZARD READING, and it is enforced as one: the
        renderer raises unless every amber cell sits on a run whose products
        name carbon monoxide and every run that names it carries one. So the
        tint tracks the poison, not the dial — hydrogen with the hole shut is
        an incomplete run and stays plain, because nothing in its products can
        hurt anyone. That is the same spend as `.ks3-readout-where[data-where=
        "absent"]` and `.ks3-fit-badge[data-state="fails"]`, both live. It is
        never a selection, never a mark, and never a category hue.

   ⚠️ EVERY LIVE VALUE ON A CREAM GROUND IS `--ks3-ink` HERE, AND THAT IS
   DESIGN'S OWN CALL RATHER THAN AN OMISSION. `.ks3-burner-value` is a SENTENCE
   that changes with the dials — "Blue, roaring, around 1500 °C", "Black
   deposit on the beaker within seconds" — not a number in a slot, so she sets
   it in 21px display ink and not in the accent. `--ks3-accent-text` is for the
   figures a bench reports (c4-04's balance readings); a paragraph of it at
   21px is a wall. `--ks3-data` is `#8FB7FF`, an ON-INK token, and every ground
   in this lesson except the fuel cards' reveal is cream, so there is nothing
   here to spend it on — which is the same reading C3 made across nine
   instruments. `--ks3-accent` appears in this block on exactly one thing and
   it is not text: the 6px offset shadow on the bench's closing summary.

   ⚠️ NOTHING BELOW IS A MARK (R3, MRB-196 R10). Only the mastery ladder marks
   correctness. `--ks3-ok` and `--ks3-danger` do not appear in this block at
   all: a chosen dial and a chosen card option keep the platform's ordinary
   pressed treatment, a spent sibling dims to `--ks3-dim-spent`, and every
   verdict is a PANEL OF WORDS.

   ⊖ NO REDUCED-MOTION BLOCK, DELIBERATELY. No canvas, no rAF, no timer, and
   this block declares no transition and no animation of its own, so there is
   nothing here for `prefers-reduced-motion` to degrade. What motion the page
   has is the shell's arrive treatment, which `shared/ks3.css` degrades inside
   its own media query.

   ⚠️ NO `display` ON ANYTHING THAT ALSO TAKES `[hidden]` — except through the
   guard at the very bottom of this block. An author `display` beats the UA
   stylesheet's `[hidden] { display: none }` at ANY specificity, and eleven
   things on this page are emitted hidden: all eight run panels, the closing
   summary and all three card reveals. Nothing below currently sets `display`
   on one of them, so the guard is not load-bearing TODAY — it is here because
   the day someone gives `.ks3-burner-panel` a `display: grid` to tidy the
   layout, eight panels appear at once and the CSS still reads correctly. */


/* ═══ #s-burner · burner-bench ════════════════════════════════════════════
   Two dials, a prediction gate, a four-tile readout, a word equation and a
   verdict. Design's own measurements throughout; the segmented buttons
   themselves are `.ks3-seg-btn`, the system's ONE segmented control (the
   Drift-4 ruling), and nothing here restyles it. */

.ks3-burner .ks3-burner-dials {
  margin-top: 20px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.ks3-burner .ks3-burner-diallabel {
  margin: 0 0 8px;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-burner .ks3-burner-dialrow {
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}

/* The gate. Amber, and see the block header for why that is the ruled meaning
   and not a drift into category. It stays on screen after it is answered —
   see `wireBurnerBench` — so it needs no "spent" treatment: a prediction the
   student can still see beside the readout is the comparison Law 4 is for. */
.ks3-burner .ks3-burner-gate {
  margin-top: 20px;
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-alert-tint);
  border: 2px solid var(--ks3-ink);
}
/* ⚠️ `.ks3-commit` is 22px in `--ks3-alert`, which is right on the ink grounds
   where nearly every gate in the key stage sits and about 1.1:1 here. Design
   draws this prompt as plain 19px/700 ink inside the amber panel — 13:1 — so
   that is what it gets, at (0,2,0), which beats `.ks3-commit` at (0,1,0).
   Scoped to this gate so the shared component stays shared. The three C3 gates
   and c4-04's are the standing precedent. */
.ks3-burner .ks3-commit {
  margin: 0;
  font-size: 19px;
  font-weight: 700;
  line-height: 1.45;
  color: var(--ks3-ink);
}
/* Design caps the lettered list rather than letting three options run the full
   width of the rail (page line 133). */
.ks3-burner .ks3-options { max-width: 32rem; }

/* The readout. `auto-fit` at 210px so the four tiles reflow to two columns and
   then one on a phone rather than squeezing "Black deposit on the beaker
   within seconds" into 90px. `min()` guards the case where the container is
   itself narrower than 210px (MRB-210). */
.ks3-burner .ks3-burner-panel { margin-top: 22px; }
.ks3-burner .ks3-burner-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(210px, 100%), 1fr));
  gap: 14px;
}
.ks3-burner .ks3-burner-tile {
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
/* ⚑ THE ONE AMBER CELL. See the block header: this is a hazard READING and the
   renderer refuses to build a run where the tint and the products disagree.
   The border does not change with it — a solid ink border on every tile is
   what keeps this one a reading in the same row rather than a badge stuck to
   the side of it. */
.ks3-burner .ks3-burner-tile.is-alert { background: var(--ks3-alert-tint); }
.ks3-burner .ks3-burner-tilelabel {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
/* ⚠️ DISPLAY 21px INK, NOT MONO IN THE ACCENT. These readings are sentences,
   not figures — see the block header. */
.ks3-burner .ks3-burner-value {
  margin: 8px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 21px;
  line-height: 1.25;
  color: var(--ks3-ink);
}
.ks3-burner .ks3-burner-tilenote {
  margin: 7px 0 0;
  font-size: 16px;
  line-height: 1.45;
  color: var(--ks3-ink-body);
}

/* The word equation for the run. Card ground, so it reads as the page's own
   statement about what just happened rather than as another instrument
   reading. */
.ks3-burner .ks3-burner-eq {
  margin-top: 18px;
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
}
.ks3-burner .ks3-burner-eqlabel {
  margin: 0 0 12px;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
/* `flex-wrap` so a long left-hand side ("candle wax + oxygen") and a long
   right-hand side ("carbon monoxide + carbon + water") stack on a phone with
   the drawn arrow between them rather than overflowing the panel. */
.ks3-burner .ks3-burner-eqline {
  margin: 0;
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 21px;
  line-height: 1.3;
  color: var(--ks3-ink);
}
.ks3-burner .ks3-burner-arrow { flex: 0 0 auto; }
/* The charcoal hedge (science flag 4). Body size and muted, because it is a
   footnote on the equation above it and not a second claim: the equation is
   written for the carbon, and this says why that is a simplification. */
.ks3-burner .ks3-burner-eqnote {
  margin: 10px 0 0;
  font-size: 16px;
  line-height: 1.5;
  color: var(--ks3-ink-body);
}

.ks3-burner .ks3-burner-note {
  margin-top: 16px;
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
}
/* The "you have run it both ways" summary. `--ks3-accent` as a 6px offset
   shadow is not text and is legal at any size. */
.ks3-burner .ks3-burner-close {
  margin-top: 16px;
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
  box-shadow: 6px 6px 0 var(--ks3-accent);
}
.ks3-burner .ks3-burner-note p,
.ks3-burner .ks3-burner-close p {
  margin: 0;
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-ink);
}


/* ═══ #s-fuels · fuel-cards ═══════════════════════════════════════════════
   Three cards stacked, each a fuel, three buttons and a reply. The buttons are
   `.ks3-seg-btn` and take nothing from here (Drift 4: one segmented control
   for the system). */

.ks3-fcard {
  margin-top: 20px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.ks3-fcard .ks3-fcard-card {
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-fcard .ks3-fcard-name {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 20px;
  color: var(--ks3-ink);
}
.ks3-fcard .ks3-fcard-made {
  margin: 6px 0 0;
  font-size: 17px;
  line-height: 1.5;
  color: var(--ks3-ink-body);
}
.ks3-fcard .ks3-fcard-opts {
  margin-top: 14px;
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
/* ⚠️ THE SPENT TREATMENT IS AN OPACITY, NOT A COLOUR, AND IT IS NOT A MARK.
   Once a card is decided every button disables; the one that was pressed keeps
   the ordinary `aria-pressed` accent treatment and the ones that were not dim
   to `--ks3-dim-spent`. That says WHICH WAS CHOSEN and says nothing whatever
   about whether it was right — the reply below says what the products are, in
   the same voice either way. c4-03's `.ks3-eqr-opt` is the same pair. */
.ks3-fcard .ks3-fcard-opt[disabled] { cursor: default; }
.ks3-fcard .ks3-fcard-opt[disabled][aria-pressed="false"] {
  opacity: var(--ks3-dim-spent);
}

/* The reply. INK-DARK, and it is the only dark surface in either instrument —
   Design draws it that way (page line 197) so the answer reads as the page
   speaking rather than as a fourth card. 16px radius, not `--ks3-r-panel`:
   hers is a smaller inner corner than the card it sits inside. */
.ks3-fcard .ks3-fcard-reveal {
  margin-top: 14px;
  padding: 16px 18px;
  border-radius: 16px;
  background: var(--ks3-ink);
  color: var(--ks3-on-dark);
}
.ks3-fcard .ks3-fcard-eq {
  margin: 0;
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 19px;
  color: var(--ks3-on-dark);
}
.ks3-fcard .ks3-fcard-why {
  margin: 10px 0 0;
  font-size: 17px;
  line-height: 1.55;
  color: var(--ks3-on-dark-body);
}
/* The drawn arrow inherits `currentColor`, so it is ink inside the bench's
   card panel and on-dark inside this one without either being named twice. */
.ks3-fcard .ks3-burner-arrow { flex: 0 0 auto; }


/* ── the guard, and it is LAST on purpose ────────────────────────────────
   An author `display` beats the UA stylesheet's `[hidden] { display: none }`
   at ANY specificity, and eleven things on this page are emitted hidden. See
   the block header: nothing above sets `display` on one of them today, so this
   is a guard against the next edit rather than a repair of this one. Both are
   (0,2,0), the same as the component rules they would have to beat, so they
   win on source order and must stay at the bottom of the block.
   `setHidden()` also writes an inline `display:none`, but only once JS has
   run: this is what holds between first paint and then, which is the window a
   slow phone makes long. */
.ks3-burner [hidden],
.ks3-fcard [hidden] { display: none; }

/* ── from css_02.css ── */
/* ═══ c5-02 · tube-run + decomp-sort ═════════════════════════════════════
   Two DOM instruments, no canvas. Every value below is measured off Design's
   approved page, `docs/ks3/design-reference/c5/c5-02-thermal-decomposition
   .dc.html`, which carries its real design in inline `style` attributes and
   in the style strings `seg()` composes. Where the page and the engine
   disagreed, the page won.

   ⚠️ BOTH INSTRUMENTS SIT ON A LIGHT `ks3-block`. Measured, not inferred from
   the kind names: `#s-tube` and `#s-sort` both carry `class="ks3-block"` and
   nothing else. So this whole block is written for cream, and the ONLY ink
   ground in it is the tube run's finished panel, which Design paints
   `--ks3-ink` herself (page line 173). That panel takes `--ks3-on-dark` and
   `--ks3-on-dark-body` and NEVER `--ks3-accent-text`.

   ⚠️ EVERY TEXT RULE IS SELF-SCOPED AT (0,2,0) — `.ks3-tuber .ks3-tuber-title`,
   never a bare `.ks3-tuber-title`. A bare component class is (0,1,0) and loses
   to `.ks3-dark p` at (0,1,1) the day anything moves one of these onto ink,
   and it loses SILENTLY: the CSS reads correctly and the sentence ships at
   about 1.2:1. C1's, C3's and C4's blocks say the same thing for the same
   reason.

   ⭐ AMBER, AND WHY IT IS ON THE COOLING GATE. `--ks3-alert-tint` grounds the
   commitment panel inside stage 4, which is what Design draws (page line 136).
   That is amber carrying its ruled meaning exactly: the wrong option in that
   gate is REACT-13 — "it changes back as it cools" — offered one press before
   the run takes it apart. It is never a category and never a selection.

   ⚠️ `.ks3-commit` IS 22px IN `--ks3-alert`, which is right on the hook's ink
   ground and would be 1.4:1 on this amber tint. Design paints the gate's
   question as plain ink at 19px/700, and that is what it gets, at (0,3,0),
   which beats `.ks3-commit` at (0,1,0). Same correction C4 makes on its own
   two amber gates.

   ⚠️ `--ks3-data` DOES NOT APPEAR IN THIS BLOCK. It is an ON-INK token and
   both instruments live on cream, so there is nothing to route to it here.
   Every live value in this block — the three tile readouts, which are the
   masses, the limewater and the heat — is `--ks3-accent-text` at 6.0:1, which
   is what Design paints them (page line 160).

   ⚠️ NOTHING BELOW IS A MARK (R3). Only the mastery ladder marks. No option in
   either instrument takes green or red at any size: a committed gate option
   keeps the platform's ordinary pressed treatment, a decided sort card keeps
   its pressed button and dims its spent sibling, and the verdict is a PANEL OF
   WORDS. `--ks3-ok` does not appear in this block and neither does
   `--ks3-danger`.

   ⚠️ `--ks3-accent` IS 3.4:1 — LARGE TEXT ONLY, and it appears below on
   exactly two things and neither is text: the 2px border round the stage the
   next press will open, and the fill of that stage's number badge, where the
   digit on it is `--ks3-on-dark`.

   ⊖ AND THERE IS NO C5 REDUCED-MOTION BLOCK, deliberately, on B10's, B11's,
   C3's and C4's reasoning: no canvas, no rAF, no timer, and this block
   declares no transition and no animation of its own, so there is nothing here
   for the gate that freezes transitions to freeze. What motion these sections
   have is the shell's arrive treatment.

   ⚠️ NO `display` ON ANYTHING THAT ALSO TAKES `[hidden]`. An author `display`
   beats the UA stylesheet's `[hidden] { display: none }` at ANY specificity,
   and a great deal in these two instruments ships hidden: two of the three
   intros, all twelve stage sentences, the three completed-tab marks, the
   cooling gate, five of the six mass readings, one of the two limewater
   readings, two of the three heat readings, three of the four advance labels,
   all three finished panels, the all-three panel and all five sort reveals.
   Everything that needs a `display` is therefore an element that is NEVER
   hidden — the dial row, the stage list and each stage row, the number badge,
   the tile grid, the control row, the equation line, the sort column and each
   sort's button row — and the guard is the LAST rule in this block so source
   order settles the (0,2,0) ties in its favour.

   ⊖ AND ONE THING THIS BLOCK DELIBERATELY DOES NOT DO. The dial label / dial
   row pair below is the same three declarations C3 groups across five families
   and C4 writes out twice more. It is written out again here rather than added
   to that grouped selector, because C3's and C4's blocks belong to other lanes
   and a lane that edits another lane's file stops being a lane. Reported to
   the commander: when C5 is in, that grouped selector wants all of C4's and
   C5's new families adding to it in one pass. */

/* ── the substance dial ────────────────────────────────────────────────
   The BUTTONS are `.ks3-seg-btn` and take nothing from here — Drift 4 ruled
   the platform's segmented control the key stage's only one, and Design's own
   `seg()` (16px / 11px 16px on `--ks3-option-border`) is reported rather than
   re-declared. */
.ks3-tuber-dial { margin-top: 20px; }
.ks3-tuber .ks3-tuber-diallabel {
  margin: 0 0 8px;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-tuber-tabs {
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
/* The completed mark. A middot, and it is NOT a tick: a run that has been
   carried to the end is a run that has been carried to the end, not an answer
   that was right. R3 keeps `.ks3-mark` off every control in this block. */
.ks3-tuber .ks3-tuber-tabdone { color: var(--ks3-accent-text); }

/* ── what is in the tube ───────────────────────────────────────────────
   56ch, which is Design's own measure and roughly two lines shorter than the
   lesson's body text — this paragraph is read once per substance and switching
   between three of them wants a shape that does not reflow. */
.ks3-tuber .ks3-tuber-intro {
  margin: 18px 0 0;
  font-size: 19px;
  line-height: 1.65;
  max-width: 56ch;
}

/* ── the four stages ───────────────────────────────────────────────────
   `data-open` is a stage the student has watched; `data-current` is the one
   the next press will open. Both are attributes rather than classes because
   both are STATE, and the wiring sets them on every paint. */
.ks3-tuber-stages {
  list-style: none;
  margin: 22px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.ks3-tuber-stage {
  display: flex;
  gap: 14px;
  align-items: flex-start;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-rule);
}
.ks3-tuber-stage[data-open="1"] { background: var(--ks3-inset); }
/* The only two uses of `--ks3-accent` in this block, and neither is text. */
.ks3-tuber-stage[data-current="1"] { border-color: var(--ks3-accent); }
.ks3-tuber-num {
  flex: 0 0 32px;
  height: 32px;
  border-radius: 10px;
  display: grid;
  place-items: center;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 16px;
  background: var(--ks3-band);
  color: var(--ks3-ink-muted);
  border: 2px solid var(--ks3-ink);
}
.ks3-tuber-stage[data-open="1"] .ks3-tuber-num {
  background: var(--ks3-accent);
  color: var(--ks3-on-dark);
}
.ks3-tuber-body { flex: 1; }
.ks3-tuber .ks3-tuber-title {
  margin: 0;
  font-size: 19px;
  line-height: 1.45;
  font-weight: 700;
}
.ks3-tuber .ks3-tuber-text {
  margin: 8px 0 0;
  font-size: 18px;
  line-height: 1.6;
  color: var(--ks3-ink-body);
}

/* ── ⭐ the cooling gate ───────────────────────────────────────────────
   The lesson. Amber, at Design's own geometry, inside the last stage and
   above nothing — the run cannot go on while it is open. See the block note
   for why `.ks3-commit`'s amber is overridden here. */
.ks3-tuber-gate {
  margin-top: 14px;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-alert-tint);
  border: 2px solid var(--ks3-ink);
}
.ks3-tuber .ks3-tuber-gate .ks3-commit {
  margin: 0;
  font-size: 19px;
  font-weight: 700;
  color: var(--ks3-ink);
}
.ks3-tuber-gate .ks3-options { max-width: 30rem; }

/* ── the three readouts ────────────────────────────────────────────────
   `auto-fit` at a 200px floor, so the three sit in a row on a desk and stack
   on a phone without a second breakpoint. The VALUE is mono and
   `--ks3-accent-text`, which is how this key stage draws a live reading. */
.ks3-tuber-tiles {
  margin-top: 18px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 14px;
}
.ks3-tuber-tile {
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-tuber .ks3-tuber-tilelabel {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-tuber .ks3-tuber-tileval {
  margin: 8px 0 0;
  font-family: var(--ks3-font-mono);
  font-weight: 500;
  font-size: 22px;
  line-height: 1.2;
  color: var(--ks3-accent-text);
}

/* ── the two controls ──────────────────────────────────────────────────
   `.ks3-reveal-btn` and `.ks3-retry` are the platform's, and neither takes
   anything from here beyond the row that holds them. */
.ks3-tuber-controls {
  margin-top: 16px;
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

/* ── the finished run ──────────────────────────────────────────────────
   The one ink ground in this block, and the word equation is the reason: a
   display equation on cream would be one more panel, and Design gives the
   result of the run its own weight. `currentColor` on the drawn arrow inks it
   from this rule and not from a second copy of the token. */
.ks3-tuber-done {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ink);
  color: var(--ks3-on-dark);
}
.ks3-tuber .ks3-tuber-eq {
  margin: 0;
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 21px;
  color: var(--ks3-on-dark);
}
.ks3-tuber .ks3-tuber-arrow { flex: 0 0 auto; }
.ks3-tuber .ks3-tuber-finish {
  margin: 12px 0 0;
  font-size: 18px;
  line-height: 1.6;
  color: var(--ks3-on-dark-body);
}

/* ── all three, and the shape they share ───────────────────────────────
   The offset accent shadow is Design's card treatment for a conclusion, the
   same one the key fact takes. It arrives only when every substance has been
   carried to the end, which is also when the rail stop ticks. */
.ks3-tuber-all {
  margin-top: 16px;
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
  box-shadow: 6px 6px 0 var(--ks3-accent);
}
.ks3-tuber .ks3-tuber-all p {
  margin: 0;
  font-size: 19px;
  line-height: 1.6;
}

/* ── the five changes ──────────────────────────────────────────────────
   One column of equals, deliberately: sorting them into two named bins would
   put the answer on screen before the question, and the whole construction is
   that heat is in all five. */
.ks3-dcomp {
  margin-top: 18px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.ks3-dcomp-item {
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-dcomp .ks3-dcomp-text {
  margin: 0;
  font-size: 19px;
  line-height: 1.5;
}
.ks3-dcomp-opts {
  margin-top: 12px;
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
/* The reveal. A card inset inside the item's own inset, on `--ks3-rule` rather
   than ink, so it reads as a reply to the item rather than a second item. No
   `display` here — it ships `hidden`. */
.ks3-dcomp-reveal {
  margin: 12px 0 0;
  padding: 14px 16px;
  border-radius: 14px;
  background: var(--ks3-card);
  border: 2px solid var(--ks3-rule);
}
.ks3-dcomp .ks3-dcomp-reveal p {
  margin: 0;
  font-size: 17px;
  line-height: 1.55;
}

/* ── the hidden guard, LAST ────────────────────────────────────────────
   Everything in these two instruments that ships `hidden` is covered here, so
   that a later rule giving one of them a `display` is undone by source order
   rather than shipping a stage list with all twelve sentences on it at once.

   Written the way C3's and C4's guards are written — (0,2,0), no
   `!important`, last in its own block — so it ties with every component rule
   above it and wins on source order, which is the same mechanism and not a
   second one. */
.ks3-tuber [hidden],
.ks3-dcomp [hidden] { display: none; }

/* ── from css_03.css ── */
/* ═══ c5-03 · control-tubes (#s-rust) and rust-stop (#s-stop) ═══════════
   Two families, one page, every value measured off Design's approved page
   `docs/ks3/design-reference/c5/c5-03-oxidation.dc.html` — a React
   prototype carrying its real design in inline `style` attributes and in
   the style strings its component script composes. Where the page and the
   engine disagreed, the page won.

   ⚠️ BOTH SIT ON A LIGHT `ks3-block`. Measured, not inferred: `#s-rust`
   and `#s-stop` both carry `class="ks3-block"` and nothing else. There is
   no ink-dark practical block on this page — or anywhere in C4 or C5 — so
   everything below is written for cream, and the ONLY ink ground is the
   result panel inside a tube card, which Design paints `--ks3-ink`
   herself. It takes `--ks3-on-dark` / `--ks3-on-dark-body` and never
   `--ks3-accent-text`.

   ⚠️ EVERY TEXT RULE IS SELF-SCOPED AT (0,2,0) — `.ks3-ctube
   .ks3-ctube-setup`, not a bare `.ks3-ctube-setup`. A bare component class
   is (0,1,0) and loses to `.ks3-dark p` at (0,1,1) the day anything moves
   one of these onto ink, and it loses SILENTLY: the CSS reads correctly
   and the sentence ships at ~1.2:1. C1's block, C3's and C4's say the same
   thing for the same reason.

   ⚠️ NOTHING BELOW IS A MARK (R3). Only the mastery ladder marks. A tube
   predicted wrong is drawn exactly like a tube predicted right — the
   pressed button keeps the platform's ordinary pressed treatment, its
   spent sibling dims, and the ink panel underneath is the same panel in
   both cases. `--ks3-ok` and `--ks3-danger` do not appear in this block at
   all, and neither does amber: the confrontation on this page is the
   four-tube summary, which is a READING of evidence rather than a wrong
   idea being warned about, so it takes Design's `--ks3-band` and not
   `--ks3-alert-tint`.

   ⚖️ THE CHIPS ARE A SETUP, NOT A VERDICT. `--ks3-accent-tint` for a
   factor that is present and `--ks3-band` for one that has been removed is
   Design's own pair, and it is a category (what is in the tube), never
   correctness. R2 is satisfied without the colour: every chip carries the
   WORD — "Oxygen present", "Water removed" — so a reader who sees no
   difference between the two grounds has still been told which is which.

   ⊖ NO `--ks3-data` HERE, and no reading to route to it. It is an on-ink
   token and the one ink panel on this page carries sentences ("Rusted —
   orange and flaking at the water line"), not values. Nothing in this
   lesson names a quantity.

   ⊖ AND NO REDUCED-MOTION BLOCK, deliberately: no canvas, no rAF, no
   timer, and this block declares no transition and no animation of its
   own. The arrive treatment on a revealed panel is the shell's.

   ⚠️ NO `display` ON ANYTHING THAT ALSO TAKES `[hidden]` — and a good deal
   here is emitted hidden, because both instruments are built on
   emit-both-show-one: four result panels, five answer paragraphs and the
   whole summary. An author `display` beats the UA stylesheet's
   `[hidden] { display: none }` at ANY specificity, so the guard is the
   LAST rule in this block and source order settles the (0,2,0) ties in its
   favour. */

/* ── #s-rust · the four tubes ─────────────────────────────────────────
   `auto-fit` / `minmax(250px, 1fr)` is Design's own, and it is what makes
   the shape readable at every width: four across on a laptop, two across
   on a tablet, one column on a phone — and in every case the chips of two
   adjacent tubes line up, which is the whole reason the controlled
   variables are chipped rather than written into the sentence. */
.ks3-ctube-grid {
  margin-top: 22px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 16px;
}

/* `--ks3-inset` on the block's own card: the block is the bench, and each
   tube is a thing standing on it. `flex-direction: column` is Design's and
   is load-bearing — it is what keeps the four buttons and the four chip
   rows on the same baselines across a row of cards whose setup sentences
   are different lengths. */
.ks3-ctube-card {
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
  display: flex;
  flex-direction: column;
}
.ks3-ctube .ks3-ctube-num {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-ctube .ks3-ctube-name {
  margin: 6px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 20px;
  line-height: 1.3;
}
.ks3-ctube .ks3-ctube-setup {
  margin: 8px 0 0;
  font-size: 17px;
  line-height: 1.5;
  color: var(--ks3-ink-body);
}

/* The controlled variables, at a glance. See the block header for why the
   ground is not a mark and why the word carries the state. */
.ks3-ctube-chips {
  margin-top: 12px;
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}
.ks3-ctube .ks3-ctube-chip {
  display: inline-block;
  padding: 6px 12px;
  border-radius: var(--ks3-r-pill);
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  border: 2px solid var(--ks3-ink);
  background: var(--ks3-band);
  color: var(--ks3-ink);
}
.ks3-ctube .ks3-ctube-chip[data-on="1"] { background: var(--ks3-accent-tint); }

.ks3-ctube-opts {
  margin-top: 14px;
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
/* ⚖️ `--ks3-dim-spent` (.65), not Design's .45 — MRB-257 audit 3.21 ruled
   that value for exactly this state, and C3 and C4 carry the same
   divergence for the same reason: "you may not press this any more" and
   "you may not read this" are two different sentences, and .45 says the
   second. The card still has to say what the student predicted. */
.ks3-ctube-opt[disabled],
.ks3-rstop-opt[disabled] { cursor: default; }
.ks3-ctube-opt[disabled][aria-pressed="false"],
.ks3-rstop-opt[disabled][aria-pressed="false"] { opacity: var(--ks3-dim-spent); }

/* The one ink ground on the page. It is a panel of WORDS whichever way the
   prediction went, and it is the same panel in all four cards. */
.ks3-ctube-open {
  margin-top: 14px;
  padding: 16px 18px;
  border-radius: 16px;
  background: var(--ks3-ink);
  color: var(--ks3-on-dark);
}
.ks3-ctube .ks3-ctube-result {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 19px;
  line-height: 1.3;
  color: var(--ks3-on-dark);
}
.ks3-ctube .ks3-ctube-why {
  margin: 8px 0 0;
  font-size: 17px;
  line-height: 1.55;
  color: var(--ks3-on-dark-body);
}

/* ── the four-tube summary ────────────────────────────────────────────
   `--ks3-band` on a 2px ink border, wider than the cards and sitting under
   all of them: it is the reading of the four, and it is drawn as one thing
   spanning the row rather than as a fifth card in it. This panel carries
   `id="four-tube-summary"` — it is REACT-15's `confronted_by`, and the
   only place on the page that reads tubes 2 and 3 together. */
.ks3-ctube-summary {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
}
.ks3-ctube .ks3-ctube-sumtitle {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 22px;
  line-height: 1.25;
}
.ks3-ctube .ks3-ctube-sumtext {
  margin: 10px 0 0;
  font-size: 19px;
  line-height: 1.6;
}
/* The one equation on this page. `currentColor` on the drawn arrow is what
   lets the path ink itself against whatever ground the panel carries, and
   `flex: 0 0 auto` stops the flex row squeezing it out of shape. */
.ks3-ctube .ks3-ctube-eq {
  margin: 14px 0 0;
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 20px;
}
.ks3-ctube-arrow { flex: 0 0 auto; }

/* ═══ #s-stop · rust-stop ═══════════════════════════════════════════════
   Five methods stacked, not gridded: each is a sentence about a real
   object and they are read down the page in Design's order, ending on the
   galvanised gate that is both kinds at once. */
.ks3-rstop {
  margin-top: 18px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.ks3-rstop-card {
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-rstop .ks3-rstop-name {
  margin: 0;
  font-size: 19px;
  line-height: 1.5;
  font-weight: 700;
}
.ks3-rstop .ks3-rstop-what {
  margin: 6px 0 0;
  font-size: 17px;
  line-height: 1.5;
  color: var(--ks3-ink-body);
}
.ks3-rstop-opts {
  margin-top: 12px;
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
/* `--ks3-card` on `--ks3-rule`, deliberately NOT ink: this is the card
   answering itself, not a verdict arriving from somewhere else. The same
   two-tone stack c4-03's readings take. */
.ks3-rstop .ks3-rstop-answer {
  margin: 12px 0 0;
  padding: 14px 16px;
  border-radius: 14px;
  background: var(--ks3-card);
  border: 2px solid var(--ks3-rule);
  font-size: 17px;
  line-height: 1.55;
}

/* ── narrow ───────────────────────────────────────────────────────────
   A 44px tap target that has been squeezed to fit is not a tap target, so
   the control rows keep their gap and wrap instead. At 390px a
   `ks3-block`'s content box is about 330px and the grid is already one
   column, so the only thing that has to give is the space between two
   buttons whose labels are five words long. */
@media (max-width: 480px) {
  .ks3-ctube-opts,
  .ks3-rstop-opts,
  .ks3-ctube-chips { gap: 10px; }
  .ks3-ctube-card { padding: 18px 18px; }
  .ks3-ctube-summary { padding: 20px 20px; }
}

/* ── the `[hidden]` guard, and it is LAST on purpose ───────────────────
   An author `display` beats the UA stylesheet's `[hidden] { display: none }`
   at ANY specificity, and this block sets `display` in seven places. None
   of those seven is currently an element that ships hidden — the result
   panels, the answer paragraphs and the summary all carry padding and a
   ground and no `display` — so today this rule changes nothing. It is here
   because the next person to give `.ks3-ctube-open` a `display: flex` for
   an icon row would break four reveals on a live page and the CSS would
   still read correctly. Last in the block so source order settles the
   (0,2,0) ties in its favour. */
.ks3-ctube [hidden],
.ks3-rstop [hidden] { display: none; }

/* ── from css_04.css ── */
/* ═══ c5-04 · displacement ═════════════════════════════════════════════════
   Two instruments — `reactivity-grid` and `reactivity-use` — and both of them
   sit in a LIGHT `ks3-block`. Measured off Design's own markup (page lines 145
   and 216: `class="ks3-block"` and nothing else). There is no ink-dark
   practical block anywhere in C5, exactly as there is none in C3 or C4, so
   nothing here paints one.

   ⚠️ ONLY THE LADDER MARKS. Every value below is a GROUND or a BORDER, and not
   one of them is keyed to whether the student was right. A run cell is painted
   by what the tube did — `--ks3-accent-tint` where a reaction happened,
   `--ks3-band` where none did, which are Design's own two grounds — and
   nothing in this file selects on a prediction. There is no green and no red
   in it, and no `--ks3-ok` at all.

   ⊖ NO `--ks3-data` HERE. It is an on-ink token and everything in these two
   instruments except the result panel sits on the light ground. The one ink
   surface is the result panel, and what it carries is prose and a word
   equation rather than a reading, so there is no live value to route to it.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── #s-grid · reactivity-grid ─────────────────────────────────────────── */

.ks3-rgrid { margin-top: 20px; }

/* THE SCROLL REGION IS NOT A NICETY. Five columns do not fit a 390px phone,
   and most of these students are on a phone. `tabindex="0"` in the markup is
   what makes the region reachable without a finger (WCAG 2.1.1); the table's
   sr-only caption is what gives that focus stop something to announce. */
/* ⚠️ `position: relative` IS LOAD-BEARING AND IT IS NOT A TIDY-UP.
   Without it this page scrolls SIDEWAYS BY 140px at 390px, and the cause is
   the last thing you would look at: the grid's sixty-five `.ks3-sr-only`
   labels — the screen-reader text naming each cell's metal and solution —
   are `position: absolute`. A static scroller is not their containing block,
   so their containing block is an ancestor OUTSIDE it, and `overflow-x: auto`
   does not clip a descendant it does not contain. The labels in the far-right
   columns therefore sat past the viewport and extended the document.

   So the accessibility text, invisible to a sighted student, was dragging the
   page sideways for every student. `overflow: hidden` does NOT fix it —
   measured — because the problem is containment, not clipping; `contain:
   paint` does, and so does this, which is the honest one-word version.

   MRB-229: a figure scrolling inside its own box is the treatment, the page
   scrolling is the defect it exists to prevent. The table still scrolls. */
.ks3-rgrid-scroll { overflow-x: auto; position: relative; }

.ks3-rgrid-table {
  border-collapse: collapse;
  min-width: 34rem;
}
.ks3-rgrid-table th[scope="col"] {
  padding: 10px 12px;
  text-align: left;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-rgrid-table th[scope="row"] {
  padding: 10px 12px;
  text-align: left;
  font-size: 18px;
  font-weight: 700;
  border-top: 2px solid var(--ks3-rule);
}
.ks3-rgrid-table td {
  padding: 6px;
  border-top: 2px solid var(--ks3-rule);
}

/* One cell. The three marks inside it are all in the document and one is
   shown, so the button never has a sentence written into it. */
.ks3-rgrid-cell {
  font: inherit;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  min-width: 5.6rem;
  min-height: var(--ks3-tap);
  padding: 8px 10px;
  border-radius: 12px;
  cursor: pointer;
  border: 2px solid var(--ks3-ink);
  background: var(--ks3-card);
  color: var(--ks3-ink-ghost);
}
/* The two RESULT grounds. A tube that reacted and a tube that did not are two
   readings, not two verdicts on the student — which is why they are the accent
   tint and the band rather than anything that reads as a mark. */
.ks3-rgrid-cell[data-rgrid-state="reacts"] {
  background: var(--ks3-accent-tint);
  color: var(--ks3-ink);
}
.ks3-rgrid-cell[data-rgrid-state="none"] {
  background: var(--ks3-band);
  color: var(--ks3-ink);
}
/* R2 — the open cell carries a WORD (`aria-pressed`) as well as the ring, so
   the selection is never colour alone. */
.ks3-rgrid-cell[aria-pressed="true"] {
  border-color: var(--ks3-accent);
  box-shadow: 0 0 0 4px var(--ks3-accent-tint);
}

/* ── the panel under the table ─────────────────────────────────────────── */

.ks3-rgrid-panel {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-rgrid .ks3-rgrid-title {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 21px;
  line-height: 1.3;
}
.ks3-rgrid .ks3-rgrid-line {
  margin: 8px 0 0;
  font-size: 18px;
  line-height: 1.6;
  color: var(--ks3-ink-body);
}

.ks3-rgrid-predict { margin-top: 14px; }
.ks3-rgrid .ks3-rgrid-ask {
  margin: 0;
  font-size: 19px;
  font-weight: 700;
}
.ks3-rgrid-opts {
  margin-top: 12px;
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
/* ⚖️ `--ks3-dim-spent` (.65), not Design's .45 — MRB-257 audit 3.21 ruled that
   value for exactly this state. "You may not press this any more" and "you may
   not read this" are two different sentences, and .45 says the second; the row
   still has to say what the student predicted about this cell. */
.ks3-rgrid-opt[disabled] { cursor: default; }
.ks3-rgrid-opt[disabled][aria-pressed="false"] { opacity: var(--ks3-dim-spent); }

/* ── the result: the one ink ground in the instrument ──────────────────── */

.ks3-rgrid-result {
  margin-top: 16px;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ink);
  color: var(--ks3-on-dark);
}
.ks3-rgrid .ks3-rgrid-verdict {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 20px;
  line-height: 1.3;
  color: var(--ks3-on-dark);
}
.ks3-rgrid .ks3-rgrid-why {
  margin: 10px 0 0;
  font-size: 17px;
  line-height: 1.6;
  color: var(--ks3-on-dark-body);
}
.ks3-rgrid .ks3-rgrid-eq {
  margin: 12px 0 0;
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 19px;
  color: var(--ks3-on-dark);
}
/* The drawn arrow. `currentColor` is what lets one path serve the ink panel
   without a second copy, and `flex: 0 0 auto` stops the row squeezing it out
   of shape. */
.ks3-rgrid-arrow { flex: 0 0 auto; }

/* ── the payoff, on the band ───────────────────────────────────────────── */

.ks3-rgrid-pattern {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
}
.ks3-rgrid .ks3-rgrid-pattern-title {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 22px;
  line-height: 1.25;
}
.ks3-rgrid .ks3-rgrid-pattern-body {
  margin: 10px 0 0;
  font-size: 19px;
  line-height: 1.6;
}
.ks3-rgrid .ks3-rgrid-pattern-order {
  margin: 14px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 21px;
}

/* ── #s-uses · reactivity-use ──────────────────────────────────────────── */

.ks3-ruse {
  margin-top: 18px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.ks3-ruse-card {
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-ruse .ks3-ruse-q {
  margin: 0;
  font-size: 19px;
  line-height: 1.55;
}
.ks3-ruse-opts {
  margin-top: 12px;
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
/* Deliberately NOT ink: this is the card answering itself, not a verdict
   arriving from somewhere else. Same two-tone stack c4-03's readings use. */
.ks3-ruse .ks3-ruse-answer {
  margin: 12px 0 0;
  padding: 14px 16px;
  border-radius: 14px;
  background: var(--ks3-card);
  border: 2px solid var(--ks3-rule);
  font-size: 17px;
  line-height: 1.55;
}
.ks3-ruse-opt[disabled] { cursor: default; }
.ks3-ruse-opt[disabled][aria-pressed="false"] { opacity: var(--ks3-dim-spent); }

/* ── narrow ────────────────────────────────────────────────────────────
   A 44px tap target squeezed to fit is not a tap target, so the control rows
   keep their gap and wrap. The grid does not wrap — it scrolls — because a
   table whose columns reflow is no longer the shape the lesson is about. */
@media (max-width: 480px) {
  .ks3-rgrid-opts,
  .ks3-ruse-opts { gap: 10px; }
  .ks3-rgrid-panel,
  .ks3-rgrid-pattern,
  .ks3-ruse-card { padding-left: 18px; padding-right: 18px; }
}

/* ── the `[hidden]` guard, and it is LAST on purpose ───────────────────
   An author `display` beats `[hidden]` at any specificity, and several rules
   above set `display` on elements that are emitted hidden — every result
   panel's equation row and the payoff among them. */
.ks3-rgrid [hidden],
.ks3-ruse [hidden] { display: none; }

/* ── from css_05.css ── */
/* ═══ c5-05 · type-sorter (#s-sort) and rule-write (#s-rule) ═════════════
   The unit's assessment in disguise. Every value below is measured off
   Design's approved page, `docs/ks3/design-reference/c5/
   c5-05-which-reaction-is-this.dc.html`, which carries its real design in
   inline `style` attributes and in the style strings its component script
   composes (`seg()`, `it.boxStyle`, the two reveal panels).

   ⚠️ BOTH BLOCKS SIT ON A LIGHT `ks3-block ks3-check`. There is no ink-dark
   practical block anywhere in C4 or C5 — measured, not inferred from the kind
   names — so everything here is written for cream, and the ONLY ink ground
   below is the ANSWER PANEL a decided reaction opens, which Design paints
   `--ks3-ink` herself. That panel takes `--ks3-on-dark` / `--ks3-on-dark-body`
   and never `--ks3-accent-text`.

   ⚠️ EVERY TEXT RULE IS SELF-SCOPED AT (0,2,0) — `.ks3-tsort .ks3-tsort-why`,
   not a bare `.ks3-tsort-why`. A bare component class is (0,1,0) and loses to
   `.ks3-dark p` at (0,1,1) the day anything moves one of these instruments
   onto ink, and it loses SILENTLY: the CSS reads correctly and the sentence
   ships at ~1.2:1. C1's and C3's blocks say the same thing for the same
   reason, and two builds have been bitten by it.

   ⚠️ NOTHING BELOW IS A MARK (R3, MRB-196 R10). Only the mastery ladder marks
   correctness. No type button takes green or red at any size: a committed
   button keeps the platform's ordinary accent-tint pressed treatment, its four
   spent siblings dim to `--ks3-dim-spent`, and the answer is a PANEL OF WORDS.
   `--ks3-ok` does not appear in this block at all, and neither does
   `--ks3-danger`.

   ⊖ NO AMBER ANYWHERE. Amber warns a wrong IDEA being confronted, and the
   confrontation on this page is `#s-think`, which is the shell's own
   `.ks3-misconception` block and is not styled here. A reaction a student
   named wrongly is not a wrong idea being confronted; it is a commitment,
   and it takes the ordinary treatment.

   ⊖ AND `--ks3-data` IS UNUSABLE HERE, deliberately. It is `#8FB7FF`, an
   on-ink token, and the two grounds these instruments live on are cream and
   the block's own card. The one live value on the page — the "n of 8 named"
   tally — is the SHELL's `[data-count]`, drawn in `--ks3-accent-text` (6.0:1)
   by `data-tone="accent"`, which is what Design paints it.

   ⊖ AND THERE IS NO REDUCED-MOTION BLOCK, for C3's reason and B10's: no
   canvas, no rAF, no timer, so there is no tick that would have to re-test
   the query inside itself. This block declares NO transition and NO animation
   of its own; the arrive treatment on an opened panel is the shell's.

   ⚠️ NO `display` ON ANYTHING THAT ALSO TAKES `[hidden]`. An author `display`
   beats the UA stylesheet's `[hidden] { display: none }` at ANY specificity,
   and ten things in these two instruments are emitted hidden — the eight
   answer panels, the close panel and the comparison panel. The guard is the
   LAST rule in this block so source order settles the ties in its favour. */

/* ── #s-sort · the eight reactions ────────────────────────────────────── */
/* A COLUMN, not a grid. Design stacks the eight full-width
   (`flex-direction: column`, gap 16px) rather than tiling them, and that is
   the instrument's argument as much as the shared option list is: the eight
   are read IN ORDER, four straightforward then three harder then one that is
   none of the four, and a reflowing two-up grid would put reaction 8 beside
   reaction 7 on a wide screen and lose the rising stakes entirely. */
.ks3-tsort-list {
  margin-top: 22px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
/* Two grounds stacked, which is Design's drawing: the block is the page and
   the reaction is an inset card on it. The third ground — ink — arrives only
   when the card is decided. */
.ks3-tsort-card {
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
/* `align-items: baseline`, so the 13px position and the 12px difficulty tag
   sit on one line rather than on two centres. */
.ks3-tsort-head {
  display: flex;
  align-items: baseline;
  gap: 12px;
  flex-wrap: wrap;
}
.ks3-tsort .ks3-tsort-num {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
/* The difficulty tag — "Straightforward", "Harder", "Awkward on purpose".
   `--ks3-accent-text` (6.0:1), never `--ks3-accent` (3.4:1), because it is
   12px and §11's rule for accent under 24px is the text token. It labels the
   card, and it is emphatically NOT a mark: it says how hard the author thinks
   the reaction is, before the student has answered it. */
.ks3-tsort .ks3-tsort-level {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-accent-text);
}
.ks3-tsort .ks3-tsort-text {
  margin: 8px 0 0;
  font-size: 19px;
  line-height: 1.55;
  color: var(--ks3-ink);
}
.ks3-tsort-opts {
  margin-top: 14px;
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
/* ── the one-shot commit bank ──────────────────────────────────────────
   All five buttons on a reaction disable the moment any one is pressed.
   Design dims a disabled button that was NOT the one pressed and leaves the
   pressed one at full strength, so the card still says what the student
   chose.

   ⚖️ `--ks3-dim-spent` (.65), not Design's .45. MRB-257 audit 3.21 ruled that
   value for exactly this state: "you may not press this any more" and "you
   may not read this" are two different sentences, and .45 says the second.
   C3's nine instruments made the same divergence for the same reason. */
.ks3-tsort-opt[disabled] { cursor: default; }
.ks3-tsort-opt[disabled][aria-pressed="false"] { opacity: var(--ks3-dim-spent); }
/* ── the answer a decided reaction opens ───────────────────────────────
   THE ONLY INK GROUND IN EITHER INSTRUMENT, and it is Design's. It arrives on
   top of the inset card, which is why it is the darkest thing in the block:
   the block is the page, the card is a reaction, and this is the answer
   landing on it. */
.ks3-tsort-reveal {
  margin-top: 14px;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ink);
  color: var(--ks3-on-dark);
}
.ks3-tsort .ks3-tsort-answer {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 20px;
  line-height: 1.3;
  color: var(--ks3-on-dark);
}
.ks3-tsort .ks3-tsort-why {
  margin: 10px 0 0;
  font-size: 17px;
  line-height: 1.6;
  color: var(--ks3-on-dark-body);
}
/* The all-eight close. A BAND panel — the key stage's "here is what you just
   did" treatment, and not a verdict on the student. Same values as C3's
   `.ks3-psort-close` and C4's close panels; a separate rule rather than a
   name added to that selector list, because a lane appends and does not edit
   what is above it. */
.ks3-tsort-close {
  margin-top: 20px;
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
}
.ks3-tsort .ks3-tsort-close p {
  margin: 0;
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-ink);
}

/* ── #s-rule · write it yourself, then compare ─────────────────────────
   The written-answer control itself is the key stage's shared one —
   `.ks3-answer-label`, `.ks3-answer`, `.ks3-check-btn`, all already in this
   stylesheet — so nothing is redeclared for it here, including the inactive
   treatment on the gated button (`.ks3-check-btn[disabled]`, measured
   `--ks3-dim-spent` with the hover lift cancelled). There is no copy anywhere
   about the sixty-character unlock and there must not be: §8.10, and Mide's
   ruling of 19 Aug 2026 takes it knowingly. */
.ks3-rwrite-reveal {
  margin-top: 18px;
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
/* Design's own list, bullets kept: this is a comparison a student reads
   against four rules of their own, and four bulleted lines are four items to
   check off. `padding-left` rather than `list-style: none`, so the markers
   sit inside the panel. */
.ks3-rwrite-list {
  margin: 0;
  padding: 0 0 0 20px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.ks3-rwrite .ks3-rwrite-item {
  font-size: 18px;
  line-height: 1.6;
  color: var(--ks3-ink);
}
/* "If yours says the same things in worse English, yours is the more useful
   one" — quieter than the four rules above it, which is Design's own
   hierarchy: the model is the reference and this is the remark about it. */
.ks3-rwrite .ks3-rwrite-close {
  margin: 14px 0 0;
  font-size: 18px;
  line-height: 1.6;
  color: var(--ks3-ink-body);
}

/* ── narrow readers ───────────────────────────────────────────────────
   Nothing needs a media query. The reaction column is already full-width at
   every size, and the five type buttons are a wrapping flex row: at 390px a
   `ks3-block`'s content box is about 330px and the card's own padding takes
   it to roughly 286px, which fits the longest label ("Thermal decomposition"
   at 17px/700 plus the control's 34px of padding) on a line of its own. A
   media query that only restated the wrap would be a rule that can drift
   away from it. */

/* ── the hidden guard, and it is LAST on purpose ─────────────────────── */
.ks3-tsort [hidden],
.ks3-rwrite [hidden] { display: none; }

/* ═══ END C5 ═══ */

/* ═══ BEGIN C7 ═══════════════════════════════════════════════════════════
   C7's instrument styling — *Energy changes in reactions*. A PURE APPEND at
   EOF — every byte above this marker is unchanged, which is the one conflict
   shape that resolves mechanically when another lane merges.

   Every value below is measured off Claude Design's approved pages in
   `docs/ks3/design-reference/c7/`, which carry their real design in inline
   `style` attributes and in the style strings `lessonVals()` composes. Where
   the page and the engine disagreed, the page won, except where a
   disagreement is written out below as a ruling.

   ALL EIGHT SECTIONS ARE LIGHT. `#s-curve` and `#s-uses` on c7-01, `#s-bench`
   and `#s-uses` on c7-02, `#s-compare` and `#s-uses` on c7-03, `#s-plan` and
   `#s-bench` on c7-04 are each `class="ks3-block"` with no second class.
   There is no ink-dark practical block anywhere in C7, exactly as there is
   none in C3, C4 or C5. The INK PANELS inside the two benches are readouts
   nested in a light block, which is a different thing and is styled as one. A
   later pass that "restores" a practical shell here would paint an instrument
   on a ground Design did not draw it on and resolve every text token inside it
   wrong.

   ⚠️ EVERY TEXT RULE IS SELF-SCOPED AT (0,2,0) — `.ks3-hcurve
   .ks3-hcurve-note`, never a bare `.ks3-hcurve-note`. A bare component class
   is (0,1,0) and loses to `.ks3-dark p` at (0,1,1) the day anything moves one
   of these instruments onto ink, and it loses SILENTLY: the CSS reads
   correctly and the sentence ships at about 1.2:1. C1's, C3's, C4's and C5's
   blocks all say the same thing for the same reason and two builds have been
   bitten by it.

   ⊖ AMBER APPEARS NOWHERE IN THIS BLOCK. Amber warns — a wrong IDEA being
   confronted — and C7's confrontations all live in the shell's own
   `#s-think` register, which is already amber and is not restyled here. None
   of the six instruments has a prediction gate that elicits a named
   misconception, so there is nothing here to spend it on. `--ks3-alert-tint`
   does not appear below.

   ⊖ AND NEITHER DOES `--ks3-data`, WHICH IS THE SAME READING C3 AND C5 MADE.
   It is an ON-INK token (#8FB7FF, minted under MRB-252 for category and
   selection) and the two ink panels in this unit already carry Design's own
   treatment: the readings are `--ks3-on-dark` at display weight, the labels
   are `--ks3-on-dark-body` mono, and the one badge that distinguishes a
   category is `--ks3-accent` WITH A WORD IN IT. Substituting a token for a
   colour Design drew would be the engine overruling the page.

   ⚠️ NOTHING BELOW IS A MARK (R3, MRB-196 R10). Only the mastery ladder marks
   correctness. `--ks3-ok` and `--ks3-danger` do not appear in this block at
   all: a chosen dial, a chosen tab and a chosen card option keep the
   platform's ordinary pressed treatment, a spent sibling dims to
   `--ks3-dim-spent`, and every verdict is a PANEL OF WORDS.

   ⚠️ THE HEATING CURVE'S ACCENT BARS ARE NOT COLOUR-ALONE. A flat bar is
   accent and a climbing one is ink, and both facts are ALSO carried in words:
   the readout beside the trace says "· temperature unchanged" on exactly
   those minutes, and the bars themselves are drawn at equal heights, which is
   what "flat" means. R1 is satisfied by the height and the sentence; the
   colour is the third channel, not the only one.

   ⊖ NO REDUCED-MOTION BLOCK, DELIBERATELY. No canvas, no rAF, no timer, and
   this block declares no transition and no animation of its own, so there is
   nothing here for `prefers-reduced-motion` to degrade. What motion the pages
   have is the shell's arrive treatment, which `shared/ks3.css` degrades
   inside its own media query.

   ⚠️ NO `display` ON ANYTHING THAT ALSO TAKES `[hidden]` — except through the
   guard at the very bottom of this block. An author `display` beats the UA
   stylesheet's `[hidden] { display: none }` at ANY specificity, and this unit
   emits a great many hidden things: eighteen curve readouts, five bench cards,
   eight rig panels and eight rig titles, plus every reveal and every closing
   panel. Several of them WOULD have taken a `display: flex` for their layout,
   so the guard here is load-bearing from the first day rather than a
   precaution.
   ═══ */


/* ═══ c7-01 · heating-curve ═══════════════════════════════════════════════
   A readout and a bar trace side by side, and one button. Design's grid is
   `minmax(0, 1fr) minmax(0, 21rem)` with a 22px gap; it collapses to one
   column below 860px so that "Boiling — liquid and gas together" is not
   hyphenated into a 90px column on a phone. */

.ks3-hcurve .ks3-hcurve-grid {
  margin-top: 20px;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 21rem);
  gap: 22px;
  align-items: start;
}
@media (max-width: 860px) {
  .ks3-hcurve .ks3-hcurve-grid { grid-template-columns: minmax(0, 1fr); }
}

/* The readout. Design's inset panel on a 2px ink border. */
.ks3-hcurve .ks3-hcurve-readout {
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-hcurve .ks3-hcurve-point {
  /* No `display` here — this element is emitted `hidden` seventeen times out
     of eighteen. See the block header. */
  margin: 0;
}
/* Design's 40px display figure and the state beside it, baseline-aligned and
   wrapping rather than squeezing. */
.ks3-hcurve .ks3-hcurve-temp {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 40px;
  line-height: 1;
  color: var(--ks3-ink);
}
.ks3-hcurve .ks3-hcurve-state {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 21px;
  line-height: 1.25;
  color: var(--ks3-ink);
}
.ks3-hcurve .ks3-hcurve-minute {
  margin: 4px 0 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-hcurve .ks3-hcurve-note {
  margin: 14px 0 0;
  font-size: 18px;
  line-height: 1.6;
  color: var(--ks3-ink-body);
  /* The eighteen notes vary from two lines to five. A min-height holds the
     panel still as the student steps, so the trace beside it does not walk up
     and down the page under the cursor. */
  min-height: 5.8em;
}
.ks3-hcurve .ks3-hcurve-controls {
  margin-top: 16px;
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
.ks3-hcurve .ks3-hcurve-reset {
  font: inherit;
  font-size: 16px;
  font-weight: 700;
  padding: 11px 16px;
  min-height: var(--ks3-tap);
  border-radius: var(--ks3-r-control);
  cursor: pointer;
  border: 2px solid var(--ks3-rule-strong);
  background: transparent;
  color: var(--ks3-ink-muted);
}
/* The step button spends itself at the end of the run. `--ks3-dim-spent` is
   the measured .65, not a value chosen by eye. */
.ks3-hcurve .ks3-hcurve-step[disabled] {
  opacity: var(--ks3-dim-spent);
  cursor: default;
}

/* The trace. A card, not an inset tile — Design draws it on `--ks3-card` so
   the bars have a lighter ground than the readout beside them. */
.ks3-hcurve .ks3-hcurve-trace {
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
}
.ks3-hcurve .ks3-hcurve-tracelabel,
.ks3-hcurve .ks3-hcurve-axis {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-hcurve .ks3-hcurve-axis {
  margin-top: 10px;
  font-size: 12px;
  letter-spacing: .06em;
}
/* ⚠️ `position: relative` ON THE BAR ROW. Nothing in this unit is absolutely
   positioned inside it today, but this is the row a future annotation would be
   pinned to, and an absolutely positioned child of a non-positioned ancestor
   escapes to the nearest one and widens the whole document. `overflow: hidden`
   does NOT fix that. It bit C5 badly — 65 screen-reader labels — and the cost
   of the guard is one line. */
.ks3-hcurve .ks3-hcurve-bars {
  position: relative;
  margin-top: 12px;
  display: flex;
  align-items: flex-end;
  gap: 3px;
  height: 160px;
}
.ks3-hcurve .ks3-hcurve-bar {
  flex: 1 1 0;
  min-width: 0;
  border-radius: 3px 3px 0 0;
  background: var(--ks3-band);
}
/* A bar the run has reached. Ink for a minute that climbed, accent for one
   that held — and see the block header for why that is not colour-alone. */
.ks3-hcurve .ks3-hcurve-bar.is-lit { background: var(--ks3-ink); }
.ks3-hcurve .ks3-hcurve-bar.is-lit[data-flat="1"] {
  background: var(--ks3-accent);
}

/* The closing panel. Design's band ground on a 2px ink border. */
.ks3-hcurve .ks3-hcurve-close,
.ks3-tempb .ks3-tempb-close,
.ks3-esort .ks3-esort-close,
.ks3-rigb .ks3-rigb-close {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
}
.ks3-hcurve .ks3-hcurve-closetitle,
.ks3-tempb .ks3-tempb-closetitle,
.ks3-esort .ks3-esort-closetitle,
.ks3-rigb .ks3-rigb-closetitle {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 22px;
  line-height: 1.25;
  color: var(--ks3-ink);
}
.ks3-hcurve .ks3-hcurve-close p + p,
.ks3-tempb .ks3-tempb-close p + p,
.ks3-esort .ks3-esort-close p + p,
.ks3-rigb .ks3-rigb-close p + p {
  margin: 12px 0 0;
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-ink);
}


/* ═══ c7-02 · temp-bench ══════════════════════════════════════════════════
   Five tabs, an inset card per beaker, a prediction gate that stays put, and
   an ink readout with a drawn arrow across it. */

.ks3-tempb .ks3-tempb-tabs {
  margin-top: 18px;
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
.ks3-tempb .ks3-tempb-card {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-tempb .ks3-tempb-title {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 21px;
  line-height: 1.3;
  color: var(--ks3-ink);
}
.ks3-tempb .ks3-tempb-setup {
  margin: 8px 0 0;
  font-size: 18px;
  line-height: 1.6;
  color: var(--ks3-ink-body);
}
.ks3-tempb .ks3-tempb-setuplabel {
  display: block;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}

/* The prediction gate (Law 4). It STAYS after it is answered — see
   `wireTempBench` — so it needs no spent treatment of its own beyond the
   buttons dimming, which `.ks3-seg-btn[disabled]` already does. Design draws
   the ask as plain 19px/700 ink rather than as `.ks3-commit`, which is 22px in
   `--ks3-alert` and belongs on the ink grounds where nearly every gate in the
   key stage sits. Scoped at (0,2,0) so the shared component stays shared; the
   three C3 gates, c4-04's and c5-01's are the standing precedent. */
.ks3-tempb .ks3-tempb-gate { margin-top: 14px; }
.ks3-tempb .ks3-commit {
  margin: 0;
  font-size: 19px;
  font-weight: 700;
  line-height: 1.45;
  color: var(--ks3-ink);
}
.ks3-tempb .ks3-tempb-gate .ks3-seg-btn { margin-top: 12px; }

/* The reading. An ink panel nested in the light block — Design's own, and the
   only dark ground in the unit that the engine does not draw itself. */
.ks3-tempb .ks3-tempb-run {
  margin-top: 16px;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ink);
  color: var(--ks3-on-dark);
}
.ks3-tempb .ks3-tempb-readout {
  display: flex;
  gap: 20px;
  align-items: center;
  flex-wrap: wrap;
}
.ks3-tempb .ks3-tempb-figlabel {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-on-dark-body);
}
.ks3-tempb .ks3-tempb-value {
  margin: 2px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 26px;
  line-height: 1;
  color: var(--ks3-on-dark);
}
.ks3-tempb .ks3-tempb-arrow {
  flex: 0 0 auto;
  color: var(--ks3-on-dark);
}
/* The verdict badge. It carries its own WORD — "+39 °C · exothermic" — so R2
   is satisfied by the text and the fill is Design's own. It is not a mark: it
   describes the reaction, not the student's prediction, and it looks the same
   whichever button was pressed. */
.ks3-tempb .ks3-tempb-delta {
  display: inline-block;
  padding: 8px 14px;
  border-radius: 12px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .05em;
  text-transform: uppercase;
  border: 2px solid var(--ks3-on-dark);
  background: transparent;
  color: var(--ks3-on-dark);
}
.ks3-tempb .ks3-tempb-delta[data-exo="1"] {
  border-color: var(--ks3-accent);
  background: var(--ks3-accent);
  color: var(--ks3-on-dark);
}
.ks3-tempb .ks3-tempb-why {
  margin: 12px 0 0;
  font-size: 17px;
  line-height: 1.6;
  color: var(--ks3-on-dark-body);
}


/* ═══ c7-03 · energy-sorter ═══════════════════════════════════════════════
   Eight cards, two buttons each, and a reveal that names the direction. */

.ks3-esort {
  margin-top: 18px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.ks3-esort .ks3-esort-item {
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
/* Design's decided card lifts to the card ground so a sorted row reads as
   spent without anything being marked. */
.ks3-esort .ks3-esort-item[data-open="1"] { background: var(--ks3-card); }
.ks3-esort .ks3-esort-head {
  display: flex;
  gap: 16px;
  align-items: baseline;
  flex-wrap: wrap;
  justify-content: space-between;
}
.ks3-esort .ks3-esort-name {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 21px;
  line-height: 1.25;
  color: var(--ks3-ink);
}
.ks3-esort .ks3-esort-where {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-esort .ks3-esort-opts,
.ks3-euse .ks3-euse-opts,
.ks3-rplan .ks3-rplan-opts {
  margin-top: 12px;
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
.ks3-esort .ks3-esort-reveal,
.ks3-rplan .ks3-rplan-reveal {
  margin-top: 12px;
  padding: 14px 16px;
  border-radius: 14px;
  background: var(--ks3-card);
  border: 2px solid var(--ks3-rule);
}
.ks3-esort .ks3-esort-verdict,
.ks3-rplan .ks3-rplan-verdict {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 18px;
  line-height: 1.35;
  color: var(--ks3-ink);
}
.ks3-esort .ks3-esort-why,
.ks3-rplan .ks3-rplan-why {
  margin: 7px 0 0;
  font-size: 17px;
  line-height: 1.55;
  color: var(--ks3-ink-body);
}


/* ═══ c7-01, c7-02, c7-03 · energy-uses ═══════════════════════════════════
   ONE FAMILY, PLACED THREE TIMES — see `ks3_data/c7/__init__.py`. Three cards,
   one commitment each, an answer paragraph on the card that opens. */

.ks3-euse {
  margin-top: 18px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.ks3-euse .ks3-euse-card {
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-euse .ks3-euse-q {
  margin: 0;
  font-size: 19px;
  line-height: 1.55;
  color: var(--ks3-ink);
}
.ks3-euse .ks3-euse-answer {
  margin: 12px 0 0;
  padding: 14px 16px;
  border-radius: 14px;
  background: var(--ks3-card);
  border: 2px solid var(--ks3-rule);
  font-size: 17px;
  line-height: 1.55;
  color: var(--ks3-ink-body);
}


/* ═══ c7-04 · rig-plan-critique ═══════════════════════════════════════════
   Five steps of somebody else's method, each quoted, each ruled on once.
   Same shape as c3-07's and a different family — see `ks3_art/c7.py` for why
   a lane may not reuse another unit's. */

.ks3-rplan {
  margin-top: 18px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.ks3-rplan .ks3-rplan-step {
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-rplan .ks3-rplan-tag {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
/* The step is a QUOTATION of somebody else's plan, and the quotation marks
   are drawn rather than typed into the content so the string in
   `ks3_data/c7/` stays the sentence the student is judging. */
.ks3-rplan .ks3-rplan-quote {
  margin: 6px 0 0;
  font-size: 19px;
  line-height: 1.55;
  color: var(--ks3-ink);
}
.ks3-rplan .ks3-rplan-quote::before { content: "\201C"; }
.ks3-rplan .ks3-rplan-quote::after { content: "\201D"; }


/* ═══ c7-04 · rig-builder ═════════════════════════════════════════════════
   Three dials, an inset bench, and an ink readout that never quite reaches
   the true value beside it. */

.ks3-rigb .ks3-rigb-dials {
  margin-top: 20px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(14rem, 100%), 1fr));
  gap: 18px;
}
.ks3-rigb .ks3-rigb-diallabel {
  margin: 0 0 10px;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-rigb .ks3-rigb-dialrow {
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
.ks3-rigb .ks3-rigb-bench {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-rigb .ks3-rigb-title {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 21px;
  line-height: 1.3;
  color: var(--ks3-ink);
}
.ks3-rigb .ks3-rigb-runrow { margin-top: 14px; }
.ks3-rigb .ks3-rigb-panel {
  margin-top: 16px;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ink);
  color: var(--ks3-on-dark);
}
.ks3-rigb .ks3-rigb-readout {
  display: flex;
  gap: 20px;
  align-items: center;
  flex-wrap: wrap;
}
.ks3-rigb .ks3-rigb-figlabel {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-on-dark-body);
}
.ks3-rigb .ks3-rigb-value {
  margin: 2px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 30px;
  line-height: 1;
  color: var(--ks3-on-dark);
}
/* ⚑ DESIGN LIGHTS THIS BADGE ON `rig.v >= 6.5`. That threshold selects
   exactly one rig — the 6.8 — so it is reproduced here as `data-best`, which
   the renderer derives from the highest reading in the payload and asserts
   against the authored `best`. Same result, and it cannot drift the day a
   reading is re-tuned. It is a reading about the APPARATUS ("this is the
   closest the bench gets") and carries its own figure, not a mark on the
   student. */
.ks3-rigb .ks3-rigb-lost {
  display: inline-block;
  padding: 8px 14px;
  border-radius: 12px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .05em;
  text-transform: uppercase;
  border: 2px solid var(--ks3-on-dark);
  background: transparent;
  color: var(--ks3-on-dark);
}
.ks3-rigb .ks3-rigb-lost[data-best="1"] {
  border-color: var(--ks3-accent);
  background: var(--ks3-accent);
  color: var(--ks3-on-dark);
}
.ks3-rigb .ks3-rigb-why {
  margin: 12px 0 0;
  font-size: 17px;
  line-height: 1.6;
  color: var(--ks3-on-dark-body);
}


/* ── the hidden guard, and it is LAST on purpose ───────────────────────
   Load-bearing here from day one, not a precaution: `.ks3-esort`, `.ks3-euse`
   and `.ks3-rplan` all take `display: flex` above and every one of them
   contains elements emitted `hidden`, and `.ks3-hcurve-point`,
   `.ks3-tempb-card`, `.ks3-rigb-title` and `.ks3-rigb-panel` are hidden in
   bulk. An author `display` beats the UA stylesheet's `[hidden]` at any
   specificity, so without this the heating curve would show all eighteen
   readouts at once and still read as correct CSS. */
.ks3-hcurve [hidden],
.ks3-tempb [hidden],
.ks3-esort [hidden],
.ks3-euse [hidden],
.ks3-rplan [hidden],
.ks3-rigb [hidden] { display: none; }

/* ═══ END C7 ═══ */

/* ═══ BEGIN C6 ═══════════════════════════════════════════════════════════
   C6's instrument styling. A PURE APPEND at EOF — every byte above this
   marker is unchanged, which is the one conflict shape that resolves
   mechanically when another lane merges. Paste after `/* ═══ END C5 ═══ *​/`.

   Every value below is measured off Claude Design's approved pages in
   `docs/ks3/design-reference/c6/`, which carry their real design in inline
   `style` attributes and in the style strings `lessonVals()` composes. Where
   the page and the engine disagreed, the page won, except where a
   disagreement is written out below as a ruling.

   ── EVERY SECTION IS LIGHT ─────────────────────────────────────────────
   Measured off her own markup: `#s-bench`, `#s-hazard`, `#s-scale`,
   `#s-choose`, `#s-titrate`, `#s-uses`, `#s-test`, `#s-rig`, `#s-world`,
   `#s-name` and `#s-method`
   are each `class="ks3-block"` with no second class. There is no ink-dark
   practical block anywhere in C6, exactly as there is none in C3, C4 or C5. A
   later pass that "restores" a practical shell here would paint an instrument
   on a ground Design did not draw it on and resolve every text token inside
   it wrong.

   ── EVERY TEXT RULE IS SELF-SCOPED AT (0,2,0) ──────────────────────────
   `.ks3-bottle .ks3-bottle-why`, never a bare `.ks3-bottle-why`. A bare
   component class is (0,1,0) and loses to `.ks3-dark p` at (0,1,1) the day
   anything moves one of these instruments onto ink, and it loses SILENTLY:
   the CSS reads correctly and the sentence ships at about 1.2:1. C1's, C3's,
   C4's and C5's blocks all say the same thing for the same reason and two
   builds have been bitten by it.

   ── NOTHING BELOW IS A MARK (R3, MRB-196 R10) ──────────────────────────
   `--ks3-ok` and `--ks3-danger` do not appear in this block at all. A chosen
   option keeps the platform's ordinary pressed treatment, a spent sibling
   dims to `--ks3-dim-spent`, and every verdict is a PANEL OF WORDS. There is
   no `data-correct` in any C6 markup for a selector to reach.

   ── COLOUR, AND THE ONE PLACE IT IS NOT A TOKEN ────────────────────────
   ⚑ `.ks3-phbench-chip` and `.ks3-titr-beaker` take an INLINE background,
   written into the markup by `ks3_art/c6.py` from `PH_COLOURS`. That array is
   fifteen literal hex values and it is the only non-token colour in C1–C8.

   RULED (NOTES-C6 §7): the ramp is the printed universal-indicator chart —
   scientific data, not brand colour — and routing it through `--ks3-accent`
   would make the chart wrong in order to make the palette tidy. The ruling is
   conditional, and the condition is enforced in Python rather than here:
   EVERY cell of the strip and EVERY reading the bench prints carries its
   NUMBER, so identity is never hue-only. Both rules below therefore set a
   near-white ink and a 2px border that do not depend on which of the fifteen
   grounds arrives.

   ⚑ `--ks3-data` IS SPENT HERE, and this is the first C-unit ground that can
   spend it: `--ks3-data` is `#8FB7FF`, an ON-INK token, and C3, C4 and C5 are
   cream all the way down. C6's readouts are ink-dark panels (Design's own
   treatment on `#s-bench`, `#s-titrate`, `#s-name` and `#s-bench` again), so
   the live figures inside them — the recovered mass, the oxygen volume, the
   drop count — take it. It is a CATEGORY AND SELECTION token and never a
   mark, which is exactly how it is used: it says "this is a reading", not
   "this is right".

   ── AMBER APPEARS ONCE AND IT IS A HAZARD, NOT A CATEGORY ──────────────
   ⊖ Actually it appears NOWHERE in this block, and that is worth writing
   down because C5's block spends it twice. C6 has no prediction gate drawn on
   an amber ground — Design draws every one of its gates on the block ground —
   and no readout that names a poison. The `#s-think` sections are amber and
   that is the shell's own `.ks3-misconception`, emitted by `build_ks3.py`.

   ── CONTAINMENT, NOT CLIPPING ──────────────────────────────────────────
   ⚠️ `.ks3-amgrid-scroll` IS `position: relative` AND THAT IS LOAD-BEARING
   THE DAY SOMEBODY ADDS A HIDDEN LABEL. An absolutely positioned child
   escapes a scroller that is not positioned, and `overflow: hidden` does not
   fix it — that is what widened five C5 pages by 140px at 390px with 65
   screen-reader labels. C6's grid currently contains no absolutely positioned
   element at all (every cell's accessible name is an `aria-label` ATTRIBUTE,
   which has no box), so this is a belt rather than the fix. The pH strip is
   not here at all: it is a drawn SVG figure and goes through the platform's
   own `.ks3-figure-scroll`.

   ── NO `display` ON ANYTHING THAT ALSO TAKES `[hidden]` ────────────────
   Except through the guard at the very bottom of this block. An author
   `display` beats the UA stylesheet's `[hidden] { display: none }` at ANY
   specificity, and C6 emits a great many things hidden: every bottle reveal,
   every judgement reveal, six pH results, six titration notes and states,
   eight grid results, twelve naming results, five trial results, and every
   closing panel. Several rules below DO set `display` on those elements'
   containers, so the guard is load-bearing here rather than precautionary.

   ── NO REDUCED-MOTION BLOCK, DELIBERATELY ──────────────────────────────
   ⊖ No canvas, no rAF, no timer, and this block declares no transition and
   no animation of its own, so there is nothing here for
   `prefers-reduced-motion` to degrade. What motion the pages have is the
   shell's arrive treatment, which `shared/ks3.css` degrades inside its own
   media query.
   ═══ */


/* ═══ c6-01 · #s-bench · bottle-sorter ═════════════════════════════════
   Eight cards in one column, each a name, a where-it-lives tag, three
   buttons and a reveal. Design's own measurements: 20px 22px padding,
   `--ks3-r-panel`, a 2px ink border, and the ground moving from `--ks3-inset`
   to `--ks3-card` once the bottle is decided — which is a STATE, not a mark:
   it says "you have been here", the same thing `data-open` says everywhere
   else in the key stage. */

.ks3-bottle .ks3-bottle-cards {
  margin-top: 20px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.ks3-bottle .ks3-bottle-card {
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  border: 2px solid var(--ks3-ink);
  background: var(--ks3-inset);
}
.ks3-bottle .ks3-bottle-card[data-open="1"] { background: var(--ks3-card); }

.ks3-bottle .ks3-bottle-head {
  display: flex;
  gap: 16px;
  align-items: baseline;
  flex-wrap: wrap;
  justify-content: space-between;
}
.ks3-bottle .ks3-bottle-name {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 21px;
  line-height: 1.25;
  color: var(--ks3-ink);
}
/* The where-it-lives tag. Muted mono, uppercase — it is a label on the
   evidence rather than part of the question, and the whole payoff of the
   block is reading the eight of them together. */
.ks3-bottle .ks3-bottle-where {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-bottle .ks3-bottle-opts {
  margin-top: 12px;
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
.ks3-bottle .ks3-bottle-reveal {
  margin-top: 12px;
  padding: 14px 16px;
  border-radius: 14px;
  background: var(--ks3-card);
  border: 2px solid var(--ks3-rule);
}
.ks3-bottle .ks3-bottle-verdict {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 18px;
  color: var(--ks3-ink);
}
.ks3-bottle .ks3-bottle-why {
  margin: 7px 0 0;
  font-size: 17px;
  line-height: 1.55;
  color: var(--ks3-ink-body);
}

/* The closing panel. `--ks3-band` on a 2px ink border, which is the same
   treatment C5's bench summaries take: a payoff the student has earned, not
   a card and not a mark. */
.ks3-bottle .ks3-bottle-close {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
}
.ks3-bottle .ks3-bottle-closetitle {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 22px;
  line-height: 1.25;
  color: var(--ks3-ink);
}
.ks3-bottle .ks3-bottle-closebody {
  margin: 10px 0 0;
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-ink);
}


/* ═══ five pages · acid-judgements ═════════════════════════════════════
   ONE family, five placements — c6-01 `#s-hazard`, c6-02 `#s-choose`, c6-03
   `#s-uses`, c6-04 `#s-test`, c6-07 `#s-uses`. Design draws the identical
   component on all five and these rules are measured off `#s-hazard`; the
   other four match it to the pixel.

   Two of the five ask yes/no and three ask a three-way choice. Nothing below
   depends on how many buttons a card carries — the row wraps. */

.ks3-ajudge {
  margin-top: 18px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.ks3-ajudge .ks3-ajudge-card {
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-ajudge .ks3-ajudge-q {
  margin: 0;
  font-size: 19px;
  line-height: 1.55;
  color: var(--ks3-ink);
}
.ks3-ajudge .ks3-ajudge-opts {
  margin-top: 12px;
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
.ks3-ajudge .ks3-ajudge-reveal {
  margin-top: 12px;
  padding: 14px 16px;
  border-radius: 14px;
  background: var(--ks3-card);
  border: 2px solid var(--ks3-rule);
}
.ks3-ajudge .ks3-ajudge-reveal p {
  margin: 0;
  font-size: 17px;
  line-height: 1.55;
  color: var(--ks3-ink-body);
}


/* ═══ c6-02 · #s-bench · ph-bench ══════════════════════════════════════
   Six sample tabs, one inset panel, a band gate and an ink-dark result.

   ⚑ THE RESULT PANEL IS INK-DARK and it is Design's own (page line 155). It
   is the first C-unit readout on ink, which is what makes `--ks3-data`
   spendable in this block at all — see the header. */

.ks3-phbench .ks3-phbench-tabs {
  margin-top: 18px;
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
.ks3-phbench .ks3-phbench-panel {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-phbench .ks3-phbench-name {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 21px;
  line-height: 1.3;
  color: var(--ks3-ink);
}
.ks3-phbench .ks3-phbench-setup {
  margin: 8px 0 0;
  font-size: 18px;
  line-height: 1.6;
  color: var(--ks3-ink-body);
}

/* The gate. It STAYS on screen after it is answered — see `wirePhBench` —
   so it needs no "spent" treatment of its own: the pressed band keeps the
   segmented control's ordinary treatment and its siblings take the
   platform's `[disabled]` dim. */
.ks3-phbench .ks3-phbench-guess { margin-top: 14px; }
.ks3-phbench .ks3-phbench-guess .ks3-commit {
  margin: 0;
  font-size: 19px;
  font-weight: 700;
  color: var(--ks3-ink);
}
.ks3-phbench .ks3-phbench-bands {
  margin-top: 12px;
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}

.ks3-phbench .ks3-phbench-result {
  margin-top: 16px;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ink);
  color: var(--ks3-on-dark);
}
.ks3-phbench .ks3-phbench-row {
  display: flex;
  gap: 14px;
  align-items: center;
  flex-wrap: wrap;
}
/* ⚑ THE CHIP TAKES ITS GROUND FROM THE MARKUP — one of the fifteen chart
   colours, written in by `ks3_art/c6.py`. See the header for the ruling. The
   ink and the border are fixed here precisely BECAUSE the ground is not: a
   near-white that clears all fifteen, including the yellow at pH 6.
   And the chip always prints its number, which is the condition the ruling
   is conditional on. */
.ks3-phbench .ks3-phbench-chip {
  display: grid;
  place-items: center;
  min-width: 96px;
  padding: 12px 16px;
  border-radius: 14px;
  border: 2px solid #FFFDF8;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 22px;
  color: #FFFDF8;
}
.ks3-phbench .ks3-phbench-verdict {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 20px;
  line-height: 1.25;
  color: var(--ks3-on-dark);
}
/* The litmus line is a READING and it is on ink, so it takes `--ks3-data`.
   Category and selection, never a mark. */
.ks3-phbench .ks3-phbench-litmus {
  margin: 4px 0 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ks3-data);
}
.ks3-phbench .ks3-phbench-why {
  margin: 12px 0 0;
  font-size: 17px;
  line-height: 1.6;
  color: var(--ks3-on-dark-body);
}


/* ═══ c6-03 · #s-titrate · titration-dial ══════════════════════════════
   A beaker, a live reading and a twenty-one bar trace, side by side above
   1000px and stacked below it.

   ⚠️ THE GRID COLLAPSES RATHER THAN SHRINKING. Design draws
   `minmax(0, 1fr) minmax(0, 22rem)`; at 390px that second column is wider
   than the viewport, so the trace would be a 3px-wide bar chart. One query,
   one stack. */

.ks3-titr .ks3-titr-grid {
  margin-top: 20px;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 22px;
  align-items: start;
}
@media (min-width: 1000px) {
  .ks3-titr .ks3-titr-grid {
    grid-template-columns: minmax(0, 1fr) minmax(0, 22rem);
  }
}

.ks3-titr .ks3-titr-beakerpanel {
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-titr .ks3-titr-row {
  display: flex;
  gap: 18px;
  align-items: center;
  flex-wrap: wrap;
}
/* ⚑ The beaker's ground is one of the fifteen chart colours and arrives
   inline. It is `aria-hidden` and it is never the only carrier of the
   reading — the pH prints beside it at 34px. */
.ks3-titr .ks3-titr-beaker {
  display: block;
  width: 96px;
  height: 96px;
  flex: 0 0 96px;
  border-radius: 18px;
  border: 2px solid var(--ks3-ink);
}
.ks3-titr .ks3-titr-ph {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 34px;
  line-height: 1;
  color: var(--ks3-ink);
}
.ks3-titr .ks3-titr-count {
  margin: 6px 0 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-titr .ks3-titr-state {
  margin: 8px 0 0;
  font-size: 18px;
  font-weight: 700;
  color: var(--ks3-ink);
}
.ks3-titr .ks3-titr-note {
  margin: 14px 0 0;
  font-size: 18px;
  line-height: 1.6;
  color: var(--ks3-ink-body);
}
.ks3-titr .ks3-titr-controls {
  margin-top: 16px;
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}

.ks3-titr .ks3-titr-tracepanel {
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
}
.ks3-titr .ks3-titr-tracelabel,
.ks3-titr .ks3-titr-axis {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-titr .ks3-titr-axis { margin-top: 10px; }
.ks3-titr .ks3-titr-trace {
  margin-top: 12px;
  display: flex;
  align-items: flex-end;
  gap: 3px;
  height: 150px;
}
/* ⚠️ THE BAR'S HEIGHT IS AN INLINE PERCENTAGE COMPUTED AT BUILD TIME from the
   curve, so the resting render already draws the whole shape and nothing here
   has to know a pH. `data-on` is the only thing the wiring moves: a drop that
   has been reached is drawn, one that has not is a dormant slot. Both states
   are visible, so the trace shows the shape of the run rather than appearing
   out of nothing. */
.ks3-titr .ks3-titr-bar {
  flex: 1 1 0;
  min-width: 0;
  border-radius: 3px 3px 0 0;
  background: var(--ks3-band);
  border: 1px solid transparent;
}
.ks3-titr .ks3-titr-bar[data-on="1"] {
  background: var(--ks3-accent);
  border-color: var(--ks3-ink);
}

.ks3-titr .ks3-titr-close {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
}
.ks3-titr .ks3-titr-closetitle {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 22px;
  line-height: 1.25;
  color: var(--ks3-ink);
}
.ks3-titr .ks3-titr-close p + p { margin-top: 12px; }
.ks3-titr .ks3-titr-close p {
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-ink);
}


/* ═══ c6-04 · #s-bench · acid-metal-grid ═══════════════════════════════
   A 4 x 2 table that scrolls sideways, and an ink-dark readout under it.

   ⚠️ NOT `.ks3-rgrid-*`. `ks3_art/c5.py` owns `reactivity-grid` and its
   `ks3-rgrid-block`; this is C6's own family with its own prefix. Two
   families wearing one shell class is MRB-279's gate, and it fails silently:
   both pages render and only a browser open on the other one would show it.

   ⚠️ `position: relative` ON THE SCROLLER. See the block header — this is the
   C5 defect, pre-empted. */

.ks3-amgrid .ks3-amgrid-scroll {
  position: relative;
  margin-top: 20px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.ks3-amgrid .ks3-amgrid-table {
  border-collapse: collapse;
  min-width: 32rem;
}
.ks3-amgrid .ks3-amgrid-table th {
  padding: 10px 12px;
  text-align: left;
}
.ks3-amgrid .ks3-amgrid-table thead th {
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-amgrid .ks3-amgrid-table tbody th {
  font-size: 18px;
  font-weight: 700;
  color: var(--ks3-ink);
  border-top: 2px solid var(--ks3-rule);
}
.ks3-amgrid .ks3-amgrid-table td {
  padding: 6px;
  border-top: 2px solid var(--ks3-rule);
}
/* A cell is a control and it clears the 44px tap target. `data-run` is a
   STATE — this tube has been run — and `aria-pressed` is the selection.
   Neither is a mark: "fizzes" and "none" are observations. */
.ks3-amgrid .ks3-amgrid-cell {
  font: inherit;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  min-width: 6.4rem;
  min-height: 44px;
  padding: 8px 10px;
  border-radius: 12px;
  cursor: pointer;
  border: 2px solid var(--ks3-ink);
  background: var(--ks3-card);
  color: var(--ks3-ink-ghost);
}
.ks3-amgrid .ks3-amgrid-cell[data-run="1"] {
  background: var(--ks3-band);
  color: var(--ks3-ink);
}
.ks3-amgrid .ks3-amgrid-cell[aria-pressed="true"] {
  border-color: var(--ks3-accent);
  box-shadow: 0 0 0 4px var(--ks3-accent-tint);
}

.ks3-amgrid .ks3-amgrid-panel {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-amgrid .ks3-amgrid-title {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 21px;
  line-height: 1.3;
  color: var(--ks3-ink);
}
.ks3-amgrid .ks3-amgrid-setup {
  margin: 8px 0 0;
  font-size: 18px;
  line-height: 1.6;
  color: var(--ks3-ink-body);
}
.ks3-amgrid .ks3-amgrid-predict { margin-top: 14px; }
.ks3-amgrid .ks3-amgrid-predict .ks3-commit {
  margin: 0;
  font-size: 19px;
  font-weight: 700;
  color: var(--ks3-ink);
}
.ks3-amgrid .ks3-amgrid-predictopts {
  margin-top: 12px;
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
.ks3-amgrid .ks3-amgrid-result {
  margin-top: 16px;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ink);
  color: var(--ks3-on-dark);
}
.ks3-amgrid .ks3-amgrid-verdict {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 20px;
  line-height: 1.3;
  color: var(--ks3-on-dark);
}
.ks3-amgrid .ks3-amgrid-why {
  margin: 10px 0 0;
  font-size: 17px;
  line-height: 1.6;
  color: var(--ks3-on-dark-body);
}
/* The word equation. `currentColor` on the drawn arrow means it inks itself
   from this rule rather than from a second opinion about the panel. */
.ks3-amgrid .ks3-amgrid-eq {
  margin: 12px 0 0;
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 19px;
  color: var(--ks3-on-dark);
}
.ks3-amgrid .ks3-amgrid-arrow { flex: 0 0 auto; }

.ks3-amgrid .ks3-amgrid-close {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
}
.ks3-amgrid .ks3-amgrid-closetitle {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 22px;
  line-height: 1.25;
  color: var(--ks3-ink);
}
.ks3-amgrid .ks3-amgrid-close p + p { margin-top: 12px; }
.ks3-amgrid .ks3-amgrid-close p {
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-ink);
}


/* ═══ c6-06 · #s-name · salt-namer ═════════════════════════════════════
   Two dials — three acids, four bases — and an ink-dark answer panel. */

.ks3-namer .ks3-namer-dials {
  margin-top: 18px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 18px;
}
.ks3-namer .ks3-namer-diallabel {
  margin: 0 0 10px;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-namer .ks3-namer-dialrow {
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
.ks3-namer .ks3-namer-panel {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-namer .ks3-namer-title {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 21px;
  line-height: 1.3;
  color: var(--ks3-ink);
}
.ks3-namer .ks3-namer-ask { margin-top: 14px; }
.ks3-namer .ks3-namer-ask .ks3-commit {
  margin: 0;
  font-size: 19px;
  font-weight: 700;
  color: var(--ks3-ink);
}
.ks3-namer .ks3-namer-opts {
  margin-top: 12px;
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
.ks3-namer .ks3-namer-result {
  margin-top: 16px;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ink);
  color: var(--ks3-on-dark);
}
.ks3-namer .ks3-namer-salt {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 22px;
  line-height: 1.25;
  color: var(--ks3-on-dark);
}
.ks3-namer .ks3-namer-eq {
  margin: 10px 0 0;
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 18px;
  color: var(--ks3-on-dark);
}
.ks3-namer .ks3-namer-arrow { flex: 0 0 auto; }
.ks3-namer .ks3-namer-note {
  margin: 12px 0 0;
  font-size: 17px;
  line-height: 1.6;
  color: var(--ks3-on-dark-body);
}


/* ═══ c6-06 · #s-method · method-order ═════════════════════════════════
   Six full-width rows, tapped into order, with a numbered badge each.

   ⚖️ NOTHING HERE MARKS. `data-placed` says a step has been put down and the
   accent border says which ones; it is a POSITION, not a verdict, and the
   verdict panel below carries the same six reasons whichever order was
   built. A green tick on a correctly placed step would be the block marking,
   and R3 reserves that for the mastery ladder. */

.ks3-morder .ks3-morder-list {
  list-style: none;
  margin: 20px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 9px;
}
.ks3-morder .ks3-morder-step {
  font: inherit;
  font-size: 17px;
  line-height: 1.5;
  width: 100%;
  display: flex;
  gap: 14px;
  align-items: center;
  text-align: left;
  padding: 14px 16px;
  border-radius: var(--ks3-r-panel);
  cursor: pointer;
  border: 2px solid var(--ks3-option-border);
  background: var(--ks3-card);
  color: var(--ks3-ink);
}
.ks3-morder .ks3-morder-step[data-placed="1"] {
  border-color: var(--ks3-accent);
  background: var(--ks3-accent-tint);
  cursor: default;
}
.ks3-morder .ks3-morder-badge {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  flex: 0 0 30px;
  border-radius: 9px;
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  background: var(--ks3-band);
  color: var(--ks3-ink-ghost);
}
.ks3-morder .ks3-morder-step[data-placed="1"] .ks3-morder-badge {
  background: var(--ks3-accent);
  color: var(--ks3-on-dark);
}
.ks3-morder .ks3-morder-text { text-align: left; }
.ks3-morder .ks3-morder-controls {
  margin-top: 14px;
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}

.ks3-morder .ks3-morder-answerpanel {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
}
/* ⚠️ ONE TONE FOR BOTH VERDICTS. `.ks3-morder-verdict` carries no
   `[data-morder-verdict="right"]` variant and must never grow one: the two
   sentences differ in what they say and not in how they look, which is what
   stops the block marking. */
.ks3-morder .ks3-morder-verdict {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 22px;
  line-height: 1.25;
  color: var(--ks3-ink);
}
.ks3-morder .ks3-morder-answer {
  margin: 14px 0 0;
  padding-left: 22px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.ks3-morder .ks3-morder-answer li {
  font-size: 18px;
  line-height: 1.55;
  color: var(--ks3-ink);
}


/* ═══ c6-07 · #s-bench · catalyst-bench ════════════════════════════════
   Five trial tabs, an inset panel, and an ink-dark result with two readouts
   side by side.

   ⚑ THE TWO FIGURES TAKE `--ks3-data`. They are readings on an ink panel and
   they are the pair the whole lesson turns on: how much oxygen, and how much
   solid came back. `--ks3-data` is category and selection, never a mark, and
   nothing here says which trial was right. */

.ks3-catb .ks3-catb-tabs {
  margin-top: 18px;
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
.ks3-catb .ks3-catb-panel {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-catb .ks3-catb-title {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 21px;
  line-height: 1.3;
  color: var(--ks3-ink);
}
.ks3-catb .ks3-catb-setup {
  margin: 8px 0 0;
  font-size: 18px;
  line-height: 1.6;
  color: var(--ks3-ink-body);
}
.ks3-catb .ks3-catb-predict { margin-top: 14px; }
.ks3-catb .ks3-catb-predict .ks3-commit {
  margin: 0;
  font-size: 19px;
  font-weight: 700;
  color: var(--ks3-ink);
}
.ks3-catb .ks3-catb-predictopts {
  margin-top: 12px;
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
.ks3-catb .ks3-catb-result {
  margin-top: 16px;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-ink);
  color: var(--ks3-on-dark);
}
.ks3-catb .ks3-catb-verdict {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 20px;
  line-height: 1.3;
  color: var(--ks3-on-dark);
}
.ks3-catb .ks3-catb-tiles {
  margin: 12px 0 0;
  display: flex;
  gap: 20px;
  flex-wrap: wrap;
}
.ks3-catb .ks3-catb-tilelabel {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-on-dark-body);
}
.ks3-catb .ks3-catb-value {
  margin: 2px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 26px;
  line-height: 1;
  color: var(--ks3-data);
}
/* ⚑ THE ILLUSTRATIVE-FIGURES NOTE (NOTES-C6 §5 flags 13 and 16). It sits
   directly under the two readouts rather than in a footnote, because a figure
   a student can quote has to carry its own caveat where it is quoted. Muted
   and small — it is a qualification on the numbers, not a second finding. */
.ks3-catb .ks3-catb-figsnote {
  margin: 10px 0 0;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  line-height: 1.5;
  color: var(--ks3-on-dark-body);
}
.ks3-catb .ks3-catb-why {
  margin: 12px 0 0;
  font-size: 17px;
  line-height: 1.6;
  color: var(--ks3-on-dark-body);
}
.ks3-catb .ks3-catb-close {
  margin-top: 20px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
}
.ks3-catb .ks3-catb-closetitle {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 22px;
  line-height: 1.25;
  color: var(--ks3-ink);
}
.ks3-catb .ks3-catb-close p + p { margin-top: 12px; }
.ks3-catb .ks3-catb-close p {
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-ink);
}


/* ═══ c6-05 · #s-rig · step-rig ════════════════════════════════════════
   Five steps in one column. Every step's INSTRUCTION is on the page from the
   start and only its REASON is staged, so the list never changes height
   under the student's finger by more than the paragraph they just asked for.

   ⚠️ `data-open` IS A STATE, NOT A MARK. There is no right answer anywhere in
   this block — it is a method — so the only thing the border and the ground
   say is "you have been here", which is what `data-open` says everywhere else
   in the key stage. `--ks3-ok` and `--ks3-danger` do not appear.

   Measured off Design's own composed style strings: 16px 18px padding,
   `--ks3-r-panel`, a 2px border moving from `--ks3-rule` to `--ks3-ink` and a
   ground moving from `--ks3-inset` to `--ks3-card` as each step opens; the
   number is 14px mono at `flex: 0 0 24px`, the instruction is display 700 at
   20px, and the reason is 18px body indented to clear the number. */

.ks3-srig .ks3-srig-list {
  margin: 20px 0 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.ks3-srig .ks3-srig-step {
  padding: 16px 18px;
  border-radius: var(--ks3-r-panel);
  border: 2px solid var(--ks3-rule);
  background: var(--ks3-inset);
}
.ks3-srig .ks3-srig-step[data-open="1"] {
  border-color: var(--ks3-ink);
  background: var(--ks3-card);
}
.ks3-srig .ks3-srig-head {
  display: flex;
  gap: 14px;
  align-items: baseline;
}
.ks3-srig .ks3-srig-num {
  font-family: var(--ks3-font-mono);
  font-size: 14px;
  font-weight: 500;
  color: var(--ks3-ink-muted);
  flex: 0 0 24px;
}
.ks3-srig .ks3-srig-what {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 20px;
  line-height: 1.3;
  color: var(--ks3-ink);
}
/* Indented to the instruction's own text edge — 24px of number plus the
   14px gap — so the reason reads as belonging to the line above it rather
   than as a sixth step. */
.ks3-srig .ks3-srig-why {
  margin: 10px 0 0 38px;
  font-size: 18px;
  line-height: 1.6;
  color: var(--ks3-ink-body);
}
.ks3-srig .ks3-srig-controls {
  margin-top: 16px;
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
  align-items: center;
}


/* ═══ c6-05 · #s-bench · solid-sorter ══════════════════════════════════
   Four solids in one column, each a name, a what-it-looks-like tag, two
   buttons and a reveal. `bottle-sorter`'s measurements to the pixel, because
   Design draws the same card on both pages — the difference is that this one
   carries a word equation inside the reveal for the rows that react.

   The `looks` tag is doing the same work `bottle-sorter`'s where-it-lives tag
   does: three of the four look identical in the bottle and the fourth is
   green and fizzes hardest, so the tag is the evidence the bench takes away
   rather than a caption. */

.ks3-solid .ks3-solid-cards {
  margin-top: 18px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.ks3-solid .ks3-solid-card {
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  border: 2px solid var(--ks3-ink);
  background: var(--ks3-inset);
}
.ks3-solid .ks3-solid-card[data-open="1"] { background: var(--ks3-card); }

.ks3-solid .ks3-solid-head {
  display: flex;
  gap: 16px;
  align-items: baseline;
  flex-wrap: wrap;
  justify-content: space-between;
}
.ks3-solid .ks3-solid-name {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 800;
  font-size: 21px;
  line-height: 1.25;
  color: var(--ks3-ink);
}
.ks3-solid .ks3-solid-looks {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-solid .ks3-solid-opts {
  margin-top: 12px;
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
}
.ks3-solid .ks3-solid-reveal {
  margin-top: 12px;
  padding: 14px 16px;
  border-radius: 14px;
  background: var(--ks3-card);
  border: 2px solid var(--ks3-rule);
}
.ks3-solid .ks3-solid-verdict {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 18px;
  color: var(--ks3-ink);
}
.ks3-solid .ks3-solid-why {
  margin: 7px 0 0;
  font-size: 17px;
  line-height: 1.55;
  color: var(--ks3-ink-body);
}
/* The word equation, and it WRAPS rather than scrolls: "hydrochloric acid +
   sodium hydrogencarbonate makes sodium chloride + water + carbon dioxide"
   is the longest line in the unit and at 390px it cannot be one row. Wrapping
   keeps every part on the ground it belongs to; a sideways scroller here
   would be a second scroll direction inside a card. */
.ks3-solid .ks3-solid-eq {
  margin: 10px 0 0;
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 17px;
  color: var(--ks3-ink);
}
.ks3-solid .ks3-solid-arrow { flex: 0 0 auto; }


/* ═══ THE `[hidden]` GUARD ═════════════════════════════════════════════
   ⚠️ LOAD-BEARING IN THIS BLOCK, not precautionary. An author `display`
   beats the UA stylesheet's `[hidden] { display: none }` at ANY specificity,
   and several rules above set `display` on elements that C6 emits hidden —
   `.ks3-titr-close` (flex children via `p + p` is fine, but the panel itself
   is grid-adjacent), `.ks3-catb-tiles`' parent, `.ks3-namer-opts`,
   `.ks3-amgrid-predictopts` and every reveal container. Without this line, a
   single tidy-up that gives one of them `display: flex` would put all six pH
   results, all eight grid results and all twelve naming answers on screen at
   once, and the CSS would still read correctly.

   Scoped to C6's own components so it changes nothing anywhere else. */
.ks3-bottle [hidden],
.ks3-ajudge [hidden],
.ks3-phbench [hidden],
.ks3-titr [hidden],
.ks3-amgrid [hidden],
.ks3-namer [hidden],
.ks3-morder [hidden],
.ks3-catb [hidden],
.ks3-srig [hidden],
.ks3-solid [hidden] { display: none !important; }

/* ═══ END C6 ═══ */

/* ═══ BEGIN C8 ═══════════════════════════════════════════════════════════
   C8's instrument styling — *The periodic table*. A PURE APPEND at EOF —
   every byte above this marker is unchanged, which is the one conflict shape
   that resolves mechanically when another lane merges.

   Every value below is measured off Claude Design's approved pages in
   `docs/ks3/design-reference/c8/`, which carry their real design in inline
   `style` attributes and in the style strings `lessonVals()` composes. Where
   the page and the engine disagreed, the page won, except where a
   disagreement is written out below as a ruling.

   ALL ELEVEN INSTRUMENT SECTIONS ARE LIGHT. `#s-bench` on c8-01, `#s-gap` and
   `#s-rules` on c8-02, `#s-table` and `#s-read` on c8-03, `#s-trough` and
   `#s-predict` on c8-04, `#s-grid` on c8-05, `#s-shells`, `#s-file` and
   `#s-uses` on c8-06 are each `class="ks3-block"` with no second class. There
   is no ink-dark practical block anywhere in C8, exactly as there is none in
   C3, C4, C5 or C7. A later pass that "restores" a practical shell here would
   paint an instrument on a ground Design did not draw it on and resolve every
   text token inside it wrong.

   ⚠️ EVERY TEXT RULE IS SELF-SCOPED AT (0,2,0) — `.ks3-prop .ks3-prop-why`,
   never a bare `.ks3-prop-why`. A bare component class is (0,1,0) and loses to
   `.ks3-dark p` at (0,1,1) the day anything moves one of these instruments
   onto ink, and it loses SILENTLY: the CSS reads correctly and the sentence
   ships at about 1.2:1. C1's, C3's, C4's, C5's and C7's blocks all say this
   and it is said again because it is the rule most easily lost in a merge.

   ⚠️ `--ks3-data` IS NOT SPENT ANYWHERE IN THIS UNIT. It is an ON-INK token
   (the C3 ruling), and every C8 instrument sits on a light ground, so there is
   nothing here for it to colour. Selection and category state is carried by
   `--ks3-accent` / `--ks3-accent-tint` with an `--ks3-ink` outline, and EVERY
   selectable cell also carries a WORD — "reacts", "none", "?" — so colour is
   never the only channel (audit law 9 / R2).

   ⚠️ CONTAINMENT, NOT CLIPPING. Two horizontal scrollers exist in this unit:
   the twenty-element periodic table on c8-03 and the nine-cell halogen grid on
   c8-05. Both carry `position: relative` so that anything absolutely
   positioned inside them is contained rather than escaping and widening the
   document — the C5 trap. Neither is `overflow: hidden`: a table a student
   cannot scroll to the end of is a table with elements they cannot reach.
   ═══ */

/* ── shared shells ─────────────────────────────────────────────────────
   Seven families, seven shell classes, all light. Every one is checked free
   against every KIND_SHELL value in `ks3_art` before minting (MRB-279). */
.ks3-prop-block,
.ks3-gapfill-block,
.ks3-pcards-block,
.ks3-tread-block,
.ks3-trough-block,
.ks3-hgrid-block,
.ks3-shell-block,
.ks3-oxb-block { --c8-gap: 12px; }

/* ── c8-01 · property-sorter ───────────────────────────────────────────── */
.ks3-prop { display: flex; flex-direction: column; gap: var(--c8-gap); }
.ks3-prop .ks3-prop-item {
  padding: 18px 20px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset); border: 2px solid var(--ks3-ink);
}
.ks3-prop .ks3-prop-head {
  display: flex; gap: 16px; align-items: baseline;
  flex-wrap: wrap; justify-content: space-between;
}
.ks3-prop .ks3-prop-code {
  margin: 0; font-family: var(--ks3-font-display);
  font-weight: 800; font-size: 21px; line-height: 1.25; color: var(--ks3-ink);
}
.ks3-prop .ks3-prop-state {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; letter-spacing: .05em; text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-prop .ks3-prop-facts {
  margin: 10px 0 0; padding-left: 20px; display: flex;
  flex-direction: column; gap: 4px;
  font-size: 17px; line-height: 1.5; color: var(--ks3-ink-body);
}
.ks3-prop .ks3-prop-opts,
.ks3-gapf .ks3-gapf-opts,
.ks3-pcards .ks3-pcards-opts {
  margin-top: 12px; display: flex; gap: 9px; flex-wrap: wrap;
}
.ks3-prop .ks3-prop-reveal,
.ks3-pcards .ks3-pcards-answer,
.ks3-gapf .ks3-gapf-answer {
  margin-top: 12px; padding: 14px 16px; border-radius: 14px;
  background: var(--ks3-card); border: 2px solid var(--ks3-rule);
}
.ks3-prop .ks3-prop-verdict {
  margin: 0; font-family: var(--ks3-font-display);
  font-weight: 700; font-size: 18px; color: var(--ks3-ink);
}
.ks3-prop .ks3-prop-why {
  margin: 7px 0 0; font-size: 17px; line-height: 1.55;
  color: var(--ks3-ink-body);
}

/* ── c8-02 · gap-filler ────────────────────────────────────────────────
   ⚠️ THE PERIOD-1-STYLE HOLES ARE DRAWN, NOT COLLAPSED. Four corners of the
   3 x 3 are `empty` spacers and the centre is the `blank` gap; they are
   different things and are styled differently on purpose. The gap is a dashed
   square with a question mark in it — the subject of the lesson — and a
   spacer is nothing at all. Collapsing either would destroy the plus-shape
   that makes the prediction a READING off four neighbours. */
.ks3-gapf .ks3-gapf-grid {
  display: grid; grid-template-columns: repeat(var(--gapf-cols, 3), 1fr);
  gap: 10px; margin-bottom: 20px;
}
.ks3-gapf .ks3-gapf-cell {
  padding: 14px 12px; border-radius: 14px; text-align: center;
  background: var(--ks3-card); border: 2px solid var(--ks3-ink);
}
.ks3-gapf .ks3-gapf-spacer { border: 0; background: none; }
.ks3-gapf .ks3-gapf-blank {
  background: transparent; border: 2px dashed var(--ks3-ink-muted);
  display: flex; align-items: center; justify-content: center;
  min-height: 88px;
}
.ks3-gapf .ks3-gapf-qm {
  font-family: var(--ks3-font-display); font-weight: 800;
  font-size: 30px; color: var(--ks3-accent-text);
}
.ks3-gapf .ks3-gapf-sym {
  margin: 0; font-family: var(--ks3-font-display); font-weight: 800;
  font-size: 22px; color: var(--ks3-ink);
}
.ks3-gapf .ks3-gapf-name {
  margin: 2px 0 0; font-size: 15px; color: var(--ks3-ink-body);
}
.ks3-gapf .ks3-gapf-data {
  margin: 4px 0 0; font-family: var(--ks3-font-mono);
  font-size: 12px; color: var(--ks3-ink-muted);
}
.ks3-gapf .ks3-gapf-cards,
.ks3-pcards { display: flex; flex-direction: column; gap: 14px; }
.ks3-gapf .ks3-gapf-tablewrap { margin-top: 20px; overflow-x: auto; }
.ks3-gapf .ks3-gapf-table { border-collapse: collapse; width: 100%; }
.ks3-gapf .ks3-gapf-table caption {
  text-align: left; font-family: var(--ks3-font-display); font-weight: 700;
  font-size: 19px; padding-bottom: 10px; color: var(--ks3-ink);
}
.ks3-gapf .ks3-gapf-table th,
.ks3-gapf .ks3-gapf-table td {
  padding: 10px 14px; text-align: left; font-size: 16px;
  border-top: 2px solid var(--ks3-rule); color: var(--ks3-ink-body);
}
.ks3-gapf .ks3-gapf-table thead th {
  font-family: var(--ks3-font-mono); font-size: 12px; font-weight: 500;
  letter-spacing: .06em; text-transform: uppercase;
  color: var(--ks3-ink-muted); border-top: 0;
}
.ks3-gapf .ks3-gapf-table tbody th { color: var(--ks3-ink); font-weight: 700; }

/* ── c8-02/03/04/06 · predict-cards (five placements, one family) ─────── */
.ks3-pcards .ks3-pcards-lead {
  margin: 0 0 4px; font-size: 19px; line-height: 1.6;
  max-width: var(--ks3-measure); color: var(--ks3-ink-body);
}
.ks3-pcards .ks3-pcards-card,
.ks3-gapf .ks3-gapf-card {
  padding: 20px 22px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset); border: 2px solid var(--ks3-ink);
}
.ks3-pcards .ks3-pcards-tag {
  margin: 0 0 8px; font-family: var(--ks3-font-mono); font-size: 12px;
  font-weight: 500; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ks3-accent-text);
}
.ks3-pcards .ks3-pcards-q,
.ks3-gapf .ks3-gapf-q {
  margin: 0; font-size: 19px; line-height: 1.55; color: var(--ks3-ink);
}
.ks3-pcards .ks3-pcards-answer,
.ks3-gapf .ks3-gapf-answer {
  font-size: 17px; line-height: 1.55; color: var(--ks3-ink-body);
}

/* ── c8-03 · table-reader ──────────────────────────────────────────────
   ⚠️ `position: relative` on the scroller is CONTAINMENT, NOT CLIPPING —
   the C5 trap, where 65 absolutely positioned screen-reader labels escaped a
   scroller and widened the document. Nothing here is absolutely positioned
   today; the rule is here so that the next thing added inside it cannot
   escape. `overflow-x: auto`, never `hidden`: a table a student cannot scroll
   to the end of is a table with elements they cannot reach. */
.ks3-tread .ks3-tread-scroll {
  position: relative; overflow-x: auto; padding-bottom: 4px;
}
.ks3-tread .ks3-tread-table {
  border-collapse: separate; border-spacing: 5px; min-width: 40rem;
}
.ks3-tread .ks3-tread-gh,
.ks3-tread .ks3-tread-ph {
  font-family: var(--ks3-font-mono); font-size: 12px; font-weight: 500;
  letter-spacing: .06em; text-transform: uppercase;
  color: var(--ks3-ink-muted); padding: 4px 6px; text-align: center;
}
.ks3-tread .ks3-tread-ph { text-align: right; white-space: nowrap; }
.ks3-tread .ks3-tread-corner { width: 1px; }
/* ⚠️ A null cell is DRAWN as a dashed transparent box, never collapsed.
   NOTES-C8 §7: the SHAPE OF THE GAP is part of what the table teaches —
   hydrogen and helium sit at opposite ends of period 1 with six holes between
   them, and a tidied row would teach that they are neighbours. */
.ks3-tread .ks3-tread-empty {
  border: 2px dashed var(--ks3-rule); border-radius: 10px;
  min-width: 4.2rem; height: 62px; background: transparent;
}
.ks3-tread .ks3-tread-cell {
  font: inherit; display: flex; flex-direction: column; gap: 1px;
  align-items: center; justify-content: center;
  min-width: 4.2rem; min-height: 62px; padding: 6px 8px;
  border-radius: 10px; cursor: pointer;
  background: var(--ks3-card); border: 2px solid var(--ks3-ink);
  color: var(--ks3-ink);
}
.ks3-tread .ks3-tread-cell[aria-pressed="true"] {
  background: var(--ks3-accent-tint); border-color: var(--ks3-accent);
  box-shadow: 0 0 0 4px var(--ks3-accent-tint);
}
.ks3-tread .ks3-tread-num {
  font-family: var(--ks3-font-mono); font-size: 10px;
  color: var(--ks3-ink-muted);
}
.ks3-tread .ks3-tread-sym {
  font-family: var(--ks3-font-display); font-weight: 800; font-size: 18px;
}
.ks3-tread .ks3-tread-nm { font-size: 10px; color: var(--ks3-ink-body); }
.ks3-tread .ks3-tread-readout {
  margin-top: 18px; padding: 18px 20px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset); border: 2px solid var(--ks3-ink);
}
.ks3-tread .ks3-tread-rest {
  margin: 0; font-size: 17px; color: var(--ks3-ink-muted);
}
.ks3-tread .ks3-tread-addr {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 13px;
  letter-spacing: .04em; color: var(--ks3-accent-text);
}
.ks3-tread .ks3-tread-fam {
  margin: 6px 0 0; font-family: var(--ks3-font-display); font-weight: 700;
  font-size: 18px; line-height: 1.3; color: var(--ks3-ink);
}
.ks3-tread .ks3-tread-note {
  margin: 7px 0 0; font-size: 17px; line-height: 1.55;
  color: var(--ks3-ink-body);
}

/* ── c8-04 · water-trough ─────────────────────────────────────────────── */
.ks3-trough .ks3-trough-picker,
.ks3-trough .ks3-trough-predict { display: flex; gap: 9px; flex-wrap: wrap; }
.ks3-trough .ks3-trough-predict {
  margin-top: 14px; flex-direction: column; align-items: flex-start;
}
.ks3-trough .ks3-trough-prompt {
  margin: 0 0 8px; font-size: 18px; color: var(--ks3-ink-body);
}
.ks3-trough .ks3-trough-stage {
  margin-top: 18px; padding: 20px 22px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset); border: 2px solid var(--ks3-ink);
}
.ks3-trough .ks3-trough-rest {
  margin: 0; font-size: 17px; color: var(--ks3-ink-muted);
}
.ks3-trough .ks3-trough-headline {
  margin: 0; font-family: var(--ks3-font-display); font-weight: 800;
  font-size: 21px; line-height: 1.25; color: var(--ks3-ink);
}
.ks3-trough .ks3-trough-data {
  margin: 6px 0 0; font-family: var(--ks3-font-mono); font-size: 13px;
  color: var(--ks3-ink-muted);
}
.ks3-trough .ks3-trough-obs {
  margin: 12px 0 0; padding-left: 20px; display: flex;
  flex-direction: column; gap: 5px;
  font-size: 17px; line-height: 1.5; color: var(--ks3-ink-body);
}
.ks3-trough .ks3-trough-eq {
  margin: 12px 0 0; padding: 10px 14px; border-radius: 12px;
  background: var(--ks3-card); border: 2px solid var(--ks3-rule);
  font-family: var(--ks3-font-mono); font-size: 14px; color: var(--ks3-ink);
}

/* ── c8-05 · halogen-grid ──────────────────────────────────────────────
   ⚠️ Same containment rule as the periodic table above, and for the same
   reason. Every cell carries a WORD as well as a colour. */
.ks3-hgrid .ks3-hgrid-scroll {
  position: relative; overflow-x: auto; padding-bottom: 4px;
}
.ks3-hgrid .ks3-hgrid-table {
  border-collapse: separate; border-spacing: 6px; min-width: 30rem;
}
.ks3-hgrid .ks3-hgrid-table th {
  font-family: var(--ks3-font-mono); font-size: 12px; font-weight: 500;
  letter-spacing: .05em; color: var(--ks3-ink-muted);
  padding: 6px 8px; text-align: left;
}
.ks3-hgrid .ks3-hgrid-corner { width: 1px; }
.ks3-hgrid .ks3-hgrid-cell {
  font: inherit; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; min-width: 5.6rem; min-height: 44px; padding: 8px 10px;
  border-radius: 12px; cursor: pointer;
  background: var(--ks3-card); border: 2px solid var(--ks3-ink);
  color: var(--ks3-ink-ghost);
}
.ks3-hgrid .ks3-hgrid-cell[aria-pressed="true"] {
  background: var(--ks3-accent-tint); border-color: var(--ks3-accent);
  color: var(--ks3-ink); box-shadow: 0 0 0 4px var(--ks3-accent-tint);
}
.ks3-hgrid .ks3-hgrid-predict {
  margin-top: 16px; display: flex; flex-direction: column;
  align-items: flex-start; gap: 9px;
}
.ks3-hgrid .ks3-hgrid-prompt {
  margin: 0; font-size: 18px; color: var(--ks3-ink-body);
}
.ks3-hgrid .ks3-hgrid-readout {
  margin-top: 18px; padding: 20px 22px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset); border: 2px solid var(--ks3-ink);
}
.ks3-hgrid .ks3-hgrid-rest {
  margin: 0; font-size: 17px; color: var(--ks3-ink-muted);
}
.ks3-hgrid .ks3-hgrid-title {
  margin: 0; font-family: var(--ks3-font-display); font-weight: 800;
  font-size: 20px; line-height: 1.25; color: var(--ks3-ink);
}
.ks3-hgrid .ks3-hgrid-setup {
  margin: 7px 0 0; font-size: 16px; line-height: 1.5;
  color: var(--ks3-ink-muted);
}
.ks3-hgrid .ks3-hgrid-verdict {
  margin: 12px 0 0; font-family: var(--ks3-font-display); font-weight: 700;
  font-size: 18px; color: var(--ks3-accent-text);
}
.ks3-hgrid .ks3-hgrid-obs {
  margin: 7px 0 0; font-size: 17px; line-height: 1.55;
  color: var(--ks3-ink-body);
}
.ks3-hgrid .ks3-hgrid-eq {
  margin: 12px 0 0; padding: 10px 14px; border-radius: 12px;
  background: var(--ks3-card); border: 2px solid var(--ks3-rule);
  font-family: var(--ks3-font-mono); font-size: 14px; color: var(--ks3-ink);
}

/* ── c8-06 · shell-strip ───────────────────────────────────────────────
   ⚠️ ONE ROW IS ONE OUTER SHELL. The strip stops short of shells-within-
   shells deliberately (NOTES-C8 §4) and is to be left there: drawing inner
   shells would make the picture right and the lesson unreadable, and "group
   number = outer electrons" is the whole of what c8-03 and c8-06 claim. */
.ks3-shel { display: flex; flex-direction: column; gap: var(--c8-gap); }
.ks3-shel .ks3-shel-row {
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset); border: 2px solid var(--ks3-ink);
}
.ks3-shel .ks3-shel-toggle {
  font: inherit; display: block; width: 100%; text-align: left;
  padding: 18px 20px; min-height: 44px; cursor: pointer;
  background: none; border: 0; border-radius: var(--ks3-r-panel);
  color: var(--ks3-ink);
}
.ks3-shel .ks3-shel-head {
  display: flex; gap: 14px; align-items: baseline;
  flex-wrap: wrap; justify-content: space-between;
}
.ks3-shel .ks3-shel-title {
  font-family: var(--ks3-font-display); font-weight: 800; font-size: 19px;
  line-height: 1.25; color: var(--ks3-ink);
}
.ks3-shel .ks3-shel-tag {
  font-family: var(--ks3-font-mono); font-size: 12px; font-weight: 500;
  letter-spacing: .05em; text-transform: uppercase;
  color: var(--ks3-accent-text);
}
.ks3-shel .ks3-shel-dots {
  display: flex; gap: 7px; margin: 12px 0 0;
}
.ks3-shel .ks3-shel-dot {
  width: 15px; height: 15px; border-radius: 50%;
  border: 2px solid var(--ks3-ink); background: transparent;
}
/* The filled state is a FILL, and the row's own summary says the number in
   words as well — colour is never the only channel (R2 / audit law 9). */
.ks3-shel .ks3-shel-dot[data-on="1"] { background: var(--ks3-accent); }
.ks3-shel .ks3-shel-summary {
  display: block; margin: 12px 0 0; font-size: 17px; line-height: 1.5;
  color: var(--ks3-ink-body);
}
.ks3-shel .ks3-shel-detail {
  margin: 0; padding: 0 20px 18px; font-size: 17px; line-height: 1.55;
  color: var(--ks3-ink-body);
}

/* ── c8-07 · oxide-bench ───────────────────────────────────────────────
   Six tray chips, two beakers, and one ink-dark comparison panel. Measured off
   Design's `c8-07-metal-and-non-metal-oxides.dc.html` — the beaker at
   100 x 128 with a 3px ink wall and no top edge, the fill at 76% of the
   height, the reading at 48px display 800.

   ⚠️ CONTAINMENT, NOT CLIPPING — AND THE GLASS IS THE POSITIONED BOX. The
   water fill and the undissolved solid are both `position: absolute`, so
   `.ks3-oxb-glass` carries `position: relative` and they are contained by it
   rather than escaping to the nearest positioned ancestor and widening the
   document. `overflow: hidden` is on the glass ONLY: it is what makes the
   fill take the rounded bottom of the beaker, and there is nothing inside it
   a student needs to scroll to.

   ⚠️ Z-ORDER IS THE DRAWING. Body, then fill, then solid, then the numeral
   OUTSIDE the glass. It falls out of source order — the solid is emitted after
   the water — so no `z-index` is spent and no later rule can quietly reverse
   it by stacking context.

   ⚠️ THE PH NUMERAL IS `--ks3-accent-text` ON THE LIGHT CARD AND `--ks3-data`
   ON THE INK PANEL, and that is a CORRECTION to Design's markup rather than a
   copy of it. She writes `var(--ks3-data, var(--ks3-accent-text))` in both
   places. `--ks3-data` is DEFINED, so the fallback never fires and the light
   card would take `#8FB7FF` — an ON-INK token, about 2:1 on cream, at the one
   size on this page that the whole instrument is read from. The fallback she
   wrote is the value she meant on that ground, so it is taken directly. On the
   side-by-side panel, which really is on ink, `--ks3-data` is correct and is
   the only place C8 spends it. */
.ks3-oxb { display: flex; flex-direction: column; gap: 18px; }
.ks3-oxb .ks3-oxb-tray { display: flex; gap: 9px; flex-wrap: wrap; }
.ks3-oxb .ks3-oxb-chip {
  display: inline-flex; align-items: center; gap: 9px;
  font-size: 16px; font-weight: 700;
}
.ks3-oxb .ks3-oxb-chipformula {
  font-family: var(--ks3-font-mono); font-size: 14px; font-weight: 500;
  color: var(--ks3-ink-muted);
}
.ks3-oxb .ks3-oxb-chip[aria-pressed="true"] .ks3-oxb-chipformula {
  color: var(--ks3-accent-text);
}
.ks3-oxb .ks3-oxb-beakers { display: flex; gap: 16px; flex-wrap: wrap; }
.ks3-oxb .ks3-oxb-beaker { flex: 1 1 300px; min-width: 0; }
.ks3-oxb .ks3-oxb-panel {
  padding: 20px 22px; border-radius: var(--ks3-r-panel);
  border: 2px solid var(--ks3-ink); background: var(--ks3-card);
}
/* The empty beaker is the INSET ground, so "nothing added yet" and "something
   is in here" differ by more than a sentence. Both still say which they are. */
.ks3-oxb .ks3-oxb-panel[data-oxb-panel$=":empty"] {
  background: var(--ks3-inset);
}
.ks3-oxb .ks3-oxb-head {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 12px; flex-wrap: wrap;
}
.ks3-oxb .ks3-oxb-slot,
.ks3-oxb .ks3-oxb-kind {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; letter-spacing: .08em; text-transform: uppercase;
}
.ks3-oxb .ks3-oxb-slot { color: var(--ks3-ink-muted); }
.ks3-oxb .ks3-oxb-kind { color: var(--ks3-accent-text); }
.ks3-oxb .ks3-oxb-title {
  margin: 7px 0 0; font-family: var(--ks3-font-display); font-weight: 800;
  font-size: 22px; line-height: 1.25; color: var(--ks3-ink);
}
.ks3-oxb .ks3-oxb-row {
  margin-top: 16px; display: flex; gap: 20px; align-items: flex-end;
  flex-wrap: wrap;
}
.ks3-oxb .ks3-oxb-glass {
  position: relative; flex: 0 0 100px; width: 100px; height: 128px;
  border: 3px solid var(--ks3-ink); border-top-width: 0;
  border-radius: 4px 4px 20px 20px; background: var(--ks3-ground);
  overflow: hidden;
}
.ks3-oxb .ks3-oxb-water {
  display: block; position: absolute; left: 0; right: 0; bottom: 0; height: 76%;
}
.ks3-oxb .ks3-oxb-solid {
  display: block; position: absolute; left: 5px; right: 5px; bottom: 0;
  border-radius: 9px 9px 15px 15px;
}
.ks3-oxb .ks3-oxb-phlabel {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 12px;
  font-weight: 500; letter-spacing: .1em; text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-oxb .ks3-oxb-ph {
  margin: 2px 0 0; font-family: var(--ks3-font-display); font-weight: 800;
  font-size: 48px; line-height: 1; letter-spacing: -.03em;
  color: var(--ks3-accent-text);
}
.ks3-oxb .ks3-oxb-ui {
  margin: 10px 0 0; font-size: 17px; line-height: 1.4;
  color: var(--ks3-ink-body);
}
.ks3-oxb .ks3-oxb-residue {
  margin: 14px 0 0; font-size: 17px; line-height: 1.55; font-weight: 600;
  color: var(--ks3-ink);
}
.ks3-oxb .ks3-oxb-note {
  margin: 8px 0 0; font-size: 17px; line-height: 1.55;
  color: var(--ks3-ink-body);
}
/* The one ink-dark surface in C8. Every text rule under it is written at
   (0,2,0) so `.ks3-dark p` can never out-specify a component's own colour. */
.ks3-oxb .ks3-oxb-compare {
  padding: 20px 24px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-ink);
}
.ks3-oxb .ks3-oxb-cmp-eyebrow {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; letter-spacing: .09em; text-transform: uppercase;
  color: var(--ks3-data);
}
.ks3-oxb .ks3-oxb-cmp {
  margin: 8px 0 0; font-size: 19px; line-height: 1.6;
  color: var(--ks3-on-dark-body);
}
.ks3-oxb .ks3-oxb-controls {
  display: flex; gap: 14px; flex-wrap: wrap; align-items: center;
}
.ks3-oxb .ks3-oxb-clear {
  font: inherit; font-size: 16px; font-weight: 700;
  padding: 12px 20px; min-height: 44px;
  border-radius: var(--ks3-r-control); border: 2px solid var(--ks3-ink);
  background: var(--ks3-band); color: var(--ks3-ink); cursor: pointer;
}
.ks3-oxb .ks3-oxb-clear[disabled] {
  background: var(--ks3-row-dim); cursor: default; opacity: .45;
}
.ks3-oxb .ks3-oxb-untested {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 14px;
  font-weight: 500; color: var(--ks3-ink-muted);
}
.ks3-oxb .ks3-oxb-untested-lead { color: var(--ks3-ink-muted); }

/* ── closing panels, shared shape ──────────────────────────────────────── */
.ks3-prop-close,
.ks3-gapf-close,
.ks3-pcards-close,
.ks3-tread-close,
.ks3-trough-close,
.ks3-hgrid-close,
.ks3-oxb-close,
.ks3-shel-close {
  margin-top: 20px; padding: 22px 24px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-band); border: 2px solid var(--ks3-ink);
}
.ks3-prop .ks3-prop-closetitle,
.ks3-oxb .ks3-oxb-closetitle,
.ks3-gapf .ks3-gapf-closetitle,
.ks3-pcards .ks3-pcards-closetitle,
.ks3-tread .ks3-tread-closetitle,
.ks3-trough .ks3-trough-closetitle,
.ks3-hgrid .ks3-hgrid-closetitle,
.ks3-shel .ks3-shel-closetitle {
  margin: 0 0 10px; font-family: var(--ks3-font-display); font-weight: 800;
  font-size: 22px; line-height: 1.25; color: var(--ks3-ink);
}
.ks3-prop-close p,
.ks3-oxb-close p,
.ks3-gapf-close p,
.ks3-pcards-close p,
.ks3-tread-close p,
.ks3-trough-close p,
.ks3-hgrid-close p,
.ks3-shel-close p {
  margin: 0 0 10px; font-size: 19px; line-height: 1.6;
  color: var(--ks3-ink);
}
.ks3-prop-close p:last-child,
.ks3-oxb-close p:last-child,
.ks3-gapf-close p:last-child,
.ks3-pcards-close p:last-child,
.ks3-tread-close p:last-child,
.ks3-trough-close p:last-child,
.ks3-hgrid-close p:last-child,
.ks3-shel-close p:last-child { margin-bottom: 0; }

/* ⚠️ `[hidden]` LAST, AND SELF-SCOPED. Several rules above set `display` on
   elements that are also `hidden` at rest, and `display: flex` on a bare class
   beats the UA's `[hidden] { display: none }` on specificity — so without
   this the halogen grid would show all nine readouts at once, the periodic
   table all twenty, and both would still read as correct CSS. */
.ks3-prop [hidden],
.ks3-oxb [hidden],
.ks3-gapf [hidden],
.ks3-pcards [hidden],
.ks3-tread [hidden],
.ks3-trough [hidden],
.ks3-hgrid [hidden],
.ks3-shel [hidden] { display: none; }

/* ═══ END C8 ═══ */

/* ═══ BEGIN C9 ═══════════════════════════════════════════════════════════
   C9's instrument styling — *Metals and materials*. A PURE APPEND at EOF —
   every byte above this marker is unchanged, which is the one conflict shape
   that resolves mechanically when another lane merges.

   ALL FOUR INSTRUMENT SECTIONS ARE LIGHT — `#s-bench` on c9-01, `#s-deck` on
   c9-02, `#s-bench` on c9-03 and `#s-bench` on c9-04 are each
   `class="ks3-block"` with no second class. There is no ink-dark practical
   block anywhere in C9, exactly as there is none in C3, C4, C5, C7 or C8.

   ⚠️ EVERY TEXT RULE IS SELF-SCOPED AT (0,2,0) — `.ks3-raud .ks3-raud-obs`,
   never a bare `.ks3-raud-obs`. A bare component class is (0,1,0) and loses
   to `.ks3-dark p` at (0,1,1) the day anything moves one of these onto ink,
   and it loses SILENTLY.

   ⚠️ `--ks3-data` IS NOT SPENT HERE EITHER. It is an ON-INK token (the C3
   ruling) and every C9 instrument sits on a light ground. `NOTES-C9.md` §9.2
   reports the token as missing entirely and asks for it to be added or for
   audit law 9 to be amended: it EXISTS, in `shared/tokens.css`, and is
   on-ink-only, so a light-ground unit never spends it and law 9 needs no
   amendment. Selection and category state uses `--ks3-accent` /
   `--ks3-accent-tint` with an `--ks3-ink` outline, and EVERY selectable cell
   also carries a WORD, so colour is never the only channel.

   ⚠️ CONTAINMENT, NOT CLIPPING. The twelve-cell audit matrix is the unit's
   one horizontal scroller and carries `position: relative`.
   ═══ */

.ks3-raudit-block,
.ks3-pdeck-block,
.ks3-xroute-block,
.ks3-specb-block { --c9-gap: 12px; }

/* ── c9-01 · reaction-audit ────────────────────────────────────────────── */
.ks3-raud .ks3-raud-scroll {
  position: relative; overflow-x: auto; padding-bottom: 4px;
}
.ks3-raud .ks3-raud-table {
  border-collapse: separate; border-spacing: 6px; min-width: 32rem;
}
.ks3-raud .ks3-raud-table th {
  font-family: var(--ks3-font-mono); font-size: 12px; font-weight: 500;
  letter-spacing: .05em; color: var(--ks3-ink-muted);
  padding: 6px 8px; text-align: left;
}
.ks3-raud .ks3-raud-corner { width: 1px; }
.ks3-raud .ks3-raud-mname {
  display: block; font-family: var(--ks3-font-display); font-weight: 800;
  font-size: 17px; text-transform: none; letter-spacing: 0;
  color: var(--ks3-ink);
}
.ks3-raud .ks3-raud-mform {
  display: block; margin-top: 2px; font-size: 11px; text-transform: none;
  letter-spacing: 0; color: var(--ks3-ink-muted);
}
.ks3-raud .ks3-raud-cell {
  font: inherit; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; min-width: 5.2rem; min-height: 44px; padding: 8px 10px;
  border-radius: 12px; cursor: pointer;
  background: var(--ks3-card); border: 2px solid var(--ks3-ink);
  color: var(--ks3-ink-ghost);
}
.ks3-raud .ks3-raud-cell[aria-pressed="true"] {
  background: var(--ks3-accent-tint); border-color: var(--ks3-accent);
  color: var(--ks3-ink); box-shadow: 0 0 0 4px var(--ks3-accent-tint);
}
.ks3-raud .ks3-raud-predict {
  margin-top: 16px; display: flex; flex-direction: column;
  align-items: flex-start; gap: 9px;
}
.ks3-raud .ks3-raud-prompt,
.ks3-xroute .ks3-xroute-rest,
.ks3-specb .ks3-specb-rest {
  margin: 0; font-size: 18px; color: var(--ks3-ink-body);
}
.ks3-raud .ks3-raud-readout,
.ks3-xroute .ks3-xroute-readout,
.ks3-specb .ks3-specb-readout {
  margin-top: 18px; padding: 20px 22px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset); border: 2px solid var(--ks3-ink);
}
.ks3-raud .ks3-raud-rest {
  margin: 0; font-size: 17px; color: var(--ks3-ink-muted);
}
.ks3-raud .ks3-raud-title {
  margin: 0; font-family: var(--ks3-font-display); font-weight: 800;
  font-size: 20px; line-height: 1.25; color: var(--ks3-ink);
}
.ks3-raud .ks3-raud-verdict {
  margin: 10px 0 0; font-family: var(--ks3-font-display); font-weight: 700;
  font-size: 18px; color: var(--ks3-accent-text);
}
.ks3-raud .ks3-raud-obs {
  margin: 7px 0 0; font-size: 17px; line-height: 1.55;
  color: var(--ks3-ink-body);
}
.ks3-raud .ks3-raud-eq,
.ks3-xroute .ks3-xroute-eq {
  margin: 12px 0 0; padding: 10px 14px; border-radius: 12px;
  background: var(--ks3-card); border: 2px solid var(--ks3-rule);
  font-family: var(--ks3-font-mono); font-size: 14px; color: var(--ks3-ink);
}
/* The apparatus line is the one place in C9 that carries the alert tone, and
   it carries a WORD too — it is a sentence, not a colour. */
.ks3-raud .ks3-raud-care {
  margin: 12px 0 0; padding: 10px 14px; border-radius: 12px;
  background: var(--ks3-alert-tint); border: 2px solid var(--ks3-alert-border);
  font-size: 16px; line-height: 1.5; color: var(--ks3-alert-text);
}
.ks3-raud .ks3-raud-bands,
.ks3-xroute .ks3-xroute-groups {
  margin-top: 18px; display: flex; flex-direction: column; gap: var(--c9-gap);
}
.ks3-raud .ks3-raud-band,
.ks3-xroute .ks3-xroute-group {
  padding: 18px 20px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-card); border: 2px solid var(--ks3-ink);
}
.ks3-raud .ks3-raud-blabel,
.ks3-xroute .ks3-xroute-glabel {
  margin: 0; font-family: var(--ks3-font-display); font-weight: 800;
  font-size: 19px; color: var(--ks3-ink);
}
.ks3-raud .ks3-raud-bmembers,
.ks3-xroute .ks3-xroute-gmembers {
  margin: 6px 0 0; font-family: var(--ks3-font-mono); font-size: 13px;
  color: var(--ks3-accent-text);
}
.ks3-raud .ks3-raud-bnote,
.ks3-xroute .ks3-xroute-gnote {
  margin: 8px 0 0; font-size: 17px; line-height: 1.55;
  color: var(--ks3-ink-body);
}

/* ── c9-02 · prediction-deck ───────────────────────────────────────────── */
.ks3-pdeck .ks3-pdeck-strip {
  margin: 0 0 20px; padding: 16px 18px; list-style: none;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card); border: 2px solid var(--ks3-ink);
  counter-reset: pdeck;
}
.ks3-pdeck .ks3-pdeck-strip-item {
  counter-increment: pdeck; display: flex; gap: 10px; align-items: baseline;
  padding: 5px 0; font-size: 17px; color: var(--ks3-ink-body);
}
.ks3-pdeck .ks3-pdeck-strip-item::before {
  content: counter(pdeck); font-family: var(--ks3-font-mono); font-size: 12px;
  color: var(--ks3-ink-muted); min-width: 1.2rem;
}
.ks3-pdeck .ks3-pdeck-sname {
  font-family: var(--ks3-font-display); font-weight: 700; font-size: 17px;
  color: var(--ks3-ink);
}
.ks3-pdeck .ks3-pdeck-stag {
  font-family: var(--ks3-font-mono); font-size: 12px;
  color: var(--ks3-accent-text);
}
.ks3-pdeck .ks3-pdeck-cards {
  display: flex; flex-direction: column; gap: 14px;
}
.ks3-pdeck .ks3-pdeck-card {
  padding: 20px 22px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset); border: 2px solid var(--ks3-ink);
}
.ks3-pdeck .ks3-pdeck-label {
  margin: 0; font-family: var(--ks3-font-display); font-weight: 800;
  font-size: 19px; line-height: 1.25; color: var(--ks3-ink);
}
.ks3-pdeck .ks3-pdeck-setup {
  margin: 7px 0 0; font-size: 17px; line-height: 1.5;
  color: var(--ks3-ink-muted);
}
.ks3-pdeck .ks3-pdeck-opts {
  margin-top: 12px; display: flex; gap: 9px; flex-wrap: wrap;
}
.ks3-pdeck .ks3-pdeck-reveal {
  margin-top: 12px; padding: 14px 16px; border-radius: 14px;
  background: var(--ks3-card); border: 2px solid var(--ks3-rule);
}
.ks3-pdeck .ks3-pdeck-verdict {
  margin: 0; font-family: var(--ks3-font-display); font-weight: 700;
  font-size: 18px; color: var(--ks3-ink);
}
.ks3-pdeck .ks3-pdeck-obs {
  margin: 7px 0 0; font-size: 17px; line-height: 1.55;
  color: var(--ks3-ink-body);
}
.ks3-pdeck .ks3-pdeck-eq {
  margin: 10px 0 0; font-family: var(--ks3-font-mono); font-size: 14px;
  color: var(--ks3-accent-text);
}

/* ── c9-03 · extraction-route ──────────────────────────────────────────── */
.ks3-xroute .ks3-xroute-pickers {
  display: flex; flex-direction: column; gap: 12px;
}
.ks3-xroute .ks3-xroute-ores,
.ks3-xroute .ks3-xroute-methods,
.ks3-specb .ks3-specb-shelf {
  display: flex; gap: 9px; flex-wrap: wrap;
}
.ks3-xroute .ks3-xroute-setup {
  margin: 0; font-size: 16px; line-height: 1.5; color: var(--ks3-ink-muted);
}
.ks3-xroute .ks3-xroute-title {
  margin: 10px 0 0; font-family: var(--ks3-font-display); font-weight: 800;
  font-size: 20px; line-height: 1.25; color: var(--ks3-ink);
}
.ks3-xroute .ks3-xroute-why {
  margin: 7px 0 0; font-size: 17px; line-height: 1.55;
  color: var(--ks3-ink-body);
}

/* ── c9-04 · spec-bench ────────────────────────────────────────────────── */
.ks3-specb .ks3-specb-jobs {
  display: flex; flex-direction: column; gap: var(--c9-gap);
  margin-bottom: 16px;
}
.ks3-specb .ks3-specb-job {
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset); border: 2px solid var(--ks3-ink);
}
.ks3-specb .ks3-specb-jbtn {
  font: inherit; display: block; width: 100%; text-align: left;
  padding: 18px 20px; min-height: 44px; cursor: pointer;
  background: none; border: 0; border-radius: var(--ks3-r-panel);
  color: var(--ks3-ink);
}
.ks3-specb .ks3-specb-job[data-open="1"] {
  border-color: var(--ks3-accent); box-shadow: 0 0 0 4px var(--ks3-accent-tint);
}
.ks3-specb .ks3-specb-jname {
  display: block; font-family: var(--ks3-font-display); font-weight: 800;
  font-size: 19px; line-height: 1.25; color: var(--ks3-ink);
}
.ks3-specb .ks3-specb-setup {
  display: block; margin-top: 6px; font-size: 17px; line-height: 1.5;
  color: var(--ks3-ink-body);
}
.ks3-specb .ks3-specb-reqs {
  display: flex; gap: 7px; flex-wrap: wrap; margin-top: 10px;
}
.ks3-specb .ks3-specb-req {
  padding: 4px 10px; border-radius: 999px;
  background: var(--ks3-card); border: 2px solid var(--ks3-rule);
  font-family: var(--ks3-font-mono); font-size: 12px;
  color: var(--ks3-ink-body);
}
.ks3-specb .ks3-specb-head {
  margin: 0; font-family: var(--ks3-font-display); font-weight: 800;
  font-size: 20px; line-height: 1.25; color: var(--ks3-ink);
}
.ks3-specb .ks3-specb-verdict {
  margin: 10px 0 0; font-family: var(--ks3-font-display); font-weight: 700;
  font-size: 18px; color: var(--ks3-accent-text);
}
.ks3-specb .ks3-specb-praise {
  margin: 7px 0 0; font-size: 17px; line-height: 1.55;
  color: var(--ks3-ink-body);
}
/* A rejection is a LIST OF NAMED REQUIREMENTS, never a count. The renderer
   refuses a material that fails a requirement without a reason, so every row
   below is guaranteed to have one. */
.ks3-specb .ks3-specb-fails {
  margin: 10px 0 0; padding: 0; list-style: none;
  display: flex; flex-direction: column; gap: 10px;
}
.ks3-specb .ks3-specb-rname {
  display: block; font-family: var(--ks3-font-mono); font-size: 12px;
  letter-spacing: .04em; text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-specb .ks3-specb-rwhy {
  display: block; margin-top: 3px; font-size: 17px; line-height: 1.5;
  color: var(--ks3-ink-body);
}

/* ── closing panels, shared shape ──────────────────────────────────────── */
.ks3-raud-close,
.ks3-pdeck-close,
.ks3-xroute-close,
.ks3-specb-close {
  margin-top: 20px; padding: 22px 24px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-band); border: 2px solid var(--ks3-ink);
}
.ks3-raud .ks3-raud-closetitle,
.ks3-pdeck .ks3-pdeck-closetitle,
.ks3-xroute .ks3-xroute-closetitle,
.ks3-specb .ks3-specb-closetitle {
  margin: 0 0 10px; font-family: var(--ks3-font-display); font-weight: 800;
  font-size: 22px; line-height: 1.25; color: var(--ks3-ink);
}
.ks3-raud-close p,
.ks3-pdeck-close p,
.ks3-xroute-close p,
.ks3-specb-close p {
  margin: 0 0 10px; font-size: 19px; line-height: 1.6; color: var(--ks3-ink);
}
.ks3-raud-close p:last-child,
.ks3-pdeck-close p:last-child,
.ks3-xroute-close p:last-child,
.ks3-specb-close p:last-child { margin-bottom: 0; }

/* ⚠️ `[hidden]` LAST, AND SELF-SCOPED — several rules above set `display` on
   elements that are also `hidden` at rest, and `display: flex` on a bare
   class beats the UA's `[hidden] { display: none }`. Without this the route
   bench would show all twenty-four verdicts at once and still read as
   correct CSS. */
.ks3-raud [hidden],
.ks3-pdeck [hidden],
.ks3-xroute [hidden],
.ks3-specb [hidden] { display: none; }

/* ═══ END C9 ═══ */

/* ═══ BEGIN C10 ══════════════════════════════════════════════════════════
   C10's instrument styling — *The Earth and its atmosphere*. A PURE APPEND at
   EOF — every byte above this marker is unchanged, which is the one conflict
   shape that resolves mechanically when another lane merges.

   ⚠️ WAVE 5. Nine families are here: c10-04's `material-loop` (`#s-loop`)
   and `stock-limits` (`#s-stock`), c10-01's `earth-layers` (`#s-layers`) and
   `depth-evidence` (`#s-evidence`), c10-02's `rock-bench` (`#s-bench`),
   c10-03's `grain-journey` (`#s-journey`) and `process-arrows`
   (`#s-processes`), and c10-05's `air-mix` (`#s-mix`) and `atmos-history`
   (`#s-history`). The unit's other two families are listed in
   `ks3_art/c10.py` and their rules land with their lessons, inside this same
   marked block.

   ⚠️ `#s-loop` IS THE UNIT'S ONE INK-DARK INSTRUMENT — `ks3-block ks3-dark`
   in Design's file, built as `ks3-block ks3-dark ks3-practical` (the ruling
   is in `ks3_art/c10.py`). `.ks3-dark p` is (0,1,1) and a bare component
   class is (0,1,0), so an unscoped colour rule on this bench LOSES and loses
   SILENTLY: `--ks3-on-dark-body` at #E7DECE would paint over every readout on
   the panel. EVERY rule below is self-scoped at (0,2,0) — `.ks3-mloop
   .ks3-mloop-mass`, never a bare `.ks3-mloop-mass`.

   ⚠️ `--ks3-ember` DOES NOT EXIST, and neither does any other on-dark accent
   token in the KS3 family. NOTES-C10's closing note records it: the name
   people reach for is `--st-ember`, which belongs to the studio surface, and
   an undefined custom property is DROPPED — the declaration vanishes, the
   element inherits, contrast stays fine and no gate trips. The mono labels on
   this bench are `--ks3-on-dark-muted`, which is the same token the stat
   labels under them use.

   ⚠️ `--ks3-data` IS NOT SPENT HERE. It is an ON-INK token (the C3 ruling) and
   the one ink block in this unit reads its numbers in `--ks3-on-dark` at
   display weight, which is what Design drew. `--ks3-ok` is used ONLY as a bar
   FILL, never as `color:` on text, which is the split `shared/tokens.css`
   spells out at the token itself.

   ⚠️ THE BAR TRACK IS `rgba(0,0,0,.25)` WITH AN INSET WHITE HAIRLINE — the
   same fix applied to the b7/b8 oxygen readouts. Without the hairline the
   empty part of a bar disappears into the panel and a 4% bar reads as no bar
   at all, which on this bench is the difference between "almost nothing came
   back" and "the instrument is broken".

   ⚠️ SELECTION AND CATEGORY STATE USES THE HOUSE `.ks3-seg-btn`, unchanged.
   On ink that is `--ks3-alert` with ink text (MRB-242), not the `--ks3-accent`
   pair Design drew here; the reasoning is in `ks3_art/c10.py`. Every button
   also carries `aria-pressed` and a WORD, so colour is never the only channel.
   ═══ */

.ks3-mloop-block,
.ks3-stock-block { --c10-gap: 12px; }

/* ── c10-04 · material-loop (ON INK) ───────────────────────────────────── */
.ks3-mloop .ks3-mloop-glabel {
  margin: 22px 0 0; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; letter-spacing: .09em; text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-mloop .ks3-mloop-group {
  margin-top: 10px; display: flex; gap: 9px; flex-wrap: wrap;
}
.ks3-mloop .ks3-mloop-hint {
  margin: 14px 0 0; font-size: 16px; line-height: 1.5;
  color: var(--ks3-on-dark-muted);
}
.ks3-mloop .ks3-mloop-readout { margin-top: 24px; }
.ks3-mloop .ks3-mloop-panel {
  padding: 22px 24px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-dark-panel);
}
.ks3-mloop .ks3-mloop-name {
  margin: 0; font-family: var(--ks3-font-display); font-weight: 800;
  font-size: 22px; line-height: 1.25; color: var(--ks3-on-dark);
}
.ks3-mloop .ks3-mloop-what {
  margin: 6px 0 0; font-size: 17px; line-height: 1.55;
  color: var(--ks3-on-dark-muted);
}
.ks3-mloop .ks3-mloop-bars {
  list-style: none; margin: 20px 0 0; padding: 0;
  display: flex; flex-direction: column; gap: 11px;
}
.ks3-mloop .ks3-mloop-bar {
  display: flex; align-items: center; gap: 14px;
}
.ks3-mloop .ks3-mloop-blabel {
  flex: 0 0 78px; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
/* CONTAINMENT, NOT CLIPPING: the fill is a child of the track and the track
   owns the radius, so a 100% bar cannot spill a square corner past it. */
.ks3-mloop .ks3-mloop-track {
  flex: 1 1 auto; min-width: 0; height: 18px; border-radius: 99px;
  background: rgba(0, 0, 0, .25);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .16);
  overflow: hidden;
}
.ks3-mloop .ks3-mloop-fill {
  display: block; height: 100%; background: var(--ks3-ok);
}
.ks3-mloop .ks3-mloop-mass {
  flex: 0 0 84px; text-align: right; font-family: var(--ks3-font-mono);
  font-size: 15px; font-weight: 500; color: var(--ks3-on-dark);
}
.ks3-mloop .ks3-mloop-stats {
  margin-top: 22px; display: flex; gap: 14px 26px; flex-wrap: wrap;
}
.ks3-mloop .ks3-mloop-stat { flex: 1 1 12rem; min-width: 0; }
.ks3-mloop .ks3-mloop-slabel {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 12px;
  font-weight: 500; letter-spacing: .09em; text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-mloop .ks3-mloop-svalue {
  margin: 4px 0 0; font-family: var(--ks3-font-display); font-weight: 800;
  font-size: 27px; line-height: 1.15; color: var(--ks3-on-dark);
}
.ks3-mloop .ks3-mloop-snote {
  margin: 3px 0 0; font-size: 15px; line-height: 1.5;
  color: var(--ks3-on-dark-muted);
}
.ks3-mloop .ks3-mloop-verdict {
  margin: 20px 0 0; font-size: 20px; line-height: 1.55; font-weight: 600;
  color: var(--ks3-on-dark);
}
.ks3-mloop [hidden] { display: none; }

/* At 360px the label + mass columns take 162px of a 300px row and the track
   collapses to a stub, so the two fixed columns give ground rather than the
   thing the bench is for. */
@media (max-width: 30rem) {
  .ks3-mloop .ks3-mloop-bar { gap: 10px; }
  .ks3-mloop .ks3-mloop-blabel { flex: 0 0 58px; font-size: 12px; }
  .ks3-mloop .ks3-mloop-mass { flex: 0 0 66px; font-size: 14px; }
  .ks3-mloop .ks3-mloop-svalue { font-size: 24px; }
}

/* ── c10-04 · stock-limits (light) ─────────────────────────────────────── */
.ks3-stock .ks3-stock-shelf {
  margin-top: 18px; display: flex; gap: 9px; flex-wrap: wrap;
}
.ks3-stock .ks3-stock-readout {
  margin-top: 20px; padding: 22px 24px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset); border: 2px solid var(--ks3-ink);
}
.ks3-stock .ks3-stock-name {
  margin: 0; font-family: var(--ks3-font-display); font-weight: 800;
  font-size: 24px; line-height: 1.25; color: var(--ks3-ink);
}
.ks3-stock .ks3-stock-use {
  margin: 10px 0 0; font-size: 18px; line-height: 1.6;
  color: var(--ks3-ink-body);
}
.ks3-stock .ks3-stock-row {
  margin: 12px 0 0; font-size: 18px; line-height: 1.6; color: var(--ks3-ink);
}
.ks3-stock [hidden] { display: none; }

/* ── c10-01 · earth-layers (light) ─────────────────────────────────────
   ⚠️ THE BAR IS THE ARGUMENT. `flex-grow` is written inline by
   `ks3_art/c10.py` as the layer's share of the distance to the centre, so
   nothing in here may set `flex` on a segment — a rule that did would
   silently overwrite the one number the instrument exists to draw.

   ⚠️ `min-width` IS THE ONE DELIBERATE DISTORTION, and only the thinnest
   layer ever meets it: half of one per cent of a phone screen is under four
   pixels and is not a tappable target (44px is the guidance; 34 is the floor
   at which a vertical mono label is still readable inside a 96px-tall bar).
   The scale panel states the true share as a derived number and says the bar
   draws it wider, which is why `r_earth_layers` REFUSES a scale panel that
   does not name `{thin_share}`. */
.ks3-elay .ks3-elay-bar {
  margin: 22px 0 0; display: flex; height: 96px; gap: 3px;
}
.ks3-elay .ks3-elay-seg {
  font: inherit; flex-basis: 0; flex-shrink: 1; min-width: 34px;
  display: grid; place-items: center; padding: 6px 2px; border-radius: 10px;
  cursor: pointer; border: 2px solid var(--ks3-ink);
  background: var(--ks3-card); color: var(--ks3-ink);
}
.ks3-elay .ks3-elay-liquid { background: var(--ks3-band); }
.ks3-elay .ks3-elay-seg[aria-pressed="true"] {
  border-color: var(--ks3-accent); background: var(--ks3-accent-tint);
  box-shadow: 0 0 0 4px var(--ks3-accent-tint);
}
.ks3-elay .ks3-elay-slabel {
  font-family: var(--ks3-font-mono); font-size: 11px; font-weight: 500;
  letter-spacing: .04em; text-transform: uppercase; color: var(--ks3-ink);
  writing-mode: vertical-rl; text-orientation: mixed;
}
.ks3-elay .ks3-elay-ends {
  margin-top: 8px; display: flex; justify-content: space-between; gap: 12px;
}
.ks3-elay .ks3-elay-end {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 12px;
  font-weight: 500; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
/* The sentence the block is not allowed to ship without: which layer the bar
   draws wider than the truth, and what its real share is. It sits under the
   bar's own end captions, it is always visible, and it is not behind a tap —
   c10-05's `.ks3-amix-foot` pattern, applied where the same distortion is. */
.ks3-elay .ks3-elay-foot {
  margin: 14px 0 0; font-size: 17px; line-height: 1.6;
  color: var(--ks3-ink-muted); max-width: 62ch;
}
.ks3-elay .ks3-elay-readout {
  margin-top: 20px; padding: 22px 24px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset); border: 2px solid var(--ks3-ink);
}
.ks3-elay .ks3-elay-head {
  display: flex; gap: 18px; align-items: baseline; flex-wrap: wrap;
  justify-content: space-between;
}
.ks3-elay .ks3-elay-name {
  margin: 0; font-family: var(--ks3-font-display); font-weight: 800;
  font-size: 26px; line-height: 1.2; color: var(--ks3-ink);
}
.ks3-elay .ks3-elay-depth {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-elay .ks3-elay-facts {
  margin-top: 14px; display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); gap: 12px;
}
.ks3-elay .ks3-elay-fact {
  padding: 12px 14px; border-radius: 14px; background: var(--ks3-card);
  border: 2px solid var(--ks3-rule);
}
.ks3-elay .ks3-elay-flabel {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 11px;
  font-weight: 500; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-elay .ks3-elay-fvalue {
  margin: 5px 0 0; font-size: 17px; font-weight: 700; line-height: 1.35;
  color: var(--ks3-ink);
}
.ks3-elay .ks3-elay-note {
  margin: 14px 0 0; font-size: 18px; line-height: 1.6;
  color: var(--ks3-ink-body);
}
.ks3-elay .ks3-elay-scale {
  margin-top: 20px; padding: 22px 24px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-band); border: 2px solid var(--ks3-ink);
}
.ks3-elay .ks3-elay-stitle {
  margin: 0; font-family: var(--ks3-font-display); font-weight: 800;
  font-size: 22px; line-height: 1.25; color: var(--ks3-ink);
}
.ks3-elay .ks3-elay-stext {
  margin: 10px 0 0; font-size: 19px; line-height: 1.6; color: var(--ks3-ink);
}
.ks3-elay [hidden] { display: none; }

/* At 360px a 96px bar leaves the vertical label of a 34px segment clipped
   against its own padding, so the bar loses height rather than the label
   losing its word. */
@media (max-width: 30rem) {
  .ks3-elay .ks3-elay-bar { height: 84px; gap: 2px; }
  .ks3-elay .ks3-elay-seg { min-width: 30px; }
  .ks3-elay .ks3-elay-name { font-size: 22px; }
  .ks3-elay .ks3-elay-stext { font-size: 18px; }
  .ks3-elay .ks3-elay-foot { font-size: 16px; }
}

/* ── c10-01 · depth-evidence (light) ───────────────────────────────────
   Selection uses the house `.ks3-seg-btn` unchanged. The block is on the
   card ground, so MRB-242's on-ink correction does not arise here; what does
   arise is that a spent, unchosen button is `disabled` by `shared/ks3.js`
   and must still read as text rather than as a smudge. */
.ks3-edep-block .ks3-edep {
  margin-top: 18px; display: flex; flex-direction: column; gap: 14px;
}
.ks3-edep .ks3-edep-one {
  padding: 20px 22px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset); border: 2px solid var(--ks3-ink);
}
.ks3-edep .ks3-edep-q {
  margin: 0; font-size: 19px; line-height: 1.55; color: var(--ks3-ink);
}
.ks3-edep .ks3-edep-picks {
  margin-top: 12px; display: flex; gap: 9px; flex-wrap: wrap;
}
.ks3-edep .ks3-edep-choice[disabled] { opacity: .55; cursor: default; }
.ks3-edep .ks3-edep-answer {
  margin: 12px 0 0; padding: 14px 16px; border-radius: 14px;
  background: var(--ks3-card); border: 2px solid var(--ks3-rule);
  font-size: 17px; line-height: 1.55; color: var(--ks3-ink);
}
.ks3-edep [hidden] { display: none; }

/* ── c10-02 · rock-bench (light) ───────────────────────────────────────
   Six sample rows on the card ground, each moving from `--ks3-inset` to
   `--ks3-card` the moment it is decided — Design's own two-state row, and
   the ONLY thing on this bench that changes colour. Nothing here is green
   and nothing here is red: the verdict is a sentence.

   ⚠️ `[data-decided]` IS SET BY `shared/ks3.js` AND BY NOTHING ELSE, and
   the resting page carries none — six undecided rows and a hidden pattern
   panel is the on-load state, which is the state the payload models.

   Selection uses the house `.ks3-seg-btn` unchanged. The block is light, so
   MRB-242's on-ink correction does not arise; what does arise is that a
   spent, unchosen button is `disabled` and must still read as text rather
   than as a smudge, which is `depth-evidence`'s rule above at the same
   opacity. */
.ks3-rockb .ks3-rockb-note {
  margin: 12px 0 0; font-size: 19px; line-height: 1.6; max-width: 54ch;
  color: var(--ks3-ink-body);
}
.ks3-rockb .ks3-rockb-list {
  margin-top: 18px; display: flex; flex-direction: column; gap: 12px;
}
.ks3-rockb .ks3-rockb-row {
  padding: 20px 22px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset); border: 2px solid var(--ks3-ink);
}
.ks3-rockb .ks3-rockb-row[data-decided] { background: var(--ks3-card); }
.ks3-rockb .ks3-rockb-head {
  display: flex; gap: 16px; align-items: baseline; flex-wrap: wrap;
  justify-content: space-between;
}
.ks3-rockb .ks3-rockb-code {
  margin: 0; font-family: var(--ks3-font-display); font-weight: 800;
  font-size: 21px; line-height: 1.25; color: var(--ks3-ink);
}
.ks3-rockb .ks3-rockb-found {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; letter-spacing: .05em; text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-rockb .ks3-rockb-facts {
  margin: 10px 0 0; padding-left: 20px; display: flex;
  flex-direction: column; gap: 4px; font-size: 17px; line-height: 1.5;
  color: var(--ks3-ink-body);
}
.ks3-rockb .ks3-rockb-picks {
  margin-top: 12px; display: flex; gap: 9px; flex-wrap: wrap;
}
.ks3-rockb .ks3-rockb-pick[disabled] { opacity: .55; cursor: default; }
.ks3-rockb .ks3-rockb-out {
  margin-top: 12px; padding: 14px 16px; border-radius: 14px;
  background: var(--ks3-card); border: 2px solid var(--ks3-rule);
}
.ks3-rockb .ks3-rockb-verdict {
  margin: 0; font-family: var(--ks3-font-display); font-weight: 700;
  font-size: 18px; line-height: 1.35; color: var(--ks3-ink);
}
.ks3-rockb .ks3-rockb-why {
  margin: 7px 0 0; font-size: 17px; line-height: 1.55;
  color: var(--ks3-ink-body);
}
.ks3-rockb .ks3-rockb-pattern {
  margin-top: 20px; padding: 22px 24px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-band); border: 2px solid var(--ks3-ink);
}
.ks3-rockb .ks3-rockb-ptitle {
  margin: 0; font-family: var(--ks3-font-display); font-weight: 800;
  font-size: 22px; line-height: 1.25; color: var(--ks3-ink);
}
.ks3-rockb .ks3-rockb-ptext {
  margin: 10px 0 0; font-size: 19px; line-height: 1.6; color: var(--ks3-ink);
}
.ks3-rockb [hidden] { display: none; }

/* At 360px three group buttons on one row leave "Sedimentary" and
   "Metamorphic" each on a line of their own, so the row gives padding
   rather than the buttons giving their words. */
@media (max-width: 30rem) {
  .ks3-rockb .ks3-rockb-row { padding: 16px 15px; }
  .ks3-rockb .ks3-rockb-code { font-size: 19px; }
  .ks3-rockb .ks3-rockb-picks { gap: 8px; }
  .ks3-rockb .ks3-rockb-ptext { font-size: 18px; }
}
/* ── c10-03 · grain-journey (light) ────────────────────────────────────
   A SEQUENCER, and the only one in C10. Seven full-width rows on the card
   ground; a row that has been placed moves to the accent tint with an accent
   badge carrying its position. That is Design's own two-state row, and it is
   the only thing on this block that changes colour — the verdict is a
   sentence, and nothing here is green or red.

   ⚠️ `[data-placed]` IS SET BY `shared/ks3.js` AND BY NOTHING ELSE, and the
   resting page carries none: seven live rows, seven empty badges and a hidden
   panel is the on-load state, which is the state the payload models.

   ⚠️ A PLACED ROW IS LIT, NOT DIMMED, and it is NOT disabled — the ruling and
   its reason are in `ks3_art/c10.py`. There is deliberately no `[disabled]`
   opacity rule below, because nothing on this block is ever disabled; adding
   one would be a style waiting for a state that cannot happen.

   No element here is absolutely positioned and no part of the block scrolls,
   so the containment rule the sticky rail and the wide tables need does not
   arise. Recorded rather than assumed. */
.ks3-grain-block .ks3-grain { margin-top: 18px; }
.ks3-grain .ks3-grain-list {
  list-style: none; margin: 0; padding: 0; display: flex;
  flex-direction: column; gap: 9px;
}
.ks3-grain .ks3-grain-step {
  font: inherit; font-size: 17px; line-height: 1.5; width: 100%;
  display: flex; gap: 14px; align-items: center; text-align: left;
  padding: 14px 16px; border-radius: var(--ks3-r-panel); cursor: pointer;
  border: 2px solid var(--ks3-option-border); background: var(--ks3-card);
  color: var(--ks3-ink);
}
.ks3-grain .ks3-grain-step[data-placed] {
  border-color: var(--ks3-accent); background: var(--ks3-accent-tint);
  cursor: default;
}
.ks3-grain .ks3-grain-badge {
  display: grid; place-items: center; width: 30px; height: 30px;
  flex: 0 0 30px; border-radius: 9px; font-family: var(--ks3-font-mono);
  font-size: 14px; font-weight: 500; background: var(--ks3-band);
  color: var(--ks3-ink-ghost);
}
.ks3-grain .ks3-grain-step[data-placed] .ks3-grain-badge {
  background: var(--ks3-accent); color: var(--ks3-on-dark);
}
.ks3-grain .ks3-grain-tools {
  margin-top: 14px; display: flex; gap: 9px; flex-wrap: wrap;
}
.ks3-grain .ks3-grain-reset {
  font: inherit; font-size: 16px; font-weight: 700; padding: 11px 16px;
  min-height: 44px; border-radius: var(--ks3-r-control); cursor: pointer;
  border: 2px solid var(--ks3-rule-strong); background: transparent;
  color: var(--ks3-ink-muted);
}
.ks3-grain .ks3-grain-panel {
  margin-top: 20px; padding: 22px 24px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-band); border: 2px solid var(--ks3-ink);
}
.ks3-grain .ks3-grain-verdict {
  margin: 0; font-family: var(--ks3-font-display); font-weight: 800;
  font-size: 22px; line-height: 1.25; color: var(--ks3-ink);
}
.ks3-grain .ks3-grain-answer {
  margin: 14px 0 0; padding-left: 22px; display: flex;
  flex-direction: column; gap: 10px; font-size: 18px; line-height: 1.55;
  color: var(--ks3-ink);
}
.ks3-grain .ks3-grain-close {
  margin: 16px 0 0; font-size: 19px; line-height: 1.6; color: var(--ks3-ink);
}
.ks3-grain [hidden] { display: none; }

/* At 360px a 30px badge plus 14px of gap leaves a seven-word row breaking
   mid-word against its own padding, so the row gives padding and gap rather
   than the stage giving its words. */
@media (max-width: 30rem) {
  .ks3-grain .ks3-grain-step {
    padding: 12px 13px; gap: 11px; font-size: 16px;
  }
  .ks3-grain .ks3-grain-badge { width: 26px; height: 26px; flex: 0 0 26px; }
  .ks3-grain .ks3-grain-panel { padding: 18px 17px; }
  .ks3-grain .ks3-grain-verdict { font-size: 20px; }
  .ks3-grain .ks3-grain-answer { font-size: 17px; padding-left: 19px; }
  .ks3-grain .ks3-grain-close { font-size: 18px; }
}

/* ── c10-03 · process-arrows (light) ───────────────────────────────────
   Six segmented buttons over one panel, the panel swapping as the buttons do.
   Selection uses the house `.ks3-seg-btn` unchanged; the block is light, so
   MRB-242's on-ink correction does not arise, and nothing here is ever
   disabled, so `depth-evidence`'s spent-button opacity does not arise either.

   ⚠️ THE ROUTE LINE IS A FLEX ROW THAT WRAPS, and the arrow between its two
   ends is DRAWN as SVG on `currentColor` — the fonts in `shared/fonts/` carry
   no U+2192, which is why `ks3_art.kit.MARKS` exists. At 360px "any rock, deep
   and hot enough" and "igneous rock" each take a line and the arrow sits
   between them; `flex: 0 0 auto` is what stops it being squashed to a stub
   when they do. */
.ks3-parrow-block .ks3-parrow { margin-top: 18px; }
.ks3-parrow .ks3-parrow-picks {
  display: flex; gap: 9px; flex-wrap: wrap;
}
.ks3-parrow .ks3-parrow-panel {
  margin-top: 20px; padding: 22px 24px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset); border: 2px solid var(--ks3-ink);
}
.ks3-parrow .ks3-parrow-name {
  margin: 0; font-family: var(--ks3-font-display); font-weight: 800;
  font-size: 24px; line-height: 1.25; color: var(--ks3-ink);
}
.ks3-parrow .ks3-parrow-route {
  margin: 10px 0 0; display: flex; align-items: center; gap: 12px;
  flex-wrap: wrap; font-family: var(--ks3-font-display); font-weight: 700;
  font-size: 19px; line-height: 1.3; color: var(--ks3-ink);
}
.ks3-parrow .ks3-parrow-arrow { flex: 0 0 auto; }
.ks3-parrow .ks3-parrow-note {
  margin: 12px 0 0; font-size: 18px; line-height: 1.6;
  color: var(--ks3-ink-body);
}
.ks3-parrow .ks3-parrow-time {
  margin: 12px 0 0; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-parrow [hidden] { display: none; }

/* At 360px six buttons wrap to four rows and the panel's display-weight
   route line is the first thing to overflow, so the panel gives padding and
   the route line gives leading. */
@media (max-width: 30rem) {
  .ks3-parrow .ks3-parrow-panel { padding: 18px 17px; }
  .ks3-parrow .ks3-parrow-name { font-size: 21px; }
  .ks3-parrow .ks3-parrow-route { font-size: 18px; gap: 9px; }
  .ks3-parrow .ks3-parrow-note { font-size: 17px; }
}
/* ── c10-05 · air-mix (light) ──────────────────────────────────────────
   A bar whose segment widths ARE the proportions — `flex-grow` is the gas's
   own percentage and `flex-basis` is 0 — over one panel that swaps as the
   segments do. Selection uses this family's own segment treatment rather than
   the house `.ks3-seg-btn`, because the control IS the drawing: it has to
   carry a width, and a button that carries a width cannot also carry the
   house control's padding.

   ⚠️ `min-width` IS THE ONE DELIBERATE DISTORTION ON THIS BLOCK, and it is
   `c10-01`'s solution reused. Argon is 0.9% of the air and carbon dioxide is
   0.04%; at any width that leaves nitrogen readable those two are a hairline
   and a sub-pixel, and neither is a tappable target (44px is the guidance; 34
   is the floor at which a vertical mono label still fits inside an 88px bar).
   `r_air_mix` REFUSES a payload whose `scale_note` does not name both the
   gases the floor affects and their true combined share, so the page cannot
   exaggerate a proportion without admitting it in the same breath.

   ⚠️ `--ks3-ember` DOES NOT EXIST and neither does any other on-dark accent
   token. This block is light and does not want one; the note is here because
   the unit's ink-dark bench is eight hundred lines above and the name is the
   one people reach for. */
.ks3-amix-block .ks3-amix { margin-top: 18px; }
.ks3-amix .ks3-amix-lead {
  margin: 0; font-size: 19px; line-height: 1.6; max-width: 54ch;
  color: var(--ks3-ink);
}
.ks3-amix .ks3-amix-bar {
  margin: 22px 0 0; display: flex; height: 88px; gap: 3px;
}
.ks3-amix .ks3-amix-seg {
  font: inherit; flex-basis: 0; flex-shrink: 1; min-width: 34px;
  display: grid; place-items: center; padding: 6px 2px; border-radius: 10px;
  cursor: pointer; border: 2px solid var(--ks3-ink);
  background: var(--ks3-card); color: var(--ks3-ink);
}
/* The gas a body actually takes out of a breath. Derived from the payload's
   `lungs` value, never from a gas name — see `r_air_mix`. */
.ks3-amix .ks3-amix-used { background: var(--ks3-band); }
.ks3-amix .ks3-amix-seg[aria-pressed="true"] {
  border-color: var(--ks3-accent); background: var(--ks3-accent-tint);
  box-shadow: 0 0 0 4px var(--ks3-accent-tint);
}
.ks3-amix .ks3-amix-slabel {
  font-family: var(--ks3-font-mono); font-size: 11px; font-weight: 500;
  letter-spacing: .04em; text-transform: uppercase; color: var(--ks3-ink);
}
/* Only a segment sitting on the width floor turns its label. Emitted from the
   same comparison that chooses the floored set, so the geometry and the
   lettering cannot disagree about which strips are the thin ones. */
.ks3-amix .ks3-amix-thin .ks3-amix-slabel {
  writing-mode: vertical-rl; text-orientation: mixed;
}
.ks3-amix .ks3-amix-readout {
  margin-top: 18px; padding: 22px 24px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset); border: 2px solid var(--ks3-ink);
}
.ks3-amix .ks3-amix-head {
  display: flex; gap: 18px; align-items: baseline; flex-wrap: wrap;
  justify-content: space-between;
}
.ks3-amix .ks3-amix-name {
  margin: 0; font-family: var(--ks3-font-display); font-weight: 800;
  font-size: 26px; line-height: 1.2; color: var(--ks3-ink);
}
.ks3-amix .ks3-amix-pct {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 15px;
  font-weight: 500; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ks3-accent-text);
}
.ks3-amix .ks3-amix-note {
  margin: 12px 0 0; font-size: 18px; line-height: 1.6;
  color: var(--ks3-ink-body);
}
/* The two sentences the block is not allowed to ship without: what the bar
   exaggerates, and the fact that these are the figures for DRY air. Both sit
   under the readout, both are always visible, and neither is behind a tap. */
.ks3-amix .ks3-amix-foot {
  margin: 14px 0 0; font-size: 17px; line-height: 1.6;
  color: var(--ks3-ink-muted); max-width: 62ch;
}
.ks3-amix [hidden] { display: none; }

/* At 360px an 88px bar leaves the vertical label of a 34px segment clipped
   against its own padding, so the bar loses height rather than the label
   losing its word. */
@media (max-width: 30rem) {
  .ks3-amix .ks3-amix-bar { height: 80px; gap: 2px; }
  .ks3-amix .ks3-amix-seg { min-width: 30px; }
  .ks3-amix .ks3-amix-lead { font-size: 18px; }
  .ks3-amix .ks3-amix-readout { padding: 18px 17px; }
  .ks3-amix .ks3-amix-name { font-size: 22px; }
  .ks3-amix .ks3-amix-note { font-size: 17px; }
  .ks3-amix .ks3-amix-foot { font-size: 16px; }
}

/* ── c10-05 · atmos-history (light) ────────────────────────────────────
   Five stages stacked, each one a closed card until it is revealed. `data-open`
   is what the runtime sets and what everything visual below hangs on, so the
   open state is one attribute rather than a class the handler has to remember
   to add and remove.

   ⚠️ NOTHING HERE IS ABSOLUTELY POSITIONED, so the containment rule does not
   bite on this block. The date chip is a flex item with a fixed basis, not an
   overlay, which is what keeps it aligned down the left of a list that wraps.

   The reveal button is styled here rather than taken from `.ks3-seg-btn`
   because it is not a selection: it has one job, it is never pressed-in, and
   `aria-pressed` on it would be a lie. It is never disabled either — MRB-257,
   the ruling recorded in `ks3_art/c10.py`. */
.ks3-ahist-block .ks3-ahist { margin-top: 18px; }
.ks3-ahist .ks3-ahist-list {
  list-style: none; margin: 20px 0 0; padding: 0; display: flex;
  flex-direction: column; gap: 10px;
}
.ks3-ahist .ks3-ahist-step {
  padding: 16px 18px; border-radius: var(--ks3-r-panel);
  border: 2px solid var(--ks3-rule); background: var(--ks3-inset);
}
.ks3-ahist .ks3-ahist-step[data-open="1"] {
  border-color: var(--ks3-ink); background: var(--ks3-card);
}
.ks3-ahist .ks3-ahist-head {
  display: flex; gap: 14px; align-items: baseline; flex-wrap: wrap;
}
.ks3-ahist .ks3-ahist-when {
  font-family: var(--ks3-font-mono); font-size: 13px; font-weight: 500;
  letter-spacing: .05em; text-transform: uppercase;
  color: var(--ks3-accent-text); flex: 0 0 74px;
}
.ks3-ahist .ks3-ahist-what {
  margin: 0; font-family: var(--ks3-font-display); font-weight: 700;
  font-size: 20px; line-height: 1.3; color: var(--ks3-ink);
}
.ks3-ahist .ks3-ahist-why {
  margin: 10px 0 0; font-size: 18px; line-height: 1.6;
  color: var(--ks3-ink-body);
}
.ks3-ahist .ks3-ahist-tools {
  margin-top: 16px; display: flex; gap: 9px; flex-wrap: wrap;
  align-items: center;
}
.ks3-ahist .ks3-ahist-reveal {
  font: inherit; font-size: 16px; font-weight: 700; padding: 11px 16px;
  min-height: 44px; border-radius: var(--ks3-r-control); cursor: pointer;
  border: 2px solid var(--ks3-option-border); background: var(--ks3-ground);
  color: var(--ks3-ink);
}
.ks3-ahist .ks3-ahist-panel {
  margin-top: 20px; padding: 22px 24px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-band); border: 2px solid var(--ks3-ink);
}
.ks3-ahist .ks3-ahist-ptitle {
  margin: 0; font-family: var(--ks3-font-display); font-weight: 800;
  font-size: 22px; line-height: 1.25; color: var(--ks3-ink);
}
.ks3-ahist .ks3-ahist-ptext {
  margin: 10px 0 0; font-size: 19px; line-height: 1.6; color: var(--ks3-ink);
}
.ks3-ahist [hidden] { display: none; }

/* At 360px a 74px date chip and a display-weight headline cannot share a
   line, so the chip takes the whole row and the headline drops under it —
   which is what `flex-wrap` was already going to do, given the room. The
   card gives padding rather than the stage giving its words. */
@media (max-width: 30rem) {
  .ks3-ahist .ks3-ahist-step { padding: 14px 15px; }
  .ks3-ahist .ks3-ahist-head { gap: 6px 14px; }
  .ks3-ahist .ks3-ahist-what { font-size: 18px; }
  .ks3-ahist .ks3-ahist-why { font-size: 17px; }
  .ks3-ahist .ks3-ahist-panel { padding: 18px 17px; }
  .ks3-ahist .ks3-ahist-ptitle { font-size: 20px; }
  .ks3-ahist .ks3-ahist-ptext { font-size: 18px; }
}
/* ── c10-06 · greenhouse-steps (light) ─────────────────────────────────
   C10's second stepper, and it takes `atmos-history`'s two-state row rather
   than a new one: a closed card on `--ks3-inset` that becomes an open card on
   `--ks3-card`, driven by `data-open` so the open state is one attribute
   rather than a class the handler has to remember to add and remove. Design
   draws exactly this on `#s-how` and exactly this on `#s-history`, and two
   rulesets would be one drift away from disagreeing.

   ⊕ THE ONE THING THAT IS NOT `atmos-history`'s is `.ks3-ghouse-chip` — the
   mono line derived from what the step declares about direction, band and
   absorption. It sits UNDER the headline rather than beside it, because it is
   three facts joined by middots and a 74px flex basis would break it across
   two lines at any width. It is `--ks3-ink-muted` rather than
   `--ks3-accent-text`: the date chip on the history block is the timeline and
   earns the accent; this is a label on a claim the headline already made.

   ⚠️ NOTHING HERE IS ABSOLUTELY POSITIONED, so the containment rule does not
   bite on this block. Recorded rather than assumed.

   The reveal button is styled here rather than taken from `.ks3-seg-btn`
   because it is not a selection: it has one job, it is never pressed-in, and
   `aria-pressed` on it would be a lie. It is never disabled either — MRB-257,
   the ruling recorded in `ks3_art/c10.py`. */
.ks3-ghouse-block .ks3-ghouse { margin-top: 18px; }
.ks3-ghouse .ks3-ghouse-list {
  list-style: none; margin: 20px 0 0; padding: 0; display: flex;
  flex-direction: column; gap: 10px;
}
.ks3-ghouse .ks3-ghouse-step {
  padding: 16px 18px; border-radius: var(--ks3-r-panel);
  border: 2px solid var(--ks3-rule); background: var(--ks3-inset);
}
.ks3-ghouse .ks3-ghouse-step[data-open="1"] {
  border-color: var(--ks3-ink); background: var(--ks3-card);
}
.ks3-ghouse .ks3-ghouse-head {
  display: flex; gap: 14px; align-items: baseline;
}
.ks3-ghouse .ks3-ghouse-num {
  font-family: var(--ks3-font-mono); font-size: 14px; font-weight: 500;
  color: var(--ks3-ink-muted); flex: 0 0 24px;
}
.ks3-ghouse .ks3-ghouse-what {
  margin: 0; font-family: var(--ks3-font-display); font-weight: 700;
  font-size: 20px; line-height: 1.3; color: var(--ks3-ink);
}
.ks3-ghouse .ks3-ghouse-chip {
  margin: 8px 0 0 38px; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; letter-spacing: .05em; text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-ghouse .ks3-ghouse-why {
  margin: 10px 0 0 38px; font-size: 18px; line-height: 1.6;
  color: var(--ks3-ink-body);
}
.ks3-ghouse .ks3-ghouse-tools {
  margin-top: 16px; display: flex; gap: 9px; flex-wrap: wrap;
  align-items: center;
}
.ks3-ghouse .ks3-ghouse-reveal {
  font: inherit; font-size: 16px; font-weight: 700; padding: 11px 16px;
  min-height: 44px; border-radius: var(--ks3-r-control); cursor: pointer;
  border: 2px solid var(--ks3-option-border); background: var(--ks3-ground);
  color: var(--ks3-ink);
}
.ks3-ghouse .ks3-ghouse-panel {
  margin-top: 20px; padding: 22px 24px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-band); border: 2px solid var(--ks3-ink);
}
.ks3-ghouse .ks3-ghouse-ptitle {
  margin: 0; font-family: var(--ks3-font-display); font-weight: 800;
  font-size: 22px; line-height: 1.25; color: var(--ks3-ink);
}
.ks3-ghouse .ks3-ghouse-ptext {
  margin: 10px 0 0; font-size: 19px; line-height: 1.6; color: var(--ks3-ink);
}
.ks3-ghouse [hidden] { display: none; }

/* At 360px a 24px number gutter plus a display-weight headline is tight but
   holds; what does not hold is the 38px indent under it, which leaves the
   chip and the explanation in a 250px column. Both come back to the card's
   own left edge, which is what the number was marking anyway. */
@media (max-width: 30rem) {
  .ks3-ghouse .ks3-ghouse-step { padding: 14px 15px; }
  .ks3-ghouse .ks3-ghouse-what { font-size: 18px; }
  .ks3-ghouse .ks3-ghouse-chip { margin-left: 0; }
  .ks3-ghouse .ks3-ghouse-why { margin-left: 0; font-size: 17px; }
  .ks3-ghouse .ks3-ghouse-panel { padding: 18px 17px; }
  .ks3-ghouse .ks3-ghouse-ptitle { font-size: 20px; }
  .ks3-ghouse .ks3-ghouse-ptext { font-size: 18px; }
}

/* ── c10-06 · climate-evidence (light) ─────────────────────────────────
   `depth-evidence`'s card, plus a tag row and a closing panel. Selection uses
   the house `.ks3-seg-btn` unchanged; the block is on the card ground, so
   MRB-242's on-ink correction does not arise. What does arise is that a
   spent, unchosen button is `disabled` by `shared/ks3.js` and must still read
   as text rather than as a smudge — the same `.55` opacity `depth-evidence`
   settled on.

   ⊕ `.ks3-cev-role` IS THE LINE THAT MAKES THE BLOCK AN ARGUMENT — "On its
   own, this establishes …", derived from the entry's declared role. It is
   mono and muted and it sits ABOVE the answer prose, because it is the
   verdict and the paragraph under it is the reasoning. It is inside the
   `hidden` answer wrapper, so it cannot be read before the student commits.

   ⚠️ NOTHING HERE IS ABSOLUTELY POSITIONED, so the containment rule does not
   bite on this block either. */
.ks3-cev-block .ks3-cev {
  margin-top: 18px; display: flex; flex-direction: column; gap: 14px;
}
.ks3-cev .ks3-cev-one {
  padding: 20px 22px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset); border: 2px solid var(--ks3-ink);
}
.ks3-cev .ks3-cev-tag {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-cev .ks3-cev-q {
  margin: 6px 0 0; font-size: 19px; line-height: 1.55; color: var(--ks3-ink);
}
.ks3-cev .ks3-cev-picks {
  margin-top: 12px; display: flex; gap: 9px; flex-wrap: wrap;
}
.ks3-cev .ks3-cev-choice[disabled] { opacity: .55; cursor: default; }
.ks3-cev .ks3-cev-answer {
  margin-top: 12px; padding: 14px 16px; border-radius: 14px;
  background: var(--ks3-card); border: 2px solid var(--ks3-rule);
}
.ks3-cev .ks3-cev-role {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; letter-spacing: .05em; color: var(--ks3-accent-text);
}
.ks3-cev .ks3-cev-atext {
  margin: 8px 0 0; font-size: 17px; line-height: 1.55; color: var(--ks3-ink);
}
.ks3-cev .ks3-cev-panel {
  margin-top: 6px; padding: 22px 24px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-band); border: 2px solid var(--ks3-ink);
}
.ks3-cev .ks3-cev-ptitle {
  margin: 0; font-family: var(--ks3-font-display); font-weight: 800;
  font-size: 22px; line-height: 1.25; color: var(--ks3-ink);
}
.ks3-cev .ks3-cev-ptext {
  margin: 10px 0 0; font-size: 19px; line-height: 1.6; color: var(--ks3-ink);
}
.ks3-cev [hidden] { display: none; }

@media (max-width: 30rem) {
  .ks3-cev .ks3-cev-one { padding: 16px 15px; }
  .ks3-cev .ks3-cev-q { font-size: 18px; }
  .ks3-cev .ks3-cev-atext { font-size: 16px; }
  .ks3-cev .ks3-cev-panel { padding: 18px 17px; }
  .ks3-cev .ks3-cev-ptitle { font-size: 20px; }
  .ks3-cev .ks3-cev-ptext { font-size: 18px; }
}
/* ═══ END C10 ═══ */

/* ── ⊕ MRB-223 · A C2 DEFECT, LIVE SINCE MRB-220 ──────────────────────── */
/*
   `r_cover_bar` (`build_ks3.py`) emits its three part-whole cover plates as
   `<g class="ks3-bar-cover" hidden>`. `hidden` is a UA rule at (0,1,0), and
   `<g>` computes `display: inline` from that same UA stylesheet — so the
   attribute never closes the plates. `wireCoverBar`'s inline `display:none`
   is the ONLY thing that does, which means that before JS runs, and forever
   with JS off, all three plates are painted over the bar at once and the
   model is unreadable.

   A screenshot cannot see this and neither can computed style: it is a
   question for the CASCADE, which is what MRB-242's gate asks.

   ⚑ IT WAS NEVER SEEN BECAUSE NOTHING WAS LOOKING. `c2-06` is the only page
   in the key stage that draws a cover bar, and it carries no row in
   `ks3_parity.COMPONENTS` — and the MRB-242 sweep runs over the pages
   COMPONENTS and CONTRAST name. The defect surfaced only when a physics page
   registered a parity row on a page drawing this component.

   Kept from the MRB-223 run-1 authoring that has otherwise been stood down:
   this line is independent of that authoring, and it fixes a live page.

   ⊕ 25 Aug 2026 (MRB-220 close-out). Two updates to the note above:
   (1) "c2-06 is the only page in the key stage that draws a cover bar" is
   STALE — p6-02 and p6-07 draw it too, which is how the defect surfaced.
   (2) This rule alone flipped the failure from all-three-plates-painted to
   all-three-plates-open, which leaks the covered answer pre-JS. The other
   half of the fix is in `r_cover_bar` (build_ks3.py): the covered plate is
   no longer emitted with `hidden`, so the pre-JS paint is the correct
   one-plate-covered state and wireCoverBar's load-time show() is a no-op.

   The rules below close the same cascade hole for every OTHER SVG group
   the generators emit with `hidden`: `hidden` never closes an SVG element
   (the UA rule is HTML-namespace only), so each family needs a companion
   rule or it paints all its variants at once before ks3.js runs. These
   four all intend all-hidden pre-JS (wiring shows one on load).
*/
.ks3-bar .ks3-bar-cover[hidden] { display: none; }
[data-iboard-arrow][hidden],
[data-hrig-coil][hidden],
[data-hrig-shape][hidden],
[data-alt-case-shape][hidden],
[data-wanat-mark][hidden] { display: none; }

/* ═══ BEGIN P1 ═══════════════════════════════════════════════════════════
   P1's instrument styling — *Energy transfers*, the first physics unit.

   Values measured off Claude Design's delivered pages in
   `docs/ks3/design-reference/p1/`, where every one of them is an inline
   style string built by her `seg()` and `chip()` helpers. Only what her
   drawing actually sets is written here; anything the page inherits from the
   shared tokens is left to inherit.

   ⚠️ THE SEGMENTED BUTTONS ARE NOT REDECLARED. `.ks3-seg-btn` already carries
   Design's control geometry and its `aria-pressed="true"` branch, in both the
   light and the ink-dark forms. P1's scenario picks and sort picks use that
   class and add nothing, because a second copy of a control's values is
   exactly the drift the ruling above `.ks3-seg-btn` exists to stop.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── p1-01 · store audit ─────────────────────────────────────────────── */

.ks3-saudit-picks {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 20px;
}
.ks3-saudit-panel {
  margin-top: 18px;
  padding: 22px 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
}
.ks3-saudit-text {
  margin: 0;
  font-size: 19px;
  line-height: 1.55;
  font-weight: 600;
}
/* Design mounts every scenario's sentence and keeps one visible, so a student
   with JS off reads the physics rather than an empty panel. */
.ks3-saudit-text[hidden],
.ks3-saudit-vtext[hidden] { display: none; }

/* Two columns on a wide screen, one on a phone — Design's `[data-ba]` grid,
   at her own breakpoint. */
.ks3-saudit-cols {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
  margin-top: 20px;
}
@media (max-width: 720px) {
  .ks3-saudit-cols { grid-template-columns: minmax(0, 1fr); }
}
.ks3-saudit-coltitle {
  margin: 0 0 10px;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-saudit-chips {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.ks3-saudit-chip {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  font: inherit;
  font-size: 16px;
  font-weight: 600;
  padding: 10px 14px;
  min-height: var(--ks3-tap);
  text-align: left;
  cursor: pointer;
  border-radius: var(--ks3-r-control);
  border: 2px solid var(--ks3-option-border);
  background: var(--ks3-ground);
  color: var(--ks3-ink);
}
.ks3-saudit-chip-label { flex: 1; text-align: left; }

/* R2 — a marked chip carries a WORD in `.ks3-saudit-chip-mark` as well as a
   border, never colour alone. The three states are genuinely three: ticked
   and right, ticked and wrong, and MISSED — left untitcked when it should
   have been. Collapsing the last two into one "wrong" would throw away the
   diagnosis, because they mean opposite things about what a student
   believes. */
.ks3-saudit-chip[aria-pressed="true"] {
  border-color: var(--ks3-accent);
  background: var(--ks3-accent-tint);
}
.ks3-saudit-chip[data-state="right"] {
  border-color: var(--ks3-accent);
  background: var(--ks3-accent-tint);
}
.ks3-saudit-chip[data-state="wrong"] {
  border-color: var(--ks3-ink);
  background: var(--ks3-ground);
}
.ks3-saudit-chip[data-state="missed"] {
  border-color: var(--ks3-ink);
  background: var(--ks3-band);
}
.ks3-saudit-chip-mark {
  font-family: var(--ks3-font-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-saudit-chip[data-state="right"] .ks3-saudit-chip-mark {
  color: var(--ks3-accent-text);
}

.ks3-saudit-acts {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 18px;
}
.ks3-saudit-verdict {
  margin-top: 18px;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border-left: 6px solid var(--ks3-accent);
}
.ks3-saudit-verdict[hidden] { display: none; }
.ks3-saudit-vlabel {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks3-accent-text);
}
.ks3-saudit-vtext {
  margin: 8px 0 0;
  font-size: 18px;
  line-height: 1.55;
}

/* ── p1-01 · store / pathway sort ────────────────────────────────────── */

.ks3-spath-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 12px;
  margin-top: 18px;
}
.ks3-spath-item {
  padding: 18px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-option-border);
}
/* Answered-and-right takes the accent border; answered-and-wrong takes ink.
   Both carry their reason in `.ks3-spath-note`, so the border is never the
   only thing saying which happened. */
.ks3-spath-item[data-answered="right"] { border-color: var(--ks3-accent); }
.ks3-spath-item[data-answered="wrong"] { border-color: var(--ks3-ink); }
.ks3-spath-name {
  margin: 0 0 12px;
  font-size: 18px;
  line-height: 1.4;
  font-weight: 700;
}
.ks3-spath-picks {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
.ks3-spath-note {
  margin: 12px 0 0;
  font-size: 16px;
  line-height: 1.5;
  color: var(--ks3-ink);
}
.ks3-spath-item[data-answered="wrong"] .ks3-spath-note {
  color: var(--ks3-accent-text);
}
.ks3-spath-note[hidden] { display: none; }
/* The two source notes are carried in the markup and swapped into the live
   note by `wireStorePathwaySort`. They are never shown themselves. */
.ks3-spath-src { display: none; }

.ks3-spath-settle {
  margin-top: 20px;
  padding: 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
}
.ks3-spath-settle[hidden] { display: none; }
.ks3-spath-settle p {
  margin: 0;
  font-size: 19px;
  line-height: 1.6;
}
.ks3-spath-settle p + p { margin-top: 14px; }

/* ── p1-02 · before-and-after tally ──────────────────────────────────── */

.ks3-btally-gate {
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-btally-gate[hidden] { display: none; }
.ks3-btally-gate .ks3-commit {
  margin: 0 0 12px;
  color: var(--ks3-accent-text);
}

.ks3-btally-bench { margin-top: 22px; }
.ks3-btally-bench[hidden] { display: none; }

.ks3-btally-devs {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 18px;
}

/* The two columns. `align-items: flex-end` so a bar grows upward from the
   baseline, which is the direction Design draws them. */
.ks3-btally-cols {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 18px;
  align-items: end;
  padding: 18px;
  border-radius: var(--ks3-r-card);
  border: 2px solid var(--ks3-ink);
  background: var(--ks3-card);
}
.ks3-btally-col {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  min-height: 240px;
}
.ks3-btally-coltitle {
  margin: 0 0 10px;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-btally-bar {
  display: block;
  width: 100%;
  border: 2px solid var(--ks3-ink);
  transition: height .18s ease-out;
}
.ks3-btally-bar.is-in     { background: var(--ks3-band); }
.ks3-btally-bar.is-useful { background: var(--ks3-accent); }
.ks3-btally-bar.is-waste  { background: var(--ks3-inset); }
.ks3-btally-bar.is-useful + .ks3-btally-bar.is-waste { border-top: 0; }

@media (prefers-reduced-motion: reduce) {
  .ks3-btally-bar { transition: none; }
}

.ks3-btally-sliderlabel {
  display: block;
  margin-top: 18px;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-btally-slider {
  width: 100%;
  height: 44px;
  accent-color: var(--ks3-accent);
  cursor: pointer;
}

.ks3-btally-outs {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
  margin-top: 6px;
}
@media (max-width: 620px) {
  .ks3-btally-outs { grid-template-columns: minmax(0, 1fr); }
  .ks3-btally-cols { min-height: 0; }
  .ks3-btally-col  { min-height: 170px; }
}
.ks3-btally-outlabel {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-btally-outval {
  margin: 4px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 30px;
  line-height: 1.1;
  color: var(--ks3-ink);
}
.ks3-btally-out.is-accent .ks3-btally-outval { color: var(--ks3-accent-text); }

.ks3-btally-sum {
  margin: 16px 0 0;
  padding: 14px 16px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
}
.ks3-btally-note {
  margin: 18px 0 0;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
  font-size: 19px;
  line-height: 1.6;
}
.ks3-btally-note[hidden] { display: none; }

/* ── p1-02 · wasted / the point sort ─────────────────────────────────── */

.ks3-wsort-grid {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.ks3-wsort-card {
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-dark-panel);
  border: 2px solid var(--ks3-on-dark-muted);
}
.ks3-wsort-card.is-right { border-color: var(--ks3-alert); }
.ks3-wsort-text {
  margin: 0 0 12px;
  font-size: 18px;
  line-height: 1.5;
  font-weight: 600;
  color: var(--ks3-on-dark);
}
.ks3-wsort-picks {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
.ks3-wsort-note {
  margin: 12px 0 0;
  font-size: 17px;
  line-height: 1.5;
  color: var(--ks3-on-dark-body);
}
.ks3-wsort-note.is-wrong { color: var(--ks3-alert); }
.ks3-wsort-note[hidden] { display: none; }

.ks3-wsort-settle {
  margin-top: 20px;
  padding: 24px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-dark-panel);
  border: 2px solid var(--ks3-on-dark-muted);
}
.ks3-wsort-settle[hidden] { display: none; }
.ks3-wsort-settle p {
  margin: 0;
  font-size: 19px;
  line-height: 1.6;
  color: var(--ks3-on-dark-body);
}

/* ── p1-03 · running total ───────────────────────────────────────────── */

.ks3-rtotal-gate {
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-rtotal-gate[hidden] { display: none; }
.ks3-rtotal-gate .ks3-commit {
  margin: 0 0 12px;
  color: var(--ks3-accent-text);
}

.ks3-rtotal-bench { margin-top: 22px; }
.ks3-rtotal-bench[hidden] { display: none; }

/* The stacked bar. `position: relative` is load-bearing: the total line is
   absolutely positioned inside it, and an absolutely positioned child of a
   scrolling ancestor widens the document on a phone unless its own container
   establishes the containing block. */
.ks3-rtotal-stack {
  position: relative;
  display: flex;
  flex-direction: column-reverse;
  justify-content: flex-start;
  height: 260px;
  padding: 0;
  border-radius: var(--ks3-r-card);
  border: 2px solid var(--ks3-ink);
  background: var(--ks3-card);
  overflow: hidden;
}
.ks3-rtotal-line {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 0;
  border-top: 2px dashed var(--ks3-ink);
  pointer-events: none;
  z-index: 2;
}
.ks3-rtotal-bar {
  display: block;
  width: 100%;
  transition: height .12s linear;
}
.ks3-rtotal-bar[hidden] { display: none; }
/* Design's own segment colours, read off her canvas: GRAV #8E6C3F,
   KIN #D98A4A, THERMAL #E4572E. */
.ks3-rtotal-bar.is-grav  { background: #8E6C3F; }
.ks3-rtotal-bar.is-kin   { background: #D98A4A; }
.ks3-rtotal-bar.is-therm { background: #E4572E; }

@media (prefers-reduced-motion: reduce) {
  .ks3-rtotal-bar { transition: none; }
}

.ks3-rtotal-outs {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 12px;
  margin-top: 14px;
}
@media (max-width: 620px) {
  .ks3-rtotal-outs { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ks3-rtotal-stack { height: 200px; }
}
.ks3-rtotal-outlabel {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-rtotal-outval {
  margin: 4px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 26px;
  line-height: 1.1;
  color: var(--ks3-ink);
}
.ks3-rtotal-out.is-accent .ks3-rtotal-outval { color: var(--ks3-accent-text); }

.ks3-rtotal-ctls {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 16px;
}
.ks3-rtotal-note {
  margin: 18px 0 0;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
  font-size: 19px;
  line-height: 1.6;
}
.ks3-rtotal-note[hidden] { display: none; }

/* ── p1-03 · conservation beam ───────────────────────────────────────── */

.ks3-cbeam-picks {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 18px;
}

.ks3-cbeam-rig {
  padding: 22px 20px 18px;
  border-radius: var(--ks3-r-card);
  border: 2px solid var(--ks3-ink);
  background: var(--ks3-card);
}
/* The beam itself: a level rule between the two pans. It never tilts,
   because the pans always match — that is the claim. */
.ks3-cbeam-beam {
  display: block;
  height: 6px;
  border-radius: 99px;
  background: var(--ks3-ink);
  margin-bottom: 0;
}
.ks3-cbeam-pans {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 22px;
  align-items: end;
  min-height: 190px;
}
.ks3-cbeam-pan {
  display: flex;
  flex-direction: column-reverse;
  justify-content: flex-start;
  height: 170px;
  border: 2px solid var(--ks3-ink);
  border-top: 0;
  border-radius: 0 0 var(--ks3-r-panel) var(--ks3-r-panel);
  overflow: hidden;
  background: var(--ks3-inset);
}
.ks3-cbeam-seg {
  display: block;
  width: 100%;
  transition: height .18s ease-out;
}
.ks3-cbeam-seg[hidden] { display: none; }
.ks3-cbeam-seg.is-whole { background: var(--ks3-accent); height: 100%; }
.ks3-cbeam-seg.is-grav  { background: #8E6C3F; }
.ks3-cbeam-seg.is-kin   { background: #D98A4A; }
.ks3-cbeam-seg.is-therm { background: #E4572E; }

@media (prefers-reduced-motion: reduce) {
  .ks3-cbeam-seg { transition: none; }
}

.ks3-cbeam-panlabel {
  margin: 0;
  padding: 8px 10px;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
  background: var(--ks3-card);
  order: -1;
}
.ks3-cbeam-caption {
  margin: 14px 0 0;
  text-align: center;
  font-family: var(--ks3-font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ks3-accent-text);
}
.ks3-cbeam-ctl {
  margin: 16px 0 0;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-cbeam-note {
  margin: 16px 0 0;
  font-size: 19px;
  line-height: 1.6;
}
.ks3-cbeam-note[hidden] { display: none; }

/* ── p1-04 · two quantities ──────────────────────────────────────────── */

.ks3-twoq-axis + .ks3-twoq-axis { margin-top: 16px; }
.ks3-twoq-axislabel {
  margin: 0 0 8px;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-twoq-btns {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.ks3-twoq-outs {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
  margin-top: 20px;
}
@media (max-width: 620px) {
  .ks3-twoq-outs { grid-template-columns: minmax(0, 1fr); }
}
.ks3-twoq-outlabel {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-twoq-outval {
  margin: 4px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 30px;
  line-height: 1.1;
  color: var(--ks3-ink);
}
.ks3-twoq-out.is-accent .ks3-twoq-outval { color: var(--ks3-accent-text); }

.ks3-twoq-bar {
  display: block;
  height: 22px;
  margin-top: 14px;
  border-radius: 99px;
  border: 2px solid var(--ks3-ink);
  background: var(--ks3-accent);
  transition: width .18s ease-out;
}
@media (prefers-reduced-motion: reduce) {
  .ks3-twoq-bar { transition: none; }
}
.ks3-twoq-scale {
  margin: 8px 0 0;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  line-height: 1.5;
  color: var(--ks3-ink-muted);
}
.ks3-twoq-close {
  margin: 18px 0 0;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
  font-size: 19px;
  line-height: 1.6;
}
.ks3-twoq-close[hidden] { display: none; }

/* ── p1-04 · one-way flow ────────────────────────────────────────────── */

.ks3-oflow-picks {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 18px;
}

.ks3-oflow-rig {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  gap: 14px;
  align-items: center;
  padding: 22px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-dark-panel);
  border: 2px solid var(--ks3-on-dark-muted);
}
@media (max-width: 620px) {
  .ks3-oflow-rig { grid-template-columns: minmax(0, 1fr); }
}
.ks3-oflow-body {
  padding: 16px 14px;
  border-radius: var(--ks3-r-panel);
  border: 2px solid var(--ks3-on-dark-muted);
  text-align: center;
}
.ks3-oflow-body.is-hot  { background: rgba(228, 87, 46, .22); }
.ks3-oflow-body.is-cold { background: rgba(142, 108, 63, .18); }
.ks3-oflow-name {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ks3-on-dark-body);
}
.ks3-oflow-temp {
  margin: 6px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 28px;
  line-height: 1.1;
  color: var(--ks3-on-dark);
}

.ks3-oflow-arrows {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  min-width: 130px;
}
/* The real transfer: one solid arrow, hotter to colder. */
.ks3-oflow-arrow {
  display: block;
  width: 110px;
  height: 0;
  border-top: 4px solid var(--ks3-accent);
  position: relative;
}
.ks3-oflow-arrow[hidden] { display: none; }
.ks3-oflow-arrow::after {
  content: "";
  position: absolute;
  right: -2px;
  top: -9px;
  border-left: 14px solid var(--ks3-accent);
  border-top: 7px solid transparent;
  border-bottom: 7px solid transparent;
}
/* ⚖️ THE ARROW THAT DOES NOT EXIST. Dashed, struck through, and captioned
   "no cold travels this way". Design's science flag 11: drawing the thing
   that does not happen is the confrontation of ENER-14. Do not delete it
   as a contradictory label. */
.ks3-oflow-ghost {
  display: block;
  width: 110px;
  height: 0;
  border-top: 3px dashed var(--ks3-on-dark-muted);
  opacity: .55;
  position: relative;
}
.ks3-oflow-ghost::after {
  content: "";
  position: absolute;
  left: -2px;
  top: -8px;
  border-right: 12px solid var(--ks3-on-dark-muted);
  border-top: 6px solid transparent;
  border-bottom: 6px solid transparent;
}
.ks3-oflow-ghostlabel {
  margin: 0;
  text-align: center;
  font-family: var(--ks3-font-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
  text-decoration: line-through;
}

.ks3-oflow-run { margin-top: 16px; }
.ks3-oflow-note {
  margin: 16px 0 0;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-dark-panel);
  border: 2px solid var(--ks3-on-dark-muted);
  font-size: 18px;
  line-height: 1.6;
  color: var(--ks3-on-dark-body);
}
.ks3-oflow-note[hidden] { display: none; }

/* ── p1-05 · conduction bench ────────────────────────────────────────── */

.ks3-cbench-picks {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 18px;
}

.ks3-cbench-rig {
  padding: 22px 20px;
  border-radius: var(--ks3-r-card);
  border: 2px solid var(--ks3-ink);
  background: var(--ks3-card);
}
/* The rod. `--cbench-front` is set from JS and is how far the warm front has
   travelled; the gradient stop follows it, so the rod warms from the flame
   end rather than all at once. */
.ks3-cbench-rod {
  --cbench-front: 0%;
  position: relative;
  height: 46px;
  border-radius: 8px;
  border: 2px solid var(--ks3-ink);
  background: linear-gradient(
    to right,
    #E4572E 0%,
    #D98A4A var(--cbench-front),
    var(--ks3-inset) var(--cbench-front),
    var(--ks3-inset) 100%);
  transition: background .12s linear;
}
/* Free electrons, shown only for a metal — a second, faster route drawn as
   a dotted overlay running the whole length. */
.ks3-cbench-rod.is-electrons::before {
  content: "";
  position: absolute;
  left: 6px;
  right: 6px;
  top: 50%;
  height: 0;
  border-top: 3px dotted #FBF3E6;
  opacity: .85;
}
.ks3-cbench-flame {
  position: absolute;
  left: -14px;
  top: 50%;
  transform: translateY(-50%);
  width: 18px;
  height: 26px;
  border-radius: 50% 50% 50% 50% / 60% 60% 40% 40%;
  background: #E4572E;
  border: 2px solid var(--ks3-ink);
}
.ks3-cbench-wax {
  position: absolute;
  right: 10px;
  top: 50%;
  transform: translateY(-50%);
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #C6B9A7;
  border: 2px solid var(--ks3-ink);
  transition: opacity .2s ease-out, transform .2s ease-out;
}
.ks3-cbench-wax.is-gone {
  opacity: 0;
  transform: translateY(14px);
}

@media (prefers-reduced-motion: reduce) {
  .ks3-cbench-rod, .ks3-cbench-wax { transition: none; }
}

.ks3-cbench-rings {
  margin: 14px 0 0;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  line-height: 1.5;
  color: var(--ks3-ink-muted);
}

.ks3-cbench-ctls {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  align-items: center;
  margin-top: 16px;
}
.ks3-cbench-clock {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 22px;
  color: var(--ks3-accent-text);
}
.ks3-cbench-elecnote,
.ks3-cbench-note {
  margin: 16px 0 0;
  padding: 16px 18px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
  font-size: 18px;
  line-height: 1.6;
}
.ks3-cbench-elecnote[hidden],
.ks3-cbench-note[hidden] { display: none; }
.ks3-cbench-model {
  margin: 12px 0 0;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  line-height: 1.5;
  color: var(--ks3-ink-muted);
}

/* ── p1-06 · three routes ────────────────────────────────────────────── */

.ks3-troute-picks {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 18px;
}
.ks3-troute-lamps {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
}
@media (max-width: 620px) {
  .ks3-troute-lamps { grid-template-columns: minmax(0, 1fr); }
}
.ks3-troute-lamp {
  padding: 18px 16px;
  border-radius: var(--ks3-r-panel);
  border: 2px solid var(--ks3-ink);
  background: var(--ks3-card);
  text-align: center;
}
.ks3-troute-lamp.is-off { opacity: .55; }
.ks3-troute-dot {
  display: block;
  width: 22px;
  height: 22px;
  margin: 0 auto 10px;
  border-radius: 50%;
  border: 2px solid var(--ks3-ink);
  background: var(--ks3-inset);
}
.ks3-troute-lamp.is-on .ks3-troute-dot { background: var(--ks3-accent); }
.ks3-troute-lamplabel {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 19px;
  line-height: 1.2;
  color: var(--ks3-ink);
}
.ks3-troute-lampstate {
  margin: 6px 0 0;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-troute-note {
  margin: 18px 0 0;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
  font-size: 19px;
  line-height: 1.6;
}
.ks3-troute-note[hidden] { display: none; }

/* ── p1-07 · insulation trial ────────────────────────────────────────── */

.ks3-itrial-ctls {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  align-items: center;
  margin-bottom: 18px;
}
.ks3-itrial-clock {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 15px;
  font-weight: 500;
  color: var(--ks3-ink-muted);
}

/* ⚠️ The table scrolls INSIDE this wrapper. A wide table that widens the
   document instead is the phone failure this rule exists to stop, and the
   wrapper is `position: relative` so nothing absolutely positioned inside a
   row can escape it either. */
.ks3-itrial-tablewrap,
.ks3-lever-tablewrap {
  position: relative;
  overflow-x: auto;
  border-radius: var(--ks3-r-card);
  border: 2px solid var(--ks3-ink);
  background: var(--ks3-card);
}
.ks3-itrial-table,
.ks3-lever-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 17px;
}
.ks3-itrial-table th,
.ks3-itrial-table td,
.ks3-lever-table th,
.ks3-lever-table td {
  padding: 12px 14px;
  text-align: left;
  border-bottom: 2px solid var(--ks3-rule);
  white-space: nowrap;
}
.ks3-itrial-table thead th,
.ks3-lever-table thead th {
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  background: var(--ks3-band);
  color: var(--ks3-ink-muted);
}
.ks3-itrial-table tbody tr:last-child td,
.ks3-lever-table tbody tr:last-child td { border-bottom: 0; }

.ks3-itrial-close {
  margin: 18px 0 0;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
  font-size: 19px;
  line-height: 1.6;
}
.ks3-itrial-close[hidden] { display: none; }

/* ── p1-07 · the ice trial ───────────────────────────────────────────── */

.ks3-itrial2-cubes {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 18px;
  padding: 24px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-dark-panel);
  border: 2px solid var(--ks3-on-dark-muted);
}
.ks3-itrial2-col {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  min-height: 120px;
  justify-content: flex-end;
}
.ks3-itrial2-cube {
  display: block;
  width: 76px;
  height: 76px;
  border-radius: 8px;
  border: 2px solid var(--ks3-on-dark);
  background: #C9E6F0;
  transform-origin: center bottom;
  transition: transform .18s linear;
}
.ks3-itrial2-cube.is-wrapped {
  box-shadow: 0 0 0 8px rgba(217, 138, 74, .55);
}
@media (prefers-reduced-motion: reduce) {
  .ks3-itrial2-cube { transition: none; }
}
.ks3-itrial2-label {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ks3-on-dark-body);
}
.ks3-itrial2-ctls {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  align-items: center;
  margin-top: 16px;
}
.ks3-itrial2-clock {
  margin: 0;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 22px;
  color: var(--ks3-accent);
}
.ks3-itrial2-note {
  margin: 16px 0 0;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-dark-panel);
  border: 2px solid var(--ks3-on-dark-muted);
  font-size: 18px;
  line-height: 1.6;
  color: var(--ks3-on-dark-body);
}
.ks3-itrial2-note[hidden] { display: none; }

/* ── p1-08 · lever bench ─────────────────────────────────────────────── */

.ks3-lever-gate {
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-lever-gate[hidden] { display: none; }
.ks3-lever-gate .ks3-commit {
  margin: 0 0 12px;
  color: var(--ks3-accent-text);
}
.ks3-lever-bench { margin-top: 22px; }
.ks3-lever-bench[hidden] { display: none; }

/* The bar and its fulcrum. `position: relative` on the rig is load-bearing:
   the pivot is absolutely positioned and would otherwise be placed against
   the page and widen the document on a phone. */
.ks3-lever-rig {
  position: relative;
  height: 64px;
  padding: 0 12px;
  border-radius: var(--ks3-r-card);
  border: 2px solid var(--ks3-ink);
  background: var(--ks3-card);
}
.ks3-lever-bar {
  position: absolute;
  left: 12px;
  right: 12px;
  top: 24px;
  height: 8px;
  border-radius: 99px;
  background: var(--ks3-ink);
}
.ks3-lever-pivot {
  position: absolute;
  top: 30px;
  width: 0;
  height: 0;
  transform: translateX(-11px);
  border-left: 11px solid transparent;
  border-right: 11px solid transparent;
  border-bottom: 20px solid var(--ks3-accent);
  transition: left .1s linear;
}
@media (prefers-reduced-motion: reduce) {
  .ks3-lever-pivot { transition: none; }
}

.ks3-lever-sliderlabel {
  display: block;
  margin-top: 18px;
  font-family: var(--ks3-font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-lever-slider {
  width: 100%;
  height: 44px;
  accent-color: var(--ks3-accent);
  cursor: pointer;
}
.ks3-lever-outs {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 12px;
  margin-top: 6px;
}
@media (max-width: 620px) {
  .ks3-lever-outs { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.ks3-lever-outlabel {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-lever-outval {
  margin: 4px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 700;
  font-size: 24px;
  line-height: 1.1;
  color: var(--ks3-ink);
}
.ks3-lever-acts {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  margin: 16px 0;
}
.ks3-lever-close {
  margin: 18px 0 0;
  padding: 18px 20px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
  font-size: 19px;
  line-height: 1.6;
}
.ks3-lever-close[hidden] { display: none; }

/* ═══ END P1 ═══ */

/* ═══ BEGIN P2 ═══════════════════════════════════════════════════════════
   P2 — *Energy at home*. One marked block per unit, so a future lane can
   sequence behind this one without a merge conflict on a shared file.

   ⚠️ EVERY SELECTOR HERE IS P2-ONLY. The shell class each family renders
   into is asserted unique across the whole registry by `ks3_art.load()`
   since MRB-279, because two families wearing one class silently puts one
   unit's stylesheet on another unit's instrument.
   ═══════════════════════════════════════════════════════════════════════ */

/* p2-01 #s-burn — the calorimeter */
.ks3-calor { margin-top: 22px; }
.ks3-calor-gate {
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-calor-bench {
  border-radius: var(--ks3-r-card);
  border: 2px solid var(--ks3-ink);
  overflow: hidden;
  background: var(--ks3-card);
}
.ks3-calor-picks {
  display: flex; gap: 8px; flex-wrap: wrap;
  padding: 16px 18px;
  border-bottom: 2px solid var(--ks3-ink);
}

/* ⚠️ THE RIG IS `position: relative` AND IT IS LOAD-BEARING. The flame and
   the escaping-energy specks are absolutely positioned inside it. Without
   a positioned ancestor they resolve against the page and widen the
   document on a phone — the standing trap. */
.ks3-calor-rig {
  position: relative;
  display: grid;
  place-items: center;
  min-height: 190px;
  padding: 18px;
  background: var(--ks3-inset);
  border-bottom: 2px solid var(--ks3-ink);
  overflow: hidden;
}
.ks3-calor-tube {
  position: relative;
  display: block;
  width: 62px; height: 118px;
  border: 3px solid var(--ks3-ink);
  border-top-width: 0;
  border-radius: 0 0 30px 30px;
  background: var(--ks3-ground);
  overflow: hidden;
}
/* The water's colour tracks the temperature rise on Design's own ramp,
   `min(1, rise/60)`, handed in by the wiring as `--warm`. */
.ks3-calor-water {
  --warm: 0;
  position: absolute; left: 0; right: 0; bottom: 0;
  height: 62%;
  background: color-mix(in srgb,
    var(--ks3-accent) calc(var(--warm) * 100%), var(--ks3-blue-tint));
}
.ks3-calor-flame {
  position: absolute;
  left: 50%; transform: translateX(-50%);
  bottom: 26px;
  width: 18px; height: 26px;
  border-radius: 50% 50% 50% 50% / 60% 60% 40% 40%;
  background: var(--ks3-ink-ghost);
}
.ks3-calor-flame[data-lit="1"] { background: var(--ks3-accent); }
/* The one-directional escape. Design draws energy leaving the flame
   SIDEWAYS, and that drawing is the visual argument for why the bench
   reads low. */
.ks3-calor-escape {
  position: absolute;
  left: 50%; transform: translateX(-50%);
  bottom: 30px;
  width: 150px; height: 18px;
  background:
    radial-gradient(circle 2px at 10% 40%, var(--ks3-accent) 99%, transparent) ,
    radial-gradient(circle 2px at 32% 70%, var(--ks3-accent) 99%, transparent),
    radial-gradient(circle 2px at 68% 30%, var(--ks3-accent) 99%, transparent),
    radial-gradient(circle 2px at 90% 60%, var(--ks3-accent) 99%, transparent);
  opacity: .75;
}
.ks3-calor-sliderlabel {
  display: block;
  padding: 16px 18px 0;
  font-family: var(--ks3-font-mono);
  font-size: 12px; font-weight: 500;
  letter-spacing: .09em; text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-calor-slider {
  display: block;
  width: calc(100% - 36px);
  margin: 0 18px;
  height: 44px;
  accent-color: var(--ks3-accent);
  cursor: pointer;
}
.ks3-calor-outs {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px;
  padding: 6px 18px 0;
}
.ks3-calor-outlabel {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 12px; font-weight: 500;
  letter-spacing: .09em; text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-calor-outval {
  margin: 4px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 700; font-size: 30px; line-height: 1.1;
  color: var(--ks3-ink);
}
.ks3-calor-acts {
  display: flex; gap: 12px; flex-wrap: wrap;
  padding: 16px 18px;
}
.ks3-calor-tablewrap {
  overflow-x: auto;
  border-top: 2px solid var(--ks3-ink);
}
.ks3-calor-table { width: 100%; border-collapse: collapse; }
.ks3-calor-table th, .ks3-calor-table td {
  padding: 10px 14px;
  text-align: left;
  border-bottom: 1px solid var(--ks3-rule);
  font-size: 16px;
}
.ks3-calor-table thead th {
  font-family: var(--ks3-font-mono);
  font-size: 12px; font-weight: 500;
  letter-spacing: .09em; text-transform: uppercase;
  background: var(--ks3-band);
}
.ks3-calor-table tbody th { background: var(--ks3-band); }
.ks3-calor-table tbody td:last-child {
  color: var(--ks3-accent-text); font-weight: 500;
}
.ks3-calor-note, .ks3-calor-close {
  margin: 0;
  padding: 18px 20px;
  border-top: 2px solid var(--ks3-ink);
  background: var(--ks3-band);
  font-size: 19px; line-height: 1.6;
}
.ks3-calor-note:empty { display: none; }

/* p2-02 #s-bench — the power bench */
.ks3-pbench { margin-top: 22px; }
.ks3-pbench-gate {
  padding: 20px 22px;
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
  border: 2px solid var(--ks3-ink);
}
.ks3-pbench-bench {
  border-radius: var(--ks3-r-card);
  border: 2px solid var(--ks3-ink);
  overflow: hidden;
  background: var(--ks3-card);
}
/* ⚠️ `position: relative` on the scroller. Anything absolutely positioned
   inside a scroller needs a positioned ancestor or it resolves against the
   page and widens the document on a phone. */
.ks3-pbench-rig {
  position: relative;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 18px;
  padding: 20px 18px;
  background: var(--ks3-inset);
  border-bottom: 2px solid var(--ks3-ink);
  overflow-x: auto;
}
.ks3-pbench-name {
  margin: 0 0 10px;
  font-family: var(--ks3-font-mono);
  font-size: 12px; font-weight: 500;
  letter-spacing: .09em; text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-pbench-bars { display: flex; gap: 16px; align-items: flex-end; }
.ks3-pbench-col {
  display: flex; flex-direction: column; align-items: center;
  justify-content: flex-end;
  min-width: 54px; height: 168px;
}
/* The POWER bar is a height and never moves. The ENERGY bar is the running
   total and does. Two bars on purpose — the tall one loses. */
.ks3-pbench-pow, .ks3-pbench-nrg {
  --h: 0;
  display: block; width: 34px;
  height: calc(var(--h) * 132px);
  min-height: 3px;
  border: 2px solid var(--ks3-ink);
  border-radius: 6px 6px 0 0;
  transition: height .18s linear;
}
.ks3-pbench-pow { background: var(--ks3-blue-tint); }
.ks3-pbench-nrg { background: var(--ks3-accent); }
.ks3-pbench-cap {
  margin: 6px 0 0;
  font-family: var(--ks3-font-mono);
  font-size: 11px; color: var(--ks3-ink-muted);
  white-space: nowrap;
}
.ks3-pbench-outs {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px;
  padding: 16px 18px 0;
}
.ks3-pbench-outlabel {
  margin: 0;
  font-family: var(--ks3-font-mono);
  font-size: 12px; font-weight: 500;
  letter-spacing: .09em; text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-pbench-outval {
  margin: 4px 0 0;
  font-family: var(--ks3-font-display);
  font-weight: 700; font-size: 30px; line-height: 1.1;
  color: var(--ks3-ink);
}
.ks3-pbench-acts {
  display: flex; gap: 12px; flex-wrap: wrap;
  padding: 16px 18px;
}
.ks3-pbench-note, .ks3-pbench-close {
  margin: 0;
  padding: 18px 20px;
  border-top: 2px solid var(--ks3-ink);
  background: var(--ks3-band);
  font-size: 19px; line-height: 1.6;
}
.ks3-pbench-note:empty { display: none; }

/* p2-02 #s-sort — power or energy. Sits in a DARK practical shell. */
.ks3-pwsort { margin-top: 20px; }
.ks3-pwsort-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 12px;
  margin: 0; padding: 0; list-style: none;
}
.ks3-pwsort-item {
  padding: 16px 18px;
  border-radius: var(--ks3-r-panel);
  border: 2px solid var(--ks3-on-dark-muted);
  background: var(--ks3-dark-inset, rgba(255, 255, 255, .05));
}
.ks3-pwsort-text {
  margin: 0 0 12px;
  font-size: 17px; line-height: 1.45; font-weight: 600;
  color: var(--ks3-on-dark);
}
.ks3-pwsort-btns { display: flex; gap: 8px; flex-wrap: wrap; }
/* R3 — a choice shows it was CHOSEN, never whether it was right. No green,
   no red, and both buttons stay enabled. */
.ks3-pwsort-note {
  margin: 12px 0 0;
  font-size: 16px; line-height: 1.5;
  color: var(--ks3-on-dark-body);
}

/* p2-03 #s-bench — the appliance bench */
.ks3-abench { margin-top: 22px; }
.ks3-abench-gate {
  padding: 20px 22px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset); border: 2px solid var(--ks3-ink);
}
.ks3-abench-bench {
  border-radius: var(--ks3-r-card); border: 2px solid var(--ks3-ink);
  overflow: hidden; background: var(--ks3-card);
}
.ks3-abench-picks {
  display: flex; gap: 8px; flex-wrap: wrap;
  padding: 16px 18px; border-bottom: 2px solid var(--ks3-ink);
}
.ks3-abench-sliderlabel {
  display: block; padding: 16px 18px 0;
  font-family: var(--ks3-font-mono); font-size: 12px; font-weight: 500;
  letter-spacing: .09em; text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-abench-slider {
  display: block; width: calc(100% - 36px); margin: 0 18px;
  height: 44px; accent-color: var(--ks3-accent); cursor: pointer;
}
.ks3-abench-presets {
  display: flex; gap: 8px; flex-wrap: wrap; padding: 6px 18px 0;
}
.ks3-abench-outs {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 12px; padding: 18px;
}
.ks3-abench-outlabel {
  margin: 0; font-family: var(--ks3-font-mono);
  font-size: 12px; font-weight: 500; letter-spacing: .09em;
  text-transform: uppercase; color: var(--ks3-ink-muted);
}
.ks3-abench-outval {
  margin: 4px 0 0; font-family: var(--ks3-font-display);
  font-weight: 700; font-size: 28px; line-height: 1.1;
  color: var(--ks3-ink); overflow-wrap: anywhere;
}
.ks3-abench-note, .ks3-abench-close {
  margin: 0; padding: 18px 20px; border-top: 2px solid var(--ks3-ink);
  background: var(--ks3-band); font-size: 19px; line-height: 1.6;
}
.ks3-abench-note:empty { display: none; }

/* p2-04 #s-kwh — one kilowatt-hour, four ways */
.ks3-kwh { margin-top: 20px; }
.ks3-kwh-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 14px; margin: 0; padding: 0; list-style: none;
}
.ks3-kwh-btn {
  display: block; width: 100%; text-align: left;
  padding: 14px 16px; border-radius: var(--ks3-r-panel);
  border: 2px solid var(--ks3-option-border); background: var(--ks3-card);
  cursor: pointer;
  transition: transform .14s, border-color .16s;
}
.ks3-kwh-btn:hover { transform: translate(-2px, -2px); }
.ks3-kwh-btn[aria-pressed="true"] { border-color: var(--ks3-accent); }
/* The rectangle IS the teaching: four different shapes, one area. Width and
   height are derived by the renderer, never by flex — a rectangle laid
   out by the container is a bar model that lies. */
.ks3-kwh-rect {
  --w: 50%; --h: 50%;
  display: block; width: var(--w); height: calc(var(--h) * 0.72);
  min-height: 8px; margin-bottom: 10px;
  background: var(--ks3-accent);
  border: 2px solid var(--ks3-ink); border-radius: 4px;
}
.ks3-kwh-label { font-size: 17px; font-weight: 600; line-height: 1.35; }
.ks3-kwh-note, .ks3-kwh-close {
  margin: 16px 0 0; padding: 16px 18px;
  border-radius: var(--ks3-r-panel); background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
  font-size: 18px; line-height: 1.55;
}
.ks3-kwh-note:empty { display: none; }

/* p2-04 #s-bill — the bill builder */
.ks3-bill { margin-top: 22px; }
.ks3-bill-gate {
  padding: 20px 22px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset); border: 2px solid var(--ks3-ink);
}
.ks3-bill-panel {
  border-radius: var(--ks3-r-card); border: 2px solid var(--ks3-ink);
  overflow: hidden; background: var(--ks3-card);
}
.ks3-bill-sliders {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 14px 20px; padding: 18px;
  border-bottom: 2px solid var(--ks3-ink); background: var(--ks3-inset);
}
.ks3-bill-slider label {
  display: block;
  font-family: var(--ks3-font-mono); font-size: 12px; font-weight: 500;
  letter-spacing: .09em; text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-bill-slider input {
  width: 100%; height: 44px; accent-color: var(--ks3-accent);
  cursor: pointer;
}
/* ⚠️ `position: relative` so nothing inside the scroller resolves against
   the page and widens the document on a phone. */
.ks3-bill-tablewrap { position: relative; overflow-x: auto; }
.ks3-bill-table { width: 100%; border-collapse: collapse; }
.ks3-bill-table th, .ks3-bill-table td {
  padding: 10px 14px; text-align: left;
  border-bottom: 1px solid var(--ks3-rule); font-size: 16px;
  white-space: nowrap;
}
.ks3-bill-table thead th {
  font-family: var(--ks3-font-mono); font-size: 12px; font-weight: 500;
  letter-spacing: .09em; text-transform: uppercase;
  background: var(--ks3-band);
}
.ks3-bill-table tbody th { font-weight: 600; }
/* The two footer rows are the SUM. The standing charge sits above the
   amount due precisely so a student sees it being added. */
.ks3-bill-table tfoot th, .ks3-bill-table tfoot td {
  background: var(--ks3-band); font-weight: 600;
}
.ks3-bill-table tfoot tr:last-child th,
.ks3-bill-table tfoot tr:last-child td {
  border-top: 3px solid var(--ks3-ink);
  font-family: var(--ks3-font-display); font-size: 19px;
}
.ks3-bill-close {
  margin: 0; padding: 18px 20px; border-top: 2px solid var(--ks3-ink);
  background: var(--ks3-band); font-size: 19px; line-height: 1.6;
}

/* p2-05 #s-sort — will it run out */
.ks3-rsort { margin-top: 20px; }
.ks3-rsort-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(238px, 1fr));
  gap: 12px; margin: 0; padding: 0; list-style: none;
}
.ks3-rsort-item {
  padding: 16px 18px; border-radius: var(--ks3-r-panel);
  border: 2px solid var(--ks3-option-border); background: var(--ks3-card);
}
.ks3-rsort-name {
  margin: 0; font-family: var(--ks3-font-display);
  font-weight: 700; font-size: 20px; line-height: 1.2;
}
.ks3-rsort-store {
  margin: 4px 0 12px; font-family: var(--ks3-font-mono);
  font-size: 12px; color: var(--ks3-ink-muted); line-height: 1.4;
}
.ks3-rsort-btns { display: flex; gap: 8px; flex-wrap: wrap; }
/* R3 — a choice shows it was CHOSEN, never whether it was right. */
.ks3-rsort-note {
  margin: 12px 0 0; font-size: 16px; line-height: 1.5;
  color: var(--ks3-ink-body);
}

/* p2-05 #s-grid — the two-axis grid */
.ks3-grid2 { margin-top: 20px; }
.ks3-grid2-tabs { display: flex; gap: 8px; flex-wrap: wrap; }
.ks3-grid2-axislabel {
  margin: 16px 0 8px; font-family: var(--ks3-font-mono);
  font-size: 12px; font-weight: 500; letter-spacing: .09em;
  text-transform: uppercase; color: var(--ks3-ink-muted);
}
/* ⚠️ `position: relative` — the dots are absolutely positioned inside
   this plot, and without a positioned ancestor they resolve against the
   page and widen the document on a phone. */
.ks3-grid2-plot {
  position: relative;
  height: 320px;
  border: 2px solid var(--ks3-ink);
  border-radius: var(--ks3-r-panel);
  background:
    linear-gradient(to right, transparent 49.6%,
      var(--ks3-rule) 49.6%, var(--ks3-rule) 50.4%, transparent 50.4%),
    var(--ks3-inset);
  overflow: hidden;
}
.ks3-grid2-hi, .ks3-grid2-lo, .ks3-grid2-xlab {
  position: absolute; left: 10px;
  font-family: var(--ks3-font-mono); font-size: 11px;
  letter-spacing: .06em; text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-grid2-hi { top: 8px; }
.ks3-grid2-lo { bottom: 26px; }
.ks3-grid2-xlab { bottom: 6px; left: 50%; transform: translateX(-50%); }
/* x is the renewable/finite split and NEVER moves; y is the chosen axis. */
.ks3-grid2-dot {
  --x: .5; --y: .5;
  position: absolute;
  left: calc(var(--x) * 100%);
  bottom: calc(34px + var(--y) * (100% - 76px));
  transform: translate(-50%, 50%);
  width: 14px; height: 14px; border-radius: 50%;
  background: var(--ks3-accent); border: 2px solid var(--ks3-ink);
  transition: bottom .22s ease;
}
.ks3-grid2-dot[data-renew="0"] { background: var(--ks3-blue-tint); }
.ks3-grid2-tag {
  position: absolute; left: 18px; top: -3px;
  white-space: nowrap;
  font-family: var(--ks3-font-mono); font-size: 11px;
  color: var(--ks3-ink);
}
.ks3-grid2-note, .ks3-grid2-close {
  margin: 16px 0 0; padding: 16px 18px;
  border-radius: var(--ks3-r-panel); background: var(--ks3-band);
  border: 2px solid var(--ks3-ink);
  font-size: 18px; line-height: 1.55;
}

/* ═══ END P2 ═══ */

/* ═══ BEGIN P3 ═══════════════════════════════════════════════════════════
   P3 — *Describing motion*. One marked block per unit.

   ⚠️ EVERY SELECTOR HERE IS P3-ONLY, and every shell stem was checked
   against the whole registry before it was written: `ks3-cpair-` was
   already c4's, so the compare block is `ks3-spair-`, and `ks3-jmatch-`
   was taken, so the journey matcher is `ks3-jwalk-`.
   ═══════════════════════════════════════════════════════════════════════ */

/* Shared P3 gates — every bench opens behind a commitment. */
.ks3-lgate-gate, .ks3-rframe-gate {
  padding: 20px 22px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset); border: 2px solid var(--ks3-ink);
}

/* p3-01 #s-track — the light gates. Sits in a DARK practical shell. */
.ks3-lgate { margin-top: 22px; }
.ks3-lgate-bench {
  border-radius: var(--ks3-r-card); border: 2px solid var(--ks3-on-dark-muted);
  overflow: hidden; background: var(--ks3-dark-inset, rgba(255,255,255,.05));
}
.ks3-lgate-ramps {
  display: flex; gap: 8px; flex-wrap: wrap; padding: 16px 18px;
  border-bottom: 2px solid var(--ks3-on-dark-muted);
}
/* ⚠️ `position: relative` — the trolley and both beams are absolutely
   positioned in here. Without a positioned ancestor they resolve against
   the page and widen the document on a phone. */
.ks3-lgate-track {
  position: relative; height: 130px; overflow: hidden;
  border-bottom: 2px solid var(--ks3-on-dark-muted);
}
.ks3-lgate-slope {
  position: absolute; left: 0; bottom: 22px;
  width: 90px; height: 62px;
  background: var(--ks3-on-dark-muted);
  clip-path: polygon(0 0, 100% 100%, 0 100%);
}
.ks3-lgate-trolley {
  position: absolute; bottom: 24px; left: 16%;
  width: 26px; height: 16px; border-radius: 4px;
  background: var(--ks3-accent); border: 2px solid var(--ks3-on-dark);
  transition: left .7s linear;
}
.ks3-lgate-trolley[data-rolling="1"] { left: 78%; }
/* The two beams. The timer runs only between them, which is what makes the
   reading a speed over a KNOWN distance. */
.ks3-lgate-beam {
  position: absolute; bottom: 18px; width: 3px; height: 74px;
  background: var(--ks3-alert, #FFD93D);
}
.ks3-lgate-beam.is-a { left: 18%; }
.ks3-lgate-sliderlabel {
  display: block; padding: 16px 18px 0;
  font-family: var(--ks3-font-mono); font-size: 12px; font-weight: 500;
  letter-spacing: .09em; text-transform: uppercase;
  color: var(--ks3-on-dark-body);
}
.ks3-lgate-slider {
  display: block; width: calc(100% - 36px); margin: 0 18px;
  height: 44px; accent-color: var(--ks3-accent); cursor: pointer;
}
.ks3-lgate-outs {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 12px; padding: 16px 18px 0;
}
.ks3-lgate-outlabel {
  margin: 0; font-family: var(--ks3-font-mono);
  font-size: 12px; font-weight: 500; letter-spacing: .09em;
  text-transform: uppercase; color: var(--ks3-on-dark-body);
}
.ks3-lgate-outval {
  margin: 4px 0 0; font-family: var(--ks3-font-display);
  font-weight: 700; font-size: 28px; line-height: 1.1;
  color: var(--ks3-on-dark);
}
/* ⚖️ THE SPEED TILE IS NOT A READING. It is smaller, unemphasised and
   never written to — the instrument refuses to do the arithmetic, and
   the tile has to LOOK like a refusal rather than a pending value. */
.ks3-lgate-outval.is-unmeasured {
  font-family: var(--ks3-font-body); font-weight: 600;
  font-size: 16px; line-height: 1.35; color: var(--ks3-on-dark-body);
}
.ks3-lgate-acts {
  display: flex; gap: 12px; flex-wrap: wrap; padding: 16px 18px;
}
.ks3-lgate-tablewrap {
  position: relative; overflow-x: auto;
  border-top: 2px solid var(--ks3-on-dark-muted);
}
.ks3-lgate-table { width: 100%; border-collapse: collapse; }
.ks3-lgate-table th, .ks3-lgate-table td {
  padding: 10px 14px; text-align: left; font-size: 16px;
  color: var(--ks3-on-dark);
  border-bottom: 1px solid var(--ks3-on-dark-muted);
}
.ks3-lgate-table thead th {
  font-family: var(--ks3-font-mono); font-size: 12px; font-weight: 500;
  letter-spacing: .09em; text-transform: uppercase;
  color: var(--ks3-on-dark-body);
}
.ks3-lgate-close {
  margin: 0; padding: 18px 20px;
  border-top: 2px solid var(--ks3-on-dark-muted);
  font-size: 19px; line-height: 1.6; color: var(--ks3-on-dark-body);
}

/* p3-01 #s-compare — three pairs */
.ks3-spair { margin-top: 20px; }
.ks3-spair-list {
  display: grid; gap: 14px; margin: 0; padding: 0; list-style: none;
}
.ks3-spair-row {
  padding: 16px 18px; border-radius: var(--ks3-r-panel);
  border: 2px solid var(--ks3-option-border); background: var(--ks3-card);
}
.ks3-spair-label {
  margin: 0 0 12px; font-family: var(--ks3-font-mono);
  font-size: 12px; font-weight: 500; letter-spacing: .09em;
  text-transform: uppercase; color: var(--ks3-ink-muted);
}
.ks3-spair-picks { display: flex; gap: 8px; flex-wrap: wrap; }
/* The ARITHMETIC first, the reason after — two numbers, not a mark. */
.ks3-spair-sums {
  margin: 14px 0 0; font-family: var(--ks3-font-mono);
  font-size: 16px; color: var(--ks3-accent-text);
}
.ks3-spair-why {
  margin: 8px 0 0; font-size: 17px; line-height: 1.5;
  color: var(--ks3-ink-body);
}
.ks3-spair-close {
  margin: 16px 0 0; padding: 16px 18px;
  border-radius: var(--ks3-r-panel); background: var(--ks3-band);
  border: 2px solid var(--ks3-ink); font-size: 18px; line-height: 1.55;
}

/* p3-02 #s-plot — a grid of REAL BUTTONS, never a canvas */
.ks3-gplot { margin-top: 20px; }
.ks3-gplot-seek {
  margin: 0 0 12px; font-family: var(--ks3-font-mono);
  font-size: 13px; color: var(--ks3-accent-text);
}
.ks3-gplot-wrap { display: grid; gap: 6px; }
.ks3-gplot-ylab, .ks3-gplot-xlab {
  font-family: var(--ks3-font-mono); font-size: 11px;
  letter-spacing: .06em; text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-gplot-xlab { text-align: center; }
/* ⚠️ `position: relative` — the joining line is an overlaid SVG. */
.ks3-gplot-grid {
  position: relative;
  display: grid;
  grid-template-columns: repeat(var(--cols), 1fr);
  grid-template-rows: repeat(var(--rows), 1fr);
  aspect-ratio: 3 / 2;
  border: 2px solid var(--ks3-ink); border-radius: var(--ks3-r-panel);
  background: var(--ks3-inset);
}
.ks3-gplot-cell {
  border: 1px solid var(--ks3-rule); background: transparent;
  cursor: pointer; padding: 0; min-width: 0; min-height: 0;
}
.ks3-gplot-cell:hover { background: var(--ks3-band); }
.ks3-gplot-cell[data-plotted="1"] {
  background: radial-gradient(circle 5px at 50% 50%,
    var(--ks3-accent) 99%, transparent);
}
.ks3-gplot-line {
  position: absolute; inset: 0; width: 100%; height: 100%;
  pointer-events: none; color: var(--ks3-accent-text);
}
.ks3-gplot-join { margin-top: 12px; }
.ks3-gplot-reads {
  display: grid; gap: 12px; margin: 18px 0 0; padding: 0; list-style: none;
}
.ks3-gplot-read {
  padding: 14px 16px; border-radius: var(--ks3-r-panel);
  border: 2px solid var(--ks3-option-border); background: var(--ks3-card);
}
.ks3-gplot-q { margin: 0 0 10px; font-size: 17px; font-weight: 600; }
.ks3-gplot-opts { display: flex; gap: 8px; flex-wrap: wrap; }
.ks3-gplot-why {
  margin: 12px 0 0; font-size: 16px; line-height: 1.5;
  color: var(--ks3-ink-body);
}
.ks3-gplot-close {
  margin: 16px 0 0; padding: 16px 18px;
  border-radius: var(--ks3-r-panel); background: var(--ks3-band);
  border: 2px solid var(--ks3-ink); font-size: 18px; line-height: 1.55;
}

/* p3-02 #s-match — walk the graph. DARK practical shell. */
.ks3-jwalk { margin-top: 20px; }
/* ⚠️ `position: relative` — the walker is absolutely positioned. */
.ks3-jwalk-corridor {
  position: relative; height: 54px;
  border: 2px solid var(--ks3-on-dark-muted);
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-dark-inset, rgba(255,255,255,.05));
  overflow: hidden;
}
.ks3-jwalk-walker {
  position: absolute; top: 14px; left: 0;
  width: 22px; height: 22px; border-radius: 50%;
  background: var(--ks3-accent); border: 2px solid var(--ks3-on-dark);
  transition: left .3s linear;
}
.ks3-jwalk-read {
  margin: 10px 0 0; font-family: var(--ks3-font-mono);
  font-size: 15px; color: var(--ks3-on-dark);
  min-height: 1.2em;
}
.ks3-jwalk-graph {
  display: block; width: 100%; height: auto; margin-top: 12px;
  border: 2px solid var(--ks3-on-dark-muted);
  border-radius: var(--ks3-r-panel);
  background: var(--ks3-dark-inset, rgba(255,255,255,.05));
}
.ks3-jwalk-targetline {
  fill: none; stroke: var(--ks3-on-dark-muted);
  stroke-width: 3; stroke-dasharray: 7 6;
}
.ks3-jwalk-line { fill: none; stroke: var(--ks3-accent); stroke-width: 4; }
.ks3-jwalk-segs {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 12px; margin-top: 16px;
}
.ks3-jwalk-seglabel {
  margin: 0 0 8px; font-family: var(--ks3-font-mono);
  font-size: 11px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--ks3-on-dark-body);
}
.ks3-jwalk-modes { display: flex; gap: 6px; flex-wrap: wrap; }
.ks3-jwalk-acts { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 16px; }
/* R3 — a FACTUAL end-point comparison. No colour, no verdict. */
.ks3-jwalk-verdict, .ks3-jwalk-close {
  margin: 16px 0 0; font-size: 18px; line-height: 1.55;
  color: var(--ks3-on-dark-body);
}

/* p3-03 #s-frames — three viewpoints. DARK practical shell. */
.ks3-rframe { margin-top: 22px; }
.ks3-rframe-bench {
  border-radius: var(--ks3-r-card); border: 2px solid var(--ks3-on-dark-muted);
  overflow: hidden; background: var(--ks3-dark-inset, rgba(255,255,255,.05));
}
.ks3-rframe-picks {
  display: flex; gap: 8px; flex-wrap: wrap; padding: 16px 18px;
  border-bottom: 2px solid var(--ks3-on-dark-muted);
}
/* ⚠️ `position: relative` — both cars and the label are absolutely
   positioned inside the scene. */
.ks3-rframe-scene {
  position: relative; height: 150px; overflow: hidden;
  border-bottom: 2px solid var(--ks3-on-dark-muted);
}
/* The road belongs to the GROUND, so it slides in a car's frame. */
.ks3-rframe-road {
  position: absolute; left: 0; right: 0; top: 62px; height: 6px;
  background: repeating-linear-gradient(to right,
    var(--ks3-on-dark-muted) 0 26px, transparent 26px 52px);
}
.ks3-rframe-road[data-slide="1"] {
  animation: ks3-rframe-slide 1.1s linear infinite;
}
@keyframes ks3-rframe-slide {
  from { background-position-x: 0; }
  to   { background-position-x: -52px; }
}
@media (prefers-reduced-motion: reduce) {
  .ks3-rframe-road[data-slide="1"] { animation: none; }
  .ks3-lgate-trolley, .ks3-jwalk-walker { transition: none; }
}
.ks3-rframe-car {
  position: absolute; display: grid; place-items: center;
  width: 46px; height: 26px; border-radius: 6px;
  font-family: var(--ks3-font-display); font-weight: 800; font-size: 15px;
  color: var(--ks3-ink); border: 2px solid var(--ks3-on-dark);
}
.ks3-rframe-car.is-a { top: 26px; left: 20%; background: var(--ks3-accent); }
.ks3-rframe-car.is-b { top: 86px; left: 56%; background: var(--ks3-blue-tint); }
/* ⚠️ Orientation follows the GROUND velocity, never the relative one.
   Deliberate — see the note in ks3_art/p3.py. */
.ks3-rframe-car[data-facing="l"] { transform: scaleX(-1); }
.ks3-rframe-still {
  position: absolute; right: 12px; bottom: 10px;
  font-family: var(--ks3-font-mono); font-size: 12px;
  letter-spacing: .06em; color: var(--ks3-alert, #FFD93D);
}
.ks3-rframe-controls { padding: 16px 18px; display: grid; gap: 8px; }
.ks3-rframe-controls label {
  font-family: var(--ks3-font-mono); font-size: 12px; font-weight: 500;
  letter-spacing: .09em; text-transform: uppercase;
  color: var(--ks3-on-dark-body);
}
.ks3-rframe-controls input {
  width: 100%; height: 44px; accent-color: var(--ks3-accent);
  cursor: pointer;
}
.ks3-rframe-outs {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 12px; padding: 0 18px 16px;
}
.ks3-rframe-outlabel {
  margin: 0; font-family: var(--ks3-font-mono);
  font-size: 12px; font-weight: 500; letter-spacing: .09em;
  text-transform: uppercase; color: var(--ks3-on-dark-body);
}
.ks3-rframe-outval {
  margin: 4px 0 0; font-family: var(--ks3-font-display);
  font-weight: 700; font-size: 26px; line-height: 1.1;
  color: var(--ks3-on-dark);
}
/* The reading that belongs to the current viewpoint is outlined, not
   isolated — the other three stay readable, which is the comparison. */
.ks3-rframe-out[data-live="1"] {
  outline: 2px solid var(--ks3-alert, #FFD93D);
  outline-offset: 4px; border-radius: 6px;
}
.ks3-rframe-sent, .ks3-rframe-close {
  margin: 0; padding: 16px 18px;
  border-top: 2px solid var(--ks3-on-dark-muted);
  font-size: 18px; line-height: 1.55; color: var(--ks3-on-dark-body);
}

/* p3-03 #s-pass — four passes */
.ks3-pass { margin-top: 20px; }
.ks3-pass-list {
  display: grid; gap: 14px; margin: 0; padding: 0; list-style: none;
}
.ks3-pass-row {
  padding: 16px 18px; border-radius: var(--ks3-r-panel);
  border: 2px solid var(--ks3-option-border); background: var(--ks3-card);
}
.ks3-pass-label {
  margin: 0 0 8px; font-family: var(--ks3-font-mono);
  font-size: 12px; font-weight: 500; letter-spacing: .09em;
  text-transform: uppercase; color: var(--ks3-ink-muted);
}
.ks3-pass-q { margin: 0 0 12px; font-size: 18px; line-height: 1.45; }
.ks3-pass-opts { display: flex; gap: 8px; flex-wrap: wrap; }
/* The SUM first — an addition or a subtraction, never a product, which
   is why this lesson carries no formula triangle. */
.ks3-pass-sum {
  margin: 14px 0 0; font-family: var(--ks3-font-mono);
  font-size: 18px; color: var(--ks3-accent-text);
}
.ks3-pass-why {
  margin: 8px 0 0; font-size: 17px; line-height: 1.5;
  color: var(--ks3-ink-body);
}
.ks3-pass-close {
  margin: 16px 0 0; padding: 16px 18px;
  border-radius: var(--ks3-r-panel); background: var(--ks3-band);
  border: 2px solid var(--ks3-ink); font-size: 18px; line-height: 1.55;
}

/* ═══ END P3 ═══ */



/* ═══ BEGIN P4 ═══════════════════════════════════════════════════════════
   P4 — *Forces*. One marked block per unit.

   ⚠️ EVERY SELECTOR HERE IS P4-ONLY, and every shell stem was checked
   against the whole registry before it was written. `ks3-srig-` was already
   C6's `step-rig`, and `ks3_art.load()` caught it on the first run, so the
   support rig is `ks3-hrig-`.

   ⚠️ EVERY FIGURE WRAPPER IS `position: relative`. Design's own note for
   the generator: every live value on a P4 diagram is an absolutely
   positioned HTML `<span>` over the SVG, because a `{{ hole }}` used as
   text inside an SVG `<text>` renders NOTHING — the runtime wraps
   interpolated text in a `<span>`, and a `span` created in the SVG
   namespace is not a renderable element, and it fails silently. Without a
   positioned ancestor those spans resolve against the page and widen the
   document on a phone, which is the second half of the same rule.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── shared P4 shapes ───────────────────────────────────────────────── */

.ks3-iboard-gate, .ks3-rbench-gate, .ks3-hrig-gate, .ks3-grun-gate,
.ks3-dlane-gate, .ks3-fall-gate, .ks3-span-gate, .ks3-splot-gate {
  padding: 20px 22px; border-radius: var(--ks3-r-panel);
  background: rgba(255, 255, 255, .06);
  border: 2px solid var(--ks3-on-dark-muted);
}

.ks3-iboard-head, .ks3-rbench-head, .ks3-hrig-head, .ks3-grun-head,
.ks3-dlane-head, .ks3-fall-head, .ks3-span-head, .ks3-splot-head,
.ks3-fsort-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 20px; flex-wrap: wrap; margin-bottom: 20px;
}
.ks3-iboard-head h2, .ks3-rbench-head h2, .ks3-hrig-head h2,
.ks3-grun-head h2, .ks3-dlane-head h2, .ks3-fall-head h2,
.ks3-span-head h2, .ks3-splot-head h2, .ks3-fsort-head h2 {
  margin: 0; font-size: 34px; line-height: 1.12; letter-spacing: -.03em;
  color: var(--ks3-on-dark);
}
.ks3-iboard-progress, .ks3-rbench-progress, .ks3-hrig-progress,
.ks3-grun-progress, .ks3-dlane-progress, .ks3-fall-progress,
.ks3-span-progress, .ks3-splot-progress, .ks3-fsort-progress {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 15px;
  font-weight: 500; color: var(--ks3-on-dark-muted);
}

.ks3-grun-lead, .ks3-dlane-lead, .ks3-fall-lead, .ks3-span-lead,
.ks3-splot-lead, .ks3-fsort-lead {
  margin: 0 0 18px; font-size: 18px; line-height: 1.55;
  color: var(--ks3-on-dark-body);
}

/* The readout row every bench closes on. */
.ks3-iboard-tiles, .ks3-rbench-tiles, .ks3-hrig-tiles, .ks3-grun-tiles,
.ks3-dlane-tiles, .ks3-fall-tiles, .ks3-span-tiles, .ks3-splot-tiles,
.ks3-fsort-tiles {
  margin-top: 16px; display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr));
  gap: 12px;
}
.ks3-iboard-tile, .ks3-rbench-tile, .ks3-hrig-tile, .ks3-grun-tile,
.ks3-dlane-tile, .ks3-fall-tile, .ks3-span-tile, .ks3-splot-tile,
.ks3-fsort-tile {
  padding: 15px 17px; border-radius: var(--ks3-r-panel);
  background: rgba(255, 255, 255, .07);
}
.ks3-iboard-tile-label, .ks3-rbench-tile-label, .ks3-hrig-tile-label,
.ks3-grun-tile-label, .ks3-dlane-tile-label, .ks3-fall-tile-label,
.ks3-span-tile-label, .ks3-splot-tile-label, .ks3-fsort-tile-label {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-iboard-tile-value, .ks3-rbench-tile-value, .ks3-hrig-tile-value,
.ks3-grun-tile-value, .ks3-dlane-tile-value, .ks3-fall-tile-value,
.ks3-span-tile-value, .ks3-splot-tile-value, .ks3-fsort-tile-value {
  margin: 5px 0 0; font-family: var(--ks3-font-mono); font-size: 20px;
  font-weight: 500; color: var(--ks3-on-dark);
}

/* The closing note. */
.ks3-iboard-live, .ks3-rbench-note, .ks3-hrig-note, .ks3-grun-note,
.ks3-dlane-note, .ks3-dlane-compare, .ks3-fall-note, .ks3-span-note,
.ks3-splot-note, .ks3-fsort-note {
  margin: 16px 0 0; padding: 16px 18px;
  border-radius: var(--ks3-r-panel); background: rgba(255, 255, 255, .07);
  font-size: 18px; line-height: 1.55; color: var(--ks3-on-dark-body);
}
.ks3-dlane-compare { margin-top: 10px; }

/* ⚠️ POSITIONED, ALWAYS. See the block note above. */
.ks3-iboard-figwrap, .ks3-rbench-figwrap, .ks3-hrig-figwrap,
.ks3-grun-figwrap, .ks3-dlane-figwrap, .ks3-fall-figwrap,
.ks3-span-figwrap, .ks3-splot-figwrap, .ks3-fsort-figwrap {
  position: relative; margin-top: 20px;
}
.ks3-iboard-svg, .ks3-rbench-svg, .ks3-hrig-svg, .ks3-grun-svg,
.ks3-dlane-svg, .ks3-fall-svg, .ks3-span-svg, .ks3-splot-spring,
.ks3-splot-graph, .ks3-fsort-svg {
  display: block; width: 100%; height: auto;
}
.ks3-iboard-fill, .ks3-rbench-fill, .ks3-hrig-fill, .ks3-grun-fill,
.ks3-dlane-fill, .ks3-fall-fill, .ks3-span-fill, .ks3-splot-fill,
.ks3-fsort-fill {
  position: absolute; font-family: var(--ks3-font-mono); font-weight: 500;
  font-size: clamp(10px, 1.5vw, 19px); white-space: nowrap;
  pointer-events: none;
}

/* The picker rows. */
.ks3-iboard-tabslabel, .ks3-hrig-pickerlabel, .ks3-grun-pickerlabel,
.ks3-dlane-pickerlabel, .ks3-fall-pickerlabel, .ks3-span-pickerlabel {
  margin: 0 0 9px; font-family: var(--ks3-font-mono); font-size: 14px;
  font-weight: 500; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-iboard-tabrow, .ks3-hrig-tabrow, .ks3-grun-tabrow, .ks3-dlane-tabrow,
.ks3-fall-tabrow, .ks3-span-tabrow, .ks3-fsort-tabrow {
  display: flex; gap: 9px; flex-wrap: wrap;
}
.ks3-grun-controls, .ks3-dlane-controls, .ks3-hrig-controls,
.ks3-fall-controls, .ks3-span-controls, .ks3-splot-controls {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
  gap: 18px;
}

/* The sliders. */
.ks3-rbench-row, .ks3-hrig-row, .ks3-fall-row, .ks3-span-row,
.ks3-splot-row { margin-top: 4px; }
.ks3-rbench-rowhead, .ks3-hrig-rowhead, .ks3-fall-rowhead,
.ks3-span-rowhead, .ks3-splot-rowhead {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px;
}
.ks3-rbench-rowhead label, .ks3-hrig-rowhead label, .ks3-fall-rowhead label,
.ks3-span-rowhead label, .ks3-splot-rowhead label {
  font-family: var(--ks3-font-mono); font-size: 14px; font-weight: 500;
  letter-spacing: .07em; text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-rbench-reading, .ks3-hrig-reading, .ks3-fall-reading,
.ks3-span-reading, .ks3-splot-reading {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 20px;
  font-weight: 500; color: var(--ks3-on-dark);
}
.ks3-rbench-slider, .ks3-hrig-slider, .ks3-fall-slider, .ks3-span-slider,
.ks3-splot-slider {
  width: 100%; margin-top: 8px; accent-color: var(--ks3-accent);
  min-height: 44px;
}
.ks3-rbench-controls { display: grid; gap: 16px; }

/* ── p4-01 · the interaction board ──────────────────────────────────── */

.ks3-iboard-stage {
  margin-top: 20px; padding: 24px; border-radius: var(--ks3-r-card);
  background: var(--ks3-dark-panel);
}
.ks3-iboard-prompt {
  margin: 0; font-family: var(--ks3-font-display); font-weight: 800;
  font-size: 26px; line-height: 1.2; letter-spacing: -.02em;
  color: var(--ks3-on-dark);
}
.ks3-iboard-ask {
  margin-top: 16px; display: flex; flex-direction: column; gap: 9px;
  max-width: 34rem;
}
.ks3-iboard-opt {
  font: inherit; font-size: 16px; font-weight: 600; text-align: left;
  padding: 11px 16px; min-height: 44px;
  border-radius: var(--ks3-r-control);
  border: 2px solid var(--ks3-on-dark-muted); background: transparent;
  color: var(--ks3-on-dark); cursor: pointer;
}
.ks3-iboard-opt[aria-pressed="true"] {
  background: var(--ks3-blue-light); border-color: var(--ks3-blue-light);
  color: var(--ks3-ink);
}
.ks3-iboard-opt[disabled] { cursor: default; }
.ks3-iboard-opt[disabled][aria-pressed="false"] { opacity: .45; }
.ks3-iboard-body { fill: #15110C; stroke: var(--ks3-on-dark-muted);
  stroke-width: 3; }
.ks3-iboard-shaft { fill: none; stroke: #8FB7FF; stroke-width: 7;
  stroke-linecap: round; }
.ks3-iboard-head { fill: #8FB7FF; }
.ks3-iboard-touch { fill: var(--ks3-on-dark-muted); }
.ks3-iboard-gapline { fill: none; stroke: var(--ks3-on-dark-muted);
  stroke-width: 4; stroke-dasharray: 12 12; }
.ks3-iboard-name-a, .ks3-iboard-name-b {
  font-family: var(--ks3-font-display); font-weight: 800;
  font-size: clamp(15px, 2vw, 26px);
}
.ks3-iboard-cap-a, .ks3-iboard-cap-b {
  font-family: var(--ks3-font-body); font-weight: 600;
  font-size: clamp(11px, 1.55vw, 20px);
}
.ks3-iboard-join { letter-spacing: .08em; }

/* ── p4-02 · the sledge on ice ──────────────────────────────────────── */

.ks3-rbench-ice, .ks3-rbench-base {
  fill: none; stroke: var(--ks3-on-dark-muted); stroke-width: 3;
}
.ks3-rbench-base { stroke-dasharray: 10 10; }
.ks3-rbench-sledge { fill: #15110C; stroke: var(--ks3-on-dark-muted);
  stroke-width: 3; }
.ks3-rbench-caption {
  font-family: var(--ks3-font-display); font-weight: 800; font-size: 26px;
  fill: #FBF3E6;
}
.ks3-rbench-baselabel {
  font-family: var(--ks3-font-mono); font-size: 16px; letter-spacing: .08em;
  fill: #C6B9A7;
}
.ks3-rbench-shaft { fill: none; stroke: #8FB7FF; stroke-width: 8;
  stroke-linecap: round; }
.ks3-rbench-head { fill: #8FB7FF; }
.ks3-rbench-res.ks3-rbench-shaft { stroke: #FFC53D; }
.ks3-rbench-res.ks3-rbench-head { fill: #FFC53D; }
.ks3-rbench-nores {
  font-family: var(--ks3-font-mono); font-size: 20px; fill: #C6B9A7;
}

/* ── p4-03 · the support rig ────────────────────────────────────────── */

.ks3-hrig-picker { display: grid; gap: 0; }
.ks3-hrig-slab { fill: var(--ks3-on-dark-muted); }
.ks3-hrig-hatch { fill: none; stroke: var(--ks3-on-dark-muted);
  stroke-width: 3; }
.ks3-hrig-sheet { fill: none; stroke: #C6B9A7; stroke-width: 5;
  stroke-linecap: round; }
.ks3-hrig-torn { stroke-dasharray: none; }
.ks3-hrig-coil { fill: none; stroke: #C6B9A7; stroke-width: 5;
  stroke-linejoin: round; }
.ks3-hrig-load { fill: #15110C; stroke: var(--ks3-on-dark-muted);
  stroke-width: 3; }
.ks3-hrig-shaft { fill: none; stroke: #8FB7FF; stroke-width: 8;
  stroke-linecap: round; }
.ks3-hrig-head { fill: #8FB7FF; }
.ks3-hrig-over.ks3-hrig-shaft { stroke: #FFC53D; }
.ks3-hrig-over.ks3-hrig-head { fill: #FFC53D; }
.ks3-hrig-word { letter-spacing: .08em; font-size: clamp(10px, 1.4vw, 17px); }

/* ── p4-04 · the trolley and the light gates ────────────────────────── */

.ks3-grun-rail { fill: none; stroke: var(--ks3-on-dark-muted);
  stroke-width: 4; }
.ks3-grun-gate { fill: none; stroke: #FFC53D; stroke-width: 4; }
.ks3-grun-gatelabel {
  font-family: var(--ks3-font-mono); font-size: 17px; fill: #C6B9A7;
}
.ks3-grun-track { fill: none; stroke: #C6B9A7; stroke-width: 3;
  stroke-dasharray: 12 10; }
.ks3-grun-trolley { fill: var(--ks3-accent); stroke: var(--ks3-on-dark);
  stroke-width: 3; }
.ks3-grun-shaft { fill: none; stroke: #FFC53D; stroke-width: 8;
  stroke-linecap: round; }
.ks3-grun-head { fill: #FFC53D; }

/* ── p4-05 · block and spring balance ───────────────────────────────── */

.ks3-dlane-floor { fill: none; stroke: var(--ks3-on-dark-muted);
  stroke-width: 4; }
.ks3-dlane-texture { fill: none; stroke: #C6B9A7; stroke-width: 3; }
.ks3-dlane-block { fill: #15110C; stroke: var(--ks3-on-dark-muted);
  stroke-width: 3; }
.ks3-dlane-lanelabel {
  font-family: var(--ks3-font-mono); font-size: 17px; letter-spacing: .08em;
  fill: #C6B9A7;
}
.ks3-dlane-shaft { fill: none; stroke: #8FB7FF; stroke-width: 7;
  stroke-linecap: round; }
.ks3-dlane-head { fill: #8FB7FF; }
.ks3-dlane-fric.ks3-dlane-shaft { stroke: #C6B9A7; }
.ks3-dlane-fric.ks3-dlane-head { fill: #C6B9A7; }

/* ── p4-06 · the fall ───────────────────────────────────────────────── */

.ks3-fall-streaks { fill: none; stroke: #C6B9A7; stroke-width: 3;
  opacity: .55; }
.ks3-fall-body { fill: #15110C; stroke: var(--ks3-on-dark-muted);
  stroke-width: 3; }
.ks3-fall-shaft { fill: none; stroke: #8FB7FF; stroke-width: 8;
  stroke-linecap: round; }
.ks3-fall-head { fill: #8FB7FF; }
.ks3-fall-drag.ks3-fall-shaft { stroke: #C6B9A7; }
.ks3-fall-drag.ks3-fall-head { fill: #C6B9A7; }
.ks3-fall-over.ks3-fall-shaft { stroke: #FFC53D; }
.ks3-fall-over.ks3-fall-head { fill: #FFC53D; }
.ks3-fall-nodrag {
  font-family: var(--ks3-font-mono); font-size: 20px; fill: #C6B9A7;
}
.ks3-fall-word { letter-spacing: .08em; font-size: clamp(10px, 1.4vw, 17px); }

/* ── p4-07 · the spanner and the nut ────────────────────────────────── */

.ks3-span-nut { fill: #15110C; stroke: var(--ks3-on-dark-muted);
  stroke-width: 4; }
.ks3-span-pivot { fill: var(--ks3-accent); }
.ks3-span-handle { fill: #C6B9A7; }
.ks3-span-grip { fill: #15110C; stroke: #C6B9A7; stroke-width: 3; }
.ks3-span-shaft { fill: none; stroke: #8FB7FF; stroke-width: 8;
  stroke-linecap: round; }
.ks3-span-head { fill: #8FB7FF; }
.ks3-span-dim { fill: none; stroke: var(--ks3-on-dark-muted);
  stroke-width: 2; }
/* Solid when the moment clears the threshold, dashed when it does not —
   and the verdict is also a WORD. Hue is never the only channel. */
.ks3-span-turn { fill: none; stroke: #FFC53D; stroke-width: 6;
  stroke-linecap: round; }
.ks3-span-turn-label { letter-spacing: .08em; }

/* ── p4-08 · loading a spring ───────────────────────────────────────── */

.ks3-splot-figs {
  display: grid;
  grid-template-columns: minmax(160px, 1fr) minmax(min(320px, 100%), 2.4fr);
  gap: 18px; align-items: start; margin-top: 20px;
}
@media (max-width: 620px) {
  .ks3-splot-figs { grid-template-columns: 1fr; }
}
.ks3-splot-btns { display: flex; gap: 9px; flex-wrap: wrap;
  align-items: flex-end; }
.ks3-splot-clamp { fill: none; stroke: var(--ks3-on-dark-muted);
  stroke-width: 8; stroke-linecap: round; }
.ks3-splot-natural { fill: none; stroke: #C6B9A7; stroke-width: 2;
  stroke-dasharray: 8 8; }
.ks3-splot-coil { fill: none; stroke: #C6B9A7; stroke-width: 5;
  stroke-linejoin: round; }
.ks3-splot-hanger { fill: var(--ks3-accent); stroke: var(--ks3-on-dark);
  stroke-width: 3; }
.ks3-splot-extbar { fill: none; stroke: #8FB7FF; stroke-width: 3; }
.ks3-splot-axis { fill: none; stroke: var(--ks3-on-dark-muted);
  stroke-width: 4; }
.ks3-splot-grid { fill: none; stroke: var(--ks3-on-dark-muted);
  stroke-width: 2; }
/* The line of proportionality the first readings make. Dashed, because it
   is a prediction rather than a measurement. */
.ks3-splot-prop { fill: none; stroke: #C6B9A7; stroke-width: 3;
  stroke-dasharray: 10 8; }
.ks3-splot-line { fill: none; stroke: #8FB7FF; stroke-width: 3; }
.ks3-splot-dots { fill: var(--ks3-accent); stroke: none; }
.ks3-splot-axislabel {
  font-family: var(--ks3-font-mono); font-size: 18px; fill: #C6B9A7;
}

/* ── p4-09 · the sorter ─────────────────────────────────────────────── */

.ks3-fsort-title {
  margin: 18px 0 0; font-family: var(--ks3-font-display); font-weight: 800;
  font-size: 26px; line-height: 1.2; letter-spacing: -.02em;
  color: var(--ks3-on-dark);
}
.ks3-fsort-body { fill: #15110C; stroke: var(--ks3-on-dark-muted);
  stroke-width: 3; }
.ks3-fsort-shaft { fill: none; stroke: #8FB7FF; stroke-width: 7;
  stroke-linecap: round; }
.ks3-fsort-head { fill: #8FB7FF; }
.ks3-fsort-gap { fill: none; stroke: var(--ks3-on-dark-muted);
  stroke-width: 4; stroke-dasharray: 12 12; }
.ks3-fsort-touch { fill: none; stroke: var(--ks3-on-dark-muted);
  stroke-width: 10; stroke-linecap: round; }
.ks3-fsort-joinword {
  font-family: var(--ks3-font-mono); font-size: 18px; letter-spacing: .08em;
  fill: #C6B9A7;
}
.ks3-fsort-askhead {
  margin: 20px 0 9px; font-family: var(--ks3-font-mono); font-size: 14px;
  font-weight: 500; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-fsort-labels { display: flex; gap: 9px; flex-wrap: wrap; }
.ks3-fsort-label {
  font: inherit; font-size: 16px; font-weight: 700;
  padding: 11px 16px; min-height: 44px;
  border-radius: var(--ks3-r-control);
  border: 2px solid var(--ks3-on-dark-muted); background: transparent;
  color: var(--ks3-on-dark); cursor: pointer;
}
.ks3-fsort-label[aria-pressed="true"] {
  background: var(--ks3-blue-light); border-color: var(--ks3-blue-light);
  color: var(--ks3-ink);
}
.ks3-fsort-label[disabled] { cursor: default; }
.ks3-fsort-label[disabled][aria-pressed="false"] { opacity: .45; }
/* The unlabelled state says so in WORDS. Nothing is revealed until the
   student commits to one of the four. */
.ks3-fsort-sealed {
  margin: 16px 0 0; font-size: 17px; line-height: 1.5;
  color: var(--ks3-on-dark-muted);
}

/* ── the band block · #s-pairs · #s-three · #s-rules · #s-stages ─────── */

.ks3-fband-heading {
  margin: 4px 0 0; font-family: var(--ks3-font-display); font-weight: 800;
  font-size: clamp(26px, 3.6vw, 40px); line-height: 1.1;
  letter-spacing: -.03em; max-width: 26ch;
}
.ks3-fband-lead {
  margin: 12px 0 0; font-size: 18px; line-height: 1.55;
  color: var(--ks3-ink-body);
}
.ks3-fband-strip {
  margin-top: 22px; overflow-x: auto; -webkit-overflow-scrolling: touch;
}
.ks3-fband-strip svg { display: block; min-width: 560px; width: 100%;
  height: auto; }
.ks3-fband-list {
  list-style: none; margin: 24px 0 0; padding: 0;
  display: flex; flex-direction: column; gap: 12px;
}
.ks3-fband-item {
  display: grid; grid-template-columns: auto 1fr; gap: 8px 18px;
  padding: 20px 24px; border-radius: var(--ks3-r-card);
  background: var(--ks3-card); border: 2px solid var(--ks3-ink);
}
.ks3-fband-num {
  grid-row: span 3; display: grid; place-items: center;
  width: 38px; height: 38px; border-radius: 11px;
  background: var(--ks3-accent); color: var(--ks3-on-dark);
  font-family: var(--ks3-font-display); font-weight: 800; font-size: 19px;
}
.ks3-fband-name {
  margin: 0; font-family: var(--ks3-font-display); font-weight: 800;
  font-size: 22px; line-height: 1.2; letter-spacing: -.02em;
}
.ks3-fband-body { margin: 0; font-size: 18px; line-height: 1.55; }
.ks3-fband-tell {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 16px;
  line-height: 1.5; color: var(--ks3-accent-text);
}
.ks3-fband-cols {
  margin-top: 20px; display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
  gap: 14px;
}
.ks3-fband-col {
  padding: 18px 22px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-card); border: 2px solid var(--ks3-ink);
}
.ks3-fband-coltitle {
  margin: 0 0 10px; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; letter-spacing: .09em; text-transform: uppercase;
  color: var(--ks3-accent-text);
}
.ks3-fband-collist { margin: 0; padding-left: 1.15em; font-size: 17px;
  line-height: 1.7; }
.ks3-fband-close {
  margin: 20px 0 0; font-size: 18px; line-height: 1.55;
  color: var(--ks3-ink-body);
}

/* ── the P4 formula figures ─────────────────────────────────────────── */
/* ⚠️ NONE OF THESE IS A TRIANGLE. p4-07 is the unit's only product and it
   takes the engine's own `.ks3-triangle` path; every figure below draws a
   sum, a difference or a ratio. */

.ks3-p4fig { display: block; width: 100%; height: auto; }
.ks3-p4fig-beam, .ks3-p4fig-spring, .ks3-p4fig-strip { min-width: 520px; }
.ks3-formula-figure:has(.ks3-p4fig) {
  overflow-x: auto; -webkit-overflow-scrolling: touch;
}
.ks3-p4fig-bar { fill: var(--ks3-blue-tint); stroke: var(--ks3-ink);
  stroke-width: 3; }
.ks3-p4fig-head { fill: var(--ks3-ink); }
.ks3-p4fig-shaft { fill: none; stroke: var(--ks3-ink); stroke-width: 6;
  stroke-linecap: round; }
/* The two tie-lines that make "the parts fill the whole" a drawn claim
   rather than a sentence. */
.ks3-p4fig-tie { fill: none; stroke: var(--ks3-ink-muted); stroke-width: 2;
  stroke-dasharray: 8 8; }
.ks3-p4fig-tick { fill: none; stroke: var(--ks3-ink); stroke-width: 3; }
.ks3-p4fig-rowlabel {
  font-family: var(--ks3-font-mono); font-size: 20px; letter-spacing: .06em;
  fill: var(--ks3-ink-muted);
}
.ks3-p4fig-barlabel {
  font-family: var(--ks3-font-display); font-weight: 800; font-size: 26px;
  fill: var(--ks3-ink);
}
.ks3-p4fig-panel { fill: none; stroke: var(--ks3-ink); stroke-width: 3; }
.ks3-p4fig-panellabel {
  font-family: var(--ks3-font-mono); font-size: 20px; letter-spacing: .08em;
  fill: var(--ks3-accent-text);
}
.ks3-p4fig-load { fill: var(--ks3-blue-tint); stroke: var(--ks3-ink);
  stroke-width: 3; }
.ks3-p4fig-arrowlabel {
  font-family: var(--ks3-font-mono); font-size: 19px;
  fill: var(--ks3-ink-muted);
}
.ks3-p4fig-verdict {
  font-family: var(--ks3-font-display); font-weight: 700; font-size: 21px;
  fill: var(--ks3-ink);
}
.ks3-p4fig-note {
  font-family: var(--ks3-font-mono); font-size: 19px;
  fill: var(--ks3-ink-muted);
}
.ks3-p4fig-axis { fill: none; stroke: var(--ks3-ink); stroke-width: 4; }
.ks3-p4fig-line { fill: none; stroke: var(--ks3-accent-text);
  stroke-width: 4; }
.ks3-p4fig-curve { fill: none; stroke: var(--ks3-accent-text);
  stroke-width: 4; stroke-dasharray: 10 8; }
.ks3-p4fig-limit { fill: none; stroke: var(--ks3-ink-muted);
  stroke-width: 2; stroke-dasharray: 6 6; }
.ks3-p4fig-axislabel {
  font-family: var(--ks3-font-mono); font-size: 18px; letter-spacing: .06em;
  fill: var(--ks3-ink-muted);
}

/* ── the CFIFA attempt panel ────────────────────────────────────────── */
/* Design's `Cfifa` is one component with TWO halves and only the first had
   ever been ported. This is the second: the student's own five lines, on
   the numbers their own bench is showing. */

.ks3-cfa-tabs { display: flex; gap: 9px; flex-wrap: wrap; margin: 10px 0 0; }
.ks3-cfa-head {
  margin: 18px 0 0; font-family: var(--ks3-font-display); font-weight: 800;
  font-size: clamp(22px, 3vw, 30px); line-height: 1.15;
  letter-spacing: -.02em;
}
.ks3-cfa-lead { margin: 10px 0 0; font-size: 18px; line-height: 1.55;
  color: var(--ks3-ink-body); }
.ks3-cfa-blocked {
  margin: 14px 0 0; padding: 14px 18px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-alert-tint, var(--ks3-band));
  border: 2px solid var(--ks3-alert-border, var(--ks3-ink));
  font-size: 17px; line-height: 1.5;
}
.ks3-cfa-rows { margin-top: 18px; display: flex; flex-direction: column;
  gap: 10px; }
.ks3-cfa-row, .ks3-cfa-modelrow {
  display: grid; grid-template-columns: auto 1fr; gap: 14px;
  align-items: start;
}
.ks3-cfa-chip {
  display: grid; place-items: center; width: 38px; height: 38px;
  border-radius: 11px; background: var(--ks3-accent-tint);
  border: 2px solid var(--ks3-ink); color: var(--ks3-ink);
  font-family: var(--ks3-font-display); font-weight: 800; font-size: 18px;
}
.ks3-cfa-field { display: flex; flex-direction: column; gap: 5px; }
.ks3-cfa-label {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-cfa-input {
  font: inherit; font-family: var(--ks3-font-mono); font-size: 18px;
  padding: 11px 14px; min-height: 44px; width: 100%;
  border-radius: var(--ks3-r-control); border: 2px solid var(--ks3-ink);
  background: var(--ks3-ground); color: var(--ks3-ink);
}
.ks3-cfa-check {
  margin-top: 18px; display: flex; gap: 14px; align-items: center;
  flex-wrap: wrap;
}
.ks3-cfa-hint {
  font-family: var(--ks3-font-mono); font-size: 15px;
  color: var(--ks3-ink-muted);
}
.ks3-cfa-reveal {
  margin-top: 20px; padding: 22px 24px; border-radius: var(--ks3-r-card);
  background: var(--ks3-dark-panel); color: var(--ks3-on-dark);
}
/* ⚠️ `--ks3-on-dark-muted`, NOT `--ks3-accent`. The accent is 3.4:1 and
   tokens.css marks it LARGE TEXT ONLY; this is a 13px mono eyebrow, and
   MRB-252's gate caught it on the first run. The token for a caption on an
   ink-dark block is the one used here, and it is 8.6:1. */
.ks3-cfa-revealhead {
  margin: 0 0 16px; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; letter-spacing: .09em; text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-cfa-reveal .ks3-cfa-modelrow { margin-bottom: 14px; }
.ks3-cfa-reveal .ks3-cfa-chip {
  background: rgba(255, 255, 255, .1); border-color: var(--ks3-on-dark-muted);
  color: var(--ks3-on-dark);
}
.ks3-cfa-reveal .ks3-cfa-label { color: var(--ks3-on-dark-muted); }
.ks3-cfa-line {
  margin: 4px 0 0; font-family: var(--ks3-font-mono); font-size: 21px;
  color: var(--ks3-on-dark);
}
.ks3-cfa-stepnote {
  margin: 6px 0 0; font-size: 17px; line-height: 1.5;
  color: var(--ks3-on-dark-body);
}
/* The self-tick. Nothing here marks a line right or wrong — the model
   appears beside what the student wrote and they decide, which is the same
   contract the ladder's rungs 3 and 4 already use. */
.ks3-cfa-yours {
  margin-top: 10px; padding: 12px 14px; border-radius: var(--ks3-r-panel);
  background: rgba(255, 255, 255, .07);
  display: flex; gap: 12px; align-items: center; flex-wrap: wrap;
}
.ks3-cfa-yourline {
  margin: 0; flex: 1 1 14rem; font-family: var(--ks3-font-mono);
  font-size: 16px; color: var(--ks3-on-dark-body);
}
.ks3-cfa-tick {
  font: inherit; font-size: 15px; font-weight: 700;
  padding: 9px 14px; min-height: 44px; border-radius: var(--ks3-r-control);
  border: 2px solid var(--ks3-on-dark-muted); background: transparent;
  color: var(--ks3-on-dark); cursor: pointer;
}
.ks3-cfa-tick[aria-pressed="true"] {
  background: var(--ks3-accent); border-color: var(--ks3-accent);
  color: var(--ks3-on-dark);
}
/* Same rule at 17px. The tally is a readout, so it takes the plain
   on-dark text colour rather than a hue that has to carry meaning. */
.ks3-cfa-tally {
  margin: 16px 0 0; font-family: var(--ks3-font-mono); font-size: 17px;
  color: var(--ks3-on-dark);
}
.ks3-cfa-close {
  margin: 14px 0 0; font-size: 18px; line-height: 1.55;
  color: var(--ks3-on-dark-body);
}

/* ═══ END P4 ═══ */

/* ═══ BEGIN P5 ═══════════════════════════════════════════════════════════
   P5 — *Pressure*. One marked block per unit.

   ⚠️ EVERY SELECTOR HERE IS P5-ONLY, and every shell stem was checked
   against the whole registry before it was written.

   ⚠️ EVERY FIGURE WRAPPER IS `position: relative`, for the reason the P4
   block gives at length: every live value is an absolutely positioned HTML
   span over the SVG, because a `{{ hole }}` inside an SVG `<text>` renders
   nothing and fails silently — and without a positioned ancestor those
   spans resolve against the page and widen the document on a phone.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── shared P5 shapes ───────────────────────────────────────────────── */

.ks3-sand-gate, .ks3-dprobe-gate, .ks3-ftank-gate, .ks3-alt-gate {
  padding: 20px 22px; border-radius: var(--ks3-r-panel);
  background: rgba(255, 255, 255, .06);
  border: 2px solid var(--ks3-on-dark-muted);
}

.ks3-sand-head, .ks3-dprobe-head, .ks3-ftank-head, .ks3-alt-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 20px; flex-wrap: wrap; margin-bottom: 20px;
}
.ks3-sand-head h2, .ks3-dprobe-head h2, .ks3-ftank-head h2,
.ks3-alt-head h2 {
  margin: 0; font-size: 34px; line-height: 1.12; letter-spacing: -.03em;
  color: var(--ks3-on-dark);
}
.ks3-sand-progress, .ks3-dprobe-progress, .ks3-ftank-progress,
.ks3-alt-progress {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 15px;
  font-weight: 500; color: var(--ks3-on-dark-muted);
}
.ks3-sand-lead, .ks3-dprobe-lead, .ks3-ftank-lead, .ks3-alt-lead {
  margin: 0 0 18px; font-size: 18px; line-height: 1.55;
  color: var(--ks3-on-dark-body);
}

.ks3-sand-tiles, .ks3-dprobe-tiles, .ks3-ftank-tiles, .ks3-alt-tiles {
  margin-top: 16px; display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr));
  gap: 12px;
}
.ks3-sand-tile, .ks3-dprobe-tile, .ks3-ftank-tile, .ks3-alt-tile {
  padding: 15px 17px; border-radius: var(--ks3-r-panel);
  background: rgba(255, 255, 255, .07);
}
.ks3-sand-tile-label, .ks3-dprobe-tile-label, .ks3-ftank-tile-label,
.ks3-alt-tile-label {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-sand-tile-value, .ks3-dprobe-tile-value, .ks3-ftank-tile-value,
.ks3-alt-tile-value {
  margin: 5px 0 0; font-family: var(--ks3-font-mono); font-size: 20px;
  font-weight: 500; color: var(--ks3-on-dark);
}
/* Design's second line on a tile — the working, not a second reading. */
.ks3-sand-tile-sub, .ks3-dprobe-tile-sub, .ks3-ftank-tile-sub,
.ks3-alt-tile-sub {
  margin: 4px 0 0; font-family: var(--ks3-font-mono); font-size: 14px;
  color: var(--ks3-on-dark-muted);
}

.ks3-sand-note, .ks3-dprobe-note, .ks3-ftank-note, .ks3-alt-note {
  margin: 16px 0 0; padding: 16px 18px;
  border-radius: var(--ks3-r-panel); background: rgba(255, 255, 255, .07);
  font-size: 18px; line-height: 1.55; color: var(--ks3-on-dark-body);
}

.ks3-sand-figwrap, .ks3-dprobe-figwrap, .ks3-ftank-figwrap,
.ks3-alt-figwrap { position: relative; margin-top: 20px; }
.ks3-sand-svg, .ks3-dprobe-svg, .ks3-ftank-svg, .ks3-alt-svg {
  display: block; width: 100%; height: auto;
}
.ks3-sand-fill, .ks3-dprobe-fill, .ks3-ftank-fill, .ks3-alt-fill {
  position: absolute; font-family: var(--ks3-font-mono); font-weight: 500;
  font-size: clamp(10px, 1.5vw, 19px); white-space: nowrap;
  pointer-events: none;
}

.ks3-sand-pickerlabel, .ks3-dprobe-pickerlabel, .ks3-ftank-pickerlabel,
.ks3-alt-pickerlabel {
  margin: 0 0 9px; font-family: var(--ks3-font-mono); font-size: 14px;
  font-weight: 500; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-sand-tabrow, .ks3-dprobe-tabrow, .ks3-ftank-tabrow, .ks3-alt-tabrow {
  display: flex; gap: 9px; flex-wrap: wrap;
}
.ks3-sand-controls, .ks3-dprobe-controls, .ks3-ftank-controls,
.ks3-alt-controls {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
  gap: 18px;
}
.ks3-sand-rowhead, .ks3-dprobe-rowhead {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px;
}
.ks3-sand-rowhead label, .ks3-dprobe-rowhead label {
  font-family: var(--ks3-font-mono); font-size: 14px; font-weight: 500;
  letter-spacing: .07em; text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-sand-reading, .ks3-dprobe-reading {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 20px;
  font-weight: 500; color: var(--ks3-on-dark);
}
.ks3-sand-slider, .ks3-dprobe-slider {
  width: 100%; margin-top: 8px; accent-color: var(--ks3-accent);
  min-height: 44px;
}

/* ── p5-01 · the block on sand ──────────────────────────────────────── */

.ks3-sand-grit { fill: none; stroke: #C6B9A7; stroke-width: 2;
  stroke-dasharray: 4 14; opacity: .5; }
.ks3-sand-line { fill: none; stroke: var(--ks3-on-dark-muted);
  stroke-width: 5; stroke-linejoin: round; }
/* The original sand level, dashed behind the trough, so the reader can see
   how far it gave way. */
.ks3-sand-was { fill: none; stroke: #C6B9A7; stroke-width: 2;
  stroke-dasharray: 8 8; }
.ks3-sand-block { fill: #15110C; stroke: var(--ks3-on-dark-muted);
  stroke-width: 3; }
.ks3-sand-shaft { fill: none; stroke: #8FB7FF; stroke-width: 8;
  stroke-linecap: round; }
.ks3-sand-head { fill: #8FB7FF; }
.ks3-sand-dim { fill: none; stroke: #E9DCC8; stroke-width: 2; }
.ks3-sand-verdict { letter-spacing: .08em;
  font-size: clamp(10px, 1.4vw, 17px); }

/* ── p5-02 · the probe in the tank ──────────────────────────────────── */

.ks3-dprobe-tank { fill: none; stroke: var(--ks3-on-dark-muted);
  stroke-width: 4; }
.ks3-dprobe-liquid { fill: #1D2A3A; }
/* The column ABOVE the probe face, shaded as its own band — the weight the
   pressure is worked out from. */
.ks3-dprobe-col { fill: #2F4A6B; }
.ks3-dprobe-cable { fill: none; stroke: #C6B9A7; stroke-width: 3; }
.ks3-dprobe-probe { fill: #15110C; stroke: #E9DCC8; stroke-width: 3; }
/* Four arrows at the face. A liquid presses equally in every direction at
   one depth, and the drawing says so whatever the depth. */
.ks3-dprobe-rosette { fill: none; stroke: #8FB7FF; stroke-width: 5;
  stroke-linecap: round; }
.ks3-dprobe-dim { fill: none; stroke: #E9DCC8; stroke-width: 2; }
.ks3-dprobe-surfacelabel {
  font-family: var(--ks3-font-mono); font-size: 17px; letter-spacing: .08em;
  fill: #C6B9A7;
}
.ks3-dprobe-dir { letter-spacing: .08em;
  font-size: clamp(10px, 1.4vw, 17px); }

/* ── p5-03 · five blocks, one tank ──────────────────────────────────── */

.ks3-ftank-tank { fill: none; stroke: var(--ks3-on-dark-muted);
  stroke-width: 4; }
.ks3-ftank-water { fill: #1D2A3A; }
.ks3-ftank-surface { fill: none; stroke: #8FB7FF; stroke-width: 3; }
.ks3-ftank-box { fill: #15110C; stroke: #E9DCC8; stroke-width: 3; }
.ks3-ftank-shaft { fill: none; stroke: #E9DCC8; stroke-width: 8;
  stroke-linecap: round; }
.ks3-ftank-head { fill: #E9DCC8; }
.ks3-ftank-up.ks3-ftank-shaft { stroke: #8FB7FF; }
.ks3-ftank-up.ks3-ftank-head { fill: #8FB7FF; }
/* ⊕ MRB-254 · was `.ks3-ftank-leftover`, an SVG <text> that shipped empty.
   The caption is now an overlay span at the same place in the viewBox —
   x 500/1000 and y 600/620 — and it is the only fill on this bench whose
   position never changes, so it is placed here rather than from JS. */
.ks3-ftank-fill.ks3-ftank-leftword {
  left: 50%; top: 92.5%; transform: translateX(-50%);
  letter-spacing: .08em; color: #FFC53D;
}
.ks3-ftank-hold { width: 100%; }

/* ── p5-04 · up the mountain ────────────────────────────────────────── */

.ks3-alt-shaft { fill: none; stroke: var(--ks3-on-dark-muted);
  stroke-width: 3; }
/* The air still ABOVE the marker. It is what presses, and it is the only
   thing that changes as you climb. */
.ks3-alt-air { fill: #2F4A6B; }
.ks3-alt-ground { fill: none; stroke: var(--ks3-on-dark-muted);
  stroke-width: 5; }
.ks3-alt-marker { fill: #8FB7FF; }
.ks3-alt-bag { fill: #15110C; stroke: #E9DCC8; stroke-width: 3; }
.ks3-alt-bagseal { fill: none; stroke: #C6B9A7; stroke-width: 3;
  stroke-dasharray: 8 6; }
.ks3-alt-pan { fill: none; stroke: #E9DCC8; stroke-width: 4; }
.ks3-alt-panwater { fill: #2F4A6B; }
.ks3-alt-flame { fill: none; stroke: #FFC53D; stroke-width: 4;
  stroke-linecap: round; }
.ks3-alt-dial { fill: #15110C; stroke: #E9DCC8; stroke-width: 4; }
/* An attribute-hole rotation, never a redrawn path — Design's own note. */
.ks3-alt-needle { fill: none; stroke: #8FB7FF; stroke-width: 6;
  stroke-linecap: round; }
.ks3-alt-pin { fill: #E9DCC8; }

/* ── the P5 formula figures ─────────────────────────────────────────── */
/* ⚠️ NONE OF THESE IS A TRIANGLE. p5-01 is the unit's only product and it
   takes the engine's own `.ks3-triangle`; the stack is a sum and the
   opposed beam is a difference. Neither has cover buttons: covering a
   layer of water, or one of two opposed arrows, asks nothing. */

.ks3-p5fig { display: block; width: 100%; height: auto; }
.ks3-p5fig-opposed { min-width: 480px; }
.ks3-formula-figure:has(.ks3-p5fig) {
  overflow-x: auto; -webkit-overflow-scrolling: touch;
}
.ks3-p5fig-layer { fill: var(--ks3-blue-tint); stroke: var(--ks3-ink);
  stroke-width: 3; }
.ks3-p5fig-layerlabel {
  font-family: var(--ks3-font-mono); font-size: 18px;
  fill: var(--ks3-ink);
}
/* The running total beside each boundary — the sum, made visible. */
.ks3-p5fig-total {
  font-family: var(--ks3-font-display); font-weight: 800; font-size: 22px;
  fill: var(--ks3-accent-text);
}
.ks3-p5fig-floor { fill: none; stroke: var(--ks3-ink); stroke-width: 5;
  stroke-linecap: round; }
.ks3-p5fig-footlabel {
  font-family: var(--ks3-font-mono); font-size: 18px; letter-spacing: .08em;
  fill: var(--ks3-ink-muted);
}
.ks3-p5fig-panel { fill: none; stroke: var(--ks3-ink); stroke-width: 3; }
.ks3-p5fig-surface { fill: none; stroke: var(--ks3-accent-text);
  stroke-width: 3; }
.ks3-p5fig-box { fill: var(--ks3-blue-tint); stroke: var(--ks3-ink);
  stroke-width: 3; }
.ks3-p5fig-shaft { fill: none; stroke: var(--ks3-ink); stroke-width: 6;
  stroke-linecap: round; }
.ks3-p5fig-head { fill: var(--ks3-ink); }
.ks3-p5fig-arrowlabel {
  font-family: var(--ks3-font-mono); font-size: 18px; letter-spacing: .05em;
  fill: var(--ks3-ink-muted);
}
.ks3-p5fig-over { fill: none; stroke: var(--ks3-accent-text);
  stroke-width: 5; stroke-linecap: round; }
.ks3-p5fig-overlabel {
  font-family: var(--ks3-font-display); font-weight: 800; font-size: 20px;
  fill: var(--ks3-accent-text);
}
.ks3-p5fig-verdict {
  font-family: var(--ks3-font-display); font-weight: 700; font-size: 21px;
  fill: var(--ks3-ink);
}

/* ═══ END P5 ═══ */

/* ═══ BEGIN P6 ═══════════════════════════════════════════════════════════
   P6 — *Waves and sound*. One marked block per unit.

   ⚠️ EVERY SELECTOR HERE IS P6-ONLY, and every shell stem was checked
   against the whole registry before it was written: none of `ks3-wanat-`,
   `ks3-rtank-`, `ks3-slane-`, `ks3-vchain-`, `ks3-slink-`, `ks3-scope-`,
   `ks3-mrange-`, `ks3-echo-`, `ks3-lrange-`, `ks3-fgauge-` or
   `ks3-wband-` was taken.

   ⚠️ EVERY FIGURE WRAPPER IS `position: relative`, for the reason the P4
   block gives at length: every live value is an absolutely positioned HTML
   span over the SVG, because a hole inside an SVG `<text>` renders nothing
   and fails silently — and without a positioned ancestor those spans
   resolve against the page and widen the document on a phone.

   ⚠️ TOKENS, NOT LITERALS, FOR EVERY PIECE OF TEXT. `--ks3-accent` is
   3.4:1 and is LARGE TEXT ONLY; anything under 24px that wants orange
   takes `--ks3-accent-text`. Captions on the ink-dark benches take
   `--ks3-on-dark-muted`, which is 6.08:1 there.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── shared P6 bench shapes — the nine dark instruments ─────────────── */

.ks3-rtank-gate, .ks3-slane-gate, .ks3-vchain-gate, .ks3-slink-gate,
.ks3-scope-gate, .ks3-mrange-gate, .ks3-echo-gate, .ks3-lrange-gate,
.ks3-fgauge-gate {
  padding: 20px 22px; border-radius: var(--ks3-r-panel);
  background: rgba(255, 255, 255, .06);
  border: 2px solid var(--ks3-on-dark-muted);
}

.ks3-rtank-head, .ks3-slane-head, .ks3-vchain-head, .ks3-slink-head,
.ks3-scope-head, .ks3-mrange-head, .ks3-echo-head, .ks3-lrange-head,
.ks3-fgauge-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 20px; flex-wrap: wrap; margin-bottom: 20px;
}
.ks3-rtank-head h2, .ks3-slane-head h2, .ks3-vchain-head h2,
.ks3-slink-head h2, .ks3-scope-head h2, .ks3-mrange-head h2,
.ks3-echo-head h2, .ks3-lrange-head h2, .ks3-fgauge-head h2 {
  margin: 0; font-size: 34px; line-height: 1.12; letter-spacing: -.03em;
  color: var(--ks3-on-dark);
}
.ks3-rtank-progress, .ks3-slane-progress, .ks3-vchain-progress,
.ks3-slink-progress, .ks3-scope-progress, .ks3-mrange-progress,
.ks3-echo-progress, .ks3-lrange-progress, .ks3-fgauge-progress {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 15px;
  font-weight: 500; color: var(--ks3-on-dark-muted);
}
.ks3-rtank-lead, .ks3-slane-lead, .ks3-vchain-lead, .ks3-slink-lead,
.ks3-scope-lead, .ks3-mrange-lead, .ks3-echo-lead, .ks3-lrange-lead,
.ks3-fgauge-lead {
  margin: 0 0 18px; font-size: 18px; line-height: 1.55;
  color: var(--ks3-on-dark-body);
}

.ks3-rtank-controls, .ks3-slane-controls, .ks3-vchain-controls,
.ks3-slink-controls, .ks3-scope-controls, .ks3-mrange-controls,
.ks3-echo-controls, .ks3-lrange-controls, .ks3-fgauge-controls {
  display: grid; gap: 18px;
}
.ks3-rtank-row, .ks3-slane-row, .ks3-slink-row, .ks3-scope-row,
.ks3-mrange-row, .ks3-echo-row, .ks3-lrange-row, .ks3-fgauge-row {
  display: grid; gap: 8px;
}
.ks3-rtank-rowhead, .ks3-slane-rowhead, .ks3-slink-rowhead,
.ks3-scope-rowhead, .ks3-mrange-rowhead, .ks3-echo-rowhead,
.ks3-lrange-rowhead, .ks3-fgauge-rowhead {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 16px; flex-wrap: wrap;
  font-family: var(--ks3-font-mono); font-size: 15px; font-weight: 500;
  color: var(--ks3-on-dark-muted);
}
.ks3-rtank-reading, .ks3-slane-reading, .ks3-slink-reading,
.ks3-scope-reading, .ks3-mrange-reading, .ks3-echo-reading,
.ks3-lrange-reading, .ks3-fgauge-reading {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 19px;
  font-weight: 500; color: var(--ks3-on-dark);
}
/* 44px of vertical target, which is the smallest a thumb should be asked
   to find — the sliders are the only control on five of these benches. */
.ks3-rtank-slider, .ks3-slane-slider, .ks3-slink-slider,
.ks3-scope-slider, .ks3-mrange-slider, .ks3-echo-slider,
.ks3-lrange-slider, .ks3-fgauge-slider {
  width: 100%; min-height: 44px; accent-color: var(--ks3-accent);
}

.ks3-rtank-picker, .ks3-slane-picker, .ks3-vchain-picker,
.ks3-slink-picker, .ks3-mrange-picker, .ks3-echo-picker,
.ks3-lrange-picker, .ks3-fgauge-picker { display: grid; gap: 9px; }
.ks3-rtank-pickerlabel, .ks3-slane-pickerlabel, .ks3-vchain-pickerlabel,
.ks3-slink-pickerlabel, .ks3-mrange-pickerlabel, .ks3-echo-pickerlabel,
.ks3-lrange-pickerlabel, .ks3-fgauge-pickerlabel {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 14px;
  font-weight: 500; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-rtank-tabrow, .ks3-slane-tabrow, .ks3-vchain-tabrow,
.ks3-slink-tabrow, .ks3-mrange-tabrow, .ks3-echo-tabrow,
.ks3-lrange-tabrow, .ks3-fgauge-tabrow, .ks3-wanat-tabrow {
  display: flex; flex-wrap: wrap; gap: 9px;
}

.ks3-rtank-figwrap, .ks3-slane-figwrap, .ks3-vchain-figwrap,
.ks3-slink-figwrap, .ks3-scope-figwrap, .ks3-mrange-figwrap,
.ks3-echo-figwrap, .ks3-lrange-figwrap, .ks3-fgauge-figwrap,
.ks3-wanat-figwrap { position: relative; margin-top: 20px; }
.ks3-rtank-svg, .ks3-slane-svg, .ks3-vchain-svg, .ks3-slink-svg,
.ks3-scope-svg, .ks3-mrange-svg, .ks3-echo-svg, .ks3-lrange-svg,
.ks3-fgauge-svg, .ks3-wanat-svg { display: block; width: 100%; height: auto; }
.ks3-rtank-fill, .ks3-vchain-fill, .ks3-slink-fill, .ks3-scope-fill,
.ks3-mrange-fill, .ks3-echo-fill, .ks3-lrange-fill, .ks3-fgauge-fill {
  position: absolute; font-family: var(--ks3-font-mono); font-weight: 500;
  font-size: clamp(10px, 1.5vw, 19px); white-space: nowrap;
  pointer-events: none;
}

.ks3-rtank-tiles, .ks3-slane-tiles, .ks3-vchain-tiles, .ks3-slink-tiles,
.ks3-scope-tiles, .ks3-mrange-tiles, .ks3-echo-tiles, .ks3-lrange-tiles,
.ks3-fgauge-tiles {
  margin-top: 16px; display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr));
  gap: 12px;
}
.ks3-rtank-tile, .ks3-slane-tile, .ks3-vchain-tile, .ks3-slink-tile,
.ks3-scope-tile, .ks3-mrange-tile, .ks3-echo-tile, .ks3-lrange-tile,
.ks3-fgauge-tile {
  padding: 15px 17px; border-radius: var(--ks3-r-panel);
  background: rgba(255, 255, 255, .07);
}
.ks3-rtank-tile-label, .ks3-slane-tile-label, .ks3-vchain-tile-label,
.ks3-slink-tile-label, .ks3-scope-tile-label, .ks3-mrange-tile-label,
.ks3-echo-tile-label, .ks3-lrange-tile-label, .ks3-fgauge-tile-label {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-rtank-tile-value, .ks3-slane-tile-value, .ks3-vchain-tile-value,
.ks3-slink-tile-value, .ks3-scope-tile-value, .ks3-mrange-tile-value,
.ks3-echo-tile-value, .ks3-lrange-tile-value, .ks3-fgauge-tile-value {
  margin: 5px 0 0; font-family: var(--ks3-font-mono); font-size: 20px;
  font-weight: 500; color: var(--ks3-on-dark);
}
.ks3-rtank-tile-sub, .ks3-vchain-tile-sub, .ks3-slink-tile-sub,
.ks3-scope-tile-sub, .ks3-mrange-tile-sub, .ks3-echo-tile-sub,
.ks3-lrange-tile-sub, .ks3-fgauge-tile-sub {
  margin: 4px 0 0; font-family: var(--ks3-font-mono); font-size: 14px;
  color: var(--ks3-on-dark-muted);
}

.ks3-rtank-note, .ks3-slane-note, .ks3-vchain-note, .ks3-slink-note,
.ks3-scope-note, .ks3-mrange-note, .ks3-echo-note, .ks3-lrange-note,
.ks3-fgauge-note {
  margin: 16px 0 0; padding: 16px 18px;
  border-radius: var(--ks3-r-panel); background: rgba(255, 255, 255, .07);
  font-size: 18px; line-height: 1.55; color: var(--ks3-on-dark-body);
}

/* ── p6-01 · the wave anatomy figure (light ground) ─────────────────── */

.ks3-wanat { margin-top: 26px; }
.ks3-wanat-heading {
  margin: 6px 0 16px; font-family: var(--ks3-font-display);
  font-weight: 800; font-size: 30px; line-height: 1.12;
  letter-spacing: -.03em; color: var(--ks3-ink);
}
.ks3-wanat-still {
  fill: none; stroke: var(--ks3-rule-strong); stroke-width: 3;
  stroke-dasharray: 14 12;
}
.ks3-wanat-wave {
  fill: none; stroke: var(--ks3-ink); stroke-width: 6;
  stroke-linecap: round; stroke-linejoin: round;
}
.ks3-wanat-travel {
  fill: none; stroke: var(--ks3-ink-muted); stroke-width: 3;
  stroke-linecap: round;
}
.ks3-wanat-travellabel {
  font-family: var(--ks3-font-mono); font-size: 17px; letter-spacing: .07em;
  text-transform: uppercase; fill: var(--ks3-ink-muted);
}
.ks3-wanat-mark circle { fill: var(--ks3-accent); stroke: var(--ks3-ink);
  stroke-width: 4; }
.ks3-wanat-dim path {
  fill: none; stroke: var(--ks3-accent-text); stroke-width: 5;
  stroke-linecap: round;
}
.ks3-wanat-note {
  margin: 18px 0 0; padding: 16px 18px;
  border-radius: var(--ks3-r-panel); background: var(--ks3-card);
  border: 2px solid var(--ks3-rule);
  font-size: 18px; line-height: 1.55; color: var(--ks3-ink-body);
}

/* ── p6-01 · the ripple tank ────────────────────────────────────────── */

.ks3-rtank-tank { fill: rgba(255, 255, 255, .05);
  stroke: var(--ks3-on-dark-muted); stroke-width: 3; }
.ks3-rtank-still { fill: none; stroke: var(--ks3-on-dark-muted);
  stroke-width: 2; stroke-dasharray: 12 10; }
.ks3-rtank-wave { fill: none; stroke: #8FB7FF; stroke-width: 5;
  stroke-linecap: round; stroke-linejoin: round; }
.ks3-rtank-paddle { fill: var(--ks3-on-dark-muted); }
.ks3-rtank-float { fill: #FFC53D; stroke: var(--ks3-on-dark);
  stroke-width: 3; }
.ks3-rtank-swing { fill: none; stroke: #FFC53D; stroke-width: 3;
  stroke-linecap: round; }
.ks3-rtank-widthlabel {
  font-family: var(--ks3-font-mono); font-size: 17px; letter-spacing: .07em;
  fill: var(--ks3-on-dark-muted);
}
.ks3-rtank-swing-label { color: #FFC53D; }

/* ── p6-02 · the superposition lanes ────────────────────────────────── */

.ks3-slane-still { fill: none; stroke: var(--ks3-on-dark-muted);
  stroke-width: 2; stroke-dasharray: 12 10; }
.ks3-slane-lanelabel {
  font-family: var(--ks3-font-mono); font-size: 16px; letter-spacing: .07em;
  fill: var(--ks3-on-dark-muted);
}
/* Three traces, three hues AND three positions — the lane label is what
   names each one, so hue is never the only channel. */
.ks3-slane-trace { fill: none; stroke-width: 5; stroke-linecap: round;
  stroke-linejoin: round; }
.ks3-slane-trace-a { stroke: #8FB7FF; }
.ks3-slane-trace-b { stroke: #C6B9A7; }
.ks3-slane-trace-r { stroke: #FFC53D; stroke-width: 6; }

/* ── p6-03 · the vibration chain ────────────────────────────────────── */

.ks3-vchain-ghost { fill: none; stroke: var(--ks3-on-dark-muted);
  stroke-width: 3; stroke-dasharray: 10 9; }
.ks3-vchain-shape { fill: none; stroke: var(--ks3-on-dark);
  stroke-width: 6; stroke-linecap: round; stroke-linejoin: round; }
.ks3-vchain-arrow { fill: none; stroke: #FFC53D; stroke-width: 4;
  stroke-linecap: round; }
.ks3-vchain-air { fill: none; stroke: #8FB7FF; stroke-width: 3;
  stroke-linecap: round; }
.ks3-vchain-diaphragm { fill: var(--ks3-on-dark-muted); }
.ks3-vchain-travel { fill: none; stroke: var(--ks3-on-dark-muted);
  stroke-width: 3; stroke-linecap: round; }
.ks3-vchain-travellabel {
  font-family: var(--ks3-font-mono); font-size: 16px; letter-spacing: .07em;
  fill: var(--ks3-on-dark-muted);
}

/* ── p6-04 · the dual slinky ────────────────────────────────────────── */

.ks3-slink-axis { fill: none; stroke: var(--ks3-on-dark-muted);
  stroke-width: 2; stroke-dasharray: 12 10; }
.ks3-slink-coils { fill: none; stroke: #8FB7FF; stroke-width: 4;
  stroke-linecap: round; stroke-linejoin: round; }
.ks3-slink-rest { fill: none; stroke: var(--ks3-on-dark-muted);
  stroke-width: 3; stroke-dasharray: 8 8; }
.ks3-slink-marked { fill: #FFC53D; stroke: var(--ks3-on-dark);
  stroke-width: 3; }
.ks3-slink-arrow { fill: #FFC53D; stroke: #FFC53D; stroke-width: 5;
  stroke-linecap: round; }
.ks3-slink-travel { fill: none; stroke: var(--ks3-on-dark-muted);
  stroke-width: 3; stroke-linecap: round; }
.ks3-slink-travellabel {
  font-family: var(--ks3-font-mono); font-size: 16px; letter-spacing: .07em;
  fill: var(--ks3-on-dark-muted);
}
.ks3-slink-caption { color: var(--ks3-on-dark-muted); }

/* ── p6-05 · the oscilloscope ───────────────────────────────────────── */

.ks3-scope-screen { fill: rgba(255, 255, 255, .05);
  stroke: var(--ks3-on-dark-muted); stroke-width: 3; }
.ks3-scope-zero { fill: none; stroke: var(--ks3-on-dark-muted);
  stroke-width: 2; stroke-dasharray: 12 10; }
.ks3-scope-ticks { fill: none; stroke: var(--ks3-on-dark-muted);
  stroke-width: 2; }
.ks3-scope-trace { fill: none; stroke: #8FB7FF; stroke-width: 4;
  stroke-linecap: round; stroke-linejoin: round; }
.ks3-scope-bracket { fill: none; stroke: #FFC53D; stroke-width: 3;
  stroke-linecap: round; }
.ks3-scope-axislabel {
  font-family: var(--ks3-font-mono); font-size: 17px; letter-spacing: .07em;
  fill: var(--ks3-on-dark-muted);
}
.ks3-scope-amp { color: #FFC53D; }

/* ── p6-06 · the medium range ───────────────────────────────────────── */

.ks3-mrange-gapbox { fill: rgba(255, 255, 255, .05);
  stroke: var(--ks3-on-dark-muted); stroke-width: 3; }
.ks3-mrange-particles { fill: #8FB7FF; stroke: none; }
.ks3-mrange-links { fill: none; stroke: #8FB7FF; stroke-width: 2; }
.ks3-mrange-striker, .ks3-mrange-mic { fill: var(--ks3-on-dark-muted); }
.ks3-mrange-dim { fill: none; stroke: var(--ks3-on-dark-muted);
  stroke-width: 2; }
.ks3-mrange-gap, .ks3-mrange-caption { color: var(--ks3-on-dark-muted); }

/* The chain's two end captions. They are overlay spans rather than SVG
   `<text>` for the reason the whole key stage now uses spans: a hole
   inside a `<text>` renders nothing and fails silently. */
.ks3-vchain-src, .ks3-vchain-det { color: var(--ks3-on-dark-muted); }

/* ── p6-07 · the echo bench ─────────────────────────────────────────── */

.ks3-echo-ground { fill: none; stroke: var(--ks3-on-dark-muted);
  stroke-width: 3; }
.ks3-echo-person { fill: var(--ks3-on-dark-muted); }
.ks3-echo-wall { fill: rgba(255, 255, 255, .12);
  stroke: var(--ks3-on-dark); stroke-width: 3; }
.ks3-echo-out { fill: #8FB7FF; stroke: #8FB7FF; stroke-width: 5;
  stroke-linecap: round; }
/* ⚠️ NO `stroke-width` HERE. The returning arrow's width IS the fraction
   reflected and is set per state from JS; a value in this rule would win
   or lose depending on specificity and make the drawing lie. The dash is
   set from JS for the same reason — it is the second channel, so that the
   audible threshold is never carried on thickness alone. */
.ks3-echo-back { fill: #FFC53D; stroke: #FFC53D; stroke-linecap: round; }
.ks3-echo-dim { fill: none; stroke: var(--ks3-on-dark-muted);
  stroke-width: 2; }
.ks3-echo-dist, .ks3-echo-caption { color: var(--ks3-on-dark-muted); }
.ks3-echo-back-label { color: #FFC53D; }

/* ── p6-08 · the decade axis ────────────────────────────────────────── */

.ks3-lrange-axis { fill: none; stroke: var(--ks3-on-dark-muted);
  stroke-width: 3; }
.ks3-lrange-tick { fill: none; stroke: var(--ks3-on-dark-muted);
  stroke-width: 2; }
.ks3-lrange-ticklabel {
  font-family: var(--ks3-font-mono); font-size: 17px;
  fill: var(--ks3-on-dark-muted);
}
.ks3-lrange-band { fill: rgba(143, 183, 255, .38);
  stroke: #8FB7FF; stroke-width: 2; }
.ks3-lrange-marker { fill: none; stroke: #FFC53D; stroke-width: 5;
  stroke-linecap: round; }
.ks3-lrange-axislabel {
  font-family: var(--ks3-font-mono); font-size: 16px; letter-spacing: .07em;
  fill: var(--ks3-on-dark-muted);
}
.ks3-lrange-mark { color: #FFC53D; }
.ks3-lrange-bandlabel {
  font-family: var(--ks3-font-mono); font-size: 14px; letter-spacing: .07em;
  fill: var(--ks3-on-dark-muted);
}

/* ── p6-09 · the flaw gauge ─────────────────────────────────────────── */

.ks3-fgauge-block { fill: rgba(255, 255, 255, .05);
  stroke: var(--ks3-on-dark-muted); stroke-width: 3; }
.ks3-fgauge-probe { fill: var(--ks3-on-dark-muted); }
.ks3-fgauge-flaw { fill: #FFC53D; }
.ks3-fgauge-down { fill: #8FB7FF; stroke: #8FB7FF; stroke-width: 5;
  stroke-linecap: round; }
.ks3-fgauge-up { fill: #FFC53D; stroke: #FFC53D; stroke-width: 5;
  stroke-linecap: round; }
.ks3-fgauge-dim { fill: none; stroke: var(--ks3-on-dark-muted);
  stroke-width: 2; }
.ks3-fgauge-screen { fill: rgba(255, 255, 255, .05);
  stroke: var(--ks3-on-dark-muted); stroke-width: 3; }
.ks3-fgauge-base { fill: none; stroke: var(--ks3-on-dark-muted);
  stroke-width: 2; }
.ks3-fgauge-pip { fill: none; stroke: #FFC53D; stroke-width: 5;
  stroke-linecap: round; }
.ks3-fgauge-screenlabel {
  font-family: var(--ks3-font-mono); font-size: 16px; letter-spacing: .07em;
  fill: var(--ks3-on-dark-muted);
}
.ks3-fgauge-partlabel {
  font-family: var(--ks3-font-mono); font-size: 15px; letter-spacing: .07em;
  fill: var(--ks3-on-dark-muted);
}
.ks3-fgauge-depth, .ks3-fgauge-caption { color: var(--ks3-on-dark-muted); }

/* ── the band block · five shapes on one light ground ───────────────── */

.ks3-wband { margin-top: 26px; }
.ks3-wband-heading {
  margin: 6px 0 14px; font-family: var(--ks3-font-display);
  font-weight: 800; font-size: 30px; line-height: 1.12;
  letter-spacing: -.03em; color: var(--ks3-ink);
}
.ks3-wband-lead, .ks3-wband-close {
  margin: 0 0 16px; font-size: 18px; line-height: 1.55;
  color: var(--ks3-ink-body);
}
.ks3-wband-close { margin: 18px 0 0; }

.ks3-wband-list {
  list-style: none; margin: 0; padding: 0; display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
  gap: 14px;
}
.ks3-wband-item {
  padding: 17px 19px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-card); border: 2px solid var(--ks3-rule);
}
.ks3-wband-num {
  display: grid; place-items: center; width: 30px; height: 30px;
  border-radius: 9px; background: var(--ks3-accent);
  font-family: var(--ks3-font-display); font-weight: 800; font-size: 16px;
  color: var(--ks3-on-dark);
}
.ks3-wband-name {
  margin: 10px 0 6px; font-family: var(--ks3-font-display);
  font-weight: 800; font-size: 19px; line-height: 1.2; color: var(--ks3-ink);
}
.ks3-wband-body { margin: 0; font-size: 17px; line-height: 1.55;
  color: var(--ks3-ink-body); }
/* ⚠️ `--ks3-accent-text`, NOT `--ks3-accent`. This is 14px, and the accent
   is 3.4:1 — large text only. MRB-252's gate reads exactly this pair. */
.ks3-wband-tell {
  margin: 9px 0 0; font-family: var(--ks3-font-mono); font-size: 14px;
  font-weight: 500; letter-spacing: .05em; color: var(--ks3-accent-text);
}

.ks3-wband-cols {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
  gap: 14px;
}
.ks3-wband-col {
  padding: 17px 19px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-card); border: 2px solid var(--ks3-rule);
}
.ks3-wband-coltitle {
  margin: 0 0 10px; font-family: var(--ks3-font-mono); font-size: 14px;
  font-weight: 500; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-wband-collist {
  margin: 0; padding-left: 20px; font-size: 17px; line-height: 1.6;
  color: var(--ks3-ink-body);
}
.ks3-wband-collist li { margin: 0 0 7px; }

/* ⚠️ THE TWO CHART SHAPES SCROLL SIDEWAYS RATHER THAN SHRINK. A 1000-unit
   viewBox squeezed into a 358px phone column puts every label near 7px,
   which is not a small chart but an unreadable one. WCAG 1.4.10 allows a
   scroll region for content that genuinely needs two dimensions, and a
   decade axis with seven labelled rows does. */
.ks3-wband-strip, .ks3-wband-chart {
  margin-top: 16px; overflow-x: auto; position: relative;
}
.ks3-wband-stripsvg, .ks3-wband-chartsvg {
  display: block; width: 100%; min-width: 660px; height: auto;
}
.ks3-wband-stagebox { fill: var(--ks3-card); stroke: var(--ks3-ink);
  stroke-width: 3; }
.ks3-wband-stageglyph { fill: none; stroke: var(--ks3-accent-text);
  stroke-width: 5; stroke-linecap: round; stroke-linejoin: round; }
.ks3-wband-stagearrow { fill: var(--ks3-ink-muted);
  stroke: var(--ks3-ink-muted); stroke-width: 3; stroke-linecap: round; }
.ks3-wband-stagelabel {
  font-family: var(--ks3-font-display); font-weight: 800; font-size: 19px;
  fill: var(--ks3-ink);
}
.ks3-wband-stagecaption {
  font-family: var(--ks3-font-mono); font-size: 15px;
  fill: var(--ks3-ink-muted);
}

.ks3-wband-axhead {
  font-family: var(--ks3-font-mono); font-size: 14px; letter-spacing: .07em;
  text-transform: uppercase; fill: var(--ks3-ink-muted);
}
.ks3-wband-humanband { fill: rgba(228, 87, 46, .13); }
.ks3-wband-bound { fill: none; stroke: var(--ks3-accent-text);
  stroke-width: 3; stroke-dasharray: 10 9; }
.ks3-wband-boundlabel {
  font-family: var(--ks3-font-mono); font-size: 15px; letter-spacing: .06em;
  fill: var(--ks3-accent-text);
}
.ks3-wband-rowlabel {
  font-family: var(--ks3-font-mono); font-size: 17px; fill: var(--ks3-ink);
}
.ks3-wband-rowband { fill: var(--ks3-ink); }
.ks3-wband-rowvalue {
  font-family: var(--ks3-font-mono); font-size: 16px;
  fill: var(--ks3-ink-body);
}
/* ── the five-material speed chart and the paired cards ─────────────── */

.ks3-wband-rowempty {
  font-family: var(--ks3-font-mono); font-size: 16px; letter-spacing: .06em;
  fill: var(--ks3-ink-muted);
}
.ks3-wband-particle { fill: var(--ks3-ink); }
.ks3-wband-lattice { fill: none; stroke: var(--ks3-ink); stroke-width: 2; }

.ks3-wband-pair {
  margin-top: 18px; display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
  gap: 16px;
}
.ks3-wband-paircard {
  padding: 18px; border-radius: var(--ks3-r-card);
  background: var(--ks3-card); border: 2px solid var(--ks3-ink);
}
.ks3-wband-pairtitle {
  margin: 0 0 12px; font-family: var(--ks3-font-mono); font-size: 14px;
  font-weight: 500; letter-spacing: .08em; text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-wband-pairsvg { display: block; width: 100%; height: auto; }
.ks3-wband-pairbody {
  margin: 12px 0 0; font-size: 17px; line-height: 1.55;
  color: var(--ks3-ink-body);
}
.ks3-wband-pairwave, .ks3-wband-paircoil {
  fill: none; stroke: #2F6FEB; stroke-width: 6; stroke-linecap: round;
}
.ks3-wband-paircoil { stroke-width: 4; }
.ks3-wband-pairdot {
  fill: var(--ks3-accent); stroke: var(--ks3-ink); stroke-width: 4;
}
/* ⚠️ THE PARTICLE'S ARROW AND THE TRAVEL ARROW ARE DIFFERENT WEIGHTS AND
   DIFFERENT COLOURS, because the entire figure is the ANGLE between them.
   Drawn alike, a reader has to work out which is which before the point
   can land. */
.ks3-wband-pairmove {
  fill: none; stroke: var(--ks3-ink); stroke-width: 4;
  stroke-linecap: round; stroke-linejoin: round;
}
.ks3-wband-pairtravel {
  fill: none; stroke: var(--ks3-ink-muted); stroke-width: 4;
  stroke-linecap: round; stroke-linejoin: round;
}
.ks3-wband-pairregion {
  font-family: var(--ks3-font-mono); font-weight: 500; font-size: 13px;
  letter-spacing: .06em; fill: var(--ks3-ink-muted);
}
.ks3-wband-pairtravellabel {
  font-family: var(--ks3-font-mono); font-weight: 500; font-size: 15px;
  fill: var(--ks3-ink-muted);
}

.ks3-wband-tick { fill: none; stroke: var(--ks3-rule-strong);
  stroke-width: 2; }
.ks3-wband-ticklabel {
  font-family: var(--ks3-font-mono); font-size: 16px;
  fill: var(--ks3-ink-muted);
}

/* ═══ END P6 ═══ */

/* ═══ BEGIN P7 ═══════════════════════════════════════════════════════════
   P7 — *Light*. One marked block per unit.

   ⚠️ EVERY SELECTOR HERE IS P7-ONLY, and every shell stem was checked
   against the whole registry before it was written: none of `ks3-lrace-`,
   `ks3-rsurf-`, `ks3-rblock-`, `ks3-pinh-`, `ks3-eyecam-`, `ks3-prism-`,
   `ks3-clamp-` or `ks3-lband-` was taken. `ks3-cbench-` WAS taken (C7's),
   which is why the colour bench is `clamp`.

   ⚠️ EVERY FIGURE WRAPPER IS `position: relative`, for the reason the P4
   and P6 blocks give at length: every live value is an absolutely
   positioned HTML span over the SVG, because a hole inside an SVG `<text>`
   renders nothing and fails silently — and without a positioned ancestor
   those spans resolve against the page and widen the document on a phone.

   ⚠️ TOKENS, NOT LITERALS, FOR EVERY PIECE OF TEXT. `--ks3-accent` is
   3.4:1 and is LARGE TEXT ONLY; anything under 24px that wants orange
   takes `--ks3-accent-text`. Captions on the ink-dark benches take
   `--ks3-on-dark-muted`, which is 6.08:1 there, and readings take
   `--ks3-blue-light`.

   ⚠️ NO P7 BENCH STYLES A HEAD ROW, because no P7 bench draws one: the
   shell's `.ks3-blockhead` is Design's row and it is already styled.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── shared P7 bench shapes — the seven dark instruments ────────────── */

.ks3-lrace-gate, .ks3-rsurf-gate, .ks3-rblock-gate, .ks3-pinh-gate,
.ks3-eyecam-gate, .ks3-prism-gate, .ks3-clamp-gate {
  margin-top: 20px; padding: 20px 22px; border-radius: var(--ks3-r-panel);
  background: rgba(255, 255, 255, .06);
  border: 2px solid var(--ks3-on-dark-muted);
}

.ks3-lrace-body, .ks3-rsurf-body, .ks3-rblock-body, .ks3-pinh-body,
.ks3-eyecam-body, .ks3-prism-body, .ks3-clamp-body { margin-top: 22px; }

.ks3-lrace-controls, .ks3-rsurf-controls, .ks3-rblock-controls,
.ks3-pinh-controls, .ks3-eyecam-controls, .ks3-prism-controls,
.ks3-clamp-controls {
  display: grid; gap: 18px;
}
.ks3-lrace-row, .ks3-rsurf-row, .ks3-rblock-row, .ks3-pinh-row,
.ks3-eyecam-row { display: grid; gap: 8px; }
.ks3-lrace-rowhead, .ks3-rsurf-rowhead, .ks3-rblock-rowhead,
.ks3-pinh-rowhead, .ks3-eyecam-rowhead {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 16px; flex-wrap: wrap;
  font-family: var(--ks3-font-mono); font-size: 15px; font-weight: 500;
  letter-spacing: .07em; text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-lrace-reading, .ks3-rsurf-reading, .ks3-rblock-reading,
.ks3-pinh-reading, .ks3-eyecam-reading {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 17px;
  font-weight: 500; letter-spacing: normal; text-transform: none;
  color: var(--ks3-blue-light);
}
/* 44px of vertical target, which is the smallest a thumb should be asked
   to find — on p7-04 the sliders are two of the three controls. */
.ks3-lrace-slider, .ks3-rsurf-slider, .ks3-rblock-slider,
.ks3-pinh-slider, .ks3-eyecam-slider {
  width: 100%; min-height: 44px; accent-color: var(--ks3-accent);
}

.ks3-lrace-picker, .ks3-rsurf-picker, .ks3-rblock-picker,
.ks3-pinh-picker, .ks3-eyecam-picker, .ks3-prism-picker,
.ks3-clamp-picker { display: grid; gap: 9px; }
.ks3-lrace-pickerlabel, .ks3-rsurf-pickerlabel, .ks3-rblock-pickerlabel,
.ks3-pinh-pickerlabel, .ks3-eyecam-pickerlabel, .ks3-prism-pickerlabel,
.ks3-clamp-pickerlabel {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 14px;
  font-weight: 500; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-lrace-tabrow, .ks3-rsurf-tabrow, .ks3-rblock-tabrow,
.ks3-pinh-tabrow, .ks3-eyecam-tabrow, .ks3-prism-tabrow,
.ks3-clamp-tabrow { display: flex; flex-wrap: wrap; gap: 9px; }

.ks3-lrace-figwrap, .ks3-rsurf-figwrap, .ks3-rblock-figwrap,
.ks3-pinh-figwrap, .ks3-eyecam-figwrap, .ks3-prism-figwrap,
.ks3-clamp-figwrap {
  position: relative; margin-top: 18px; padding: 18px;
  border-radius: var(--ks3-r-card); background: var(--ks3-dark-panel);
}
/* ⚠️ THE INNER DIV IS THE POSITIONING CONTEXT, AND THE PADDING IS ON THE
   PANEL. Every live label's `left` / `top` is a percentage of Design's own
   viewBox, so it has to resolve against the DRAWING and not against the
   drawing plus 18px of card on every side. Making the padded panel the
   positioned ancestor slides every label on all seven benches up and to the
   left by that padding — on `p7-07` far enough to put "White light" under
   the lamp, so the page read "hite light". See `ks3_art/p7.py`. */
.ks3-lrace-figinner, .ks3-rsurf-figinner, .ks3-rblock-figinner,
.ks3-pinh-figinner, .ks3-eyecam-figinner, .ks3-prism-figinner,
.ks3-clamp-figinner { position: relative; }
.ks3-lrace-svg, .ks3-rsurf-svg, .ks3-rblock-svg, .ks3-pinh-svg,
.ks3-eyecam-svg, .ks3-prism-svg, .ks3-clamp-svg {
  display: block; width: 100%; height: auto;
}
.ks3-lrace-fill, .ks3-rsurf-fill, .ks3-rblock-fill, .ks3-pinh-fill,
.ks3-eyecam-fill, .ks3-prism-fill, .ks3-clamp-fill {
  position: absolute; font-family: var(--ks3-font-mono); font-weight: 500;
  font-size: clamp(10px, 1.4vw, 17px); letter-spacing: .08em;
  pointer-events: none;
}

.ks3-lrace-tiles, .ks3-rsurf-tiles, .ks3-rblock-tiles, .ks3-pinh-tiles,
.ks3-eyecam-tiles, .ks3-prism-tiles, .ks3-clamp-tiles {
  margin-top: 16px; display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr));
  gap: 12px;
}
.ks3-lrace-tile, .ks3-rsurf-tile, .ks3-rblock-tile, .ks3-pinh-tile,
.ks3-eyecam-tile, .ks3-prism-tile, .ks3-clamp-tile {
  padding: 15px 17px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-dark-panel);
}
.ks3-lrace-tile-label, .ks3-rsurf-tile-label, .ks3-rblock-tile-label,
.ks3-pinh-tile-label, .ks3-eyecam-tile-label, .ks3-prism-tile-label,
.ks3-clamp-tile-label {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-lrace-tile-value, .ks3-rsurf-tile-value, .ks3-rblock-tile-value,
.ks3-pinh-tile-value, .ks3-eyecam-tile-value, .ks3-prism-tile-value,
.ks3-clamp-tile-value {
  margin: 5px 0 0; font-family: var(--ks3-font-mono); font-size: 20px;
  font-weight: 500; color: var(--ks3-on-dark);
}
/* ⚖️ THE VERDICT TILE IS A SENTENCE, NOT A NUMBER. Design sets the fourth
   tile in the body face at 700 rather than mono, on every P7 bench, and it
   is the tile that says in WORDS what the drawing has just said in
   geometry — so hue is never carrying it alone. */
.ks3-lrace-tile:last-child .ks3-lrace-tile-value,
.ks3-rsurf-tile:last-child .ks3-rsurf-tile-value,
.ks3-rblock-tile:last-child .ks3-rblock-tile-value,
.ks3-prism-tile:last-child .ks3-prism-tile-value,
.ks3-clamp-tile:last-child .ks3-clamp-tile-value,
.ks3-eyecam-tile:nth-child(3) .ks3-eyecam-tile-value,
.ks3-eyecam-tile:nth-child(4) .ks3-eyecam-tile-value,
.ks3-pinh-tile:nth-child(4) .ks3-pinh-tile-value {
  font-family: var(--ks3-font-body); font-size: 20px; font-weight: 700;
}
.ks3-lrace-tile-sub, .ks3-rsurf-tile-sub, .ks3-rblock-tile-sub,
.ks3-pinh-tile-sub, .ks3-eyecam-tile-sub, .ks3-prism-tile-sub,
.ks3-clamp-tile-sub {
  margin: 4px 0 0; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; color: var(--ks3-on-dark-muted);
}

.ks3-lrace-note, .ks3-rsurf-note, .ks3-rblock-note, .ks3-pinh-note,
.ks3-eyecam-note, .ks3-prism-note, .ks3-clamp-note {
  margin: 16px 0 0; padding: 16px 18px;
  border-radius: var(--ks3-r-panel); background: var(--ks3-dark-panel);
  font-size: 18px; line-height: 1.55; color: var(--ks3-on-dark-body);
}

/* ── p7-01 · the flash-and-bang race ────────────────────────────────── */

.ks3-lrace-axis { fill: none; stroke: #8C8177; stroke-width: 4; }
.ks3-lrace-tick { fill: none; stroke: #8C8177; stroke-width: 3; }
.ks3-lrace-ticklabel {
  font-family: var(--ks3-font-mono); font-weight: 500; font-size: 14px;
  fill: var(--ks3-on-dark-muted);
}
.ks3-lrace-mark { fill: none; stroke: #E9DCC8; stroke-width: 5;
  stroke-linecap: round; }
.ks3-lrace-lanelabel {
  font-family: var(--ks3-font-mono); font-weight: 500; font-size: 15px;
  letter-spacing: .08em; fill: var(--ks3-on-dark-muted);
}
.ks3-lrace-light, .ks3-lrace-sound {
  fill: none; stroke: var(--ks3-blue-light); stroke-width: 8;
  stroke-linecap: round; stroke-linejoin: round;
}
/* ⚖️ THE BLOCKED SOUND ARROW IS THINNER AND DASHED AND STOPS SHORT. Three
   channels for one fact, because "it never arrives" is the fact the whole
   lesson turns on and a reader who cannot judge a length still gets it. */
.ks3-lrace-sound.is-blocked { stroke-width: 5; stroke-dasharray: 14 12; }
.ks3-lrace-axislabel {
  font-family: var(--ks3-font-mono); font-weight: 500; font-size: 14px;
  letter-spacing: .08em; fill: #8C8177;
}
.ks3-lrace-lt, .ks3-lrace-st { letter-spacing: normal; }

/* ── p7-02 · the ray box and four surfaces ──────────────────────────── */

.ks3-rsurf-surface { fill: none; stroke: #E9DCC8; stroke-width: 10;
  stroke-linecap: round; }
.ks3-rsurf-normal { fill: none; stroke: #8C8177; stroke-width: 3;
  stroke-dasharray: 9 9; }
.ks3-rsurf-normallabel {
  font-family: var(--ks3-font-mono); font-weight: 500; font-size: 15px;
  letter-spacing: .08em; fill: #8C8177;
}
.ks3-rsurf-in, .ks3-rsurf-out {
  fill: none; stroke: var(--ks3-blue-light); stroke-width: 7;
  stroke-linecap: round; stroke-linejoin: round;
}
/* ⚖️ MOSTLY ABSORBED IS DRAWN THIN AND FADED, and the readout says "about
   6%" beside it. Two channels: nobody has to judge a stroke width. */
.ks3-rsurf-out.is-faint { stroke-width: 3; opacity: .55; }
.ks3-rsurf-arcs { fill: none; stroke: var(--ks3-on-dark-muted);
  stroke-width: 3; }
.ks3-rsurf-caption, .ks3-rsurf-inc, .ks3-rsurf-ref {
  color: var(--ks3-on-dark-muted);
}
.ks3-rsurf-inc, .ks3-rsurf-ref { letter-spacing: normal; }

/* ── p7-03 · the ray into a block ───────────────────────────────────── */

.ks3-rblock-block { fill: #3A4A63; stroke: var(--ks3-blue-light);
  stroke-width: 4; }
.ks3-rblock-normal { fill: none; stroke: #8C8177; stroke-width: 3;
  stroke-dasharray: 9 9; }
.ks3-rblock-normallabel, .ks3-rblock-airlabel {
  font-family: var(--ks3-font-mono); font-weight: 500; font-size: 15px;
  letter-spacing: .08em; fill: #8C8177;
}
.ks3-rblock-ray { fill: none; stroke: var(--ks3-blue-light);
  stroke-width: 7; stroke-linecap: round; stroke-linejoin: round; }
/* The ray INSIDE the block is drawn in bone rather than blue, because the
   block itself is blue and a blue ray on a blue ground would be carrying
   its own path on contrast alone. */
.ks3-rblock-inner { fill: none; stroke: #E9DCC8; stroke-width: 7;
  stroke-linecap: round; stroke-linejoin: round; }
.ks3-rblock-ghost { fill: none; stroke: #5C554D; stroke-width: 3;
  stroke-dasharray: 10 10; }
.ks3-rblock-caption, .ks3-rblock-inc, .ks3-rblock-ref {
  color: var(--ks3-on-dark-muted);
}
.ks3-rblock-inc, .ks3-rblock-ref { letter-spacing: normal; }

/* ── p7-04 · the pinhole camera ─────────────────────────────────────── */

.ks3-pinh-axis { fill: none; stroke: #5C554D; stroke-width: 3;
  stroke-dasharray: 9 9; }
.ks3-pinh-object { fill: none; stroke: #E9DCC8; stroke-width: 8;
  stroke-linecap: round; stroke-linejoin: round; }
.ks3-pinh-box { fill: none; stroke: #8C8177; stroke-width: 5;
  stroke-linejoin: round; }
.ks3-pinh-ray { fill: none; stroke: var(--ks3-blue-light); stroke-width: 4;
  stroke-linecap: round; }
/* ⚠️ THE STROKE WIDTH IS THE BLUR, set from the payload at paint time, so
   it is deliberately NOT declared here — a `stroke-width` in this rule
   would be overridden by the inline one on every state and would be dead
   CSS that reads as the answer. */
.ks3-pinh-img { fill: none; stroke: #E9DCC8; stroke-linecap: round; }
.ks3-pinh-partlabel {
  font-family: var(--ks3-font-mono); font-weight: 500; font-size: 15px;
  letter-spacing: .08em; fill: var(--ks3-on-dark-muted);
}
.ks3-pinh-axislabel {
  font-family: var(--ks3-font-mono); font-weight: 500; font-size: 15px;
  letter-spacing: .08em; fill: #8C8177;
}
.ks3-pinh-hole-label { color: var(--ks3-on-dark-muted); }

/* ── p7-05 · the eye and the camera ─────────────────────────────────── */

.ks3-eyecam-scene { fill: none; stroke: #E9DCC8; stroke-width: 8;
  stroke-linecap: round; stroke-linejoin: round; }
.ks3-eyecam-case { fill: none; stroke: #8C8177; stroke-width: 6;
  stroke-linejoin: round; }
.ks3-eyecam-stop { fill: none; stroke: #E9DCC8; stroke-width: 12;
  stroke-linecap: round; }
.ks3-eyecam-lens { fill: #3A4A63; stroke: var(--ks3-blue-light);
  stroke-width: 4; }
.ks3-eyecam-back { fill: none; stroke: var(--ks3-blue-light);
  stroke-width: 12; stroke-linecap: round; }
.ks3-eyecam-rays { fill: none; stroke: var(--ks3-blue-light);
  stroke-width: 4; stroke-linecap: round; }
.ks3-eyecam-img { fill: none; stroke: #E9DCC8; stroke-width: 7;
  stroke-linecap: round; stroke-linejoin: round; }
.ks3-eyecam-partlabel {
  font-family: var(--ks3-font-mono); font-weight: 500; font-size: 15px;
  letter-spacing: .08em; fill: var(--ks3-on-dark-muted);
}
.ks3-eyecam-caption { color: var(--ks3-on-dark-muted); }
.ks3-eyecam-fill.ks3-eyecam-stop { color: #E9DCC8; }
.ks3-eyecam-fill.ks3-eyecam-absorb { color: var(--ks3-blue-light); }

/* ── p7-06 · the prism bench ────────────────────────────────────────── */

.ks3-prism-glass { fill: #3A4A63; stroke: var(--ks3-blue-light);
  stroke-width: 4; stroke-linejoin: round; }
.ks3-prism-beam { fill: none; stroke: #F2ECDD; stroke-width: 9;
  stroke-linecap: round; }
/* ⚖️ HUE IS PART OF THE MESSAGE HERE AND IT IS NEVER ALONE. The six rays
   are the six colours, and every state also names them in the readout
   tiles and in the note — "red bent least, violet bent most" — so the
   figure is answerable without seeing the hues at all. */
.ks3-prism-ray { fill: none; stroke-width: 7; stroke-linecap: round; }
.ks3-prism-ray-r { stroke: #D9563A; }
.ks3-prism-ray-o { stroke: #DE8A3C; }
.ks3-prism-ray-y { stroke: #D8C24A; }
.ks3-prism-ray-g { stroke: #5BA45C; }
.ks3-prism-ray-b { stroke: #4F7DC4; }
.ks3-prism-ray-v { stroke: #8567B5; }
.ks3-prism-screen { fill: none; stroke: var(--ks3-on-dark-muted);
  stroke-width: 10; stroke-linecap: round; }
.ks3-prism-partlabel {
  font-family: var(--ks3-font-mono); font-weight: 500; font-size: 15px;
  letter-spacing: .08em; fill: var(--ks3-on-dark-muted);
}
.ks3-prism-caption { color: var(--ks3-on-dark-muted); }

/* ── p7-07 · the colour bench ───────────────────────────────────────── */

.ks3-clamp-lampbody { fill: var(--ks3-on-dark-muted); stroke: #8C8177;
  stroke-width: 4; stroke-linejoin: round; }
.ks3-clamp-in, .ks3-clamp-out {
  fill: none; stroke-width: 11; stroke-linecap: round;
}
/* ⚖️ NOTHING COMES BACK IS DRAWN AS A GREY DASH, and the label beside it
   says "nothing comes back". Two channels, and the second one survives a
   reader who cannot tell the dashes from the colours. */
.ks3-clamp-out.is-blocked { stroke: #5C554D; stroke-width: 4;
  stroke-dasharray: 12 12; }
.ks3-clamp-rect { stroke: var(--ks3-on-dark-muted); stroke-width: 4; }
.ks3-clamp-eye { fill: none; stroke: var(--ks3-on-dark-muted);
  stroke-width: 5; }
.ks3-clamp-pupil { fill: var(--ks3-on-dark-muted); }
.ks3-clamp-partlabel {
  font-family: var(--ks3-font-mono); font-weight: 500; font-size: 15px;
  letter-spacing: .08em; fill: var(--ks3-on-dark-muted);
}
.ks3-clamp-absorbcap, .ks3-clamp-objcap, .ks3-clamp-inlabel,
.ks3-clamp-outlabel { color: var(--ks3-on-dark-muted); }

/* ── the band blocks · the six fixed figures, on the light ground ───── */

.ks3-lband { margin-top: 6px; }
.ks3-lband-heading {
  margin: 6px 0 16px; font-family: var(--ks3-font-display);
  font-weight: 800; font-size: 30px; line-height: 1.12;
  letter-spacing: -.03em; color: var(--ks3-ink);
}
/* ⚠️ A WIDE TABLE SCROLLS INSIDE ITS OWN BOX, never on the page body, and
   the scroller is `position: relative` because §5A's rule for absolutely
   positioned children applies to any scroller — this one has none today
   and would resolve them against the page if it ever gained one. */
.ks3-lband-scroller {
  position: relative; margin-top: 18px; overflow-x: auto;
  border-radius: var(--ks3-r-card); background: var(--ks3-card);
  border: 2px solid var(--ks3-ink); padding: 4px;
}
.ks3-lband-table {
  border-collapse: collapse; width: 100%; min-width: 620px; font-size: 18px;
}
.ks3-lband-table th[scope="col"] {
  text-align: left; padding: 14px 16px;
  border-bottom: 3px solid var(--ks3-ink);
  font-family: var(--ks3-font-mono); font-size: 14px; font-weight: 500;
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-lband-table th[scope="row"] {
  text-align: left; padding: 14px 16px; font-weight: 700;
  border-bottom: 2px solid var(--ks3-rule); color: var(--ks3-ink);
}
.ks3-lband-table td {
  padding: 14px 16px; border-bottom: 2px solid var(--ks3-rule);
  color: var(--ks3-ink-body);
}
.ks3-lband-table tbody tr:last-child th[scope="row"],
.ks3-lband-table tbody tr:last-child td { border-bottom: 0; }

.ks3-lband-figwrap {
  position: relative; margin-top: 18px; padding: 18px;
  border-radius: var(--ks3-r-card); background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
}
.ks3-lband-svg { display: block; width: 100%; height: auto; }

.ks3-lband-close {
  margin: 16px 0 0; padding: 16px 18px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-card); border: 2px solid var(--ks3-option-border);
  font-size: 19px; line-height: 1.6; color: var(--ks3-ink-body);
}

/* p7-03 · the apparent-depth drawing */
.ks3-lband-glass { fill: none; stroke: var(--ks3-ink); stroke-width: 6;
  stroke-linejoin: round; }
.ks3-lband-water { fill: var(--ks3-blue-tint); }
.ks3-lband-surface { fill: none; stroke: var(--ks3-blue); stroke-width: 5; }
.ks3-lband-straw { fill: none; stroke: var(--ks3-ink); stroke-width: 12;
  stroke-linecap: round; }
.ks3-lband-rayline { fill: none; stroke: var(--ks3-accent);
  stroke-width: 5; stroke-linecap: round; }
.ks3-lband-back { fill: none; stroke: var(--ks3-ink-ghost);
  stroke-width: 4; stroke-dasharray: 10 10; }
.ks3-lband-normal { fill: none; stroke: var(--ks3-ink-ghost);
  stroke-width: 3; stroke-dasharray: 8 8; }
.ks3-lband-eye { fill: none; stroke: var(--ks3-ink); stroke-width: 5; }
.ks3-lband-pupil { fill: var(--ks3-ink); }
.ks3-lband-looks { fill: none; stroke: var(--ks3-accent); stroke-width: 5; }
.ks3-lband-is { fill: var(--ks3-ink); }
.ks3-lband-figlabel {
  font-family: var(--ks3-font-mono); font-weight: 500; font-size: 15px;
  fill: var(--ks3-ink-muted);
}
/* `--ks3-accent-text`, not `--ks3-accent`: this label is 14px and the
   accent is LARGE TEXT ONLY. */
.ks3-lband-looklabel {
  font-family: var(--ks3-font-mono); font-weight: 500; font-size: 14px;
  fill: var(--ks3-accent-text);
}

/* p7-04 · the two lens diagrams */
.ks3-lband-pair {
  margin-top: 18px; display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
  gap: 16px;
}
.ks3-lband-paircard {
  border-radius: var(--ks3-r-card); background: var(--ks3-card);
  border: 2px solid var(--ks3-ink); padding: 18px;
}
.ks3-lband-pairtitle {
  margin: 0 0 12px; font-family: var(--ks3-font-mono); font-size: 14px;
  font-weight: 500; letter-spacing: .08em; text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-lband-pairsvg { display: block; width: 100%; height: auto; }
.ks3-lband-pairbody {
  margin: 14px 0 0; font-size: 18px; line-height: 1.55;
  color: var(--ks3-ink-body);
}
.ks3-lband-axis { fill: none; stroke: var(--ks3-ink-ghost);
  stroke-width: 2; stroke-dasharray: 8 8; }
.ks3-lband-lens { fill: var(--ks3-blue-tint); stroke: var(--ks3-ink);
  stroke-width: 4; }
.ks3-lband-beam { fill: none; stroke: var(--ks3-blue); stroke-width: 4; }
.ks3-lband-focus { fill: var(--ks3-accent); stroke: var(--ks3-ink);
  stroke-width: 4; }
.ks3-lband-object { fill: none; stroke: var(--ks3-ink); stroke-width: 6;
  stroke-linecap: round; stroke-linejoin: round; }
.ks3-lband-screen { fill: none; stroke: var(--ks3-ink-ghost);
  stroke-width: 6; }
.ks3-lband-image { fill: none; stroke: var(--ks3-accent); stroke-width: 6;
  stroke-linecap: round; stroke-linejoin: round; }

/* p7-06 · the spectrum band */
.ks3-lband-segframe { fill: none; stroke: var(--ks3-ink); stroke-width: 4; }
.ks3-lband-segname {
  font-family: var(--ks3-font-mono); font-weight: 500; font-size: 17px;
  fill: var(--ks3-ink);
}
.ks3-lband-arrow { fill: none; stroke: var(--ks3-ink); stroke-width: 4;
  stroke-linecap: round; stroke-linejoin: round; }
.ks3-lband-arrow-accent { stroke: var(--ks3-accent); }
.ks3-lband-arrowlabel {
  font-family: var(--ks3-font-body); font-weight: 600; font-size: 19px;
  fill: var(--ks3-ink);
}

/* ═══ END P7 ═══ */

/* ═══ BEGIN P8 ═══════════════════════════════════════════════════════════
   P8 — *Electric circuits*. One marked block per unit.

   ⚠️ EVERY SELECTOR HERE IS P8-ONLY, and every shell stem was checked
   against the whole registry before it was written: none of `ks3-cloop-`,
   `ks3-parr-`, `ks3-junc-`, `ks3-vtap-`, `ks3-cundt-`, `ks3-tgap-`,
   `ks3-mplace-`, `ks3-cband-` or `ks3-cthink-` was taken. Two obvious ones
   WERE — `ks3-cut-block` and `ks3-fault-block` — which is why
   `component-under-test` wears `ks3-cundt-` and the fault table goes
   through the band family.

   ⚠️ EVERY FIGURE WRAPPER IS `position: relative`, for the reason the P4
   block gives at length: every live value is an absolutely positioned HTML
   span over the SVG, because a hole inside an SVG `<text>` renders nothing
   and fails silently — and without a positioned ancestor those spans
   resolve against the page and widen the document on a phone. The band
   block's table SCROLLER is positioned for the same reason.

   ⚠️ TOKENS, NOT LITERALS, FOR EVERY PIECE OF TEXT. `--ks3-accent` is
   3.4:1 and is LARGE TEXT ONLY; anything under 24px that wants orange
   takes `--ks3-accent-text`, and that token never lands on a dark ground.
   Captions on the ink-dark benches take `--ks3-on-dark-muted`, which is
   6.08:1 there, and readings take `--ks3-blue-light`.

   ⚠️ AMBER WARNS; IT NEVER MERELY LABELS. The only amber in this unit is
   `p8-07`'s LOOSE marker, which is a fault state, and the divider the
   engine already draws above a second misconception quote.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── shared P8 bench shapes — the seven dark instruments ────────────── */

.ks3-cloop-gate, .ks3-parr-gate, .ks3-junc-gate, .ks3-vtap-gate,
.ks3-cundt-gate, .ks3-tgap-gate, .ks3-mplace-gate {
  padding: 20px 22px; border-radius: var(--ks3-r-panel);
  background: rgba(255, 255, 255, .06);
  border: 2px solid var(--ks3-on-dark-muted);
}

/* ⚠️ NO `-head` OR `-progress` RULES HERE, AND NO `-cband-heading`.
   `r_activity`'s shell draws the head row — eyebrow, `<h2>` and the
   right-aligned mono readout — and styles it as `.ks3-blockhead` /
   `.ks3-blockhead-count`, which already exist. An instrument that drew its
   own would ship the title twice; a stylesheet that styled the second one
   would be dead CSS, which is evidence to a grep and nothing to a browser. */
.ks3-cloop-lead, .ks3-parr-lead, .ks3-junc-lead, .ks3-vtap-lead,
.ks3-cundt-lead, .ks3-tgap-lead, .ks3-mplace-lead {
  margin: 0 0 18px; font-size: 19px; line-height: 1.65;
  color: var(--ks3-on-dark-body); max-width: 58ch;
}

.ks3-cloop-controls, .ks3-parr-controls, .ks3-junc-controls,
.ks3-vtap-controls, .ks3-cundt-controls, .ks3-tgap-controls,
.ks3-mplace-controls {
  display: flex; flex-wrap: wrap; gap: 20px 30px;
}
.ks3-cloop-picker, .ks3-parr-picker, .ks3-junc-picker, .ks3-vtap-picker,
.ks3-cundt-picker, .ks3-tgap-picker, .ks3-mplace-picker {
  display: grid; gap: 9px; flex: 1 1 260px;
}
.ks3-cloop-pickerlabel, .ks3-parr-pickerlabel, .ks3-junc-pickerlabel,
.ks3-vtap-pickerlabel, .ks3-cundt-pickerlabel, .ks3-tgap-pickerlabel,
.ks3-mplace-pickerlabel {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 14px;
  font-weight: 500; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-cloop-tabrow, .ks3-parr-tabrow, .ks3-junc-tabrow, .ks3-vtap-tabrow,
.ks3-cundt-tabrow, .ks3-tgap-tabrow, .ks3-mplace-tabrow {
  display: flex; flex-wrap: wrap; gap: 9px;
}

/* Only `p8-05` carries a slider, and it is the supply. 44px of vertical
   target, which is the smallest a thumb should be asked to find. */
.ks3-cundt-row { display: grid; gap: 8px; flex: 1 1 280px; min-width: 240px; }
.ks3-cundt-rowhead {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 16px; flex-wrap: wrap;
  font-family: var(--ks3-font-mono); font-size: 14px; font-weight: 500;
  letter-spacing: .07em; text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-cundt-reading {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 17px;
  font-weight: 500; color: var(--ks3-blue-light); white-space: nowrap;
}
.ks3-cundt-slider {
  width: 100%; min-height: 44px; accent-color: var(--ks3-accent);
}

.ks3-cloop-figwrap, .ks3-parr-figwrap, .ks3-junc-figwrap,
.ks3-vtap-figwrap, .ks3-cundt-figwrap, .ks3-tgap-figwrap,
.ks3-mplace-figwrap {
  position: relative; margin-top: 18px; padding: 18px;
  border-radius: var(--ks3-r-card); background: var(--ks3-dark-panel);
}
.ks3-cloop-svg, .ks3-parr-svg, .ks3-junc-svg, .ks3-vtap-svg,
.ks3-cundt-svg, .ks3-tgap-svg, .ks3-mplace-svg {
  display: block; width: 100%; height: auto;
}
.ks3-cloop-fill, .ks3-parr-fill, .ks3-junc-fill, .ks3-vtap-fill,
.ks3-cundt-fill, .ks3-tgap-fill, .ks3-mplace-fill {
  position: absolute; font-family: var(--ks3-font-mono); font-weight: 500;
  font-size: clamp(10px, 1.6vw, 19px); white-space: nowrap;
  pointer-events: none;
}
/* The two that are part names rather than readings sit a size down, as
   Design draws them, and take the muted on-dark rather than the reading
   blue — a name is not a measurement. */
.ks3-junc-aname, .ks3-junc-bname, .ks3-vtap-lamp, .ks3-vtap-comp2 {
  font-size: clamp(9px, 1.2vw, 15px); letter-spacing: .07em;
  text-transform: uppercase;
}
.ks3-tgap-name {
  font-size: clamp(9px, 1.3vw, 16px); letter-spacing: .06em;
  text-transform: uppercase;
}
.ks3-cundt-name, .ks3-mplace-joint {
  font-size: clamp(9px, 1.2vw, 15px); letter-spacing: .07em;
  text-transform: uppercase;
}

.ks3-cloop-tiles, .ks3-parr-tiles, .ks3-junc-tiles, .ks3-vtap-tiles,
.ks3-cundt-tiles, .ks3-tgap-tiles, .ks3-mplace-tiles {
  margin-top: 16px; display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr));
  gap: 12px;
}
.ks3-cloop-tile, .ks3-parr-tile, .ks3-junc-tile, .ks3-vtap-tile,
.ks3-cundt-tile, .ks3-tgap-tile, .ks3-mplace-tile {
  padding: 15px 17px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-dark-panel);
}
.ks3-cloop-tile-label, .ks3-parr-tile-label, .ks3-junc-tile-label,
.ks3-vtap-tile-label, .ks3-cundt-tile-label, .ks3-tgap-tile-label,
.ks3-mplace-tile-label {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-cloop-tile-value, .ks3-parr-tile-value, .ks3-junc-tile-value,
.ks3-vtap-tile-value, .ks3-cundt-tile-value, .ks3-tgap-tile-value,
.ks3-mplace-tile-value {
  margin: 5px 0 0; font-family: var(--ks3-font-mono); font-size: 22px;
  font-weight: 500; color: var(--ks3-on-dark);
}
/* A verdict is a WORD, not a reading, so it takes the body face at a
   weight rather than the mono a measurement takes. Design's own split. */
/* ⚠️ `.ks3-vtap-tile-word` IS NOT IN THIS LIST. `p8-04`'s four readouts are
   all numbers — the battery, the two shares and the meter — so it has no
   word tile, and a selector for one would be dead CSS. */
.ks3-cloop-tile-word, .ks3-parr-tile-word, .ks3-junc-tile-word,
.ks3-cundt-tile-word, .ks3-tgap-tile-word, .ks3-mplace-tile-word {
  font-family: inherit; font-size: 20px; font-weight: 700;
}
/* ⚠️ THE SECOND VALUE ELEMENT, and it exists for exactly one tile in the
   unit: `p8-06`'s ammeter, which reads a mono figure for six specimens and
   the sentence "limited by the supply, not by the wire" for copper. Two
   elements and a `hidden` toggle, because they are different type at
   different sizes. */
.ks3-tgap-tile-alt {
  margin: 5px 0 0; font-size: 18px; font-weight: 700; line-height: 1.3;
  color: var(--ks3-on-dark);
}
.ks3-cloop-tile-sub, .ks3-parr-tile-sub, .ks3-junc-tile-sub,
.ks3-vtap-tile-sub, .ks3-cundt-tile-sub, .ks3-tgap-tile-sub,
.ks3-mplace-tile-sub {
  margin: 4px 0 0; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; color: var(--ks3-on-dark-muted);
}

.ks3-cloop-note, .ks3-parr-note, .ks3-junc-note, .ks3-vtap-note,
.ks3-cundt-note, .ks3-tgap-note, .ks3-mplace-note {
  margin: 16px 0 0; padding: 16px 18px;
  border-radius: var(--ks3-r-panel); background: var(--ks3-dark-panel);
  font-size: 18px; line-height: 1.55; color: var(--ks3-on-dark-body);
}

/* ── the wire, the cells and the meters — shared across all seven ───── */

.ks3-cloop-wire, .ks3-parr-wirepath, .ks3-junc-wire, .ks3-vtap-wire,
.ks3-cundt-wire, .ks3-tgap-wire, .ks3-mplace-wire {
  fill: none; stroke: var(--ks3-on-dark-muted); stroke-width: 6;
  stroke-linecap: round;
}
.ks3-cloop-cell, .ks3-parr-cell, .ks3-junc-cell, .ks3-vtap-cell,
.ks3-cundt-cell, .ks3-tgap-cell, .ks3-mplace-cell {
  fill: none; stroke: var(--ks3-on-dark); stroke-width: 6;
  stroke-linecap: round;
}
.ks3-cloop-dot, .ks3-parr-dot, .ks3-junc-dot { fill: var(--ks3-on-dark); }
.ks3-cloop-meter, .ks3-parr-meter, .ks3-junc-meter, .ks3-vtap-meter,
.ks3-cundt-meter, .ks3-tgap-meter, .ks3-mplace-meter {
  fill: var(--ks3-ink); stroke: var(--ks3-blue-light); stroke-width: 6;
}
.ks3-cloop-meterletter, .ks3-parr-meterletter, .ks3-junc-meterletter,
.ks3-vtap-meterletter, .ks3-cundt-meterletter, .ks3-tgap-meterletter,
.ks3-mplace-meterletter {
  font-family: var(--ks3-font-display); font-weight: 800; font-size: 40px;
  fill: var(--ks3-blue-light);
}
.ks3-junc-meterletter-sm { font-size: 32px; }
.ks3-parr-meterletter, .ks3-mplace-meterletter { font-size: 36px; }
.ks3-junc-meterletter { font-size: 36px; }
.ks3-cloop-partlabel, .ks3-parr-partlabel, .ks3-junc-partlabel,
.ks3-vtap-partlabel, .ks3-cundt-partlabel, .ks3-tgap-partlabel,
.ks3-mplace-partlabel {
  font-family: var(--ks3-font-mono); font-weight: 500; font-size: 15px;
  fill: var(--ks3-on-dark-muted);
}
/* The lamp: solid ring, cross always drawn while the bulb is present, and
   a blue fill only while it is lit. Hue is never the only channel — the
   tile beside it says `dim`, `dark` or `lit, at full brightness`. */
.ks3-cloop-bulb, .ks3-parr-bulb, .ks3-mplace-lamp {
  fill: var(--ks3-ink); stroke: var(--ks3-on-dark-muted); stroke-width: 6;
}
.ks3-cloop-bulb[data-lit="1"], .ks3-parr-bulb[data-lit="1"],
.ks3-mplace-lamp[data-lit="1"] { fill: var(--ks3-blue-light); }
.ks3-parr-bulb[data-present="0"] {
  fill: none; stroke: var(--ks3-ink-muted); stroke-dasharray: 13 11;
}
.ks3-cloop-filament, .ks3-parr-cross, .ks3-mplace-filament {
  fill: none; stroke: var(--ks3-ink); stroke-width: 5; stroke-linecap: round;
}
/* ⊕ THE FILAMENT CROSS STAYS LEGIBLE WHEN THE LAMP IS DARK, and that is a
   registered departure from Design's drawing. Hers strokes the cross in
   `#221E1B` on an unlit fill of `#15110C` — about 1.2:1, which is invisible
   — so a dark lamp reads as a plain circle with a ring round it. The cross
   IS the lamp symbol, and `p8-01` spends a whole figure teaching a student
   to read it; a symbol that disappears exactly when the page asks "why is
   it dark?" is the one moment it has to be there. Dark ink on the blue
   filament when lit, as she drew it; muted on-dark when not.
   See DEPARTURES-P8.md. */
.ks3-cloop-bulb[data-lit="0"] + .ks3-cloop-filament,
.ks3-parr-bulb[data-lit="0"] + .ks3-parr-cross,
.ks3-mplace-lamp[data-lit="0"] + .ks3-mplace-filament {
  stroke: var(--ks3-on-dark-muted);
}
.ks3-cloop-rays, .ks3-parr-rays, .ks3-mplace-rays {
  fill: none; stroke: var(--ks3-blue-light); stroke-linecap: round;
}
.ks3-cloop-lever {
  fill: none; stroke: var(--ks3-on-dark); stroke-width: 6;
  stroke-linecap: round;
}

/* ── p8-03 · the junction bench ─────────────────────────────────────── */

.ks3-junc-comp {
  fill: none; stroke: var(--ks3-on-dark-muted); stroke-width: 6;
  stroke-linecap: round; stroke-linejoin: round;
}
.ks3-junc-compfill { fill: var(--ks3-on-dark-muted); stroke: none; }
.ks3-junc-arrow {
  fill: none; stroke: var(--ks3-blue-light); stroke-linecap: round;
  stroke-linejoin: round;
}

/* ── p8-04 · the voltmeter tap ──────────────────────────────────────── */

.ks3-vtap-comp {
  fill: none; stroke: var(--ks3-on-dark-muted); stroke-width: 6;
  stroke-linecap: round; stroke-linejoin: round;
}
.ks3-vtap-lead, .ks3-cundt-lead {
  fill: none; stroke: var(--ks3-blue-light); stroke-width: 5;
  stroke-linecap: round; stroke-linejoin: round;
}
.ks3-vtap-tap, .ks3-cundt-tap { fill: var(--ks3-blue-light); stroke: none; }

/* ── p8-05 · the component under test ───────────────────────────────── */

.ks3-cundt-comppath {
  fill: none; stroke: var(--ks3-on-dark-muted); stroke-linejoin: round;
}
.ks3-cundt-dial {
  fill: none; stroke: var(--ks3-blue-light); stroke-width: 5;
  stroke-linecap: round; stroke-linejoin: round;
}
.ks3-cundt-flow {
  fill: none; stroke: var(--ks3-blue-light); stroke-linecap: round;
  stroke-linejoin: round;
}

/* ── p8-06 · the test gap ───────────────────────────────────────────── */

.ks3-tgap-clip { fill: var(--ks3-on-dark-muted); stroke: none; }
.ks3-tgap-specimen {
  fill: var(--ks3-dark-panel); stroke: var(--ks3-on-dark-muted);
  stroke-width: 5;
}
.ks3-tgap-flow {
  fill: none; stroke: var(--ks3-blue-light); stroke-linecap: round;
  stroke-linejoin: round;
}

/* ── p8-07 · the meter placement bench ──────────────────────────────── */

.ks3-mplace-lead {
  fill: none; stroke: var(--ks3-blue-light); stroke-width: 5;
  stroke-linecap: round; stroke-linejoin: round;
}
.ks3-mplace-tap { fill: var(--ks3-blue-light); stroke: none; }
/* ⚠️ AMBER WARNS. The loose joint is a FAULT state, which is the one thing
   §8's amber is reserved for, and the word LOOSE is printed beside it so
   the colour is never the only channel. */
.ks3-mplace-joint { fill: var(--ks3-alert); stroke: none; }

/* ── the band block · every fixed figure in the unit ────────────────── */

.ks3-cband { margin-top: 4px; }
.ks3-cband-lead {
  margin: 12px 0 0; font-size: 19px; line-height: 1.6;
  color: var(--ks3-ink-body); max-width: 62ch;
}
.ks3-cband-close {
  margin: 16px 0 0; padding: 16px 18px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-card); border: 2px solid var(--ks3-option-border);
  font-size: 19px; line-height: 1.6; color: var(--ks3-ink-body);
}

/* p8-01's eight symbols. */
.ks3-cband-symbols {
  margin-top: 20px; display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr));
  gap: 12px;
}
.ks3-cband-symcard {
  padding: 16px 18px 12px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-card); border: 2px solid var(--ks3-ink);
}
.ks3-cband-symsvg { display: block; width: 100%; height: auto; }
.ks3-cband-symstroke {
  fill: none; stroke: var(--ks3-ink); stroke-linecap: round;
}
.ks3-cband-symletter {
  font-family: var(--ks3-font-display); font-weight: 800; font-size: 32px;
  fill: var(--ks3-ink);
}
.ks3-cband-symname {
  margin: 10px 0 0; font-weight: 700; font-size: 18px; color: var(--ks3-ink);
}
.ks3-cband-symnote {
  margin: 2px 0 0; font-family: var(--ks3-font-mono); font-size: 14px;
  color: var(--ks3-ink-muted);
}

/* The four tables. ⚠️ `position: relative` on the scroller: nothing here is
   absolutely positioned today, and an absolutely positioned element inside
   a scroller with no positioned ancestor resolves against the page. */
.ks3-cband-scroll {
  position: relative; margin-top: 18px; overflow-x: auto;
  border-radius: var(--ks3-r-card); background: var(--ks3-card);
  border: 2px solid var(--ks3-ink); padding: 4px;
}
.ks3-cband-table {
  border-collapse: collapse; width: 100%; font-size: 18px;
  color: var(--ks3-ink-body);
}
.ks3-cband-table thead th {
  text-align: left; padding: 14px 16px;
  border-bottom: 3px solid var(--ks3-ink);
  font-family: var(--ks3-font-mono); font-size: 14px; font-weight: 500;
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-cband-table tbody th {
  text-align: left; padding: 13px 16px; font-weight: 700;
  color: var(--ks3-ink);
}
.ks3-cband-table tbody td { padding: 13px 16px; }
.ks3-cband-table tbody tr { border-bottom: 2px solid var(--ks3-rule); }
.ks3-cband-table tbody tr:last-child { border-bottom: 0; }
/* A `same resistor` / `same lamp` row is a REPEAT of the row above it, and
   Design lightens its row header to say so. The word is what carries the
   meaning; the weight is the redundant second channel. */
.ks3-cband-samehead { font-weight: 400 !important; color: var(--ks3-ink-muted) !important; }

/* p8-06's decade chart. */
.ks3-cband-chartwrap {
  margin-top: 18px; border-radius: var(--ks3-r-card);
  background: var(--ks3-card); border: 2px solid var(--ks3-ink);
  padding: 16px;
}
.ks3-cband-chart { display: block; width: 100%; height: auto; }
.ks3-cband-axis { fill: none; stroke: var(--ks3-ink); stroke-width: 4; }
.ks3-cband-tick { fill: none; stroke: var(--ks3-ink); stroke-width: 3; }
.ks3-cband-ticklabel, .ks3-cband-axisnote {
  font-family: var(--ks3-font-mono); font-weight: 500; font-size: 15px;
  fill: var(--ks3-ink-muted);
}
.ks3-cband-axisnote { font-size: 14px; letter-spacing: .08em; }
/* ⚠️ THE BOUNDARY IS DASHED AND ITS LABEL SAYS `NO SHARP LINE`. It is a
   convenience, not a real dividing line, and the figure says so on its own
   face rather than in a caption a reader may not reach. */
.ks3-cband-bound {
  fill: none; stroke: var(--ks3-rule-strong); stroke-width: 3;
  stroke-dasharray: 9 8;
}
.ks3-cband-boundlabel {
  font-family: var(--ks3-font-mono); font-weight: 500; font-size: 14px;
  letter-spacing: .06em; fill: var(--ks3-ink-muted);
}
/* Two tones, derived from the boundary rather than authored per row — and
   every bar carries its own resistance in words beside it, so the tone is
   the redundant channel and never the only one. */
.ks3-cband-bar {
  fill: var(--ks3-blue-tint); stroke: var(--ks3-ink); stroke-width: 3;
}
.ks3-cband-bar-high { fill: var(--ks3-accent-tint); }
.ks3-cband-rowlabel {
  font-family: var(--ks3-font-body); font-weight: 700; font-size: 17px;
  fill: var(--ks3-ink);
}
.ks3-cband-rowvalue {
  font-family: var(--ks3-font-mono); font-weight: 500; font-size: 15px;
  fill: var(--ks3-ink-muted);
}

/* ── p8-01 · the one confrontation that is a rail stop ──────────────── */
/*
   No treatment of its own, deliberately. `circ-think` renders exactly the
   three classes `r_confrontation` renders — `.ks3-mis-quote`,
   `.ks3-mis-body` and the `.ks3-mis-next` divider — inside the same
   `ks3-misconception` shell, so the block a student sees is the block every
   other "Think again" in the key stage is. The family exists only to get
   `data-stage-done="0"` into the tag, because Design puts this section on
   `p8-01`'s rail and `ks3_parity.check_rail_reachable` reads the shipped
   bytes.
*/
.ks3-cthink { display: contents; }

/* ═══ END P8 ═══ */

/* ═══ BEGIN P9 ═══════════════════════════════════════════════════════════
   P9 — *Static electricity*. One marked block per unit.

   ⚠️ EVERY SELECTOR HERE IS P9-ONLY, and every shell stem was checked
   against the whole registry before it was written. One of them had to
   move: `ks3-cpair-` — the obvious stem for a pair of charged spheres — is
   already C4's `change-pairs`, so this unit uses `ks3-chpair-`. That is the
   MRB-279 collision exactly, caught by the assertion rather than in a
   browser open on a chemistry page.

   ⚠️ EVERY FIGURE WRAPPER IS `position: relative`. Every live value on all
   three benches is an absolutely positioned HTML span over the SVG, because
   a hole inside an SVG `<text>` renders nothing and fails silently — and
   without a positioned ancestor those spans resolve against the page and
   widen the document on a phone.

   ⚠️ TOKENS, NOT LITERALS, FOR EVERY PIECE OF TEXT. Design draws her fixed
   SVG captions at `#8C8177`, which is 3.08:1 on `--ks3-dark-panel` at 14px
   — below AA for body-size text, and the exact case `shared/tokens.css`
   warns about beside `--ks3-on-dark-muted`. Every one of them takes
   `--ks3-on-dark-muted` here (6.09:1). Registered in `DEPARTURES-P9.md`.

   ⚠️ THE ONE AMBER IN THE UNIT IS `p9-03`'s TEST POINT, AND IT STAYS.
   `--ks3-data` is the token 5A.2 sends a selection mark to — and its value
   is `#8FB7FF`, which is the colour of the ninety-one field arrows the test
   point has to be distinguishable from. Design's own §9 records amber on
   this marker as one of four declared uses in P8/P9, the tiles carry the
   same reading in words, and the substitution would erase the distinction
   rather than improve it. Kept, and logged as considered-not-changed.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── shared P9 bench shapes — the three dark instruments ─────────────── */

.ks3-xfer-gate, .ks3-chpair-gate, .ks3-fgrid-gate {
  padding: 20px 22px; border-radius: var(--ks3-r-panel);
  background: rgba(255, 255, 255, .06);
  border: 2px solid var(--ks3-on-dark-muted);
}

/* ⚠️ NO `.ks3-*-head` RULES, AND NO `-progress` RULE. This unit draws no head
   row of its own: `r_activity`'s shell owns it, `.ks3-blockhead` and
   `.ks3-blockhead-count` already style it, and it is already Design's layout
   — eyebrow and heading left, readout right. See the long note where `_head`
   is NOT, in `ks3_art/p9.py`, and the live P4/P5/P6 pages that render both. */
.ks3-xfer-lead, .ks3-chpair-lead, .ks3-fgrid-lead {
  margin: 0 0 18px; font-size: 19px; line-height: 1.65;
  color: var(--ks3-on-dark-body); max-width: 58ch;
}

.ks3-xfer-controls, .ks3-chpair-controls, .ks3-fgrid-controls {
  display: flex; flex-wrap: wrap; gap: 20px 30px; align-items: flex-start;
}
.ks3-xfer-picker, .ks3-chpair-picker, .ks3-fgrid-picker {
  display: grid; gap: 9px; flex: 1 1 320px;
}
.ks3-xfer-row, .ks3-chpair-row, .ks3-fgrid-row {
  display: grid; gap: 8px; flex: 1 1 280px; min-width: 240px;
}
.ks3-xfer-pickerlabel, .ks3-chpair-pickerlabel, .ks3-fgrid-pickerlabel {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 14px;
  font-weight: 500; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-xfer-tabrow, .ks3-chpair-tabrow, .ks3-fgrid-tabrow {
  display: flex; flex-wrap: wrap; gap: 9px;
}
.ks3-xfer-rowhead, .ks3-chpair-rowhead, .ks3-fgrid-rowhead {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 16px; flex-wrap: wrap;
  font-family: var(--ks3-font-mono); font-size: 14px; font-weight: 500;
  letter-spacing: .07em; text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-xfer-reading, .ks3-chpair-reading, .ks3-fgrid-reading {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 17px;
  font-weight: 500; letter-spacing: 0; text-transform: none;
  color: var(--ks3-blue-light); white-space: nowrap;
}
/* 44px of vertical target, which is the smallest a thumb should be asked
   to find — the slider is one of only two or three controls on each bench. */
.ks3-xfer-slider, .ks3-chpair-slider, .ks3-fgrid-slider {
  width: 100%; min-height: 44px; accent-color: var(--ks3-accent);
}

.ks3-xfer-figwrap, .ks3-chpair-figwrap, .ks3-fgrid-figwrap {
  position: relative; margin-top: 18px; padding: 18px;
  border-radius: var(--ks3-r-card); background: var(--ks3-dark-panel);
}
.ks3-xfer-svg, .ks3-chpair-svg, .ks3-fgrid-svg {
  display: block; width: 100%; height: auto;
}
.ks3-xfer-fill, .ks3-chpair-fill, .ks3-fgrid-fill {
  position: absolute; font-family: var(--ks3-font-mono); font-weight: 500;
  white-space: nowrap; pointer-events: none;
}
/* Every fixed caption inside the three drawings. `--ks3-on-dark-muted`
   rather than Design's `#8C8177`: see the block header. */
.ks3-xfer-caption, .ks3-chpair-caption, .ks3-fgrid-caption {
  font-family: var(--ks3-font-mono); font-weight: 500; font-size: 15px;
  letter-spacing: .07em; fill: var(--ks3-on-dark-muted);
}

.ks3-xfer-tiles, .ks3-chpair-tiles, .ks3-fgrid-tiles {
  margin-top: 16px; display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr));
  gap: 12px;
}
.ks3-xfer-tile, .ks3-chpair-tile, .ks3-fgrid-tile {
  padding: 15px 17px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-dark-panel);
}
.ks3-xfer-tile-label, .ks3-chpair-tile-label, .ks3-fgrid-tile-label {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-xfer-tile-value, .ks3-chpair-tile-value, .ks3-fgrid-tile-value {
  margin: 5px 0 0; font-family: var(--ks3-font-mono); font-size: 22px;
  font-weight: 500; color: var(--ks3-on-dark);
}
/* Design sets the four VERDICT tiles on `p9-02` and `p9-03` in the body
   face at 700 rather than in mono, because they carry a WORD and not a
   reading. Kept: the two shapes are how a student tells a verdict from a
   measurement without reading either. */
.ks3-chpair-tile-value, .ks3-fgrid-tile-value {
  font-family: var(--ks3-font-body); font-size: 20px; font-weight: 700;
}
.ks3-chpair-tile-value[data-chpair-out="sep"] {
  font-family: var(--ks3-font-mono); font-size: 22px; font-weight: 500;
}
.ks3-xfer-tile-sub, .ks3-chpair-tile-sub, .ks3-fgrid-tile-sub {
  margin: 4px 0 0; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; color: var(--ks3-on-dark-muted);
}

.ks3-xfer-note, .ks3-chpair-note, .ks3-fgrid-note {
  margin: 16px 0 0; padding: 16px 18px;
  border-radius: var(--ks3-r-panel); background: var(--ks3-dark-panel);
  font-size: 18px; line-height: 1.55; color: var(--ks3-on-dark-body);
}

/* ── p9-01 · the transfer pair ───────────────────────────────────────── */

/* Design's block fill is a shade darker than the panel behind it, so the
   two objects read as objects rather than as regions of the panel. */
.ks3-xfer-block .ks3-xfer-block,
.ks3-xfer rect.ks3-xfer-block {
  fill: #2A241E; stroke: var(--ks3-on-dark-muted); stroke-width: 5;
}
.ks3-xfer-signs {
  fill: none; stroke: var(--ks3-blue-light); stroke-width: 6;
  stroke-linecap: round;
}
.ks3-xfer-arrow {
  fill: none; stroke: var(--ks3-blue-light); stroke-linecap: round;
  stroke-linejoin: round;
}
.ks3-xfer-dots { fill: var(--ks3-blue-light); stroke: none; }
.ks3-xfer-aname, .ks3-xfer-bname {
  color: var(--ks3-on-dark-body); font-size: clamp(9px, 1.3vw, 16px);
  letter-spacing: .06em; text-transform: uppercase;
}
.ks3-xfer-acharge, .ks3-xfer-bcharge {
  color: var(--ks3-blue-light); font-size: clamp(10px, 1.6vw, 19px);
}
.ks3-xfer-transfer {
  color: var(--ks3-on-dark-muted); font-size: clamp(9px, 1.3vw, 16px);
}

/* ── p9-02 · the charged pair ────────────────────────────────────────── */

.ks3-chpair-bench {
  fill: none; stroke: var(--ks3-on-dark-muted); stroke-width: 5;
}
.ks3-chpair-stand {
  fill: none; stroke: var(--ks3-on-dark-muted); stroke-width: 6;
  stroke-linecap: round;
}
/* A charged sphere is filled, a neutral one is hollow — and the sphere's
   own label span names which, so the fill is never the only channel. */
.ks3-chpair-sphere {
  fill: var(--ks3-blue-light); stroke: var(--ks3-on-dark-muted);
  stroke-width: 6;
}
.ks3-chpair-sphere[data-neutral="1"] { fill: #2A241E; }
.ks3-chpair-sign {
  fill: none; stroke: var(--ks3-ink); stroke-width: 7; stroke-linecap: round;
}
.ks3-chpair-ind {
  fill: none; stroke: var(--ks3-on-dark-body); stroke-width: 5;
  stroke-linecap: round;
}
.ks3-chpair-arrow {
  fill: none; stroke: var(--ks3-blue-light); stroke-width: 8;
  stroke-linecap: round; stroke-linejoin: round;
}
.ks3-chpair-dim {
  fill: none; stroke: var(--ks3-on-dark-muted); stroke-width: 3;
}
.ks3-chpair-alabel, .ks3-chpair-blabel {
  color: var(--ks3-on-dark-body); font-size: clamp(9px, 1.2vw, 15px);
  letter-spacing: .07em;
}
.ks3-chpair-sep {
  color: var(--ks3-on-dark-body); font-size: clamp(10px, 1.6vw, 19px);
}
.ks3-chpair-verdict {
  color: var(--ks3-blue-light); font-size: clamp(10px, 1.6vw, 19px);
}

/* ── p9-03 · the field grid ──────────────────────────────────────────── */

/* ⚠️ THE GRID IS THE INSTRUMENT'S PICTURE, SO IT CLEARS THE 3:1 BAR FOR A
   MEANINGFUL GRAPHIC. Design draws it at `#6E655D`, which is 2.05:1 on the
   panel; this is `#8C8177` — a colour already on her own drawing, and
   3.08:1. Registered in `DEPARTURES-P9.md`. It stays a step below the test
   point and the charges, which is the hierarchy she drew. */
.ks3-fgrid-grid {
  fill: none; stroke: #8C8177; stroke-width: 3; stroke-linecap: round;
  stroke-linejoin: round;
}
.ks3-fgrid-axis {
  fill: none; stroke: #4A423B; stroke-width: 2; stroke-dasharray: 8 10;
}
.ks3-fgrid-charges {
  fill: var(--ks3-blue-light); stroke: var(--ks3-on-dark-body);
  stroke-width: 5;
}
.ks3-fgrid-signs {
  fill: none; stroke: var(--ks3-ink); stroke-width: 7; stroke-linecap: round;
}
/* The one amber in the unit. See the block header for why it is not
   `--ks3-data`. */
.ks3-fgrid-test {
  fill: none; stroke: #FFC53D; stroke-width: 8; stroke-linecap: round;
  stroke-linejoin: round;
}
.ks3-fgrid-point {
  fill: #FFC53D; stroke: var(--ks3-ink); stroke-width: 4;
}
.ks3-fgrid-reading {
  color: #FFC53D; font-size: clamp(10px, 1.6vw, 19px);
}
.ks3-fgrid-caption { font-size: 14px; }

/* ── the fixed figures · the ladder, the matrix, the three fields ─────── */

/* Design's band sections sit on `--ks3-band` rather than the check shell's
   card, and the ground is a measured property of her block. */
.ks3-chband-block { background: var(--ks3-band); }
/* The eyebrow and the `<h2>` are the SHELL's — this unit draws neither. */
.ks3-chband-block > .ks3-eyebrow { color: var(--ks3-accent-text); }
.ks3-chband-block > h2 { margin: 6px 0 0; }
.ks3-chband-lead {
  margin: 12px 0 0; max-width: 62ch; font-size: 19px; line-height: 1.6;
  color: var(--ks3-ink-body);
}
.ks3-chband-close {
  margin: 16px 0 0; padding: 16px 18px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-card); border: 2px solid var(--ks3-option-border);
  font-size: 19px; line-height: 1.6; color: var(--ks3-ink-body);
}

/* p9-01's triboelectric ladder. The arrow between the two axis labels is
   the figure's argument, so it is drawn and not written. */
.ks3-chband-ladder {
  margin-top: 20px; display: flex; gap: 24px; flex-wrap: wrap;
  align-items: stretch;
}
.ks3-chband-axis {
  flex: 0 0 auto; display: flex; flex-direction: column; align-items: center;
  justify-content: space-between; padding: 10px 0;
}
.ks3-chband-axislabel {
  font-family: var(--ks3-font-mono); font-size: 13px; font-weight: 500;
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--ks3-ink-muted); writing-mode: vertical-rl;
  text-orientation: mixed;
}
.ks3-chband-arrowsvg { flex: 0 0 auto; }
.ks3-chband-arrow {
  fill: none; stroke: var(--ks3-accent); stroke-width: 4;
  stroke-linecap: round; stroke-linejoin: round;
}
.ks3-chband-rungs {
  flex: 1 1 320px; list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: 8px;
}
/* ⚠️ `flex-wrap` IS LOAD-BEARING AND IT IS NOT COSMETIC. Design's row is a
   single non-wrapping flex — badge, name, tell — and her tell is
   `flex: 0 0 auto`. Measured at 360 and 390: the tell keeps its intrinsic
   width, the row runs to 464px, and the DOCUMENT scrolls sideways on a
   phone. The tell drops to its own line instead. */
.ks3-chband-rung {
  display: flex; align-items: center; gap: 6px 14px; flex-wrap: wrap;
  padding: 12px 18px;
  border-radius: var(--ks3-r-panel); background: var(--ks3-card);
  border: 2px solid var(--ks3-ink);
}
/* The one material Design marks as middling takes the inset ground and the
   softer border — a poor choice for a demonstration, said by the row's own
   treatment as well as by its note. */
.ks3-chband-rung.is-middle {
  background: var(--ks3-inset); border-color: var(--ks3-option-border);
}
.ks3-chband-num {
  flex: 0 0 44px; display: grid; place-items: center;
  width: 44px; height: 44px; border-radius: 12px;
  background: var(--ks3-card); border: 2px solid var(--ks3-ink);
  font-family: var(--ks3-font-display); font-weight: 800; font-size: 24px;
  color: var(--ks3-ink);
}
/* Design's three badge tones. The split the list is about, before a word is
   read — and every row still carries its number and its name. */
.ks3-chband-rung.is-loses  .ks3-chband-num { background: var(--ks3-accent-tint); }
.ks3-chband-rung.is-middle .ks3-chband-num { background: var(--ks3-band); }
.ks3-chband-rung.is-gains  .ks3-chband-num { background: var(--ks3-blue-tint); }
.ks3-chband-name {
  flex: 1 1 auto; min-width: 0; font-size: 19px; font-weight: 700;
}
.ks3-chband-tell {
  flex: 0 1 auto; min-width: 0; font-family: var(--ks3-font-mono);
  font-size: 15px; color: var(--ks3-ink-muted);
}

/* p9-02's nine-case table. It scrolls inside its own box on a phone — the
   page body never scrolls sideways. */
.ks3-chband-tablewrap {
  margin-top: 18px; overflow-x: auto; border-radius: var(--ks3-r-card);
  background: var(--ks3-card); border: 2px solid var(--ks3-ink); padding: 4px;
}
.ks3-chband-table {
  border-collapse: collapse; width: 100%; min-width: 620px; font-size: 18px;
}
.ks3-chband-table th[scope="col"] {
  text-align: left; padding: 14px 16px;
  border-bottom: 3px solid var(--ks3-ink);
  font-family: var(--ks3-font-mono); font-size: 14px; font-weight: 500;
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-chband-table th[scope="row"] {
  text-align: left; padding: 14px 16px; font-weight: 700;
  color: var(--ks3-ink);
}
.ks3-chband-table td { padding: 14px 16px; color: var(--ks3-ink-body); }
.ks3-chband-table tbody tr:not(:last-child) th,
.ks3-chband-table tbody tr:not(:last-child) td {
  border-bottom: 2px solid var(--ks3-rule);
}

/* p9-03's three fields. One layout for all three cards, because the claim
   is that the electric field is not a special case. */
.ks3-chband-cards {
  margin-top: 20px; display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
  gap: 14px;
}
.ks3-chband-card {
  padding: 20px 22px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-card); border: 2px solid var(--ks3-ink);
}
.ks3-chband-cardtitle {
  margin: 0 0 14px; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; letter-spacing: .09em; text-transform: uppercase;
  color: var(--ks3-accent-text);
}
.ks3-chband-cardsvg { display: block; width: 100%; height: auto; }
.ks3-chband-cardbody {
  margin: 14px 0 0; font-size: 18px; line-height: 1.5;
  color: var(--ks3-ink-body);
}
.ks3-chband-body {
  fill: var(--ks3-blue-tint); stroke: var(--ks3-ink); stroke-width: 4;
}
.ks3-chband-charge {
  fill: none; stroke: var(--ks3-ink); stroke-width: 5; stroke-linecap: round;
}
/* ⚠️ THE GAP IS DASHED AND IT IS NEVER FILLED. `CHRG-10` is *the air in the
   gap must be carrying the force*, and a card that drew something in the
   gap would be committing it. */
.ks3-chband-gap {
  fill: none; stroke: var(--ks3-rule-strong); stroke-width: 3;
  stroke-dasharray: 8 9;
}
.ks3-chband-cardarrow {
  fill: none; stroke: var(--ks3-accent); stroke-width: 4;
  stroke-linecap: round; stroke-linejoin: round;
}

/* ── p9-01 · `#s-think` ──────────────────────────────────────────────────

   ⚠️ `.ks3-chthink` DELIBERATELY CARRIES NO RULE, and this comment is here so
   the next reader does not add one. It is a SHELL, not a treatment: the
   class exists so that the section carrying it declares
   `data-stage-done="0"` in the shipped bytes, because `p9-01` is the only
   lesson in the key stage whose rail includes the confrontation. The block
   is styled by `.ks3-misconception`, exactly as the other fifty-six are,
   and a rule here would make one page's wrong-idea block look different
   from every other one for a reason that is about the rail. */
/* ═══ END P9 ═══ */

/* ═══ BEGIN P10 ══════════════════════════════════════════════════════════
   P10 — *Magnetism and electromagnetism*. One marked block per unit.

   ⚠️ EVERY SELECTOR HERE IS P10-ONLY, and all six shell stems were checked
   against the whole registry, every line of `shared/ks3.js` and every rule in
   this file before they were written. All six were free — `ks3-tpair-`,
   `ks3-cplot-`, `ks3-dipc-`, `ks3-solen-`, `ks3-mcoil-` and `ks3-magband-`.

   ⚠️ EVERY FIGURE WRAPPER IS `position: relative`. Every live value on all
   five benches is an absolutely positioned HTML span over the SVG, because a
   hole inside an SVG `<text>` renders nothing and fails silently — and
   without a positioned ancestor those spans resolve against the page and
   widen the document on a phone.

   ⚠️ TOKENS, NOT LITERALS, FOR EVERY PIECE OF TEXT. Design draws her fixed
   SVG captions at `#8C8177`, which is 3.08:1 on `--ks3-dark-panel` at 15px —
   below AA for body-size text, and the exact case `shared/tokens.css` warns
   about beside `--ks3-on-dark-muted`. Every one of them takes
   `--ks3-on-dark-muted` here (6.09:1), which is P9's ruling applied again.
   Her `#C6B9A7` IS `--ks3-on-dark-muted` to the byte, so those move nowhere.

   ⚠️ AND THE MEANINGFUL GRAPHICS CLEAR 3:1. Design draws the field lattices,
   the Earth's field arcs and the solenoid's field loops at `#6E6357`, which
   is 2.05:1 on the panel. Every one of them is `#8C8177` here — a colour
   already on her own drawings, and 3.08:1, which is the bar for a graphic
   that carries meaning. Each stays a step below the instrument mark above it,
   which is the hierarchy she drew.

   ⚖️ `--ks3-data` IS USED HERE, AND THIS IS THE UNIT THAT OPENS IT.
   `docs/ks3/design-reference/p10/design-reference-font-and-token-law.md` §R8
   grants it from P10 onward and asks for a fallback because the token is
   absent from the design-system copy bound to her project. The ENGINE's
   `shared/tokens.css` carries it (MRB-252), so no fallback is written: the
   value is `#8FB7FF` — Design's own blue, byte for byte — and what is new is
   that the ROLE has a name. It marks CATEGORY AND SELECTION: a magnet's
   north face, a chosen button, the current in a coil. It is never a mark of
   correctness and never amber's job.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── shared P10 bench shapes — the five dark instruments ─────────────── */

.ks3-tpair-gate, .ks3-cplot-gate, .ks3-dipc-gate, .ks3-solen-gate,
.ks3-mcoil-gate {
  padding: 20px 22px; border-radius: var(--ks3-r-panel);
  background: rgba(255, 255, 255, .06);
  border: 2px solid var(--ks3-on-dark-muted);
}

/* ⚠️ NO `.ks3-*-head` RULES, AND NO `-progress` RULE. This unit draws no head
   row of its own: `r_activity`'s shell owns it, `.ks3-blockhead` and
   `.ks3-blockhead-count` already style it, and it is already Design's layout
   — eyebrow and heading left, readout right. See the long note where `_head`
   is NOT, in `ks3_art/p10.py`, and the live P4/P5/P6 pages that render both. */
.ks3-tpair-lead, .ks3-cplot-lead, .ks3-dipc-lead, .ks3-solen-lead,
.ks3-mcoil-lead {
  margin: 0 0 18px; font-size: 19px; line-height: 1.65;
  color: var(--ks3-on-dark-body); max-width: 58ch;
}

.ks3-tpair-controls, .ks3-cplot-controls, .ks3-dipc-controls,
.ks3-solen-controls, .ks3-mcoil-controls {
  display: flex; flex-wrap: wrap; gap: 20px 30px; align-items: flex-start;
}
.ks3-tpair-picker, .ks3-cplot-picker, .ks3-dipc-picker, .ks3-solen-picker,
.ks3-mcoil-picker {
  display: grid; gap: 9px; flex: 1 1 300px;
}
.ks3-tpair-row, .ks3-cplot-row, .ks3-dipc-row, .ks3-solen-row,
.ks3-mcoil-row {
  display: grid; gap: 8px; flex: 1 1 240px; min-width: 220px;
}
.ks3-tpair-pickerlabel, .ks3-cplot-pickerlabel, .ks3-dipc-pickerlabel,
.ks3-solen-pickerlabel, .ks3-mcoil-pickerlabel {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 14px;
  font-weight: 500; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-tpair-tabrow, .ks3-cplot-tabrow, .ks3-dipc-tabrow, .ks3-solen-tabrow,
.ks3-mcoil-tabrow {
  display: flex; flex-wrap: wrap; gap: 9px;
}
.ks3-tpair-rowhead, .ks3-cplot-rowhead, .ks3-dipc-rowhead,
.ks3-solen-rowhead, .ks3-mcoil-rowhead {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 16px; flex-wrap: wrap;
  font-family: var(--ks3-font-mono); font-size: 14px; font-weight: 500;
  letter-spacing: .07em; text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-tpair-reading, .ks3-cplot-reading, .ks3-dipc-reading,
.ks3-solen-reading, .ks3-mcoil-reading {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 17px;
  font-weight: 500; letter-spacing: 0; text-transform: none;
  color: var(--ks3-data); white-space: normal;
}
/* 44px of vertical target, which is the smallest a thumb should be asked to
   find — the slider is one of only two or three controls on each bench. */
.ks3-tpair-slider, .ks3-cplot-slider, .ks3-dipc-slider, .ks3-solen-slider,
.ks3-mcoil-slider {
  width: 100%; min-height: 44px; accent-color: var(--ks3-accent);
}

.ks3-tpair-figwrap, .ks3-cplot-figwrap, .ks3-dipc-figwrap,
.ks3-solen-figwrap, .ks3-mcoil-figwrap {
  margin-top: 18px; padding: 18px;
  border-radius: var(--ks3-r-card); background: var(--ks3-dark-panel);
}
/* ⚠️ THE POSITIONED ANCESTOR IS THIS ONE, NOT THE PADDED PANEL, and the
   distinction is the whole correctness of every live label on these benches.
   A label's `left` is a percentage read straight off the SVG's viewBox, so
   the box it resolves against has to be the SVG's box. Put it on the panel
   and the padding is inside the percentage: `36f − 18` px of error, nothing
   at the centre and eighteen pixels at either edge — which is how `p10-05`'s
   `N` ended up half off the pole face it names. */
.ks3-tpair-fig, .ks3-cplot-fig, .ks3-dipc-fig, .ks3-solen-fig,
.ks3-mcoil-fig { position: relative; }
.ks3-tpair-svg, .ks3-cplot-svg, .ks3-dipc-svg, .ks3-solen-svg,
.ks3-mcoil-svg {
  display: block; width: 100%; height: auto;
}
.ks3-tpair-fill, .ks3-cplot-fill, .ks3-dipc-fill, .ks3-solen-fill,
.ks3-mcoil-fill {
  position: absolute; font-family: var(--ks3-font-mono); font-weight: 500;
  white-space: nowrap; pointer-events: none;
}
/* Every fixed caption inside the five drawings. `--ks3-on-dark-muted` rather
   than Design's `#8C8177`: see the block header. */
.ks3-tpair-caption, .ks3-dipc-caption, .ks3-solen-caption {
  font-family: var(--ks3-font-mono); font-weight: 500; font-size: 15px;
  letter-spacing: .07em; fill: var(--ks3-on-dark-muted);
}

.ks3-tpair-tiles, .ks3-cplot-tiles, .ks3-dipc-tiles, .ks3-solen-tiles,
.ks3-mcoil-tiles {
  margin-top: 16px; display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr));
  gap: 12px;
}
.ks3-tpair-tile, .ks3-cplot-tile, .ks3-dipc-tile, .ks3-solen-tile,
.ks3-mcoil-tile {
  padding: 15px 17px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-dark-panel);
}
.ks3-tpair-tile-label, .ks3-cplot-tile-label, .ks3-dipc-tile-label,
.ks3-solen-tile-label, .ks3-mcoil-tile-label {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
/* Design sets a tile that carries a WORD in the body face at 700, and a tile
   that carries a READING in mono. Kept: the two shapes are how a student
   tells a verdict from a measurement without reading either. */
.ks3-tpair-tile-value, .ks3-cplot-tile-value, .ks3-dipc-tile-value,
.ks3-solen-tile-value, .ks3-mcoil-tile-value {
  margin: 5px 0 0; font-family: var(--ks3-font-body); font-size: 20px;
  font-weight: 700; color: var(--ks3-on-dark);
}
.ks3-tpair-tile-value[data-tpair-out="gap"],
.ks3-cplot-tile-value[data-cplot-out="bearing"],
.ks3-dipc-tile-value[data-dipc-out="dip"],
.ks3-dipc-tile-value[data-dipc-out="horiz"],
.ks3-solen-tile-value[data-solen-out="clips"],
.ks3-mcoil-tile-value[data-mcoil-out="torque"] {
  font-family: var(--ks3-font-mono); font-size: 22px; font-weight: 500;
}
.ks3-tpair-tile-sub, .ks3-cplot-tile-sub, .ks3-dipc-tile-sub,
.ks3-solen-tile-sub, .ks3-mcoil-tile-sub {
  margin: 4px 0 0; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; color: var(--ks3-on-dark-muted);
}

.ks3-tpair-note, .ks3-cplot-note, .ks3-dipc-note, .ks3-solen-note,
.ks3-mcoil-note {
  margin: 16px 0 0; padding: 16px 18px;
  border-radius: var(--ks3-r-panel); background: var(--ks3-dark-panel);
  font-size: 18px; line-height: 1.55; color: var(--ks3-on-dark-body);
}

/* ── p10-01 · two objects on a track ─────────────────────────────────── */

/* The track the two objects slide on. Design draws it at `#4A4139`, 1.6:1 on
   the panel — the one line on the drawing a student is told is frictionless,
   and effectively invisible. Lifted to the caption tone, which is what the
   caption under it already uses. */
.ks3-tpair-track {
  fill: none; stroke: var(--ks3-on-dark-muted); stroke-width: 6;
  stroke-linecap: round;
}
/* A block is a shade darker than the panel behind it, so it reads as an
   object rather than as a region of the panel — and a NON-MAGNETIC one is
   dashed, so identity is never carried by hue alone. */
.ks3-tpair-body {
  fill: #2A241E; stroke: var(--ks3-on-dark-muted); stroke-width: 5;
  stroke-linejoin: round;
}
.ks3-tpair-body[data-kind="non"] { stroke-dasharray: 14 11; }
.ks3-tpair-pole { fill: var(--ks3-data); stroke: none; }
.ks3-tpair-arrow {
  fill: none; stroke: var(--ks3-data); stroke-width: 8;
  stroke-linecap: round; stroke-linejoin: round;
}
.ks3-tpair-dim {
  fill: none; stroke: var(--ks3-on-dark-muted); stroke-width: 3;
}
.ks3-tpair-aname, .ks3-tpair-bname {
  color: var(--ks3-on-dark-muted); font-size: clamp(9px, 1.3vw, 15px);
  letter-spacing: .06em;
}
/* The NEAR letter sits on the filled pole face, so it takes ink (8.2:1 on
   `--ks3-data`); the FAR letter sits on the dark body and takes the light
   caption tone. */
.ks3-tpair-anear, .ks3-tpair-bnear {
  color: var(--ks3-ink); font-weight: 700;
  font-size: clamp(11px, 1.9vw, 24px);
}
.ks3-tpair-afar, .ks3-tpair-bfar {
  color: var(--ks3-on-dark-muted); font-weight: 700;
  font-size: clamp(11px, 1.9vw, 24px);
}
.ks3-tpair-gap {
  color: var(--ks3-on-dark-muted); font-size: clamp(9px, 1.3vw, 15px);
}

/* ── p10-02 · the plotting compass ───────────────────────────────────── */

/* The lattice of ninety-one arrows. `#8C8177` rather than Design's `#6E6357`
   — see the block header. It stays a step below the needle and the magnets,
   which is the hierarchy she drew. */
.ks3-cplot-grid {
  fill: none; stroke: #8C8177; stroke-width: 3; stroke-linecap: round;
  stroke-linejoin: round;
}
.ks3-cplot-mag {
  fill: #2A241E; stroke: var(--ks3-on-dark-muted); stroke-width: 5;
  stroke-linejoin: round;
}
.ks3-cplot-north { fill: var(--ks3-data); stroke: none; }
.ks3-cplot-dial {
  fill: #15110C; stroke: var(--ks3-on-dark); stroke-width: 4;
}
/* The needle is the one moving instrument mark on this bench and it takes the
   accent, exactly as Design draws it. It is a stroke on a drawing, never
   text, so the accent's large-text-only rule does not reach it. */
.ks3-cplot-needle {
  fill: none; stroke: var(--ks3-accent); stroke-width: 7;
  stroke-linecap: round; stroke-linejoin: round;
}
/* ⚠️ THE POLE LETTER'S TONE FOLLOWS WHICH HALF IT IS STANDING ON, and the
   wiring sets the attribute. Design fixes the four letters at two colours
   that happen to be right for her four layouts and would be wrong the moment
   a bar was turned round. */
.ks3-cplot-pole {
  font-family: var(--ks3-font-display); font-weight: 800;
  font-size: clamp(11px, 1.9vw, 24px);
}
.ks3-cplot-pole[data-on-north="1"] { color: var(--ks3-ink); }
.ks3-cplot-pole[data-on-north="0"] { color: var(--ks3-on-dark-muted); }

/* The five-by-five grid of positions. A 44px target each, which is what a
   thumb needs.

   ⊕ MRB-297 · 1 Sep 2026 — THE SELECTION IS THE ACCENT. This used to end
   "and the selection takes `--ks3-data` because selection is exactly what
   that token was minted for". That sentence was true when it was written and
   is not now: Mide's ruling of 30 Aug 2026 splits the token's two jobs and
   moves SELECTION to `--ks3-accent`, leaving `--ks3-data` for CATEGORY only
   (see the amendment on `--ks3-data` in `shared/tokens.css`). Picking where
   to stand the compass is "you picked this", not "this column rather than
   that one", so it moves. The kept sentence is the reason the run that made
   the ruling walked past its own physics unit without applying it.
   The spot carries no text — only an `aria-label` — so the fill has the 3:1
   non-text bar to clear and no text bar at all. */
.ks3-cplot-grid-btns {
  display: grid; grid-template-columns: repeat(5, 44px); gap: 6px;
}
.ks3-cplot-spot {
  width: 44px; height: 44px; min-height: 44px; border-radius: 10px;
  cursor: pointer; background: transparent;
  border: 2px solid var(--ks3-on-dark-muted);
}
.ks3-cplot-spot[aria-pressed="true"] {
  background: var(--ks3-accent); border-color: var(--ks3-accent);
}

/* ── p10-03 · the dip circle ─────────────────────────────────────────── */

.ks3-dipc-levelline {
  fill: none; stroke: #8C8177; stroke-width: 4; stroke-dasharray: 12 10;
}
.ks3-dipc-pivot { fill: var(--ks3-on-dark); }
/* The needle goes to the accent when it has been CAPTURED by something on the
   bench — a state the tiles also name in words — and to the data blue when it
   is reading the Earth. Two states, two channels. */
.ks3-dipc-needle {
  fill: none; stroke: var(--ks3-data); stroke-width: 8;
  stroke-linecap: round; stroke-linejoin: round;
}
.ks3-dipc-needle[data-captured="1"] { stroke: var(--ks3-accent); }
.ks3-dipc-arc { fill: none; stroke: var(--ks3-data); stroke-width: 4; }
.ks3-dipc-globe {
  fill: #221E1B; stroke: var(--ks3-on-dark-muted); stroke-width: 5;
}
.ks3-dipc-lines { fill: none; stroke: #8C8177; stroke-width: 3; }
.ks3-dipc-bar {
  fill: #2A241E; stroke: var(--ks3-on-dark-muted); stroke-width: 4;
}
.ks3-dipc-barnorth { fill: var(--ks3-data); stroke: none; }
.ks3-dipc-you {
  fill: var(--ks3-accent); stroke: var(--ks3-ink); stroke-width: 3;
}
.ks3-dipc-level {
  color: var(--ks3-on-dark-muted); font-size: clamp(9px, 1.2vw, 14px);
  letter-spacing: .07em;
}
.ks3-dipc-dip {
  color: var(--ks3-data); font-size: clamp(9px, 1.3vw, 16px);
  letter-spacing: .06em;
}

/* ── p10-04 · the solenoid bench ─────────────────────────────────────── */

.ks3-solen-wire {
  fill: none; stroke: var(--ks3-on-dark-muted); stroke-width: 6;
  stroke-linecap: round;
}
.ks3-solen-cell {
  fill: none; stroke: var(--ks3-on-dark); stroke-width: 6;
  stroke-linecap: round;
}
/* A live coil takes the data blue; a dead one drops to the graphic grey, and
   the tiles say `no field at all` in words beside it. */
.ks3-solen-coil {
  fill: none; stroke: #8C8177; stroke-linecap: round;
}
.ks3-solen-coil[data-on="1"] { stroke: var(--ks3-data); }
/* Iron is solid; a plastic former is dashed and hollow, which is the same
   channel `p10-01` uses for a non-magnetic object — and the tile says
   `nothing — plastic is not magnetic` in words as well. */
.ks3-solen-corepath {
  fill: #4A4139; stroke: var(--ks3-on-dark-muted); stroke-width: 4;
}
.ks3-solen-corepath[data-core="plastic"] {
  fill: #2A241E; stroke: #8C8177; stroke-dasharray: 12 9;
}
.ks3-solen-field { fill: none; stroke: #8C8177; }
.ks3-solen-clips {
  fill: none; stroke: var(--ks3-on-dark-muted); stroke-width: 5;
  stroke-linecap: round;
}
.ks3-solen-west, .ks3-solen-east {
  color: var(--ks3-data); font-family: var(--ks3-font-display);
  font-weight: 800; font-size: clamp(11px, 1.9vw, 24px);
}

/* ── p10-05 · the motor coil ─────────────────────────────────────────── */

.ks3-mcoil-magnet {
  fill: #2A241E; stroke: var(--ks3-on-dark-muted); stroke-width: 5;
  stroke-linejoin: round;
}
.ks3-mcoil-pole { fill: var(--ks3-data); stroke: none; }
.ks3-mcoil-field { fill: none; stroke: #8C8177; stroke-width: 3; }
.ks3-mcoil-loop {
  fill: none; stroke: var(--ks3-on-dark-muted); stroke-width: 8;
  stroke-linejoin: round;
}
.ks3-mcoil-lead {
  fill: none; stroke: #8C8177; stroke-width: 3; stroke-dasharray: 10 8;
}
.ks3-mcoil-current {
  fill: none; stroke: var(--ks3-data); stroke-linecap: round;
}
/* The two force arrows take the accent, as Design draws them: they are the
   one thing on this bench that is neither a part nor a category but the
   RESULT, and the tiles name their directions in words. */
.ks3-mcoil-force {
  fill: none; stroke: var(--ks3-accent); stroke-width: 8;
  stroke-linecap: round; stroke-linejoin: round;
}
.ks3-mcoil-axlepin {
  fill: #15110C; stroke: var(--ks3-on-dark-muted); stroke-width: 4;
}
/* A split ring is solid; two plain rings are dashed — and the caption under
   the drawing says which, in words, in both states. */
.ks3-mcoil-ring {
  fill: none; stroke: var(--ks3-on-dark); stroke-width: 5;
}
.ks3-mcoil-ring[data-ring="plain"] {
  stroke: #8C8177; stroke-dasharray: 9 7;
}
.ks3-mcoil-spin {
  fill: none; stroke: var(--ks3-on-dark); stroke-width: 5;
  stroke-linecap: round; stroke-linejoin: round;
}
.ks3-mcoil-axle {
  color: var(--ks3-on-dark-muted); font-size: clamp(10px, 1.4vw, 17px);
  letter-spacing: .07em; white-space: normal; text-align: center;
}
/* ⚠️ SAME TONE RULE AS `p10-02`, AND HERE IT IS A CORRECTION. Design draws
   BOTH pole letters at `#15110C`; only one of the two ever sits on the filled
   face, and the other is near-black on the near-black magnet body at 1.1:1 —
   invisible in half the states, and it is the letter that names the pole. */
.ks3-mcoil-leftpole, .ks3-mcoil-rightpole {
  font-family: var(--ks3-font-display); font-weight: 800;
  font-size: clamp(11px, 1.9vw, 24px);
}
.ks3-mcoil-leftpole[data-on-north="1"],
.ks3-mcoil-rightpole[data-on-north="1"] { color: var(--ks3-ink); }
.ks3-mcoil-leftpole[data-on-north="0"],
.ks3-mcoil-rightpole[data-on-north="0"] { color: var(--ks3-on-dark-muted); }

/* ── the band figure beside each bench ───────────────────────────────── */

/* Design's band sections sit on `--ks3-band` rather than the check shell's
   card, and the ground is a measured property of her block. */
.ks3-magband-block { background: var(--ks3-band); }
/* The eyebrow and the `<h2>` are the SHELL's — this unit draws neither. */
.ks3-magband-block > .ks3-eyebrow { color: var(--ks3-accent-text); }
.ks3-magband-block > h2 { margin: 6px 0 0; }
.ks3-magband-lead {
  margin: 12px 0 0; max-width: 62ch; font-size: 19px; line-height: 1.6;
  color: var(--ks3-ink-body);
}
.ks3-magband-tiles {
  margin-top: 18px; display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(250px, 100%), 1fr));
  gap: 14px;
}
.ks3-magband-tile {
  padding: 20px 22px; border-radius: var(--ks3-r-card);
  background: var(--ks3-card); border: 3px solid var(--ks3-ink);
}
/* ⚠️ EXACTLY ONE TILE PER FIGURE TAKES THE OFFSET, and the drawer refuses a
   payload that marks two: it is the tile that is the ANSWER rather than one
   of the alternatives, and two marked tiles is two claims to be the point. */
.ks3-magband-tile.is-accent { box-shadow: 5px 5px 0 var(--ks3-accent); }
.ks3-magband-eyebrow {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; letter-spacing: .09em; text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-magband-tile.is-accent > .ks3-magband-eyebrow {
  color: var(--ks3-accent-text);
}
.ks3-magband-art { display: block; width: 100%; height: auto; }
.ks3-magband-title {
  margin: 12px 0 0; font-family: var(--ks3-font-display); font-weight: 800;
  font-size: 24px; line-height: 1.2; letter-spacing: -.02em;
  color: var(--ks3-ink);
}
.ks3-magband-eyebrow + .ks3-magband-title { margin-top: 7px; font-size: 22px; }
.ks3-magband-body {
  margin: 8px 0 0; font-size: 18px; line-height: 1.55;
  color: var(--ks3-ink-body);
}
.ks3-magband-panels {
  margin-top: 16px; display: flex; flex-wrap: wrap; gap: 12px;
}
.ks3-magband-panel {
  flex: 1 1 260px; padding: 16px 18px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-card); border: 2px solid var(--ks3-option-border);
}
.ks3-magband-panellabel {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; letter-spacing: .09em; text-transform: uppercase;
  color: var(--ks3-ink-muted);
}
.ks3-magband-paneltext {
  margin: 6px 0 0; font-size: 19px; font-weight: 700; line-height: 1.4;
  color: var(--ks3-ink);
}
.ks3-magband-close {
  margin: 16px 0 0; padding: 16px 18px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-card); border: 2px solid var(--ks3-option-border);
  font-size: 19px; line-height: 1.6; color: var(--ks3-ink-body);
}

/* The seven drawings inside the band figures. Every one is a constant, so
   every caption is a literal `<text>` — MRB-254's rule is about a `<text>`
   that ships EMPTY and is filled later, and none of these is. */
.ks3-magband-bar {
  fill: var(--ks3-blue-tint); stroke: var(--ks3-ink); stroke-width: 3;
}
.ks3-magband-plain { fill: var(--ks3-inset); stroke: var(--ks3-ink); stroke-width: 3; }
.ks3-magband-dashed {
  fill: var(--ks3-inset); stroke: var(--ks3-ink); stroke-width: 3;
  stroke-dasharray: 7 6;
}
.ks3-magband-arrow {
  fill: none; stroke: var(--ks3-accent); stroke-width: 5;
  stroke-linecap: round;
}
/* The inconclusive outcome's arrows are DELIBERATELY not the accent: the
   figure's whole claim is that only one of the three settles anything. */
.ks3-magband-quiet {
  fill: none; stroke: var(--ks3-ink-muted); stroke-width: 5;
  stroke-linecap: round;
}
.ks3-magband-line { fill: none; stroke: var(--ks3-accent); stroke-width: 4; }
.ks3-magband-head { fill: var(--ks3-accent); }
.ks3-magband-ghost {
  fill: none; stroke: var(--ks3-rule-strong); stroke-width: 3;
  stroke-dasharray: 8 7;
}
.ks3-magband-ring { fill: none; stroke: var(--ks3-accent); stroke-width: 4; }
.ks3-magband-strike {
  fill: none; stroke: var(--ks3-accent); stroke-width: 4;
  stroke-linecap: round;
}
.ks3-magband-dial {
  fill: var(--ks3-inset); stroke: var(--ks3-ink); stroke-width: 3;
}
.ks3-magband-needle {
  fill: none; stroke: var(--ks3-accent); stroke-width: 4;
  stroke-linecap: round;
}
.ks3-magband-pole {
  font-family: var(--ks3-font-display); font-weight: 800; font-size: 17px;
  fill: var(--ks3-ink);
}
.ks3-magband-artcap {
  font-family: var(--ks3-font-mono); font-weight: 500; font-size: 14px;
  letter-spacing: .06em; fill: var(--ks3-ink-muted);
}

/* ⚠️ THE POSITION GRID DROPS TO A SCROLLER RATHER THAN WIDENING THE PAGE.
   Five 44px targets plus gaps is 244px, which fits a 360px viewport inside
   the block's padding — but the tab rows beside it do not, and a flex row
   that cannot wrap is what scrolled the whole document sideways on `p9-01`.
   Everything here wraps; nothing has an intrinsic minimum wider than the
   narrowest phone. */
@media (max-width: 460px) {
  .ks3-cplot-grid-btns { grid-template-columns: repeat(5, minmax(0, 1fr)); }
  .ks3-cplot-spot { width: 100%; }
}

/* The pressed segmented button keeps the key stage's shared dark rule
   (`--ks3-alert`). Unified at integration with P11 and P12: one control for
   the whole system by an explicit ruling; the corpus-wide question is raised
   as a finding, not answered in one unit. */

/* ═══ END P10 ═══ */

/* ═══ BEGIN P11 ═══════════════════════════════════════════════════════════
   P11 — *Matter and the particle model*. ONE block, because Design drew one
   component: `Bench.dc.html`, mounted on all four pages and on all six of
   P12's. Everything below is measured off her inline styles in that file.

   ⚠️ EVERY SELECTOR HERE IS P11-ONLY, and the shell stem was checked against
   the whole registry before it was written (MRB-279). `ks3-mtbench-` is free;
   so are `ks3-mtthink` and `ks3-p11cfa-`.

   ⚠️ NO `.ks3-mtbench-head` RULE, AND NO `-progress` RULE. This unit draws no
   head row of its own: `r_activity`'s shell owns it, `.ks3-blockhead` and
   `.ks3-blockhead-count` already style it, and it is already Design's layout
   — eyebrow and heading left, readout right. See the long note where `_head`
   is NOT, in `ks3_art/p9.py`, and the live P4/P5/P6 pages that render both.

   ⚠️ THE FOCUS BAR IS THE `--ks3-data` TOKEN **AND** A RING, AND ONE WITHOUT
   THE OTHER WOULD BE WRONG.

   Design fills the bar in focus with `var(--ks3-alert)`. MRB-252 reserves
   amber for warning and confrontation and sends category and selection to
   `--ks3-data` — but `--ks3-data` and `--ks3-blue-light` are the SAME VALUE
   today (`#8FB7FF`, minted that way deliberately so the two roles can
   separate later), and every other bar is `--ks3-blue-light`. The token
   substitution ALONE would leave six identical bars and make the panel's own
   aria-label ("…with gold highlighted") false.

   So the focus bar takes the correct TOKEN, so the role is named and the two
   colours can move apart, and a structural mark that works today: a 3px
   `--ks3-on-dark` ring on its track and its label at 800. That is 5A.4's
   ruled answer where no legal category hue is available — *"a figure that
   carries no category hue carries the distinction on something else, and
   says so"* — and the bar's own sub-line says so in words on every bench.
   Registered in `DEPARTURES-P11.md`.

   ⚠️ NO RULE FOR `.ks3-mtthink`. `#s-think` on p11-02/03/04 is styled by
   `.ks3-misconception`, exactly as the other sixty are, and a rule here would
   make three pages' wrong-idea block look different from every other one for
   a reason that is about the rail.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── the lead, above the gate ────────────────────────────────────────── */

.ks3-mtbench-lead {
  margin: 14px 0 0; font-size: 19px; line-height: 1.65;
  color: var(--ks3-on-dark-body); max-width: 58ch;
}

/* ── the commit gate ─────────────────────────────────────────────────
   Design's gate is a bare block with a top margin — no panel, no border.
   Measured: `<div style="margin-top: 20px;">` with `.ks3-commit` at
   `margin: 0 0 4px` and her shared `.ks3-options` under it. */

.ks3-mtbench-gate { margin-top: 20px; }
.ks3-mtbench-gate .ks3-commit { margin: 0 0 4px; }

/* ── the body, revealed once the gate is answered ────────────────────── */

.ks3-mtbench-body { margin-top: 22px; }

.ks3-mtbench-controls {
  display: flex; flex-wrap: wrap; gap: 20px 30px; align-items: flex-start;
}
.ks3-mtbench-picker { flex: 1 1 460px; }
.ks3-mtbench-pickerlabel,
.ks3-mtbench-rowhead label {
  margin: 0 0 9px; font-family: var(--ks3-font-mono); font-size: 14px;
  font-weight: 500; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-mtbench-tabrow { display: flex; flex-wrap: wrap; gap: 9px; }

.ks3-mtbench-row { flex: 1 1 280px; min-width: 240px; }
.ks3-mtbench-rowhead {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px;
}
.ks3-mtbench-reading {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 17px;
  font-weight: 500; color: var(--ks3-blue-light); white-space: nowrap;
}

/* Design's own range styling, ported. The 28px thumb is comfortably over the
   44px-of-target floor once its hit area is counted, and the track carries a
   border so it is visible against the panel it sits on. */
.ks3-mtbench-slider {
  -webkit-appearance: none; appearance: none;
  width: 100%; max-width: 22rem; min-height: 44px; background: transparent;
}
.ks3-mtbench-slider::-webkit-slider-runnable-track {
  height: 10px; border-radius: 99px; background: var(--ks3-dark-panel);
  border: 2px solid var(--ks3-on-dark-muted);
}
.ks3-mtbench-slider::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 28px; height: 28px; margin-top: -11px; border-radius: 50%;
  background: var(--ks3-blue-light); border: 3px solid var(--ks3-ink);
  cursor: pointer;
}
.ks3-mtbench-slider::-moz-range-track {
  height: 10px; border-radius: 99px; background: var(--ks3-dark-panel);
  border: 2px solid var(--ks3-on-dark-muted);
}
.ks3-mtbench-slider::-moz-range-thumb {
  width: 28px; height: 28px; border-radius: 50%;
  background: var(--ks3-blue-light); border: 3px solid var(--ks3-ink);
  cursor: pointer;
}

/* ── the bar panel ───────────────────────────────────────────────────── */

.ks3-mtbench-panel {
  margin-top: 18px; padding: 20px 22px;
  border-radius: var(--ks3-r-card); background: var(--ks3-dark-panel);
}
.ks3-mtbench-barscaption {
  margin: 0 0 14px; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; letter-spacing: .08em; text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-mtbench-bars { display: flex; flex-direction: column; gap: 14px; }

.ks3-mtbench-barhead {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; margin-bottom: 6px;
}
.ks3-mtbench-barlabel {
  font-size: 17px; font-weight: 600; color: var(--ks3-on-dark);
}
.ks3-mtbench-barvalue {
  font-family: var(--ks3-font-mono); font-size: 17px; font-weight: 500;
  color: var(--ks3-blue-light); white-space: nowrap;
}
.ks3-mtbench-bartrack {
  display: block; height: 22px; border-radius: 8px;
  background: rgba(255, 255, 255, .07);
  border: 2px solid var(--ks3-on-dark-muted); overflow: hidden;
}
/* The width is the only thing the wiring writes inline: it is the RATIO the
   label beside it claims, and it comes from the same arithmetic that produced
   the printed value. */
.ks3-mtbench-barfill {
  display: block; height: 100%; width: 0;
  background: var(--ks3-blue-light);
}
.ks3-mtbench-barsub {
  margin: 6px 0 0; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; color: var(--ks3-on-dark-muted);
}

/* THE BAR IN FOCUS — see the block header for the whole argument. */
.ks3-mtbench-bar[data-focus] .ks3-mtbench-barfill {
  background: var(--ks3-data);
}
.ks3-mtbench-bar[data-focus] .ks3-mtbench-bartrack {
  border-width: 3px; border-color: var(--ks3-on-dark);
}
.ks3-mtbench-bar[data-focus] .ks3-mtbench-barlabel { font-weight: 800; }

/* The reference line — p11-04's liquid water at 1.00. Deliberately recessive:
   it is the thing the other two are judged against and not one of them. */
.ks3-mtbench-bar[data-muted] .ks3-mtbench-barfill {
  background: var(--ks3-on-dark-muted);
}

/* ── the readout cards ───────────────────────────────────────────────── */

.ks3-mtbench-tiles {
  margin-top: 16px; display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr));
  gap: 12px;
}
.ks3-mtbench-tile {
  padding: 15px 17px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-dark-panel);
}
.ks3-mtbench-tile-label {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-mtbench-tile-value {
  margin: 5px 0 0; font-family: var(--ks3-font-mono); font-size: 22px;
  font-weight: 500; color: var(--ks3-on-dark);
}
.ks3-mtbench-tile-sub {
  margin: 4px 0 0; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; color: var(--ks3-on-dark-muted);
}

/* ── the closing note ────────────────────────────────────────────────── */

.ks3-mtbench-note {
  margin: 16px 0 0; padding: 16px 18px;
  border-radius: var(--ks3-r-panel); background: var(--ks3-dark-panel);
  font-size: 18px; line-height: 1.55; color: var(--ks3-on-dark-body);
}
/* ═══ END P11 ═══ */

/* ═══ BEGIN P12 ══════════════════════════════════════════════════════════
   P12 — *Space*. ONE marked block, and ONE instrument inside it.

   Design ships `Bench.dc.html` as a shared child component mounted by all
   six pages, so the whole unit's bench styling is one set of selectors and
   the six lessons cannot drift apart. Every number below is measured off
   that component's own inline styles.

   ⚠️ EVERY SELECTOR HERE IS P12-ONLY, and every shell stem was checked
   against the whole registry before it was written. TWO obvious ones were
   already taken — `ks3-sbench-` and `ks3-bbench-` — so this unit uses
   `ks3-spbench-`, `ks3-spthink` and `ks3-p12cfa-`. That is the MRB-279
   collision caught by the assertion rather than in a browser open on
   somebody else's page.

   ⚠️ NO FIGURE WRAPPER NEEDS `position: relative`, BECAUSE THERE IS NO
   FIGURE. Design draws no SVG on any P12 bench: every varying value is a
   bar label, a readout card or the note, all of them HTML text. Her own
   §3 says so and the pages measure that way.

   ⚖️ THE SELECTED BAR IS NOT AMBER, AND THE SUBSTITUTION IS NOT THE
   OBVIOUS ONE. Design paints the current bar `var(--ks3-alert)` on all six
   benches; 5A.2 reserves amber for a wrong IDEA being confronted and sends
   category and selection to `--ks3-data`. But `--ks3-data` and
   `--ks3-blue-light` are THE SAME VALUE (`#8FB7FF`, deliberately — see
   `shared/tokens.css`), and the other bars are already painted with it, so
   a straight swap would make the selected bar identical to the four beside
   it. The selection therefore moves to a structural mark — a 3px
   `--ks3-on-dark` RING on the track plus the row label at 800, the SAME mark
   P11's `matter-bench` uses on the same Bench shell (unified at integration;
   the unit had first drawn a 2px accent ring) — and every readout card names
   the current selection in words as well. Registered in DEPARTURES-P12.md.

   ⚠️ NO `.ks3-spbench-head` RULE AND NO `-progress` RULE. This unit draws
   no head row of its own: `r_activity`'s shell owns it, `.ks3-blockhead`
   already styles it, and it is already Design's layout — eyebrow and
   heading left, readout right. See the note where `_head` is NOT, in
   `ks3_art/p12.py`.
   ═══════════════════════════════════════════════════════════════════════ */

.ks3-spbench-gate {
  padding: 20px 22px; border-radius: var(--ks3-r-panel);
  background: rgba(255, 255, 255, .06);
  border: 2px solid var(--ks3-on-dark-muted);
}
.ks3-spbench-lead {
  margin: 0 0 18px; font-size: 19px; line-height: 1.65;
  color: var(--ks3-on-dark-body); max-width: 58ch;
}
.ks3-spbench-body { margin-top: 22px; }

.ks3-spbench-controls {
  display: flex; flex-wrap: wrap; gap: 20px 30px; align-items: flex-start;
}
.ks3-spbench-picker { display: grid; gap: 9px; flex: 1 1 460px; }
.ks3-spbench-row {
  display: grid; gap: 8px; flex: 1 1 280px; min-width: 240px;
}
.ks3-spbench-pickerlabel {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 14px;
  font-weight: 500; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-spbench-tabrow { display: flex; flex-wrap: wrap; gap: 9px; }
.ks3-spbench-rowhead {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 16px; flex-wrap: wrap;
  font-family: var(--ks3-font-mono); font-size: 14px; font-weight: 500;
  letter-spacing: .07em; text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-spbench-reading {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 17px;
  font-weight: 500; letter-spacing: 0; text-transform: none;
  color: var(--ks3-blue-light); white-space: nowrap;
}
/* 44px of vertical target, which is the smallest a thumb should be asked to
   find — the slider is one of only two controls on the bench. */
.ks3-spbench-slider {
  width: 100%; max-width: 22rem; min-height: 44px;
  accent-color: var(--ks3-accent);
}

/* ── the bar panel · the comparison, on every page ───────────────────── */

.ks3-spbench-panel {
  margin-top: 18px; padding: 20px 22px;
  border-radius: var(--ks3-r-card); background: var(--ks3-dark-panel);
}
.ks3-spbench-caption {
  margin: 0 0 14px; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; letter-spacing: .08em; text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-spbench-bars { display: flex; flex-direction: column; gap: 14px; }
.ks3-spbench-barhead {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; margin-bottom: 6px;
}
.ks3-spbench-barlabel {
  font-size: 17px; font-weight: 600; color: var(--ks3-on-dark);
}
.ks3-spbench-barvalue {
  font-family: var(--ks3-font-mono); font-size: 17px; font-weight: 500;
  color: var(--ks3-blue-light); white-space: nowrap;
}
.ks3-spbench-track {
  display: block; height: 22px; border-radius: 8px;
  background: rgba(255, 255, 255, .07);
  border: 2px solid var(--ks3-on-dark-muted); overflow: hidden;
}
.ks3-spbench-fill {
  display: block; height: 100%; width: 0;
  background: var(--ks3-data);
}
/* The current row. See the header: a ring rather than a colour swap,
   because the token 5A.2 sends a selection to is the colour the other bars
   are already painted in. */
.ks3-spbench-bar[data-current="1"] .ks3-spbench-track {
  /* ⊕ Integration, 25 Aug 2026 — the SAME mark as P11's `matter-bench`, which
     draws the same Bench shell: a 3px `--ks3-on-dark` ring and the label at
     800. The unit had a 2px `--ks3-accent` ring; P11 and P12 are one shell
     on ten pages and a student moving between them meets one treatment. */
  border-width: 3px; border-color: var(--ks3-on-dark);
}
.ks3-spbench-bar[data-current="1"] .ks3-spbench-barlabel {
  font-weight: 800;
}
.ks3-spbench-barsub {
  margin: 6px 0 0; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; color: var(--ks3-on-dark-muted);
}

/* ── the readout cards and the closing note ─────────────────────────── */

.ks3-spbench-readouts {
  margin-top: 16px; display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr));
  gap: 12px;
}
.ks3-spbench-tile {
  padding: 15px 17px; border-radius: var(--ks3-r-panel);
  background: var(--ks3-dark-panel);
}
.ks3-spbench-tile-label {
  margin: 0; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ks3-on-dark-muted);
}
.ks3-spbench-tile-value {
  margin: 5px 0 0; font-family: var(--ks3-font-mono); font-size: 22px;
  font-weight: 500; color: var(--ks3-on-dark);
}
.ks3-spbench-tile-sub {
  margin: 4px 0 0; font-family: var(--ks3-font-mono); font-size: 13px;
  font-weight: 500; color: var(--ks3-on-dark-muted);
}
.ks3-spbench-note {
  margin: 16px 0 0; padding: 16px 18px;
  border-radius: var(--ks3-r-panel); background: var(--ks3-dark-panel);
  font-size: 18px; line-height: 1.55; color: var(--ks3-on-dark-body);
}

/* ── #s-think on p12-03, p12-04 and p12-05 ──────────────────────────────

   ⚠️ NO RULES, AND THAT IS THE COMPONENT. `space-think` is a SHELL-ONLY
   family: `r_space_think` draws nothing and the section's whole body is
   Design's confrontation, rendered by `r_confrontation` from the block
   type and already styled by `.ks3-misconception`. The class exists so the
   section can declare `data-stage-done="0"` in the shipped bytes, which is
   what `check_nothing_ticks_on_load` requires of a rail anchor. A rule
   here would be a rule on a component with no markup of its own. */

/* ── #s-formula's attempt panel on p12-01, p12-02 and p12-06 ───────────

   ⚠️ NO RULES EITHER. The drawing is `ks3_art.kit.r_cfifa_attempt`, shared
   with P4–P7, and `.ks3-cfa*` above already styles every part of it. The
   family is P12's own so the registry's one-family-one-module rule holds
   and the placement gates see it as this unit's; the shell class carries
   the marker and nothing else. */

/* ═══ END P12 ═══ */
