:root {
  --bg: #0b1220;
  --card: #141c2e;
  --card-hover: #1b2540;
  --border: #23304d;
  --text: #eef1f8;
  --text-dim: #9aa5bd;
  /* #4c8dff (the old value) only hits 3.2:1 contrast with white text -
     darkened per the accessibility audit so every solid-fill + white-text
     use (reveal button, active language toggle) clears WCAG 1.4.3 (4.5:1). */
  --accent: #3968d8;
  --accent-dim: #2c4a86;
  --amber: #e2a63d;
  --amber-bg: #3a2c10;
  --green: #3fbf76;
  --correct-bg: #103a26;
  --red: #e2593d;
  /* Paired with --red the same way --correct-bg is paired with --green -
     used by the spaced-repetition "I didn't know it" self-assessment button
     (DN-16) so its fill+text combo has the same contrast-checked shape as
     the existing correct/wrong visual language, not an ad-hoc new color. */
  --wrong-bg: #3a1c16;
  --header-bg: rgba(11, 18, 32, 0.92);
  --badge-topic-text: #cfe0ff;
  --badge-neutral-bg: #24304a;
  --focus-ring: #7ab0ff;
  --radius: 12px;
  font-size: 16px;
}

/* Light theme - toggled via [data-theme="light"] on <html>. Colors re-tuned
   (not just inverted) so contrast and the amber/green distinction both hold
   up on a white background too - see UX review re: color-only signalling. */
[data-theme="light"] {
  --bg: #f5f7fb;
  --card: #ffffff;
  --card-hover: #eef2fb;
  --border: #d7deec;
  --text: #1a2233;
  --text-dim: #5b6478;
  --accent: #2f6fed;
  --accent-dim: #dce8ff;
  /* #92610a (the old value) hit only 4.14:1 against --badge-neutral-bg for
     the bold ~13.6px exam-timer low-time text - just under the 4.5:1
     threshold for text below the "large/bold" 14px cutoff. Darkened
     slightly per the 2026-08-05 UX review, same reasoning as --green
     below. */
  --amber: #8a5b09;
  --amber-bg: #fdf1da;
  /* #17874f (the old value) hits only 4.08:1 against --correct-bg for the
     bold ~12.5px correct-mark text, just under the 4.5:1 threshold for text
     that size/weight - darkened per the accessibility audit. */
  --green: #0f6b3d;
  --correct-bg: #e5f7ed;
  --red: #b8402a;
  --wrong-bg: #fbe8e3;
  --header-bg: rgba(245, 247, 251, 0.92);
  --badge-topic-text: #1e3a8a;
  --badge-neutral-bg: #dde3ee;
  --focus-ring: #1e4fd6;
}

* { box-sizing: border-box; }

/* Explicit focus style - the app previously relied entirely on each
   browser's unstyled default outline. Verified in Chromium this happened to
   look fine (its "auto" outline paints as an inverted-color ring), but nothing
   pinned that down, and this app is mostly used on mobile Safari, whose
   default focus ring behaves differently and wasn't verifiable in this
   environment. Defined once so it's consistent and guaranteed everywhere. */
