/**
 * HAM — own-track template styles (lesson renderer + checkpoint, V11 task #4).
 *
 * Lives outside app/static/vendor/ deliberately: this file is HAM-authored,
 * not part of the inherited/SHA-256-pinned Donkey Factory design layer (see
 * repo CLAUDE.md "D2 — Extract the design layer" + VENDOR-MANIFEST.json).
 * Every rule below consumes design-system tokens (var(--*) from
 * vendor/css/styles.css) — no hardcoded color literals, per CLAUDE.md
 * "Do NOT hand-pick additional colors. ONE color only, the OKLCH engine
 * derives the rest."
 */

/* ── Token aliases (HAM-owned) ───────────────────────────────────────────── */
/* --bg2 is consumed by the vendored `.wt-confirm-pending` rule (a
   destructive-action two-step confirm state -- danger outline + a
   danger-tinted background, applied by `wtConfirm()` in app.js) at
   styles.css:1141 and :1157, but no canonical v7 token by that name has ever
   been declared in the vendored file: `grep -c -- "--bg2:"
   app/static/vendor/css/styles.css` returns 0 -- in no theme block, with no
   fallback on either `var()` call. The comment directly above that rule
   (styles.css:1062-1066) claims both it and the empty-state vignette rules
   were "rewritten to the canonical v7 token names" -- true for the vignette,
   false for this one property. That comment is left exactly as it is: it
   lives inside the SHA-256-pinned vendor file (repo CLAUDE.md D2/D8), and
   fixing it is a deliberate re-vendor event, not a side effect of this file.

   Currently DORMANT: no HAM template or JS references `.wt-confirm-pending`
   or `wtConfirm()` (verified -- `grep -rn "wt-confirm-pending|wtConfirm"`
   across app/templates and app/static/js turns up zero HAM hits), so this
   alias changes nothing visible today. It must resolve correctly BEFORE that
   rule is ever wired up, though, or the confirm-pending background will
   silently fall back to no tint at all (the exact fail-open class of bug
   this repo has been burned by before -- see CLAUDE.md "a check is not
   trusted until it has been watched to fail").

   Alias target: --bg, not a --surface-* tier. Two independent lines of
   evidence:
     1. Naming pattern -- the legacy v6->v7 shim (removed from HAM's vendored
        copy, still present upstream -- see point 2) mapped --pg ("page"
        background) -> --bg, and --s1/--s2/--s3 (surface tiers) ->
        --surface-mute/--surface/--surface-mute. --bg2 belongs to the "--bg"
        family by name, not the "--sN" family -- it reads as a second
        page-background variant, not a third surface tier.
     2. Direct upstream precedent, not just inference -- the CURRENT (not
        yet re-vendored) ~/donkeys/factory-core/templates/frontend/css/
        styles.css hit this exact same dangling `var(--bg2)` reference in
        this exact same `.wt-confirm-pending` rule during its own "task #13"
        mobile-polish backport, and resolved it as `--bg2: var(--bg);` (see
        that file's "25. Legacy token aliases" section, which explicitly
        calls out --bg2 as a "pre-existing dangling ref"). That is upstream's
        own answer to the identical question this file is answering.
   Rejected: --surface / --surface-raised (no naming-pattern support, and the
   `color-mix(danger 14%, ...)` treatment reads as tinting the base canvas,
   not an elevated card); a hardcoded color literal (forbidden outright --
   CLAUDE.md "Do NOT hand-pick colors. ONE color only, the OKLCH engine
   derives the rest").

   One :root declaration covers all 4 shipped variants (light, dark,
   hc-light, hc-dark): custom properties resolve `var()` at used-value time
   against whatever --bg's own final cascaded value is under the currently
   active .dark/.hc classes on <html> (styles.css's :root/.dark/.hc.dark/
   .hc:not(.dark) blocks, further overridden per-brand by theme-engine.js's
   injected OKLCH palette) -- no separate per-theme override of --bg2 itself
   is needed. */
:root {
  --bg2: var(--bg);
}

/* ── Skip link (keyboard a11y) ──────────────────────────────────────────── */

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  padding: 0.75rem 1.25rem;
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  font-weight: 600;
}
.skip-link:focus {
  left: 1rem;
  top: 1rem;
}

.ham-shell {
  padding-block: 1.5rem 3rem;
  padding-inline: 1rem; /* viewport gutter — cards never touch the screen edge */
}

/* The vendored .wt-card primitive ships without padding (upstream pages pad
   their own content). Give every card inside the HAM shell breathing room. */
