/* Base styles shared by every page. Phase 0 skeleton -- deliberately minimal;
   the real visual design lands with the screens that need it (Phases 3-5). */

*, *::before, *::after {
  box-sizing: border-box;
}

:root {
  --color-bg: #ffffff;
  --color-text: #1a1a1a;
  --color-muted: #595959; /* ~7:1 contrast on white, meets WCAG AA for body text */
  --color-accent: #0b5fff;
  --color-border: #d0d0d0;
  --color-correct: #15803d; /* ~5:1 contrast on white, meets WCAG AA for normal text */
  --color-incorrect: #b91c1c; /* ~6.5:1 contrast on white, meets WCAG AA for normal text */
  --font-sans: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

  /* 5165 scientific calculator (Phase 5.2) -- kept as its own small palette rather
     than reusing --color-bg/--color-border everywhere, so the calculator reads as a
     distinct tool (screen + keypad) against the page's own plainer surfaces. */
  --color-calc-body: #eef0f3;
  --color-calc-screen: #1a1a1a;
  --color-calc-screen-text: #f5f5f5;
  --color-calc-key: #ffffff;
  --color-calc-fn-key: #e2e8f0;
  --color-calc-op-key: #dbe9ff;

  /* 5485 periodic table (Phase 5.3, D-22) -- ten pastel category colors, light
     enough that the page's own dark --color-text stays comfortably legible on any
     of them without a per-category text-color override. Decorative only: the
     legend in run.html states each mapping in real text (SCHEMA.md S1.3), so this
     palette isn't the only signal for category. */
  --color-cat-alkali-metal: #ffd6d6;
  --color-cat-alkaline-earth-metal: #ffe4c4;
  --color-cat-transition-metal: #fff3b0;
  --color-cat-post-transition-metal: #d9f2d9;
  --color-cat-metalloid: #c9f2e0;
  --color-cat-nonmetal: #cdeaff;
  --color-cat-halogen: #d6d6ff;
  --color-cat-noble-gas: #e8d6ff;
  --color-cat-lanthanide: #ffd6ea;
  --color-cat-actinide: #f0d6c0;

  /* S6 progress dashboard redesign (Phase 6.6) -- meters + bullet-graph bars.
     Reuses --color-accent/--color-correct/--color-incorrect directly rather
     than a parallel palette; only a middle "building" amber and the neutral
     bar-track grays are new. */
  --color-accent-light: #b8d1ff; /* pale --color-accent, the "first attempt" meter */
  --color-warning: #b45309; /* ~4.6:1 contrast on white, meets WCAG AA for normal text */
  --color-bar-track: #edeff2;
  --color-bar-band-2: #e0e4ea;
  --color-bar-band-3: #ccd3dd;

  /* S2 test hub (Phase 6.8) -- a pale accent wash for the "pick up where you
     left off" callout background, distinct from --color-accent-light (which
     is reserved for the dashboard meter's "first attempt" stroke). */
  --color-accent-wash: #eef3ff;

  /* S1 masthead mark (Phase 6.11) -- a fixed navy/gold pair for the personal
     wordmark accent, deliberately distinct from --color-accent so the mark
     never reads as a clickable link. */
  --color-mark-navy: #12294f;
  --color-mark-gold: #c9a227;
}

html {
  color-scheme: light;
  /* Stop iOS's text autosizing. When a block of text is laid out wider than the screen,
     WebKit on iPhone decides the page wasn't built for phones and inflates that block's
     font size -- so one overflowing card rendered its description at nearly twice the
     size of the card beside it (seen in the iOS app, 2026-09-18). This only switches off
     that guesswork: a reader's own text-size setting still applies (N-19). */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  margin: 0;
  font-family: var(--font-sans);
  color: var(--color-text);
  background: var(--color-bg);
  line-height: 1.5;
}

/* Type scale for tablets and desktops (N-19). Every size in this sheet is in `rem`
   and `main` is a 40rem column, so raising the root size scales the whole layout --
   text, spacing, and the reading column -- from one knob, with no per-rule tuning.
   Phones are deliberately untouched: at 16px the column already fills the screen.

   Percentages rather than px, so a reader who has raised their browser's default
   text size keeps that increase on top of these steps -- setting px here would
   silently override that accessibility preference.

   The breakpoints are in rem, which a media query always resolves against the
   browser's default font size, never against the value set inside the query. No
   feedback loop: 48rem/64rem mean 768px/1024px whatever :root says below. */
@media (min-width: 48rem) {
  :root {
    font-size: 112.5%; /* 18px from a 16px default -- iPad portrait */
  }
}

@media (min-width: 64rem) {
  :root {
    font-size: 125%; /* 20px -- iPad landscape, desktop browsers */
  }
}

main {
  max-width: 40rem;
  margin: 0 auto;
  padding: 1.5rem;
}

/* `format: "code"` content (issue #45, 5652's pseudocode) -- js/reference-panel.js's
   renderContent wraps it in <pre><code>. overflow-x: auto on the block itself, not the
   page, so a long unwrapped line scrolls just the block (same finding-#17 reasoning as
   the periodic-table grid above). Scoped to the single <main> every page has, since
   this is the app's first real `<pre>` usage outside a reference/teaching panel. */