:focus-visible {
  outline: 3px solid var(--focus-ring);
  outline-offset: 2px;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.theme-toggle {
  border: 1px solid var(--border);
  background: var(--card);
  color: var(--text);
  border-radius: 999px;
  width: 44px;
  height: 44px;
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

header {
  position: sticky;
  top: 0;
  z-index: 10;
  background: var(--header-bg);
  backdrop-filter: blur(6px);
  border-bottom: 1px solid var(--border);
  /* DN-23: safe-area-inset padding for notched phones (the viewport meta
     already has viewport-fit=cover in app.html, so these env() values
     resolve to real insets rather than 0 on devices that have one - a
     no-op elsewhere). Additive via calc() so the existing spacing is a
     floor, not replaced. Top/left/right here since header sits at the
     very top of the layout; main below gets the bottom inset. */
  padding: calc(14px + env(safe-area-inset-top)) calc(16px + env(safe-area-inset-right)) 10px calc(16px + env(safe-area-inset-left));
}

.title-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
}

h1 {
  font-size: 1.15rem;
  margin: 0;
  font-weight: 700;
  letter-spacing: -0.01em;
}

.subtitle {
  font-size: 0.78rem;
  color: var(--text-dim);
  margin-top: 2px;
}

/* Replaced the old two-button DE/EN toggle with a <select> once the app grew
   to 7 languages - a button row stops being usable (or even fitting on a
   phone screen) well before 7 options, a native select scales to any count
   and comes with built-in keyboard/screen-reader support for free.
   Option labels are each language's own endonym ("Українська," "العربية,"
   not "UK"/"AR" 2-letter codes) - a UX review found the codes were only
   decodable by someone already fluent in Latin-script abbreviations, which
   defeats the point for the very languages (Arabic/Chinese/Hindi) added to
   reach readers who may not be. A flags-only picker was considered and
   rejected: flags represent countries, not languages (Arabic has no single
   flag; English/Chinese are spoken across many), so it trades a literacy
   problem for an accuracy problem. The 🌐 icon is a decorative, non-
   language-specific hint that this control is a language switcher, not a
   substitute for the endonym text. */
.lang-select-wrap {
  display: flex;
  align-items: center;
  gap: 6px;
  border: 1px solid var(--border);
  background: var(--card);
  border-radius: 999px;
  padding-inline-start: 12px;
  flex-shrink: 0;
}

.lang-icon {
  font-size: 0.95rem;
  line-height: 1;
}

.lang-select {
  border: none;
  background: transparent;
  color: var(--text);
  padding: 12px 14px 12px 2px;
  min-height: 44px;
  max-width: 120px;
  border-radius: 999px;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
  flex-shrink: 0;
}

.filters {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding-bottom: 2px;
}

.filters button {
  border: 1px solid var(--border);
  background: var(--card);
  color: var(--text-dim);
  padding: 12px 14px;
  min-height: 44px;
  border-radius: 999px;
  font-size: 0.82rem;
  white-space: nowrap;
  cursor: pointer;
}

.filters button.active {
  border-color: var(--accent);
  /* was hardcoded "white" - broke in light theme where --accent-dim is a
     pale background (~1.2:1 contrast). Reuses the same theme-aware token
     the topic badge already uses for text-on-accent-dim. */
  color: var(--badge-topic-text);
  background: var(--accent-dim);
}

/* DN-44: role filter row - same button look as .filters above (deliberately
   not a new visual language), just a slightly smaller/dimmer variant so it
   reads as a secondary, additive filter under the topic row rather than a
   second identical-weight control. */
.role-filters {
  margin-top: -2px;
}

.role-filters button {
  font-size: 0.78rem;
  padding: 10px 12px;
  min-height: 40px;
}

/* REAL BUG, found 2026-08-08 from a user report ("driving test still shows
   IT/compliance role filter"): #role-filters is `class="filters role-filters"`
   and starts `hidden` in app.html, with renderRoleFilter() (app.js) toggling
   the `hidden` property based on whether the current module is a compliance
   module. But `.filters` above sets `display: flex`, and a plain author-
   stylesheet rule always beats the browser's default `[hidden] { display:
   none }` UA rule regardless of specificity (origin/importance is checked
   BEFORE specificity in the cascade) - so the role-filter row was actually
   showing on EVERY module, including Fuehrerschein, the whole time DN-44's
   role filter has existed. This is the exact same class of bug already
   fixed twice elsewhere in this file (.image-note[hidden]/.image-illustration
   [hidden] above, .exam-modal[hidden]/.exam-view[hidden] below) - just never
   applied here too. Fixed the same way: an explicit [hidden] override. */
.role-filters[hidden] {
  display: none;
}

main {
  max-width: 720px;
  margin: 0 auto;
  padding: 16px;
  /* DN-23: safe-area-inset-bottom so content isn't flush against a phone's
     home-indicator gesture area; left/right too for landscape notches. */
  padding-left: calc(16px + env(safe-area-inset-left));
  padding-right: calc(16px + env(safe-area-inset-right));
  padding-bottom: calc(64px + env(safe-area-inset-bottom));
}

.list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.q-card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 16px;
  cursor: pointer;
  transition: background 0.12s ease;
}

.q-card:hover, .q-card:focus-visible {
  background: var(--card-hover);
}

.q-card-top {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 6px;
  flex-wrap: wrap;
}