.ham-shell .wt-card {
  padding: clamp(1.25rem, 4vw, 2rem);
}

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

.lesson-block__label {
  margin: 0;
}

.lesson-block__text {
  margin: 0;
  /* 16px minimum body text -- repo a11y floor (CLAUDE.md "Accessibility
     floor: 16px body min"). 1rem resolves to 16px at the default root
     size, and to 18px automatically under .hc (styles.css scales the root
     font-size to 112.5% in high-contrast mode). */
  font-size: 1rem;
  line-height: 1.65;
}

.lesson-block--analogy {
  padding: 1.25rem 1.5rem;
}

.lesson-block__cites {
  margin: 0;
}

.lesson-block--checkpoint {
  padding-top: 0.5rem;
}

/* ── Checkpoint ──────────────────────────────────────────────────────────── */

.checkpoint-question {
  padding: 1.25rem 1.5rem;
}

.checkpoint-question--correct {
  border-color: var(--success);
}

.checkpoint-question--incorrect {
  border-color: var(--danger);
}

/* Legend/fieldset border-intersection fix (reported bug: question stems
   "weirdly intersect the container lines" on the checkpoint page).
   Browsers give <legend> a UA-special box that straddles a fieldset's
   block-start border no matter what `display` value it's given -- verified
   by actually rendering it, not assumed. `.wt-card`'s border is visible
   (unlike a bare unstyled fieldset), so a long, wrapping question stem
   sliced right through the card's top edge. Per the fieldset/legend layout
   model, only `float` or `position: absolute/fixed` remove a legend from
   that special notch treatment back into ordinary block layout --
   `display: block` alone does not. Floating the legend full-width renders
   it as an ordinary block strictly BELOW the border/padding instead of
   overlapping it, while keeping the real <fieldset>/<legend> pairing
   screen readers use to bind the stem to its radio group (do not replace
   with a <div> -- semantics must survive this fix).

   `~ *` below (not relying on `.wt-stack-sm`'s own `> * + *` margin, which
   still applies unchanged) explicitly `clear: both` on every sibling that
   follows the floated legend -- both `.checkpoint-choices` AND the
   optional `.checkpoint-question__feedback` paragraph once a checkpoint is
   graded. This was verified necessary by actually rendering it, not
   assumed: `.checkpoint-choices` is `display: flex`, which independently
   establishes its own formatting context, and a formatting-context-
   establishing box avoids OVERLAPPING a float horizontally when the
   browser computes it has room beside the float -- but this legend is
   floated to 100% width, so there is NO room beside it, and Chromium
   computed a squeezed, ZERO-WIDTH box for `.checkpoint-choices` sitting to
   the float's right instead of pushing it below (caught only by directly
   inspecting getBoundingClientRect(), not by any existing test, since the
   only prior assertion on this selector checks height, not width).
   `clear: both` sidesteps that quirk entirely: it unconditionally pushes
   the cleared box below the float's bottom edge instead of trying to fit
   beside it. The clearfix on `.checkpoint-question` itself (below) is
   still needed on top of this so the fieldset's own auto height wraps the
   floated legend too, not just its cleared siblings. */
.checkpoint-question__stem {
  float: left;
  width: 100%;
  font-size: 1.0625rem;
  line-height: 1.55;
  font-weight: 700;
  color: var(--text);
  padding: 0;
  margin: 0 0 0.25rem;
}

.checkpoint-question__stem ~ * {
  clear: both;
}

.checkpoint-question::after {
  content: "";
  display: table;
  clear: both;
}

.checkpoint-choices {
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
}

.checkpoint-choice {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.75rem 1rem;
  cursor: pointer;
  font-size: 1rem;
  line-height: 1.45;
}
.checkpoint-choice:hover {
  border-color: var(--primary);
}

.checkpoint-choice input[type="radio"] {
  width: 1.25rem;
  height: 1.25rem;
  flex-shrink: 0;
  accent-color: var(--primary);
}

.checkpoint-choice__letter {
  font-weight: 700;
  color: var(--text-muted);
  flex-shrink: 0;
}

.checkpoint-choice__text {
  color: var(--text);
}

.checkpoint-choice--correct {
  outline: 2px solid var(--success);
  outline-offset: 2px;
  background: var(--success-soft);
}

.checkpoint-choice--incorrect {
  outline: 2px solid var(--danger);
  outline-offset: 2px;
  background: var(--danger-soft);
}

