/* The v2 reader shell.
 *
 * Loaded INSTEAD of shell.css, and AFTER tokens.css and app.css. app.css keeps
 * providing the card internals that were already right — rectangular option
 * tiles, match-table stacking, statement chips, assertion/reason blocks, the
 * shake. This file replaces the four-pane shell around them and corrects the
 * six card defects listed in the design doc.
 *
 * It defines no design token. tokens.css is the only place a colour, radius,
 * shadow or type face is chosen, and every value below refers to one.
 *
 * The layout rule that shapes everything here: the question is the page, and
 * navigation is summoned. On a phone that means the reader is fullscreen from
 * first paint and the navigator arrives as a bottom sheet — replacing three
 * off-canvas drawers that shared one screen edge.
 */

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html,
body {
  width: 100%;
  height: 100%;
  overflow: hidden;
  font-family: var(--font-ui);
  background-color: var(--surface-page);
  color: var(--text-main);
}

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 600;
}

button {
  font: inherit;
  color: inherit;
}

/* dvh, not vh: on mobile Safari the URL bar makes 100vh taller than the
   visible viewport, which would push the bottom bar — the one control a
   thumb reaches for — below the fold. The vh line is the fallback for
   browsers without dvh, where the two are equal anyway. */
.reader {
  display: flex;
  width: 100%;
  height: 100vh;
  height: 100dvh;
  position: relative;
  overflow: hidden;
}

/* ── Navigator ─────────────────────────────────────────────────────────── */

.navigator {
  width: 300px;
  flex-shrink: 0;
  height: 100%;
  display: flex;
  flex-direction: column;
  background: var(--surface-card);
  border-right: 1px solid var(--border-color);
  min-height: 0;
}

.navigator-head {
  padding: 14px 16px 0;
  border-bottom: 1px solid var(--border-color);
  background: var(--surface-sunken);
  flex-shrink: 0;
}

.navigator-brand {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 12px;
}

.navigator-title {
  font-family: var(--font-display);
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: -0.015em;
  line-height: 1.25;
}

.navigator-subtitle {
  font-family: var(--font-ui);
  font-size: 0.53rem;
  font-weight: 600;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-top: 5px;
}

.back-link {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--text-muted);
  text-decoration: none;
  padding: 4px 0;
  margin-bottom: 8px;
}

.back-link:hover {
  color: var(--primary-text);
}

/* Browse / Questions tabs. These are what let one 300px column carry both the
   syllabus tree and the question rail — v1 gave each its own permanent column
   and spent 410px before the question got any width at all. */
.nav-tabs {
  display: flex;
  gap: 2px;
}

.nav-tab {
  flex: 1;
  min-width: 0;
  min-height: var(--tap-target);
  padding: 10px 4px;
  white-space: nowrap;
  border: none;
  border-bottom: 2px solid transparent;
  background: none;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--text-muted);
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s;
}

.nav-tab:hover {
  color: var(--text-main);
}

.nav-tab[aria-selected="true"] {
  color: var(--primary-text);
  border-bottom-color: var(--primary);
}

.nav-tab-count {
  font-family: var(--font-display);
  font-variant-numeric: tabular-nums;
  font-size: 0.72rem;
  color: var(--text-faint);
  margin-left: 4px;
}

.navigator-body {
  flex: 1;
  overflow-y: auto;
  overscroll-behavior: contain;
  min-height: 0;
}

.nav-pane {
  display: none;
  padding: 14px;
  flex-direction: column;
  gap: 14px;
}

.nav-pane.is-active {
  display: flex;
}

/* ── Filters disclosure ────────────────────────────────────────────────── */

.filters {
  border: 1px solid var(--border-color);
  border-radius: var(--radius);
  background: var(--surface-sunken);
  overflow: hidden;
}

.filters-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
  min-height: var(--tap-target);
  padding: 10px 12px;
  border: none;
  background: none;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--text-main);
  cursor: pointer;
  text-align: left;
}

.filters-summary::-webkit-details-marker {
  display: none;
}

.filters-count {
  margin-left: auto;
  min-width: 18px;
  height: 18px;
  padding: 0 6px;
  border-radius: 9px;
  background: var(--primary-text);
  color: #ffffff;
  font-family: var(--font-display);
  font-variant-numeric: tabular-nums;
  font-size: 0.66rem;
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.filters-count:empty,
.filters-count[hidden] {
  display: none;
}

.filters-caret {
  font-size: 0.6rem;
  color: var(--text-muted);
  transition: transform 0.2s;
}

.filters[open] .filters-caret {
  transform: rotate(90deg);
}

.filters-body {
  padding: 0 12px 12px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.filter-group {
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.filter-label {
  font-size: 0.53rem;
  font-weight: 600;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.filter-select {
  width: 100%;
  padding: 8px 10px;
  /* Some browsers ignore `height` on <select> but respect min-height. */
  min-height: var(--tap-target);
  border: 1px solid var(--border-color);
  border-radius: var(--radius);
  background: var(--surface-card);
  font-family: var(--font-ui);
  font-size: 0.8rem;
  color: var(--text-main);
  cursor: pointer;
}

.filter-select:hover {
  border-color: var(--primary);
}

/* ── Unit list and syllabus tree ───────────────────────────────────────── */

.nav-section-label {
  font-size: 0.53rem;
  font-weight: 600;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.unit-list,
.tree {
  display: flex;
  flex-direction: column;
  gap: 5px;
  list-style: none;
}

.unit-btn {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  min-height: var(--tap-target);
  padding: 9px 11px;
  border: 1px solid var(--border-color);
  border-radius: var(--radius);
  background: var(--surface-card);
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--text-main);
  text-align: left;
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s;
}

.unit-btn:hover {
  border-color: var(--border-strong);
  background: var(--surface-sunken);
}

.unit-btn.is-active {
  border-color: var(--primary);
  background: var(--primary-glow);
}

.unit-btn-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.unit-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
  background: var(--primary);
}

.count {
  font-family: var(--font-display);
  font-variant-numeric: tabular-nums;
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--text-faint);
  flex-shrink: 0;
}

.tree-group {
  border: 1px solid var(--border-color);
  border-radius: var(--radius);
  overflow: hidden;
}

.tree-head {
  display: flex;
  align-items: stretch;
  background: var(--surface-card);
}

.tree-title {
  flex: 1;
  min-width: 0;
  min-height: var(--tap-target);
  padding: 10px 12px;
  border: none;
  border-left: 3px solid transparent;
  background: none;
  font-size: 0.8rem;
  font-weight: 600;
  line-height: 1.35;
  color: var(--text-main);
  text-align: left;
  cursor: pointer;
}

.tree-title:hover {
  background: var(--surface-sunken);
}

.tree-group.is-selected .tree-title {
  border-left-color: var(--primary);
  background: var(--primary-glow);
  color: var(--primary-text);
}

.tree-toggle {
  width: var(--tap-target);
  min-height: var(--tap-target);
  border: none;
  border-left: 1px solid var(--border-color);
  background: none;
  color: var(--text-muted);
  font-size: 0.62rem;
  cursor: pointer;
  transition: transform 0.2s;
}

.tree-group.is-open .tree-toggle {
  transform: rotate(90deg);
  color: var(--primary-text);
}

.tree-topics {
  display: none;
  flex-direction: column;
  gap: 3px;
  padding: 6px;
  border-top: 1px solid var(--border-color);
  background: var(--surface-sunken);
  list-style: none;
}

.tree-group.is-open .tree-topics {
  display: flex;
}

/* Tight: a topic name is a label to scan down a column, not a paragraph. The
   earlier padding and size pushed a two-word topic into a box the height of a
   card, and twelve of them filled the pane. min-height still clears the tap
   target — the room comes out of the horizontal padding and the type size,
   not out of the touch area. */
.topic-btn {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  min-height: var(--tap-target);
  padding: 6px 8px;
  border: none;
  border-radius: var(--radius);
  background: none;
  font-size: 0.72rem;
  line-height: 1.3;
  color: var(--text-muted);
  text-align: left;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}

.topic-btn:hover {
  background: var(--surface-card);
  color: var(--text-main);
}

.topic-btn.is-active {
  background: var(--primary-text);
  color: #ffffff;
  font-weight: 600;
}

.topic-btn.is-active .count {
  color: rgba(255, 255, 255, 0.75);
}

.topic-name {
  flex: 1;
  min-width: 0;
}

/* ── Question rail ─────────────────────────────────────────────────────── */

.rail {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--tap-target), 1fr));
  gap: 6px;
}