.badge {
  font-size: 0.7rem;
  font-weight: 700;
  padding: 2px 8px;
  border-radius: 999px;
  letter-spacing: 0.02em;
}

.badge.topic { background: var(--accent-dim); color: var(--badge-topic-text); }
.badge.points { background: var(--badge-neutral-bg); color: var(--text-dim); }
.badge.high-stakes { background: var(--amber-bg); color: var(--amber); }
/* DN-4 multi-select hint badge - reuses the already contrast-verified
   --correct-bg/--green pairing (see .option.correct above) rather than
   introducing a new, unaudited color token. */
.badge.multi-select { background: var(--correct-bg); color: var(--green); }
/* DN-44: renewal-due note on a certificate card - same amber/warning token
   pair as .badge.high-stakes above (deliberately not a new color), just a
   distinct class name since the semantic meaning differs (a completion
   record's renewal status, not a question's safety-criticality). */
.badge.renewal-due { background: var(--amber-bg); color: var(--amber); }
/* DN-14: small star indicator on a list row that's been manually bookmarked
   - reuses the amber token pair (already contrast-verified for
   .badge.high-stakes/.badge.renewal-due above) rather than a new, unaudited
   color, just a distinct class name since the semantic meaning differs
   (a manual bookmark, not a safety-criticality or renewal flag). */
.badge.star-badge { background: var(--amber-bg); color: var(--amber); }
/* DN-23 (tap-affordance hint): pre-reveal-only badge telling the user the
   option rows below are actually tappable (the "try it yourself" flashcard
   self-answer, see renderDetail() in app.js) - added because a plain
   cursor:pointer (see .option[role="button"] etc. below) is invisible on
   touch devices, which are this app's primary audience. Reuses the same
   already-contrast-verified --accent-dim/accent pairing as filter chips
   rather than a new color. */
.badge.try-it-hint { background: var(--accent-dim); color: var(--accent); }

.q-card-text {
  font-size: 0.95rem;
  line-height: 1.4;
  color: var(--text);
}

.q-card-id {
  font-size: 0.72rem;
  color: var(--text-dim);
  /* logical property (not margin-left) so this still pushes to the trailing
     edge of the row under dir="rtl" (Arabic), not just physical left. */
  margin-inline-start: auto;
}

.empty {
  text-align: center;
  color: var(--text-dim);
  padding: 40px 0;
  font-size: 0.9rem;
}

/* Detail view */
.detail {
  position: fixed;
  inset: 0;
  background: var(--bg);
  overflow-y: auto;
  z-index: 20;
}

.detail-header {
  position: sticky;
  top: 0;
  background: var(--header-bg);
  backdrop-filter: blur(6px);
  border-bottom: 1px solid var(--border);
  /* DN-23: same safe-area treatment as header - this is a fixed, full-
     screen (.detail is inset:0) view's own top bar, so it needs its own
     top inset rather than inheriting the page header's. */
  padding: calc(14px + env(safe-area-inset-top)) calc(16px + env(safe-area-inset-right)) 14px calc(16px + env(safe-area-inset-left));
  display: flex;
  align-items: center;
  gap: 10px;
}

.back-btn {
  border: 1px solid var(--border);
  background: var(--card);
  color: var(--text);
  border-radius: 999px;
  padding: 12px 16px;
  min-height: 44px;
  font-size: 0.85rem;
  cursor: pointer;
}

.detail-body {
  max-width: 680px;
  margin: 0 auto;
  /* DN-23: safe-area-inset-bottom/left/right - this is the scrollable body
     inside the fixed, edge-to-edge .detail/.exam-view overlays, shared by
     the flashcard detail view and Simulation/Training exam screens alike. */
  padding: 20px calc(16px + env(safe-area-inset-right)) calc(64px + env(safe-area-inset-bottom)) calc(16px + env(safe-area-inset-left));
}

.detail-meta {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}

.detail-question {
  font-size: 1.25rem;
  line-height: 1.4;
  font-weight: 600;
  margin: 0 0 18px;
}

.image-note {
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  padding: 12px 14px;
  color: var(--text-dim);
  font-size: 0.82rem;
  margin-bottom: 18px;
}

/* [hidden] and a same-specificity class rule can tie in the cascade -
   this makes sure hidden always wins regardless of what class is present. */