/* ── Sim single-pass "stop" boolean controls (task #12, S3 sim breadth) ──── */
/* Same label-wraps-input touch-target pattern as .checkpoint-choice above --
   click anywhere in the row to toggle, not just the small checkbox glyph. */
.sim-checkbox-field {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.75rem 1rem;
  cursor: pointer;
  font-size: 1rem;
  line-height: 1.45;
}
.sim-checkbox-field:hover {
  border-color: var(--primary);
}

.sim-checkbox-field input[type="checkbox"] {
  width: 1.25rem;
  height: 1.25rem;
  flex-shrink: 0;
  accent-color: var(--primary);
}

.checkpoint-question__feedback {
  margin: 0.25rem 0 0;
}

/* ── Readiness (V11 task #21, D7) ───────────────────────────────────────── */

.readiness-weakest__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
}

.readiness-weakest__link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  width: 100%;
  text-align: left;
}

.readiness-weakest__pct {
  font-size: 0.875rem;
}

/* ── Landing page nav ────────────────────────────────────────────────────── */
/* .wt-btn is inline-flex (shrink-to-fit): a long label makes the element
   wider than the card instead of wrapping. Make home links full-width blocks
   whose text wraps inside the container. */
.home-nav__link {
  display: flex;
  width: 100%;
  justify-content: flex-start;
  text-align: left;
  white-space: normal;
  overflow-wrap: anywhere;
  height: auto;
  padding-block: 0.625rem;
}

/* Per-unit lesson chips: a dense, wrapping grid instead of one full-width
   .home-nav__link row per lesson (35 lessons made that list very tall).
   Visual language borrows `.wt-pill` (vendor/css/styles.css §8 "Pills &
   badges" -- radius-pill, surface-mute + border, small type) but sized up
   to the 48px touch-target floor and kept on top of `.wt-btn` (still on the
   anchor's class list) so focus-visible, active-press and disabled states
   come from the shared button primitive instead of being reimplemented
   here -- same layering pattern `.home-nav__link` above already uses. */
.home-nav__chip-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.home-nav__chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4375rem;
  max-width: 22rem;
  padding: 0.5rem 0.875rem;
  border-radius: var(--radius-pill);
  border-color: var(--border);
  background: var(--surface-mute);
  white-space: normal;
  text-align: left;
  line-height: 1.3;
}

.home-nav__chip:hover {
  background: var(--primary-soft);
  border-color: var(--primary);
}

.home-nav__chip-code {
  flex: 0 0 auto;
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--primary);
}

.home-nav__chip-title {
  font-size: 0.8125rem;
}

/* Curriculum-unit sub-headings inside the Theory section (e.g. "Welcome to
   Amateur Radio", "Staying Safe") -- one step down from .wt-eyebrow's
   section-level weight (used for "Theory" itself), so the hierarchy reads
   Theory > unit > lesson rather than two identical-looking label sizes. */
.home-nav__unit {
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-block: 0.5rem 0.125rem;
}

.home-nav__unit:first-of-type {
  margin-block-start: 0;
}

/* ── Scroll reveal (landing page step/activity cards) ───────────────────── */
/* Additive + media-gated, deliberately double-guarded (see
   app/static/js/scroll-reveal.js's module docstring, DEVIATION 2): the
   hidden state below only ever applies to an element that ALSO carries
   `.ham-reveal--pending`, a class scroll-reveal.js adds to itself, and only
   after it has already confirmed IntersectionObserver exists and
   `prefers-reduced-motion` is not "reduce". Wrapping the rule in the same
   media query here is a second, independent guard -- if a future bug in
   that script ever applied `.ham-reveal--pending` outside those
   conditions, a reduced-motion user would still see fully visible,
   unanimated content, because this whole block simply would not apply.
   Absent BOTH the class and the media match, `.ham-reveal` alone carries
   no styling at all: the default state is always visible, never
   opacity: 0 unconditionally (the exact failure pattern the task brief
   warns against -- content invisible forever if the observer never
   fires). */
@media (prefers-reduced-motion: no-preference) {
  .ham-reveal.ham-reveal--pending {
    opacity: 0;
    transform: translateY(0.75rem);
    transition: opacity 0.4s ease-out, transform 0.4s ease-out;
  }

  .ham-reveal.ham-reveal--pending.ham-reveal--visible {
    opacity: 1;
    transform: translateY(0);
  }
}

