/* ============================================================
   THE ANNOTATED NOTEBOOK — reading experience (Phase 2, Task 3).
   Everything in here is scoped .nb-notes and restyles the classes
   rendering.py emits (restyle, NEVER rename — the pipeline and the
   reading scripts own those names). Two emitted callout families
   exist: inline promotions (.note-callout.note-callout--X with
   > strong + > span) and blockquote promotions (blockquote.callout-X
   with .callout-icon-col + .callout-body). Both are covered.

   Voices (DESIGN.md):
   · body/UI = the canonical sans; serif italic = the examiner + sparse
     editorial accents ONLY
   · callouts use the SEMANTIC admonition palette (DESIGN.md §2): each
     type wears its own muted accent + icon + kicker. examiner-insight
     alone keeps the serif-italic examiner voice (now GOLD); exam-tip is
     a plain GREEN sans note; every other type is quiet accented ink.
   · bracketed inline definitions stay as quiet, unhighlighted prose
   Discipline (test-enforced): zero raw colors — tokens/color-mix only.
   ============================================================ */

/* ---------- 1 · The reading voice --------------------------------- */
.nb-notes {
  font: var(--nb-type-prose);
  color: var(--nb-ink);
  overflow-wrap: break-word;
}
.nb-notes p,
.nb-notes ul,
.nb-notes ol {
  margin: 0 0 var(--nb-s4);
}

/* The pipeline's .note-prose-surface was a painted panel in the old
   theme. In the notebook the page IS the paper: the wrapper becomes
   invisible and prose sits directly on the dotted grid, exactly like
   the approved demo's copy blocks. */
.nb-notes .note-prose-surface {
  background: none;
  border: none;
  padding: 0;
  margin: 0;
}

/* ---------- 2 · Headings ------------------------------------------- */
.nb-notes h2,
.nb-notes h3,
.nb-notes h4 {
  font-family: var(--nb-sans);
  font-weight: 700;
  color: var(--nb-ink);
  letter-spacing: -0.01em;
}
.nb-notes h2 {
  margin: var(--nb-s9) 0 var(--nb-s4);
  padding-bottom: var(--nb-s2);
  border-bottom: 1px solid var(--nb-border); /* thin rule under h2 */
  font-size: var(--nb-text-2xl);
  line-height: 1.25;
}
/* Section-opening accent bar under the h2 text (above the full-width
   hairline) -- lifts the h2 clear of bold body text. Open-page h2s only:
   box/review/objective headings restyle h2 as an in-card title and must
   NOT carry the bar (reset below). */
.nb-notes h2::after {
  content: "";
  display: block;
  width: 44px;
  height: 3px;
  margin-top: var(--nb-s2);
  background: var(--nb-blue);
  border-radius: 2px;
}
.nb-notes :is(.note-worked-example,
              .note-worked-example-body,
              .note-practical,
              .note-practical-note,
              .note-derivation,
              .note-review-section,
              .nt-objectives-card) :is(h2, h3)::after,
.nb-notes .nb-paywall-title::after {
  content: none;
}
/* h3 is the REAL top of the emitted heading tree (the corpus writes ###/####
   and the first h2 is dropped server-side), so it carries the same
   section-header treatment as h2 — rule + accent bar, one step smaller.
   Box titles (worked example / practical / review / objectives) restyle
   their own h3 and are excluded from the bar above. */
.nb-notes h3 {
  margin: var(--nb-s8) 0 var(--nb-s3);
  padding-bottom: var(--nb-s2);
  border-bottom: 1px solid var(--nb-border);
  font-size: var(--nb-text-xl);
  line-height: 1.3;
}
.nb-notes h3::after {
  content: "";
  display: block;
  width: 36px;
  height: 3px;
  margin-top: var(--nb-s2);
  background: var(--nb-blue);
  border-radius: 2px;
}
/* h4 reads as a subtopic title, one canonical token step above body prose. */
.nb-notes h4 {
  margin: var(--nb-s6) 0 var(--nb-s3);
  font-size: var(--nb-text-lg);
  line-height: 1.35;
}

/* The note's first <h2> duplicated the page-header H1. It is now dropped
   server-side (_nb_drop_first_heading, notebook-proof path) so note-reading.js
   never scans it into a phantom "On this page" entry — no CSS hide needed. */

/* ---------- 3 · Lead paragraphs ------------------------------------
   rendering.py tags the first plain paragraph after each h2/h3. */
.nb-notes .note-lead {
  font-family: inherit;
  font-size: inherit;
  font-weight: inherit;
  line-height: inherit;
  color: var(--nb-ink);
}

/* ---------- 4 · Lists ----------------------------------------------- */
.nb-notes ul,
.nb-notes ol {
  padding-left: var(--nb-s5);
}
.nb-notes li {
  margin-bottom: var(--nb-s2);
}
.nb-notes li::marker {
  color: var(--nb-ink-3); /* quiet structural marks, not content */
}
/* markdown often wraps li text in <p> — collapse the double margin */
.nb-notes li > p {
  margin-bottom: var(--nb-s1);
}
.nb-notes li > p:last-child {
  margin-bottom: 0;
}

/* ---------- 5 · Concepts + tiers ------------------------------------
   Bracketed inline definitions remain ordinary prose. The leading concept
   term keeps a little extra weight so the definition is still easy to scan. */

/* Tier flag. CIE marks supplement content the Extended tier must study
   (Edexcel IGCSE Maths A: Higher); the server promotes its rocket marker to
   this tag (_nb_flag_extended). One look sitewide (owner 2026-10-01, his
   picture, then "more brighter a bit text white and color more saturated"):
   the word in white, capitals, tracked, on a saturated blue, its rounded left
   end edged in a brighter blue; never the examiner's red. The three colours
   are --nb-tier-* (notebook-blocks.css section 14). */
.nb-notes .nb-tier {
  display: inline-block;
  padding: 0.22em 0.8em 0.22em 0.7em;
  vertical-align: 0.08em;
  margin-right: 0.45em;
  font: var(--nb-type-label);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--nb-tier-ink, var(--nb-ink));
  background: var(--nb-tier-fill, var(--nb-blue-wash));
  border: 0;
  border-left: 3px solid var(--nb-tier-edge, var(--nb-blue));
  border-radius: var(--nb-r-pill);
  white-space: nowrap;
}

.nb-notes .note-concept > strong {
  font-weight: 700;
}

/* ---------- 6 · Callouts — the SEMANTIC admonition system ------------
   The owner asked for each callout type to be identifiable at a glance:
   its own muted accent, matching icon + kicker, harmonious NOT rainbow.
   This is the ONE documented relaxation of the three-ink rule (DESIGN.md
   §2 "Callout admonition palette") — the accents are SEMANTIC (each maps
   to a specific purpose), never decorative candy.

   Each type sets one local var, --adm (its accent token); the shared base
   below resolves the tint bg, the FULL hairline (no banned one-side
   stripe), and the kicker/icon colour from that single var. Both emitted
   families are covered:
     · inline   blockquote.note-callout.note-callout--X  (> strong + > span)
     · document blockquote.callout-X  (.callout-icon-col + .callout-body)
   The examiner's serif-italic voice is now reserved for examiner-insight
   ONLY (in gold); exam-tip is a plain GREEN sans note. Icons are pure CSS
   unicode — no icon lib.
   -------------------------------------------------------------------- */

/* Shared aside anatomy for BOTH families: a soft tint wash of the type's
   accent over paper, a full hairline in the same accent, quiet ink body. */
.nb-notes .note-callout,
.nb-notes blockquote[class^="callout-"],
.nb-notes blockquote[class*=" callout-"] {
  --adm: var(--nb-ink-2); /* fallback until a type sets its accent */
  display: block;
  margin: var(--nb-s6) 0;
  padding: var(--nb-s4) var(--nb-s5);
  background: color-mix(in srgb, var(--adm) var(--nb-adm-tint), var(--nb-paper));
  border: 1px solid color-mix(in srgb, var(--adm) 28%, transparent);
  border-radius: var(--nb-r-md);
  font: var(--nb-type-prose);
  color: var(--nb-ink);
}

/* Per-type accent (semantic). One assignment each; everything else
   (bg / border / kicker / icon) derives from --adm. */
.nb-notes .note-callout--misconception,
.nb-notes blockquote.callout-misconception { --adm: var(--nb-adm-warn); }
/* Dark night-desk: the warm terracotta wash muted the cool --nb-ink body a
   touch. Thin the misconception wash so the body ink reads crisper (higher
   contrast), still comfortably AA on the darker paper. Kicker/border keep the
   full accent. */
html.theme-dark .nb-notes .note-callout--misconception,
html.theme-dark .nb-notes blockquote.callout-misconception {
  background: color-mix(in srgb, var(--adm) 6%, var(--nb-paper));
}
.nb-notes .note-callout--exam-tip,
.nb-notes blockquote.callout-exam-tip { --adm: var(--nb-adm-tip); }
/* Examiner Insight takes the summary ink rather than --nb-adm-insight:
   the token itself must keep its value, because the homepage showcase
   card reads it too (notebook-public-skin.css, .lp-sv-co--examiner)
   and that surface stays gold. Voice is untouched — serif italic body,
   ✱ glyph, frame, label casing all unchanged. Colour only. */
.nb-notes .note-callout--insight,
.nb-notes blockquote.callout-examiner { --adm: var(--nb-summary-ink); }
.nb-notes blockquote.callout-key-point { --adm: var(--nb-adm-key); }
.nb-notes blockquote.callout-definition { --adm: var(--nb-adm-def); }
.nb-notes .note-callout--example,
.nb-notes blockquote.callout-illustrative-example { --adm: var(--nb-adm-example); }
.nb-notes .note-callout--out-of-scope,
.nb-notes blockquote.callout-out-of-scope { --adm: var(--nb-adm-scope); }
.nb-notes .note-callout--mnemonic { --adm: var(--nb-adm-mnemonic); }
.nb-notes .note-callout--math-skill { --adm: var(--nb-adm-mathskill); }

/* — Inline family: small-caps kicker in the accent, a leading glyph, and
     a plain-ink body. — */
.nb-notes .note-callout > strong {
  display: block;
  margin-bottom: var(--nb-s2);
  font: var(--nb-type-label);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--adm);
}
.nb-notes .note-callout > strong::before {
  content: var(--adm-glyph, "\2022"); /* the type's icon, colour = accent */
  display: inline-block;
  margin-right: var(--nb-s2);
  font-weight: 700;
  transform: translateY(0.02em);
}
.nb-notes .note-callout > span {
  display: block;
}
/* out-of-scope reads a shade quieter in the body — a "you may stop here" */
.nb-notes .note-callout--out-of-scope > span {
  color: var(--nb-ink);
}
/* ONE INK (owner, 2026-09-26). The out-of-scope accent is a neutral slate,
   and the illustrative example's is a periwinkle slate (#9AA6D0 by night,
   #355A74 by day, the "weird bluish, purple gray" the owner ruled out), so
   their kickers and glyphs read as grey words; they take the full ink, and
   each callout keeps its own slate edge and wash (and the glyph's badge its
   slate wash). The summaries register sets its own label ink and outranks
   this. */