.image-note[hidden], .image-illustration[hidden] {
  display: none;
}

.image-illustration {
  display: flex;
  justify-content: center;
  align-items: center;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 12px;
  margin-bottom: 18px;
}

.image-illustration .q-illustration {
  width: 100%;
  max-width: 220px;
  height: auto;
  display: block;
}

.options {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-bottom: 20px;
}

.option {
  display: flex;
  align-items: flex-start;
  flex-wrap: wrap;
  gap: 10px;
  border: 1px solid var(--border);
  background: var(--card);
  /* Same bug class as .exam-mode-btn's 2026-08-05 fix (see its comment
     below): no explicit color was ever declared here, so answer/flashcard
     option text fell back to the browser's default text color instead of
     the themed one - unreadable against this dark --card background.
     Found 2026-08-12 from a user report of unreadable button text
     "within a course" (i.e. the option rows, not the module picker, which
     already had its own color declared). */
  color: var(--text);
  border-radius: var(--radius);
  padding: 12px 14px;
  font-size: 0.95rem;
  line-height: 1.4;
}

/* Flashcard options are only clickable ("try it yourself") before reveal -
   renderDetail() only sets role="button"/"checkbox" on the pre-reveal
   elements, so this selector naturally stops applying once revealed. */
.option[role="button"], .option[role="checkbox"] {
  cursor: pointer;
}

/* DN-23: explicit tap feedback for touch users, since cursor:pointer above
   gives no feedback at all on a touch device (no hover state to reveal it).
   Kept subtle (background shift only, no layout move) so it doesn't fight
   with the .picked/.correct/.your-wrong-pick state colors that can apply
   at the same time. */
.option[role="button"]:active, .option[role="checkbox"]:active {
  background: var(--accent-dim);
}

.option .key {
  font-weight: 700;
  color: var(--text-dim);
  flex-shrink: 0;
  width: 1.2em;
}

.option.correct {
  border-color: var(--green);
  background: var(--correct-bg);
}

.option.correct .key { color: var(--green); }

/* Correct answer is never signalled by color alone - this text+checkmark
   is the primary signal; color is a secondary reinforcement only. */
.correct-mark {
  display: block;
  width: 100%;
  margin-top: 4px;
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--green);
}

/* "Try it yourself" flashcard self-answer (requested after users found the
   reveal-only flow disconnected from the review-mode know/don't-know
   buttons). .picked is the pre-reveal "you tapped this one" state - a
   lighter version of exam mode's .exam-selected so it doesn't get
   confused with the post-reveal green/red verdict. .your-wrong-pick is the
   post-reveal "this is what you picked, and it wasn't right" state -
   paired with the already-shown-elsewhere-in-this-file --red/--wrong-bg
   tokens (see the review-know/dontknow buttons) so a wrong self-answer and
   a wrong review self-assessment read as the same kind of feedback. */
.options .option.picked {
  border-color: var(--accent);
  background: var(--accent-dim);
  cursor: pointer;
}

.option.your-wrong-pick {
  border-color: var(--red);
  background: var(--wrong-bg);
}

.option.your-wrong-pick .key { color: var(--red); }

.wrong-pick-mark {
  display: block;
  width: 100%;
  margin-top: 4px;
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--red);
}

/* DN-14: manual star/bookmark toggle, sits directly above the reveal
   button - a secondary, outline-style action (not the primary accent-filled
   look .reveal-btn uses below) since starring is optional bookkeeping, not
   the main flow of a question. Toggled state (aria-pressed) gets the same
   accent-dim treatment already used for .filters button.active/.review-btn.
   has-due, so "starred" reads consistently with every other toggle in the
   app rather than inventing a new visual language. */
.star-btn {
  display: block;
  width: 100%;
  border: 1px solid var(--border);
  background: var(--card);
  color: var(--text-dim);
  font-weight: 600;
  font-size: 0.85rem;
  padding: 10px 14px;
  min-height: 44px;
  border-radius: var(--radius);
  cursor: pointer;
  margin-bottom: 10px;
}

.star-btn[aria-pressed="true"] {
  border-color: var(--accent);
  background: var(--accent-dim);
  color: var(--badge-topic-text);
}

