/* SurveyorSim product pages — shared stylesheet.
 *
 * The identity is the FIELD BOOK: the whole site is set like pages from the
 * book at the center of the product. Cream paper, the page art's own ruled
 * blue horizontals and red column rule, ink-navy annotations (#26456b is the
 * book's actual ink color in the sim), brass-stamp monospace headings, and
 * screenshots taped in like photographs. One accent: flagging-tape orange.
 *
 * Served verbatim from assets/site/ (Vite publicDir) at /site/site.css —
 * shared by about/instructors/students/play so the pages stay in step.
 * System font stacks only: the CSP is font-src 'self' data:, and a silent
 * webfont fallback is worse than a stack chosen on purpose.
 */

:root {
  --paper: #f5f0e1;
  --paper-deep: #ece4cd;
  --paper-edge: #ddd2b4;
  --ink: #26456b;        /* the field book's ink */
  --body: #33312a;
  /* 5.10:1 on --paper-deep (.colhead .note, 13.6 px) — #6d6858 was 4.39:1 there
     and failed SC 1.4.3 on the deep bands only; it passed at 4.89:1 on --paper,
     which is why it survived the last sweep. L-W4. Also lifts .stat .k, the
     .datacard heading and .booknote .who, none of which were failing. */
  /* ⚠ #5c5749 since 2026-09-23: #635e4f was 4.47:1 on a .graph LINE, where
     .colhead .note and the guide's screenshot captions would sit. LATENT: no
     graph line is drawn today (.leaf.deep's `background:` wipes .graph), so
     this is here so the grid can be switched on safely. Now 6.32:1 on
     --paper, 5.68:1 on --paper-deep, 4.55:1 where two graph lines cross.
     tests/ops/paperArtContrast.test.ts. */
  --pencil: #5c5749;
  --cover: #2c5f2d;      /* book-cover green = the app's green */
  --cover-deep: #1d3f1e;
  --flag: #d95f0a;       /* flagging tape / lath paint */
  /* --flag-deep does two jobs and both are contrast-critical, so it is tuned to
     the harder one. As TEXT it sat on --paper-deep (.colhead .no, 14.4 px bold),
     where #b34c04 measured 4.19:1 against the 4.5:1 SC 1.4.3 asks — L-W4.
     (.colhead .no and the hero kicker moved to --flag-ink on 2026-09-23: they
     sit on the paper ART, whose lines this token does not clear.) As a
     BUTTON FILL under #fff (.btn.primary, a.cta) #b34c04 was already fine at
     5.31:1. #a34503 satisfies both: 4.85:1 on the deep band, 6.16:1 under white.
     --flag itself is NOT touched: it is the decorative accent, and #d95f0a is
     load-bearing at 3.30:1 for `.hero h1 em` (27.2 px bold, 3:1 threshold) and at
     3.14:1 for the focus ring on the dark nav (SC 1.4.11). Darkening the token
     would be the wrong fix; changing which token the two button rules reach for
     is the right one. */
  --flag-deep: #a34503;
  /* TEXT PAINTED OVER THE PAPER ART (.ruled, .graph) needs a third orange.
     Measured on the built site 2026-09-23: every hero kicker crosses a rule,
     and --flag-deep on a .ruled line is 4.47:1 against the 4.5:1 SC 1.4.3
     asks (about.html's № 3 too). The paper alone was never the problem.
     --flag-deep is NOT darkened to fix it, because it is also the button fill
     and --flag-deeper must stay darker than it for the hover to read.
     #923e03: 6.33:1 on --paper, 5.23:1 on a rule, 4.56:1 on a graph crossing. */
  --flag-ink: #923e03;
  /* …and the large accent in the hero heading, for the same reason: --flag is
     3.30:1 on the paper and 2.73:1 on a rule, against 3:1 for large text.
     --flag itself stays: it is the focus ring's 3.14:1 on the dark nav.
     #cb5909: 3.71:1 on --paper, 3.07:1 on a rule. */
  --flag-display: #cb5909;
  /* Hover for anything filled with --flag-deep, so the state change still reads
     darker instead of lighter. 7.41:1 with #fff. */
  --flag-deeper: #8f3d03;
  --rule-blue: #a9c0d8;  /* the page art's ruled horizontals */
  --rule-red: #c96a5a;   /* …and its red column verticals */
  --tape: rgba(222, 205, 160, 0.75);

  --mono: ui-monospace, 'Cascadia Mono', Consolas, 'SF Mono', Menlo, monospace;
  --serif: Charter, 'Bitstream Charter', 'Iowan Old Style', Georgia, 'Times New Roman', serif;
  --hand: 'Segoe Print', 'Bradley Hand', 'Lucida Handwriting', cursive;
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  margin: 0;
  background: var(--paper);
  color: var(--body);
  font: 17px/1.65 var(--serif);
  -webkit-font-smoothing: antialiased;
}
/* height:auto is load-bearing: the pages set width/height attributes for
   layout stability, and without it max-width squashes every landscape shot
   into a portrait smear. */