.chip {
  position: relative;
  min-height: var(--tap-target);
  padding: 4px 2px;
  border: 1px solid var(--border-color);
  border-radius: var(--radius);
  background: var(--surface-card);
  font-family: var(--font-display);
  font-variant-numeric: tabular-nums;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--text-main);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: border-color 0.15s, background 0.15s;
}

.chip:hover {
  border-color: var(--primary);
}

/* State is never carried by colour alone — solved and exhausted also print a
   glyph, so the rail is readable without colour vision. */
.chip[data-state="attempted"] {
  border-color: var(--accent-amber-line);
  background: var(--accent-amber-bg);
  color: var(--accent-amber);
}

.chip[data-state="exhausted"] {
  border-color: var(--accent-red-line);
  background: var(--accent-red-bg);
  color: var(--accent-red);
}

/* Recovered — right, but not first time. It wears the amber of an unfinished
   question rather than the green of a solved one, because green is reserved
   for a first-attempt answer and nothing else. It still prints a tick, so it
   is never mistaken for untouched. */
.chip[data-state="recovered"] {
  border-color: var(--accent-amber-line);
  background: var(--accent-amber-bg);
  color: var(--accent-amber);
}

/* Correct — first attempt, no help — is the one state given its own hue, and
   it is the only colour in this stylesheet that does not come from tokens.css
   — flagged deliberately rather than smuggled in. tokens.css rules that a
   correct ANSWER is teal, not green, so that the palette carries one accent.
   This is not an answer: it is a data-state grid, closer to the twelve
   sanctioned unit hues, and teal here collided with the teal that already
   marks the current chip, the active tab and every hover — correct chips
   disappeared into the chrome. Values chosen to clear AA on their own fill in
   both themes. */
.chip[data-state="correct"] {
  border-color: #1F8A5B;
  background: #E7F5EE;
  color: #14653F;
}

html.dark-mode .chip[data-state="correct"],
body.dark-mode .chip[data-state="correct"] {
  border-color: #3E9C74;
  background: rgba(111, 211, 161, 0.16);
  color: #8FE0B8;
}

.chip-mark {
  position: absolute;
  top: 1px;
  right: 3px;
  font-size: 0.6rem;
  line-height: 1;
}

/* The unit hue, on the unit-wise reader only. A dot, not a fill — the fill is
   already spoken for by answer state, and answer state must win. */
.chip-unit {
  position: absolute;
  bottom: 3px;
  left: 3px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
}

.chip.is-current {
  outline: 2px solid var(--primary);
  outline-offset: 1px;
}

.rail-empty,
.nav-empty {
  padding: 24px 8px;
  text-align: center;
  font-size: 0.8rem;
  color: var(--text-muted);
}

/* ── Reader column ─────────────────────────────────────────────────────── */

.reader-main {
  flex: 1;
  min-width: 0;
  height: 100%;
  display: flex;
  flex-direction: column;
  background: var(--surface-page);
}

.reader-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 16px;
  background: var(--surface-card);
  border-bottom: 1px solid var(--border-color);
  flex-shrink: 0;
}

.reader-bar-title {
  min-width: 0;
  flex: 1;
}

.reader-bar-title h1 {
  font-size: 0.92rem;
  font-weight: 600;
  line-height: 1.3;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.reader-bar-title p {
  font-size: 0.72rem;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-top: 2px;
}

.icon-btn {
  min-width: var(--tap-target);
  min-height: var(--tap-target);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border-color);
  border-radius: var(--radius);
  background: var(--surface-card);
  color: var(--text-muted);
  font-size: 0.86rem;
  cursor: pointer;
  flex-shrink: 0;
  transition: border-color 0.15s, color 0.15s;
}

.icon-btn:hover {
  border-color: var(--primary);
  color: var(--primary-text);
}

/* Segmented controls: Study/Quiz and the three languages. */
.segmented {
  display: inline-flex;
  padding: 2px;
  gap: 2px;
  border: 1px solid var(--border-color);
  border-radius: var(--radius);
  background: var(--surface-sunken);
  flex-shrink: 0;
}

.segmented button {
  min-height: 36px;
  padding: 6px 11px;
  border: none;
  border-radius: var(--radius);
  background: none;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--text-muted);
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s, color 0.15s;
}

.segmented button[aria-pressed="true"] {
  background: var(--surface-card);
  color: var(--primary-text);
  box-shadow: var(--shadow-sm);
}

.nav-btn {
  padding: 8px 12px;
  min-height: var(--tap-target);
  border: 1px solid var(--border-color);
  border-radius: var(--radius);
  background: var(--surface-card);
  font-family: var(--font-ui);
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--text-main);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  transition: border-color 0.15s, color 0.15s;
}

.nav-btn:hover:not(:disabled) {
  border-color: var(--primary);
  color: var(--primary-text);
}

.nav-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* ── Display popover ───────────────────────────────────────────────────── */

.popover-host {
  position: relative;
  flex-shrink: 0;
}

/* The Display button carries a caret beside its dots, so it reads as something
   that opens rather than as an ornament. Slightly wider than a square icon
   button to hold both marks without crowding either. */