.reveal-btn {
  border: none;
  background: var(--accent);
  color: white;
  font-weight: 700;
  font-size: 0.95rem;
  padding: 12px 18px;
  border-radius: var(--radius);
  width: 100%;
  cursor: pointer;
  margin-bottom: 18px;
}

.reveal-btn:disabled {
  opacity: 0.5;
  cursor: default;
}

.explanation {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 16px;
  font-size: 0.9rem;
  line-height: 1.5;
  color: var(--text-dim);
}

.explanation strong { color: var(--text); }

.legal-cite {
  font-size: 0.78rem;
  color: var(--text-dim);
  margin-top: 14px;
}

.progress-bar {
  font-size: 0.75rem;
  color: var(--text-dim);
  text-align: center;
  margin: 4px 0 14px;
}

.install-hint {
  font-size: 0.72rem;
  color: var(--text-dim);
  text-align: center;
  padding: 8px 16px 0;
}

/* Exam mode (DN-29) ------------------------------------------------- */

.exam-start-btn {
  border: 1px solid var(--accent);
  background: var(--accent-dim);
  /* Was color: var(--accent) - accent-on-accent-dim is only 1.7:1 in dark
     mode and 3.7:1 in light mode, both well under WCAG AA's 4.5:1 for
     normal text (2026-08-05 UX review - reported as "dark blue on blue,
     hard to read"). --badge-topic-text is this codebase's existing token
     for readable text-on-accent-dim (already used by .badge.topic and
     .filters button.active) - reusing it clears 6.5:1/8.4:1. */
  color: var(--badge-topic-text);
  border-radius: 8px;
  padding: 7px 12px;
  font-size: 0.82rem;
  font-weight: 600;
  cursor: pointer;
}

.exam-start-btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.module-switch-btn {
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text-dim);
  border-radius: 8px;
  padding: 7px 10px;
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
  max-width: 160px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Spaced repetition / Leitner review mode (DN-16) ------------------- */

/* Same base look as .module-switch-btn (it sits right next to it in the
   header), but never hidden even at 0 due - a learner should be able to see
   "nothing due right now" rather than wonder if the feature exists at all. */
.review-btn {
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text-dim);
  border-radius: 8px;
  padding: 7px 10px;
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}

.review-btn.has-due {
  border-color: var(--accent);
  background: var(--accent-dim);
  color: var(--badge-topic-text);
}

/* "Prepare for offline" (DN-46) - button reuses .module-switch-btn's look
   via the shared class; this just lays out the small status text next to
   it, dimmed like the other header hint text rather than a new color. */