img { max-width: 100%; height: auto; display: block; }
a { color: var(--ink); }

/* ---- where an in-page anchor lands, under a sticky header ---- */
/* ⚠ EVERY FRAGMENT LINK ON THIS SITE LANDED BEHIND THE NAV, and the skip link
   joined them rather than causing it. `.site-nav` is `position: sticky; top: 0`
   and nothing set a scroll offset, so a jump to `#foo` scrolls that element to
   y = 0 and the header sits on top of it. Measured after a real Tab+Enter on
   the skip link: at 375 px the `.kicker` was entirely covered and 59 of the
   `<h1>`'s 61 px with it — the heading's first line invisible. The six guide
   pages are worse: about forty table-of-contents anchors land an `<h2>` fully
   behind the header at that width, and those shipped before this.
   ⚠ THE NAV HAS NO BREAKPOINT OF ITS OWN — it wraps on its own content, so the
   heights below are MEASURED, not derived: 57 px above 732, 104 px from 732
   down to 545, 142 px at 544 and below. Each value here clears its nav by a
   few px so the target lands with a gap rather than flush. Change what the nav
   carries and these move; re-measure rather than adjusting by eye. */
html {
  scroll-padding-top: 60px;
}
@media (max-width: 732px) {
  html {
    scroll-padding-top: 108px;
  }
}
@media (max-width: 544px) {
  html {
    scroll-padding-top: 146px;
  }
}

/* ---- skip link: bypass the header (#104) ---- */
/* SC 2.4.1 BYPASS BLOCKS, LEVEL A. Every page composing src/site/nav.html made
   a keyboard user tab the brand, four nav links and the call to action before
   reaching a word of content — eleven pages, every visit. This is the first
   focusable thing on all of them.
   ⚠ IT PAINTS ITS OWN GROUND ON PURPOSE. Focused, it lands at the very top of
   the page, which on every one of these pages is the near-black `--cover-deep`
   header. Transparent, it would be paper-coloured text on a dark slab.
   Measured: --ink on --paper is 8.59:1 against SC 1.4.3's 4.5, and the block
   reads 10.34:1 against the --cover-deep header behind it. ⚠ THE FOCUS RING IS
   JUDGED AGAINST THE HEADER, NOT AGAINST THIS BLOCK — an earlier version of
   this comment said 3.30:1 "on that ground", which is --flag on --paper and the
   wrong pair: `outline-offset: 2px` puts the ring OUTSIDE the link's background
   box, so both the ring and its 2 px gap fall on the dark nav behind it. The
   real figure is --flag on --cover-deep, 3.14:1, against SC 1.4.11's 3.0 — a
   pass, and a narrower one than the number it replaces. The test computes the
   ink pair rather than trusting any of this.
   ⚠ `fixed`, NOT `absolute`. Absolute would park it above the document, so a
   user who tabs back to it after scrolling would focus something they cannot
   see. And z-index clears `.site-nav`, which is sticky at 20.
   ⚠ NO TRANSITION. A slide-in here is motion under a `prefers-reduced-motion`
   block that already exists below, and it would have to be listed there. It
   earns nothing: the link either is on screen or is not. */