.display-btn {
  gap: 3px;
  padding: 0 8px;
}

.display-caret {
  /* Big enough to read AS a caret. At 0.58rem the ▾ glyph rendered as a speck
     beside the dots — present, but indistinguishable from a fourth dot, which
     is worse than absent because it looks like a rendering fault. */
  font-size: 0.7rem;
  line-height: 1;
  color: var(--text-muted);
  transition: transform 0.18s;
}

/* Points up while the panel is open — the caret then describes what pressing
   it again does, which is what makes it a control rather than a decoration.
   Same behaviour as .filters[open] .filters-caret. */
.display-btn[aria-expanded="true"] .display-caret {
  transform: rotate(180deg);
}

.display-btn[aria-expanded="true"] {
  border-color: var(--primary);
  color: var(--primary-text);
}

.popover {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 60;
  width: 232px;
  padding: 12px;
  display: none;
  flex-direction: column;
  gap: 12px;
  background: var(--surface-card);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
}

.popover.is-open {
  display: flex;
}

.popover-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.popover-label {
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--text-muted);
}

.popover-danger {
  width: 100%;
  min-height: var(--tap-target);
  padding: 8px 12px;
  border: 1px solid var(--accent-red-line);
  border-radius: var(--radius);
  background: var(--accent-red-bg);
  color: var(--accent-red);
  font-size: 0.76rem;
  font-weight: 600;
  cursor: pointer;
}

/* ── Viewport ──────────────────────────────────────────────────────────── */

.reader-viewport {
  flex: 1;
  overflow-y: auto;
  overscroll-behavior-y: contain;
  padding: 24px 20px 40px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
}

/* Holds the card in tap flow and the whole column in scroll flow. Centring and
   the gap live here rather than on the viewport so the Prev/Next bar below is
   not treated as one more card in the stack. */
#card-slot {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
}

.scroll-sentinel {
  width: 100%;
  height: 1px;
}

/* In scroll flow the cards are a continuous column, so the fade-in that reads
   as "here is your next question" in tap flow becomes fifteen things twitching
   as you scroll. */
.flow-scroll .question-card {
  animation: none;
}

/* The inline Prev/Next sits after the whole column, which in scroll flow means
   after all 288 cards — a control nobody will ever reach. Scrolling is the
   navigation in this flow, and the rail is still one tab away. This is the
   difference from the Tap/Scroll toggle that was cut from v1: that one hid the
   only navigation there was. */
.flow-scroll .card-nav {
  display: none;
}

/* ── Card corrections (defects 1-6 in the design doc) ──────────────────── */

.question-card {
  width: 100%;
  max-width: 760px;
  padding: 22px 24px;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  animation: fadeIn 0.18s ease-out;
}

/* 1. The reading face finally reaches the question. .q-stem asks for
   --font-read, but renderText wraps every stem in .en-text, which sets
   --font-ui — equal specificity, later source order, so Inter won and no
   stem has ever rendered in Newsreader in English or Bilingual mode. The
   descendant selector outranks it. */
.q-stem .en-text {
  font-family: var(--font-read);
}

/* 3. Reading leading, not display leading, on a serif at 1.05em. */
.q-stem {
  line-height: 1.6;
  max-width: var(--measure);
}

/* 2. Tamil is no longer dimmed. Many students read it first; the pair is
   distinguished by a rule, not by making one half weaker. The 1.85 leading
   is untouched — that value is not compressed to save vertical space. */
.q-tamil-text {
  color: var(--text-main);
}

.en-text + .q-tamil-text {
  border-left: 2px solid var(--primary);
  padding-left: 12px;
  margin-top: 10px;
}

/* Not inside a match table. A rule per prose block reads as "the same thing in
   the other language"; forty of them, one per cell, reads as a grid of stripes
   and the table stops being a table. The cells get the full ink and the
   spacing, and nothing else. */
.match-table .en-text + .q-tamil-text {
  border-left: none;
  padding-left: 0;
  margin-top: 4px;
}

/* Quieter inside an option. Four teal bars stacked in the answer block compete
   with the one on the stem, and the stem's is the one that means "this is the
   same question in the other language". The option still gets a rule, in the
   neutral line colour, so the pair stays visibly a pair. */
.option-card .en-text + .q-tamil-text {
  border-left-color: var(--border-color);
  padding-left: 10px;
  margin-top: 6px;
}

.card-header {
  display: flex;
  align-items: center;
  gap: 9px;
  margin-bottom: 14px;
}

/* 4. Only the counter and the unit dot lead. Source, unit, theme, topic and
   type moved to the strip below the options — reference, not preamble. */
.card-header .q-progress {
  font-family: var(--font-display);
  font-variant-numeric: tabular-nums;
  font-size: 0.95rem;
  font-weight: 600;
}

.q-progress-of {
  color: var(--text-faint);
  font-weight: 400;
}

.q-number {
  margin-left: auto;
  font-family: var(--font-display);
  font-variant-numeric: tabular-nums;
  font-size: 0.72rem;
  color: var(--text-faint);
}

/* 5. A visible seam between reading and answering. */
.answer-block {
  margin-top: 22px;
  padding-top: 18px;
  border-top: 1px solid var(--border-color);
}

/* 6. --text-faint is 2.74:1 and decorative; this is the letter a student
   presses in quiz mode. */
.option-card .option-label {
  color: var(--text-muted);
  font-weight: 600;
}

/* Two rows: the year, then the exam it came from. The unit, theme and topic
   chips that used to sit under here are gone — they are the tagging, and the
   filters own that. What is left is the pair a student checks after answering,
   and paper names run to "COMBINED TECHNICAL SERVICES EXAMINATION IN
   NON-INTERVIEW POSTS", which shares a line with nothing. */
.q-meta-strip {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--line-soft);
}

.meta-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}

.meta-source {
  font-size: 0.72rem;
  color: var(--text-muted);
  min-width: 0;
}

/* The year is set exactly as the unit chips were: sunken block, accent rule
   down the left edge. Same weight of thing, same treatment — a reader who
   learned that shape on the old strip does not have to learn a new one. */
.meta-tag {
  font-size: 0.6rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 3px 8px;
  border-radius: var(--radius);
}

.meta-tag-year {
  border-left: 3px solid var(--primary-text);
  background: var(--surface-sunken);
  color: var(--text-main);
  font-variant-numeric: tabular-nums;
}

/* Prev/Next under the question, where reading ends — not in the toolbar,
   where you have to travel back up to reach them. */
.card-nav {
  width: 100%;
  max-width: 760px;
  display: flex;
  gap: 12px;
}

.card-nav .nav-btn {
  flex: 1;
  padding: 12px 16px;
  font-size: 0.85rem;
}

.card-nav[hidden] {
  display: none;
}