.nb-notes .note-callout--out-of-scope > strong,
.nb-notes blockquote.callout-out-of-scope .callout-icon-col,
.nb-notes blockquote.callout-out-of-scope .callout-body p:first-child > strong:first-child,
.nb-notes .note-callout--example > strong,
.nb-notes blockquote.callout-illustrative-example .callout-icon-col,
.nb-notes blockquote.callout-illustrative-example .callout-body p:first-child > strong:first-child {
  color: var(--nb-ink);
}

/* — Per-type icon glyph (unicode, pure CSS). Simple + consistent size. — */
/* Definitions carry NO mark: § is a reference mark ("section"), not a
   definition mark, and it was the optically lightest glyph in the set at
   6.3px against ✓ 9.7 / ✦ 11.6. The register keeps its accent and tint and
   lets the label stand alone, exactly as note-callout--proportionality has
   done on 525 notes callouts. The declaration is kept rather than deleted:
   `none` resolves through content: var(--adm-glyph, ...) to content: none,
   so the pseudo-element is never generated and the label sits flush with
   the body text beneath it. noteviewer/tests.py asserts this declaration
   exists, so removing the line would fail that test. */
.nb-notes .note-callout--misconception { --adm-glyph: "\0021"; }        /* ! warning */
.nb-notes .note-callout--exam-tip { --adm-glyph: "\2713"; }             /* ✓ tick */
.nb-notes .note-callout--insight { --adm-glyph: "\2731"; }              /* ✱ star */
.nb-notes .note-callout--example { --adm-glyph: "\25C8"; }              /* ◈ figure */
.nb-notes .note-callout--out-of-scope { --adm-glyph: "\2013"; }        /* – dash */
.nb-notes .note-callout--mnemonic { --adm-glyph: "\2726"; }             /* ✦ memory spark */
.nb-notes .note-callout--math-skill { --adm-glyph: "\221A"; }           /* √ math technique */
.nb-notes blockquote.callout-misconception { --adm-glyph: "\0021"; }
.nb-notes blockquote.callout-exam-tip { --adm-glyph: "\2713"; }
.nb-notes blockquote.callout-examiner { --adm-glyph: "\2731"; }
.nb-notes blockquote.callout-key-point { --adm-glyph: "\25C6"; }        /* ◆ key idea */
.nb-notes blockquote.callout-definition { --adm-glyph: none; }          /* label alone - see below */
.nb-notes blockquote.callout-illustrative-example { --adm-glyph: "\25C8"; }
.nb-notes blockquote.callout-out-of-scope { --adm-glyph: "\2013"; }

/* — Examiner insight KEEPS the examiner's serif-italic voice, now in GOLD
     (examiner-insight / examiner only — exam-tip is a plain green sans note
     per the owner's brief). — */
.nb-notes .note-callout--insight,
.nb-notes blockquote.callout-examiner {
  font-family: var(--nb-serif);
  font-style: italic;
  font-size: var(--nb-text-md);
  line-height: 1.5;
}
.nb-notes .note-callout--insight > strong {
  font-family: var(--nb-serif);
  font-style: italic;
  font-size: var(--nb-text-sm);
  font-weight: 600;
  line-height: 1.5;
  letter-spacing: 0.02em;
  text-transform: none;
}
.nb-notes .note-callout--insight > strong::before {
  font-style: normal;
  transform: rotate(-4deg); /* the hand never stamps perfectly straight */
}

/* -- Proportionality -- the equation-family relationship note (SI unit /
     Variables / Rearrangements prose). Speaks in the SLIM WE-callout
     register (mini label, 3px left rule, light wash), NOT the admonition
     register: no icon badge, no full frame, no rounded card.
     Now the summary orange, like the examiner register above: the
     --nb-adm-proportion token keeps its bronze so its value pins and
     the contrast gate stay true, and only this consumer moves. -- */
.nb-notes .note-callout--proportionality {
  --adm: var(--nb-summary-ink);
  padding: var(--nb-s2) var(--nb-s3) var(--nb-s3);
  background: color-mix(in srgb, var(--adm) 7%, transparent);
  border: none;
  border-left: 3px solid var(--adm);
  border-radius: 0;
}
.nb-notes .note-callout--proportionality > strong {
  margin-bottom: 2px;
  letter-spacing: 0.08em; /* match .note-we-callout-label tracking */
}
.nb-notes .note-callout--proportionality > strong::before {
  content: none; /* slim register: no icon glyph */
}

/* ── Summary leaves: ONE warm register for every callout ──────────────────
   A Summary.docx gives all 700 of its callout cells the same treatment —
   one left rule, one warm wash, one label — whatever the type. On this site
   the three summary registers (588 Key Definition, 69 Exam Tip, 11 Examiner
   Insight; corpus-verified across 960 sources) render in three different
   hues. Here they collapse to one.

   COLOUR collapses; MARKUP does not. rendering.py still emits
   callout-definition / note-callout--exam-tip / note-callout--insight and
   those classes stay in the DOM — only the accent they resolve to is
   overridden. Notes are untouched: every rule below is scoped to
   .note-body--summaries (nb_resource_detail.html:101), the first consumer of
   that discriminator in the notebook layer.

   The accent is --nb-summary-ink, not the source's own orange. That
   orange measures 2.19:1 on --nb-paper and 2.07:1 on its own wash — far
   under the 4.5:1 the token layer requires of every accent (see
   notebook-tokens.css, the callout admonition palette). --nb-summary-ink
   is its AA-clearing relative: 5.11 on paper / 4.85 on wash in light,
   7.74 / 7.08 in dark, where it is the raw orange.

   The FORM is the slim register directly above: left rule, wash, no frame,
   square corners, no glyph. Two deliberate exclusions:
     · .note-chem-equation is not selected. It reads --nb-adm-def directly
       rather than through --adm, and it appears on 26 SUMMARY leaves (37
       plates) — those keep the plate they have.
     · the body voice is left alone. Examiner Insight keeps its serif italic;
       only its LABEL is brought into line, because a register whose point is
       that the labels match cannot have one of the three set mixed-case. ── */
.note-body--summaries.nb-notes .note-callout {
  --adm: var(--nb-summary-ink);
  padding: var(--nb-s3) var(--nb-s4);
  background: color-mix(in srgb, var(--nb-summary) 7%, transparent);
  border: none;
  /* 3px, matching .note-callout--proportionality above: one stripe width in
     this sheet, not two. */
  border-left: 3px solid var(--nb-summary);
  border-radius: 0;
}
/* One label voice. The insight label sets its own casing above
   (text-transform: none, serif italic); this outranks it on specificity. */
.note-body--summaries.nb-notes .note-callout > strong {
  font: var(--nb-type-label);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--adm);
}
/* Slim register: the label carries the type on its own, so no icon badge.
   `none` resolves through content: var(--adm-glyph, …) to content: none,
   and the pseudo-element is never generated. */
.note-body--summaries.nb-notes .note-callout > strong::before {
  content: none;
}

/* — Blockquote family internals: the empty icon column becomes a small
     round accent badge carrying the type glyph; the body's leading
     <strong> becomes the accent kicker. — */
/* :not(.note-callout) scopes the flex row to the BLOCKQUOTE anatomy it was
   written for — <div class="callout-icon-col"> beside <div class="callout-body">
   (tag_callout_blockquotes, rendering.py:563). The INLINE anatomy is
   <strong> + <span> (promote_inline_callouts) and stacks: label on its own
   line, body beneath, full width.

   Without the guard these attribute selectors also caught the one inline
   register whose modifier lives in this namespace, "note-callout
   callout-definition" — 588 of the corpus's 6,501 callouts. Its <strong> and
   <span> became the two flex items, so KEY DEFINITION sat in a shrink-to-fit
   column beside the body, wrapped to two lines, and left a ragged gutter
   (measured 92-138px wide depending on body length). Every other register is
   note-callout--X and never matched, which is why only definitions diverged.

   Guarding the rule is preferred over adding a summaries override: the inline
   family now inherits the notes shape rather than getting a copy of it, and
   any future callout-* modifier is immune to the same collision. */
.nb-notes blockquote[class^="callout-"]:not(.note-callout),
.nb-notes blockquote[class*=" callout-"]:not(.note-callout) {
  display: flex;
  align-items: flex-start;
  gap: var(--nb-s3);
}
.nb-notes .callout-icon-col {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.4em;
  height: 1.4em;
  margin-top: 0.1em; /* optically on the first line */
  border-radius: var(--nb-r-pill);
  background: color-mix(in srgb, var(--adm) 16%, transparent);
  color: var(--adm);
  font-family: var(--nb-sans);
  font-style: normal;
  font-size: var(--nb-text-sm);
  font-weight: 700;
  line-height: 1;
}
.nb-notes .callout-icon-col::before {
  content: var(--adm-glyph, "\2022");
}
/* examiner insight badge: the star sits a touch smaller in serif to echo
   the examiner voice; every other type stays sans. */
.nb-notes blockquote.callout-examiner .callout-icon-col {
  font-family: var(--nb-serif);
}
.nb-notes .callout-body {
  flex: 1 1 auto;
  min-width: 0;
}
.nb-notes .callout-body p {
  margin-bottom: var(--nb-s2);
}
.nb-notes .callout-body p:last-child {
  margin-bottom: 0;
}
.nb-notes .callout-body p:first-child > strong:first-child {
  display: block;
  margin-bottom: var(--nb-s1);
  font: var(--nb-type-label);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--adm);
}
/* examiner insight body + kicker keep the serif-italic examiner voice (gold) */
.nb-notes blockquote.callout-examiner {
  font-family: var(--nb-serif);
  font-style: italic;
  font-size: var(--nb-text-md);
  line-height: 1.5;
}
.nb-notes blockquote.callout-examiner .callout-body p:first-child > strong:first-child {
  font-family: var(--nb-serif);
  font-style: italic;
  font-size: var(--nb-text-sm);
  font-weight: 600;
  line-height: 1.5;
  letter-spacing: 0.02em;
  text-transform: none;
}

/* ---------- 7 · Key idea ---------------------------------------------
   A raised card sliver with a canonical sans label and study-prose body. */
.nb-notes .note-key-idea {
  display: block;
  margin: var(--nb-s6) 0;
  padding: var(--nb-s4) var(--nb-s5);
  background: var(--nb-paper-raised);
  border: 1px solid var(--nb-border);
  border-radius: var(--nb-r-lg);
  box-shadow: var(--nb-shadow-1);
}
.nb-notes .note-key-idea-label {
  display: block;
  margin-bottom: var(--nb-s1);
  font: var(--nb-type-label);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--nb-blue);
}
.nb-notes .note-key-idea-text {
  display: block;
  font: var(--nb-type-prose);
}

/* ---------- 8 · Section takeaway -------------------------------------
   A sunken summary block; each point is ticked off in the student's
   pen. */
.nb-notes .note-section-takeaway {
  margin: var(--nb-s6) 0;
  padding: var(--nb-s4) var(--nb-s5);
  background: var(--nb-paper-sunken);
  border: 1px solid var(--nb-border);
  border-radius: var(--nb-r-md);
}
.nb-notes .note-section-takeaway-label {
  display: block;
  margin-bottom: var(--nb-s2);
  font-size: var(--nb-text-xs);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--nb-ink-2);
}
.nb-notes .note-section-takeaway-text {
  margin: 0;
  font: var(--nb-type-prose);
}
.nb-notes .note-section-takeaway-list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.nb-notes .note-section-takeaway-list > li {
  position: relative;
  margin-bottom: var(--nb-s2);
  padding-left: var(--nb-s5);
  font: var(--nb-type-prose);
}
.nb-notes .note-section-takeaway-list > li::before {
  content: "\2713";
  position: absolute;
  left: 0;
  top: 0;
  font-weight: 700;
  color: var(--nb-blue);
}