.offline-prep-wrap {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.offline-prep-status {
  font-size: 0.72rem;
  color: var(--text-dim);
  white-space: nowrap;
  max-width: 160px;
  overflow: hidden;
  text-overflow: ellipsis;
}

.review-actions {
  margin: 14px 0 18px;
}

.review-actions-caption {
  margin: 0 0 8px;
  font-size: 0.85rem;
  color: var(--text-dim);
}

.review-actions-buttons {
  display: flex;
  gap: 10px;
}

.review-know-btn,
.review-dontknow-btn {
  flex: 1;
  border-radius: var(--radius);
  padding: 10px 14px;
  font-size: 0.88rem;
  font-weight: 700;
  cursor: pointer;
}

/* A subtle "this matches what you just did" hint on whichever button lines
   up with the user's own pre-reveal self-answer attempt (if they made one) -
   still requires an explicit tap, never auto-submitted, but gives someone
   who DID try the card something more concrete than a bare feelings-based
   choice to anchor on. */
.review-know-btn.suggested,
.review-dontknow-btn.suggested {
  box-shadow: 0 0 0 2px var(--focus-ring) inset;
}

/* Same "never color alone" principle as .correct-mark/.selected-mark - the
   button TEXT ("I knew it" / "I didn't know it") is the primary signal,
   the green/red fill is only a reinforcement. */
.review-know-btn {
  border: 1px solid var(--green);
  background: var(--correct-bg);
  color: var(--green);
}

.review-dontknow-btn {
  border: 1px solid var(--red);
  background: var(--wrong-bg);
  color: var(--red);
}

.exam-modal,
.exam-view {
  position: fixed;
  inset: 0;
  z-index: 30;
  background: var(--bg);
  overflow-y: auto;
}

/* [hidden] and a class rule have equal CSS specificity, so source order
   would otherwise let this class win and show the modal/view even while
   the [hidden] attribute is set - same pattern already noted for
   .image-note/.image-illustration above. Force it explicitly. */
.exam-modal[hidden],
.exam-view[hidden] {
  display: none;
}

.exam-modal {
  display: flex;
  /* Bug found 2026-08-13 from a live user report ("normal driving licence
     is unpickable") after the module list grew past one screen's worth of
     items: plain `align-items: center` on a flex container with
     `overflow-y: auto` is a well-known trap - when the centered child
     (.exam-modal-card, here holding the scrollable #module-picker-body
     list) is taller than the viewport, flexbox centers it by pushing the
     overflow equally above AND below the container. The part pushed above
     y=0 has no scroll position that reaches it (scrollTop can't go
     negative), so top-of-list items - confirmed via live DOM inspection to
     be "Driving licence", the very first module - render fully off-screen
     and are permanently unclickable/unreadable, while later items that
     still fit in view work fine. `safe center` keeps the centering when
     content fits, but falls back to start-alignment (scrollable from the
     top) once content overflows, so nothing is ever trapped off-screen. */
  align-items: safe center;
  justify-content: center;
  padding: 20px;
}

.exam-modal-card {
  max-width: 440px;
  width: 100%;
  background: var(--surface, var(--bg));
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 22px;
  text-align: center;
}

.exam-modal-card h2 {
  margin: 0 0 8px;
  font-size: 1.15rem;
}

.exam-modal-card p {
  color: var(--text-dim);
  font-size: 0.85rem;
  margin: 0 0 18px;
}

.exam-mode-btn {
  display: block;
  width: 100%;
  text-align: left;
  border: 1px solid var(--border);
  background: var(--badge-neutral-bg, transparent);
  /* Real bug found via a user screenshot (2026-08-05): this rule never set
     an explicit color, so text fell back to the browser's default <button>
     text color (near-black) instead of inheriting the page's theme text
     color - nearly unreadable against this dark custom background
     (11.6:1/12.3:1 once fixed vs. effectively unreadable before, since a
     UA-default button color isn't a themed value at all). This is the
     class used for module-picker module/scope options and the exam-mode
     (training/simulation) picker, so it was a wide-reaching miss by the
     earlier CSS-custom-property-only contrast audit, which only checked
     declared var-to-var pairings and had no way to catch an absent color
     declaration.
  */
  color: var(--text);
  border-radius: 10px;
  padding: 12px 14px;
  margin-bottom: 10px;
  cursor: pointer;
  font-size: 0.9rem;
  line-height: 1.35;
}

.exam-mode-btn:hover,
.exam-mode-btn:focus-visible {
  border-color: var(--accent);
}

.exam-mode-btn strong {
  display: block;
  margin-bottom: 3px;
  font-size: 0.95rem;
}

/* Module intro wizard (DN-43) */
.intro-dots {
  display: flex;
  justify-content: center;
  gap: 6px;
  margin: 4px 0 18px;
}

.intro-dots .dot {
  width: 7px;
  height: 7px;
  border-radius: 999px;
  background: var(--border);
}

.intro-dots .dot.active {
  background: var(--accent);
}

.intro-nav {
  display: flex;
  gap: 8px;
  align-items: center;
}

.intro-nav .back-btn {
  flex: 0 0 auto;
}

.intro-nav .intro-next-btn {
  flex: 1 1 auto;
  margin-bottom: 0;
  text-align: center;
}

.intro-nav .intro-next-btn strong {
  margin-bottom: 0;
}

#module-intro-back[disabled] {
  visibility: hidden;
}

/* Kickstart-learning-journey topic primers (DN-52 Phase 1). The primer
   body text is genuinely a short paragraph or two (unlike the module-intro
   wizard's one-liners, or Sign Reference's short descriptions), so the
   inherited `.exam-modal-card p { text-align:center; color:var(--text-dim);
   font-size:.85rem }` reads poorly here - override to left-aligned, full
   text color, and a slightly larger/looser line-height for readability. */
#primer-reader-body {
  text-align: left;
  color: var(--text);
  font-size: 0.92rem;
  line-height: 1.55;
  max-height: 50vh;
  overflow-y: auto;
}