.quiz-prompt {
  font-size: 0.78rem;
  color: var(--text-muted);
}

.correct-ans {
  font-size: 0.82rem;
  color: var(--text-muted);
}

.correct-ans span {
  font-family: var(--font-display);
  font-weight: 600;
  background: var(--primary-glow);
  color: var(--primary-text);
  padding: 2px 8px;
  border-radius: var(--radius);
  margin-left: 4px;
}

/* A question TNPSC published and then dropped. Amber, not red: nothing has
   gone wrong here and the student did nothing — the Commission released no
   key for it. Study mode is the only place this can appear; quiz mode does
   not offer the question at all. */
.dropped-ans {
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--accent-amber);
  background: var(--accent-amber-bg);
  border-radius: var(--radius);
  padding: 7px 11px;
}

/* ── States ────────────────────────────────────────────────────────────── */

.empty-state,
.error-card {
  width: 100%;
  max-width: 520px;
  margin-top: 40px;
  padding: 32px 24px;
  text-align: center;
  color: var(--text-muted);
  background: var(--surface-card);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-lg);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
}

.empty-icon {
  font-size: 2.6rem;
}

/* ── Mobile bottom bar ─────────────────────────────────────────────────── */

.bottom-bar {
  display: none;
  align-items: center;
  gap: 8px;
  padding: 8px 10px calc(8px + env(safe-area-inset-bottom));
  background: var(--surface-card);
  border-top: 1px solid var(--border-color);
  flex-shrink: 0;
  z-index: 70;
}

.bottom-bar .nav-btn {
  flex: 0 0 auto;
  min-width: 56px;
}

.sheet-trigger {
  flex: 1;
  min-height: var(--tap-target);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border: 1px solid var(--border-color);
  border-radius: var(--radius);
  background: var(--surface-sunken);
  font-family: var(--font-display);
  font-variant-numeric: tabular-nums;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text-main);
  cursor: pointer;
}

/* ── Backdrop, shared by the tablet overlay and the phone sheet ────────── */

.backdrop {
  display: none;
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  z-index: 80;
}

.backdrop.is-open {
  display: block;
}

.sheet-handle {
  display: none;
}

/* ── Tablet: navigator overlays instead of taking a column ─────────────── */

@media (max-width: 1023px) and (min-width: 769px) {
  .navigator {
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    z-index: 90;
    box-shadow: var(--shadow-lg);
    transform: translateX(0);
    transition: transform 0.24s cubic-bezier(0.4, 0, 0.2, 1);
  }

  .navigator.is-closed {
    transform: translateX(-100%);
    pointer-events: none;
  }
}

@media (min-width: 1024px) {
  /* The navigator is a permanent column here; nothing may hide it. */
  .navigator.is-closed {
    transform: none;
    pointer-events: auto;
  }

  .nav-close,
  .navigator-toggle {
    display: none;
  }
}

/* ── Phone: reader is fullscreen, navigator is a bottom sheet ──────────── */

@media (max-width: 768px) {
  .navigator {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    top: auto;
    z-index: 90;
    width: 100%;
    height: 78dvh;
    max-height: 78dvh;
    border-right: none;
    border-top-left-radius: var(--radius-lg);
    border-top-right-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    transform: translateY(100%);
    transition: transform 0.26s cubic-bezier(0.4, 0, 0.2, 1);
  }

  .navigator.is-open {
    transform: translateY(0);
  }

  .navigator.is-closed {
    transform: translateY(100%);
    pointer-events: none;
  }

  .sheet-handle {
    display: block;
    width: 38px;
    height: 4px;
    margin: 8px auto 2px;
    border-radius: 2px;
    background: var(--border-strong);
    flex-shrink: 0;
  }

  .navigator-head {
    padding-top: 6px;
  }

  /* The brand block is the reader bar's job on a phone; the sheet only needs
     its tabs. */
  .navigator-brand {
    display: none;
  }

  .bottom-bar {
    display: flex;
  }

  .reader-viewport {
    padding: 14px 12px 24px;
  }

  .question-card {
    padding: 16px 15px;
    border-radius: var(--radius);
  }

  .reader-bar {
    flex-wrap: wrap;
    gap: 8px;
    padding: 9px 12px;
  }

  /* Toggle and title share the first row; the control strip takes the second
     on its own. Giving the title `width: 100%` instead pushed the toggle onto
     a row of its own, spending a whole line of a 390px screen on one button. */
  #nav-toggle {
    order: -2;
  }

  /* Same order as the toggle, not a lower one: equal order falls back to DOM
     order, which already has the toggle first. Left at the default 0 the Home
     link sorted after the title (-1) and took a third row of its own.

     It was hidden here for a while, when the row also had to seat the collapse
     chevron: three 44px targets left the unit name 180px and ran it to three
     lines. The chevron is a full-width strip below the bar now, so the seat it
     held is free and Home has it back — the name keeps the 232px it gained. */
  .reader-home {
    order: -2;
  }

  /* Against the top of the row rather than its middle. The buttons are 44px and
     the title beside them is two or three lines tall, so centred they drifted
     down beside the second line while the name started at the first. */
  #nav-toggle,
  .reader-home {
    align-self: flex-start;
  }

  /* Basis 0, not auto. Flex breaks a line on each item's HYPOTHETICAL size —
     max-content for `auto` — and only shrinks what survives on the line, so a
     long unit label ("Unit 5B: Development Administration") claimed the whole
     row and drove the control strip down to a third row: 167px of a 780px
     screen, on a bar that does not scroll away. With basis 0 the title only
     ever asks for the space left after its row-mates and wraps inside it. */
  .reader-bar-title {
    order: -1;
    flex: 1 1 0;
    min-width: 0;
  }

  .reader-bar-title h1,
  .reader-bar-title p {
    white-space: normal;
  }

  /* A ceiling, not a working limit: at every unit label the bank actually
     carries the name fits in two lines even at 150% text, and this only ever
     catches something longer — a topic scope, or a text scale past that — so
     the bar cannot grow without bound above the question. */
  .reader-bar-title h1 {
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }

  /* Prev/Next are already in the fixed bottom bar on a phone, permanently in
     thumb reach. The inline pair under the card would be a second copy of the
     same two buttons sitting directly above them. */
  .card-nav {
    display: none;
  }

  /* The strip is the containing block for the overflow hint below. */
  .reader-bar {
    position: relative;
  }

  /* The cut-off edge of the control strip: a fade and a chevron over the last
     few pixels, shown only while there is genuinely something past the right
     edge. reader-shell.js's syncControlsOverflow measures the row and sets the
     two classes; the same treatment, and the same reasoning, as the landing
     page's section nav in site.css.
     
     On .reader-bar rather than on .reader-controls itself: an ::after inside an
     overflow container scrolls away with the content, so it would slide off
     exactly when it is meant to be pointing. */
  .reader-bar::after {
    content: '›';
    position: absolute;
    right: 0;
    bottom: 9px;
    width: 40px;
    height: var(--tap-target);
    display: none;
    align-items: center;
    justify-content: flex-end;
    padding-right: 5px;
    font-size: 1.15rem;
    line-height: 1;
    color: var(--primary-text);
    background: linear-gradient(to left, var(--surface-card) 48%, transparent);
    /* The controls underneath must stay tappable through the hint. */
    pointer-events: none;
    transition: opacity 0.2s;
  }

  .reader-bar.controls-overflow::after { display: flex; }
  .reader-bar.controls-overflow.controls-at-end::after { opacity: 0; }

  /* The control strip scrolls sideways rather than wrapping onto three rows. */
  .reader-controls {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    gap: 7px;
    width: 100%;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }

  .reader-controls::-webkit-scrollbar {
    display: none;
  }

  /* Scrolled fully right, the last control would otherwise sit under the fade
     that exists to say it was hidden. */
  .reader-bar.controls-overflow .reader-controls {
    padding-right: 34px;
  }

  /* The popover becomes a bottom sheet, and it MUST be position: fixed.
     .reader-controls above is an overflow container, and an overflow container
     clips its absolutely positioned descendants — so the absolute popover was
     being cropped to a 44px-tall scroll strip. It still opened, still measured
     232x288, and was completely untappable: elementFromPoint at its own centre
     returned the card behind it. Fixed positioning is the only way out of a
     clipping ancestor short of moving the element in the DOM.

     A sheet is also the right shape here — it matches the navigator, and it
     puts these controls in thumb reach instead of up in the corner. */
  .popover {
    position: fixed;
    top: auto;
    right: 0;
    left: 0;
    bottom: 0;
    z-index: 95;
    width: 100%;
    padding: 16px 16px calc(16px + env(safe-area-inset-bottom));
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    gap: 16px;
  }

  /* Full-width rows: a sheet has the width to give each control real room,
     and the segmented pills should be thumb-sized here, not toolbar-sized. */
  .popover-row {
    justify-content: space-between;
  }

  .popover .segmented button {
    min-height: var(--tap-target);
    padding: 8px 16px;
  }

  .popover-danger {
    margin-top: 4px;
  }
}