/* ---------- 9 · Worked example ---------------------------------------
   A raised sheet. No corner stamp: the labels that sat in a box's
   top-right corner ([worked example], [ practical note ], [ derivation ],
   and the sealed sheets' [Free] and [Premium]) were removed (owner,
   2026-09-28), so those titles take the box's full width. The core
   practical keeps its tag, which sits above its title, not in a corner. */
.nb-notes .note-worked-example {
  position: relative;
  margin: var(--nb-s7) 0;
  padding: var(--nb-s5) var(--nb-s6);
  background: var(--nb-paper-raised);
  border: 1px solid var(--nb-border);
  border-radius: var(--nb-r-lg);
  box-shadow: var(--nb-shadow-1);
}
.nb-notes .note-worked-example > h2,
.nb-notes .note-worked-example > h3,
.nb-notes .note-worked-example > h4 {
  margin: 0 0 var(--nb-s3);
  padding: 0;
  border: none;
  font-size: var(--nb-text-lg);
  text-align: center;
}
.nb-notes .note-worked-example-body > h2,
.nb-notes .note-worked-example-body > h3,
.nb-notes .note-worked-example-body > h4 {
  margin: var(--nb-s4) 0 var(--nb-s2);
  padding: 0;
  border: none;
  font-size: var(--nb-text-base);
  letter-spacing: 0.01em;
}
.nb-notes .note-worked-example-body > p:last-child,
.nb-notes .note-worked-example-body > ul:last-child,
.nb-notes .note-worked-example-body > ol:last-child {
  margin-bottom: 0;
}

/* ---------- 9b · Core-practical box ----------------------------------
   Sibling of the worked-example box; the definition-teal voice marks it
   as a lab/experiment rather than a judgment moment. Shares the field
   rows below with the worked-example body. */
.nb-notes .note-practical {
  position: relative;
  margin: var(--nb-s7) 0;
  /* P1 "stacked left" layout (owner pick, 2026-07-03): fields run full-width
     with kickers above them, so the box returns to the standard inner
     padding — content aligns to the padding edge, no grid gutter to fight. */
  padding: var(--nb-s5) var(--nb-s6);
  background: var(--nb-paper-raised);
  border: 1px solid var(--nb-border);
  border-radius: var(--nb-r-lg);
  box-shadow: var(--nb-shadow-1);
}
/* No bracketed core practical tag over the box (owner 2026-10-05, of its 52
   boxes: "all instances of things like this removed"): every core
   practical's own title already says "CORE PRACTICAL n:". */
.nb-notes .note-practical > h2,
.nb-notes .note-practical > h3,
.nb-notes .note-practical > h4 {
  margin: 0 0 var(--nb-s4);
  padding: 0 0 var(--nb-s2);
  text-align: left;
  border: none;
  border-bottom: 1px solid var(--nb-border);
  font-size: var(--nb-text-lg);
}

/* Practical Note — a lightweight lab-technique block; reuses the Core Practical
   shell + field rows. It has no top-right corner label (owner, 2026-09-28);
   having no core-practical tag above its title tells it from a numbered
   Core Practical. */
.nb-notes .note-practical-note {
  position: relative;
  margin: var(--nb-s7) 0;
  padding: var(--nb-s5) var(--nb-s6);
  background: var(--nb-paper-raised);
  border: 1px solid var(--nb-border);
  border-radius: var(--nb-r-lg);
  box-shadow: var(--nb-shadow-1);
}
.nb-notes .note-practical-note > h4 {
  margin: 0 0 var(--nb-s3);
  padding: 0;
  border: none;
  font-size: var(--nb-text-lg);
}

/* Derivation — a mathematical proof box on the worked-example shell. Its
   top-right corner tag is gone (owner, 2026-09-28). */
.nb-notes .note-derivation {
  position: relative;
  margin: var(--nb-s7) 0;
  padding: var(--nb-s5) var(--nb-s6);
  background: var(--nb-paper-raised);
  border: 1px solid var(--nb-border);
  border-radius: var(--nb-r-lg);
  box-shadow: var(--nb-shadow-1);
}
.nb-notes .note-derivation > h4 {
  margin: 0 0 var(--nb-s3);
  padding: 0;
  border: none;
  font-size: var(--nb-text-lg);
}
/* Internal step headings (###) kept inside the proof body must stay quiet
   in-card subheads, not open-page section headers (rule/bar reset above). */
.nb-notes .note-derivation-body > h3 {
  margin: var(--nb-s4) 0 var(--nb-s2);
  padding: 0;
  border: none;
  font-size: var(--nb-text-lg);
}
.nb-notes .note-derivation-body > p:last-child {
  margin-bottom: 0;
}

/* One look for every box (owner 2026-09-30: the maths worked examples should
   read like the practical): each box's title sits left with a rule under it,
   as the practical's does; its field rows below share the practical's voice. */
.nb-notes :is(.note-worked-example, .note-derivation, .note-practical-note) > :is(h2, h3, h4) {
  margin: 0 0 var(--nb-s4);
  padding: 0 0 var(--nb-s2);
  text-align: left;
  border: none;
  border-bottom: 1px solid var(--nb-border);
  font-size: var(--nb-text-lg);
}

/* ---------- 9c · Structured field rows -------------------------------
   Shared by .note-practical-body and .note-worked-example-body. P1 "stacked
   left" (owner pick, 2026-07-03): the old two-column grid stranded labels in
   a wide gutter and squeezed the content — each field now runs full-width
   with its label as a small kicker above it. */
.nb-notes .note-field {
  display: block;
  margin: 0 0 var(--nb-s4);
}
.nb-notes .note-field:last-child {
  margin-bottom: 0;
}
.nb-notes .note-field-label {
  display: block;
  margin-bottom: 4px;
  font: var(--nb-type-label);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--nb-ink-2);
}
/* every box's rows speak in the definition-teal voice the practical set */
.nb-notes :is(.note-practical, .note-worked-example, .note-derivation) .note-field-label {
  color: var(--nb-adm-def);
  margin-bottom: 6px;
}
/* Practical density (owner request, 2026-07-09): the method fields packed
   too much text too tightly. Looser row rhythm, a touch more label-to-value
   air, hairline seams between consecutive plain rows, and a slightly taller
   reading line. Every box shares it since 2026-09-30 (owner: one look).
   The --warn variant paints its own wash + accent edge, so it is exempt
   from the seam (a hairline against the wash reads as a glitch). */
.nb-notes :is(.note-practical, .note-worked-example, .note-derivation) .note-field {
  margin-bottom: var(--nb-s5);
}
.nb-notes :is(.note-practical, .note-worked-example, .note-derivation) .note-field:last-child {
  margin-bottom: 0;
}
.nb-notes :is(.note-practical, .note-worked-example, .note-derivation)
  .note-field:not(.note-field--warn) + .note-field:not(.note-field--warn) {
  border-top: 1px solid var(--nb-border);
  padding-top: var(--nb-s4);
}
.nb-notes :is(.note-practical-body, .note-worked-example-body, .note-derivation-body) {
  line-height: 1.75;
}
.nb-notes .note-field-value > :first-child { margin-top: 0; }
.nb-notes .note-field-value > :last-child { margin-bottom: 0; }
.nb-notes .note-field--warn {
  padding: var(--nb-s2) var(--nb-s3) var(--nb-s3);
  border-left: 3px solid var(--nb-adm-warn);
  background: color-mix(in srgb, var(--nb-adm-warn) 7%, transparent);
}
.nb-notes .note-field--warn .note-field-label {
  color: var(--nb-adm-warn);
}

/* ---------- 10 · Review sections (recap + progress check) ------------
   Sunken end-of-note blocks with a chip kicker. The progress list is
   interactive: note-reading.js injects the .esa-check SVG per item
   and persists checks — the SVG classes below are its contract. */
.nb-notes .note-review-section {
  margin: var(--nb-s8) 0 var(--nb-s6);
  padding: var(--nb-s5) var(--nb-s6);
  background: var(--nb-paper-sunken);
  border: 1px solid var(--nb-border);
  border-radius: var(--nb-r-lg);
}
.nb-notes .note-review-heading {
  margin: 0 0 var(--nb-s1);
  padding: 0;
  border: none;
  font-size: var(--nb-text-lg);
  line-height: 1.3;
}
.nb-notes .note-review-kicker {
  /* "Key Points" / "Self-Assessment" tags removed per owner feedback —
     the section heading already names the block. */
  display: none;
  align-items: center;
  margin: 0 0 var(--nb-s4);
  padding: var(--nb-s1) var(--nb-s3);
  background: var(--nb-paper-raised);
  border: 1px solid var(--nb-border);
  border-radius: var(--nb-r-pill);
  font-size: var(--nb-text-xs);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--nb-ink-2);
}
.nb-notes .note-review-list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.nb-notes .note-review-list > li {
  margin-bottom: var(--nb-s3);
  font: var(--nb-type-prose);
}
.nb-notes .note-review-list > li:last-child {
  margin-bottom: 0;
}
.nb-notes .note-review-list > li p {
  margin: 0;
}
/* Recap points are clean, calm bullets (owner request) — a small round
   disc in quiet ink, optically centred on the first line. NOT the blue
   tick (the progress-check circles below keep their own hollow marker). */
.nb-notes .note-review-section--recap .note-review-list > li {
  position: relative;
  padding-left: var(--nb-s5);
}
.nb-notes .note-review-section--recap .note-review-list > li::before {
  content: "";
  position: absolute;
  left: var(--nb-s1);
  top: 0.62em;
  width: 5px;
  height: 5px;
  border-radius: var(--nb-r-pill);
  background: var(--nb-ink-3);
}

/* — Progress check: self-assessment checkboxes. — */
.nb-notes .note-review-section--progress .note-review-list > li {
  display: grid;
  grid-template-columns: var(--nb-s5) 1fr;
  gap: var(--nb-s3);
  align-items: start;
  padding: var(--nb-s1) 0;
  cursor: pointer;
  border-radius: var(--nb-r-sm);
}
/* A row a finger ticks keeps the 44px floor (audit N-019b, 2026-09-25): a
   one-line row was 36px on a tablet or a landscape phone. The padding grows
   on both sides, so the text stays where it sits in the row and no empty
   paper gathers under it; the 12px between rows is unchanged. */
@media (pointer: coarse) {
  .nb-notes .note-review-section--progress .note-review-list > li {
    padding-block: var(--nb-s2);
  }
}
/* no-JS fallback: a plain drawn box; suppressed once the SVG lands */
.nb-notes .note-review-section--progress .note-review-list > li::before {
  content: "";
  width: var(--nb-s4);
  height: var(--nb-s4);
  margin-top: 0.2em;
  border: 1.5px solid var(--nb-border-strong);
  border-radius: var(--nb-r-pill);
}
.nb-notes .note-review-section--progress .note-review-list.has-svg-checks > li::before {
  content: none;
}
/* Marker and sentence are PINNED to their tracks. The li's inline content is
   wrapped server-side in one .note-review-text span — without the pin, inline
   KaTeX spans would auto-flow into the narrow marker column and chop the
   sentence across grid cells. */
