/* Site-wide light/dark toggle overrides.
   index.html already ships real CSS-variable light/dark palettes gated by
   prefers-color-scheme; these attribute-selector rules mirror them so the
   toggle can force either mode regardless of OS setting (higher specificity
   than the plain `:root{}` inside the media query, so no !important needed).
   Every other page (quiz/study/practicum templates) has its own one-off
   :root palette with no dark variant, so dark mode there is done generically
   by inverting the shared `.wrap` content container and re-inverting real
   photographic imagery so it isn't rendered as a photo negative. */

#corner-actions {
  position: fixed;
  top: 14px;
  right: 14px;
  z-index: 600;
  display: flex;
  gap: 8px;
}
/* Settings, Search, Your highlights, and Draw are the only tools that stay
   as standalone corner icons (per explicit product decision) -- Calculator,
   Study timer, Refresh, Dark mode, and Account are consolidated into
   quick-action rows inside the Settings panel instead (see
   initSettingsPanel() in theme.js), at every screen width, not just on
   mobile. This used to be a narrow-screen-only collapse (5-6 same-weight
   icons is a lot of competing chrome on a small screen); folding it in
   permanently keeps the corner row down to just the tools meant to be
   reached for constantly, everywhere.
   Exception: #refresh-btn stays a visible icon on the homepage specifically
   (higher specificity than the plain #refresh-btn rule below, so no
   !important needed) -- the homepage is the one page people land on
   repeatedly wanting a quick "check for updates" without detouring through
   Settings; initSettingsPanel() skips adding its quick-action row there to
   match (see theme.js), so it's not duplicated between icon and panel. */
#refresh-btn, #theme-toggle-btn, #calc-btn, #pomo-btn, #account-btn { display: none; }
body.homepage #refresh-btn { display: flex; }
/* Guides page keeps Refresh as a standalone corner icon too (same as the
   homepage and Arcade), rather than tucking it inside Settings -- it's a
   landing/browse page people return to for "check for new material". */
body.guides-page #refresh-btn { display: flex; }
#install-app-btn {
  left: 14px;
}
/* "Back to previous page" corner button -- top-left, mirroring
   #corner-actions' top-right placement. Only ever present on non-homepage
   pages (see theme.js), so it never actually competes with
   #install-app-btn (index.html-only) for the same spot, but it's still its
   own flex container so a future left-side button could join it without
   fighting over a single fixed id, same pattern as #corner-actions.
   Unlike #corner-actions, this is hidden until the page has been scrolled
   a little (see theme.js's scroll listener) -- checked across a sample of
   page types (quiz/guide/practicum templates) and every one puts its own
   title/eyebrow text within ~20-30px of the top-left corner with zero
   clearance reserved for a left-side element (nothing has ever lived there
   before), so an always-visible button here would sit on top of that text
   at any viewport narrower than that page's own max-width. Revealing it
   only once real content has scrolled past that zone sidesteps the
   collision on all ~179 pages without touching any of their own CSS. */
#corner-actions-left {
  position: fixed;
  top: 14px;
  left: 14px;
  z-index: 600;
  display: flex;
  gap: 8px;
  opacity: 0;
  pointer-events: none;
  transition: opacity .15s var(--ease);
}
#corner-actions-left.visible {
  opacity: 1;
  pointer-events: auto;
}

/* Guide/study-aid "back" bar -- always visible, no scroll-gate needed like
   #corner-actions-left above, because this is placed in normal document
   flow as literally the first thing on the page (before .wrap or any
   full-bleed header a guide might have), not floated on top of content.
   That makes it collision-proof by construction for any future guide
   regardless of that guide's own header design -- nothing else occupies
   this row, everything else is simply pushed below it. Sticky (not fixed)
   so it stays reachable while scrolling.
   **Sticky-stacking gotcha:** two independent `position:sticky; top:0`
   siblings do NOT auto-stack -- each sticks relative to the scroll
   container, not to each other, so a guide with its own sticky nav/topbar
   (already true of both existing narrative guides: the Physiology field
   guide's `.topbar`, the Anatomy guide's `nav.sysnav`) will visually
   overlap this bar once both are in their stuck state, unless that
   element's own `top` is offset by this bar's height (fixed at 38px
   specifically so this math is exact, not measured/guessed). Any future
   guide that has its own sticky element must set that element's `top` to
   `38px`, not `0`, to stack below this bar instead of colliding with it.
   Standard markup for any new guide, right after <body> opens, before
   anything else:
     <div class="guide-back-bar">
       <a href="#" class="guide-back-link"
          onclick="event.preventDefault(); window.guideGoBack();">&larr; Back</a>
     </div> */
.guide-back-bar {
  position: sticky;
  top: 0;
  z-index: 550;
  height: 38px;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  padding: 0 16px;
  background: #ffffff;
  border-bottom: 1px solid #e5e7eb;
}
:root[data-theme="dark"] .guide-back-bar {
  background: #191c22;
  border-bottom-color: #2a2e37;
}
/* One toolbar (design review item 8): #corner-actions docked inside the bar.
   Height stays 38px, so the nav.toc top:38px convention still holds. */