@media (min-width: 769px) {
  .reader-controls {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-left: auto;
    flex-wrap: wrap;
    justify-content: flex-end;
  }

  .sheet-trigger,
  .bottom-bar {
    display: none;
  }
}

/* Collapse the toolbar, so the question gets the screen. Kept from v1 along
   with its localStorage key; what changed is how it is reached — a strip
   across the top of the question rather than an item three taps deep in the
   Display popover, or the pair of cornered chevrons that replaced that. */
.header-hidden .reader-bar {
  display: none;
}

/* The one control, in both directions. A flex item in .reader-main's column,
   so it takes its own band of the layout and the viewport below starts under
   it — it cannot cover the question the way the floating chevron it replaces
   did, at any scroll position. It also never leaves: with the bar hidden this
   strip is the only way back, so it must not scroll away with the card.

   28px is under the 44px tap floor the icon buttons keep, deliberately: the
   target is the full width of the column, which is a far easier thing to hit
   than a 44px square in a corner, and the height is what the reader is buying
   back. The sheet handle in the navigator is the same shape for the same
   reason. */
.header-toggle {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  min-height: 28px;
  padding: 0;
  border: none;
  border-bottom: 1px solid var(--border-color);
  /* Sunken rather than card white: the strip divides the toolbar from the
     question and should read as the seam between them, not as a third band of
     chrome stacked on the two. */
  background: var(--surface-sunken);
  color: var(--text-muted);
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}

.header-toggle:hover {
  background: var(--surface-card);
  color: var(--primary-text);
}

/* Two short rules flanking the chevron, borrowed from the navigator's sheet
   handle: they say "the whole strip is the control" where a lone chevron in a
   390px band reads as a small button someone forgot to centre properly. The
   span is the left one and ::after the right — a pseudo-element for the second
   because there is nothing for it to mark up. */
.header-toggle-grip,
.header-toggle::after {
  content: '';
  width: 34px;
  height: 3px;
  border-radius: 2px;
  background: var(--border-color);
}

.header-toggle:hover .header-toggle-grip,
.header-toggle:hover::after {
  background: var(--primary);
}

/* Up while the bar is there and down once it has gone, so the arrow always
   points the way pressing it moves things. */
.header-hidden .header-toggle svg {
  transform: rotate(180deg);
}

/* ── Unit hues ─────────────────────────────────────────────────────────── */

.color-1 { background-color: var(--unit-1); }
.color-2 { background-color: var(--unit-2); }
.color-3A { background-color: var(--unit-3A); }
.color-3B { background-color: var(--unit-3B); }
.color-4 { background-color: var(--unit-4); }
.color-5A { background-color: var(--unit-5A); }
.color-5B { background-color: var(--unit-5B); }
.color-6A { background-color: var(--unit-6A); }
.color-6B { background-color: var(--unit-6B); }
.color-6C { background-color: var(--unit-6C); }
.color-7 { background-color: var(--unit-7); }
.color-8 { background-color: var(--unit-8); }