main pre {
  overflow-x: auto;
  margin: 0.5rem 0;
  padding: 0.6rem 0.85rem;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: 4px;
  font-size: 0.95rem;
  line-height: 1.4;
}

/* #question-fieldset/#study-fieldset (run.html) use a real <legend> for the question
   stem so the fieldset's accessible name comes from it natively (Phase 3.4) -- but
   browsers only reserve border space for a legend's first line, so any wrapped lines
   on a long word problem spilled out above the box instead of inside it (session
   owner finding, live-testing). `float: left; width: 100%` is the standard fix: it
   makes the browser lay the legend out as a normal block inside the fieldset's border
   rather than straddling it, so a stem of any length wraps fully inside the box next
   to the options below it. */
#question-stem,
#study-stem {
  float: left;
  width: 100%;
}

a {
  color: var(--color-accent);
}

/* S2's test-menu nav (test.html) is the only place several sibling <a> links get
   appended directly with no wrapping <li>/block markup between them -- a bare <nav>
   has no default layout, so with none of this those inline links ran together with
   zero visual gap (e.g. "Take a practice testStudy a topic" read as one link, found
   live-testing the NAS deploy). Scoped to the bare element since this is the only
   <nav> in the app today. */
nav {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.6rem;
  margin: 1rem 0;
}

/* Never rely on color alone (SCHEMA.md S1.3) -- every interactive element gets a
   visible, non-color focus indicator. */
:focus-visible {
  outline: 3px solid var(--color-accent);
  outline-offset: 2px;
}

/* Present to screen readers, invisible on screen (Phase 3.4, SCHEMA.md S1.3, finding
   #15) -- for the runner's aria-live announcer region. `display:none`/`hidden` would
   remove it from the accessibility tree entirely, defeating the point. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* run.html's Start screen (full test only, not the untimed study/drill flows) --
   the wall-clock timer never pauses while the app is backgrounded or killed
   (session owner's explicit call over pausing it, given a real proctored exam
   doesn't pause either), so this states that plainly before commitment rather
   than as a surprise mid-test. Deliberately plain -- same font/size/color as
   #start-summary above it, just spaced apart, with only "real clock time" and
   "does not pause" bolded inline for emphasis. */
.start-warning {
  margin-top: 1.5rem;
}

.status-note {
  color: var(--color-muted);
  border: 1px dashed var(--color-border);
  border-radius: 4px;
  padding: 0.75rem 1rem;
}

/* Review-pass list (Phase 3.1, SCHEMA.md S3) and S4's full review list (Phase 4.2) --
   one bordered block per question, spaced enough that each row's text (and, on S3,
   its Reopen button) reads as one unit, not a wall of text. `> li` on the S4 selector
   is deliberate, not just tidiness: each full-review row nests its own `<ul>` of
   options (code review finding), and a bare `#full-review-list li` descendant selector
   would also catch those, wrapping every individual option in its own bordered box.
   #drill-full-review-list (Phase 6.9.4) reuses js/review-row.js's identical markup for
   Practice-a-topic/Category-test's own completion-screen review, same reasoning. */
#review-list li,
#full-review-list > li,
#drill-full-review-list > li {
  border: 1px solid var(--color-border);
  border-radius: 4px;
  padding: 0.5rem 0.75rem;
  margin-bottom: 0.5rem;
}

/* Sighted-user visual enhancement for a flagged row's Reopen button -- the
   "(flagged)" text already carries this to a screen reader (SCHEMA.md S1.3's
   "never by color/icon alone"), so this is decorative, not the only signal. */
.flag-icon {
  margin-left: 0.5rem;
}

/* S4 full review list (Phase 4.2) -- colors just the "(correct answer)"/"(your
   answer -- correct/incorrect)" suffix results.html's optionSuffix() adds, not the
   whole option line. Same "never by color alone" pattern as .flag-icon above: the
   suffix text itself is the real signal to a screen reader, this is additive. */
.option-suffix-correct {
  color: var(--color-correct);
}
.option-suffix-incorrect {
  color: var(--color-incorrect);
}

/* S5 topic-study category picker (Phase 4.1, SCHEMA.md §2.4) -- depth-based
   indentation is set inline per row (arbitrary tree depth), this just removes the
   default list markers/margin so the indentation reads cleanly. */
#category-list {
  list-style: none;
  padding-left: 0;
}

#category-list li {
  padding-top: 0.25rem;
  padding-bottom: 0.25rem;
}

/* S6 progress dashboard (Phase 6.6) -- two circular meters (first vs. latest
   completed attempt) plus a delta indicator between them. One hue
   (--color-accent), two shades: --color-accent-light recedes for "first",
   the full accent is reserved for "latest" so the pair reads as a
   before/after progression rather than two unrelated series. */
#comparison-meters {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1.25rem;
  flex-wrap: wrap;
  margin: 1rem 0;
}