/* ── Accessibility floor: 48px touch targets everywhere ─────────────────── */
/* Applied to checkpoint choice labels and the primary lesson/checkpoint CTAs
   (repo CLAUDE.md "Accessibility floor: 48px targets", WCAG 2.5.5). Falls
   back to a literal 48px on both axes if --tap-comfort is ever unset, so the
   floor holds even if this file is loaded before styles.css for some reason. */
.touch-target {
  min-height: var(--tap-comfort, 48px);
  min-width: var(--tap-comfort, 48px);
}

/* ── Header display-toggle buttons (theme + high contrast) ──────────────── */
/* base.html's #theme-toggle-btn / #hc-toggle-btn render via the vendored
   .wt-icon-btn primitive with `background: transparent` and
   `border: 1px solid transparent` (styles.css line ~490-503) -- i.e. bare
   glyphs with no resting chrome, easy to miss as controls at all. This
   section gives both real visual weight and a legible active state.
   Tokens only, no hardcoded color literals -- per repo CLAUDE.md ("Do NOT
   hand-pick colors. ONE color only, the OKLCH engine derives the rest.")
   every color value below is var(--*), verified against the ACTUAL
   generated palette for this app's brand (#0d9488, base.html's
   data-brand) via `node -e` against theme-engine.js's own contrastRatio(),
   not eyeballed -- see task notes for the numbers. Selectors are ID-based
   deliberately: the vendored `.wt-icon-btn[aria-pressed="true"]` rule
   (0,2,0 specificity) needed to be reliably outranked without depending on
   stylesheet load order. */

#theme-toggle-btn,
#hc-toggle-btn {
  /* Larger glyph (deliverable): #theme-toggle-btn's glyph is a CSS
     ::before (see the data-theme-resolved rules below); #hc-toggle-btn's
     "Aa" is static markup text (base.html). Neither had an explicit
     font-size before this pass, so both rendered at the inherited body
     size (1rem). 1.25rem matches the SVG icon convention the vendor
     stylesheet already uses elsewhere for this exact primitive
     (`.wt-icon-btn svg { width: 1.25rem; height: 1.25rem; }`, line 513) --
     not an arbitrary size. */
  font-size: 1.25rem;
  /* Resting border + background: --surface-mute / --border-strong is the
     same resting-chrome token pair `.wt-btn-secondary` already uses
     (styles.css line ~457-461) for its non-transparent resting state, so
     this reads as "a real button" via an established pattern rather than
     a one-off choice. Measured (node, theme-engine.js contrastRatio,
     text-muted vs surface-mute): 7.566:1 light / 8.948:1 dark / 21.0:1 hc
     -- comfortably clear of the 4.5:1 floor in all four rendered variants
     (hc-light and hc-dark both resolve through the same .hc palette). */
  background: var(--surface-mute);
  border-color: var(--border-strong);
  color: var(--text-muted);
}

/* #hc-toggle-btn's glyph is ◑ (U+25D1), the conventional half-filled-circle
   contrast symbol -- a single glyph, so it needs no letter-spacing correction
   (an earlier "Aa" label did, and that rule was removed with it).

   Why ◑ and not "Aa": this control toggles CONTRAST. It does scale
   --tap-comfort 48px -> 54px as a side effect, but it changes no font size --
   styles.css declares no font-size tokens at all -- so "Aa" promised text
   resizing that does not exist. And not a magnifying glass: cmdk.js ships a
   Cmd+K command palette on every page, so that glyph would read as search.

   ◑ is safe to pair with the theme button ONLY because that button now uses
   ☾/☀. Both buttons previously used mirrored half-circles (◐/◑), which is
   what made them indistinguishable. If the theme icon is ever changed back to
   a half-circle shape, this collision returns. */

#theme-toggle-btn:hover,
#hc-toggle-btn:hover {
  /* Extends, not regresses, the existing hover contract: vendor's
     `.wt-icon-btn:hover` only stepped background/color up from a
     transparent resting state (styles.css line 505-508); hover here is
     still a clear step up from the new heavier resting style, and now
     also picks up a primary-colored border. Measured (text vs
     surface-raised): 18.775:1 light / 17.317:1 dark / 21.0:1 hc. */
  background: var(--surface-raised);
  border-color: var(--primary);
  color: var(--text);
}