@keyframes fadeIn {
  from { opacity: 0; transform: translateY(5px); }
  to { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

/* ── The one thing tokens cannot express ───────────────────────────────── */
/* --primary-text lightens in dark mode, so white ink on a teal fill drops to
   1.7:1 and the fill has to take dark ink instead. app.css makes the same flip
   for .cta-btn. Every teal-filled element on this page is listed here. */
html.dark-mode .topic-btn.is-active,
body.dark-mode .topic-btn.is-active,
html.dark-mode .filters-count,
body.dark-mode .filters-count {
  color: #0B1A1C;
}

html.dark-mode .topic-btn.is-active .count,
body.dark-mode .topic-btn.is-active .count {
  color: rgba(11, 26, 28, 0.7);
}

/* ── Additions: home link, rail reset, toolbar prev/next, multi-select ──── */

/* The navigator head now carries two links, not one: Home and the one-step
   Back. A reader who arrived from a search result or a shared link had no
   route to the site at all before this. */
.nav-links {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}

.nav-links .back-link {
  margin-bottom: 4px;
}

/* Reset, above the chips it clears. It used to exist only inside the Display
   popover, which is the last place a student looks for it — the pane that
   shows what has been attempted is the pane that should offer to clear it. */
.rail-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 6px;
}

.rail-head-count {
  font-size: 0.6rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.rail-reset {
  min-height: 34px;
  padding: 5px 12px;
  font-size: 0.72rem;
  /* Full width on the line below the filters. The filter block now claims the
     whole row, so Reset always wraps — and a 60px button parked at the left
     end of an empty line reads as a stray rather than as the deliberate last
     step of the block above it. */
  flex: 1 1 100%;
}

/* Disabled rather than hidden: a control that comes and goes is one a student
   stops looking for. */
.rail-reset:disabled {
  opacity: 0.45;
  cursor: default;
}

/* All / Correct / Incorrect, then Reset — the order they are read in and the
   order of consequence, with the destructive one last. They wrap onto two
   lines in a narrow navigator rather than squeezing the counts out of the
   buttons: the number is half of what each button says. */
.rail-tools {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 10px;
}

/* Five filters cannot share one line in a 300px navigator — "Unattempted"
   alone is wider than a fifth of it — so the row wraps instead of shrinking
   labels into ellipses. A filter whose name you cannot read is not a filter.

   All takes a line of its own at the top: it is the way back from every other
   state, it is the one pressed most often, and giving it the full width means
   the four buckets below it divide the space evenly rather than one of them
   being squeezed by a neighbour. */
.rail-filter {
  flex: 1 1 100%;
  min-width: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 3px;
}

.rail-filter button {
  /* Two per line by default; four when the column is wide enough for them.
     Never one truncated word on a line of its own. */
  flex: 1 1 calc(50% - 3px);
  min-width: 0;
  padding: 6px 7px;
  font-size: 0.7rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  white-space: nowrap;
}

/* All takes the whole first line on its own — except when the selection holds
   a question TNPSC dropped, and Dropped appears beside it. The two share that
   line as a pair: they are both statements about the set rather than about the
   student's work, which is what the four buckets below them are. */
.rail-filter .rail-filter-all {
  flex: 1 1 100%;
}
.rail-filter.has-dropped .rail-filter-all {
  flex: 1 1 calc(50% - 3px);
}

/* Amber, matching the notice on the card itself. Not red: nothing has gone
   wrong and the student did nothing — the Commission released no key. */
.rail-filter button[aria-pressed="true"].rail-filter-dropped .rail-filter-count {
  background: var(--accent-amber-bg);
  color: var(--accent-amber);
}

/* Deliberately no wider breakpoint putting all four on one line. The navigator
   is a fixed 300px column on every desktop, so viewport width buys this row
   nothing — keying off it squeezed each button to 60px and clipped both
   "Incorrect" and "Unattempted" on the widest screens, which is the one thing
   this layout exists to prevent. Two per line, every word in full, at every
   width. */

.rail-filter button:disabled {
  opacity: 0.4;
  cursor: default;
}

.rail-filter-count {
  font-variant-numeric: tabular-nums;
  font-size: 0.62rem;
  font-weight: 700;
  padding: 1px 5px;
  border-radius: 999px;
  background: var(--surface-sunken);
  color: var(--text-muted);
}

.rail-filter button[aria-pressed="true"] .rail-filter-count {
  background: var(--accent-teal-bg, var(--surface-sunken));
  color: var(--primary-text);
}

/* The reset confirmation. In the flow of the pane rather than floating over
   it: the navigator is a bottom sheet on a phone, and one more absolutely
   positioned layer inside a sheet is a layer that ends up half off-screen. */
.confirm-pop {
  margin: -2px 0 12px;
  padding: 12px;
  border: 1px solid var(--accent-red-line);
  border-radius: var(--radius-lg);
  background: var(--accent-red-bg);
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.confirm-pop[hidden] {
  display: none;
}

.confirm-pop-text {
  margin: 0;
  font-size: 0.76rem;
  line-height: 1.45;
  color: var(--text-main);
}

.confirm-pop-actions {
  display: flex;
  gap: 8px;
}

.confirm-pop-actions > * {
  flex: 1 1 0;
}

/* Cancel is the plain .nav-btn beside it; only the destructive half is
   coloured, so the safe choice is the calm one. */
.confirm-pop-go {
  min-height: var(--tap-target);
  padding: 8px 12px;
  border: 1px solid var(--accent-red-line);
  border-radius: var(--radius);
  background: var(--accent-red);
  color: var(--surface-card);
  font-family: var(--font-ui);
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
}

/* Prev/Next in the toolbar beside the Display button. Off on a phone, where
   the fixed bottom bar already carries the pair in thumb reach; on everywhere
   above it.
   
   Tablet used to be the hole between those two. The bottom bar switches off at
   769px and this used to switch on at 1024px, so between them a reader had
   exactly one Prev/Next — the pair under the card, inside the scrolling
   viewport. On an iPad Air in portrait that put Next at y=1298 on an 1180px
   screen: below the fold, reachable only by scrolling, and at a different
   place for every question because a match-table question is twice the height
   of a plain one. Navigation you have to go looking for is navigation that
   stops being used.
   
   The toolbar is the right home for it because the toolbar is OUTSIDE
   .reader-viewport. A row added above the question would have scrolled off the
   top as you read, trading "scroll down to find Next" for "scroll up to find
   it"; this pair cannot move, because it is not in the scroller. */
.bar-nav {
  display: none;
}

@media (min-width: 769px) {
  .bar-nav {
    display: inline-flex;
  }

  .bar-nav button {
    min-width: 40px;
    font-size: 0.95rem;
  }

  .bar-nav button:disabled {
    opacity: 0.4;
    cursor: default;
  }
}

@media (min-width: 1024px) {
  /* The pair under the card would now be a second copy of the two buttons in
     the bar — the same reasoning the phone rules already apply to it.
     
     Desktop only, deliberately: a tablet in portrait scrolls far more of the
     question than a desktop does, so down there the second pair is not a
     duplicate but the one that sits where reading actually ends. It is also
     what is left if the toolbar is collapsed. */
  .card-nav {
    display: none;
  }
}

/* The multi-select inside a navigator filter group. The panel styling itself
   is app.css's; these are the parts that have to match the <select> it sits
   beside in the same column. */
.filter-group .multiselect-summary {
  width: 100%;
  box-sizing: border-box;
  justify-content: space-between;
  min-height: var(--tap-target);
  padding: 8px 10px;
  font-size: 0.8rem;
}

/* The navigator column is narrow and already scrolls, so the menu opens in
   flow rather than floating over the tree behind it. */
.filter-group .multiselect-menu {
  position: static;
  min-width: 0;
  margin-top: 6px;
  max-height: 260px;
  box-shadow: none;
}

.filter-group .multiselect-option {
  font-size: 0.8rem;
}

/* Home, in the toolbar. .icon-btn is written for <button>, so the <a> needs
   the two properties a link brings with it and a button does not. */
.reader-home { text-decoration: none; }
.reader-home:hover { border-color: var(--primary); color: var(--primary-text); }

/* ── Busy ──────────────────────────────────────────────────────────────── */
/* A unit selection fetches a slice of the question bank, which on a phone can
   take seconds. Without this the reader sat there holding the PREVIOUS unit's
   questions, indistinguishable from a tap that never registered — so students
   tapped again, or gave up and switched away mid-load.

   The dim is on the two panes that are about to be replaced, not on the whole
   shell: the toolbar, the theme toggle and the font controls keep working and
   keep looking like they work. reader-shell.js only adds .is-busy after 150ms,
   so a cached unit never flashes this. */
.reader.is-busy { cursor: progress; }

.reader.is-busy .reader-viewport,
.reader.is-busy .navigator-body {
  opacity: 0.45;
  transition: opacity 120ms ease-in;
}

/* The shell already drops clicks while a load is in flight; this stops the
   navigator from *inviting* one. Hover states firing on a tree that is being
   replaced read as a live control. */
.reader.is-busy .navigator-body { pointer-events: none; }

/* Share, in the Display popover above the reset control. Full width like the
   danger button under it, so the two read as a stacked pair rather than as a
   stray button beside the settings rows. */
.popover-share {
  width: 100%;
}

/* ── Bookmarks ─────────────────────────────────────────────────────────── */

/* The ribbon, in the card header immediately before the question number.
   Drawn only for subscribers — render2.js omits it entirely otherwise, rather
   than showing a control that opens a paywall, because a control that does not
   work is bait in the middle of somebody's reading.
   
   Wrapped with the number in .card-header-end — see the note in render2.js —
   so the two read as one group at the right-hand end: this question, and what
   you have done about it. */
.card-header-end {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  flex: none;
}

.bookmark-btn {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap-target);
  height: var(--tap-target);
  padding: 0;
  border: none;
  border-radius: var(--radius);
  background: none;
  color: var(--text-faint);
  cursor: pointer;
  transition: color 0.15s, background 0.15s, transform 0.12s;
}

/* The tint is a hover affordance, not a state. `:hover` alone keeps it painted
   after a click on a mouse — the pointer is still over the button — leaving the
   ribbon in a permanent blue box that reads as a second, louder "on" state
   competing with the fill. `@media (hover: hover)` also keeps it off touch
   entirely, where a sticky hover has nothing to mean. */
@media (hover: hover) and (pointer: fine) {
  .bookmark-btn:hover {
    background: var(--accent-blue-bg);
  }
}

.bookmark-btn:hover {
  color: var(--accent-blue);
}

/* Blue, not teal. Teal is the answer colour on this card — it marks the
   correct option — and a bookmarked question has nothing to do with being
   right. See the note on --accent-blue in tokens.css for why this particular
   blue and not a closer one. The filled/hollow difference carries the state
   without relying on hue at all. */
.bookmark-btn.is-on {
  color: var(--accent-blue);
}

.bookmark-btn:active {
  transform: scale(0.9);
}

/* The cap refused it. A star that simply fails to light reads as a broken
   button, so it says no out loud for a moment. */
.bookmark-btn.is-refused {
  color: var(--accent-red);
  animation: shake 0.3s;
}

/* ── Reporting a question ──────────────────────────────────────────────── */

/* The pennant, left of the ribbon in .card-header-end. Same box and same
   transitions as .bookmark-btn so the two read as one pair rather than as a
   control with a stray sibling — only the resting weight differs, below.

   Drawn only when QUESTION_FLAGS is on and the reader is signed in; render2.js
   omits it entirely otherwise. This whole block goes with the feature when it
   is switched off, and nothing else depends on it. */
.flag-btn {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap-target);
  height: var(--tap-target);
  padding: 0;
  border: none;
  border-radius: var(--radius);
  background: none;
  /* Fainter at rest than the ribbon beside it, and deliberately. Bookmarking is
     part of studying; reporting is an exception, and a control for an exception
     that sits at the same weight as one used every minute keeps drawing the eye
     mid-question for no reason. */
  color: var(--text-faint);
  opacity: 0.55;
  cursor: pointer;
  transition: color 0.15s, background 0.15s, opacity 0.15s, transform 0.12s;
}

/* Same reasoning as .bookmark-btn's: a tint that survives the click reads as a
   second "on" state, and a sticky hover means nothing on touch. */
@media (hover: hover) and (pointer: fine) {
  .flag-btn:hover {
    background: var(--accent-amber-bg);
  }
}

.flag-btn:hover,
.flag-btn:focus-visible {
  color: var(--accent-amber);
  opacity: 1;
}

/* Amber and not red. Red on this card is the wrong-answer colour, and a
   reported question is not a wrong one — the mark says "somebody is looking at
   this", which is caution rather than failure. Full opacity as well as hue, so
   the state survives a screenshot in greyscale. */
.flag-btn.is-on {
  color: var(--accent-amber);
  opacity: 1;
}

.flag-btn:active {
  transform: scale(0.9);
}

/* The server refused it, or could not be reached, and the mark has just been
   put back. Says so for a moment: a flag that lights and then quietly goes out
   again is indistinguishable from a misfire. */
.flag-btn.is-refused {
  color: var(--accent-red);
  opacity: 1;
  animation: shake 0.3s;
}

/* Bottom-right on the chip. Three corners, three facts: .chip-mark takes
   top-right (correct/exhausted), .chip-unit bottom-left (the unit hue), this
   bottom-right. None of them overlap at any chip size.
   
   A ribbon glyph rather than the star's ★: at 10px the star's five points blur
   into a dot, and a dot in the corner of a chip already means something else
   two corners over. */
.chip-saved {
  position: absolute;
  bottom: 2px;
  right: 3px;
  line-height: 0;
  color: var(--accent-blue);
}

.chip-saved svg {
  width: 9px;
  height: 9px;
  display: block;
}

/* ── Bookmarks page ────────────────────────────────────────────────────── */

/* The sync line under the rail head: how many are saved overall, when they
   last reached the server, and a way to force it. Everything here is about
   the student never having to wonder whether their bookmarks are safe. */
.sync-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 10px;
  padding: 8px 10px;
  border: 1px solid var(--border-color);
  border-radius: var(--radius);
  background: var(--surface-sunken);
}