.dashboard-meter {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
}

.dashboard-meter svg {
  display: block;
}

.dashboard-meter-track {
  stroke: var(--color-bar-track);
}

.dashboard-meter-fill-first {
  stroke: var(--color-accent-light);
}

.dashboard-meter-fill-latest,
.dashboard-meter-fill-single {
  stroke: var(--color-accent);
}

.dashboard-meter-value {
  font-size: 1.7rem;
  font-weight: 700;
  fill: var(--color-text);
}

.dashboard-meter-caption {
  text-align: center;
  font-size: 0.85rem;
}

.dashboard-meter-caption .label {
  display: block;
  font-weight: 600;
}

.dashboard-meter-caption .sub {
  display: block;
  color: var(--color-muted);
  font-size: 0.78rem;
}

.dashboard-delta {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.2rem;
}

.dashboard-delta svg {
  width: 1.75rem;
  height: 1.75rem;
}

.dashboard-delta-value {
  font-weight: 700;
  font-size: 0.95rem;
  white-space: nowrap;
}

/* Not color-only (SCHEMA.md S1.3): the up/down arrow glyph and the sign on the
   number itself both carry direction, this color is additive. */
.dashboard-delta-value.up {
  color: var(--color-correct);
}

.dashboard-delta-value.down {
  color: var(--color-incorrect);
}

.dashboard-delta-value.flat {
  color: var(--color-muted);
}

.dashboard-delta-note {
  font-size: 0.72rem;
  color: var(--color-muted);
  text-align: center;
  max-width: 6.5rem;
}

/* S6 category breakdown (Phase 6.6) -- a bullet graph per category: the
   background bands mark the needs-practice/building/strong ranges, the
   status-colored inner bar is the actual all-time accuracy, and the tick at
   75% marks the "Strong" threshold the legend below states in real text. */
#category-stats-list {
  list-style: none;
  padding-left: 0;
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
}

.category-stat-row {
  display: grid;
  grid-template-columns: 9.5rem 1fr auto;
  align-items: center;
  gap: 0.85rem;
}

.category-stat-name {
  font-size: 0.9rem;
  font-weight: 600;
}

.category-stat-track {
  position: relative;
  height: 1.1rem;
  border-radius: 4px;
  overflow: hidden;
  background: linear-gradient(
    to right,
    var(--color-bar-track) 0%, var(--color-bar-track) 50%,
    var(--color-bar-band-2) 50%, var(--color-bar-band-2) 75%,
    var(--color-bar-band-3) 75%, var(--color-bar-band-3) 100%
  );
}

.category-stat-track.is-pending {
  background: var(--color-bar-track);
}

.category-stat-fill {
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  height: 0.45rem;
  width: var(--pct, 0%);
  border-radius: 2px;
}

.category-stat-fill.good { background: var(--color-correct); }
.category-stat-fill.warn { background: var(--color-warning); }
.category-stat-fill.crit { background: var(--color-incorrect); }

.category-stat-target {
  position: absolute;
  top: 1px;
  bottom: 1px;
  left: 75%;
  width: 2px;
  background: var(--color-text);
  opacity: 0.5;
}

.category-stat-meta {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  justify-self: end;
}

.category-stat-pct {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  font-size: 0.9rem;
  min-width: 2.6em;
  text-align: right;
}

/* Not color-only (SCHEMA.md S1.3): every chip pairs its color with both an
   icon and a text label ("Strong"/"Building"/"Needs practice"/"N of 5
   answered"), matching .flag-icon/.option-suffix-* elsewhere on the site. */
.status-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.72rem;
  font-weight: 600;
  padding: 0.15rem 0.55rem 0.15rem 0.4rem;
  border-radius: 999px;
  white-space: nowrap;
}

.status-chip svg {
  width: 0.75rem;
  height: 0.75rem;
  flex: none;
}

.status-chip.good { background: rgba(21, 128, 61, 0.12); color: var(--color-correct); }
.status-chip.warn { background: rgba(180, 83, 9, 0.14); color: var(--color-warning); }
.status-chip.crit { background: rgba(185, 28, 28, 0.12); color: var(--color-incorrect); }
.status-chip.pending { background: rgba(89, 89, 89, 0.12); color: var(--color-muted); }

#category-legend {
  margin: 1rem 0 0;
  padding-top: 0.85rem;
  border-top: 1px solid var(--color-border);
  font-size: 0.8rem;
  color: var(--color-muted);
}

@media (max-width: 30rem) {
  .category-stat-row {
    grid-template-columns: 1fr;
    row-gap: 0.35rem;
  }
  .category-stat-meta {
    justify-self: start;
  }
}

/* S6 "Start another test" (Phase 6.6) -- two visually distinct paths, so the
   full-test-vs-category-test "does this count?" distinction is legible in
   the UI itself, not just something the user has to remember. */
#start-test-options {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
}

.start-test-option {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1rem;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  flex-wrap: wrap;
}

.start-test-text {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  min-width: 12rem;
  flex: 1 1 14rem;
}

.start-test-title {
  font-weight: 700;
  font-size: 0.92rem;
}