#primers-list {
  text-align: left;
  max-height: 55vh;
  overflow-y: auto;
}

/* Local profile switcher */
#profile-list {
  text-align: left;
  max-height: 45vh;
  overflow-y: auto;
}

.profile-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.profile-row.active {
  border-color: var(--accent);
}

.profile-add-row {
  display: flex;
  gap: 8px;
  margin: 4px 0 14px;
}

.profile-add-input {
  flex: 1 1 auto;
  border: 1px solid var(--border);
  background: var(--surface, var(--bg));
  color: var(--text);
  border-radius: 10px;
  padding: 10px 12px;
  font-size: 0.9rem;
}

.profile-add-row .profile-add-btn {
  flex: 0 0 auto;
  width: auto;
  margin-bottom: 0;
  display: flex;
  align-items: center;
}

/* Completion tracking & certificates (DN-14 / DN-44 prep) */
#certificates-list {
  text-align: left;
  max-height: 55vh;
  overflow-y: auto;
}

#certificates-list .empty {
  color: var(--text-dim);
  font-size: 0.85rem;
  text-align: center;
}

.cert-card {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px 14px;
  margin-bottom: 10px;
  background: var(--badge-neutral-bg, transparent);
}

/* Badge display (2026-08-07): visually distinguishes a real, cryptographically
   signed credential from the honest self-issued/unverified fallback - see
   renderBadgeRow() in app.js. Gold-toned emblem + "verified" badge pill for a
   real signature; a plain grey/neutral emblem + "self-issued" pill otherwise.
   Kept as a color+icon+text combination (not color alone) for colorblind
   users, matching this project's existing selection-mark convention. */
.cert-badge-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 8px;
}

.cert-badge-emblem {
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.1rem;
  line-height: 1;
}