.nb-notes .note-review-section--progress .note-review-list > li > .esa-check {
  grid-column: 1;
  grid-row: 1;
}
.nb-notes .note-review-section--progress .note-review-list:not(.has-svg-checks) > li::before {
  grid-column: 1;
  grid-row: 1;
}
.nb-notes .note-review-section--progress .note-review-list > li > .note-review-text {
  grid-column: 2;
  grid-row: 1;
  min-width: 0;
}
.nb-notes .note-review-list > li > .note-review-text {
  display: block;
  margin: 0;
}
.nb-notes .note-review-section--progress .note-review-list.has-svg-checks > li > .esa-check {
  width: var(--nb-s5);
  height: var(--nb-s5);
  margin-top: 0.1em;
  overflow: visible;
  pointer-events: none;
  transform-origin: center;
  transition: transform var(--nb-dur-1) var(--nb-ease);
}
.nb-notes .note-review-section--progress .note-review-list > li:active > .esa-check {
  transform: scale(0.92);
}
/* A row note-reading.js makes a checkbox is a Tab stop: it wears the site's
   2px blue ring, as the table regions and the figure buttons do, not the
   browser's own (orange on a Chromium phone, black on a desk, 3px in WebKit;
   audit N-038, 2026-09-25). The state at rest is unchanged. */
.nb-notes .note-review-section--progress .note-review-list > li:focus-visible {
  outline: 2px solid var(--nb-blue);
  outline-offset: 2px;
}
.nb-notes .esa-check-g {
  transform-box: fill-box;
  transform-origin: center;
}
.nb-notes .esa-check-box {
  fill: transparent;
  stroke: var(--nb-border-strong);
  stroke-width: 1.7;
}
.nb-notes .note-review-list.has-svg-checks.anim-ready > li > .esa-check .esa-check-box {
  transition:
    stroke var(--nb-dur-1) var(--nb-ease),
    fill var(--nb-dur-1) var(--nb-ease);
}
/* Hover only where a pointer hovers: a finger that unticks a row left it in
   :hover, so the empty box kept the ticked blue and wash (same bug and same
   guard as the tutor sheet, audit N-020). */
@media (hover: hover) {
  .nb-notes .note-review-list > li:hover .esa-check-box {
    stroke: var(--nb-blue);
    fill: var(--nb-blue-wash);
  }
}
.nb-notes .note-review-list > li.is-checked .esa-check-box {
  stroke: var(--nb-blue);
  fill: var(--nb-blue-wash);
}
/* the tick draws itself like a pen stroke (dash contract from JS) */
.nb-notes .esa-check-tick {
  fill: none;
  /* Rest = no ink. A round-capped dash that is fully offset still paints a
     leftover round CAP at the path's start point (the "stray dot" defect),
     so the resting tick is drawn in transparent ink and only inks up blue
     once the box is checked. The pen-stroke draw-in still runs on the
     dashoffset; the colour flips instantly to blue at the same moment. */
  stroke: transparent;
  stroke-width: 2.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: var(--esa-tick-len, 16);
  stroke-dashoffset: var(--esa-tick-len, 16);
}
.nb-notes .note-review-list > li.is-checked .esa-check-tick {
  stroke: var(--nb-blue);
  stroke-dashoffset: 0;
}
.nb-notes .note-review-list.has-svg-checks.anim-ready > li > .esa-check .esa-check-tick {
  transition: stroke-dashoffset var(--nb-dur-2) var(--nb-ease);
}
@keyframes nb-check-pop {
  0% { transform: scale(0.6); }
  100% { transform: scale(1); }
}
.nb-notes .esa-check-g.is-popping {
  animation: nb-check-pop var(--nb-dur-2) var(--nb-ease);
}
/* a checked line settles into quiet ink — done, not deleted */
.nb-notes .note-review-list > li.is-checked p {
  color: var(--nb-ink-2);
}
@media (prefers-reduced-motion: reduce) {
  .nb-notes .note-review-list > li > .esa-check,
  .nb-notes .esa-check-tick,
  .nb-notes .esa-check-box { transition: none; }
  .nb-notes .note-review-list > li:active > .esa-check { transform: none; }
  .nb-notes .esa-check-g.is-popping { animation: none; }
}

/* ---------- 11 · Learning objectives card -----------------------------
   The hero card above the prose, in the topic-card grammar: blue caps
   eyebrow, standard study-prose instruction, statically drawn ticks in
   the student's pen, syllabus codes as chips. The legacy 42px icon
   badge is retired (no icon color-coding in the notebook). */
.nb-notes .nt-objectives-card {
  margin: 0 0 var(--nb-s7);
  padding: var(--nb-s5) var(--nb-s6);
  background: var(--nb-paper-raised);
  border: 1px solid var(--nb-border);
  border-radius: var(--nb-r-lg);
  box-shadow: var(--nb-shadow-1);
}
.nb-notes .nt-objectives-icon-column {
  display: none;
}
.nb-notes .nt-objectives-title-row {
  display: flex;
  align-items: baseline;
  /* the title alone: no objective count beside it (The No-Corner-Stamp
     Rule, owner 2026-10-01) */
}
.nb-notes .nt-objective-heading {
  margin: 0;
  padding: 0;
  border: none;
  font: var(--nb-type-label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--nb-blue);
}
.nb-notes .nt-objectives-subtitle {
  margin: var(--nb-s2) 0 0;
  font: var(--nb-type-prose);
  color: var(--nb-ink);
}
.nb-notes .nt-objectives-rule {
  height: 1px;
  margin: var(--nb-s4) 0;
  background: var(--nb-border);
}
.nb-notes .nt-objective-list {
  list-style: none;
  display: grid;
  gap: var(--nb-s3);
  margin: 0;
  padding: 0;
}
.nb-notes .nt-objective-item {
  display: flex;
  align-items: flex-start;
  gap: var(--nb-s3);
  margin: 0;
  font: var(--nb-type-prose);
}
/* A hollow pen-nib marker (the nb-empty glyph grammar), NOT a blue tick:
   objectives are aspirational targets the pen has not yet touched, not
   completed judgments. A filled/coloured check would claim they are done. */
.nb-notes .nt-objective-gutter {
  position: relative;
  flex-shrink: 0;
  width: var(--nb-s4);
  height: var(--nb-s4);
  margin-top: 0.2em;
}
.nb-notes .nt-objective-gutter::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 0.4em;
  width: var(--nb-s2);
  height: var(--nb-s2);
  transform: translateX(-50%);
  border: 1.5px solid var(--nb-border-strong);
  border-radius: var(--nb-r-pill);
}
.nb-notes .nt-objective-copy {
  flex: 1 1 auto;
  min-width: 0;
}
.nb-notes .nt-objective-text p {
  display: inline; /* pipeline nests <p> inside the span */
  margin: 0;
}
/* The syllabus code ("1.1a") as a squared reference tag, not a pill (owner,
   2026-09-29: "it looks ugly in pill shaped, make a new shape"): the small
   radius of a filed index label, a hairline edge that holds against the
   card, and tabular figures so 1.1a and 1.10b share a rhythm down the list. */
.nb-notes .obj-code {
  display: inline-flex;
  align-items: center;
  margin-right: var(--nb-s2);
  padding: 0 var(--nb-s2);
  background: var(--nb-paper-sunken);
  border: 1px solid var(--nb-border-strong);
  border-radius: var(--nb-r-sm);
  font: var(--nb-type-label);
  font-variant-numeric: tabular-nums lining-nums;
  letter-spacing: 0.02em;
  color: var(--nb-ink);
  white-space: nowrap;
}
.nb-notes .nt-objective-dash {
  display: none; /* the chip already separates code from text */
}
/* extended-tier objective: the quiet annotation that replaced the 🚀 */
.nb-notes .nt-objective-item.obj--extended .nt-objective-copy::after {
  content: "extended";
  display: inline-block;
  margin-left: var(--nb-s2);
  padding: 0.22em 0.8em 0.22em 0.7em;
  font: var(--nb-type-label);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--nb-tier-ink, var(--nb-ink));
  background: var(--nb-tier-fill, var(--nb-blue-wash));
  border: 0;
  border-left: 3px solid var(--nb-tier-edge, var(--nb-blue));
  border-radius: var(--nb-r-pill);
  white-space: nowrap;
}
/* the subject's own tier word, as its other tier chips say it (Higher on
   Edexcel IGCSE Maths A); "extended" above stays for a page without it */
.nb-notes .nt-objective-item.obj--extended .nt-objective-copy[data-tier]::after {
  content: attr(data-tier);
}

/* ---------- 12 · Tables ------------------------------------------------
   Hairline rules, a sunken header row, no zebra. */
.nb-notes .note-table-scroll {
  position: relative;
  margin: var(--nb-s6) 0;
  overflow-x: auto;
  background-color: var(--nb-paper-raised);
  border: 1px solid var(--nb-border);
  border-radius: var(--nb-r-md);
  /* No edge shadow. B1 removed the first one in 2026-07 for reading as a glow;
     the overflow-aware rebuild (2026-08-18) still read as a glow in dark, where
     --nb-ink is near-white, so it is out again — owner call. The border frames
     the table; the region stays keyboard-scrollable via tabindex, and the
     tighter gutters below and the stack (12b) mean nothing needs to scroll. */
  scrollbar-width: none;          /* Firefox */
  -ms-overflow-style: none;       /* legacy Edge */
}

/* A six-column table spends 192px of an 800px measure on cell gutters alone
   (16px each side x 12 edges). Wide tables halve the inline gutter, which
   returns ~96px to the text at every width. Vertical rhythm is untouched. */
.nb-notes .note-table-scroll:has(tr > *:nth-child(5)) th,
.nb-notes .note-table-scroll:has(tr > *:nth-child(5)) td {
  padding-inline: var(--nb-s2);
}

/* ---------- 12a · A table is never wider than the note -----------------
   (owner, 2026-10-01: "some of the tables are wider than the note ... make
   sure nothing like this ever happens again".) Up to that day a table of five
   or more columns spilled 96px past each side of the 800px measure from
   1500px and 160px from 1700px, to unwrap its cells. The audit of every note
   and summary table (tools/audit/corpus28/table_width) found all 149 of them
   fit the 800px column with nothing hidden, as they already did at 1440, so
   the spill is gone. A table's frame is the note column, or the box it sits
   in, at every width: no rule may pull it past that edge with a negative
   margin or a width over 100% (noteviewer/tests_note_tables.py). A table
   that is still too wide for its column stacks into labelled rows (12b and
   note-math-fit.js), at every width. */
/* No scrollbar on note tables (owner pick 2026-08-18). The edge shadow is the
   visible affordance; pointer users scroll by drag/wheel, keyboard users by
   focusing the region. */
.nb-notes .note-table-scroll::-webkit-scrollbar {
  display: none;
}
/* A tabbable region must show where focus is (WCAG 2.4.7). Inset so the ring
   is not clipped by the wrapper's own overflow. */