.start-test-desc {
  font-size: 0.8rem;
  color: var(--color-muted);
  line-height: 1.4;
}

.category-test-controls {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex: 0 0 auto;
}

.category-test-controls select {
  font: inherit;
  font-size: 0.85rem;
  color: var(--color-text);
  padding: 0.45rem 0.5rem;
  border: 1px solid var(--color-border);
  border-radius: 4px;
  max-width: 12rem;
}

.btn {
  display: inline-flex;
  align-items: center;
  font-weight: 700;
  font-size: 0.85rem;
  text-decoration: none;
  border-radius: 4px;
  padding: 0.55rem 0.9rem;
  white-space: nowrap;
  border: 1px solid transparent;
  cursor: pointer;
}

/* S2 test hub (Phase 6.8) -- the "pick up where you left off" callout wrapping
   the pre-existing résumé/due/weakest-category entry points (previously bare
   sibling <a> links) in one grouped block, matching this redesign's move from
   plain links to something closer to a real GUI. */
.continue-callout {
  display: flex;
  flex-direction: column;
  gap: 0.65rem;
  padding: 1.1rem 1.25rem;
  margin: 0 0 1.5rem;
  border: 1px solid var(--color-accent-light);
  background: var(--color-accent-wash);
  border-radius: 10px;
}

/* An author-stylesheet `display` declaration beats the UA stylesheet's own
   `[hidden] { display: none }` regardless of source order (equal specificity,
   author origin wins) -- without this override, JS setting the `hidden`
   attribute here would do nothing and the empty callout would always render
   (caught live-testing: it rendered on a zero-state page with no resume/due/
   weakest items at all). */
.continue-callout[hidden] {
  display: none;
}

.continue-eyebrow {
  margin: 0;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-accent);
}

.continue-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}

.continue-item + .continue-item {
  padding-top: 0.6rem;
  border-top: 1px solid rgba(11, 95, 255, 0.16);
}

.continue-text {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}

.continue-title {
  font-weight: 600;
  font-size: 0.92rem;
}

.continue-sub {
  font-size: 0.78rem;
  color: var(--color-muted);
}

/* S2 test hub (Phase 6.8) -- the collapsed "Back up or restore progress"
   section wrapping the pre-existing export/import controls (Phase 6.2/6.7),
   moved out of the page's main flow now that the hub has more to show above
   it. */
details.backup {
  border: 1px solid var(--color-border);
  border-radius: 8px;
  padding: 0.15rem 0.9rem;
  margin-top: 1.5rem;
}

details.backup summary {
  padding: 0.6rem 0;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
  color: var(--color-muted);
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

details.backup summary svg {
  width: 1rem;
  height: 1rem;
}

.backup-body {
  padding: 0 0 0.9rem;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.backup-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}

.backup-row-text {
  margin: 0;
  font-size: 0.8rem;
  color: var(--color-muted);
  max-width: 22rem;
}

/* A <label> wrapping a visually-hidden file input, styled like .btn so
   "Upload progress" reads as a button even though the real interactive
   element underneath is the file input itself (finding carried over from the
   original Phase 6.7 implementation: the click target must be the actual
   <input type=file>, not a proxy button, for the OS file picker to open). */
.file-btn {
  position: relative;
  overflow: hidden;
}

.file-btn input[type="file"] {
  position: absolute;
  inset: 0;
  opacity: 0;
  cursor: pointer;
}

.btn-primary {
  background: var(--color-accent);
  color: #ffffff;
}

.btn-secondary {
  background: transparent;
  color: var(--color-accent);
  border-color: var(--color-accent);
}

/* "Clear performance data" (test.html) and its confirm dialog -- --color-incorrect
   rather than --color-accent, so a destructive/irreversible action reads visually
   distinct from the backup/restore buttons next to it. */
.btn-danger {
  background: transparent;
  color: var(--color-incorrect);
  border-color: var(--color-incorrect);
}

.btn[disabled],
.btn[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
}

/* A paid control inside the iOS app (D-46). Dimmed like a disabled button, but
   deliberately NOT aria-disabled and not `not-allowed`: since 11.2 Phase E this link is
   the way to buy the subject, so announcing it as unavailable would hide the only
   purchase entry point from a screen-reader user (code review finding). */
.btn-locked {
  opacity: 0.65;
}

@media (max-width: 26rem) {
  .start-test-option {
    flex-direction: column;
    align-items: stretch;
    /* The base rule's `space-between` is meant for the wide layout, where it pushes
       the button to the card's right edge. Once the card stacks, "between" becomes
       vertical and would push the button to the bottom of the card instead, so the
       stacked card packs from the top and lets `gap` do the spacing. */
    justify-content: flex-start;
  }
  /* In this stacked layout the card is a vertical flex column, and iOS Safari/WebKit lets
     a stretched item grow to its content's width anyway -- so one long <select> option
     widened the whole card's inner row, and the text and button went off the right edge.
     Desktop engines don't, which is why this only showed on iPhone (2026-09-18). Capping
     every child at the card's width, and letting it shrink, keeps them inside it. */
  .start-test-option > * {
    min-width: 0;
    max-width: 100%;
  }
  .category-test-controls {
    width: 100%;
  }
  .start-test-text {
    /* `flex: 1 1 14rem` on the base rule is a *width* basis for the row layout.
       flex-basis follows the main axis, so in this column layout that same 14rem
       silently became a 224px minimum *height* -- every card was forced to 314px
       tall no matter how short its text, with the button stranded at the bottom
       (measured on the live site at 375px before this fix). Size to content here. */
    flex: 0 0 auto;
  }
  .category-test-controls select {
    max-width: none;
    /* A <select> is as wide as its longest option, and Practice a topic and Study a
       topic list indented sub-topics with long labels (Category test lists only top-level
       topics, which is why it alone looked right). A zero basis and width make it claim
       no width of its own and simply fill what the row has left beside the button; a
       long option shows truncated in the closed control and in full when the picker
       opens. The card-level rule above is the other half of the fix. */
    flex: 1 1 0;
    width: 0;
    min-width: 0;
  }
}

/* S5 per-question feedback (Phase 4.1, SCHEMA.md §1.1 S5) -- same bordered-box
   treatment as .status-note, so a revealed answer reads as a distinct block from
   the question above it. */
#study-feedback {
  border: 1px solid var(--color-border);
  border-radius: 4px;
  padding: 0.75rem 1rem;
  margin-top: 0.75rem;
}