.skip-link {
  position: fixed;
  top: -3.5rem;
  left: 0.5rem;
  z-index: 30;
  background: var(--paper);
  color: var(--ink);
  font: 600 0.95rem var(--mono, ui-monospace), monospace;
  padding: 0.55rem 0.9rem;
  border: 1px solid var(--paper-edge);
  border-radius: 0 0 6px 6px;
  text-decoration: none;
}
/* `:focus` as well as `:focus-visible` — this control has no other way to be
   reached, so it must appear for any focus a browser gives it. */
.skip-link:focus,
.skip-link:focus-visible {
  top: 0;
}

/* ---- the top bar: book-cover green, stamped brand ---- */

.site-nav {
  background: var(--cover-deep);
  color: #e8f2dc;
  border-bottom: 4px solid var(--flag);
  position: sticky; top: 0; z-index: 20;
}
.site-nav .wrap {
  max-width: 72rem; margin: 0 auto; padding: 0.55rem 1.25rem;
  display: flex; align-items: center; gap: 1.1rem; flex-wrap: wrap;
}
.brand {
  display: inline-flex; align-items: center; gap: 0.55rem;
  text-decoration: none; color: #e8f2dc;
  font-family: var(--mono); font-weight: 700; font-size: 1.02rem;
  letter-spacing: 0.1em; text-transform: uppercase;
}
.brand img { width: 30px; height: 30px; }
.brand b { color: #ffb26b; font-weight: 700; }
.site-nav nav { display: flex; gap: 0.15rem; flex-wrap: wrap; margin-left: auto; align-items: center; }
.site-nav nav a {
  color: #c9dbba; text-decoration: none; font-family: var(--mono); font-size: 0.78rem;
  letter-spacing: 0.06em; text-transform: uppercase; padding: 0.45rem 0.7rem; border-radius: 6px;
}
.site-nav nav a:hover { background: rgba(232, 242, 220, 0.12); color: #fff; }
.site-nav nav a[aria-current='page'] { color: #ffb26b; }
.site-nav nav a.cta {
  /* --flag-deep, not --flag: #fff on #d95f0a is 3.7557:1 and this label is
     12.48 px bold, well under the 18.66 px large-text threshold, so 4.5:1
     applies (SC 1.4.3). "Launch the sim ▸" is the single most important link
     in the nav and was the worst-measuring text on the site. M-W3. */
  background: var(--flag-deep); color: #fff; font-weight: 700; margin-left: 0.4rem;
}
.site-nav nav a.cta:hover { background: var(--flag-deeper); }

/* ---- shells ---- */

.page { max-width: 72rem; margin: 0 auto; padding: 0 1.25rem; }
.measure { max-width: 44rem; }

/* Ruled-paper band: the book's blue horizontals.
 * The red margin vertical that used to run down the left came out on
 * 2026-08-09 (Rick) — on a narrow screen it cut straight through the hero
 * copy and the buttons, and a decoration that collides with the content is
 * just noise. The horizontals carry the paper on their own. */
.ruled {
  background: repeating-linear-gradient(to bottom, transparent 0 1.85rem, rgba(169, 192, 216, 0.4) 1.85rem calc(1.85rem + 1px));
}
/* Graph-paper band (the sketch page). */
.graph {
  background:
    repeating-linear-gradient(to right, transparent 0 22px, rgba(169, 192, 216, 0.35) 22px 23px),
    repeating-linear-gradient(to bottom, transparent 0 22px, rgba(169, 192, 216, 0.35) 22px 23px),
    var(--paper-deep);
}

/* ---- hero ---- */

.hero { padding: 3.2rem 0 2.4rem; }
.hero .kicker {
  font-family: var(--mono); font-size: 0.78rem; letter-spacing: 0.22em;
  text-transform: uppercase; color: var(--flag-ink); margin: 0 0 0.7rem;
}
.hero h1 {
  font-family: var(--mono); font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.045em; line-height: 1.12; text-wrap: balance;
  font-size: clamp(1.7rem, 4.2vw, 3rem); color: var(--cover-deep); margin: 0 0 1rem;
}
.hero h1 em { font-style: normal; color: var(--flag-display); }
.hero .lede { font-size: 1.18rem; max-width: 38rem; margin: 0 0 1.6rem; color: var(--body); }
.hero-grid { display: grid; grid-template-columns: minmax(0, 7fr) minmax(0, 6fr); gap: 2.5rem; align-items: center; }
@media (max-width: 860px) { .hero-grid { grid-template-columns: 1fr; } }

/* ---- buttons: lath-and-flagging ---- */

.btn-row { display: flex; gap: 0.8rem; flex-wrap: wrap; align-items: center; }
.btn {
  display: inline-block; text-decoration: none; font-family: var(--mono);
  font-weight: 700; font-size: 0.92rem; letter-spacing: 0.05em; text-transform: uppercase;
  padding: 0.78rem 1.5rem; border-radius: 3px; border: 2px solid transparent;
}
.btn.primary { background: var(--flag-deep); color: #fff; box-shadow: 3px 3px 0 var(--cover-deep); }
.btn.primary:hover { background: var(--flag-deeper); transform: translate(1px, 1px); box-shadow: 2px 2px 0 var(--cover-deep); }
.btn.quiet { border-color: var(--cover); color: var(--cover-deep); background: transparent; }
.btn.quiet:hover { background: rgba(44, 95, 45, 0.08); }

/* ---- taped-in photographs ---- */

.photo {
  margin: 0; background: #fff; padding: 10px 10px 12px;
  box-shadow: 0 8px 22px rgba(46, 40, 24, 0.28);
  transform: rotate(-1.1deg); position: relative;
}
.photo.tilt-r { transform: rotate(0.9deg); }
.photo.flat { transform: none; }
.photo::before, .photo::after {
  content: ''; position: absolute; width: 92px; height: 26px;
  background: var(--tape); box-shadow: 0 1px 3px rgba(46, 40, 24, 0.18);
  top: -12px; left: 50%;
}
.photo::before { transform: translateX(-130%) rotate(-5deg); }
.photo::after { transform: translateX(30%) rotate(4deg); }
.photo img { border: 1px solid #d7d2c2; }
.photo figcaption {
  font-family: var(--hand); font-size: 0.92rem; color: var(--ink);
  padding: 0.55rem 0.2rem 0; text-align: center;
}

/* ---- sections styled as book pages ---- */

.leaf { padding: 3rem 0; }
.leaf.deep { background: var(--paper-deep); border-top: 1px solid var(--paper-edge); border-bottom: 1px solid var(--paper-edge); }
.colhead {
  display: flex; align-items: baseline; gap: 0.9rem;
  border-top: 3px double var(--ink); border-bottom: 1px solid var(--ink);
  padding: 0.45rem 0.15rem; margin: 0 0 1.6rem;
}
.colhead h2 {
  margin: 0; font-family: var(--mono); font-size: 1.02rem; font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink);
}
.colhead .no {
  font-family: var(--mono); color: var(--flag-ink); font-size: 0.9rem; font-weight: 700;
}
.colhead .note { margin-left: auto; font-family: var(--hand); color: var(--pencil); font-size: 0.85rem; }
@media (max-width: 640px) { .colhead .note { display: none; } }

h3 {
  font-family: var(--mono); font-size: 0.92rem; letter-spacing: 0.09em;
  text-transform: uppercase; color: var(--cover-deep); margin: 1.8rem 0 0.5rem;
}

/* A HEADING THAT IS A SENTENCE (GitHub #158, Rick's ruling 2026-09-12).
 *
 * Twenty-eight questions on the two student help pages, and eight card labels
 * on three more, were block-level <b> — visually headings, structurally
 * nothing. That is WCAG Failure F2, and it left a screen-reader user six
 * landing points for twenty-one questions on the page they are sent to when
 * something has ALREADY gone wrong.
 *
 * ⚠ THE TICKET'S ONE-LINE FIX WOULD HAVE BEEN WORSE THAN THE BUG. A bare <h3>
 * inherits the rule directly above — uppercase, letter-spaced monospace — which
 * is right for the three-word labels that rule was written for ("What you tag")
 * and absurd for a conversational sentence in quotation marks:
 *
 *     "I PRESSED MEASURE AND NOTHING HAPPENED. IT ISN'T EVEN GREYED OUT."
 *
 * So `.q` resets it to what a bold sentence in body copy has always looked
 * like. Rick's ruling was that the outline changes and the page does not, and
 * `tests/ops/headingsAreHeadings.test.ts` holds this rule to it: shipping a
 * bare <h3> with no scoped reset turns that guard red.
 */
h3.q {
  font-family: inherit; font-size: inherit; font-weight: 700;
  letter-spacing: normal; text-transform: none; color: inherit;
  margin: 0.6rem 0 0;
}
/* The question and its answer were ONE paragraph joined by <br />, so they sat
 * on consecutive lines with no gap. They are two elements now and the rhythm
 * has to be put back deliberately rather than left to drift. */
h3.q + p { margin-top: 0; }

p { margin: 0.6rem 0 1rem; }

/* Benchmark-symbol list: the ⊕ the book stamps for a benchmark. */
ul.bm { list-style: none; padding: 0; margin: 0.8rem 0; }
ul.bm li { padding: 0.28rem 0 0.28rem 1.75rem; position: relative; }
ul.bm li::before {
  content: '⊕'; position: absolute; left: 0.2rem; top: 0.28rem;
  color: var(--flag-deep); font-weight: 700;
}
ul.bm li b { color: var(--cover-deep); }

/* ---- coordinate-table blocks (monospace data) ---- */

.datacard {
  background: #fff; border: 1px solid var(--paper-edge); border-left: 4px solid var(--rule-red);
  padding: 1rem 1.2rem; box-shadow: 0 3px 10px rgba(46, 40, 24, 0.08);
}
.datacard .dc-head {
  font-family: var(--mono); font-size: 0.72rem; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--pencil); margin: 0 0 0.5rem;
}
table.spec { border-collapse: collapse; width: 100%; font-size: 0.95rem; }
table.spec th, table.spec td { text-align: left; padding: 0.42rem 0.9rem 0.42rem 0; vertical-align: top; border-bottom: 1px solid rgba(169, 192, 216, 0.5); }
table.spec th { font-family: var(--mono); font-size: 0.78rem; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink); white-space: nowrap; }
table.spec td.num { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.spec-scroll { overflow-x: auto; }

/* ---- audience tickets (hub) ---- */

.tickets { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1.4rem; }
@media (max-width: 900px) { .tickets { grid-template-columns: 1fr; } }
.ticket {
  display: block; text-decoration: none; color: inherit; background: #fff;
  border: 1px solid var(--paper-edge); border-top: 6px solid var(--cover);
  box-shadow: 0 4px 14px rgba(46, 40, 24, 0.12); padding: 1.3rem 1.4rem 1.5rem;
  position: relative;
}
.ticket:hover { box-shadow: 0 8px 20px rgba(46, 40, 24, 0.2); transform: translateY(-2px); }
.ticket .t-for {
  font-family: var(--mono); font-size: 0.7rem; letter-spacing: 0.2em;
  text-transform: uppercase; color: var(--flag-deep);
}
.ticket h3 { margin: 0.35rem 0 0.6rem; font-size: 1.05rem; }
.ticket p { font-size: 0.95rem; color: var(--body); margin: 0 0 0.9rem; }
.ticket .t-go { font-family: var(--mono); font-size: 0.8rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--cover-deep); }
.ticket:hover .t-go { color: var(--flag-deep); }

/* ---- traverse divider: stations along a dashed line ---- */

.traverse { display: block; margin: 0 auto; padding: 2.2rem 0 0; max-width: 34rem; color: var(--pencil); }

/* ---- stat strip ---- */

.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(10.5rem, 1fr)); gap: 1px; background: var(--paper-edge); border: 1px solid var(--paper-edge); }
.stat { background: var(--paper); padding: 1.1rem 1.2rem; }
.stat .v { font-family: var(--mono); font-weight: 700; font-size: 1.35rem; color: var(--cover-deep); font-variant-numeric: tabular-nums; }
.stat .k { font-family: var(--mono); font-size: 0.72rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--pencil); margin-top: 0.2rem; }