/* Clearly distinct pressed (high-contrast-ON) state for #hc-toggle-btn.
   base.html's inline script already flips aria-pressed correctly on click
   (verified: it is NOT the bug here) -- this is purely the visual side.
   NOTE: color: var(--primary) on background: var(--primary-soft) -- the
   vendor rule's own pairing (styles.css line 509-512) -- measures only
   3.892:1 in light mode (verified via the same node contrastRatio check),
   BELOW the 4.5:1 floor. --primary-strong on --primary-soft was measured
   instead: 6.604:1 light / 13.346:1 dark / 13.512:1 hc -- all comfortably
   over floor, and still a visually "branded" accent color, not just body
   text. All three properties are re-declared (not layered on top of the
   vendor rule) because this selector's specificity (1,1,0) must and does
   outrank the plain `#hc-toggle-btn` resting rule above (1,0,0) -- if only
   border-color were set here, the resting rule's background/color would
   still win and the active state would look unpressed. */
#hc-toggle-btn[aria-pressed="true"] {
  background: var(--primary-soft);
  border-color: var(--primary-strong);
  color: var(--primary-strong);
}
#hc-toggle-btn[aria-pressed="true"]:hover {
  /* Measured (primary-strong vs primary-soft-hover): 5.896:1 light /
     12.091:1 dark / 11.719:1 hc. */
  background: var(--primary-soft-hover);
  border-color: var(--primary-strong);
  color: var(--primary-strong);
}

/* Theme-button state signal -- resolves a deliberate asymmetry: HC's
   on/off model hangs cleanly off aria-pressed; #theme-toggle-btn has no
   such boolean because WTTheme.cycle() isn't one -- it's a 2-state cycle
   (light/dark; "system" is only ever the untouched initial default, per
   theme-init.js's own cycle() comment), so aria-pressed would be the
   wrong ARIA semantic here even before considering styling (this is also
   why the button was NOT converted to role="switch" -- see task brief).
   Rather than inventing a new state-tracking layer (a data attribute on
   the button kept in sync via a new event listener), this reuses an
   attribute theme-init.js's apply() function ALREADY writes on <html> --
   data-theme-resolved="light"|"dark" -- on every page load, every
   WTTheme.set()/cycle(), and from the prefers-color-scheme listener when
   the stored pref is "system". No JS and no base.html change needed: the
   attribute is already correct before first paint (theme-init.js runs
   synchronously in <head>, ahead of the stylesheet <link>s, specifically
   to avoid FOUC -- see base.html's own head-ordering comment), so a plain
   CSS ancestor-attribute selector is sufficient.
   Deliberately border-color only, never background or color: this repo's
   a11y test (app/tests/render_query.py's contrast_ratio()) measures only
   .color against the effective backgroundColor ancestor chain -- border
   color plays no part in that computation, so this is a zero-risk way to
   signal which theme is active without touching either value the a11y
   floor test asserts on.
   Known limitation, stated rather than hidden: app/tests/render_query.py's
   set_theme() deliberately bypasses theme-init.js entirely (toggles
   .dark/.hc classlist directly, per its own module docstring, for
   deterministic tests independent of localStorage/matchMedia) -- it never
   touches data-theme-resolved. In the real app the attribute and the
   .dark class are always set together by the same apply() call, so this
   renders correctly for actual users; the automated harness just can't
   observe this specific rule because of how it forces state. It does not
   affect the a11y-floor assertions either way (see above). */
html[data-theme-resolved="dark"] #theme-toggle-btn {
  border-color: var(--info);
}
html[data-theme-resolved="light"] #theme-toggle-btn {
  border-color: var(--warning);
}

/* Theme-button GLYPH swap (distinct from the border-color signal above,
   which is intentionally left alone -- same attribute hook, different
   property). base.html's #theme-toggle-btn now ships with NO static glyph
   in its markup on purpose: the icon is entirely a function of resolved
   theme, so it lives here as a ::before, keyed off the exact same
   data-theme-resolved attribute theme-init.js's apply() already writes on
   <html> (see the long comment above for why that attribute is safe to
   rely on and its one stated test-harness limitation). No new JS.

   *** Shows the DESTINATION theme, not the CURRENT one -- this is the
   *** prevailing web convention (GitHub, Twitter/X, etc.) and is
   *** deliberate, NOT a bug: in light mode the button shows a moon
   *** ("click this to go dark"); in dark mode it shows a sun ("click this
   *** to go light"). DO NOT "fix" this to show the current theme instead
   *** -- that would invert the entire point of the icon and silently
   *** un-fix the exact problem this change shipped to solve (see task
   *** brief: two adjacent buttons rendering mirrored halves of the SAME
   *** glyph, neither indicating its function).

   Both code points carry a trailing U+FE0E (VARIATION SELECTOR-15, "text
   presentation selector") to force the monochrome/text glyph rather than
   a platform color-emoji substitute, which would visually clash with a
   monochrome icon button and silently ignore `color`. Verified empirically
   via Playwright/Chromium screenshots + pixel sampling in this repo's dev
   environment (no color-emoji font installed here -- fc-list confirms):
   both ☀ (U+2600) and ☾ (U+263E) already rendered strictly grayscale
   (R=G=B at every sampled pixel) with or without U+FE0E, i.e. no
   substitution was observed on THIS stack either way. U+FE0E is kept
   anyway as a defensive no-op here / real safeguard on platforms that DO
   ship a color-emoji font and might otherwise pick a colorful glyph for
   one of these code points -- it costs nothing where text presentation is
   already the default. */