/* Confirm-before-submit dialog (Phase 3.2, SCHEMA.md S3, finding #11) -- native
   <dialog> for its built-in focus trap and Esc-to-close, styled just enough that it
   doesn't sit edge-to-edge on a wide viewport. */
#confirm-submit-dialog {
  max-width: 28rem;
  border: 1px solid var(--color-border);
  border-radius: 4px;
  padding: 1.25rem;
}

#confirm-submit-dialog::backdrop {
  background: rgb(0 0 0 / 0.4);
}

/* 5165 scientific calculator (Phase 5.2, D-14) -- a TI-84 Plus CE-styled keypad
   interaction (dark screen, grid of keys, a "2nd" shift key), not a reproduction of
   TI's actual branding/model name/trade dress -- the screen-plus-keypad-plus-shift-
   key shape is a generic scientific-calculator paradigm shared across many devices.
   In document flow, not an overlay (see run.html's comment at the panel's markup):
   this is a bordered panel like #study-feedback below, not a floating card, which is
   the simplest way to guarantee it never obscures the question stem (finding #17). */
#calculator-panel {
  max-width: 20rem;
  margin: 0.75rem 0;
  padding: 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  background: var(--color-calc-body);
}

#calculator-mode-indicator {
  margin: 0 0 0.35rem;
  font-size: 0.85rem;
  color: var(--color-muted);
}

#calculator-expression {
  width: 100%;
  box-sizing: border-box;
  font: 1.1rem/1.4 ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
  padding: 0.5rem;
  background: var(--color-calc-screen);
  color: var(--color-calc-screen-text);
  border: 1px solid var(--color-border);
  border-radius: 4px;
}

#calculator-result {
  min-height: 1.5rem;
  margin: 0.35rem 0 0.6rem;
  padding: 0.35rem 0.5rem;
  font: 1.1rem/1.4 ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
  background: var(--color-calc-screen);
  color: var(--color-calc-screen-text);
  border: 1px solid var(--color-border);
  border-radius: 4px;
}

#calculator-keypad {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0.35rem;
}

#calculator-keypad button {
  padding: 0.6rem 0;
  font-size: 1rem;
  border: 1px solid var(--color-border);
  border-radius: 4px;
  background: var(--color-calc-key);
  color: var(--color-text);
  cursor: pointer;
}

#calculator-keypad button[data-key-role="function"] {
  background: var(--color-calc-fn-key);
}

#calculator-keypad button[data-key-role="operator"] {
  background: var(--color-calc-op-key);
}

#calculator-keypad button[data-key-role="evaluate"] {
  background: var(--color-accent);
  color: #ffffff;
}

/* Not color-only (SCHEMA.md S1.3): aria-pressed carries the 2nd-shift state to a
   screen reader, and the visible border change here is layered on top of the
   button's own text swapping (e.g. "sin" -> "sin⁻¹") and #calculator-mode-indicator's
   text, not a replacement for either. */
#calculator-keypad button[aria-pressed="true"] {
  border-color: var(--color-accent);
  border-width: 2px;
}

#calculator-close-button {
  margin-top: 0.6rem;
}

/* Reference panel (Phase 5.1, D-21) -- same in-document-flow, bordered-panel shape
   as #calculator-panel above (finding #17), but a plain reading surface rather than
   a screen-plus-keypad tool, so it reuses --color-bg/--color-border directly instead
   of the calculator's own dedicated palette. */
#reference-panel {
  max-width: 28rem;
  margin: 0.75rem 0;
  padding: 0.75rem 1rem;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  background: var(--color-bg);
}