/* ---- gallery rows ---- */

/* min() on the track floor, not a bare 19rem: at a 320 px viewport .page's
   1.25rem side padding leaves a 280 px content box, and a 304 px (19rem) floor
   overflowed it by 24 px — plus ~2 px from .photo's -1.1deg rotation, for the
   measured scrollWidth of 326. SC 1.4.10 is specified at 320 CSS px because
   that is a 1280 px desktop at 400% zoom, so this is a Chromebook-with-zoom
   failure as much as a phone one. min(19rem, 100%) is a no-op at every width
   where the container is already 19rem or wider. L-W3. */
.gallery { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(19rem, 100%), 1fr)); gap: 2.4rem 2rem; padding-top: 1rem; }

/* ---- pull-quote as a booked note ---- */

.booknote {
  border-left: 4px solid var(--flag); background: #fff;
  padding: 1.1rem 1.4rem; margin: 1.6rem 0; box-shadow: 0 3px 10px rgba(46, 40, 24, 0.08);
  font-size: 1.06rem;
}
.booknote .who { font-family: var(--mono); font-size: 0.75rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--pencil); margin-top: 0.5rem; }

/* ---- footer: inside back cover ---- */

.site-foot { background: var(--cover-deep); color: #c9dbba; margin-top: 3.5rem; border-top: 4px solid var(--flag); }
.site-foot .wrap { max-width: 72rem; margin: 0 auto; padding: 2rem 1.25rem 2.4rem; display: flex; gap: 2.5rem; flex-wrap: wrap; }
.site-foot .fb { font-family: var(--mono); font-size: 0.78rem; letter-spacing: 0.08em; text-transform: uppercase; }
.site-foot a { color: #e8f2dc; }
.site-foot .cols { display: flex; gap: 3rem; flex-wrap: wrap; margin-left: auto; }
.site-foot .cols div { display: flex; flex-direction: column; gap: 0.3rem; font-size: 0.9rem; }
.site-foot .cols b { font-family: var(--mono); font-size: 0.72rem; letter-spacing: 0.14em; text-transform: uppercase; color: #9fb094; margin-bottom: 0.25rem; }

/* ---- lightbox: click a screenshot to read the instrument ----------------
 * The shots are 1600x1000 and sit at ~500 px in the layout, which is fine for
 * "what does the game look like" and useless for "what does the LCD say".
 * site/lightbox.js turns every .photo into a button; this is what it wears.
 * Progressive enhancement — with JS off the figures render exactly as before. */

/* ⚠ .shot TOO, AND LEAVING IT OUT WAS A REGRESSION IN THE MAKING (#63).
   lightbox.js now takes `figure.shot` as well as `figure.photo`, so the guide
   pages get the same injected `<span class="zoom-hint">`. Every rule here was
   scoped to `.photo`, and `.shot` declares no `position` — so on a guide figure
   the hint landed as UNSTYLED, PERMANENTLY VISIBLE text under the caption: the
   feature's own label, printed as body copy, on seven figures.

   The hint is the lightbox's, so its styling belongs beside the lightbox's and
   not inside a page. Anything lightbox.js binds must be styled from here —
   `tests/ops/guideFigures.test.ts` fails if a bound class has no rule. */
.photo, .shot { position: relative; }
.photo.zoomable img,
.shot.zoomable img { cursor: zoom-in; }
.photo .zoom-hint,
.shot .zoom-hint {
  position: absolute; right: 16px; bottom: 46px; z-index: 1;
  font-family: var(--mono); font-size: 0.66rem; letter-spacing: 0.08em;
  text-transform: uppercase; color: #fff; background: rgba(20, 28, 18, 0.78);
  border-radius: 3px; padding: 0.28rem 0.5rem; opacity: 0; transition: opacity 0.15s ease;
  pointer-events: none;
}
/* A guide caption is one short handwritten line rather than the marketing
   pages' two, so the hint sits closer to the image. */
.shot .zoom-hint { bottom: 34px; }
.photo.zoomable:hover .zoom-hint,
.photo.zoomable:focus-within .zoom-hint,
.shot.zoomable:hover .zoom-hint,
.shot.zoomable:focus-within .zoom-hint { opacity: 1; }

/* DO NOT put `display` on the base rule. A closed <dialog> is hidden by the UA
   stylesheet's `dialog:not([open]) { display: none }`, and an author
   `display: flex` OVERRIDES it — leaving a transparent, full-screen, fixed
   overlay parked on top of the page, swallowing every click on the nav and
   the buttons. Shipped exactly that for ten minutes on 2026-08-09; a
   scripted `el.click()` sailed through it (synthetic events ignore
   overlays) and Playwright's actionability check is what caught it. The
   `[open]` selector is load-bearing. */
#lightbox {
  position: fixed; inset: 0; z-index: 100; flex-direction: column;
  align-items: center; justify-content: center; gap: 0.7rem; padding: 1.1rem 1rem;
  background: rgba(18, 22, 14, 0.94); border: 0;
  width: 100%; max-width: 100%; height: 100%; max-height: 100%;
}
#lightbox[open] { display: flex; }
#lightbox::backdrop { background: rgba(18, 22, 14, 0.94); }
#lightbox img {
  max-width: min(100%, 1600px); max-height: calc(100vh - 5.5rem);
  width: auto; object-fit: contain;
  border: 1px solid #4a5a40; box-shadow: 0 18px 50px rgba(0, 0, 0, 0.6); background: #000;
}
#lightbox .lb-cap {
  font-family: var(--hand); color: #e8f2dc; font-size: 1rem; text-align: center;
  max-width: 46rem; margin: 0;
}
#lightbox .lb-close {
  position: absolute; top: 1rem; right: 1rem; min-width: 44px; min-height: 44px;
  background: rgba(232, 242, 220, 0.12); color: #e8f2dc; border: 1px solid #5c9a52;
  border-radius: 6px; font: 700 1.1rem/1 var(--mono); cursor: pointer;
}
#lightbox .lb-close:hover { background: rgba(232, 242, 220, 0.24); }