#theme-toggle-btn::before {
  /* Base/default = light-mode glyph (moon, "click for dark"). Matches
     theme-init.js's own default resolve() (system -> light unless
     prefers-color-scheme says otherwise) and covers the (practically
     unreachable, since theme-init.js runs synchronously pre-paint) case
     where data-theme-resolved hasn't been set yet. Overridden below for
     the dark case; restated explicitly for light too, mirroring the
     border-color rules above, so both variants are equally discoverable
     rather than one being "the default nobody wrote down". */
  content: "\263E\FE0E"; /* ☾ last-quarter moon */
}
html[data-theme-resolved="light"] #theme-toggle-btn::before {
  content: "\263E\FE0E"; /* ☾ -- explicit restatement of the default above */
}
html[data-theme-resolved="dark"] #theme-toggle-btn::before {
  content: "\2600\FE0E"; /* ☀ sun -- shown while dark is active */
}

/* Focus-visible ring: vendor styles.css already supplies one (2px, 3px
   under .hc -- lines 359-374) but it read faint against the previous
   bare-glyph resting style. A touch more offset keeps it clearly clear of
   the now-visible resting border rather than overlapping it. Sets
   outline-offset only (never outline-width), so .hc's own 3px-width rule
   (line 296-303) still applies unmodified under high contrast. */
#theme-toggle-btn:focus-visible,
#hc-toggle-btn:focus-visible {
  outline-offset: 3px;
}

/* ── Exam figures (S1.6b): T-1/T-2/T-3 schematic rendering ──────────────── */
/* Renders the 12 figure-bearing pool questions' diagram (app.py mounts
   "/diagrams" straight off library/raw/diagrams/ -- see that mount's own
   comment). These are line-art JPEGs with a lot of internal whitespace
   margin around the actual schematic (measured directly against the
   source files: content occupies roughly the middle 45-80% of the 1800x1200
   canvas depending on the figure), so a plain max-width:100% render at a
   ~360-390px phone viewport puts the real content -- and its component-
   number labels -- at a genuinely small rendered size. Redrawing the
   diagrams is out of scope (repo CLAUDE.md task brief), so the remedy here
   is the tap-to-enlarge affordance below, not a smaller/cropped image. */

.exam-figure {
  align-items: center;
  text-align: center;
}

.exam-figure__trigger {
  display: inline-flex;
  padding: 0.5rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: zoom-in;
}
.exam-figure__trigger:hover,
.exam-figure__trigger:focus-visible {
  border-color: var(--primary);
}

.exam-figure__img {
  display: block;
  width: 100%;
  max-width: 20rem; /* thumbnail only -- .exam-figure-modal__img below is the legible, enlarged view */
  height: auto;
}

.exam-figure__caption {
  margin: 0;
}

.exam-figure-modal__panel {
  max-width: 95vw;
  width: min(95vw, 56rem);
  /* Deliberately tighter than the vendored .wt-modal__panel default reading
     (that primitive ships with no padding of its own -- see ham.css's
     ".ham-shell .wt-card" comment above for the same "vendored primitive
     ships bare, HAM pads it" pattern) -- every horizontal pixel here is
     legibility budget for the enlarged image at a narrow phone viewport
     (measured: reclaiming padding this way took the enlarged image from
     ~324px to ~342px wide at a 390px viewport in
     test_exam_figure_geometry.py). Kept asymmetric on purpose: the close
     button still wants comfortable clearance from the edge; the image
     doesn't need any. */
  padding: 0.75rem 0.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  align-items: center;
}

.exam-figure-modal__close {
  align-self: flex-end;
}

.exam-figure-modal__img {
  display: block;
  max-width: 100%;
  max-height: 80vh;
  width: auto;
  height: auto;
}