.nb-notes .note-table-scroll:focus-visible {
  outline: 2px solid var(--nb-blue);
  outline-offset: -2px;
}
.nb-notes .note-table-scroll table {
  width: 100%;
  border-collapse: collapse;
  font: var(--nb-type-prose);
}
.nb-notes .note-table-scroll th {
  background: var(--nb-paper-sunken);
  font: var(--nb-type-label);
  text-align: left;
  color: var(--nb-ink);
}
.nb-notes .note-table-scroll th strong,
.nb-notes .note-table-scroll td strong {
  font-weight: 700;
}
.nb-notes .note-table-scroll th,
.nb-notes .note-table-scroll td {
  padding: var(--nb-s3) var(--nb-s4);
  border-bottom: 1px solid var(--nb-border);
  vertical-align: top;
}
.nb-notes .note-table-scroll tr:last-child td {
  border-bottom: none;
}
/* B2: restore the row hover-highlight the legacy noteviewer.css had (dropped in
   the notebook skin). A subtle royal-blue wash with a short transition; the
   accent token is theme-aware, so it reads correctly in light and dark. */
.nb-notes .note-table-scroll tbody td {
  transition: background-color 120ms ease;
}
.nb-notes .note-table-scroll tbody tr:hover td {
  background-color: color-mix(in srgb, var(--nb-adm-key) 8%, transparent);
}
.nb-notes .note-table-scroll td p {
  margin: 0;
}
/* In-cell math legibility: KaTeX stacks inline \frac numerators/denominators
   at ~0.7x, which at table-cell size makes π/2-style fractions illegible.
   KaTeX's own sheet sets .katex to 1.21em, so this value REPLACES that one —
   1.45em ≈ a 1.2x bump over the default, lifting fraction digits back to
   body-text size. Scoped to table cells only; display equations and prose
   math keep their current size. */
.nb-notes .note-table-scroll th .katex,
.nb-notes .note-table-scroll td .katex {
  font-size: 1.45em;
}

/* ---------- 13 · Figures -----------------------------------------------
   A hairline frame on raised paper; the caption is a serif-italic
   margin note. */
.nb-notes .note-figure {
  margin: var(--nb-s7) 0;
  padding: var(--nb-s4);
  background: var(--nb-paper-raised);
  border: 1px solid var(--nb-border-figure);
  border-radius: var(--nb-r-md);
}
.nb-notes .note-figure-img-area img {
  display: block;
  max-width: 100%;
  height: auto;
  margin: 0 auto;
  border-radius: var(--nb-r-sm);
}
.nb-notes .note-figure-caption {
  margin-top: var(--nb-s3);
  font-family: var(--nb-serif);
  font-style: italic;
  font-size: var(--nb-text-sm);
  line-height: 1.5;
  text-align: center;
  color: var(--nb-ink-2);
}
/* Figures inside the working boxes (worked example / core practical /
   practical note / derivation) rendered visibly smaller than open-page
   figures: box padding (24px each side) plus figure padding (16px each side)
   cost the image 80px of width. Thin the frame padding and pull the frame
   back toward the box edges -- the boxes all share 24px (--nb-s6) horizontal
   padding, so calc(-1 * s6 + s2) leaves a constant 8px inset inside the box
   border (never overflows it). Figure radius (r-md 8px) stays inside the box
   radius (r-lg 12px). Vertical margins keep the .note-figure default. */
.nb-notes :is(.note-worked-example,
              .note-practical,
              .note-practical-note,
              .note-derivation) .note-figure {
  padding: var(--nb-s2);
  margin-left: calc(-1 * var(--nb-s6) + var(--nb-s2));
  margin-right: calc(-1 * var(--nb-s6) + var(--nb-s2));
}

/* 13b: ENLARGE A DIAGRAM (audit N-013, 2026-09-25). A labelled diagram drawn
   to a 360px phone's column is about 21% of its pixels, and its labels come
   out 4 to 5px tall. Where a figure can be that small (860px and narrower,
   or any touch screen) and is drawn smaller than the desk draws it,
   note-figure-zoom.js makes the diagram itself the control: a tap on it
   opens the diagram at the desk's own drawn width in a dialog, to pan
   across and pinch. No button is drawn under the figure (owner, 2026-09-27:
   "remove the arrow ... they click on the image itself"); the image takes
   a zoom-in cursor and a blue focus ring for the keyboard. The dialog is
   only ever opened there, so the desk is unchanged. */
@media (max-width: 860px), (pointer: coarse) {
  .nb-notes .note-figure[data-zoom="on"] .note-figure-img-area img { cursor: zoom-in; }
}
.nb-notes .note-figure[data-zoom="on"] .note-figure-img-area img:focus-visible,
.nb-figure-zoom-close:focus-visible {
  outline: 2px solid var(--nb-blue);
  outline-offset: 2px;
}
.nb-figure-zoom-pan:focus-visible {
  outline: 2px solid var(--nb-blue);
  outline-offset: -2px;
}
/* The dialog: the whole screen, on the page's own paper. Its display is set
   only while it is open, or it would override the closed dialog's none. */
.nb-figure-zoom[open] {
  position: fixed;
  inset: 0;
  display: flex;
  flex-direction: column;
  inline-size: 100%;
  block-size: 100%;
  max-inline-size: none;
  max-block-size: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: var(--nb-paper);
  color: var(--nb-ink);
}
.nb-figure-zoom::backdrop { background: var(--nb-paper); }
.nb-figure-zoom-bar {
  flex: none;
  display: flex;
  justify-content: flex-end;
  padding: calc(var(--nb-s2) + env(safe-area-inset-top, 0px)) calc(var(--nb-s3) + env(safe-area-inset-right, 0px)) var(--nb-s2) var(--nb-s3);
  border-bottom: 1px solid var(--nb-border);
}
.nb-figure-zoom-close {
  display: inline-flex; /* flex centering: grid does not centre a <button> */
  align-items: center;
  justify-content: center;
  inline-size: 44px;
  block-size: 44px;
  padding: 0;
  /* the one dialog's close button (nb-dialog.css; owner, 2026-10-23): a quiet tint, no edge, no capsule */
  background: var(--nb-btn-quiet, transparent);
  border: 0;
  border-radius: var(--nb-r-md);
  color: var(--nb-ink);
  cursor: pointer;
}
/* The pan box: a grid, so the diagram centres while it is smaller than the
   box and starts at the box's edge (no unreachable overflow) once it is
   larger. Pinch zooms, and the page behind never scrolls with it. */
.nb-figure-zoom-pan {
  flex: 1 1 auto;
  min-block-size: 0;
  display: grid;
  overflow: auto;
  overscroll-behavior: contain;
  touch-action: pan-x pan-y pinch-zoom;
  padding: var(--nb-s4);
}
.nb-figure-zoom-img {
  display: block;
  margin: auto;
  max-inline-size: none;
  block-size: auto;
  border-radius: var(--nb-r-sm);
}
html.nb-figure-zoom-open body { overflow: hidden; }

/* Safety net — any image the figure wrapper did NOT catch. The renderer only
   wraps a STANDALONE `<p><img></p>` in `.note-figure`; an image glued into a
   heading (`### ![](x.png)Title` → `<h4><img>Title</h4>`), sitting inline in a
   paragraph, or sharing a paragraph with siblings stays a bare <img>. Without
   the legacy `.note-body img` rule on this page, those escape ALL sizing and
   render at raw intrinsic size ("full expanded"). Constrain + skin them to the
   figure look so every image reads consistently. The `.note-figure-img-area
   img` rule above (its parent excluded by :not) keeps its in-frame treatment. */
.nb-notes img {
  max-width: 100%;
  height: auto;
}
.nb-notes :not(.note-figure-img-area) > img {
  display: block;
  margin: var(--nb-s5) auto;
  padding: var(--nb-s2);
  background: var(--nb-paper-raised);
  border: 1px solid var(--nb-border-figure);
  border-radius: var(--nb-r-sm);
  box-shadow: var(--nb-shadow-1);
}
/* A heading-glued image becomes a block above its heading text — the diagram
   reads as a figure, the title follows underneath, both inside the heading. */
.nb-notes :is(h1, h2, h3, h4, h5, h6) > img {
  margin-bottom: var(--nb-s3);
}

/* ---------- 14 · Math, chemistry, rules, links -------------------------- */
.nb-notes .katex-display {
  margin: var(--nb-s5) 0;
  padding: var(--nb-s2) var(--nb-s5) var(--nb-s2) 0;
  overflow-x: auto;
  overflow-y: hidden;
  /* The edge scroll-shadow (paper covers + ink gradients, same machinery B1
     removed from tables) painted faint vertical smudges over the dotted paper
     at both equation edges even with nothing to scroll. Removed — equations
     sit directly on the paper; overflow-x still lets wide ones scroll. */
}
/* A display equation too wide for its column breaks onto a second line, and
   ONLY between KaTeX's .base boxes: the break points KaTeX itself offers
   after a relation or an operator (=, +, -, x, arrows), the same places inline
   maths already breaks. Every .base keeps KaTeX's own white-space: nowrap, so
   a number, its unit and the unit's power always stay together. Anything
   that still cannot fit (one .base wider than the column, a long fraction)
   scrolls inside its own .katex-display (overflow-x above), never the page.

   Owner list 2026-09-24, item 3 (on mobile the units mess up the spacing).
   The rule this replaces lived in the 480px block below and ALSO set
   white-space: normal on .text / .mord.text, i.e. INSIDE the .base. KaTeX
   sizes every .base at width: min-content, so a base allowed to wrap inside
   collapses to its narrowest line: \text{useful energy output} = 12 J became
   the words over a lone '=' with a hole before '12 J', and 2.00 x 10^-4 m^3
   tore its unit into '10^-4 m' over a dropped '3'. KaTeX writes the spaces
   inside \text{} as U+00A0, so the old rule never wrapped a single word
   either; it only ever broke equations apart.
   All widths, not phones only: at 1440 the IGCSE CIE 5.1 bond-energy
   equation was sliced 57px short at the column edge by KaTeX's display
   nowrap.
   This wrap is the floor, not the layout: it is what a reader gets before
   note-math-fit.js runs, or without it. text-wrap: balance evens the lines
   where it can. It cannot choose WHERE to break well (a lone '100' left
   under a fraction, a x 10^n torn from its power of ten), so the script
   lays out every display that does not fit on one line (below). */
.nb-notes .katex-display > .katex {
  white-space: normal;
  text-wrap: balance;
}
/* The lines note-math-fit.js chose: one nowrap block per line, so each break
   falls exactly where the script put it, and a gap between lines only, so a
   left-hand side such as 'efficiency =' no longer sits on the numerator
   below it (measured 2026-09-24: 0px between every wrapped line at 390 and
   360). A single-line display gets no gap and keeps its height. */