/* On a phone the image already fills the width, so "max-width: 100%" would
   make the whole feature a no-op on the screen where the LCD is hardest to
   read. Render it ~2.8x the viewport instead — the same enlargement a desktop
   gets — and let the dialog pan. */
@media (max-width: 720px) {
  #lightbox { overflow: auto; justify-content: flex-start; }
  #lightbox img { max-width: none; width: min(1600px, 280vw); max-height: none; }
  #lightbox .lb-cap { position: sticky; left: 0; }
}

/* ---- misc ---- */

.center { text-align: center; }
.mt-2 { margin-top: 2rem; }
.hand-note { font-family: var(--hand); color: var(--ink); font-size: 0.95rem; }
:focus-visible { outline: 3px solid var(--flag); outline-offset: 2px; }
/* THE RING NEEDS A SECOND COLOR — one token cannot serve both grounds.
   `outline-offset: 2px` means the 2 px inside the ring is the PAGE, not the
   control, so the ring is judged against whatever band it lands on:
     --flag on --paper           3.30:1  PASS
     --flag on --cover-deep nav  3.14:1  PASS
     --flag on --paper-deep      2.96:1  FAIL (SC 1.4.11 asks 3:1)
   The deep bands are not a corner case: every page ends with a
   `.leaf.deep.center` CTA whose "Enter the field" / "Open the sim" button is
   the LAST thing a keyboard user tabs to, and the .photo lightbox buttons in
   the deep §2/§4 bands are focusable too. Darkening --flag globally is the
   wrong instrument — the M-W3 note above records why (it would push the ring
   on the dark nav below 3:1 and take `.hero h1 em` with it). So the ring
   changes token only where the ground does. --flag-deep measures 4.85:1 on
   --paper-deep, 4.25:1 on the graph band's rule lines, 5.41:1 on the .stat
   tiles' --paper fill and 6.16:1 on the white cards and photos inside those
   sections — every surface a focusable element can sit on in a deep band. */