.cert-badge-emblem.verified {
  background: linear-gradient(135deg, #f6d365, #d9a441);
  box-shadow: 0 0 0 2px rgba(217, 164, 65, 0.35);
}

.cert-badge-emblem.self-issued {
  background: var(--badge-neutral-bg, rgba(128, 128, 128, 0.15));
  filter: grayscale(1);
  opacity: 0.75;
}

.cert-badge-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.badge.verified { background: var(--green-bg, rgba(46, 160, 67, 0.15)); color: var(--green, #2ea043); }
.badge.self-issued { background: var(--badge-neutral-bg, rgba(128, 128, 128, 0.15)); color: var(--text-dim); }

.cert-badge-hint {
  font-size: 0.7rem;
  color: var(--text-dim);
}

.cert-card-title {
  font-weight: 700;
  font-size: 0.95rem;
  margin-bottom: 2px;
}

.cert-card-date {
  color: var(--text-dim);
  font-size: 0.8rem;
  margin-bottom: 10px;
}

/* DN-44: only populated for compliance-module completions that are overdue
   or due soon for renewal - empty and takes no visible space otherwise. */
.cert-card-renewal:not(:empty) {
  margin: -4px 0 10px;
}

.cert-card-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.cert-card-actions .back-btn {
  flex: 1 1 auto;
  min-width: 140px;
  font-size: 0.8rem;
  padding: 8px 10px;
}

/* DN-51: standalone signed-JWT download button, for real badge-wallet
   (Credly/Open Badges Passport) file-upload import - see
   renderJwtDownloadBtn() in app.js. Only populated once a record is
   genuinely signed; empty (no visible space) otherwise. */
.cert-jwt-row:not(:empty) {
  margin-top: 8px;
}

.cert-jwt-row .cert-dl-jwt {
  width: 100%;
  font-size: 0.8rem;
  padding: 8px 10px;
}

/* DN-49: permanent verification link row (0€ MVP). Only ever populated for
   compliance-module records that already carry a real signature - see
   renderVerifyLinkRow() in app.js. Empty (no visible space) otherwise. */
.cert-verify-row:not(:empty) {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px dashed var(--border);
}

.cert-verify-title {
  font-weight: 700;
  font-size: 0.8rem;
  margin-bottom: 4px;
}

.cert-verify-intro {
  font-size: 0.75rem;
  color: var(--text-dim);
  margin-bottom: 8px;
}

.cert-verify-form {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.cert-verify-name-input {
  flex: 1 1 140px;
  min-width: 120px;
  font-size: 0.85rem;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg, transparent);
  color: inherit;
}

.cert-verify-form .cert-verify-create-btn {
  flex: 0 0 auto;
  font-size: 0.8rem;
  padding: 8px 12px;
}

.cert-verify-link-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.cert-verify-link {
  font-size: 0.8rem;
  word-break: break-all;
  color: var(--accent, inherit);
}

.cert-verify-link-row .cert-verify-copy-btn {
  flex: 0 0 auto;
  font-size: 0.8rem;
  padding: 6px 10px;
}

.cert-verify-error {
  margin-top: 6px;
  font-size: 0.75rem;
  color: var(--red, #d9534f);
}

/* Sign Reference (Fuehrerschein-only browsable sign study list) */
#sign-reference-list {
  text-align: left;
  max-height: 60vh;
  overflow-y: auto;
}

#sign-reference-list .empty {
  color: var(--text-dim);
  font-size: 0.85rem;
  text-align: center;
}

.sign-ref-category {
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-dim);
  margin: 18px 0 8px;
  padding-bottom: 4px;
  border-bottom: 1px solid var(--border);
}

.sign-ref-category:first-child {
  margin-top: 0;
}

.sign-ref-item {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
  margin-bottom: 8px;
  background: var(--badge-neutral-bg, transparent);
}

.sign-ref-icon {
  width: 44px;
  height: 44px;
  flex: 0 0 auto;
}

.sign-ref-text {
  min-width: 0;
}

.sign-ref-name {
  font-weight: 700;
  font-size: 0.9rem;
  margin-bottom: 2px;
}

.sign-ref-desc {
  color: var(--text-dim);
  font-size: 0.82rem;
  line-height: 1.35;
}

.exam-timer {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  padding: 4px 10px;
  border-radius: 8px;
  background: var(--badge-neutral-bg, transparent);
  border: 1px solid var(--border);
  font-size: 0.85rem;
}

.exam-timer.low-time {
  color: var(--amber);
  border-color: var(--amber);
}

/* DN-14: "Frage schieben" (skip-and-revisit) second-pass banner - reuses the
   same amber warning token as .exam-timer.low-time/.badge.high-stakes above
   (deliberately not a new color) so it reads as "pay attention, this is a
   different mode" without introducing yet another accent. */
.exam-skip-banner {
  background: var(--amber-bg);
  color: var(--amber);
  border: 1px solid var(--amber);
  border-radius: 10px;
  padding: 10px 14px;
  font-size: 0.85rem;
  font-weight: 600;
  margin-bottom: 14px;
}

.options .option.exam-selected {
  border-color: var(--accent);
  background: var(--accent-dim);
}

/* DN-4: multi-select options get a small square checkbox indicator before
   the A/B/C/D key, so the "pick any number of these" affordance reads as a
   genuinely different interaction than the single-select radio-style cards
   above - not just relying on the shared .exam-selected border/bg tint,
   which stays as a secondary reinforcement only. */
.option-checkbox .key {
  position: relative;
  padding-inline-start: 22px;
}

.option-checkbox .key::before {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  top: 0.15em;
  width: 14px;
  height: 14px;
  border: 2px solid var(--text-dim);
  border-radius: 3px;
  background: transparent;
}

.option-checkbox.exam-selected .key::before {
  border-color: var(--accent);
  background: var(--accent);
}

/* A checkmark that appears/disappears (not just a color/background shift)
   so "this is your selected answer" isn't signalled by color alone -
   2026-08-05 UX review, same principle as .correct-mark above. */
.selected-mark {
  font-weight: 700;
  color: var(--badge-topic-text);
  margin-inline-start: auto;
  flex-shrink: 0;
}

.exam-results-pass {
  color: var(--green);
}

.exam-results-fail {
  color: var(--amber);
}

.exam-results-summary-box {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 14px;
  margin: 12px 0 20px;
}

.exam-review-item {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px 14px;
  margin-bottom: 10px;
}

.exam-review-item .q-card-text {
  margin-bottom: 8px;
}

.exam-review-item .your-answer {
  color: var(--amber);
}

.exam-review-item .right-answer {
  color: var(--green);
}