.guide-back-bar > #corner-actions.in-bar {
  position: static;
  margin-left: auto;
  gap: 2px;
  align-items: center;
}
.guide-back-bar #corner-actions.in-bar .corner-btn,
.guide-back-bar .bar-more-menu .corner-btn {
  width: 32px;
  height: 32px;
  border-radius: 8px;
  border: 0;
  box-shadow: none;
  background: transparent;
  color: #374151;
  transition: background-color var(--dur-1, 160ms) var(--ease, ease), color var(--dur-1, 160ms) var(--ease, ease);
}
.guide-back-bar #corner-actions.in-bar .corner-btn:hover,
.guide-back-bar .bar-more-menu .corner-btn:hover { transform: none; background: #f1f2f5; color: #111827; }
.guide-back-bar #corner-actions.in-bar .corner-btn:active { transform: scale(.94); }
:root[data-theme="dark"] .guide-back-bar #corner-actions.in-bar .corner-btn,
:root[data-theme="dark"] .guide-back-bar .bar-more-menu .corner-btn { background: transparent; color: #c9cdd4; border: 0; }
:root[data-theme="dark"] .guide-back-bar #corner-actions.in-bar .corner-btn:hover,
:root[data-theme="dark"] .guide-back-bar .bar-more-menu .corner-btn:hover { background: #22262e; color: #f3f4f6; }
.guide-back-bar #more-btn { display: none; }
.bar-more-menu { display: none; }
@media (max-width: 640px) {
  .guide-back-bar #corner-actions.in-bar > .corner-btn:not(#guide-search-btn):not(#settings-btn):not(#more-btn) { display: none; }
  .guide-back-bar #more-btn { display: flex; }
  .bar-more-menu.open {
    display: flex; flex-direction: column; gap: 2px;
    position: absolute; top: 40px; right: 8px; z-index: 560;
    min-width: 210px; padding: 6px;
    background: #ffffff; border: 1px solid #e5e7eb; border-radius: 12px;
    box-shadow: 0 10px 30px rgba(0, 0, 0, .14);
  }
  :root[data-theme="dark"] .bar-more-menu.open { background: #191c22; border-color: #2a2e37; box-shadow: 0 10px 30px rgba(0, 0, 0, .5); }
  .guide-back-bar .bar-more-menu .corner-btn {
    display: flex !important; width: 100%; height: 40px;
    justify-content: flex-start; gap: 12px; padding: 0 10px;
    font: 600 14px/1 var(--font-sans);
  }
  .guide-back-bar .bar-more-menu .corner-btn::after { content: attr(aria-label); }
}
@media (prefers-reduced-motion: reduce) {
  .guide-back-bar #corner-actions.in-bar .corner-btn { transition: none; }
}
.guide-back-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font: 700 13.5px/1 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  color: #1f2937;
  text-decoration: none;
  cursor: pointer;
}
:root[data-theme="dark"] .guide-back-link { color: #e5e7eb; }
.guide-back-link:hover { text-decoration: underline; }

/* "Who's studying now" (presence.js) -- sits at the left of the back bar,
   just after the Back button (margin-left gap keeps them from crowding),
   hidden by default (only shown once presence.js confirms at least one other
   signed-in viewer is fresh). */
.presence-pill {
  display: none;
  margin-left: 14px;
  font: 600 12px/1 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  color: #1f2937;
  background: #eff6ff;
  border: 1px solid #bfdbfe;
  border-radius: 999px;
  padding: 5px 10px;
  white-space: nowrap;
}
.presence-pill.visible { display: inline-flex; align-items: center; }
:root[data-theme="dark"] .presence-pill {
  color: #bfdbfe;
  background: #16213a;
  border-color: #1e3a5f;
}
@media (max-width: 480px) {
  .presence-pill { font-size: 11px; padding: 4px 8px; }
}

/* Collapsible Contents sidebar (theme.js adds the buttons + toggles
   body.toc-collapsed; state persisted per-device). Every guide styles
   nav.toc itself inline as a sticky flex sidebar -- these shared rules only
   handle the minimized state, so main reclaims the width, plus a floating
   tab to bring it back. Works for any guide because they all use nav.toc in
   a flex .layout. */
.toc-collapse-btn {
  float: right;
  background: none;
  border: 0;
  cursor: pointer;
  font-size: 15px;
  line-height: 1;
  padding: 0 2px;
  color: inherit;
  opacity: .55;
}
.toc-collapse-btn:hover { opacity: 1; }
body.toc-collapsed nav.toc { display: none !important; }
.toc-fab {
  position: fixed;
  left: 12px;
  top: 48px;
  z-index: 520;
  display: none;
  align-items: center;
  gap: 6px;
  background: #ffffff;
  border: 1px solid #e5e7eb;
  border-radius: 999px;
  padding: 6px 12px;
  font: 600 12px/1 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  color: #1f2937;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .12);
  cursor: pointer;
}
body.toc-collapsed .toc-fab { display: inline-flex; }
:root[data-theme="dark"] .toc-fab {
  background: #191c22;
  color: #cbd5e1;
  border-color: #2a3441;
}

/* In-guide search bar (theme.js, guide-search-* IDs). Sticky right below
   .guide-back-bar at top:38px per that element's own stacking convention
   (see the comment above it) -- hidden by default, toggled open by the
   corner search button or the "/" shortcut. */
.guide-search-bar {
  position: sticky;
  top: 38px;
  z-index: 540;
  display: none;
  align-items: center;
  gap: 8px;
  padding: 10px 16px;
  background: #ffffff;
  border-bottom: 1px solid #e5e7eb;
}
.guide-search-bar.open { display: flex; }
:root[data-theme="dark"] .guide-search-bar {
  background: #191c22;
  border-bottom-color: #2a2e37;
}
.guide-search-input {
  flex: 1;
  min-width: 0;
  font: 500 14px/1.3 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  padding: 7px 10px;
  border-radius: 8px;
  border: 1px solid #d1d5db;
  background: #f9fafb;
  color: #111827;
}
:root[data-theme="dark"] .guide-search-input {
  border-color: #2a2e37;
  background: #12151b;
  color: #e5e7eb;
}
.guide-search-input:focus { outline: 2px solid #3b82f6; outline-offset: 1px; }
.guide-search-count {
  font: 600 12.5px/1 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  color: #6b7280;
  min-width: 46px;
  text-align: center;
  font-variant-numeric: tabular-nums;
  flex: 0 0 auto;
}
:root[data-theme="dark"] .guide-search-count { color: #9aa2af; }
.guide-search-nav, .guide-search-close {
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  padding: 0;
}
.guide-search-nav {
  border: 1px solid #d1d5db;
  background: #ffffff;
  color: #374151;
}
:root[data-theme="dark"] .guide-search-nav {
  border-color: #2a2e37;
  background: #12151b;
  color: #cbd2dc;
}
.guide-search-close {
  border: none;
  background: transparent;
  color: #6b7280;
  font-size: 19px;
  line-height: 1;
}
:root[data-theme="dark"] .guide-search-close { color: #9aa2af; }
mark.search-hit {
  background: #fde68a;
  color: #111827;
  border-radius: 2px;
}
:root[data-theme="dark"] mark.search-hit {
  background: #7a5f10;
  color: #fef3c7;
}
mark.search-hit-current {
  background: #f97316;
  color: #111827;
  box-shadow: 0 0 0 2px #ea580c;
}
:root[data-theme="dark"] mark.search-hit-current {
  background: #fb923c;
  color: #1c1005;
  box-shadow: 0 0 0 2px #f97316;
}
.corner-btn {
  width: 38px;
  height: 38px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  padding: 0;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .15);
  background: #ffffff;
  color: #1f2937;
  border: 1px solid #e5e7eb;
  transition: transform .2s var(--ease-spring);
}
/* #word-btn is an <a download> rather than a <button>, so it needs the anchor
   defaults stripped or it inherits link colour and an underline. */
a.corner-btn { text-decoration: none; }
a.corner-btn:hover { text-decoration: none; }

.corner-btn:hover { transform: scale(1.1) rotate(-8deg); }
.corner-btn:active { transform: scale(0.92); }
:root[data-theme="dark"] .corner-btn {
  background: #191c22;
  color: #e5e7eb;
  border: 1px solid #2a2e37;
}

/* index.html's edge-decor zigzag strips are 70px wide and only render at
   min-width:1000px (see index.html's own @media rule for .edge-decor).
   Push the corner buttons in past that band at the same breakpoint --
   quiz/study/practicum pages have no edge-decor at any width (their header
   cards sit close to the viewport edge, especially on mobile, so shifting
   them there would overlap page content instead of clearing anything), and
   below 1000px on the homepage the decor itself is hidden, so 14px is
   correct there too. */
@media (min-width: 1000px) {
  body.homepage #corner-actions { right: 84px; }
  body.homepage #install-app-btn { left: 84px; }
  body.homepage .calc-overlay { right: 84px; }
}

/* index.html's own token system. Scoped to body.homepage (not a blanket
   :root override) because ~150 quiz/study/practicum pages each define their
   own :root custom properties reusing the SAME names (--ink, --muted, --bg,
   --shadow) with different values -- a blanket override here would win by
   specificity over every one of those pages' own declarations and corrupt
   their light-mode color logic before the invert filter below even runs. */
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"]  { color-scheme: dark; }

:root[data-theme="dark"] body.homepage {
  --bg: #121116;
  --card: #1c1b21;
  --border: #302d36;
  --ink: #e5e7eb;
  --muted: #9aa1ac;
  --accent: var(--a-blue);
  --accent-soft: var(--a-blue-soft);
  --accent2: var(--a-green);
  --accent2-soft: var(--a-green-soft);
  --accent3: var(--a-violet);
  --accent3-soft: var(--a-violet-soft);
  --shadow: var(--e-2);
  --shadow-lift: var(--e-3);
  --tab-border: rgba(96, 165, 250, 0.3);
}
:root[data-theme="light"] body.homepage {
  /* Warm off-white rather than the old cold #f3f4f6. The blue accent already
     supplies all the cool on this page; a cool ground underneath it is the
     main thing that separated the homepage's feel from guides.html's cream. */
  --bg: #f8f6f2;
  --card: #ffffff;
  --border: #e8e4dd;
  --ink: #1f2937;
  --muted: #6b7280;
  --accent: var(--a-blue);
  --accent-soft: var(--a-blue-soft);
  --accent2: var(--a-green);
  --accent2-soft: var(--a-green-soft);
  --accent3: var(--a-violet);
  --accent3-soft: var(--a-violet-soft);
  --shadow: var(--e-2);
  --shadow-lift: var(--e-3);
  --tab-border: rgba(37, 99, 235, 0.25);
}

/* Ambient background gradient drift (added 2026-07-17) -- a slow, barely-
   perceptible shift so the homepage feels "alive" even at rest, independent
   of the seasonal particle effects. The gradient stops are close analogous
   tints of the plain --bg color above, so the overall mood reads the same
   as before, just no longer perfectly flat. Wins over index.html's own
   `body { background: var(--bg); }` by specificity (class+element beats a
   bare element selector) regardless of load order. Each seasonal block
   below sets its own drift using that season's palette instead of this
   default one -- see [[pa-quizzes-seasonal-themes]] (confirmed 2026-07-17:
   seasonal windows should drift too, not just override to a flat color). */
:root[data-theme="dark"] body.homepage {
  background: linear-gradient(120deg, #121116, #131a28, #191216, #121116);
  background-color: #121116;   /* see note below: the shorthand above resets this to transparent */
  background-size: 300% 300%;
  animation: bg-drift 16s ease-in-out infinite;
}
:root[data-theme="light"] body.homepage {
  background: linear-gradient(120deg, #f8f6f2, #eef1f6, #faf3ec, #f8f6f2);
  background-color: #f8f6f2;   /* see note below: the shorthand above resets this to transparent */
  background-size: 300% 300%;
  animation: bg-drift 16s ease-in-out infinite;
}
@keyframes bg-drift {
  0%, 100% { background-position: 0% 50%; }
  50% { background-position: 100% 50%; }
}

/* Valentine's window (Jan 31 - Feb 14, see theme.js) -- homepage-only
   light-mode pink tint. Scoped to .vday-theme (a class theme.js adds only
   inside that date window) so it never bleeds outside the season. */
:root[data-theme="light"] body.homepage.vday-theme {
  --bg: #fbe7ee;
  --card: #fffcfd;
  --border: #f0cddb;
  background: linear-gradient(120deg, #fbe7ee, #fef0f7, #f5d3e0, #fbe7ee);
  background-color: #fbe7ee;   /* see note below: the shorthand above resets this to transparent */
  background-size: 300% 300%;
  animation: bg-drift 16s ease-in-out infinite;
}
/* Fall/Halloween window (Oct 17-31, see theme.js) -- homepage-only
   light-mode warm tint, same .theme-class-scoped pattern as vday-theme. */
:root[data-theme="light"] body.homepage.fall-theme {
  --bg: #fdf1e4;
  --card: #fffdf8;
  --border: #f0dcc0;
  background: linear-gradient(120deg, #fdf1e4, #fef8ef, #f8dfc0, #fdf1e4);
  background-color: #fdf1e4;   /* see note below: the shorthand above resets this to transparent */
  background-size: 300% 300%;
  animation: bg-drift 16s ease-in-out infinite;
}
/* St. Patrick's window (Mar 3-17, see theme.js) -- homepage-only
   light-mode green tint, same .theme-class-scoped pattern as vday-theme. */
:root[data-theme="light"] body.homepage.stpatricks-theme {
  --bg: #d7f0dd;
  --card: #f9fefb;
  --border: #a9dab8;
  background: linear-gradient(120deg, #d7f0dd, #e9f9ec, #bfe8ca, #d7f0dd);
  background-color: #d7f0dd;   /* see note below: the shorthand above resets this to transparent */
  background-size: 300% 300%;
  animation: bg-drift 16s ease-in-out infinite;
}
/* December window (see theme.js) -- homepage-only light-mode cream tint,
   same .theme-class-scoped pattern as the other seasonal tints. */
:root[data-theme="light"] body.homepage.xmas-theme {
  --bg: #faf3e3;
  --card: #fffdf7;
  --border: #ece0c4;
  background: linear-gradient(120deg, #faf3e3, #fffcf3, #f3e4c4, #faf3e3);
  background-color: #faf3e3;   /* see note below: the shorthand above resets this to transparent */
  background-size: 300% 300%;
  animation: bg-drift 16s ease-in-out infinite;
}
:root[data-theme="light"] body.homepage.paweek-theme {
  --bg: #e7f6f4; --card: #fbfffe; --border: #c7e7e1;
  background: linear-gradient(120deg, #e7f6f4, #f2fbf9, #d4edf2, #e7f6f4);
  background-color: #e7f6f4;
  background-size: 300% 300%;
  animation: bg-drift 16s ease-in-out infinite;
}
:root[data-theme="light"] body.homepage.halloween-theme {
  --bg: #f1e9f6; --card: #fefcff; --border: #dccbea;
  background: linear-gradient(120deg, #f1e9f6, #f8f1fb, #f6dcc6, #f1e9f6);
  background-color: #f1e9f6;
  background-size: 300% 300%;
  animation: bg-drift 16s ease-in-out infinite;
}
:root[data-theme="light"] body.homepage.fireworks-theme {
  --bg: #eef3fb; --card: #fbfdff; --border: #cdd9ee;
  background: linear-gradient(120deg, #eef3fb, #fbf1f1, #e0eaf9, #eef3fb);
  background-color: #eef3fb;
  background-size: 300% 300%;
  animation: bg-drift 16s ease-in-out infinite;
}
:root[data-theme="light"] body.homepage.newyear-theme {
  --bg: #eef0f6; --card: #fefeff; --border: #d3d8e8;
  background: linear-gradient(120deg, #eef0f6, #f8f4e8, #e2e6f2, #eef0f6);
  background-color: #eef0f6;
  background-size: 300% 300%;
  animation: bg-drift 16s ease-in-out infinite;
}
:root[data-theme="light"] body.homepage.thanksgiving-theme {
  --bg: #fdf6e8; --card: #fffefa; --border: #efe0bc;
  background: linear-gradient(120deg, #fdf6e8, #fffaf0, #f8ebc9, #fdf6e8);
  background-color: #fdf6e8;
  background-size: 300% 300%;
  animation: bg-drift 16s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
  /* !important needed here: the :root[data-theme=...]-scoped drift rules
     above are more specific than a plain class selector can match without
     it, so a lower-specificity guard would silently lose the cascade and
     leave the animation running. */
  body.homepage,
  body.homepage.vday-theme,
  body.homepage.fall-theme,
  body.homepage.stpatricks-theme,
  body.homepage.xmas-theme,
  body.homepage.paweek-theme,
  body.homepage.halloween-theme, body.homepage.thanksgiving-theme, body.homepage.newyear-theme, body.homepage.fireworks-theme {
    animation: none !important;
    background-position: 0% 50% !important;
  }
}
:root[data-theme="dark"] body.homepage .edge-decor {
  background-size: 60px 420px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 420'%3E%3Cpath d='M30,0 L30,50 L12,60 L48,80 L30,90 L30,104 Q40,112 30,120 L30,175 L12,185 L48,205 L30,215 L30,229 Q40,237 30,245 L30,320 L12,330 L48,350 L30,360 L30,374 Q40,382 30,390 L30,420' fill='none' stroke='%234981ee' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
:root[data-theme="dark"] body.homepage .bottom-decor {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 60'%3E%3Cpath d='M0,30 L120,30 L135,8 L150,52 L165,30 L300,30' fill='none' stroke='%2360a5fa' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

/* Generic quiz/study/practicum templates: invert the content wrapper only
   (never `html`/`body`), so position:fixed siblings like the footer logo,
   pull-to-refresh indicator, and JS-appended toast/update-banner elements
   (all direct children of <body>, outside .wrap) keep the viewport as their
   containing block instead of inheriting a filtered one. */
:root[data-theme="dark"] body:not([data-dark="tokens"]) > .wrap {
  filter: invert(1) hue-rotate(180deg);
}
:root[data-theme="dark"] body:not([data-dark="tokens"]) > .wrap .diagram-wrap,
:root[data-theme="dark"] body:not([data-dark="tokens"]) > .wrap img,
:root[data-theme="dark"] body:not([data-dark="tokens"]) > .wrap video {
  filter: invert(1) hue-rotate(180deg);
}
:root[data-theme="dark"] body {
  background: #0b0d12;
}

/* Larger-text setting: every page's font sizes are hardcoded px, not
   rem, so a root font-size bump would do nothing -- CSS `zoom` scales
   the whole rendered subtree (a real reflow, not a visual transform),
   which works regardless of what units a given page happens to use.
   Applied to the same top-level containers as the dark-mode invert
   above (body > .wrap for quiz/study/practicum pages, .page for the
   homepage) so position:fixed siblings (corner buttons, modals, the
   pull-to-refresh indicator) are never affected. */
:root[data-text-size="large"] body > .wrap {
  zoom: 1.15;
}
:root[data-text-size="large"] body.homepage .page {
  zoom: 1.15;
}

/* High-contrast setting: same top-level-container trick again, but
   `filter` (unlike `zoom`) isn't additive across separate rules -- two
   rules that both set `filter` on the same element don't combine, the
   later/more-specific one just wins outright. Dark mode already uses
   `filter` for its invert trick on quiz/study/practicum pages, so a
   plain `[data-contrast="high"] .wrap { filter: contrast(...) }` rule
   would silently replace (not add to) the dark-mode invert when both
   are on. The compound `[data-theme="dark"][data-contrast="high"]`
   selector below has higher specificity than either alone and spells
   out the combined filter value explicitly, so both settings apply
   together correctly regardless of source order. The homepage doesn't
   need this composing -- its dark mode is real CSS-variable colors,
   not a filter, so contrast there is always independent. */
:root[data-contrast="high"] body > .wrap {
  filter: contrast(1.35);
}
:root[data-theme="dark"][data-contrast="high"] body:not([data-dark="tokens"]) > .wrap {
  filter: invert(1) hue-rotate(180deg) contrast(1.35);
}
:root[data-contrast="high"] body.homepage .page {
  filter: contrast(1.3);
}

/* Keyboard-shortcuts help overlay, injected by theme.js only on pages that
   have a #timerpill element (i.e. non-practicum quiz pages, which are the
   only ones with the 1-4/Enter/Arrow keydown handler this documents). */
.shortcuts-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  z-index: 2000;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.shortcuts-overlay.open { display: flex; }
.shortcuts-panel {
  background: #ffffff;
  color: #1f2937;
  border: 1px solid #e5e7eb;
  border-radius: 16px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, .2);
  max-width: 360px;
  width: 100%;
  padding: 22px 22px 20px;
  position: relative;
}
:root[data-theme="dark"] .shortcuts-panel {
  background: #191c22;
  color: #e5e7eb;
  border-color: #2a2e37;
}
.shortcuts-panel h3 {
  margin: 0 0 14px;
  font-size: 15px;
  font-weight: 700;
}
.shortcuts-close {
  position: absolute;
  top: 12px;
  right: 12px;
  border: none;
  background: #e5e7eb;
  color: #1f2937;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  cursor: pointer;
  line-height: 1;
  font-size: 15px;
}
:root[data-theme="dark"] .shortcuts-close {
  background: #2a2e37;
  color: #e5e7eb;
}
.shortcuts-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 7px 0;
  font-size: 13.5px;
  border-bottom: 1px solid #f3f4f6;
}
:root[data-theme="dark"] .shortcuts-row { border-bottom-color: #22262e; }
.shortcuts-row:last-child { border-bottom: none; }
.shortcuts-keys kbd {
  display: inline-block;
  min-width: 20px;
  text-align: center;
  padding: 2px 6px;
  border-radius: 5px;
  background: #f3f4f6;
  border: 1px solid #e5e7eb;
  font: 700 11.5px/1.4 ui-monospace, monospace;
  color: #374151;
  margin-left: 3px;
}
:root[data-theme="dark"] .shortcuts-keys kbd {
  background: #22262e;
  border-color: #2a2e37;
  color: #d1d5db;
}
.shortcuts-guide-link {
  display: block;
  width: 100%;
  text-align: center;
  font: inherit;
  font-size: 12.5px;
  color: #6b7280;
  background: none;
  border: none;
  border-top: 1px solid #f3f4f6;
  text-decoration: underline;
  cursor: pointer;
  margin-top: 12px;
  padding: 12px 4px 0;
}
:root[data-theme="dark"] .shortcuts-guide-link {
  color: #9aa1ac;
  border-top-color: #22262e;
}

/* Settings panel, same overlay/panel treatment as the shortcuts help above. */
.settings-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  z-index: 2000;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.settings-overlay.open { display: flex; }
.settings-panel {
  background: #ffffff;
  color: #1f2937;
  border: 1px solid #e5e7eb;
  border-radius: 16px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, .2);
  max-width: 340px;
  width: 100%;
  padding: 22px 22px 20px;
  position: relative;
}
:root[data-theme="dark"] .settings-panel {
  background: #191c22;
  color: #e5e7eb;
  border-color: #2a2e37;
}
.settings-panel h3 {
  margin: 0 0 14px;
  font-size: 15px;
  font-weight: 700;
}
.settings-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 0;
  font-size: 13.5px;
  border-bottom: 1px solid #f3f4f6;
}
:root[data-theme="dark"] .settings-row { border-bottom-color: #22262e; }
.settings-row:last-child { border-bottom: none; }
.settings-action-btn {
  appearance: none;
  border: 1px solid #e5e7eb;
  background: none;
  color: #2563eb;
  font: inherit;
  font-size: 12.5px;
  font-weight: 700;
  padding: 5px 12px;
  border-radius: 8px;
  cursor: pointer;
  white-space: nowrap;
}
:root[data-theme="dark"] .settings-action-btn { border-color: #2a2e37; color: #60a5fa; }
.settings-action-btn:hover { border-color: currentColor; }
.settings-switch {
  position: relative;
  width: 38px;
  height: 22px;
  flex: 0 0 auto;
  display: inline-block;
}
.settings-switch input {
  opacity: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  cursor: pointer;
  position: relative;
  z-index: 1;
}
.settings-switch .track {
  position: absolute;
  inset: 0;
  background: #e5e7eb;
  border-radius: 999px;
  transition: background .15s var(--ease);
  pointer-events: none;
}
.settings-switch .thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: #ffffff;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .3);
  transition: transform .15s var(--ease);
  pointer-events: none;
}
:root[data-theme="dark"] .settings-switch .track { background: #2a2e37; }
.settings-switch input:checked ~ .track { background: #2563eb; }
:root[data-theme="dark"] .settings-switch input:checked ~ .track { background: #60a5fa; }
.settings-switch input:checked ~ .thumb { transform: translateX(16px); }

/* Calculator: a small floating panel near the corner button, not a
   full-screen overlay like Settings/Shortcuts above -- it needs to stay
   usable side-by-side with the question rather than blocking it, so no
   inset:0 backdrop and no click-catching outside the panel itself (theme.js
   closes it via a plain document click/Escape listener instead). Same
   hardcoded-colors-plus-dark-override pattern as the rest of this file's
   body-direct-child injected elements, for the same reason (the dark-mode
   invert filter only targets body > .wrap, not these). */
.calc-overlay {
  display: none;
  position: fixed;
  top: 60px;
  right: 14px;
  z-index: 2000;
}
.calc-overlay.open { display: block; }
.calc-panel {
  background: #ffffff;
  color: #1f2937;
  border: 1px solid #e5e7eb;
  border-radius: 14px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, .2);
  width: 220px;
  padding: 12px;
}
:root[data-theme="dark"] .calc-panel {
  background: #191c22;
  color: #e5e7eb;
  border-color: #2a2e37;
}
.calc-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 13px;
  font-weight: 700;
  margin-bottom: 8px;
  cursor: move;
  touch-action: none;
  user-select: none;
}
.calc-close {
  background: none;
  border: none;
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  color: inherit;
  padding: 0 2px;
}
.calc-display {
  background: #f3f4f6;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  padding: 10px 10px;
  margin-bottom: 8px;
  text-align: right;
  font-size: 22px;
  font-variant-numeric: tabular-nums;
  overflow-x: auto;
  white-space: nowrap;
}
:root[data-theme="dark"] .calc-display {
  background: #0f1115;
  border-color: #2a2e37;
}
.calc-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 6px;
}
.calc-btn {
  border: 1px solid #e5e7eb;
  background: #f9fafb;
  color: #1f2937;
  border-radius: 8px;
  padding: 10px 0;
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
}
.calc-btn:active { transform: scale(0.96); }
:root[data-theme="dark"] .calc-btn {
  background: #20242c;
  color: #e5e7eb;
  border-color: #2a2e37;
}
.calc-btn.calc-op {
  background: #eff6ff;
  color: #2563eb;
}
:root[data-theme="dark"] .calc-btn.calc-op {
  background: #16213a;
  color: #60a5fa;
}
.calc-btn.calc-eq {
  background: #2563eb;
  color: #fff;
  border-color: #2563eb;
}
:root[data-theme="dark"] .calc-btn.calc-eq {
  background: #3b82f6;
  border-color: #3b82f6;
}
.calc-btn.calc-fn {
  background: #fef2f2;
  color: #dc2626;
}
:root[data-theme="dark"] .calc-btn.calc-fn {
  background: #2a1414;
  color: #f87171;
}

@media (max-width: 480px) {
  .calc-overlay { left: 12px; right: 12px; }
  .calc-panel { width: auto; }
}

/* Study session timer (Pomodoro-style, theme.js initPomodoro()). Same
   overlay/panel/header/draggable shell as .calc-*, just a different body. */
.pomo-overlay {
  display: none;
  position: fixed;
  top: 60px;
  right: 62px;
  z-index: 2000;
}
.pomo-overlay.open { display: block; }
.pomo-panel {
  background: #ffffff;
  color: #1f2937;
  border: 1px solid #e5e7eb;
  border-radius: 14px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, .2);
  width: 240px;
  padding: 12px;
}
:root[data-theme="dark"] .pomo-panel {
  background: #191c22;
  color: #e5e7eb;
  border-color: #2a2e37;
}
.pomo-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 13px;
  font-weight: 700;
  margin-bottom: 6px;
  cursor: move;
  touch-action: none;
  user-select: none;
}
.pomo-close {
  background: none;
  border: none;
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  color: inherit;
  padding: 0 2px;
}
.pomo-phase {
  text-align: center;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--muted, #6b7280);
  margin-bottom: 2px;
}
.pomo-display {
  text-align: center;
  font-size: 36px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  margin-bottom: 10px;
}
.pomo-controls {
  display: flex;
  gap: 6px;
  margin-bottom: 10px;
}
.pomo-action-btn {
  flex: 1;
  border: 1px solid #e5e7eb;
  background: #f9fafb;
  color: #1f2937;
  border-radius: 8px;
  padding: 8px 0;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
.pomo-action-btn:active { transform: scale(0.97); }
.pomo-action-btn.primary {
  background: #2563eb;
  color: #fff;
  border-color: #2563eb;
}
:root[data-theme="dark"] .pomo-action-btn {
  background: #20242c;
  color: #e5e7eb;
  border-color: #2a2e37;
}
:root[data-theme="dark"] .pomo-action-btn.primary {
  background: #3b82f6;
  border-color: #3b82f6;
}
.pomo-settings {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  font-size: 12.5px;
  color: var(--muted, #6b7280);
  margin-bottom: 6px;
}
.pomo-field {
  display: flex;
  align-items: center;
  gap: 6px;
}
.pomo-field input {
  width: 44px;
  font: inherit;
  font-variant-numeric: tabular-nums;
  padding: 4px 6px;
  border-radius: 6px;
  border: 1px solid #e5e7eb;
  background: #f9fafb;
  color: #1f2937;
}
:root[data-theme="dark"] .pomo-field input {
  border-color: #2a2e37;
  background: #0f1115;
  color: #e5e7eb;
}
.pomo-sessions {
  text-align: center;
  font-size: 12px;
  color: var(--muted, #6b7280);
}

/* Always-visible mini countdown, independent of whether the panel above is
   open -- shown only while a session is actively running (see render() in
   initPomodoro()). Sits just below #corner-actions, same top offset as
   .calc-overlay/.pomo-overlay so it doesn't collide with either. */
.pomo-pill {
  display: none;
  position: fixed;
  top: 60px;
  right: 14px;
  z-index: 590;
  border: 1px solid #e5e7eb;
  background: #ffffff;
  color: #1f2937;
  border-radius: 999px;
  padding: 6px 12px;
  font-size: 13px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .12);
}
.pomo-pill.visible { display: block; }
/* The panel already shows the live countdown once open -- hiding the pill
   in that state avoids the two overlapping (panel width can reach past the
   pill's own position at right:14px). Relies on the pill being a later
   DOM sibling of .pomo-overlay (theme.js appends it after), which the
   general sibling combinator needs to match at all. */
.pomo-overlay.open ~ .pomo-pill { display: none !important; }
:root[data-theme="dark"] .pomo-pill {
  background: #191c22;
  color: #e5e7eb;
  border-color: #2a2e37;
}
.pomo-pill.pomo-pill-break {
  border-color: #86efac;
  color: #166534;
}
:root[data-theme="dark"] .pomo-pill.pomo-pill-break {
  border-color: #166534;
  color: #86efac;
}

@media (max-width: 480px) {
  .pomo-overlay { left: 12px; right: 12px; }
  .pomo-panel { width: auto; }
}

/* First-visit guided tour, shared engine (window.SiteTour) used both by
   theme.js itself (quiz-page steps) and index.html's own inline script
   (homepage steps). Hardcoded colors + dark override, same reasoning as
   the corner buttons and shortcuts overlay above. */
.tour-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  z-index: 4500;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.tour-overlay.open { display: flex; }
.tour-prompt {
  background: #ffffff;
  color: #1f2937;
  border: 1px solid #e5e7eb;
  border-radius: 16px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, .2);
  max-width: 320px;
  width: 100%;
  padding: 22px 22px 18px;
  text-align: center;
}
:root[data-theme="dark"] .tour-prompt {
  background: #191c22;
  color: #e5e7eb;
  border-color: #2a2e37;
}
.tour-prompt-title { margin: 0 0 8px; font-size: 16px; font-weight: 700; }
.tour-prompt-text { margin: 0 0 16px; font-size: 13px; color: #6b7280; }
:root[data-theme="dark"] .tour-prompt-text { color: #9aa1ac; }
.tour-prompt-actions { display: flex; gap: 10px; justify-content: center; }

.tour-spotlight {
  display: none;
  position: fixed;
  border-radius: 10px;
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, .55);
  z-index: 4501;
  pointer-events: none;
  transition: top .2s var(--ease), left .2s var(--ease), width .2s var(--ease), height .2s var(--ease);
}
.tour-tooltip {
  position: fixed;
  z-index: 4502;
  max-width: 300px;
  width: calc(100% - 32px);
  background: #ffffff;
  color: #1f2937;
  border: 1px solid #e5e7eb;
  border-radius: 14px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, .25);
  padding: 16px 16px 14px;
  transition: top .2s var(--ease), left .2s var(--ease);
}
:root[data-theme="dark"] .tour-tooltip {
  background: #191c22;
  color: #e5e7eb;
  border-color: #2a2e37;
}
.tour-tooltip-step {
  margin: 0 0 4px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: #2563eb;
}
:root[data-theme="dark"] .tour-tooltip-step { color: #60a5fa; }
.tour-tooltip-title { margin: 0 0 6px; font-size: 14.5px; font-weight: 700; }
.tour-tooltip-text { margin: 0 0 14px; font-size: 13px; line-height: 1.4; color: #4b5563; }
:root[data-theme="dark"] .tour-tooltip-text { color: #b6bcc6; }
.tour-tooltip-actions { display: flex; align-items: center; gap: 8px; }
.tour-btn {
  font: inherit;
  font-size: 12.5px;
  font-weight: 700;
  padding: 7px 13px;
  border-radius: 8px;
  border: 1px solid #e5e7eb;
  background: #f9fafb;
  color: #1f2937;
  cursor: pointer;
}
:root[data-theme="dark"] .tour-btn {
  background: #22262e;
  border-color: #2a2e37;
  color: #e5e7eb;
}
.tour-btn.primary { background: #2563eb; border-color: #2563eb; color: #fff; }
:root[data-theme="dark"] .tour-btn.primary { background: #60a5fa; border-color: #60a5fa; color: #0f1115; }

/* Cloud sync account button + panel (added 2026-07-10). Reuses the
   .tour-prompt/.tour-btn shell (prompt) and .settings-overlay/.settings-panel
   shell (account panel) for visual consistency instead of inventing new
   modal chrome -- see feedback_cloud_sync memory. */
.corner-btn.signed-in { position: relative; }
.corner-btn.signed-in::after {
  content: "";
  position: absolute;
  bottom: 2px;
  right: 2px;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: #16a34a;
  border: 2px solid #fff;
}
:root[data-theme="dark"] .corner-btn.signed-in::after { border-color: #191c22; }
.account-avatar {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  display: block;
  margin: 0 auto 12px;
}
.account-email { font-size: 13px; color: #6b7280; text-align: center; margin: 0 0 16px; }
:root[data-theme="dark"] .account-email { color: #9aa1ac; }
.account-name { font-size: 15px; font-weight: 700; text-align: center; margin: 0 0 2px; }
.cloudauth-google-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  padding: 11px 16px;
  border-radius: 10px;
  border: 1px solid #e5e7eb;
  background: #fff;
  color: #1f2937;
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}
.cloudauth-google-btn:hover { background: #f9fafb; }
:root[data-theme="dark"] .cloudauth-google-btn {
  background: #22262e;
  border-color: #2a2e37;
  color: #e5e7eb;
}
:root[data-theme="dark"] .cloudauth-google-btn:hover { background: #2a2e37; }

/* "Test yourself" popup quiz (window.openTestYourself in theme.js). Button
   uses var(--acc, ...) so it auto-themes per-section on pages that define
   that variable (the physiology field guide's per-system accent colors),
   falling back to the site blue anywhere it isn't defined. */
.test-yourself-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  width: fit-content;
  margin: 20px auto 4px;
  padding: 11px 20px;
  border-radius: 999px;
  border: none;
  background: var(--acc, #2563eb);
  color: #fff;
  font: 700 13.5px ui-sans-serif, system-ui, -apple-system, sans-serif;
  cursor: pointer;
}
.test-yourself-btn:hover { filter: brightness(0.92); }

.tq-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, .6);
  z-index: 3000;
  align-items: flex-start;
  justify-content: center;
  padding: 40px 16px;
  overflow-y: auto;
}
.tq-overlay.open { display: flex; }
.tq-panel {
  background: #ffffff;
  color: #1f2937;
  border-radius: 16px;
  max-width: 560px;
  width: 100%;
  padding: 26px 24px 30px;
  position: relative;
  box-shadow: 0 20px 60px rgba(0, 0, 0, .3);
}
:root[data-theme="dark"] .tq-panel {
  background: #191c22;
  color: #e5e7eb;
}
.tq-close {
  position: absolute;
  top: 14px;
  right: 14px;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: none;
  background: #e5e7eb;
  color: #1f2937;
  cursor: pointer;
  font-size: 17px;
  line-height: 1;
}
:root[data-theme="dark"] .tq-close {
  background: #2a2e37;
  color: #e5e7eb;
}
.tq-title { font-size: 16px; font-weight: 800; margin: 0 26px 4px 0; }
.tq-score { font-size: 13px; font-weight: 700; color: #6b7280; margin: 0 0 18px; }
:root[data-theme="dark"] .tq-score { color: #9aa1ac; }
.tq-q {
  margin-bottom: 20px;
  padding-bottom: 18px;
  border-bottom: 1px solid #e5e7eb;
}
:root[data-theme="dark"] .tq-q { border-bottom-color: #2a2e37; }
.tq-q:last-child { margin-bottom: 0; padding-bottom: 0; border-bottom: none; }
.tq-qtext { font-size: 14.5px; font-weight: 700; margin: 0 0 10px; }
.tq-opts { display: flex; flex-direction: column; gap: 6px; }
.tq-opt {
  text-align: left;
  font: inherit;
  font-size: 13.5px;
  padding: 9px 12px;
  border-radius: 9px;
  border: 1px solid #e5e7eb;
  background: #f9fafb;
  color: #1f2937;
  cursor: pointer;
}
:root[data-theme="dark"] .tq-opt {
  background: #22262e;
  border-color: #2a2e37;
  color: #e5e7eb;
}
.tq-opt:hover:not(:disabled) { border-color: #2563eb; }
.tq-opt:disabled { cursor: default; opacity: .8; }
.tq-opt.tq-correct { background: #dcfce7; border-color: #16a34a; color: #14532d; opacity: 1; }
:root[data-theme="dark"] .tq-opt.tq-correct { background: #12251a; border-color: #4ade80; color: #4ade80; }
.tq-opt.tq-wrong { background: #fee2e2; border-color: #dc2626; color: #7f1d1d; opacity: 1; }
:root[data-theme="dark"] .tq-opt.tq-wrong { background: #2a1414; border-color: #f87171; color: #f87171; }
.tq-explain { font-size: 12.5px; color: #6b7280; margin: 10px 0 0; font-style: italic; }
:root[data-theme="dark"] .tq-explain { color: #9aa1ac; }
.tq-explain.hidden { display: none; }

/* Exam Mode's question-jump grid (window.openQuestionNav in theme.js).
   Reuses the .tq-overlay/.tq-panel/.tq-close/.tq-title recipe above. */
.qnav-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, .6);
  z-index: 3000;
  align-items: flex-start;
  justify-content: center;
  padding: 40px 16px;
  overflow-y: auto;
}
.qnav-overlay.open { display: flex; }
.qnav-panel {
  background: #ffffff;
  color: #1f2937;
  border-radius: 16px;
  max-width: 420px;
  width: 100%;
  padding: 26px 24px 24px;
  position: relative;
  box-shadow: 0 20px 60px rgba(0, 0, 0, .3);
}
:root[data-theme="dark"] .qnav-panel {
  background: #191c22;
  color: #e5e7eb;
}
.qnav-summary { font-size: 13px; font-weight: 700; color: #6b7280; margin: 0 0 14px; }
:root[data-theme="dark"] .qnav-summary { color: #9aa1ac; }
.qnav-legend { display: flex; flex-wrap: wrap; gap: 12px; margin: 0 0 16px; }
.qnav-legend-item { font-size: 11.5px; font-weight: 600; color: #6b7280; }
:root[data-theme="dark"] .qnav-legend-item { color: #9aa1ac; }
.qnav-legend-item.flagged { color: #b45309; }
:root[data-theme="dark"] .qnav-legend-item.flagged { color: #fbbf24; }
.qnav-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(40px, 1fr));
  gap: 8px;
  margin: 0 0 20px;
}
.qnav-item {
  height: 40px;
  border-radius: 8px;
  border: 1.5px solid #e5e7eb;
  background: #f9fafb;
  color: #1f2937;
  font: 700 13px ui-sans-serif, system-ui;
  cursor: pointer;
  position: relative;
}
:root[data-theme="dark"] .qnav-item {
  background: #22262e;
  border-color: #2a2e37;
  color: #e5e7eb;
}
.qnav-item.answered { background: #dcfce7; border-color: #16a34a; color: #14532d; }
:root[data-theme="dark"] .qnav-item.answered { background: #12251a; border-color: #4ade80; color: #4ade80; }
.qnav-item.flagged { box-shadow: inset 0 0 0 2px #f59e0b; }
.qnav-item.current { outline: 2.5px solid #2563eb; outline-offset: 1px; }
:root[data-theme="dark"] .qnav-item.current { outline-color: #60a5fa; }
.qnav-submit { width: 100%; text-align: center; justify-content: center; }

/* In-quiz Exam Mode controls (flag button, question-nav opener) -- pill
   style matching .scorepill/.qcount already used in each quiz's topbar. */
.examctl-btn {
  font-size: 12.5px;
  font-weight: 800;
  padding: 5px 12px;
  border-radius: 999px;
  border: 1px solid #e5e7eb;
  background: #f9fafb;
  color: #374151;
  cursor: pointer;
}
:root[data-theme="dark"] .examctl-btn { background: #22262e; border-color: #2a2e37; color: #e5e7eb; }
.examctl-btn.flagged { background: #fef3c7; border-color: #f59e0b; color: #92400e; }
:root[data-theme="dark"] .examctl-btn.flagged { background: #3a2c0a; border-color: #f59e0b; color: #fbbf24; }
.examctl-timer.danger { background: #fee2e2 !important; border-color: #dc2626 !important; color: #7f1d1d !important; }
:root[data-theme="dark"] .examctl-timer.danger { background: #2a1414 !important; border-color: #f87171 !important; color: #f87171 !important; }

/* Pause/Resume (added 2026-07-10): the in-topbar Pause button reuses the
   .examctl-btn pill recipe (same visual family as Flag/Questions); the
   full-screen "paused" state reuses the .settings-overlay/.tq-overlay
   inset:0-backdrop-plus-centered-card recipe, since it needs to genuinely
   block the question (not a small floating panel like the calculator). */
.pause-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.7);
  z-index: 3000;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.pause-overlay.open { display: flex; }
.pause-card {
  background: #ffffff;
  color: #1f2937;
  border-radius: 16px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, .3);
  max-width: 360px;
  width: 100%;
  padding: 28px 26px;
  text-align: center;
}
:root[data-theme="dark"] .pause-card {
  background: #191c22;
  color: #e5e7eb;
}
.pause-title {
  font-size: 20px;
  font-weight: 800;
  margin: 0 0 10px;
}
.pause-text {
  font-size: 14px;
  color: #6b7280;
  margin: 0 0 20px;
  line-height: 1.5;
}
:root[data-theme="dark"] .pause-text { color: #9aa1ac; }
.pause-resume-btn {
  background: #2563eb;
  color: #fff;
  font-weight: 700;
  font-size: 15px;
  padding: 12px 26px;
  border-radius: 10px;
  border: none;
  cursor: pointer;
}
:root[data-theme="dark"] .pause-resume-btn { background: #3b82f6; }

/* Exam Mode toolbar (added 2026-07-10): a single row -- Flag, Questions, Submit --
   that appears directly under the progress bar in the exact same spot on every
   quiz/exam page, replacing the old inline pill buttons that lived in each
   family's own topbar (and so drifted to a different position per family).
   Bigger/bolder than .examctl-btn on its own so it reads as an obvious control
   bar, not something you have to hunt for. Each page aliases --exam-toolbar-accent
   to its own existing brand variable (see per-file :root override); the values
   below are a sane default for any page that doesn't. */
.examtoolbar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  flex-wrap: wrap;
  margin: 14px 0 18px;
  padding: 12px 14px;
  border-radius: 14px;
  background: #eef0fe;
  background: color-mix(in srgb, var(--exam-toolbar-accent, #4f46e5) 12%, transparent);
  border: 1.5px solid #c7cbf5;
  border-color: color-mix(in srgb, var(--exam-toolbar-accent, #4f46e5) 35%, transparent);
}
:root[data-theme="dark"] .examtoolbar {
  background: color-mix(in srgb, var(--exam-toolbar-accent, #4f46e5) 22%, transparent);
  border-color: color-mix(in srgb, var(--exam-toolbar-accent, #4f46e5) 50%, transparent);
}
.examtoolbar .examctl-btn-primary {
  font-size: 13.5px;
  font-weight: 800;
  padding: 9px 16px;
  border-radius: 999px;
  border: 1.5px solid var(--exam-toolbar-accent, #4f46e5);
  background: #fff;
  color: var(--exam-toolbar-accent, #4f46e5);
  cursor: pointer;
}
:root[data-theme="dark"] .examtoolbar .examctl-btn-primary { background: #1a1c22; }
.examtoolbar .examctl-btn-primary.flagged { background: #fef3c7; border-color: #f59e0b; color: #92400e; }
:root[data-theme="dark"] .examtoolbar .examctl-btn-primary.flagged { background: #3a2c0a; border-color: #f59e0b; color: #fbbf24; }
.examtoolbar .examctl-btn-submit {
  font-size: 13.5px;
  font-weight: 800;
  padding: 9px 18px;
  border-radius: 999px;
  border: none;
  background: var(--exam-toolbar-accent, #4f46e5);
  color: #fff;
  cursor: pointer;
}
.examtoolbar .examctl-btn-submit:hover { filter: brightness(1.08); }
@media (max-width: 480px) { .examtoolbar { justify-content: stretch; } .examtoolbar > button { flex: 1; } }

/* Dark mode on quiz pages is the invert filter on body > .wrap, so the dark
   overrides above were being inverted a second time: Flag/Questions came out
   as a light-grey pill with mid-blue text (2.2:1) and Pause as a glaring light
   pill on a black card. Inside the inverted wrapper, author these controls in
   their LIGHT colours and let the filter turn them dark (same rule
   calendar.html documents). The rules above still serve any page that shows
   these controls outside an inverted .wrap. */
:root[data-theme="dark"] body:not([data-dark="tokens"]) > .wrap .examctl-btn { background: #f9fafb; border-color: #e5e7eb; color: #374151; }
:root[data-theme="dark"] body:not([data-dark="tokens"]) > .wrap .examctl-btn.flagged { background: #fef3c7; border-color: #f59e0b; color: #92400e; }
:root[data-theme="dark"] body:not([data-dark="tokens"]) > .wrap .examctl-timer.danger { background: #fee2e2 !important; border-color: #dc2626 !important; color: #7f1d1d !important; }
:root[data-theme="dark"] body:not([data-dark="tokens"]) > .wrap .examtoolbar {
  background: color-mix(in srgb, var(--exam-toolbar-accent, #4f46e5) 12%, transparent);
  border-color: color-mix(in srgb, var(--exam-toolbar-accent, #4f46e5) 35%, transparent);
}
:root[data-theme="dark"] body:not([data-dark="tokens"]) > .wrap .examtoolbar .examctl-btn-primary { background: #fff; }
:root[data-theme="dark"] body:not([data-dark="tokens"]) > .wrap .examtoolbar .examctl-btn-primary.flagged { background: #fef3c7; border-color: #f59e0b; color: #92400e; }

/* Guide text highlighting (added 2026-07-10). mark.user-hl-* colors are
   deliberately NOT given a :root[data-theme="dark"] override -- they live
   inside body > .wrap, which already gets a blanket
   filter:invert(1) hue-rotate(180deg) for dark mode (see that rule above).
   Adding an explicit dark variant here would double up against that filter
   and likely land on a muddier color than just letting the same invert
   trick every other guide/quiz color already relies on handle it. The
   floating .hl-toolbar itself is NOT inside .wrap (appended to body), so it
   does need to look reasonable in both themes on its own -- kept as one
   dark, low-contrast-neutral pill that reads fine over either page theme
   rather than defining two variants. */
mark.user-hl {
  background: transparent;
  color: inherit;
  padding: 0 1px;
  border-radius: 3px;
  cursor: pointer;
}
mark.user-hl-yellow { background: rgba(253, 224, 71, .55); }
mark.user-hl-green  { background: rgba(134, 239, 172, .55); }
mark.user-hl-pink   { background: rgba(249, 168, 212, .55); }
mark.user-hl-blue   { background: rgba(147, 197, 253, .55); }

/* Other students' shared highlights (opt-in, theme.js's sharedHl module,
   added 2026-07-14) -- deliberately a neutral dotted underline, not one of
   the 4 personal highlight colors above or the orange search-hit color, so
   "someone else marked this" always reads as visually distinct from "I
   marked this" at a glance. Read-only (no click handler, no cursor:pointer,
   no color swatches) -- unlike mark.user-hl, nothing happens on click.
   Safe to nest inside (or contain) a user-hl/search-hit mark -- see the
   containment-safety reasoning in the search-hit implementation above,
   same TreeWalker-based text-node wrapping, no shared state between them. */
mark.shared-hl {
  background: rgba(148, 163, 184, .16);
  border-bottom: 2px dotted rgba(100, 116, 139, .65);
  padding: 0 1px;
  border-radius: 2px;
  cursor: default;
}

.hl-toolbar {
  position: absolute;
  z-index: 3000;
  display: flex;
  gap: 6px;
  background: #1f2937;
  padding: 6px;
  border-radius: 10px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, .3);
}
.hl-swatch {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, .55);
  cursor: pointer;
  padding: 0;
}
.hl-swatch:hover { border-color: #fff; }
.hl-swatch-yellow { background: #fde047; }
.hl-swatch-green  { background: #86efac; }
.hl-swatch-pink   { background: #f9a8d4; }
.hl-swatch-blue   { background: #93c5fd; }

/* Note indicator on an annotated highlight (added 2026-07-11) -- a
   ::after pseudo-element, deliberately not a real inserted DOM node (see
   the JS comment on setMarkNote): a real node would become part of the
   mark's textContent and permanently corrupt both the highlighted text
   itself and every future textOffset() computation inside that unit. */
mark.user-hl.has-note::after {
  content: "📝";
  font-size: 10px;
  vertical-align: super;
  margin-left: 1px;
  line-height: 0;
}

/* Note popover -- same "dark, theme-agnostic pill" treatment as
   .hl-toolbar and for the same reason (appended to body, outside .wrap,
   so it doesn't get the generic dark-mode invert every guide's own
   content does and needs to look right on its own in both themes). */
.hl-note-popover {
  position: absolute;
  z-index: 3000;
  width: 240px;
  background: #1f2937;
  padding: 10px;
  border-radius: 10px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, .3);
}
.hl-note-textarea {
  width: 100%;
  min-height: 60px;
  resize: vertical;
  box-sizing: border-box;
  font: 13px/1.4 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  color: #e5e7eb;
  background: #111827;
  border: 1px solid #374151;
  border-radius: 6px;
  padding: 6px 8px;
}
.hl-note-textarea::placeholder { color: #6b7280; }
.hl-note-row {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  margin-top: 8px;
}
.hl-note-btn {
  font: 600 12px ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  border: none;
  border-radius: 6px;
  padding: 6px 10px;
  cursor: pointer;
}
.hl-note-remove { background: #374151; color: #fca5a5; }
.hl-note-remove:hover { background: #4b5563; }
.hl-note-save { background: #2563eb; color: #fff; }
.hl-note-save:hover { background: #1d4ed8; }

/* Recolor row inside the note popover (added 2026-07-13) -- same swatch
   look as .hl-toolbar's, just smaller and with a ring marking whichever
   color is currently applied so it reads as a selection, not a fresh
   choice every time you reopen a highlight you already colored. */
.hl-note-colors { display: flex; gap: 6px; margin-bottom: 8px; }
.hl-note-colors .hl-swatch { width: 18px; height: 18px; }
.hl-swatch-current { box-shadow: 0 0 0 2px #1f2937, 0 0 0 4px #fff; }

/* "Your highlights" review panel (added 2026-07-13) -- same overlay+
   centered-card treatment as .shortcuts-panel/.settings-panel, just
   wider and scrollable since its content is a variable-length list
   rather than a short fixed menu. */
.hl-panel-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  z-index: 2000;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.hl-panel-overlay.open { display: flex; }
.hl-panel {
  background: #ffffff;
  color: #1f2937;
  border: 1px solid #e5e7eb;
  border-radius: 16px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, .2);
  max-width: 460px;
  width: 100%;
  max-height: min(560px, 80vh);
  padding: 22px 22px 20px;
  position: relative;
  display: flex;
  flex-direction: column;
}
:root[data-theme="dark"] .hl-panel {
  background: #191c22;
  color: #e5e7eb;
  border-color: #2a2e37;
}
.hl-panel h3 { margin: 0 0 14px; font-size: 15px; font-weight: 700; flex: none; }
.hl-panel-close {
  position: absolute;
  top: 12px;
  right: 12px;
  border: none;
  background: #e5e7eb;
  color: #1f2937;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  cursor: pointer;
  line-height: 1;
  font-size: 15px;
}
:root[data-theme="dark"] .hl-panel-close { background: #2a2e37; color: #e5e7eb; }
.hl-panel-empty {
  margin: 0;
  padding: 24px 4px;
  text-align: center;
  color: #6b7280;
  font-size: 13.5px;
}
.hl-panel-list { overflow-y: auto; flex: 1; margin: 0 -6px; padding: 0 6px; }
.hl-panel-item {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  padding: 10px 0;
  border-bottom: 1px solid #f3f4f6;
}
:root[data-theme="dark"] .hl-panel-item { border-bottom-color: #22262e; }
.hl-panel-item:last-child { border-bottom: none; }
.hl-panel-swatch {
  flex: none;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  margin-top: 4px;
}
.hl-panel-text { flex: 1; min-width: 0; }
.hl-panel-excerpt {
  margin: 0;
  font-size: 13.5px;
  line-height: 1.45;
  font-style: italic;
  color: inherit;
}
.hl-panel-note {
  margin: 4px 0 0;
  font-size: 12.5px;
  color: #6b7280;
  background: #f9fafb;
  border-left: 2px solid #d1d5db;
  padding: 4px 8px;
  border-radius: 0 4px 4px 0;
}
:root[data-theme="dark"] .hl-panel-note { background: #1f2329; border-left-color: #374151; color: #9ca3af; }
.hl-panel-actions { flex: none; display: flex; flex-direction: column; align-items: flex-end; gap: 4px; }
.hl-panel-jump {
  font: 600 11.5px ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  border: none;
  background: transparent;
  color: #2563eb;
  cursor: pointer;
  padding: 2px 0;
}
.hl-panel-jump:hover { text-decoration: underline; }
.hl-panel-delete {
  border: none;
  background: transparent;
  color: #9ca3af;
  cursor: pointer;
  font-size: 15px;
  line-height: 1;
  padding: 2px 4px;
}
.hl-panel-delete:hover { color: #dc2626; }

/* Brief pulse when "Jump to" scrolls a highlight into view, so it's
   obvious which one on a page full of marks is the one you asked for --
   layered on top of whatever color the mark already has, not replacing
   it, and independent of the dark-mode invert since it uses an outline/
   box-shadow rather than a background-color swap. */
mark.user-hl.hl-flash {
  animation: hl-flash-pulse 1.6s ease-out;
}
@keyframes hl-flash-pulse {
  0%, 40% { box-shadow: 0 0 0 3px #f59e0b; }
  100% { box-shadow: 0 0 0 3px transparent; }
}

/* Answer-streak toast (added 2026-07-10) -- reuses the same visual recipe
   index.html's own page-local showToast() already used, now shared via
   theme.js so any quiz page can call window.showToast() too. */
#site-toast {
  position: fixed;
  bottom: 24px;
  left: 50%;
  transform: translateX(-50%);
  background: #111827;
  color: #fff;
  padding: 10px 18px;
  border-radius: 999px;
  font: 600 13px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  z-index: 5000;
  box-shadow: 0 6px 20px rgba(0, 0, 0, .3);
  opacity: 0;
  transition: opacity .2s var(--ease);
  max-width: 80vw;
  text-align: center;
  pointer-events: none;
}

/* Freehand stylus/Apple Pencil drawing layer (guide-ink.js, added
   2026-07-14). #guide-ink-svg spans the guide's full scroll height (set/
   kept in sync by a ResizeObserver in JS) -- pointer-events stays "none"
   until the toolbar is open, so the layer never blocks normal reading,
   scrolling, link clicks, or text selection when the feature isn't in
   active use. */
#guide-ink-svg {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  pointer-events: none;
  /* touch-action has no way to say "pan-y for touch, none for pen" on the
     same element -- it's a whole-element hint, not scoped per pointer
     type, and WebKit routes Apple Pencil through the same gesture-
     recognition pipeline as touch. pan-y here let a *vertical* pen stroke
     get natively absorbed as a page scroll before guide-ink.js's own
     preventDefault() ever got a say -- confirmed by an actual user
     reporting exactly that ("when writing it thinks I'm scrolling if I
     move vertically"). none guarantees a pen stroke is never hijacked;
     guide-ink.js reimplements touch scrolling manually in JS (see
     onPointerDown's touch branch) so finger-scroll while the toolbar is
     open doesn't just regress as the tradeoff for that guarantee. */
  touch-action: none;
}
.ink-overlay {
  display: none;
  position: fixed;
  top: 60px;
  right: 110px;
  z-index: 2000;
}
.ink-overlay.open { display: block; }
.ink-toolbar {
  background: #ffffff;
  color: #1f2937;
  border: 1px solid #e5e7eb;
  border-radius: 14px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, .2);
  width: 200px;
  padding: 12px;
}
:root[data-theme="dark"] .ink-toolbar {
  background: #191c22;
  color: #e5e7eb;
  border-color: #2a2e37;
}
.ink-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 13px;
  font-weight: 700;
  margin-bottom: 10px;
  cursor: move;
  touch-action: none;
  user-select: none;
}
.ink-close {
  background: none;
  border: none;
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  color: inherit;
  padding: 0 2px;
}
.ink-tool-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 6px;
  margin-bottom: 10px;
}
.ink-tool-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid #e5e7eb;
  background: #f9fafb;
  color: #1f2937;
  border-radius: 8px;
  padding: 9px 0;
  cursor: pointer;
}
.ink-tool-btn.active {
  background: #2563eb;
  color: #fff;
  border-color: #2563eb;
}
:root[data-theme="dark"] .ink-tool-btn {
  background: #20242c;
  color: #e5e7eb;
  border-color: #2a2e37;
}
:root[data-theme="dark"] .ink-tool-btn.active {
  background: #3b82f6;
  border-color: #3b82f6;
}
.ink-color-row {
  display: flex;
  gap: 8px;
  justify-content: center;
  min-height: 26px;
  margin-bottom: 10px;
}
.ink-swatch {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  border: 2px solid transparent;
  cursor: pointer;
  padding: 0;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .12);
}
.ink-swatch.active {
  border-color: #2563eb;
}
:root[data-theme="dark"] .ink-swatch.active {
  border-color: #3b82f6;
}
.ink-actions-row {
  display: flex;
  gap: 6px;
}
.ink-action-btn {
  flex: 1;
  border: 1px solid #e5e7eb;
  background: #f9fafb;
  color: #1f2937;
  border-radius: 8px;
  padding: 8px 0;
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
}
.ink-action-btn.danger {
  color: #dc2626;
}
:root[data-theme="dark"] .ink-action-btn {
  background: #20242c;
  color: #e5e7eb;
  border-color: #2a2e37;
}
:root[data-theme="dark"] .ink-action-btn.danger {
  color: #f87171;
}
@media (max-width: 480px) {
  .ink-overlay { left: 12px; right: 12px; top: 60px; }
  .ink-toolbar { width: auto; }
}

/* Quiz-completion celebration confetti (added 2026-07-17) -- same visual
   design as arcade.css's .confetti-piece/@keyframes confetti-fall (used on
   an Arcade win), ported here so quiz pages -- which load theme.css/theme.js
   but not arcade.css/arcade.js -- get it too. See theme.js's celebrateScore()
   for what triggers it. */
.confetti-piece {
  position: fixed; top: -20px; width: 8px; height: 14px; z-index: 9999; pointer-events: none;
  animation: confetti-fall linear forwards; border-radius: 2px;
}
@keyframes confetti-fall {
  to { transform: translateY(110vh) rotate(540deg); opacity: .3; }
}
@media (prefers-reduced-motion: reduce) {
  .confetti-piece { animation: none; display: none; }
}

/* ============================================================
   Print / "Download as PDF" (theme.js's #pdf-btn on guides & aids).
   Strips all page chrome so the exported PDF is just the content.
   The button forces light theme before printing, so these rules only
   need to handle layout + hiding, not dark-mode color inversion.
   ============================================================ */
@media print {
  /* interactive chrome / overlays / decorative layers */
  #corner-actions, #corner-actions-left, .guide-back-bar, #presence-pill,
  #pull-refresh, #quiz-footer-logo, #guide-ink-svg, .ink-overlay, .tour-overlay,
  .toc-fab, #install-app-btn, .guide-search-bar, canvas { display: none !important; }
  /* the guide's Contents sidebar is navigation -- drop it, give the body the
     full page width */
  nav.toc { display: none !important; }
  .layout { display: block !important; }
  main { padding: 0 !important; max-width: none !important; }
  html, body { background: #fff !important; }
  /* don't clip wide tables (muscle O/I/A/N reference) at a scroll box edge */
  .table-scroll { overflow: visible !important; }
  /* keep related blocks together across page breaks where reasonable */
  tr, figure, .callout, .pearl, .io-box { break-inside: avoid; }
  h2, h3, h4 { break-after: avoid; }
  a { text-decoration: none !important; color: inherit !important; }
}

/* Download-as-PDF button states while it warms lazy figures before printing
   (see theme.js). Purely a status cue -- the button stays clickable. */
#pdf-btn.is-preparing { opacity: .62; cursor: progress; }
#pdf-btn.is-preparing svg { animation: pdf-warm 1.1s ease-in-out infinite; }
@keyframes pdf-warm { 0%, 100% { opacity: .45; } 50% { opacity: 1; } }
#pdf-btn.is-ready { box-shadow: 0 0 0 2px var(--accent, #2f6f6b); }
@media (prefers-reduced-motion: reduce) {
  #pdf-btn.is-preparing svg { animation: none; }
}

/* ============================================================
   POLISH PASS (2026-07-27)
   ------------------------------------------------------------
   1. Footer mark: reveal only at the end of the page
   ------------------------------------------------------------
   #quiz-footer-logo is inline-styled `position:fixed; bottom:14px;
   z-index:500` on 83 pages. It reads as a footer flourish on short
   pages, but on long ones (guides, cram sheets, study guides) it
   floats permanently over real content -- verified drawing on top of
   <td> cells, .guide-card links and body paragraphs. It is
   pointer-events:none so it never blocked clicks, only legibility.

   Rather than edit 83 files, gate it on a class theme.js toggles when
   the reader actually reaches the bottom (or the page is too short to
   scroll). Opacity isn't set inline, so this needs no !important.
   ============================================================ */
#quiz-footer-logo {
  opacity: 0;
  transition: opacity .34s cubic-bezier(.2,.8,.2,1);
}
#quiz-footer-logo.mark-visible { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  #quiz-footer-logo { transition: none; }
}

/* ============================================================
   2. Surface accent system
   ------------------------------------------------------------
   Each surface keeps its own hue -- that differentiation is the
   point -- but every accent is now built to ONE recipe: the
   lightness that lands it on 4.60:1 against its background, at
   ~90% of the maximum chroma sRGB can hold there.

   Before, the accents ranged from 3.19:1 (amber, green) to 6.29:1
   (indigo). Three failed AA outright, and the mismatch is what made
   them read as five unrelated picks rather than a set.

   Hue map -- home/quizzes blue, Arcade violet, Guides amber,
   Group Study teal, Progress green. Arcade additionally uses all
   four mode hues, because it contains all four modes.
   ============================================================ */
:root {
  --a-blue:        #2a6df2;  --a-blue-hover:   #185bdf;  --a-blue-soft:   #eff5ff;
  --a-violet:      #9c46f3;  --a-violet-hover: #8b30e0;  --a-violet-soft: #f7f2ff;
  --a-amber:       #ad631e;  --a-amber-hover:  #9b5301;  --a-amber-soft:  #fff2e8;
  --a-teal:        #228283;  --a-teal-hover:   #007172;  --a-teal-soft:   #defbfb;
  --a-green:       #218640;  --a-green-hover:  #007630;  --a-green-soft:  #e7fbea;
  /* Review (added 2026-07-27). Sixth hue in the ring, derived with the same
     recipe: the lightness that lands hue 20 on 4.60:1 against its background
     at ~90% of the maximum chroma sRGB holds there. Verified 4.61:1. */
  --a-crimson:     #df2a44;  --a-crimson-hover:#c2233a;  --a-crimson-soft:#faf2f2;
}
:root[data-theme="dark"] {
  --a-blue:        #4981ee;  --a-blue-hover:   #6a99f2;  --a-blue-soft:   #14223d;
  --a-violet:      #a465ee;  --a-violet-hover: #b783f2;  --a-violet-soft: #281c39;
  --a-amber:       #c0722d;  --a-amber-hover:  #d08a4e;  --a-amber-soft:  #361b04;
  --a-teal:        #329293;  --a-teal-hover:   #4fa8a9;  --a-teal-soft:   #00292a;
  --a-green:       #31974d;  --a-green-hover:  #4fac68;  --a-green-soft:  #0c2a13;
  --a-crimson:     #f53c52;  --a-crimson-hover:#f76f74;  --a-crimson-soft:#3c1718;
}

/* ------------------------------------------------------------
   3. Radius + elevation scale
   ------------------------------------------------------------
   theme.css previously carried twelve ad-hoc radii (2,3,4,5,6,8,
   9,10,14,16,50%,999px) and exactly two shadow tokens. Four radii
   and a three-step ramp instead. The shadows are deliberately
   wider and fainter than before, paired with a hairline border --
   that reads as depth without a visible drop shadow.
   ------------------------------------------------------------ */
:root {
  --r-sm: 8px;
  --r-md: 12px;
  --r-lg: 16px;
  --r-pill: 999px;

  --e-1: 0 1px 2px rgba(16,20,28,.05), 0 1px 3px rgba(16,20,28,.04);
  --e-2: 0 2px 4px rgba(16,20,28,.04), 0 8px 20px rgba(16,20,28,.06);
  --e-3: 0 6px 12px rgba(16,20,28,.06), 0 20px 44px rgba(16,20,28,.09);

  --ease: cubic-bezier(.2,.8,.2,1);
  --dur-1: 160ms;
  --dur-2: 240ms;
}
:root[data-theme="dark"] {
  --e-1: 0 1px 2px rgba(0,0,0,.42), 0 1px 3px rgba(0,0,0,.34);
  --e-2: 0 2px 4px rgba(0,0,0,.40), 0 8px 20px rgba(0,0,0,.46);
  --e-3: 0 6px 12px rgba(0,0,0,.46), 0 20px 44px rgba(0,0,0,.56);
}

/* ------------------------------------------------------------
   4. Type system
   ------------------------------------------------------------
   A display serif for page titles only -- body copy, UI chrome and
   quiz content stay on the existing sans. Serif is the register of
   medical and academic publishing, and it gives real hierarchy
   instead of leaving size to do all the work on its own.

   System stack only: no webfont, no network cost, works offline in
   the installed app. `ui-serif` first for the platform's own
   optical serif, Charter/Georgia as the workhorse fallbacks.
   ------------------------------------------------------------ */
:root {
  --font-display: ui-serif, "Iowan Old Style", "Charter", Georgia, "Times New Roman", serif;
  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --measure: 68ch;
}

/* Safety net: any page that never sets a body font would otherwise fall
   back to the browser's default serif (Times). :where() keeps this at zero
   specificity, so every page that DOES set its own font still wins. */
:where(body) { font-family: var(--font-sans); }

/* Page titles across the app shell. Scoped to the shells + guide/cram
   banners so no quiz question text is ever touched. */
body.homepage .brand-title,
body:not(.homepage) > .wrap > h1,
.guide-hero h1,
header.top h1,
h1.page-title,
.deck-title,
h2.deck-title {
  font-family: var(--font-display);
  letter-spacing: -0.021em;
  font-weight: 700;
}

/* No sideways panning on phones (design review item 6, 2026-09-25). A
   sweep of every guide, cram sheet and chart at 390px found 21 pages wider
   than the screen (CMS E2 guide 537px, Clin Path E1 554px, PD2 E1 682px),
   almost all from one wide comparison table, a few from a fixed-width figure
   or an unbreakable slash-joined word.
   A wide table now scrolls inside its own box -- display:block turns the
   table element into the scroll container while its rows still lay out as a
   table -- and figures never exceed the column. Scoped to guide-type pages'
   <main>, so quiz tables and practicum overlays are untouched. */
@media (max-width: 640px) {
  body > .wrap main table,
  body > .layout main table {
    display: block;
    max-width: 100%;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;
  }
  /* !important only to beat inline max-width:560px on older guides' figures */
  body > .wrap main img,
  body > .layout main img { max-width: 100% !important; height: auto; }
  /* long slash-joined runs ("deformities/asymmetry/polyps/...") break only
     when they would otherwise overflow */
  body > .wrap main,
  body > .layout main { min-width: 0; overflow-wrap: break-word; }
}

/* Long-form reading measure. Study guides currently run ~110 characters
   per line; 68 is the comfortable range. Applies to prose only -- tables,
   figures and the palpation panels keep the full column width. */
main > p,
main > ul,
main > ol,
.io-box,
.callout,
.pearl,
.tech {
  max-width: var(--measure);
}

/* Tabular figures for anything numeric that updates in place, so the
   digits stop shuffling sideways as they change. */
#class-counter-value,
#semester-progress ~ *,
.streak-pill b,
.score,
#finalPct {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* ------------------------------------------------------------
   5. Quiz: collapse the title banner once the attempt starts
   ------------------------------------------------------------
   The exam-navigator banner (a 210px navy gradient block carrying the
   title, subtitle and question-count pill) is useful on the start
   screen and pure overhead on every question after it -- it pushed the
   actual question below the fold on a laptop.

   theme.js adds .quiz-running to <body> when #quiz becomes visible, so
   the banner shrinks to a slim title bar for the duration and expands
   again on the results screen. Lives here rather than in the template
   because every generated quiz already loads theme.css -- no need to
   regenerate 250+ files.
   ------------------------------------------------------------ */
/* Plain descendant selector, deliberately: an unsupported :has() inside a
   comma-separated list invalidates the ENTIRE rule, so one older Safari
   would have lost the whole collapse. .wrap > header is the real structure
   in every generated quiz anyway. */
body.quiz-running .wrap > header {
  padding: 13px 20px;
  margin-bottom: 12px;
  transition: padding var(--dur-2, 240ms) var(--ease, ease);
}
body.quiz-running header h1 {
  font-size: 15.5px;
  line-height: 1.3;
  margin: 0;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 1;
  overflow: hidden;
}
body.quiz-running header .sub,
body.quiz-running header .pill { display: none; }
@media (prefers-reduced-motion: reduce) {
  body.quiz-running .wrap > header { transition: none; }
}

/* Question first on phones (design review item 4, 2026-09-25). At 390px the
   first screen used to be ~60% chrome: a 100px top gap, the back link, the
   banner, a wrapping stats row, a tinted toolbar box whose "Submit exam"
   wrapped to three lines, and a three-line objective chip -- only 2.5 of 4
   choices were visible. While a question is on screen:
     - the top gap goes (back link's inline 36px margin, wrap padding);
     - the stats row and the Flag / Questions / Submit row become two slim
       sticky rows (every button stays in the DOM -- swipe navigation reads
       the on-screen buttons for permission, and the engine finds them by id);
     - the objective chip is clamped to one line.
   The keyboard-shortcuts corner button is hidden on touch-only phones while a
   quiz runs (there is no keyboard to use it with) so the settings gear can sit
   at the end of the sticky row instead of over the timer. */
@media (max-width: 640px) {
  body.quiz-running > .wrap { padding-top: 6px; }
  body.quiz-running > .wrap > a[href$="index.html"]:first-child { margin: 2px 0 8px !important; }
  body.quiz-running #quiz-header-logo { margin-bottom: 4px !important; }
  body.quiz-running .wrap > header { padding: 10px 14px; margin-bottom: 10px; border-radius: 12px; }
  body.quiz-running header h1 { font-size: 14.5px; }
  body.quiz-running #quiz .qbody { padding: 0 16px 18px; }
  body.quiz-running #quiz { overflow: visible; }

  body.quiz-running #quiz .topbar {
    position: sticky; top: 0; z-index: 20;
    box-sizing: border-box; min-height: 46px;
    margin: 0 -16px; padding: 8px 60px 8px 16px;
    gap: 8px; flex-wrap: nowrap; white-space: nowrap;
    font-size: 12px;
    background: var(--card, #fff);
    border-radius: 14px 14px 0 0;
  }
  body.quiz-running #quiz .topbar .bar { flex: 1 1 0; min-width: 0; height: 6px; }
  body.quiz-running #quiz .topbar .examctl-btn { padding: 4px 9px; font-size: 12px; }

  body.quiz-running #quiz .examtoolbar {
    position: sticky; top: 46px; z-index: 19;
    margin: 0 -16px 10px; padding: 6px 16px 8px;
    gap: 6px; flex-wrap: nowrap; justify-content: flex-start;
    overflow-x: auto; scrollbar-width: none;
    background: var(--card, #fff);
    border: 0; border-bottom: 1px solid var(--line, #e5e7eb); border-radius: 0;
  }
  body.quiz-running #quiz .examtoolbar > button {
    flex: 0 0 auto; white-space: nowrap;
    font-size: 12px; padding: 6px 10px;
  }
  body.quiz-running #quiz .examtoolbar > .examctl-btn-submit { margin-left: auto; }
  body.quiz-running #quiz .examtoolbar.hidden { display: none; }

  body.quiz-running #quiz .qmeta { flex-wrap: nowrap; min-width: 0; margin-bottom: 8px; }
  body.quiz-running #quiz .qmeta .tag {
    min-width: 0; max-width: 100%;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  body.quiz-running #quiz .qmeta .tag:empty { display: none; }
}
@media (max-width: 640px) and (hover: none) and (pointer: coarse) {
  body.quiz-running #shortcuts-btn { display: none; }
}
/* Hide the live running score mid-quiz on phones (design review item 4,
   shipped as its own commit so it can be reverted alone). UWorld never shows
   it mid-block; the score still appears on the results screen, and the
   element stays in the DOM for the engine to keep updating. */
@media (max-width: 640px) {
  body.quiz-running #quiz .topbar #scoretxt { display: none; }
}

/* ============================================================
   6. MOTION LAYER (2026-07-27)
   ------------------------------------------------------------
   The site had 52 transitions on bare `ease` -- the browser default,
   which is what makes motion read as unconsidered -- plus five
   different ad-hoc cubic-beziers. One small set of curves instead,
   each with a job.
   ============================================================ */
:root {
  /* standard: decisive start, soft landing. Everything that moves
     between two states uses this unless it has a reason not to. */
  --ease: cubic-bezier(.2,.8,.2,1);
  /* entrances that should feel alive -- a small overshoot */
  --ease-spring: cubic-bezier(.34,1.4,.64,1);
  /* looping ambient motion, symmetric so the loop point is invisible */
  --ease-amb: cubic-bezier(.45,0,.55,1);
  --dur-1: 160ms;   /* hover / colour */
  --dur-2: 240ms;   /* panels, reveals */
  --dur-3: 420ms;   /* page-level entrances */
}

/* ------------------------------------------------------------
   Focus. There was not one :focus-visible rule on the whole site,
   so keyboard users got whatever the browser felt like drawing.
   One ring, in the surface's own accent, that animates in.
   ------------------------------------------------------------ */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--accent, var(--a-blue));
  outline-offset: 2px;
  border-radius: var(--r-sm, 8px);
  animation: focus-in var(--dur-1) var(--ease) 1;
}
@keyframes focus-in {
  from { outline-color: transparent; outline-offset: 6px; }
}
/* Mouse users never see it; only remove the default for pointer focus. */
:where(a, button, summary):focus:not(:focus-visible) { outline: none; }

/* ------------------------------------------------------------
   Press feedback. A control that doesn't acknowledge the press
   feels dead on touch, where there's no hover to fall back on.
   ------------------------------------------------------------ */
:where(button, .btn, .tab-btn, .quiz-link, .guide-card, .deck-card,
       .test-yourself-btn, .relax-cta):active {
  transform: scale(.985);
  transition-duration: 80ms;
}

/* ------------------------------------------------------------
   Staggered reveal. Used for lists that appear as a group -- guide
   cards, quiz links inside an opened exam, arcade decks. The delay
   is set inline by theme.js (--i) so it works for any list length.
   ------------------------------------------------------------ */
@keyframes rise-in {
  from { opacity: 0; transform: translateY(7px); }
  to   { opacity: 1; transform: none; }
}
.stagger-in {
  animation: rise-in var(--dur-3) var(--ease) both;
  animation-delay: calc(var(--i, 0) * 38ms);
}

/* Exam accordions: the panel used to appear instantly. */
@keyframes panel-open {
  from { opacity: 0; transform: translateY(-4px); }
}
.exam-section[open] .exam-content,
details[open] > *:not(summary) {
  animation: panel-open var(--dur-2) var(--ease) both;
}

/* Tab panels: crossfade instead of a hard cut. */
.tab-panel.active { animation: rise-in var(--dur-2) var(--ease) both; }

/* ------------------------------------------------------------
   One switch turns all of it off.
   ------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
  .stagger-in,
  .tab-panel.active,
  .exam-section[open] .exam-content,
  details[open] > *:not(summary) { animation: none !important; }
  :where(a, button, input, select, textarea, summary, [tabindex]):focus-visible { animation: none; }
  :where(button, .btn, .tab-btn, .quiz-link, .guide-card, .deck-card,
         .test-yourself-btn, .relax-cta):active { transform: none; }
}

/* ============================================================
   7. CONTENT-PAGE MOTION (2026-07-27)
   ------------------------------------------------------------
   The 431 quiz/exam pages, 12 study guides and 12 cram sheets carried
   three transitions and one keyframe between them -- all the motion
   work so far had landed on the app-shell pages, which is not where
   the time is actually spent.

   Everything here hangs off classes the existing engines already set
   (.opt.correct/.wrong, .expl.show, #results) or off a MutationObserver
   in theme.js, so no generated page needs regenerating.
   ============================================================ */

/* --- question change ------------------------------------------------
   renderQ() swapped the stem and options instantly. theme.js flags the
   card when #qtext changes; stem and options come in on a short offset
   so the eye lands on the question first. */
@keyframes q-enter {
  from { opacity: 0; transform: translateY(6px); }
}
.q-enter #qtext { animation: q-enter var(--dur-2) var(--ease) both; }
.q-enter #opts  { animation: q-enter var(--dur-2) var(--ease) 60ms both; }

/* --- answer feedback ------------------------------------------------
   A right answer settles; a wrong one nudges. Both are small: this is
   a study tool, not a slot machine. */
@keyframes opt-correct {
  0%   { transform: none; }
  38%  { transform: scale(1.014); }
  100% { transform: none; }
}
@keyframes opt-wrong {
  0%,100% { transform: none; }
  15%     { transform: translateX(-6px); }
  35%     { transform: translateX(6px); }
  55%     { transform: translateX(-4px); }
  75%     { transform: translateX(3px); }
  90%     { transform: translateX(-1px); }
}
/* a soft ring that breathes outward from the right answer, once */
@keyframes opt-ring {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--ok, #218640) 42%, transparent); }
  100% { box-shadow: 0 0 0 11px color-mix(in srgb, var(--ok, #218640) 0%, transparent); }
}
.opt.correct { animation: opt-correct 380ms var(--ease) 1, opt-ring 720ms ease-out 1; }
.opt.wrong   { animation: opt-wrong  440ms var(--ease) 1; }
/* The letter badge becomes a check (right) or a cross (wrong) that DRAWS itself; theme.js injects
   the svg when an option gains .correct/.wrong, and the letter is kept in the DOM for screen readers. */
.opt .ltr.fx-has-mark { font-size: 0; animation: fx-pop .34s cubic-bezier(.3,1.6,.5,1) 1; }
.opt .ltr .fx-mark { width: 16px; height: 16px; fill: none; stroke: #fff; stroke-width: 2.7; stroke-linecap: round; stroke-linejoin: round; }
.opt .ltr .fx-mark path { stroke-dasharray: 1; stroke-dashoffset: 1; animation: fx-draw .36s ease .1s forwards; }
.opt .ltr .fx-mark path + path { animation-delay: .24s; }
@keyframes fx-draw { to { stroke-dashoffset: 0; } }
@keyframes fx-pop { 0% { transform: scale(.6); } 100% { transform: none; } }
/* the eliminated options recede rather than just dimming */
.opt.dim { transition: opacity var(--dur-2) var(--ease), filter var(--dur-2) var(--ease); }

/* --- explanation ---------------------------------------------------- */
.expl.show { animation: q-enter var(--dur-2) var(--ease) both; }

/* --- results --------------------------------------------------------- */
@keyframes results-in {
  from { opacity: 0; transform: translateY(10px) scale(.995); }
}
#results { animation: results-in var(--dur-3) var(--ease-spring) both; }

/* Results reveal (theme.js "Results reveal"): rolling percentage digits + rows that slide in one by one.
   The digits are drawn with ::before so the real number is never doubled in the page text. */
.rs-sr { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.rs-od { display: inline-flex; height: 1em; line-height: 1; overflow: hidden; vertical-align: top; font-variant-numeric: tabular-nums; }
.rs-d { display: block; height: 1em; overflow: hidden; }
.rs-r { display: flex; flex-direction: column; transform: translateY(calc(var(--d, 0) * -1em)); transition: transform 1s cubic-bezier(.33, 1, .68, 1); }
.rs-c { display: block; height: 1em; line-height: 1; }
.rs-c::before { content: attr(data-d); }
.rs-p::before { content: "%"; }
#results.rs-go .brow, #results.rs-go .mitem.rs-it { animation: rs-slide .38s cubic-bezier(.2, .8, .2, 1) both; animation-delay: var(--rs-d, 0ms); }
@keyframes rs-slide { from { opacity: 0; transform: translateX(-16px); } }
@media (prefers-reduced-motion: reduce) { #results.rs-go .brow, #results.rs-go .mitem.rs-it { animation: none !important; } .rs-r { transition: none; } }

/* --- progress bar ---------------------------------------------------- */
#barfill { transition: width var(--dur-2) var(--ease) !important; }

/* --- guides & cram sheets -------------------------------------------- */
/* figures settle in as they're scrolled to; theme.js adds .fig-in */
figure.fig { transition: opacity var(--dur-3) var(--ease), transform var(--dur-3) var(--ease); }
figure.fig.fig-pending { opacity: 0; transform: translateY(10px); }
figure.fig.fig-in { opacity: 1; transform: none; }

/* cram rows respond to the pointer, so a dense table is easier to track */
.cram-table tbody tr, table tbody tr {
  transition: background-color var(--dur-1) var(--ease);
}

@media (prefers-reduced-motion: reduce) {
  .q-enter #qtext, .q-enter #opts, .expl.show, #results,
  .opt.correct, .opt.wrong, .opt .ltr.fx-has-mark { animation: none !important; }
  .opt .ltr .fx-mark path { animation: none !important; stroke-dashoffset: 0; }
  figure.fig, figure.fig.fig-pending { opacity: 1 !important; transform: none !important; }
}

/* ============================================================
   8. FORM CONTROLS + CHROME (2026-07-27)
   ------------------------------------------------------------
   The quiz start screen shipped four raw browser checkboxes -- the
   OS default grey square, on the last screen you see before every
   one of the 431 quizzes. `appearance` was used exactly once in the
   whole stylesheet and `accent-color` never.

   Styled here rather than in the template so no generated page needs
   regenerating. The width/height need !important purely because the
   template sets them inline.
   ============================================================ */
:root { accent-color: var(--accent, var(--a-blue)); }

input[type="checkbox"]:not([data-native]) {
  -webkit-appearance: none;
  appearance: none;
  width: 18px !important;
  height: 18px !important;
  flex: none;
  margin: 0;
  border: 1.5px solid var(--border, var(--line, #d3d7de));
  border-radius: 5px;
  background-color: var(--card, #fff);
  background-repeat: no-repeat;
  background-position: center;
  background-size: 12px 12px;
  cursor: pointer;
  transition: background-color var(--dur-1) var(--ease),
              border-color var(--dur-1) var(--ease),
              transform var(--dur-1) var(--ease);
}
input[type="checkbox"]:not([data-native]):hover {
  border-color: var(--accent, var(--a-blue));
}
input[type="checkbox"]:not([data-native]):checked {
  background-color: var(--accent, var(--a-blue));
  border-color: var(--accent, var(--a-blue));
  /* drawn tick, so it matches the site's 1.75px icon stroke rather than
     whatever the platform draws */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
}
input[type="checkbox"]:not([data-native]):active { transform: scale(.92); }
input[type="checkbox"]:not([data-native]):disabled { opacity: .5; cursor: default; }

/* Text inputs and selects: consistent focus + hover, one radius. */
input[type="text"], input[type="search"], input[type="email"], textarea, select {
  transition: border-color var(--dur-1) var(--ease), box-shadow var(--dur-1) var(--ease);
}
input[type="text"]:hover, input[type="search"]:hover, textarea:hover, select:hover {
  border-color: color-mix(in oklab, var(--accent, var(--a-blue)) 40%, var(--border, var(--line, #d3d7de)));
}

/* ------------------------------------------------------------
   Selection + scrollbars -- there were no rules for either.
   ------------------------------------------------------------ */
::selection {
  background-color: color-mix(in oklab, var(--accent, var(--a-blue)) 24%, transparent);
}
:root {
  scrollbar-width: thin;
  scrollbar-color: color-mix(in oklab, var(--accent, var(--a-blue)) 34%, transparent) transparent;
}
nav.toc, .table-scroll, .search-results, pre, .cram-table-wrap {
  scrollbar-width: thin;
  scrollbar-color: color-mix(in oklab, var(--accent, var(--a-blue)) 40%, transparent) transparent;
}
nav.toc::-webkit-scrollbar,
.table-scroll::-webkit-scrollbar,
.search-results::-webkit-scrollbar { width: 9px; height: 9px; }
nav.toc::-webkit-scrollbar-track,
.table-scroll::-webkit-scrollbar-track,
.search-results::-webkit-scrollbar-track { background: transparent; }
nav.toc::-webkit-scrollbar-thumb,
.table-scroll::-webkit-scrollbar-thumb,
.search-results::-webkit-scrollbar-thumb {
  background: color-mix(in oklab, var(--accent, var(--a-blue)) 34%, transparent);
  border-radius: 999px;
  border: 2px solid transparent;
  background-clip: content-box;
}
nav.toc::-webkit-scrollbar-thumb:hover,
.table-scroll::-webkit-scrollbar-thumb:hover,
.search-results::-webkit-scrollbar-thumb:hover {
  background: color-mix(in oklab, var(--accent, var(--a-blue)) 58%, transparent);
  background-clip: content-box;
}

/* Quiz start-screen keyboard hint (see theme.js) */
.kbd-hint {
  margin: 14px 0 0;
  font-size: 12.5px;
  color: var(--muted, #6b7280);
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
}
.kbd-hint kbd {
  font: 600 11px/1 var(--font-sans, ui-sans-serif);
  padding: 3px 6px;
  border: 1px solid var(--border, var(--line, #d3d7de));
  border-bottom-width: 2px;
  border-radius: 5px;
  background: var(--card, #fff);
  color: var(--ink, #1f2937);
}

/* Offline bar (see theme.js). Bottom-anchored so it never covers a
   question stem or a sticky header. */
#net-bar {
  position: fixed;
  left: 50%;
  bottom: 18px;
  transform: translate(-50%, 140%);
  z-index: 3500;
  display: flex; align-items: center; gap: 8px;
  max-width: calc(100vw - 32px);
  padding: 9px 15px;
  border-radius: var(--r-pill, 999px);
  background: #1f2430;
  color: #f3f4f6;
  font: 600 13px/1.3 var(--font-sans, ui-sans-serif);
  box-shadow: var(--e-3);
  opacity: 0;
  pointer-events: none;
  transition: transform var(--dur-2) var(--ease), opacity var(--dur-2) var(--ease);
}
#net-bar.show { transform: translate(-50%, 0); opacity: 1; }
#net-bar svg { flex: none; opacity: .85; }
@media (prefers-reduced-motion: reduce) {
  #net-bar { transition: none; }
}

/* Visually hidden but available to screen readers. Used by the quiz
   accessibility layer in theme.js for the "Question 4 of 50" live region.
   Not `display:none` / `visibility:hidden` -- both remove the node from the
   accessibility tree, which would defeat the point. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ============================================================
   SURFACE TEXTURE + RESPONSIVE ELEVATION (2026-07-28, take 2)
   ------------------------------------------------------------
   Take 1 put the grain on body::after at z-index 9998 -- i.e. a noise layer
   OVER every glyph on the page, which softened text and is why the fonts
   looked wrong. It belongs behind content, texturing the ground only. This
   version uses ::before at z-index -1, so it can never touch type.
   ============================================================ */
body.homepage::before,
body.guides-page::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;                /* behind all content, above the page background */
  pointer-events: none;
  opacity: .05;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}
:root[data-theme="dark"] body.homepage::before,
:root[data-theme="dark"] body.guides-page::before { opacity: .07; }
@media print { body.homepage::before, body.guides-page::before { display: none; } }

/* Flat at rest, tactile on hover. --e-2 (the resting default) sits at 4-6%
   alpha, i.e. effectively no shadow; this is ~3x that and tighter. Scoped to
   things that are actually clickable -- a card that lifts when it is not
   interactive is a lie. Invisible until the pointer is on it, so it cannot
   make the page look busier at rest. */
:root { --e-hover: 0 2px 6px rgba(16,20,28,.10), 0 10px 24px rgba(16,20,28,.15); }
:root[data-theme="dark"] { --e-hover: 0 2px 6px rgba(0,0,0,.42), 0 12px 28px rgba(0,0,0,.5); }

.guide-card,
body.homepage .relax-cta,
body.homepage .guides-btn,
body.homepage .continue-item {
  transition: transform var(--dur-1, 160ms) var(--ease, ease),
              box-shadow var(--dur-1, 160ms) var(--ease, ease),
              border-color var(--dur-1, 160ms) var(--ease, ease);
}
.guide-card:hover,
body.homepage .relax-cta:hover,
body.homepage .guides-btn:hover,
body.homepage .continue-item:hover {
  box-shadow: var(--e-hover);
  transform: translateY(-2px);
}
@media (prefers-reduced-motion: reduce) {
  .guide-card, body.homepage .relax-cta, body.homepage .guides-btn,
  body.homepage .continue-item { transition: none; }
  .guide-card:hover, body.homepage .relax-cta:hover,
  body.homepage .guides-btn:hover, body.homepage .continue-item:hover { transform: none; }
}

/* ============================================================
   WHY THE PAGE BOTTOM WENT WHITE (fixed 2026-07-28)
   ------------------------------------------------------------
   `background: linear-gradient(...)` is a SHORTHAND, so every one of those
   declarations silently reset background-color to transparent. The gradient
   image covers the document box, but anything outside it -- most visibly the
   overscroll/rubber-band region when you scroll past the end, and the canvas
   during load -- had no colour to fall back on, so the browser painted its
   default white. In dark mode that reads as a white strip at the bottom.

   Two fixes, deliberately both:
   1. background-color beside each gradient, so the canvas always has a solid
      colour underneath the image.
   2. `color-scheme` on :root, which is the actual root cause fix: it tells
      the browser the document is dark, so every UA-painted surface -- canvas,
      overscroll, scrollbars, form controls -- matches instead of defaulting
      to light. This was never declared anywhere on the site.

   NOTE: do NOT write `html { background-color: var(--bg) }` here. --bg is
   declared on body.<surface>, and custom properties inherit DOWNWARD only,
   so at :root it resolves to the fallback -- pinning every page, in both
   themes, to one hardcoded colour. color-scheme is what makes the UA canvas
   correct generically; the per-gradient background-color handles the
   document box. Between them nothing needs a value on html at all.
   ============================================================ */


/* Semester picker. The card heading is the control: it reads as the heading it
   replaced until you hover it, then shows it is clickable. Only rendered when
   there is more than one semester to pick -- otherwise the heading stays plain
   text, so nothing invites a click that does nothing. */
.semester-picker{ position: relative; display: inline-block; }
.semester-picker-btn{
  font: inherit; color: inherit; cursor: pointer;
  display: inline-flex; align-items: center; gap: 6px;
  border: 1px solid transparent; background: transparent;
  padding: 4px 10px 4px 12px; border-radius: 10px;
  transition: background var(--dur-1, 160ms) var(--ease, ease),
              border-color var(--dur-1, 160ms) var(--ease, ease);
}
.semester-picker-btn:hover{ background: var(--accent-soft); border-color: var(--border); }
.semester-picker-btn:focus-visible{ outline: 2px solid var(--accent); outline-offset: 2px; }
.sem-chevron{ flex: none; color: var(--muted); transition: transform var(--dur-1, 160ms) var(--ease, ease); }
.semester-picker.open .sem-chevron{ transform: rotate(180deg); }
.semester-picker.open .semester-picker-btn{ background: var(--accent-soft); border-color: var(--border); }

.semester-menu{
  display: none; position: absolute; z-index: 40;
  top: calc(100% + 6px); left: 50%; transform: translateX(-50%);
  min-width: max(100%, 240px); padding: 6px;
  background: var(--card); border: 1px solid var(--border);
  border-radius: 12px; box-shadow: var(--shadow);
  text-align: left;
}
.semester-picker.open .semester-menu{ display: block; }
.sem-option{
  display: block; width: 100%; text-align: left; cursor: pointer;
  font: inherit; font-size: 14.5px; font-weight: 600; line-height: 1.35;
  color: var(--ink); background: transparent; border: 0;
  padding: 9px 12px; border-radius: 8px;
}
.sem-option:hover{ background: var(--accent-soft); }
.sem-option.active{ background: var(--accent); color: #fff; }
.sem-option-name{ display: block; }
/* the estimated-dates note rides along under the name so the heading itself
   can stay short */
.sem-option-note{
  display: block; font-size: 11.5px; font-weight: 600; margin-top: 2px;
  color: var(--muted); font-style: italic;
}
.sem-option.active .sem-option-note{ color: rgba(255,255,255,.82); }
@media (max-width: 560px){
  .semester-menu{ min-width: max(100%, 200px); }
  .sem-option{ font-size: 14px; }
}

/* ==========================================================================
   Image lightbox
   --------------------------------------------------------------------------
   Injected by theme.js on every page that loads it, so guides, charts and
   cram sheets all get click-to-enlarge without any per-page markup. Jaxon,
   2026-08-27: "click on the images ... blow it up so we can really see it in
   good detail and then press escape or a X to go back."

   The overlay is appended to <body>, deliberately NOT inside .wrap. Dark mode
   on these pages is a `filter: invert(1) hue-rotate(180deg)` on body > .wrap
   with a counter-invert on the images inside it; anything appended into .wrap
   would inherit that stack and the enlarged photo would come out inverted.
   Sitting outside .wrap it renders in true colour in both themes, which is the
   whole point for clinical photographs.
   ========================================================================== */
.lb-able { cursor: zoom-in; }

.lb-overlay {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 10000;
  background: rgba(8, 10, 14, .94);
  flex-direction: column;
}
.lb-overlay.open { display: flex; }

.lb-bar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  color: #e8edf2;
  font: 600 13px/1.3 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}
.lb-count { opacity: .72; font-variant-numeric: tabular-nums; }
.lb-spacer { flex: 1 1 auto; }
.lb-btn {
  appearance: none;
  background: rgba(255, 255, 255, .1);
  color: #e8edf2;
  border: 1px solid rgba(255, 255, 255, .22);
  border-radius: 8px;
  padding: 6px 11px;
  font: 600 13px/1 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  cursor: pointer;
}
.lb-btn:hover { background: rgba(255, 255, 255, .2); }
.lb-btn:focus-visible { outline: 2px solid #7dd3fc; outline-offset: 2px; }
.lb-btn[disabled] { opacity: .35; cursor: default; }
.lb-x { font-size: 16px; line-height: 1; padding: 6px 10px; }

/* The scroll container: in "fit" mode nothing overflows, in "zoom" mode the
   image is wider than the stage and this is what pans. */
.lb-stage {
  flex: 1 1 auto;
  overflow: auto;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 12px;
  -webkit-overflow-scrolling: touch;
}
.lb-stage img {
  display: block;
  margin: auto;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  background: #fff;
  border-radius: 6px;
  cursor: zoom-in;
  /* no transition on width -- it fights the scroll re-centring below */
}
.lb-overlay.zoomed .lb-stage { align-items: flex-start; justify-content: flex-start; }
.lb-overlay.zoomed .lb-stage img {
  max-width: none;
  max-height: none;
  margin: 0;
  cursor: grab;
}
.lb-overlay.zoomed .lb-stage img.dragging { cursor: grabbing; }

.lb-cap {
  flex: 0 0 auto;
  color: #cbd5e1;
  font: 400 13px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  text-align: center;
  padding: 10px 18px 16px;
  max-width: 980px;
  margin: 0 auto;
}
.lb-cap:empty { display: none; }

@media (max-width: 620px) {
  .lb-bar { padding: 8px 10px; gap: 7px; }
  .lb-btn { padding: 6px 9px; font-size: 12px; }
  .lb-cap { font-size: 12px; padding: 8px 12px 12px; }
}

/* Swipe-to-navigate on the quiz pages (theme.js). The card follows the finger
   at a third of the distance -- enough to show the gesture is understood
   without it feeling like the card is being thrown. .swipe-settle is added
   only for the release, so the drag itself stays 1:1 with the finger. */
#quiz.swipe-settle { transition: transform 180ms cubic-bezier(.22,.61,.36,1); }
@media (prefers-reduced-motion: reduce) { #quiz.swipe-settle { transition: none; } }

/* Reading-position bar on the study guides (theme.js). Amber is the Guides
   hue in the map above. Sits at 4400: over the guides' own layers (their
   highest is 4000) but under theme.css's modals and overlays. */
#read-bar {
  position: fixed; top: 0; left: 0; right: 0; height: 3px;
  z-index: 4400; pointer-events: none;
  background: transparent;
  opacity: 0; transition: opacity 200ms ease;
}
#read-bar.on { opacity: 1; }
#read-bar span {
  display: block; height: 100%; width: 0;
  background: var(--a-amber, #ad631e);
  border-radius: 0 2px 2px 0;
  transition: width 90ms linear;
}
@media (prefers-reduced-motion: reduce) {
  #read-bar, #read-bar span { transition: none; }
}
/* Item 17: docked along the bottom edge of the sticky 38px back bar. */
#read-bar.in-back-bar { position: absolute; top: auto; bottom: -1px; height: 2px; z-index: 1; }
#read-bar.in-back-bar span { background: var(--accent, var(--a-amber, #ad631e)); border-radius: 0; }
/* pages still on the invert filter carry a LIGHT --accent, too dark to read on
   the dark bar; they keep the Guides amber, which has a real dark value */
:root[data-theme="dark"] body:not([data-dark="tokens"]) #read-bar.in-back-bar span { background: var(--a-amber); }


/* ==========================================================================
   DESIGNED DARK MODE for quiz / guide / cram pages (design review item 7,
   2026-09-25). Opt-in per page: tools/dark_tokens.py adds
   body[data-dark="tokens"] and writes that page's own dark palette (derived
   from its light one with the site's 4.60:1 recipe) into a <style
   id="dark-tokens"> block. The invert filter and the image counter-invert
   above are both switched off for such pages, together. Semester 1 is frozen
   and never opted in, so it stays on the filter.

   Roles: --navy/--indigo/--accent* are TEXT-role colours, re-pointed to the
   lifted dark values; --navy-l/--indigo-l/--accent*-l keep the LIGHT value for
   FILLS that carry white text (banners, filled buttons, table headers,
   letter badges). Everything below only re-points hard-coded light colours in
   the templates at those tokens -- no new colours.
   ========================================================================== */
:root[data-theme="dark"] body[data-dark="tokens"] { background: var(--page, #0f1115); color: var(--ink); }
:root[data-theme="dark"][data-contrast="high"] body[data-dark="tokens"] > .wrap { filter: contrast(1.35); }
:root[data-theme="dark"] body[data-dark="tokens"] mark.user-hl-yellow { background: rgba(253, 224, 71, .28); }
:root[data-theme="dark"] body[data-dark="tokens"] mark.user-hl-green  { background: rgba(134, 239, 172, .24); }
:root[data-theme="dark"] body[data-dark="tokens"] mark.user-hl-pink   { background: rgba(249, 168, 212, .26); }
:root[data-theme="dark"] body[data-dark="tokens"] mark.user-hl-blue   { background: rgba(147, 197, 253, .26); }

/* --- quizzes (tools/quiz-template) --- */
:root[data-theme="dark"] body[data-dark-kind="quiz"] .wrap > header { background: linear-gradient(135deg, var(--navy-l), var(--indigo-l)); }
:root[data-theme="dark"] body[data-dark-kind="quiz"] .wrap > a[href$="index.html"]:first-child { color: var(--muted) !important; }
:root[data-theme="dark"] body[data-dark-kind="quiz"] #quiz-header-logo > span:first-child { background: linear-gradient(to left, rgba(255,255,255,.14), rgba(255,255,255,0)) !important; }
:root[data-theme="dark"] body[data-dark-kind="quiz"] #quiz-header-logo > span:last-child { background: linear-gradient(to right, rgba(255,255,255,.14), rgba(255,255,255,0)) !important; }
:root[data-theme="dark"] body[data-dark-kind="quiz"] .card { box-shadow: 0 1px 0 rgba(255,255,255,.03), 0 10px 30px rgba(0,0,0,.35); }
:root[data-theme="dark"] body[data-dark-kind="quiz"] .btn-primary { background: var(--indigo-l); color: #fff; }
:root[data-theme="dark"] body[data-dark-kind="quiz"] .btn-primary:hover { background: var(--navy-l); }
:root[data-theme="dark"] body[data-dark-kind="quiz"] .btn-ghost { background: transparent; }
:root[data-theme="dark"] body[data-dark-kind="quiz"] .tag-topic,
:root[data-theme="dark"] body[data-dark-kind="quiz"] .seg-btn.is-on { background: var(--navy-l); color: #fff; }
:root[data-theme="dark"] body[data-dark-kind="quiz"] .opt { background: var(--card); }
:root[data-theme="dark"] body[data-dark-kind="quiz"] .opt.selected-exam .ltr { background: var(--indigo-l); border-color: var(--indigo-l); color: #fff; }
:root[data-theme="dark"] body[data-dark-kind="quiz"] .opt.correct .ltr { background: var(--ok-l); border-color: var(--ok-l); color: #fff; }
:root[data-theme="dark"] body[data-dark-kind="quiz"] .opt.wrong .ltr { background: var(--bad-l); border-color: var(--bad-l); color: #fff; }
:root[data-theme="dark"] body[data-dark-kind="quiz"] .bar,
:root[data-theme="dark"] body[data-dark-kind="quiz"] .brow .bbar { background: var(--line); }
:root[data-theme="dark"] body[data-dark-kind="quiz"] .ring { background: conic-gradient(var(--indigo) calc(var(--p) * 1%), var(--line) 0); }
:root[data-theme="dark"] body[data-dark-kind="quiz"] .ring i,
:root[data-theme="dark"] body[data-dark-kind="quiz"] .mitem { background: var(--card); }
:root[data-theme="dark"] body[data-dark-kind="quiz"] .erow,
:root[data-theme="dark"] body[data-dark-kind="quiz"] .brow .bname { color: var(--ink); }
:root[data-theme="dark"] body[data-dark-kind="quiz"] .perfect { border-color: var(--ok); color: var(--ok); }
:root[data-theme="dark"] body[data-dark-kind="quiz"] #resume-banner { background: var(--a-amber-soft) !important; border-color: color-mix(in oklab, var(--a-amber) 40%, transparent) !important; }
:root[data-theme="dark"] body[data-dark-kind="quiz"] #resume-banner > p:first-child { color: var(--a-amber) !important; }
:root[data-theme="dark"] body[data-dark-kind="quiz"] .examtoolbar .examctl-btn-primary { background: var(--card); color: var(--indigo); border-color: var(--indigo); }
:root[data-theme="dark"] body[data-dark-kind="quiz"] .examtoolbar .examctl-btn-submit { background: var(--indigo-l); color: #fff; }

/* --- study guides --- */
/* Fills that carry white text read their palette variable where they are
   painted, so pointing the variable back at the light value ON that element
   keeps each guide's own banner gradient and header cells exactly as designed. */
:root[data-theme="dark"] body[data-dark-kind="guide"] header.top,
:root[data-theme="dark"] body[data-dark-kind="guide"] th,
:root[data-theme="dark"] body[data-dark-kind="guide"] .lecpill {
  --accent: var(--accent-l); --accent2: var(--accent2-l, var(--accent-l)); --accent3: var(--accent3-l, var(--accent-l));
}
:root[data-theme="dark"] body[data-dark-kind="guide"] header.top { filter: none; }
:root[data-theme="dark"] body[data-dark-kind="guide"] nav.toc { background: var(--card); }
:root[data-theme="dark"] body[data-dark-kind="guide"] .io-box { background: var(--accent3-soft, var(--soft-bg)); }
:root[data-theme="dark"] body[data-dark-kind="guide"] .callout { background: var(--accent3-soft, var(--soft-bg)); }
:root[data-theme="dark"] body[data-dark-kind="guide"] .pearl { background: var(--accent2-soft, var(--soft-bg)); }
:root[data-theme="dark"] body[data-dark-kind="guide"] tr:nth-child(even) td { background: rgba(255, 255, 255, .035); }
:root[data-theme="dark"] body[data-dark-kind="guide"] code { background: var(--line); }
:root[data-theme="dark"] body[data-dark-kind="guide"] figure.fig,
:root[data-theme="dark"] body[data-dark-kind="guide"] .figgrid,
:root[data-theme="dark"] body[data-dark-kind="guide"] .figgrid .fg-nopic { background: var(--card); }
/* Section tags and "Test yourself" buttons: white on the light gold/green
   fill measured 2.4-3.4:1 even in light mode; in dark they become a tinted
   chip in the lifted accent, and the button takes the guide's primary fill. */
:root[data-theme="dark"] body[data-dark-kind="guide"] .tag { background: var(--accent2-soft, var(--soft-bg)); color: var(--accent2, var(--accent)); }
:root[data-theme="dark"] body[data-dark-kind="guide"] .test-yourself-btn { background: var(--accent-l); color: #fff; }
/* Professor-emphasis marks (professor_emphasized_content) and the amber
   notes: the site's amber, dark variant. */
:root[data-theme="dark"] body[data-dark-kind="guide"] mark.prof-highlight { background: var(--a-amber-soft); color: var(--ink); box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--a-amber) 55%, transparent); }
:root[data-theme="dark"] body[data-dark-kind="guide"] .prof-flag { background: var(--card); }
:root[data-theme="dark"] body[data-dark-kind="guide"] .prof-flag-label,
:root[data-theme="dark"] body[data-dark-kind="guide"] .cmp-star,
:root[data-theme="dark"] body[data-dark-kind="guide"] .audio-pending { background: var(--a-amber-soft); color: var(--a-amber-hover); }
:root[data-theme="dark"] body[data-dark-kind="guide"] .cmp-note b { color: var(--a-amber-hover); }
:root[data-theme="dark"] body[data-dark-kind="guide"] { --c-ice: var(--soft-bg); --c-mute: var(--soft); --c-line: var(--line); --c-panel: var(--accent-l); }
/* a legend mark written with an inline light background */
:root[data-theme="dark"] body[data-dark-kind="guide"] mark.prof-highlight[style] { background: var(--a-amber-soft) !important; }
/* muted text inside a header cell sits on the light fill: keep it light */
:root[data-theme="dark"] body[data-dark-kind="guide"] th { --soft: #e5e7eb; }
/* the referral tier bar's same-day fill measured 4.23:1 with white text in
   light mode too; its darker neighbour tier is the page's own colour */
:root[data-theme="dark"] body[data-dark-kind="guide"] .tierbar .t-sd { background: #9b5301; }
/* one TOC link carries an inline light-mode colour */
:root[data-theme="dark"] body[data-dark-kind="guide"] nav.toc a.top-link[style*="color"] { color: var(--accent2, var(--accent)) !important; }
:root[data-theme="dark"] body[data-dark-kind="guide"] .cmp-note,
:root[data-theme="dark"] body[data-dark-kind="guide"] .callout.warn,
:root[data-theme="dark"] body[data-dark-kind="guide"] .callout.audio-note,
:root[data-theme="dark"] body[data-dark-kind="guide"] dl.cmp-fields dd.contested { background: var(--a-amber-soft); }
:root[data-theme="dark"] body[data-dark-kind="guide"] details.cmp-card,
:root[data-theme="dark"] body[data-dark-kind="guide"] .cmp-prof,
:root[data-theme="dark"] body[data-dark-kind="guide"] .sixty div,
:root[data-theme="dark"] body[data-dark-kind="guide"] details.qa { background: var(--card); }
:root[data-theme="dark"] body[data-dark-kind="guide"] dl.cmp-fields dt { background: var(--soft-bg); }
:root[data-theme="dark"] body[data-dark-kind="guide"] .cmp-tools input { background: var(--card); color: var(--ink); }

/* --- comparison charts (the --c-* palette: CMS derm/ophtho/ENT comparison
   charts, derm staging chart, PD2 ENT OSCE chart; 2026-09-27) ---
   The palette variables carry almost everything; only the fills that carry
   white text (header row, pressed filter chips) take the light --acc back. */
:root[data-theme="dark"] body[data-dark-kind="chart"] thead th,
:root[data-theme="dark"] body[data-dark-kind="chart"] .filterbar button[aria-pressed="true"],
:root[data-theme="dark"] body[data-dark-kind="chart"] .filt.on { --acc: var(--acc-l); }
/* the giveaway header's sub-line is written inline in a pale teal that only
   cleared 3.99:1 on the gold fill; plain white clears 4.75:1 */
:root[data-theme="dark"] body[data-dark-kind="chart"] th.gv-h span[style*="color"] { color: #fff !important; }

/* --- cram sheets (tools/cram-sheet-template) --- */
:root[data-theme="dark"] body[data-dark-kind="cram"] .note,
:root[data-theme="dark"] body[data-dark-kind="cram"] .toc a { background: var(--card); }
:root[data-theme="dark"] body[data-dark-kind="cram"] .note { color: var(--body); }
:root[data-theme="dark"] body[data-dark-kind="cram"] thead th { color: var(--ink); }
:root[data-theme="dark"] body[data-dark-kind="cram"] #quiz-header-logo > span:first-child { background: linear-gradient(to left, rgba(255,255,255,.14), rgba(255,255,255,0)) !important; }
:root[data-theme="dark"] body[data-dark-kind="cram"] #quiz-header-logo > span:last-child { background: linear-gradient(to right, rgba(255,255,255,.14), rgba(255,255,255,0)) !important; }

/* LIGHT-MODE CONTRAST for the same opted-in pages (design-review leftovers
   item 1, 2026-09-27). Some template colours measure 2.0-4.46:1 in LIGHT mode
   too. tools/dark_tokens.py writes each page a light "-t" partner per palette
   colour (itself if it already clears 4.5:1, else the same hue re-solved with
   the site's 4.60:1 recipe against the page's darkest light surface, chroma
   capped at its own, so gold stays gold, only deeper) plus rules for colours
   set inline. The rules below use them under data-theme="light" only; every
   dark rule above is untouched. */
/* Guide section tags: white on the light gold/green --accent2 measured
   2.9-3.4:1. (Test-yourself buttons carry their colour inline, so their
   rules are written per page by the tool.) */
:root[data-theme="light"] body[data-dark-kind="guide"] .tag { background: var(--accent2-t, var(--accent2)); }

/* Guide accent TEXT: TOC section links, h3/h4 headings, IO-box titles and
   objective headings are set in the guide's --accent2 gold (2.0-3.5:1) or a
   light --accent3 (3.4-4.4:1). Re-pointed inside the content column and TOC
   only, so the header banner gradient keeps its exact colours; borders in the
   same variable deepen with it. */
:root[data-theme="light"] body[data-dark-kind="guide"] .layout {
  --accent: var(--accent-t); --accent2: var(--accent2-t); --accent3: var(--accent3-t);
}

/* Cram sheets: the kicker line is set in --primary on the page ground
   (PD2 E2 gold 3.04:1); topic term inks are re-solved per page by the tool.
   The PDM high-yield legend's tier pills carried the body ink on their dark
   fills (1.4-2.0:1): white, as the same pills inside the table already are. */
:root[data-theme="light"] body[data-dark-kind="cram"] .kicker { color: var(--primary-t, var(--primary)); }
:root[data-theme="light"] body[data-dark-kind="cram"] .tier { color: #fff; }

/* Quiz buttons: white on the Micro / Pharm E2 / PD2 / PDM teal --indigo fills
   measured 3.95-4.46:1, and the same colour as text (Back, Flag, "N left")
   on white likewise. Re-pointed inside the quiz only, so each exam keeps its
   own hue; exams already at 4.5:1 get their own colour back unchanged. */
:root[data-theme="light"] body[data-dark-kind="quiz"] > .wrap {
  --navy: var(--navy-t); --indigo: var(--indigo-t); --exam-toolbar-accent: var(--indigo-t);
}

/* Quiz stem in regular weight (design review item 10, 2026-09-25). Every
   generated quiz set .qtext at weight 600, so a 30-65 question block read as
   one long semi-bold paragraph; emphasis only means something when the body
   is regular. #quiz scopes it to the quiz engine (paged and all-on-a-page
   views) and outranks the template's own .qtext rule, so no page changes.
   Group Study's big centred question keeps its heading weight. */
#quiz .qtext {
  font-weight: 400;
  line-height: 1.6;
  max-width: var(--measure, 68ch);
}

/* Bold lead-in (design-review leftovers item 3): theme.js wraps the final
   sentence of each Semester 2 stem in .q-lead; the rest stays at the
   regular weight item 10 set. 600 is the weight the whole stem used to
   carry. */
#quiz .qtext .q-lead { font-weight: 600; }

/* Quiet the strike-out control (design review item 11, 2026-09-25). Every
   choice showed a bordered X box at all times -- four extra controls beside
   four answers. With a pointer it now rests at 35% and comes up when the row
   is hovered or focused; on touch it stays visible but borderless and
   lighter. A struck-out row keeps its X at full strength so the state reads.
   Function unchanged. */
#quiz .cross-toggle {
  transition: opacity var(--dur-1, 160ms) var(--ease, ease), border-color var(--dur-1, 160ms) var(--ease, ease);
}
@media (hover: hover) {
  #quiz .opt .cross-toggle { opacity: .35; border-color: transparent; }
  #quiz .opt:hover .cross-toggle,
  #quiz .opt:focus-within .cross-toggle,
  #quiz .opt .cross-toggle:focus-visible { opacity: 1; border-color: var(--line); }
}
@media (hover: none) {
  #quiz .opt .cross-toggle { border-color: transparent; opacity: .75; }
}
#quiz .opt.crossed .cross-toggle { opacity: 1; border-color: var(--bad); }
@media (prefers-reduced-motion: reduce) { #quiz .cross-toggle { transition: none; } }

/* Give the guide hero back the first screen on phones (design review item
   12, 2026-09-25). At 390px a guide opened on four lines of ~32px serif and
   two paragraphs of meta: 100% banner, 0% content. The title now scales with
   the screen, the first meta line is clamped to one line, and the rest sits
   behind a "Details" disclosure theme.js adds. Serif stays on the title only;
   desktop is unchanged. */
.hero-more { display: none; }
@media (max-width: 640px) {
  body > header.top { padding: 18px 18px 16px; }
  body > header.top h1 { font-size: clamp(26px, 7vw, 40px); line-height: 1.15; margin-bottom: 6px; }
  body > header.top.hero-collapsible:not(.hero-open) > p:first-of-type {
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  body > header.top.hero-collapsible:not(.hero-open) > p:first-of-type ~ p,
  body > header.top.hero-collapsible:not(.hero-open) > .hdr-note,
  body > header.top.hero-collapsible:not(.hero-open) > .hdr-save { display: none; }
  body > header.top .hero-more {
    display: inline-flex; align-items: center; gap: 6px; margin-top: 8px;
    font: 700 12.5px/1 var(--font-sans); color: #fff; cursor: pointer;
    background: rgba(255, 255, 255, .12); border: 1.5px solid rgba(255, 255, 255, .55);
    border-radius: 999px; padding: 6px 12px;
  }
  body > header.top .hero-more svg { transition: transform var(--dur-1, 160ms) var(--ease, ease); }
  body > header.top.hero-open .hero-more svg { transform: rotate(180deg); }
}
@media (prefers-reduced-motion: reduce) { body > header.top .hero-more svg { transition: none; } }

/* One back link (design review item 13, 2026-09-25). There were five
   wordings ("Back to Homepage", "Back to Home", "Back to home", "Home",
   "Back") at five weights and colours, from y=18 to y=120. Every link that
   goes home now reads "Back to home"; the guide bar keeps "Back" because
   guideGoBack() returns to the page you came from, not home. One style for
   all of them, here; `html body` outranks each page's own older rule. The
   quiz template's link is inline-styled (hence !important) and its 36px top
   margin put it ~100px lower than anyone else's -- it now sits right under
   the logo row like the others sit under theirs. Semester 1 copy is not
   edited; only this style reaches it. */
html body a.back-link,
html body .wrap > a.back,
html body .guide-back-link,
html body > .wrap > a[href$="index.html"]:first-child {
  display: inline-flex !important;
  align-items: center;
  gap: 6px;
  font: 600 13.5px/1.2 var(--font-sans) !important;
  color: var(--muted, #6b7280) !important;
  text-decoration: none;
  letter-spacing: 0;
  transition: color var(--dur-1, 160ms) var(--ease, ease);
}
html body a.back-link,
html body .wrap > a.back { margin: 0 0 16px; padding: 4px 0; }
html body > .wrap > a[href$="index.html"]:first-child { margin: 10px 0 14px !important; padding: 4px 0; }
html body a.back-link:hover,
html body .wrap > a.back:hover,
html body .guide-back-link:hover,
html body > .wrap > a[href$="index.html"]:first-child:hover { color: var(--accent, var(--ink, #1f2937)) !important; text-decoration: none; }
html body a.back-link svg,
html body .wrap > a.back svg { width: 15px; height: 15px; flex: none; }
/* the guide bar has no --muted of its own */
.guide-back-link { --muted: #4b5563; }
:root[data-theme="dark"] .guide-back-link { --muted: #c9cdd4; }

/* Site icon set (design review item 14). Icons sit on the text baseline and
   take the colour of the control they are in. */
.si { display: inline-block; flex: none; vertical-align: -0.16em; }
.has-si { display: inline-flex; align-items: center; gap: 6px; }
.examtoolbar .has-si, #pausebtn.has-si { gap: 5px; }
.toc-fab .si { vertical-align: -0.2em; margin-right: 2px; }
.qnav-legend-item .si { vertical-align: -0.15em; }
/* Report links: a quiet small link with a flag, not an italic underlined
   line wrapped in stars. Still on every page (report_feature). */
a.report-link {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12.5px; font-style: normal; font-weight: 500;
  color: var(--muted, #6b7280); text-decoration: none; cursor: pointer;
}
a.report-link:hover { color: var(--ink, #1f2937); text-decoration: underline; text-underline-offset: 3px; }
/* Phones, quiz running: the Flag button shows its icon alone (its name stays
   as aria-label), which is what lets Flag / Questions / Submit share one row
   at 390px now that each carries an icon. */
@media (max-width: 640px) {
  body.quiz-running #quiz #flagbtn .si-label { display: none; }
  body.quiz-running #quiz #flagbtn { padding-left: 10px; padding-right: 10px; }
}


/* ---- Study-guide peek (2026-09-28) -------------------------------------
   Button under an explanation + the panel it opens. See the block comment in
   theme.js. Colours come from the quiz page's own tokens (--card, --ink,
   --line, --navy, --ice, --muted), which each Semester 2 quiz already defines
   for light AND dark, so nothing here invents a colour. */
.gl-btn {
  display: flex; align-items: center; gap: 12px; width: 100%; box-sizing: border-box;
  margin: 14px 0 2px; padding: 12px 14px; text-align: left; cursor: pointer;
  font: inherit; color: var(--ink, #20233a);
  background: var(--ice, #eef2ff); border: 1px solid var(--line, #dbe7e5); border-radius: 12px;
  transition: border-color .15s, transform .15s;
}
.gl-btn:hover { border-color: var(--navy, #123456); }
.gl-btn:focus-visible { outline: 2px solid var(--navy, #123456); outline-offset: 2px; }
.gl-btn > .si:first-child { flex: none; color: var(--navy, #123456); }
.gl-btn > .si:last-child { flex: none; margin-left: auto; color: var(--muted, #5b6178); }
.gl-txt { display: flex; flex-direction: column; min-width: 0; gap: 1px; }
.gl-kicker { font-size: 11.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--muted, #5b6178); }
.gl-title { font-size: 14.5px; font-weight: 600; line-height: 1.3; }
.mitem .gl-btn { margin-top: 10px; }

.gl-root { position: fixed; inset: 0; z-index: 9500; pointer-events: none; }
.gl-backdrop { position: absolute; inset: 0; background: rgba(10, 12, 20, .5); pointer-events: auto; }
.gl-panel {
  position: absolute; pointer-events: auto; display: flex; flex-direction: column;
  background: var(--card, #fff); color: var(--ink, #20233a);
  border: 1px solid var(--line, #dbe7e5); box-shadow: 0 18px 60px rgba(0, 0, 0, .28);
  left: 0; right: 0; bottom: 0; height: 88vh; height: 88dvh;
  border-radius: 18px 18px 0 0; border-bottom: 0;
}
.gl-grab { width: 40px; height: 4px; border-radius: 4px; background: var(--line, #dbe7e5); margin: 8px auto 0; flex: none; }
.gl-head { display: flex; align-items: center; gap: 10px; padding: 10px 12px 10px 16px; border-bottom: 1px solid var(--line, #dbe7e5); flex: none; }
.gl-head > .si { flex: none; color: var(--navy, #123456); }
.gl-ht { min-width: 0; flex: 1; }
.gl-hk { font-size: 11px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--muted, #5b6178); }
.gl-hn { font-size: 15px; font-weight: 700; line-height: 1.25; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.gl-full {
  flex: none; display: inline-flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 600;
  color: var(--navy, #123456); text-decoration: none; padding: 8px 10px; border-radius: 9px; border: 1px solid var(--line, #dbe7e5);
}
.gl-full { font: inherit; font-size: 13px; background: transparent; cursor: pointer; }
.gl-full:hover { border-color: var(--navy, #123456); }
/* Expanded to the whole guide: fills the screen, the button becomes "Back to question" */
.gl-root .gl-panel.is-full { left: 0; right: 0; top: 0; bottom: 0; width: auto; height: 100%; border-radius: 0; border: 0; }
.gl-root .gl-panel.is-full .gl-grab { display: none; }
.gl-x {
  flex: none; width: 40px; height: 40px; display: inline-flex; align-items: center; justify-content: center;
  color: var(--ink, #20233a); background: transparent; border: 0; border-radius: 10px; cursor: pointer;
}
.gl-x:hover { background: var(--ice, #eef2ff); }
.gl-x:focus-visible, .gl-full:focus-visible { outline: 2px solid var(--navy, #123456); outline-offset: 2px; }
.gl-body { position: relative; flex: 1; min-height: 0; background: var(--card, #fff); }
.gl-load { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; padding: 24px; text-align: center; font-size: 14px; color: var(--muted, #5b6178); }
.gl-frame { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; background: transparent; opacity: 0; }
.gl-panel.is-ready .gl-frame { opacity: 1; }
.gl-panel.is-ready .gl-load { display: none; }

html.gl-open { overflow: hidden; }

/* Wide screens: a side drawer, no dimming, so the question stays visible and
   the page stays usable beside it. */
@media (min-width: 900px) {
  .gl-backdrop { background: transparent; pointer-events: none; }
  .gl-panel { left: auto; top: 0; height: auto; width: min(580px, 46vw); border-radius: 0; border-top: 0; border-right: 0; border-bottom: 0; }
  .gl-grab { display: none; }
  html.gl-open { overflow: auto; }
}
@media (prefers-reduced-motion: no-preference) {
  .gl-panel { animation: gl-in .22s ease-out; }
  @keyframes gl-in { from { transform: translateY(24px); opacity: 0; } to { transform: none; opacity: 1; } }
  @media (min-width: 900px) { @keyframes gl-in { from { transform: translateX(28px); opacity: 0; } to { transform: none; opacity: 1; } } }
}


/* ---- Pull-to-refresh element: hidden by default, site-wide (2026-09-28) ----
   Every page carries <div id="pull-refresh"> with its own inline rule that
   collapses it to height 0. Ten Pharmacology reference sheets (Exam 1 charts,
   the Exam 2 ocular reference) shipped the element WITHOUT that rule, so a 240px
   EKG-logo band sat above the page in both themes. This is the same collapse,
   in the one shared stylesheet, so a template that forgets it can no longer
   put the element on screen. It is a fallback: pages that define their own
   rule (later in the cascade, same specificity) keep theirs. */
#pull-refresh {
  position: fixed; top: 0; left: 0; right: 0; height: 0; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  background: transparent; z-index: 1000; pointer-events: none;
}
#pull-refresh svg { width: 100%; height: 44px; }
#pull-refresh path {
  fill: none; stroke: #6b3524; stroke-width: 3; stroke-linecap: round; stroke-linejoin: round;
  stroke-dasharray: 420; stroke-dashoffset: 420;
}


/* ---- Comparison-chart table container on narrow screens (2026-09-28) -------
   The CMS ENT / CMS ophthalmology / PD2 ENT OSCE charts wrap their 8-9 column
   tables in <div class="tblwrap">, which no stylesheet defined (the chart CSS
   only styled a .scroll class the markup never used). At 390px the columns
   collapsed to ~33px and every word broke into single letters. The derm chart,
   which uses .scroll, already scrolls sideways; this gives .tblwrap the same
   behaviour, screen-only and <=900px so desktop and print are untouched, and
   pins the first column so a row stays identifiable while scrolling. */
@media screen and (max-width: 900px) {
  .tblwrap { overflow-x: auto; -webkit-overflow-scrolling: touch;
    border: 1px solid var(--c-line, #cfdcdb); border-radius: 12px; }
  .tblwrap::before { content: "Swipe sideways to see every column"; position: sticky; left: 0;
    display: block; width: max-content; padding: 7px 12px; font-size: 12px; font-weight: 600;
    color: var(--c-mute, #4c5f5e); }
  .tblwrap > table { min-width: 1120px; }
  .tblwrap tbody tr:not(.sec) td:first-child { position: sticky; left: 0; z-index: 2;
    background: var(--c-tbl, #fff); box-shadow: 1px 0 0 var(--c-line, #cfdcdb); }
  .tblwrap tbody tr:not(.sec):nth-child(even) td:first-child { background: var(--c-zebra, #f7fbfa); }
  .tblwrap thead th:first-child { position: sticky; left: 0; z-index: 4; background: var(--acc); }
}


/* ---- "Also tested" lines inside study guides (2026-09-28) -------------------
   Facts the practice questions test that the class guide did not teach, written
   into the end of the right section by tools/apply_guide_additions.py. Neutral
   grey tint + rule so it sits inside any guide's palette and in dark mode. */
.qa-also { margin: 16px 0 8px; padding: 9px 16px 7px; border-left: 3px solid rgba(128, 128, 128, .5);
  border-radius: 0 8px 8px 0; background: rgba(128, 128, 128, .08); }
.qa-also .qa-also-label { margin: 0 0 4px; font-size: .72rem; font-weight: 800; letter-spacing: .07em;
  text-transform: uppercase; opacity: .8; }
.qa-also ul { margin: 0; padding-left: 1.25em; }
.qa-also li { margin: .35em 0; }


/* ============================================================
   PA WEEK welcome card (theme.js, Oct 5-9 2026 or ?paweek=1)
   Appended to <body> (so it sits outside the dark-mode invert wrapper and
   carries its own dark colours). Slides down, a heartbeat line keeps drawing.
   ============================================================ */
.pa-toast {
  position: fixed; left: 50%; top: 14px; z-index: 9500; transform: translate(-50%, 0);
  display: flex; align-items: center; gap: 12px; width: max-content; max-width: min(520px, calc(100vw - 24px)); box-sizing: border-box;
  padding: 10px 14px 10px 12px; border-radius: 16px; border: 1.5px solid #14b8a6;
  background: #ffffff; color: #1f2937; box-shadow: 0 10px 30px rgba(20, 184, 166, .28), 0 2px 6px rgba(0, 0, 0, .08);
  animation: pa-in .6s cubic-bezier(.3, 1.4, .5, 1) both;
}
.pa-toast.out { animation: pa-out .4s ease forwards; }
.pa-ecg { width: 62px; height: 22px; flex: none; fill: none; stroke: #f43f5e; stroke-width: 2.6; stroke-linecap: round; stroke-linejoin: round; }
.pa-ecg path { stroke-dasharray: 1; stroke-dashoffset: 1; animation: pa-beat 2.2s ease-in-out .3s infinite; }
.pa-toast-txt { display: flex; flex-direction: column; min-width: 0; line-height: 1.3; }
.pa-toast-txt b { font-size: 14.5px; }
.pa-toast-txt span { font-size: 12.5px; color: #4b5563; }
.pa-toast-x { flex: none; border: 0; background: none; color: #6b7280; font-size: 22px; line-height: 1; cursor: pointer; padding: 6px 8px; margin: -4px -4px -4px 0; }
.pa-toast-x:hover { color: #111827; }
:root[data-theme="dark"] .pa-toast { background: #1c1f26; color: #e5e7eb; border-color: #2dd4bf; box-shadow: 0 10px 30px rgba(20, 184, 166, .22); }
:root[data-theme="dark"] .pa-toast-txt span { color: #aeb4bf; }
:root[data-theme="dark"] .pa-toast-x { color: #9ca3af; }
@keyframes pa-in { from { opacity: 0; transform: translate(-50%, -26px) scale(.94); } }
@keyframes pa-out { to { opacity: 0; transform: translate(-50%, -18px) scale(.96); } }
@keyframes pa-beat { 0% { stroke-dashoffset: 1; } 55% { stroke-dashoffset: 0; } 85% { stroke-dashoffset: 0; opacity: 1; } 100% { stroke-dashoffset: -1; opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  .pa-toast { animation: none; }
  .pa-ecg path { animation: none; stroke-dashoffset: 0; }
}


/* PA Week splash text beside the homepage logo (theme.js 2b): a yellow tilted hashtag that pulses, like
   the splash on Minecraft's title screen. */
.pa-splash {
  position: absolute; display: none; z-index: 5; pointer-events: none; white-space: nowrap;
  font: 800 21px/1 ui-monospace, "SF Mono", Menlo, Consolas, monospace; letter-spacing: -.01em;
  color: #ffe93b; text-shadow: 2px 2px 0 #3d3a0a, 0 0 10px rgba(255, 233, 59, .55);
  transform: translate(-50%, -50%) rotate(14deg); animation: pa-pulse .9s ease-in-out infinite;
}
.pa-splash.on { display: block; }
.pa-splash-big { left: 87%; top: 30%; }
.pa-splash-small { left: calc(50% + 140px); top: 34%; font-size: 12px; }
.pa-splash.swap { animation: pa-pulse .9s ease-in-out infinite, pa-swap .35s ease-out 1; }
@keyframes pa-pulse { 0%, 100% { transform: translate(-50%, -50%) rotate(14deg) scale(1); } 50% { transform: translate(-50%, -50%) rotate(14deg) scale(1.13); } }
@keyframes pa-swap { from { opacity: 0; } to { opacity: 1; } }
:root[data-theme="light"] .pa-splash { color: #f5c400; text-shadow: 2px 2px 0 #5c4a00, 0 0 8px rgba(245, 196, 0, .4); }
@media (prefers-reduced-motion: reduce) { .pa-splash, .pa-splash.swap { animation: none; } }


/* ============================================================
   HALLOWEEN (theme.js, Oct 24-31 or ?halloween=1): candle glows + pumpkin trail.
   The bats are a canvas (theme.js). Everything is fixed, pointer-events none, outside the layout flow.
   ============================================================ */
.hw-glow {
  position: fixed; z-index: -1; pointer-events: none; width: 460px; height: 460px; border-radius: 50%;
  background: radial-gradient(circle, rgba(249, 115, 22, .26) 0%, rgba(251, 146, 60, .09) 36%, rgba(251, 146, 60, 0) 68%);
  animation: hw-flicker 3.3s linear infinite; will-change: opacity, transform;
}
.hw-glow-a { left: -190px; bottom: -190px; }
.hw-glow-b { right: -200px; top: -200px; animation-duration: 4.4s; animation-delay: -1.9s; }
:root[data-theme="dark"] .hw-glow {
  background: radial-gradient(circle, rgba(251, 146, 60, .30) 0%, rgba(249, 115, 22, .11) 36%, rgba(249, 115, 22, 0) 68%);
}
@keyframes hw-flicker {
  0% { opacity: .85; transform: scale(1); }
  7% { opacity: .55; transform: scale(.96); }
  13% { opacity: .95; transform: scale(1.03); }
  21% { opacity: .68; transform: scale(.98); }
  30% { opacity: 1; transform: scale(1.05); }
  39% { opacity: .72; transform: scale(.985); }
  51% { opacity: .93; transform: scale(1.03); }
  62% { opacity: .6; transform: scale(.965); }
  73% { opacity: 1; transform: scale(1.04); }
  85% { opacity: .75; transform: scale(1); }
  100% { opacity: .85; transform: scale(1); }
}
body.th-paused .hw-glow { animation-play-state: paused; }
@media (max-width: 700px) { .hw-glow { width: 300px; height: 300px; } .hw-glow-a { left: -130px; bottom: -130px; } .hw-glow-b { right: -140px; top: -140px; } }
.hw-pumpkin {
  position: fixed; left: 0; top: 0; width: 20px; height: 19px; margin: -10px 0 0 -10px; z-index: 9400;
  pointer-events: none; opacity: 0; will-change: transform, opacity;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 30'%3E%3Cpath d='M16 9 C15.5 6 17 3.5 20 2.5' fill='none' stroke='%2365a30d' stroke-width='2.6' stroke-linecap='round'/%3E%3Cellipse cx='9' cy='18.5' rx='8' ry='10.5' fill='%23c2410c'/%3E%3Cellipse cx='23' cy='18.5' rx='8' ry='10.5' fill='%23c2410c'/%3E%3Cellipse cx='12.5' cy='18.5' rx='7' ry='10.8' fill='%23ea580c'/%3E%3Cellipse cx='19.5' cy='18.5' rx='7' ry='10.8' fill='%23ea580c'/%3E%3Cellipse cx='16' cy='18.5' rx='5' ry='11' fill='%23fb923c'/%3E%3Cpath d='M10.5 16.5 L13.5 16.5 L12 13.5 Z M18.5 16.5 L21.5 16.5 L20 13.5 Z M11.5 21.5 Q16 25.5 20.5 21.5 Q16 23 11.5 21.5 Z' fill='%23431407'/%3E%3C/svg%3E") center / contain no-repeat;
}
@media (prefers-reduced-motion: reduce) {
  .hw-glow { animation: none; opacity: .8; }
  .hw-pumpkin { display: none; }
}
@media print { .hw-glow, .hw-bats, .hw-pumpkin { display: none !important; } }


/* ============================================================
   STREAK FLAME (theme.js, "In-quiz answer-streak feedback"): after 3 correct answers in a row a small flame
   sits beside the question number and grows (3 small, 5 medium, 8 large, 10+ biggest with a spark); a wrong
   answer snuffs it with a puff of smoke. Absolutely positioned inside .topbar, pointer-events none, so it can
   never move or block anything. It sits just above the progress bar and is capped so it stays inside the card
   (phones: inside the sticky bar). Reduced motion: a still flame with the count.
   ============================================================ */
.topbar.sf-host { position: relative; }
/* An in-line pill in the quiz's top bar (a flex item beside the score), fixed 24px tall so a growing flame never
   moves anything vertically. */
.sf {
  position: relative; flex: none; pointer-events: none; display: inline-flex; align-items: center; gap: 4px;
  height: 24px; padding: 0 9px 0 7px; border-radius: 999px; white-space: nowrap; transform-origin: 50% 50%;
  background: rgba(249, 115, 22, .13); box-shadow: inset 0 0 0 1px rgba(249, 115, 22, .28);
}
.sf.sf-tight { display: none; }
.sf.sf-over { position: absolute; z-index: 3; }
.topbar.sf-swap .bar { visibility: hidden; }
.sf-fl { display: block; width: 11px; height: 15px; transform-origin: 50% 100%; overflow: visible;
  animation: sf-flick .85s ease-in-out infinite alternate; transition: width .35s cubic-bezier(.3, 1.6, .5, 1), height .35s cubic-bezier(.3, 1.6, .5, 1);
  filter: drop-shadow(0 0 3px rgba(249, 115, 22, .55)); }
.sf[data-t="2"] .sf-fl { width: 13px; height: 18px; }
.sf[data-t="3"] .sf-fl { width: 15px; height: 20px; }
.sf[data-t="4"] .sf-fl { width: 16px; height: 22px; filter: drop-shadow(0 0 5px rgba(249, 115, 22, .75)); }
.sf-n { font: 800 12px/1 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; color: #c2410c; letter-spacing: .01em; font-variant-numeric: tabular-nums; }
.sf-spark { position: absolute; left: 14px; top: 3px; width: 3px; height: 3px; border-radius: 50%; background: #fde047; opacity: 0; display: none; }
.sf[data-t="4"] .sf-spark { display: block; animation: sf-spark 1.25s ease-out infinite; }
.sf-spark.b { animation-delay: .6s !important; margin-left: -6px; }
.sf.sf-ignite { animation: sf-ignite .5s cubic-bezier(.3, 1.4, .5, 1) both; }
.sf.sf-grow { animation: sf-grow .42s cubic-bezier(.3, 1.5, .5, 1) both; }
.sf.sf-out .sf-fl { animation: sf-snuff .32s ease-in forwards; }
.sf.sf-out .sf-n { opacity: 0; transition: opacity .2s; }
.sf-smoke { position: absolute; pointer-events: none; z-index: 3; width: 0; height: 0; }
.sf-smoke i { position: absolute; left: 0; bottom: 0; width: 9px; height: 9px; margin-left: -5px; border-radius: 50%;
  background: rgba(120, 113, 108, .55); filter: blur(1.5px); opacity: 0; animation: sf-puff .8s ease-out forwards; }
.sf-smoke i:nth-child(2) { animation-delay: .1s; margin-left: 0; }
.sf-smoke i:nth-child(3) { animation-delay: .2s; margin-left: -9px; }
@keyframes sf-flick {
  0% { transform: scale(1, 1) skewX(0deg); } 35% { transform: scale(.96, 1.05) skewX(-3deg); }
  70% { transform: scale(1.03, .97) skewX(2.5deg); } 100% { transform: scale(.98, 1.07) skewX(-1.5deg); }
}
@keyframes sf-ignite { 0% { transform: scale(0) rotate(-12deg); opacity: 0; } 60% { transform: scale(1.12) rotate(3deg); opacity: 1; } 100% { transform: scale(1); opacity: 1; } }
@keyframes sf-grow { 0% { transform: scale(.8); } 55% { transform: scale(1.1); } 100% { transform: scale(1); } }
@keyframes sf-snuff { 0% { transform: scale(1); opacity: 1; filter: none; } 100% { transform: scale(.15, .05) translateY(2px); opacity: 0; filter: grayscale(1); } }
@keyframes sf-spark { 0% { transform: translate(0, 0) scale(1); opacity: 0; } 15% { opacity: 1; } 100% { transform: translate(5px, -9px) scale(.3); opacity: 0; } }
@keyframes sf-puff { 0% { transform: translate(0, 0) scale(.5); opacity: 0; } 20% { opacity: .75; } 100% { transform: translate(4px, -16px) scale(2.1); opacity: 0; } }
:root[data-theme="dark"] .sf-n { color: #fdba74; }
:root[data-theme="dark"] .sf { background: rgba(251, 146, 60, .16); box-shadow: inset 0 0 0 1px rgba(251, 146, 60, .34); }
/* phones: the bar is sticky at the top of the screen, so the flame must fit inside its 46px and the spark is dropped */
@media (max-width: 640px) {
  .sf { height: 22px; padding: 0 6px 0 5px; gap: 2px; }
  .sf-fl { height: 13px; width: 10px; }
  .sf[data-t="2"] .sf-fl { width: 11px; height: 15px; }
  .sf[data-t="3"] .sf-fl { width: 12px; height: 16px; }
  .sf[data-t="4"] .sf-fl { width: 13px; height: 18px; }
  .sf[data-t="4"] .sf-spark { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .sf, .sf.sf-ignite, .sf.sf-grow { animation: none; }
  .sf-fl, .sf[data-t] .sf-fl { animation: none; transition: none; width: 11px; height: 15px; }
  .sf-spark, .sf-smoke { display: none !important; }
}
@media print { .sf, .sf-smoke { display: none !important; } }


/* ============================================================
   GUIDE MOTION (theme.js "GUIDE MOTION"): sections ease in as they are scrolled to; the Contents list gets a
   sliding marker. The hidden state is only ever added by script, and is removed again once shown.
   ============================================================ */
.rv-p { opacity: 0; transform: translateY(12px); transition: opacity .35s ease-out, transform .35s ease-out; }
.rv-p.rv-in { opacity: 1; transform: none; }
.rv-p.rv-now { transition: none; }
@media (prefers-reduced-motion: reduce) { .rv-p, .rv-p.rv-in { opacity: 1 !important; transform: none !important; transition: none !important; } }
@media print { .rv-p, .rv-p.rv-in { opacity: 1 !important; transform: none !important; transition: none !important; } .toc-mark { display: none !important; } }
.toc-mark {
  position: absolute; left: 0; top: 0; width: 100%; pointer-events: none; z-index: 0; opacity: 0;
  border-left: 3px solid var(--accent, #2563eb); border-radius: 0 4px 4px 0;
  background: color-mix(in srgb, var(--accent, #2563eb) 9%, transparent);
  transition: transform .3s cubic-bezier(.3, .8, .3, 1), height .3s cubic-bezier(.3, .8, .3, 1), opacity .2s;
}
.toc-mark.on { opacity: 1; }
:root[data-theme="light"] nav.toc a.toc-cur { color: var(--accent, #2563eb); }
@media (prefers-reduced-motion: reduce) { .toc-mark { transition: none !important; } }


/* ============================================================
   THEME KIT (theme.js, window.__themeKit): what every seasonal theme shares.
   body.th-paused is set while the tab is hidden so CSS animations stop with the canvases.
   Theme canvases and cursor trails never print.
   ============================================================ */
@media print { .th-cv { display: none !important; } }


/* ============================================================
   THANKSGIVING (theme.js, Thanksgiving week or ?theme=thanksgiving): the acorn-spark cursor trail.
   The drifting leaves, acorns and wheat are a canvas (theme.js); the tint is body.thanksgiving-theme.
   ============================================================ */
.th-acorn {
  position: fixed; left: 0; top: 0; width: 18px; height: 21px; margin: -9px 0 0 -9px; z-index: 9400;
  pointer-events: none; opacity: 0; will-change: transform, opacity;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 28'%3E%3Cellipse cx='12' cy='17' rx='6.5' ry='9' fill='%23a16207'/%3E%3Cpath d='M4.8 14.5 Q12 3.5 19.2 14.5 Z' fill='%235b3410'/%3E%3Cpath d='M12 7.5 L13.5 3' stroke='%235b3410' stroke-width='2' stroke-linecap='round'/%3E%3Ccircle cx='3' cy='6' r='1.7' fill='%23fbbf24'/%3E%3Ccircle cx='21.5' cy='10' r='1.3' fill='%23fcd34d'/%3E%3C/svg%3E") center / contain no-repeat;
}
@media (prefers-reduced-motion: reduce) { .th-acorn { display: none; } }
@media print { .th-acorn { display: none !important; } }


/* ============================================================
   HOLIDAY LIGHTS (theme.js, Dec 1-25 or ?theme=lights): a string of bulbs along the top edge.
   Absolute at the top of the document (it scrolls away), z-index 1 under every bar and control, pointer-events none, outside the layout flow (no layout shift). Composited opacity and
   transform animation only, on 10 wrappers. The bulb positions are computed in JS from the viewport width.
   ============================================================ */
.th-lights { position: absolute; top: 0; left: 0; width: 100%; height: 40px; overflow: hidden; z-index: 1; pointer-events: none; }
.th-wire { position: absolute; left: 0; top: 0; overflow: visible; fill: none; stroke: #2f5d3a; stroke-width: 1.5; stroke-linecap: round; opacity: .8; }
:root[data-theme="dark"] .th-wire { stroke: #5b9a6c; opacity: .7; }
.th-grp { position: absolute; left: 0; top: 0; width: 100%; height: 0; animation: th-twinkle 3.4s ease-in-out var(--d, 0s) infinite, th-sway 5.2s ease-in-out var(--d, 0s) infinite alternate; will-change: opacity, transform; }
.th-bulb { position: absolute; width: 7px; height: 11px; margin-left: -3.5px; }
.th-bulb::before { content: ""; position: absolute; left: 1.5px; top: -2px; width: 4px; height: 3px; border-radius: 1px; background: #3f3f46; }
.th-bulb::after {
  content: ""; position: absolute; left: 0; top: 1px; width: 7px; height: 10px; border-radius: 50% 50% 55% 55% / 40% 40% 62% 62%;
  background: var(--c); box-shadow: 0 0 7px 2px var(--c);
}
@keyframes th-twinkle { 0%, 100% { opacity: .4; } 45%, 55% { opacity: 1; } }
@keyframes th-sway { from { transform: translateX(-1.8px); } to { transform: translateX(1.8px); } }
body.th-paused .th-grp { animation-play-state: paused; }
@media (prefers-reduced-motion: reduce) { .th-grp { animation: none; opacity: 1; } }
@media print { .th-lights { display: none !important; } }


/* ============================================================
   NEW YEAR'S splash card (theme.js): same card as the PA Week welcome (.pa-toast), recolored.
   The fireworks themselves are a canvas (theme.js, kit.fireworks).
   ============================================================ */
.pa-toast.th-card-ny { border-color: #d4a017; box-shadow: 0 10px 30px rgba(212, 160, 23, .30), 0 2px 6px rgba(0, 0, 0, .08); }
.th-ny-year { flex: none; font: 800 30px/1 ui-rounded, "SF Pro Rounded", system-ui, sans-serif; letter-spacing: .02em; color: #9a6b00; animation: th-ny-pulse 1.6s ease-in-out infinite; }
@keyframes th-ny-pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.08); } }
:root[data-theme="dark"] .pa-toast.th-card-ny { border-color: #fbbf24; box-shadow: 0 10px 30px rgba(251, 191, 36, .22); }
:root[data-theme="dark"] .th-ny-year { color: #fbbf24; }
@media (prefers-reduced-motion: reduce) { .th-ny-year { animation: none; } }


/* JULY 4 splash card (theme.js, Jul 3-5 or ?theme=fireworks): the PA Week card in red and blue. */
.pa-toast.th-card-4th { border-color: #2563eb; box-shadow: 0 10px 30px rgba(37, 99, 235, .26), 0 2px 6px rgba(0, 0, 0, .08); }
.pa-toast.th-card-4th .pa-toast-txt b { color: #b91c1c; }
:root[data-theme="dark"] .pa-toast.th-card-4th { border-color: #60a5fa; box-shadow: 0 10px 30px rgba(96, 165, 250, .22); }
:root[data-theme="dark"] .pa-toast.th-card-4th .pa-toast-txt b { color: #f87171; }


/* ============================================================
   VALENTINE'S (theme.js, Jan 31 - Feb 14 or ?theme=hearts): a heartbeat glow behind the homepage logo.
   The floating hearts and their pop sparkles are a canvas (theme.js). Two copies (the big desktop logo and the
   small phone logo); the one whose logo is showing gets .on. Compositor-only animation, no layout.
   ============================================================ */
.vd-beat {
  position: absolute; display: none; z-index: -1; pointer-events: none; border-radius: 50%;
  background: radial-gradient(circle, rgba(244, 63, 94, .34) 0%, rgba(244, 63, 94, .14) 42%, rgba(244, 63, 94, 0) 70%);
  animation: vd-beat 1.9s ease-in-out infinite; will-change: transform, opacity;
}
.vd-beat.on { display: block; }
.vd-beat-big { left: 50%; top: 44%; width: 250px; height: 250px; margin: -125px 0 0 -125px; }
.vd-beat-small { left: 50%; top: 40%; width: 170px; height: 170px; margin: -85px 0 0 -85px; }
:root[data-theme="dark"] .vd-beat { background: radial-gradient(circle, rgba(251, 113, 133, .36) 0%, rgba(251, 113, 133, .15) 42%, rgba(251, 113, 133, 0) 70%); }
@keyframes vd-beat {
  0%, 56%, 100% { transform: scale(.9); opacity: .55; }
  12% { transform: scale(1.12); opacity: 1; }
  26% { transform: scale(.96); opacity: .7; }
  38% { transform: scale(1.07); opacity: .92; }
}
body.th-paused .vd-beat { animation-play-state: paused; }
@media (prefers-reduced-motion: reduce) { .vd-beat { animation: none; opacity: .7; } }
@media print { .vd-beat { display: none !important; } }


/* ============================================================
   ST. PATRICK'S (theme.js, Mar 3-17 or ?theme=clovers): the lucky-sparkle cursor trail.
   The falling clovers and their glints are a canvas (theme.js).
   ============================================================ */
.cl-spark {
  position: fixed; left: 0; top: 0; width: 16px; height: 16px; margin: -8px 0 0 -8px; z-index: 9400;
  pointer-events: none; opacity: 0; will-change: transform, opacity;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 1 Q13.4 10.6 23 12 Q13.4 13.4 12 23 Q10.6 13.4 1 12 Q10.6 10.6 12 1Z' fill='%23facc15'/%3E%3Cpath d='M12 6 Q12.8 11.2 18 12 Q12.8 12.8 12 18 Q11.2 12.8 6 12 Q11.2 11.2 12 6Z' fill='%23fef9c3'/%3E%3C/svg%3E") center / contain no-repeat;
  filter: drop-shadow(0 0 2px rgba(22, 163, 74, .65));
}
@media (prefers-reduced-motion: reduce) { .cl-spark { display: none; } }
@media print { .cl-spark { display: none !important; } }


/* ============================================================
   PA WEEK (theme.js, Oct 5-9 2026 or ?theme=paweek): the medical-cross cursor trail.
   The drifting hearts/stethoscopes/crosses/pills/badges are a canvas (theme.js).
   ============================================================ */
.pa-trail {
  position: fixed; left: 0; top: 0; width: 16px; height: 16px; margin: -8px 0 0 -8px; z-index: 9400;
  pointer-events: none; opacity: 0; will-change: transform, opacity;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M9 2h6v7h7v6h-7v7H9v-7H2V9h7z' fill='%2314b8a6'/%3E%3Cpath d='M10.6 4h2.8v7.6H20v2.8h-6.6V20h-2.8v-5.6H4v-2.8h6.6z' fill='%23ffffff'/%3E%3C/svg%3E") center / contain no-repeat;
}
@media (prefers-reduced-motion: reduce) { .pa-trail { display: none; } }
@media print { .pa-trail { display: none !important; } }

@media print { .pa-toast { display: none !important; } }