.leaf.deep :focus-visible { outline-color: var(--flag-deep); }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .ticket:hover { transform: none; }
  .btn.primary:hover { transform: none; }
}

/* ---- print: the guides as a handout (#70) ---- */
/* THE ONLY OFFLINE PATH A STUDENT HAS. The audience is Chromebooks on a school
   network that can drop, so the guide has to survive on paper.
 *
 * ⚠ THIS SHEET IS LINKED BY ELEVEN PAGES, NOT SIX — the seven guides plus
 * about, instructors, students and play. Everything in this block is chrome that is
 * identical on all ten: the nav, the footer, the lightbox, the decorative
 * bands, the paper colour. Anything page-specific stays in that page's own
 * <style>, which loads after both sheets and therefore wins on a tie —
 * help-controls' near-black controller frame is the case that needs it.
 *
 * ⚠ AND NOT IN prompts.css. index.html links that sheet too, and the sim owns
 * its own print pipeline (src/ui/bookFacsimile.ts) whose
 * `body.book-fx-printing > * { display: none !important }` is deliberately
 * total. A print rule there would silently change what a student's printed
 * field book looks like.
 *
 * "Prints as a handout" here means PAGINATES CLEANLY AND LEGIBLY, not "fits on
 * one sheet". help-records.html is 395 source lines; docs/instructor-quickstart.md
 * is the precedent for the SHAPE of a handout, not for its length. */