#reference-list section + section {
  margin-top: 1rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--color-border);
}

#reference-list h3 {
  margin: 0 0 0.5rem;
  font-size: 1.1rem;
  text-align: center;
  color: var(--color-accent);
}

#reference-list dl {
  margin: 0;
}

#reference-list dt {
  font-weight: 600;
}

#reference-list dd {
  margin: 0.15rem 0 0.6rem;
  color: var(--color-text);
}

#reference-list math {
  font-size: 1.05rem;
}

#reference-close-button {
  margin-top: 0.6rem;
}

/* 5485 periodic table + physical constants (Phase 5.3, D-22). `overflow-x: auto`
   on the grid itself, not the panel, so a narrow viewport scrolls just the table
   horizontally rather than the whole panel/page (finding #17: the panel must stay
   readable, not force horizontal scroll on unrelated content). */
.periodic-table-grid {
  display: grid;
  grid-template-columns: repeat(18, minmax(2.4rem, 1fr));
  gap: 2px;
  margin: 0.75rem 0;
  overflow-x: auto;
}

.element-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 2.6rem;
  padding: 0.15rem;
  border: 1px solid var(--color-border);
  border-radius: 3px;
  font-size: 0.7rem;
  line-height: 1.1;
}

/* Standard printed-periodic-table convention: the lanthanide/actinide display rows
   (SCHEMA.md §2.10's period 8/9) get a visual gap above them so they read as a
   footnote block, not a literal continuation of period 7. */
.element-cell[data-period="8"] {
  margin-top: 0.6rem;
}

.element-number {
  font-size: 0.55rem;
  align-self: flex-start;
}

.element-symbol {
  font-size: 1rem;
  font-weight: 600;
}

.element-mass {
  font-size: 0.55rem;
}

.element-category-alkali-metal { background: var(--color-cat-alkali-metal); }
.element-category-alkaline-earth-metal { background: var(--color-cat-alkaline-earth-metal); }
.element-category-transition-metal { background: var(--color-cat-transition-metal); }
.element-category-post-transition-metal { background: var(--color-cat-post-transition-metal); }
.element-category-metalloid { background: var(--color-cat-metalloid); }
.element-category-nonmetal { background: var(--color-cat-nonmetal); }
.element-category-halogen { background: var(--color-cat-halogen); }
.element-category-noble-gas { background: var(--color-cat-noble-gas); }
.element-category-lanthanide { background: var(--color-cat-lanthanide); }
.element-category-actinide { background: var(--color-cat-actinide); }

.element-category-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1rem;
  list-style: none;
  padding: 0;
  margin: 0.75rem 0;
  font-size: 0.85rem;
}

.element-category-legend li {
  display: flex;
  align-items: center;
  gap: 0.3rem;
}

.element-category-swatch {
  display: inline-block;
  width: 0.9rem;
  height: 0.9rem;
  border: 1px solid var(--color-border);
  border-radius: 2px;
}

/* teach.html, "Study a topic" (Phase 6.9, D-29). Same section/dl/dt/dd rules as
   #reference-list (Phase 5.1) since renderReferencePanel builds identical markup for
   both -- but read as ordinary page content, not a bordered floating panel, since
   this page has nothing else on it competing for space the way the reference panel
   shares a test-taking screen with a question. */
#chapter-content {
  max-width: 40rem;
}

#chapter-content section + section {
  margin-top: 1.5rem;
  padding-top: 1rem;
  border-top: 1px solid var(--color-border);
}

#chapter-content h3 {
  margin: 0 0 0.5rem;
}

#chapter-content dl {
  margin: 0;
}

#chapter-content dt {
  font-weight: 600;
  margin-top: 0.75rem;
}

#chapter-content dd {
  margin: 0.15rem 0 0;
  color: var(--color-text);
}

#chapter-content math {
  font-size: 1.1rem;
}

/* S1 study hub (Phase 9.2) -- index.html only, scoped under .page-hub so the
   wider grid layout doesn't touch the 40rem <main> every other page uses.
   Palette is the existing token set throughout; the bar reuses S6's bullet-graph
   language including its 75% readiness tick, so a score reads the same on S1 and
   S6. Light-only like every other screen here. */
.page-hub main {
  max-width: 60rem;
}

.hub-masthead {
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1.5rem;
  margin-bottom: 1.5rem;
  position: relative;
}

.hub-masthead-text {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  min-width: 0;
}

/* Subtle illustration background for the masthead — welcoming, approachable mood */
.hub-masthead::before {
  content: "";
  position: absolute;
  top: -3rem;
  right: -2rem;
  width: 200px;
  height: 200px;
  background: radial-gradient(circle at 30% 30%, var(--color-accent-light) 0%, transparent 70%);
  border-radius: 50%;
  z-index: -1;
  opacity: 0.3;
}

.hub-wordmark {
  font-size: 2.125rem;
  font-weight: 700;
  letter-spacing: -0.021em;
  line-height: 1.1;
  margin: 0;
  text-wrap: balance;
}

.hub-wordmark-thin {
  font-weight: 400;
  color: var(--color-muted);
}