.sync-state {
  font-size: 0.68rem;
  line-height: 1.4;
  color: var(--text-muted);
}

.sync-state strong {
  color: var(--text-main);
  font-variant-numeric: tabular-nums;
}

/* Only ever shown when something is genuinely unsent. */
.sync-pending {
  color: var(--accent-amber);
  font-weight: 600;
}

.sync-btn {
  min-height: 32px;
  padding: 4px 10px;
  font-size: 0.7rem;
  flex: none;
}

.sync-btn:disabled {
  opacity: 0.45;
  cursor: default;
}

/* ── Sync state ────────────────────────────────────────────────────────── */

/* Where a student's saved questions have got to, in the Display popover. The
   bookmarks page has always carried this line; the readers are where the
   saving actually happens, and having no answer here meant somebody who had
   just saved five questions had to leave the page they were reading to find
   out whether those five were safe.

   Separated from the settings above it by a rule, because it reports a state
   rather than offering a setting. */
.popover-sync {
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding-top: 10px;
  border-top: 1px solid var(--border-color);
}

.popover-sync[hidden] {
  display: none;
}

.popover-sync-state {
  font-size: 0.7rem;
  line-height: 1.4;
  color: var(--text-muted);
}

/* Amber only while something is genuinely unsent. The resting state is
   deliberately quiet — it is reassurance, not a warning. */