@media print {
  /* class.html:565 uses 12mm for a tear-off code sheet. Body text wants more. */
  @page { margin: 16mm; }

  /* The cream paper is a screen conceit. On paper it is either ignored or a
     full-bleed tint that costs a cartridge and buys nothing. */
  html,
  body {
    background: #fff;
  }

  /* The two full-width dark slabs — #1d3f1e with light text. `.site-nav` is
     also `position: sticky`, which some engines repeat on every sheet. */
  .site-nav,
  .site-foot,
  .skip-link {
    display: none !important;
  }

  /* The enlarge dialog: `position: fixed; inset: 0` over rgba(18,22,14,0.94),
     with its caption in #e8f2dc. Printing while a screenshot is open would put
     a near-black rectangle on the paper and write the caption invisibly across
     it. */
  #lightbox {
    display: none !important;
  }

  /* ⚠ NEVER WRITE `button { display: none }` IN THIS BLOCK. lightbox.js moves
     every screenshot INSIDE a `<button class="zoom-btn">` (lightbox.js:88-89),
     so the obvious rule to copy from class.html:561 — hide the chrome, hide the
     controls — would hide the guide's pictures. The screenshots ARE the guide.
     Only the label the lightbox adds goes; the button stays and prints as the
     plain block its inline style already makes it. */
  .zoom-hint {
    display: none !important;
  }

  /* The alternating cream bands and the ruled/graph paper gradients. A browser
     drops these by default, so with backgrounds off they are already nothing —
     this says so on purpose, and covers the reader who turns backgrounds on
     and would otherwise get a tinted block per section. */
  .hero,
  .leaf,
  .leaf.deep,
  .ruled,
  .graph {
    background: #fff !important;
  }

  /* LINKS: DE-STYLE, DO NOT ANNOTATE. `content: " (" attr(href) ")"` is the
     reflex here and it is wrong for these pages. Most of every guide page's
     links are in-page `#fragment` TOC entries — a printed "(#three)" is an
     anchor name only the HTML knows — and the rest are root-relative paths a
     reader holding paper has no origin for, whose link TEXT already names the
     destination better than the slug does. The one case that would earn its
     ink, an off-site address to retype, does not occur: there is not a single
     `http` link in the body of any guide page. */
  /* ⚠ NO `.toc a` RULE HERE, AND THAT IS DELIBERATE. One lived here and could
     never take effect: every page with a table of contents declares
     `.toc a { … border-bottom: 1px dotted var(--pencil) }` in its OWN <style>,
     at equal specificity and later source order, so the page always won — the
     exact mechanism this block's header describes, applied against itself. A
     shared rule cannot reach `.toc`; if the dotted rules under the contents
     rows ever need to go, the rule belongs in each page's own print block. */
  a {
    color: #000;
    text-decoration: underline;
  }

  /* Keep a figure with its caption, a callout whole, and a heading with the
     text it introduces. `.toc li` already carries this on screen because the
     two-column list is fragile. */
  figure,
  .tip,
  .warn,
  .toc li {
    break-inside: avoid;
  }
  h1,
  h2,
  h3 {
    break-after: avoid;
  }
}