.hub-tagline {
  margin: 0;
  font-size: 1.0625rem;
  color: var(--color-muted);
  max-width: 46ch;
}

.hub-disclaimer {
  margin: 0;
  font-size: 0.8125rem;
  color: var(--color-muted);
}

/* Motto (Phase 6.11.1). Deliberately a different font family (serif) from
   the rest of the page's system-sans typography, so it reads as a distinct
   voice -- a personal statement, not UI chrome. list-style:none plus a
   counter-driven ::before doesn't remove the <ol>'s real list semantics, so
   a screen reader still gets "list, 3 items" even with the custom numerals. */
.hub-motto {
  margin: 0 0 1.75rem;
  text-align: center;
  font-family: Georgia, "Times New Roman", Times, serif;
}

/* Same right-anchored 58% column as .hub-motto-list below (widened and
   nudged off the far edge via the small right margin, so item 2's longest
   line fits without wrapping), both left-aligned within it, so the two lines
   share one left edge instead of the lead line spanning the full width. */
.hub-motto-lead {
  margin: 0 1.5% 0.6rem auto;
  max-width: 58%;
  text-align: left;
  font-size: 1.75rem;
  font-style: italic;
  font-weight: 600;
  color: var(--color-text);
}

/* Pushed right and left-aligned so the list sits above the two seated
   students in the illustration below, rather than centered under the lead
   line -- the lead line itself is untouched. */
.hub-motto-list {
  margin: 0 1.5% 0 auto;
  padding: 0;
  max-width: 58%;
  text-align: left;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  counter-reset: motto;
}

@media (max-width: 30rem) {
  .hub-motto-lead,
  .hub-motto-list {
    margin-left: 0;
    max-width: none;
  }
}

.hub-motto-list li {
  font-size: 1.375rem;
  color: var(--color-muted);
  counter-increment: motto;
}

.hub-motto-list li::before {
  content: counter(motto) ". ";
  font-weight: 700;
  color: var(--color-mark-navy);
}

/* Hero illustration (Phase 6.11.1). Explicit aspect-ratio, not just
   height:auto -- learned live-testing the masthead mark that an inline SVG
   sized by width alone doesn't reliably derive its height from viewBox
   across browsers, and silently crops instead. */
.hub-hero-illustration {
  margin: 0.5rem 0 2rem;
}

.hub-hero-svg {
  display: block;
  width: 100%;
  aspect-ratio: 800 / 190;
  height: auto;
}

.hub-summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1.25rem;
  padding: 0.75rem 0;
  margin: 0 0 1.75rem;
  border-top: 1px solid var(--color-bar-track);
  border-bottom: 1px solid var(--color-bar-track);
  font-size: 0.9375rem;
  color: var(--color-muted);
}

.hub-summary b {
  font-weight: 650;
  color: var(--color-text);
  font-variant-numeric: tabular-nums;
}

/* Resume callout -- same accent-wash treatment as S2's own "pick up where you
   left off" box (Phase 6.8), so the two read as the same affordance. */
.hub-resume {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem 1rem;
  background: var(--color-accent-wash);
  border: 1px solid var(--color-accent-light);
  border-radius: 10px;
  padding: 1rem 1.125rem;
  margin-bottom: 1.75rem;
  text-decoration: none;
  color: var(--color-text);
}

.hub-resume:hover,
.hub-resume:focus-visible {
  border-color: var(--color-accent);
}

.hub-resume-body {
  flex: 1 1 16rem;
  min-width: 0;
}

.hub-resume-label {
  display: block;
  font-size: 0.8125rem;
  font-weight: 650;
  color: var(--color-accent);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  margin-bottom: 0.25rem;
}

.hub-resume-title {
  font-size: 1.0625rem;
  font-weight: 600;
}

.hub-resume-detail {
  font-size: 0.9375rem;
  color: var(--color-muted);
  font-variant-numeric: tabular-nums;
}

.hub-resume-cta {
  flex: 0 0 auto;
  background: var(--color-accent);
  color: #ffffff;
  font-weight: 600;
  font-size: 0.9375rem;
  padding: 0.5rem 1rem;
  border-radius: 7px;
}

.hub-section-label {
  font-size: 0.8125rem;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--color-muted);
  margin: 0 0 0.75rem;
}

/* One column on phones, a balanced 2x2 above that. Deliberately an explicit
   breakpoint rather than auto-fit: auto-fit packs 3 across at desktop widths and
   strands the fourth subject alone on its own row. */
.hub-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1rem;
}

@media (min-width: 44rem) {
  .hub-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

.hub-card {
  display: flex;
  flex-direction: column;
  gap: 0.875rem;
  background: var(--color-bg);
  border: 2px solid var(--color-bar-track);
  border-radius: 10px;
  padding: 1.125rem;
  text-decoration: none;
  color: var(--color-text);
  transition: border-color 120ms ease, transform 120ms ease, box-shadow 120ms ease;
  position: relative;
  overflow: hidden;
}

/* Illustrated background decoration on cards — welcoming visual accent */
.hub-card::before {
  content: "";
  position: absolute;
  top: -20px;
  right: -30px;
  width: 100px;
  height: 100px;
  border-radius: 50%;
  opacity: 0.06;
  z-index: 0;
  pointer-events: none;
}

.hub-card > * {
  position: relative;
  z-index: 1;
}

.hub-card:hover,
.hub-card:focus-visible {
  border-color: var(--color-accent);
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(11, 95, 255, 0.15);
}

.hub-card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
}