.nb-notes .katex-display .nb-math-line {
  display: block;
  white-space: nowrap;
}
.nb-notes .katex-display .nb-math-line + .nb-math-line {
  margin-top: var(--nb-s2);
}
@media (max-width: 480px) {
  /* Substitution lines inside the working boxes are the one shape that still
     scrolls at 390px after the prose-in-math repairs: a fraction whose
     numerator is a run of substituted constants
     (F = (6.67e-11)(5.97e24)(7.35e22) / (3.84e8)^2) has no wrap point at all,
     so overflow-x above is the only thing holding it.

     The size itself lives in --nb-we-math-size (notebook-tokens.css) with the
     measurement behind the 0.85 recorded there; component sheets carry no raw
     font sizes. Dropping the boxes' inline padding instead was measured first
     and fixes ZERO blocks — the box is not padding-limited.

     Scoped to the working boxes on purpose: open-column and callout equations
     keep full size. The few that still scroll are deep fractions that want a
     content-side split of the substitution line, not more shrinking. */
  .nb-notes :is(.note-worked-example-body,
                .note-derivation-body,
                .note-practical-body) .katex-display .katex {
    font-size: var(--nb-we-math-size);
  }
}
/* ⚑ ON A PHONE A DISPLAY NEVER SCROLLS (The No-Sideways Rule, DESIGN.md;
   owner, 2026-09-27: "i dont want this happening anywhere in mobile mode").
   Below 861px note-math-fit.js sets every display that does not fit on
   lines and, only for a piece that cannot break, a smaller size, so the
   scroll box above has nothing left to hold: overflow is visible on BOTH
   axes (overflow-x: visible beside overflow-y: hidden computes to auto,
   which is a scroller again), and the right padding that cleared the
   scroll edge goes, so the equation sits on the centre line. A word, a URL
   or a code run longer than its line breaks inside itself (break-word
   changes no min-content, so no label starts breaking mid-word); KaTeX
   never does (anywhere would collapse its min-content boxes to one glyph a
   line). Tables keep their own wrap (--nb-tbl-wrap, section 12b).
   An aligned or hanging layout (data-math-align, set by the script) sets
   its lines left inside one block as wide as the widest line, and that
   block is centred on the column like every display. */
@media (max-width: 860px) {
  .nb-notes .katex-display {
    overflow: visible;
    padding-inline: 0;
  }
  .nb-notes .katex-display[data-math-align] > .katex > .katex-html {
    display: inline-block;
    text-align: left;
  }
  .nb-notes {
    overflow-wrap: break-word;
  }
  .nb-notes .katex {
    overflow-wrap: normal;
  }
  .nb-notes pre {
    white-space: pre-wrap;
    overflow-wrap: anywhere;
  }
  .nb-notes code {
    overflow-wrap: anywhere;
  }
}
.nb-notes .esa-chem {
  font-family: inherit; /* chemistry terms are content, not UI */
}
.nb-notes hr {
  height: 1px;
  margin: var(--nb-s7) 0;
  border: none;
  background: var(--nb-border);
}
/* content links write in the pen: underlined, low stroke */
.nb-notes p a,
.nb-notes li a,
.nb-notes td a {
  color: var(--nb-blue);
  text-decoration: underline;
  text-decoration-thickness: 1.5px;
  text-underline-offset: 3px;
  transition: color var(--nb-dur-1) var(--nb-ease);
}
.nb-notes p a:hover,
.nb-notes li a:hover,
.nb-notes td a:hover {
  color: var(--nb-blue-strong);
}

/* ---------- 15 · End-of-note chrome --------------------------------------
   Practice CTA, prev/next, and the paywall's resting place. */
.nb-notes .nb-notes-practice {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--nb-s3);
  margin: var(--nb-s8) 0 0;
  padding: var(--nb-s4) var(--nb-s5);
  background: var(--nb-paper-sunken);
  border: 1px solid var(--nb-border);
  border-radius: var(--nb-r-lg);
}
.nb-notes .nb-notes-practice-label {
  margin-right: var(--nb-s2);
  font-size: var(--nb-text-xs);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--nb-ink-2);
}
.nb-notes .nb-notes-practice-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--nb-s2);
}

.nb-notes .nb-notes-pagination {
  display: flex;
  justify-content: space-between;
  gap: var(--nb-s4);
  margin-top: var(--nb-s7);
  padding-top: var(--nb-s5);
  border-top: 1px solid var(--nb-border);
}
.nb-notes .nb-pagenav {
  flex-direction: column;
  align-items: flex-start;
  gap: var(--nb-s1);
  max-width: 48%;
  white-space: normal; /* topic titles wrap; nb-btn's nowrap is for labels */
  text-align: left;
}
.nb-notes .nb-pagenav--next {
  align-items: flex-end;
  text-align: right;
  margin-left: auto;
}
.nb-notes .nb-pagenav-dir {
  font-size: var(--nb-text-sm);
  font-weight: 600;
}
.nb-notes .nb-pagenav-title {
  font-size: var(--nb-text-base);
  font-weight: 500;
  line-height: 1.4;
  color: var(--nb-ink-2);
}
.nb-notes .nb-pagenav:hover .nb-pagenav-title {
  color: var(--nb-ink);
}

/* the sealed sheet centres itself in either gating branch */
.nb-notes .nb-paywall-wrap {
  display: flex;
  justify-content: center;
  margin: var(--nb-s8) 0;
}
/* the sheet keeps its own voice — undo the article's heading/list rhythm */
.nb-notes .nb-paywall-title {
  margin: 0;
  padding: 0;
  border: none;
  font: var(--nb-type-editorial);
  letter-spacing: normal;
}
.nb-notes .nb-paywall p {
  margin: 0;
}
.nb-notes .nb-paywall ul {
  padding: 0;
}
.nb-notes .nb-paywall li {
  margin: 0;
}
/* …then restore the sheet's own rhythm (the article rules above would
   otherwise flatten the demo's spacing). p.nb-paywall-lede, (0,3,1): the
   bare .nb-paywall-lede (0,2,0) lost to the p reset above (0,2,1), so the
   lede sat flush under the title and the login sheet's sign-ups-paused line
   flush under its button (review 2026-09-28). */
.nb-notes .nb-paywall p.nb-paywall-lede {
  margin-top: var(--nb-s3);
}
.nb-notes .nb-paywall-list {
  margin: var(--nb-s5) 0 0;
}

/* ---------- 16 · Small screens ------------------------------------------- */
@media (max-width: 860px) {
  .nb-notes h2 {
    margin-top: var(--nb-s7);
  }
  .nb-notes h3 {
    margin-top: var(--nb-s7);
  }
  .nb-notes .note-worked-example,
  .nb-notes .note-review-section {
    padding: var(--nb-s4) var(--nb-s5);
  }
  /* Mirror the in-box figure pull-back to the narrower s5 box padding so the
     8px inset inside the worked-example border holds at phone width too. */
  .nb-notes .note-worked-example .note-figure {
    margin-left: calc(-1 * var(--nb-s5) + var(--nb-s2));
    margin-right: calc(-1 * var(--nb-s5) + var(--nb-s2));
  }
  .nb-notes .nt-objectives-card {
    padding: var(--nb-s4) var(--nb-s5);
  }
  .nb-notes .nb-notes-pagination {
    flex-direction: column;
  }
  .nb-notes .nb-pagenav,
  .nb-notes .nb-pagenav--next {
    max-width: 100%;
  }
}

/* At phone width the table can breathe less — tighten the cell rhythm,
   allow hyphenation, and hold columns to a sane floor so long
   words wrap cleanly instead of shredding into one-letter-per-line columns.
   The scroll-shadow affordance above signals the remaining overflow. */
@media (max-width: 480px) {
  .nb-notes .note-table-scroll th,
  .nb-notes .note-table-scroll td {
    padding: var(--nb-s2) var(--nb-s3);
    hyphens: auto;
    -webkit-hyphens: auto;
    overflow-wrap: break-word;
  }
  .nb-notes .note-table-scroll td {
    min-width: 6.5em;
  }
}

/* ---------- 12b · Tables: stacked rows when columns get too narrow -------
   PORTED 2026-08-18 from noteviewer.css:11444, which has had this working on
   the legacy rail for months but was never added to the sheet the notebook
   page loads. The renderer has been writing the data-label attributes all
   along (rendering.py wrap_tables_for_scroll); nothing here consumed them.

   WHY A TIERED BREAKPOINT AND NOT THE LEGACY BLANKET 520px
   Measured across all 2,586 corpus tables at the real container widths: a
   column stops holding a short phrase on one line at about 130px. Below that,
   p90 rows jump from ~2.5 lines to 4-6.7. The notebook reading column is
   798px on desktop, so px-per-column decides this, not viewport:

       cols   needs container   stacks below viewport
        3         390px                430px
        4         520px                560px
        5         650px                700px
        6         780px                820px
        7+        910px                always (798px desktop never suffices)

   A 520px blanket would have left 5 of the 12 six-column and 31 of the 53
   five-column tables crushed. One- and two-column tables never stack by
   column count: stacking a 2-column table only moves the value from a 50%
   cell into a 64% one, which buys nothing. On a phone, though, ANY table
   that would otherwise scroll stacks (data-nb-fit="stack", after the 430px
   tier below; The No-Sideways Rule).

   HOW THE SWITCH WORKS
   Each tier sets the same eight custom properties on the wrapper; the single
   recipe below reads them, with the un-stacked value as every fallback. That
   keeps one copy of the stack rules instead of five near-identical media
   blocks, and a table that trips no tier renders byte-identically to today.

   ACCESSIBILITY NOTE: display:block drops the native table role, so the
   column header is re-announced per cell from data-label via ::before. This
   is the legacy behaviour being ported, not a new decision. */
.nb-notes .note-table-scroll:has(tr > *:nth-child(7)) {
  --nb-tbl-box: block;
  --nb-tbl-cell: grid;
  --nb-tbl-thead: none;
  --nb-tbl-label: block;
  --nb-tbl-title: block;
  --nb-tbl-title-font: var(--nb-type-title);
  --nb-tbl-title-bg: var(--nb-paper-sunken);
  --nb-tbl-card: 1px solid var(--nb-border);
  --nb-tbl-card-bg: var(--nb-paper-raised);
  --nb-tbl-bb-lastcell: 0;
  --nb-tbl-bb-lastrow: 1px solid var(--nb-border);
  --nb-tbl-wrap: anywhere;
}
@media (max-width: 820px) {
  .nb-notes .note-table-scroll:has(tr > *:nth-child(6)) {
    --nb-tbl-box: block;
    --nb-tbl-cell: grid;
    --nb-tbl-thead: none;
    --nb-tbl-label: block;
    --nb-tbl-title: block;
    --nb-tbl-title-font: var(--nb-type-title);
    --nb-tbl-title-bg: var(--nb-paper-sunken);
    --nb-tbl-card: 1px solid var(--nb-border);
    --nb-tbl-card-bg: var(--nb-paper-raised);
    --nb-tbl-bb-lastcell: 0;
    --nb-tbl-bb-lastrow: 1px solid var(--nb-border);
    --nb-tbl-wrap: anywhere;
  }
}
@media (max-width: 700px) {
  .nb-notes .note-table-scroll:has(tr > *:nth-child(5)) {
    --nb-tbl-box: block;
    --nb-tbl-cell: grid;
    --nb-tbl-thead: none;
    --nb-tbl-label: block;
    --nb-tbl-title: block;
    --nb-tbl-title-font: var(--nb-type-title);
    --nb-tbl-title-bg: var(--nb-paper-sunken);
    --nb-tbl-card: 1px solid var(--nb-border);
    --nb-tbl-card-bg: var(--nb-paper-raised);
    --nb-tbl-bb-lastcell: 0;
    --nb-tbl-bb-lastrow: 1px solid var(--nb-border);
    --nb-tbl-wrap: anywhere;
  }
}
@media (max-width: 560px) {
  .nb-notes .note-table-scroll:has(tr > *:nth-child(4)) {
    --nb-tbl-box: block;
    --nb-tbl-cell: grid;
    --nb-tbl-thead: none;
    --nb-tbl-label: block;
    --nb-tbl-title: block;
    --nb-tbl-title-font: var(--nb-type-title);
    --nb-tbl-title-bg: var(--nb-paper-sunken);
    --nb-tbl-card: 1px solid var(--nb-border);
    --nb-tbl-card-bg: var(--nb-paper-raised);
    --nb-tbl-bb-lastcell: 0;
    --nb-tbl-bb-lastrow: 1px solid var(--nb-border);
    --nb-tbl-wrap: anywhere;
  }
}
@media (max-width: 430px) {
  .nb-notes .note-table-scroll:has(tr > *:nth-child(3)) {
    --nb-tbl-box: block;
    --nb-tbl-cell: grid;
    --nb-tbl-thead: none;
    --nb-tbl-label: block;
    --nb-tbl-title: block;
    --nb-tbl-title-font: var(--nb-type-title);
    --nb-tbl-title-bg: var(--nb-paper-sunken);
    --nb-tbl-card: 1px solid var(--nb-border);
    --nb-tbl-card-bg: var(--nb-paper-raised);
    --nb-tbl-bb-lastcell: 0;
    --nb-tbl-bb-lastrow: 1px solid var(--nb-border);
    --nb-tbl-wrap: anywhere;
  }
}