.popover-sync-state.is-pending {
  color: var(--accent-amber);
  font-weight: 600;
}

.sync-btn {
  width: 100%;
  min-height: 34px;
  padding: 5px 10px;
  font-size: 0.72rem;
}

/* Visible but greyed when there is nothing to send, rather than hidden. It
   doubles as reassurance — somebody who wants to know their work is safe can
   look and see so — and a control that comes and goes is one people stop
   looking for. */
.sync-btn:disabled {
  opacity: 0.45;
  cursor: default;
}

/* The same fact, in the rail, so it is legible without opening a panel. Quiet
   by default: it sits above a grid of chips and must not compete with them. */
.rail-sync {
  font-size: 0.62rem;
  line-height: 1.4;
  color: var(--text-faint);
  margin: -4px 0 8px;
}

.rail-sync:empty {
  display: none;
}

.rail-sync.is-pending {
  color: var(--accent-amber);
  font-weight: 600;
}


/* ── Reattempting the ones you got wrong ───────────────────────────────────
   Same shape as .confirm-pop above, in the accent colour rather than the red
   one: a reset destroys answers and this does not, so it should not borrow the
   warning that means "this cannot be undone". */

.retry-pop {
  margin: -2px 0 12px;
  padding: 12px;
  border: 1px solid var(--primary-tint);
  border-radius: var(--radius-lg);
  background: var(--primary-glow);
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.retry-pop-text {
  margin: 0;
  font-size: 0.76rem;
  line-height: 1.45;
  color: var(--text-main);
}

.retry-pop-actions {
  display: flex;
  gap: 8px;
}

.retry-pop-actions > * {
  flex: 1 1 0;
}

/* The emphasised half of the pair. It is Review rather than Reattempt on
   purpose: opening the questions you got wrong to look through them is the
   ordinary thing to do, and answering them all again is the deliberate one, so
   the fill belongs on the first. */
.retry-pop-primary {
  min-height: var(--tap-target);
  padding: 8px 12px;
  border: 1px solid var(--primary);
  border-radius: var(--radius);
  background: var(--primary);
  color: var(--surface-card);
  font-family: var(--font-ui);
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
}

.retry-pop-primary:hover {
  background: var(--primary-hover);
  border-color: var(--primary-hover);
}

/* While a round is running. Quieter than the prompt — it is a state, not a
   question, and the way out of it should not compete with the questions. */
.retry-pop-live {
  border-style: dashed;
  background: transparent;
}


/* ── The key under the question grid ───────────────────────────────────────
   The swatches are real .chip elements, so every colour here comes from the
   rules above rather than from a copy of them. All this does is shrink them
   and stop them behaving like buttons. */

.rail-key {
  margin: 16px 0 4px;
  padding-top: 12px;
  border-top: 1px solid var(--line-soft);
}

.rail-key-title {
  margin: 0 0 8px;
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.rail-key-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.rail-key-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.74rem;
  line-height: 1.3;
  color: var(--text-muted);
}

/* A swatch, not a control: no tap target, no pointer, no hover. */
.chip.rail-key-chip {
  flex: 0 0 auto;
  width: 22px;
  min-height: 0;
  height: 22px;
  padding: 0;
  cursor: default;
}

.chip.rail-key-chip:hover {
  border-color: inherit;
}

/* The mark rides at chip scale, which is too big on a 22px swatch. */
.rail-key-chip .chip-mark {
  position: static;
  font-size: 0.62rem;
}

.rail-key-chip .chip-saved {
  position: static;
}

/* A stand-in hue for the legend row only — on a real chip this dot carries
   whichever of the twelve unit colours applies. */
.rail-key-dot {
  position: static;
  background: var(--primary);
}


/* Who you are, and the way out, in the Display panel. Same shape as
   .popover-sync above it — the two are a pair, and both answer questions about
   the account rather than about the page. */
.popover-account {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--line-soft);
}

.popover-account[hidden] {
  display: none;
}

/* The address can be long and the button must not be squeezed by it. */
.popover-account-who {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.72rem;
  color: var(--text-muted);
}

.popover-account .nav-btn {
  flex-shrink: 0;
}


/* ── Progress bars in the navigator tree ───────────────────────────────────
   The same three-segment bar site.css defines for the hub, the syllabus map
   and the progress page, repeated here because reader.css loads INSTEAD of
   site.css (see the header of this file) and a shared primitive that only half
   the pages can see is not shared.

   Inline rather than stacked. The tree already fits twelve themes and their
   topics into a phone-width pane, and a bar on its own line under each name
   would halve how much of the unit a student can see at once. */
.prog-bar {
  display: flex;
  flex: 0 0 40px;
  width: 40px;
  height: 5px;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--surface-sunken);
  box-shadow: inset 0 0 0 1px var(--line-soft);
}

.prog-seg { display: block; height: 100%; }
.seg-correct { background: var(--primary); }
.seg-incorrect { background: var(--accent-amber); }
.seg-left { background: transparent; }

/* The theme row is a single button holding a wrapping label, so the bar has to
   opt out of the wrap rather than sit in the middle of the words. */
.tree-title .prog-bar {
  display: inline-flex;
  vertical-align: middle;
  margin: 0 6px 1px 0;
}

/* On the selected topic the row goes solid teal with white text, and both the
   track and the "left to do" segment would vanish into it. Lifted to a white
   wash instead — the same treatment .topic-btn.is-active .count already gets. */
.topic-btn.is-active .prog-bar {
  background: rgba(255, 255, 255, 0.28);
  box-shadow: none;
}

.topic-btn.is-active .seg-correct { background: #ffffff; }
.topic-btn.is-active .seg-incorrect { background: rgba(255, 255, 255, 0.62); }

/* And the same flip the block above makes for the ink: in dark mode the teal
   fill is light and carries dark ink, so a white bar on it would be the one
   thing on the row still shouting. */
html.dark-mode .topic-btn.is-active .prog-bar,
body.dark-mode .topic-btn.is-active .prog-bar {
  background: rgba(11, 26, 28, 0.22);
}

html.dark-mode .topic-btn.is-active .seg-correct,
body.dark-mode .topic-btn.is-active .seg-correct {
  background: #0B1A1C;
}

html.dark-mode .topic-btn.is-active .seg-incorrect,
body.dark-mode .topic-btn.is-active .seg-incorrect {
  background: rgba(11, 26, 28, 0.6);
}