.hub-card-name {
  font-size: 1.375rem;
  font-weight: 640;
  letter-spacing: -0.012em;
  line-height: 1.2;
  margin: 0;
}

.hub-card-code {
  flex: 0 0 auto;
  font-size: 0.8125rem;
  color: var(--color-muted);
  font-variant-numeric: tabular-nums;
}

.hub-card-meta {
  margin: -0.5rem 0 0;
  font-size: 0.9375rem;
  color: var(--color-muted);
  font-variant-numeric: tabular-nums;
}

.hub-state {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.hub-pill {
  font-size: 0.8125rem;
  font-weight: 650;
  padding: 0.1875rem 0.5rem;
  border-radius: 999px;
}

.hub-pill-idle {
  background: var(--color-bar-track);
  color: var(--color-muted);
}

.hub-pill-live {
  background: var(--color-accent-wash);
  color: var(--color-accent);
}

.hub-pill-done {
  background: #e7f4ec;
  color: var(--color-correct);
}

.hub-state-note {
  font-size: 0.9375rem;
  color: var(--color-muted);
  font-variant-numeric: tabular-nums;
}

.hub-score {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.hub-score-num {
  font-size: 1.5rem;
  font-weight: 680;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.hub-score-num.is-good {
  color: var(--color-correct);
}

.hub-score-num.is-building {
  color: var(--color-warning);
}

.hub-bar {
  position: relative;
  flex: 1;
  height: 8px;
  background: var(--color-bar-track);
  border-radius: 999px;
  overflow: hidden;
}

.hub-bar-fill {
  position: absolute;
  inset: 0 auto 0 0;
  border-radius: 999px;
}

.hub-bar-fill.is-good {
  background: var(--color-correct);
}

.hub-bar-fill.is-building {
  background: var(--color-warning);
}

.hub-bar-fill.is-progress {
  background: var(--color-accent-light);
}

/* The 75% readiness tick S6's category bars also draw. */
.hub-bar-tick {
  position: absolute;
  top: -2px;
  bottom: -2px;
  left: 75%;
  width: 2px;
  background: var(--color-text);
  opacity: 0.45;
}

.hub-card-cta {
  margin-top: auto;
  font-size: 0.9375rem;
  font-weight: 620;
  color: var(--color-accent);
}

.hub-empty-note {
  margin: 0;
  font-size: 0.9375rem;
  color: var(--color-muted);
}

.hub-footnote {
  margin: 2rem 0 0;
  padding-top: 1rem;
  border-top: 1px solid var(--color-bar-track);
  font-size: 0.8125rem;
  color: var(--color-muted);
}

/* Site-wide contact line. It sits outside <main>, so it has to repeat main's own width,
   centring and side padding rather than inheriting them -- and on the hub that means the
   hub's wider 60rem, not the 40rem every other page uses, or the line sits visibly
   indented from the footnote right above it. */
.site-footer {
  max-width: 40rem;
  margin: 0 auto;
  padding: 0 1.5rem 2rem;
  font-size: 0.8125rem;
  color: var(--color-muted);
}

.page-hub .site-footer {
  max-width: 60rem;
}

.site-footer p {
  margin: 0;
}

/* The page's quiet mark (Phase 6.11). Sits in the masthead's top-right corner
   -- findable, not announced. No caption by design: it's an accent for those
   who recognize it, and labelling it would defeat that. Fixed navy/gold
   rather than the muted-to-accent treatment other decorative marks on this
   site use, since this one carries its own two-color identity. */
.hub-signature {
  flex: 0 0 auto;
  display: flex;
  align-items: flex-start;
  padding-top: 0.3rem;
}

.hub-signature-mark {
  width: 100px;
  aspect-ratio: 180 / 74;
  height: auto;
  overflow: visible;
  opacity: 0.7;
  transition: opacity 200ms ease;
}

.hub-signature-mark text {
  font-family: var(--font-sans);
  font-size: 26px;
  font-weight: 800;
  paint-order: stroke fill;
  fill: var(--color-mark-navy);
  stroke: var(--color-mark-gold);
  stroke-width: 2.2;
  stroke-linejoin: round;
}

/* A small reward for noticing it. Not a control -- nothing is clickable here,
   so this only ever reads as a bit of life on the page. */
.hub-signature:hover .hub-signature-mark {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .hub-card {
    transition: none;
  }

  .hub-card:hover,
  .hub-card:focus-visible {
    transform: none;
  }

  .hub-signature-mark {
    transition: none;
  }
}

@media (max-width: 26rem) {
  .hub-signature-mark {
    width: 74px;
  }
}