/* ⚑ AT EVERY WIDTH ANY TABLE THAT WOULD STILL SCROLL STACKS (The No-Sideways
   Rule on a phone; on the desk since 2026-10-01, owner: "make sure nothing
   like this ever happens again"). The tiers above stack by column count, but
   they are keyed to the viewport and the column is not: at 1024 the rail
   leaves the note 604px, so a six-column table that needs 729px sat in a
   602px frame with its last column cut off (IGCSE CIE Physics 1.7.3), and
   six tables like it were cut by 7 to 127px. One that is still wider than
   its column after the tiers, at any width, is marked data-nb-fit="stack" by
   note-math-fit.js and takes the same stacked layout here. So the rule
   "one- and two-column tables never stack" now reads: never by column
   count; any table that would otherwise scroll stacks. */
.nb-notes .note-table-scroll[data-nb-fit="stack"] {
  --nb-tbl-box: block;
  --nb-tbl-cell: grid;
  --nb-tbl-thead: none;
  --nb-tbl-label: block;
  --nb-tbl-title: block;
  --nb-tbl-title-font: var(--nb-type-title);
  --nb-tbl-title-bg: var(--nb-paper-sunken);
  --nb-tbl-card: 1px solid var(--nb-border);
  --nb-tbl-card-bg: var(--nb-paper-raised);
  --nb-tbl-bb-lastcell: 0;
  --nb-tbl-bb-lastrow: 1px solid var(--nb-border);
  --nb-tbl-wrap: anywhere;
}

/* The recipe. Inert until a tier above trips: every fallback is the value the
   table already had. Colours are notebook tokens, never the legacy sheet's
   hardcoded rgba() — notebook-notes.css is under the zero-raw-color ratchet
   (pages/tests_notebook.py NotebookCssDisciplineTests).

   The wrapper's own `overflow-x: auto` (section 12 above) is left alone: a
   stacked table fits its container by construction, so auto shows nothing.
   tests_mathml_overflow also requires that shell to stay one isolated block
   with the literal value. */
.nb-notes .note-table-scroll > table {
  display: var(--nb-tbl-box, table);
}
.nb-notes .note-table-scroll > table tbody {
  display: var(--nb-tbl-box, table-row-group);
}
.nb-notes .note-table-scroll > table thead {
  display: var(--nb-tbl-thead, table-header-group);
}
/* One card per record. margin / border-radius / overflow are inert on a
   table-row, so only the border and the fill need the switch. */
.nb-notes .note-table-scroll > table tr {
  display: var(--nb-tbl-box, table-row);
  margin: 0 0 var(--nb-s3);
  border: var(--nb-tbl-card, 0);
  border-radius: var(--nb-r-md);
  overflow: hidden;
  background: var(--nb-tbl-card-bg, transparent);
}
.nb-notes .note-table-scroll > table tr:last-child {
  margin-bottom: 0;
}
/* ⚑ THE LABEL TRACK IS NEVER NARROWER THAN ITS LONGEST WORD (audit N-014,
   2026-09-25). At 36% of a phone card (93.6px at 320, 108px at 360) the
   uppercase label broke inside a word: "GRAVITATION / AL FORCE",
   "ELECTROSTAT / IC FORCE". min-content with the label's break-word
   (below) is its longest word, so the track grows to hold it and the label
   breaks between words; wherever the word fits (every width from 390 up,
   and every desktop card) the track is 36%, exactly as before. */
.nb-notes .note-table-scroll > table td {
  display: var(--nb-tbl-cell, table-cell);
  grid-template-columns: minmax(min-content, 36%) minmax(0, 1fr);
  gap: var(--nb-s2);
  align-items: start;
  overflow-wrap: var(--nb-tbl-wrap, normal);
}
/* Field separators. Four rules in cascade order, because this block sits
   AFTER the base `tr:last-child td { border-bottom: none }` and would
   otherwise put a 1px stub back under every table's bottom-right cell.
     table mode : every cell ruled, last ROW bare
     stacked    : every field ruled, last FIELD of each card bare
   The corner cell is bare in both, so rule 4 needs no switch. */
.nb-notes .note-table-scroll > table td:last-child {
  border-bottom: var(--nb-tbl-bb-lastcell, 1px solid var(--nb-border));
}
.nb-notes .note-table-scroll > table tr:last-child td {
  border-bottom: var(--nb-tbl-bb-lastrow, none);
}
.nb-notes .note-table-scroll > table tr:last-child td:last-child {
  border-bottom: 0;
}
.nb-notes .note-table-scroll > table td::before {
  /* attr() cannot travel through a custom property (it resolves empty), so
     the label is always authored and the switch hides it instead. */
  content: attr(data-label);
  display: var(--nb-tbl-label, none);
  min-width: 0;
  font: var(--nb-type-label);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--nb-ink-2);
  overflow-wrap: break-word;
}
/* A cell the renderer could not label (a row longer than its header row) gets
   no orphan marker — mirrors the legacy :not([data-label]) guard. */
.nb-notes .note-table-scroll > table td:not([data-label])::before {
  content: none;
}

/* The first cell of a record is its NAME, not one of its fields. Stacked, it
   headed the card as "SOLAR DEVICE / Solar cell" — a labelled row that spent
   the card's opening line restating the column header. It becomes the title
   instead: full width (display drops out of the label/value grid), one type
   step up, no marker, and the sunken ground the un-stacked header row already
   wears.

   WHY A GROUND AND NOT JUST A TYPE STEP
   Type alone had to say "header" against four identical hairlines below it,
   while the un-stacked header row says it with a filled ground (section 12,
   `th { background: var(--nb-paper-sunken) }`). Same token here, so stacked
   and un-stacked speak with one voice. The pairing carries its own contrast:
   sunken against the card's --nb-paper-raised, closed by the var(--nb-border)
   hairline the cell already carries from section 12.

   THREE THINGS THIS RULE DELIBERATELY DOES NOT DO
   No border-radius: the tr above sets border-radius var(--nb-r-md) with
   overflow: hidden, so the ground clips to the card's top corners already.
   No padding: the cell keeps its section-12 td padding, which the ground now
   makes visible — that is the point.
   No border: the bottom hairline is section 12's and stays.

   Ink needs no switch either — td already inherits var(--nb-ink) from
   .nb-notes; the ink-2 in this section belongs to the marker alone.

   background-COLOR, not the shorthand: the hover wash in section 12
   (`tbody tr:hover td`) is a background-color on a higher-specificity rule,
   and the shorthand would not compose predictably beside it. Hover still
   wins, so a hovered card washes its title with the rest — unchanged
   behaviour.

   Every fallback is today's un-stacked computed value, not an approximation:
     display     table-cell  — what `var(--nb-tbl-cell, table-cell)` resolves
                               to un-stacked on this same cell.
     font        var(--nb-type-prose)
                             — the table sets `font: var(--nb-type-prose)` and
                               td declares no font, so td INHERITS exactly
                               this; re-declaring it computes identically.
     background  transparent — no rule paints a td background at rest (only
                               the wrapper, th, the hover wash and the tr
                               itself do), and the tr fill is itself
                               var(--nb-tbl-card-bg, transparent).
   data-label stays on the cell: the un-stacked cascade and noteviewer.css both
   read it. Only the marker is suppressed, and un-stacked it was already
   display:none, so that rule is inert there too. */
.nb-notes .note-table-scroll > table td:first-child {
  display: var(--nb-tbl-title, table-cell);
  font: var(--nb-tbl-title-font, var(--nb-type-prose));
  background-color: var(--nb-tbl-title-bg, transparent);
}
.nb-notes .note-table-scroll > table td:first-child::before {
  display: none;
}

/* Display math ($$…$$) is centred throughout the notebook — in the open page
   (Key Equations, derivations in prose) as well as inside the working boxes
   (owner pick, 2026-07-04). KaTeX's own default is centred; we simply let it. */
.nb-notes .katex-display {
  text-align: center;
  margin-left: auto;
  margin-right: auto;
}
.nb-notes .katex-display > .katex {
  text-align: center;
}
/* …EXCEPT inside the working boxes (owner pick, 2026-07-03 — "Format 1
   centered ledger"): worked-example / derivation / practical working-out
   reads as a solution, so equations centre on quiet hairline ledger rows. */
.nb-notes :is(.note-worked-example-body,
              .note-derivation-body,
              .note-practical-body) .katex-display {
  text-align: center;
  margin-left: auto;
  margin-right: auto;
  padding: 10px 0 12px;
  border-bottom: 1px solid color-mix(in srgb, var(--nb-ink) 12%, transparent);
}
.nb-notes :is(.note-worked-example-body,
              .note-derivation-body,
              .note-practical-body) .katex-display > .katex {
  text-align: center;
}

/* Step kickers — bare Given / Working / Equation-used labels above their
   equations. They were spoken in the illustrative-example periwinkle-slate,
   which by night (#9AA6D0) is the "weird bluish, purple gray" the owner
   ruled out (2026-09-26, one ink); a kicker is not a callout's identity, so
   it is the full ink now, and the uppercase, the tracking and the label's
   600 alone segment the step (no accent tick — it read as a stray hyphen
   before the label). */
.nb-notes .note-step-label {
  margin: var(--nb-s5) 0 2px;
  font: var(--nb-type-label);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--nb-ink);
}
/* When the kicker opens its container the segment gap collapses back to
   the old quiet rhythm. */
.nb-notes .note-step-label:first-child {
  margin-top: var(--nb-s3);
}

/* Result / Interpretation / Scenario callouts inside WE + derivation bodies —
   the moments a scanning student must find instantly. Result speaks fully in
   emerald (wash, label, and the equation ink itself); Interpretation is the
   quieter slate-cyan reading in study prose; Scenario is the neutral
   slate problem statement in plain body ink. */
.nb-notes .note-we-callout {
  margin: var(--nb-s3) 0 0;
  padding: var(--nb-s2) var(--nb-s3) var(--nb-s3);
}
.nb-notes .note-we-result {
  border-left: 3px solid var(--nb-adm-result);
  background: color-mix(in srgb, var(--nb-adm-result) 12%, transparent);
}
.nb-notes .note-we-interp {
  border-left: 3px solid var(--nb-adm-interp);
  background: color-mix(in srgb, var(--nb-adm-interp) 7%, transparent);
}
.nb-notes .note-we-scenario {
  border-left: 3px solid var(--nb-adm-scenario);
  background: color-mix(in srgb, var(--nb-adm-scenario) 7%, transparent);
}
.nb-notes .note-we-callout-label {
  display: block;
  margin-bottom: 2px;
  font: var(--nb-type-label);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.nb-notes .note-we-result .note-we-callout-label { color: var(--nb-adm-result); }
.nb-notes .note-we-interp .note-we-callout-label { color: var(--nb-adm-interp); }
/* scenario body stays default ink -- a problem statement, not examiner voice.
   Its label is full ink too (one ink, owner 2026-09-26): the scenario accent
   is a neutral slate that read as grey words; the box keeps its slate stripe
   and wash. */
.nb-notes .note-we-scenario .note-we-callout-label { color: var(--nb-ink); }
.nb-notes .note-we-callout-body p { margin: 0; }
.nb-notes .note-we-result .note-we-callout-body,
.nb-notes .note-we-result .note-we-callout-body .katex {
  color: var(--nb-adm-result);
}
.nb-notes .note-we-result .note-we-callout-body { font-weight: 600; }
.nb-notes .note-we-interp .note-we-callout-body {
  font: var(--nb-type-prose);
}
/* equations inside a callout: the callout is the frame — no ledger rule */
.nb-notes .note-we-callout .katex-display {
  padding: 2px 0;
  margin: 4px auto;
  border-bottom: none;
}

/* Standalone chemical equations (chemistry notebook only) — the renderer
   wraps half-equations / ionic / full equations in .note-chem-equation.
   A quiet equation plate: centred at the study-prose size, subtle
   definition-teal tint behind a hairline frame. No icon, no heavy border —
   the equation itself is the content. */
.nb-notes .note-chem-equation {
  margin: var(--nb-s3) 0 var(--nb-s4);
  /* Vertical padding is --nb-s2, one step under the table cell and level with
     the WE callout's top -- the tightest full box on this surface. That is
     deliberate and it is the floor: the plate holds ONE line, not prose, and
     at --nb-s3 a 22px text box sat in a 54px plate (41% ink). --nb-s2 takes
     the lone plate to 46px (48% ink) without going below the 4px ladder.
     Horizontal stays --nb-s4; the plate is full column width by decision. */
  padding: var(--nb-s2) var(--nb-s4);
  /* The wash reads through --nb-adm-tint like every other tint on this
     surface (5% light / 10% dark). The plate used to hardcode 6% for both
     themes and was the ONE consumer bypassing the token, which is why it
     under-read on the night desk: 6% of the accent over near-black paper is
     half the lift the callouts beside it get. Measured on --nb-paper, the
     fill goes 1.0819:1 -> 1.1540:1 dark (dL* +4.06 -> +7.06) and 1.0874:1 ->
     1.0721:1 light (dL* -3.24 -> -2.70). The light loss is deliberate and
     imperceptible: the 26% hairline is what draws the plate in daylight, and
     the token buys consistency with the thirteen other washes on the page. */
  background: color-mix(in srgb, var(--nb-adm-def) var(--nb-adm-tint),
                        var(--nb-paper));
  border: 1px solid color-mix(in srgb, var(--nb-adm-def) 26%, transparent);
  border-radius: var(--nb-r-sm);
  text-align: center;
  font-size: var(--nb-text-md);
}
.nb-notes .note-chem-equation > p { margin: 0; }
/* Display math inside a plate: the PLATE is the frame, so .katex-display's
   open-column metrics must not stack inside it -- the same neutralisation
   .note-we-callout already applies for the same reason.

   Its --nb-s5 block margin was the single largest term in a KaTeX plate:
   2 border + 12+12 padding + 20+20 MARGIN + 40.67 box = 106.67px against a
   plain-text plate's 54.05, nearly double, at 23% ink. Zeroing it plus the
   padding trim takes that plate to ~50.7px, comparable at last.

   padding-inline: 0 fixes a centring bug, not a metric: the base rule runs
   0 left / --nb-s5 right to clear the scroll edge in the open column, which
   inside a text-align: center plate pushed every equation 10px LEFT of the
   plate's centre line. Symmetric zero also gives the math 20px more room at
   360 (the .katex box goes 266 -> 286px); it does NOT shorten the scroll on
   the one plate that overflows, which the block below deals with.

   padding-block stays non-zero on purpose: .katex-display carries
   overflow-y: hidden, so a tall fraction needs the clearance. --nb-s1 is
   the smallest step that keeps it. */
.nb-notes .note-chem-equation .katex-display {
  margin: 0;
  padding-inline: 0;
  padding-block: var(--nb-s1);
}
/* Phones: the plate that cannot break. KaTeX renders the spaces inside a
   \text{} run as U+00A0, so a long single run becomes ONE atomic .base that
   no CSS can wrap. 35 of the 36 math plates in the corpus are authored with
   their \text{} runs split across subscripts, which gives KaTeX break points
   and they wrap fine. One does not: AP 4-7's neutralisation equation puts
   its whole left side in a single 33-character \text{} run, measuring
   349.73px inside a 286px box at 360 -- 64px hidden behind an overlay
   scrollbar with no affordance, the line sliced mid-glyph.

   Two routes were measured and rejected, not assumed:
     · overflow-wrap: anywhere clears the overflow at every width and then
       renders the equation ONE CHARACTER PER LINE -- it collapses
       min-content sizing across KaTeX's per-glyph spans. The metric said
       fixed; the screenshot said destroyed.
     · overflow-wrap: break-word has no effect at all here; the overflow is
       inline-block layout, not an over-long word.
     · an edge scroll-shadow is the affordance this project has already
       built and reverted TWICE -- see tests_note_tables
       test_there_is_no_edge_glow: in dark, --nb-ink is near-white and the
       shadow reads as a glow.

   What works is giving the box more room and the math the working-box size.
   --nb-we-math-size is the existing 0.85 step, and at 17.48px it still sits
   above the 17px prose around it, which is the floor that token records.
   Measured at 360: 64px hidden -> 0, nothing sliced, whole equation legible.
   At 320 (content 246px) 35px is still hidden -- a scale that cleared 320
   would need 0.70, which breaks the never-smaller-than-prose floor. The
   complete cure is splitting that one authored \text{} run in the corpus. */
@media (max-width: 480px) {
  .nb-notes .note-chem-equation {
    padding-inline: var(--nb-s2);
  }
  .nb-notes .note-chem-equation .katex-display .katex {
    font-size: var(--nb-we-math-size);
  }
}
/* A plate must tint the surface it SITS on. The rule above mixes toward
   --nb-paper, which is right on the open column and wrong inside a raised
   box: on the night desk --nb-paper (L* 4.69) is DARKER than
   --nb-paper-raised (L* 9.76) by more than the tint lifts, so the plate
   inverted -- it went 1.01 L* darker than its host card and the fill
   effectively vanished (1.0207:1), leaving only the hairline. Re-basing on
   the raised token restores it to 1.1817:1 / dL* +6.76, matching the
   open-column case. Light never showed the fault because there the paper is
   darker than the raised sheet, so the same mix happened to HELP -- which is
   why light on a raised card measures lower after this fix, 1.1477:1 ->
   1.0735:1. That is the accident being removed, not a regression: every
   plate now reads at its theme's one value (~1.072 light / ~1.16 dark)
   whatever surface it sits on, instead of two values per theme.
   The renderer only admits plates inside .note-worked-example and
   .note-practical (rendering.py _chem_plate_stack_eligible);
   .note-derivation is listed for the day that widens. */
.nb-notes :is(.note-worked-example,
              .note-derivation,
              .note-practical) .note-chem-equation {
  background: color-mix(in srgb, var(--nb-adm-def) var(--nb-adm-tint),
                        var(--nb-paper-raised));
}
/* Consecutive plates are ONE reaction ledger, not a stack of cards: AgCl,
   AgBr and AgI are a single set, and a hairline pair between each of them
   asserted a break that is not in the content. The run now paints as one
   box -- the first plate keeps the top edge and top corners, the last keeps
   the bottom, and every interior joint loses both hairlines AND the gap, so
   the equal fills abut into one continuous surface with the equations as
   ledger rows.

   "Consecutive" is adjacent ELEMENT siblings: `+` steps over the whitespace
   text node the renderer emits between plates but NOT over a <p>, so a
   paragraph between two equations still splits the group -- which is the
   behaviour the corpus needs and the reason this stays pure CSS.

   Zeroing the top margin against the previous plate's --nb-s4 bottom margin
   (16 + -16) folds the gap to nothing under BOTH margin-collapsing normal
   flow and non-collapsing flex/grid parents, the same property the old
   4px-gutter form had. */
.nb-notes .note-chem-equation + .note-chem-equation {
  margin-top: calc(0px - var(--nb-s4));
  border-top: 0;
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}
.nb-notes .note-chem-equation:has(+ .note-chem-equation) {
  border-bottom: 0;
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
}

/* Standalone typed formulas on summary leaves — the renderer tags a paragraph
   whose entire text is one typed equation (``eV = ½mv²``). Summary sources
   carry no LaTeX, so these glyphs are Unicode, not KaTeX. Centring alone left
   them reading as a stray sentence, so the equation is given the weight of a
   different KIND of object, the way KaTeX does on notes:

     · one step up the type scale, --nb-text-md -> --nb-text-lg, so it out-
       weighs the prose it sits in without competing with a heading (which
       carries the same size at weight 600).
     · --nb-s6 above and below, more than the --nb-s4 gap between paragraphs,
       so the line is separated rather than merely centred.
     · tabular, lining figures and a little tracking. This is the only
       "notation" signal available without a face: the token layer defines
       exactly two families, --nb-sans and --nb-serif, and the serif is
       reserved for the examiner voice + captions by an exact-set allowlist
       (tests_typography_stage5_wave2, test_vollkorn_is_limited_to_examiner_
       and_rare_editorial_roles). Borrowing it here would make an equation
       sound like a judgment moment, so the face stays sans. Italic was
       rejected too: it is the maths convention for VARIABLES only, and these
       lines carry units and numerals ("1 u = 1.66 × 10⁻²⁷ kg") that italic
       would set wrongly.

   Still no tint, no frame, no left rule: .note-chem-equation owns the plate
   shape, and two plate styles would give one form two meanings. */
.nb-notes .note-display-equation {
  margin: var(--nb-s6) auto;
  text-align: center;
  font-size: var(--nb-text-lg);
  line-height: 1.4;
  letter-spacing: 0.01em;
  font-variant-numeric: lining-nums tabular-nums;
}
/* Consecutive formulas are one derivation, not four unrelated statements:
   collapse the gap exactly as the chemistry plates do. The negative top
   margin folds into the previous equation's --nb-s6 (24 - 16 = 8px) under
   both margin-collapsing normal flow and non-collapsing flex/grid parents. */
.nb-notes .note-display-equation + .note-display-equation {
  margin-top: calc(var(--nb-s2) - var(--nb-s6));
}
