:root {
  --bg: #f4f6f9;
  --surface: #ffffff;
  --surface-alt: #eef1f6;
  /* --border is a CONTROL boundary, so 1.4.11 puts a 3:1 floor on it against every
     surface it can sit on. Our controls are all surface-on-surface (a .btn is
     --surface on --bg, 1.05:1; a .unit-btn is --surface-alt on --surface, 1.10:1),
     so the fill identifies nothing and the border is the only thing saying "this is
     a control" — it has to carry the whole 3:1 by itself. Measured against the
     DARKEST surface it borders (--surface-alt), which is the binding case for a dark
     line, not white. --border-subtle is the old hairline, kept for rules and dividers
     between blocks of text: those are decorative, own no 1.4.11 duty, and going mid-
     grey on them would put a box around every inline <code> in the lessons. Default
     to --border and opt down deliberately, never the other way round. */
  --border: #6f7d95;
  --border-subtle: #d8dee8;
  --text: #1b2430;
  --text-dim: #5b6675;
  --accent: #0b5ab8;
  /* A tint of --accent for "this state is on" fills, per theme: a single hardcoded
     value would be a dark-theme teal washing out to nothing over a white surface. */
  --accent-soft: rgba(11, 90, 184, 0.14);
  --accent-strong: #083f80;
  --accent-contrast: #ffffff;
  --correct: #1a7a3c;
  --correct-bg: #e3f6e9;
  --wrong: #b3261e;
  --wrong-bg: #fbe6e5;
  --code-bg: #0f1720;
  --code-text: #e3e9f0;
  --info-text: #1d4ed8;
  --warn-text: #8a5a00;
  --field-border: #6b7280;
  --shadow: 0 1px 2px rgba(20, 30, 45, 0.06), 0 4px 16px rgba(20, 30, 45, 0.06);
  --radius: 14px;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #10151d;
    --surface: #1a212c;
    --surface-alt: #212a37;
    --border: #6d8099;
    --border-subtle: #303b4a;
    --text: #e7ecf3;
    --text-dim: #9aa7b8;
    --accent: #7fbfff;
    --accent-soft: rgba(127, 191, 255, 0.2);
    --accent-strong: #a8d4ff;
    --accent-contrast: #06203f;
    --correct: #4ade80;
    --correct-bg: #123321;
    --wrong: #f87171;
    --wrong-bg: #3a1616;
    --code-bg: #060a10;
    --code-text: #d8e2ee;
    --info-text: #93b8ff;
    --warn-text: #e3b341;
    --field-border: #7c8ba3;
    --shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 8px 24px rgba(0, 0, 0, 0.35);
  }
}

:root[data-theme="dark"] {
  --bg: #10151d;
  --surface: #1a212c;
  --surface-alt: #212a37;
  --border: #6d8099;
  --border-subtle: #303b4a;
  --text: #e7ecf3;
  --text-dim: #9aa7b8;
  --accent: #7fbfff;
  --accent-soft: rgba(127, 191, 255, 0.2);
  --accent-strong: #a8d4ff;
  --accent-contrast: #06203f;
  --correct: #4ade80;
  --correct-bg: #123321;
  --wrong: #f87171;
  --wrong-bg: #3a1616;
  --code-bg: #060a10;
  --code-text: #d8e2ee;
  --info-text: #93b8ff;
  --warn-text: #e3b341;
  --field-border: #7c8ba3;
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 8px 24px rgba(0, 0, 0, 0.35);
}

:root[data-theme="light"] {
  --bg: #f4f6f9;
  --surface: #ffffff;
  --surface-alt: #eef1f6;
  --border: #6f7d95;
  --border-subtle: #d8dee8;
  --text: #1b2430;
  --text-dim: #5b6675;
  --accent: #0b5ab8;
  --accent-soft: rgba(11, 90, 184, 0.14);
  --accent-strong: #083f80;
  --accent-contrast: #ffffff;
  --correct: #1a7a3c;
  --correct-bg: #e3f6e9;
  --wrong: #b3261e;
  --wrong-bg: #fbe6e5;
  --code-bg: #0f1720;
  --code-text: #e3e9f0;
  --info-text: #1d4ed8;
  --warn-text: #8a5a00;
  --field-border: #6b7280;
  --shadow: 0 1px 2px rgba(20, 30, 45, 0.06), 0 4px 16px rgba(20, 30, 45, 0.06);
}

* { box-sizing: border-box; }

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}

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

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  overflow-x: hidden;
}

/* SC 1.4.10 Reflow. `overflow-x: hidden` above means an over-wide line is not merely
   pushed off screen, it is DESTROYED: documentElement.scrollWidth stays pinned to
   clientWidth and scrollLeft will not move, so there is no scrollbar to recover it with.
   At 320px an unbreakable token - an API identifier, a URL, a CamelCase name - ran up to
   244px past the edge, and in the drill view the text broke out of the answer button
   itself, so the option a learner is being asked to choose was unreadable.
   The file already had the cure, scoped to CJK: `overflow-wrap: anywhere` on the ja/zh
   body, which is why those two languages measured zero clipped sites while the other
   five measured 361 between them. Every language needs it.
   `break-word` was tried first, on the theory that it wraps the same tokens without
   feeding back into min-content sizing. Measured: it is NOT enough. Leaving intrinsic
   size alone is exactly the problem - an inline <code> whose single token is wider than
   its containing block still refuses to break. `anywhere` is the value the CJK rule
   already uses, and the one that takes ja and zh to zero. */
body { overflow-wrap: anywhere; }

/* ---------- Internationalisation ---------- */

/* Script-appropriate font stacks. Without these, Arabic and Farsi fall back to a
   default that renders shaping poorly, and CJK gets Latin metrics. */
:root[lang="ar"], :root[lang="fa"] {
  font-family: "Segoe UI", Tahoma, "Noto Naskh Arabic", "Geeza Pro", "Iranian Sans", sans-serif;
  line-height: 1.9; /* Arabic and Persian need more leading for diacritics */
}

:root[lang="ja"] {
  font-family: -apple-system, "Hiragino Kaku Gothic ProN", "Yu Gothic", "Noto Sans JP", "Meiryo", sans-serif;
}

:root[lang="zh"] {
  font-family: -apple-system, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", sans-serif;
}

/* CJK has no word spaces, so break anywhere rather than overflowing; and italics
   are not a CJK typographic convention — synthesised obliques look broken. */
:root[lang="ja"] body, :root[lang="zh"] body { line-break: strict; word-break: normal; overflow-wrap: anywhere; }
:root[lang="ja"] em, :root[lang="zh"] em,
:root[lang="ja"] i, :root[lang="zh"] i { font-style: normal; font-weight: 600; }

/* Directional glyphs written into copy (→ ← ⏱) must mirror in RTL. They are
   decorative arrows, so flipping the glyph box is correct and safe. */
:root[dir="rtl"] .chev,
:root[dir="rtl"] .unit-status { display: inline-block; transform: scaleX(-1); }

/* Code stays left-to-right even in an RTL interface — source code is not RTL text. */
:root[dir="rtl"] pre.code-block,
:root[dir="rtl"] pre.code-block code,
:root[dir="rtl"] .paste-wrap textarea,
:root[dir="rtl"] code { direction: ltr; text-align: left; unicode-bidi: isolate; }

/* Numeric progress values shouldn't reorder inside RTL sentences. */
:root[dir="rtl"] .xp-label,
:root[dir="rtl"] .part-count,
:root[dir="rtl"] .ref-meta,
:root[dir="rtl"] .unit-count,
:root[dir="rtl"] .step-num { unicode-bidi: isolate; }

.lang-picker select {
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 5px 8px;
  font-size: 12.5px;
  font-family: inherit;
}

#app {
  max-width: 880px;
  margin: 0 auto;
  padding: 28px 20px 64px;
}

header.top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 24px;
  flex-wrap: wrap;
}

header.top h1 {
  font-size: 22px;
  margin: 0 0 4px;
  letter-spacing: -0.01em;
}

header.top p {
  margin: 0;
  color: var(--text-dim);
  font-size: 13.5px;
}

.overall-stats {
  display: flex;
  gap: 18px;
  font-size: 13px;
  color: var(--text-dim);
}

.overall-stats strong {
  color: var(--text);
  font-size: 15px;
  display: block;
}

.level-badge {
  display: inline-block;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--surface-alt);
  color: var(--text-dim);
  border: 1px solid var(--border);
}

.level-badge.beginner { color: var(--info-text); border-color: var(--info-text); }
.level-badge.intermediate { color: var(--warn-text); border-color: var(--warn-text); }
.level-badge.advanced { color: var(--wrong); border-color: var(--wrong); }

/* ---------- home: section headings ---------- */

.home-section-title {
  font-size: 18px;
  margin: 26px 0 12px;
  letter-spacing: -0.01em;
}

.home-section-title:first-child { margin-block-start: 0; }

/* ---------- home: zone 1, the single start point ---------- */

/* The only primary action on the page. Everything else on home is a quiet row, so
   this can carry weight without shouting over anything. */
/* Emphasised by its BORDER, not by a fill.
   This was a solid block of --accent, and the accent is green: in this app green
   already means a state, not an action. The completed-answer colour is green, and the
   "you are here" lesson row directly below this button is a green tint — so a green
   slab at the top of the page read as something already selected or already done,
   which is the opposite of "start". Everything else on the page is a surface card, so
   the fill also made this the only element that looked like a highlight.
   A two-pixel accent border keeps it unmistakably the lead action while leaving the
   fill — and therefore the state vocabulary — alone. */
.home-hero {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  text-align: start;
  background: var(--surface);
  color: var(--text);
  border: 2px solid var(--accent);
  border-radius: var(--radius);
  padding: 18px;
  margin-bottom: 12px;
  cursor: pointer;
  font: inherit;
  box-shadow: var(--shadow);
  transition: transform 0.12s ease;
}

.home-hero:hover { transform: translateY(-2px); }

.home-hero .icon { font-size: 26px; flex-shrink: 0; }

.home-hero .hero-text {
  display: flex;
  flex-direction: column;
  gap: 3px;
  flex: 1;
  min-inline-size: 0;
}

/* The accent moves from the fill to the type: the title and the arrow carry it, so the
   card still reads as the primary action at a glance. */
.home-hero .hero-text strong { font-size: 17px; color: var(--accent); }

.home-hero .hero-text > span { font-size: 13px; opacity: 0.92; }

/* The unit count and progress the Class banner used to carry, now inside the one
   control that replaced both — in words, so they are part of the button's own
   accessible name. The bar underneath is their decorative twin and nothing else. */
.home-hero .hero-meta {
  margin-top: 3px;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  unicode-bidi: isolate;
}

.home-hero .hero-bar {
  display: block;
  height: 8px;
  margin-top: 5px;
  /* Set the bar's own colour rather than replacing currentColor below: the card's text
     is --text now that it is no longer filled with the accent, and a dark grey rule
     would not read as progress. Keeping currentColor as the paint preserves the
     forced-colours behaviour the rule below relies on. */
  color: var(--accent);
  border: 1px solid currentColor;
  border-radius: 999px;
  overflow: hidden;
}

/* currentColor on both, so the bar keeps its contrast against the accent in either
   theme and survives forced-colours without a colour of its own. */
.home-hero .hero-bar > span {
  display: block;
  height: 100%;
  background: currentColor;
}

.home-hero .chev { font-size: 20px; flex-shrink: 0; color: var(--accent); }

/* ---------- home: zone 2, practice by topic ---------- */

/* One select replaced six collapsible parts and thirty topic cards. What went with
   them is the at-a-glance scan of every topic at once — the Class carries that, in
   the order the course means the topics to be met, so this line says where. */
.practice-hint {
  margin: -4px 0 12px;
  font-size: 12.5px;
  color: var(--text-dim);
}

.topic-picker {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 12px;
}

/* A real <label for>, never a placeholder option: the select keeps its name while
   a value is showing, and the label is a click target of its own. */
.topic-picker label { font-size: 13.5px; font-weight: 600; }

.topic-picker select {
  flex: 1 1 260px;
  min-inline-size: 0;
  max-inline-size: 100%;
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 9px 10px;
  font: inherit;
  font-size: 13.5px;
}

/* The chosen topic's contents. Swapped in place on change — never redrawn through
   render(), which would destroy the select the learner is standing on. */
.practice-panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 14px 16px;
  margin-bottom: 18px;
}

.topic-panel-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 10px;
  font-size: 15px;
}

.topic-panel-head .icon { font-size: 19px; line-height: 1; }

/* ---------- home and Reference: compact rows of links ---------- */

.ref-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  list-style: none;
  margin: 0 0 18px;
  padding: 0;
}

.ref-row > li { flex: 1 1 210px; display: flex; }

.ref-link {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  text-align: start;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 11px 13px;
  color: inherit;
  font: inherit;
  cursor: pointer;
}

.ref-link:hover { border-color: var(--accent); }

.ref-link .icon { font-size: 19px; line-height: 1; flex-shrink: 0; }

.ref-link .ref-text { flex: 1; font-size: 13.5px; font-weight: 600; min-inline-size: 0; }

.ref-meta {
  display: block;
  font-size: 11.5px;
  font-weight: 400;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
  unicode-bidi: isolate;
}

.ref-link .chev { font-size: 15px; color: var(--accent); flex-shrink: 0; }

.guide-list {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  list-style: none;
  margin: 0;
  padding: 0;
}

.guide-link {
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 6px 12px;
  font: inherit;
  font-size: 12.5px;
  color: inherit;
  cursor: pointer;
}

.guide-link:hover { border-color: var(--accent); color: var(--accent); }

/* The guides are a section of the Reference view now, not a disclosure on home. */
.ref-guides-title {
  margin: 20px 0 10px;
  font-size: 15px;
}

/* The roadmap, now opened from the top of the Class it orients you inside. */
.class-roadmap-link {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  text-align: start;
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 11px 13px;
  margin-bottom: 14px;
  color: inherit;
  font: inherit;
  cursor: pointer;
}

.class-roadmap-link:hover { border-color: var(--accent); }
.class-roadmap-link .icon { font-size: 19px; line-height: 1; flex-shrink: 0; }
.class-roadmap-link .ref-text { flex: 1; font-size: 13.5px; font-weight: 600; min-inline-size: 0; }
.class-roadmap-link .chev { font-size: 15px; color: var(--accent); flex-shrink: 0; }

/* At 320px a label beside a select leaves neither enough room, so they stack.
   Stacking turns the picker into a COLUMN flex container, where flex-basis is a
   height — so the select's row-direction basis has to be dropped, or it grows
   into a 260px-tall box. */
@media (max-width: 380px) {
  .ref-row > li { flex-basis: 100%; }
  .topic-picker { flex-direction: column; align-items: stretch; }
  .topic-picker label { margin-bottom: 2px; }
  .topic-picker select { flex: 0 0 auto; width: 100%; }
}

/* ---------- home and Reference: the rows inside a topic panel ---------- */

/* The row's type ("Lesson", "Project", …) — visible text, and part of the row's
   accessible name, so it is never carried by the icon alone. */
.row-type {
  flex-shrink: 0;
  min-inline-size: 58px;
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-dim);
}

.topic-row-btn.current .unit-title { font-weight: 700; }

/* Locked rows stay focusable (aria-disabled, never disabled) so their explanation
   is reachable. Border STYLE, not colour alone, so forced-colours keeps it. */
.topic-row-btn[aria-disabled="true"] {
  cursor: not-allowed;
  border-style: dotted;
  color: var(--text-dim);
}

.topic-row-btn[aria-disabled="true"]:hover { border-color: var(--border); }

.topic-panel-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 12px;
}

.topic-practice-btn {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  text-align: start;
  flex: 1 1 200px;
}

.practice-sub {
  font-size: 11.5px;
  font-weight: 400;
  color: var(--text-dim);
  unicode-bidi: isolate;
}

.topic-resume-btn { flex: 1 1 160px; }

/* ---------- drill picker ---------- */

.pick-group { margin: 14px 0; }

.pick-all {
  border-block-end: 1px solid var(--border-subtle);
  padding-block-end: 8px;
  margin-block-end: 6px;
  font-weight: 600;
}

.pick-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.pick-name { flex: 1; }

.pick-count {
  font-size: 11.5px;
  color: var(--text-dim);
  flex-shrink: 0;
  unicode-bidi: isolate;
}

.pick-summary {
  font-size: 13.5px;
  font-weight: 600;
  margin: 16px 0 12px;
  padding: 10px 14px;
  border: 1px solid var(--border);
  border-inline-start: 4px solid var(--accent);
  border-radius: 10px;
  background: var(--surface-alt);
}

.session-length-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 4px 0 20px;
  font-size: 13.5px;
  color: var(--text-dim);
}

.session-length-row select {
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 6px 10px;
  font-size: 13.5px;
}

button {
  font-family: inherit;
}

.btn {
  appearance: none;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  border-radius: 10px;
  padding: 9px 16px;
  font-size: 13.5px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.12s ease, border-color 0.12s ease, transform 0.05s ease;
}

.btn:hover { border-color: var(--accent); }
.btn:active { transform: scale(0.98); }

.btn.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-contrast);
}

.btn.primary:hover { background: var(--accent-strong); border-color: var(--accent-strong); }

.btn.ghost {
  background: transparent;
}

.btn.small { padding: 6px 11px; font-size: 12.5px; }

.footer-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 28px;
  padding-top: 16px;
  border-top: 1px solid var(--border-subtle);
  font-size: 12px;
  color: var(--text-dim);
  flex-wrap: wrap;
  gap: 10px;
}

/* Gamification */

.gamify-strip {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 10px 14px;
  margin-bottom: 18px;
  box-shadow: var(--shadow);
}

/* Profile sits at the far end of the strip — the top-right corner in a left-to-right
   language, and correctly the top-LEFT in Arabic and Farsi, which is what
   margin-inline-start buys over margin-left. The auto margin absorbs the free space,
   so everything else stays grouped at the start. */
/* The masthead slot the answered/accuracy score used to occupy. */
.header-profile {
  margin-inline-start: auto;
  align-self: center;
}

.profile-btn {
  margin-inline-start: auto;
  white-space: nowrap;
}

.rank-chip {
  font-size: 13px;
  font-weight: 700;
  white-space: nowrap;
}

.xp-wrap {
  flex: 1 1 180px;
  min-width: 160px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.xp-track {
  height: 8px;
  border-radius: 999px;
  background: var(--surface-alt);
  border: 1px solid var(--border);
  overflow: hidden;
}

.xp-track > div {
  height: 100%;
  background: linear-gradient(90deg, var(--accent), var(--accent-strong));
  border-radius: 999px;
  transition: width 0.4s ease;
}

.xp-label {
  font-size: 11.5px;
  color: var(--text-dim);
}

.day-streak {
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
}

.streak-idle {
  font-weight: 400;
  font-size: 11.5px;
  color: var(--text-dim);
}

.xp-chip {
  display: inline-block;
  font-size: 12px;
  font-weight: 700;
  background: var(--accent);
  color: var(--accent-contrast);
  border-radius: 999px;
  padding: 2px 10px;
  margin-inline-end: 8px;
}

/* Toasts (visual only; SR announcements go through the live region) */

.toast-stack {
  position: fixed;
  top: 14px;
  inset-inline-end: 14px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  z-index: 50;
  max-width: min(320px, calc(100vw - 28px));
  pointer-events: none; /* don't block clicks on content underneath */
}

.toast-stack .toast.interactive { pointer-events: auto; }

.toast {
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--accent);
  border-inline-start: 4px solid var(--accent);
  border-radius: 10px;
  padding: 10px 14px;
  font-size: 13.5px;
  box-shadow: var(--shadow);
  opacity: 0;
  transform: translateY(-8px);
  transition: opacity 0.3s ease, transform 0.3s ease;
}

.toast.show {
  opacity: 1;
  transform: translateY(0);
}

/* Summary celebration */

.celebrate-line {
  font-size: 14.5px;
  font-weight: 600;
  margin: 8px 0 0;
  color: var(--accent);
}

.unlock-list {
  list-style: none;
  margin: 12px auto 0;
  padding: 0;
  max-width: 460px;
  text-align: start;
  font-size: 13.5px;
}

.unlock-list li {
  padding: 6px 10px;
  border: 1px dashed var(--accent);
  border-radius: 8px;
  margin-bottom: 6px;
}

.summary-stat.xp-stat .val { color: var(--accent); }

.confetti {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  border-radius: var(--radius);
}

.confetti-piece {
  position: absolute;
  top: -12px;
  width: 8px;
  height: 12px;
  border-radius: 2px;
  background: var(--accent);
  animation: confetti-fall 2.6s ease-in forwards;
}

.confetti-piece:nth-child(3n) { background: #e0a800; }
.confetti-piece:nth-child(3n + 1) { background: #2563eb; }
.confetti-piece:nth-child(4n) { width: 6px; height: 9px; }

@keyframes confetti-fall {
  0% { transform: translateY(0) rotate(0deg); opacity: 1; }
  100% { transform: translateY(420px) rotate(340deg); opacity: 0; }
}

/* Achievements view */

.ach-grid {
  list-style: none;
  margin: 0 0 22px;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 10px;
}

.ach-item {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
  background: var(--surface-alt);
}

.ach-item.unlocked { border-color: var(--accent); }

/* Dim only the lock icon — dimming the whole card drops text below AA contrast. */
.ach-item.locked .ach-icon { opacity: 0.75; }

.ach-icon { font-size: 22px; line-height: 1.2; }

.ach-text { display: flex; flex-direction: column; gap: 2px; }

.ach-name { font-size: 13.5px; font-weight: 700; }

.ach-desc { font-size: 12.5px; color: var(--text-dim); }

.ach-date { font-size: 11.5px; color: var(--accent); font-weight: 600; }

.rank-ladder-title { font-size: 15px; margin: 0 0 10px; }

.rank-ladder {
  list-style: none;
  margin: 0 0 8px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.rank-item {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13.5px;
  padding: 7px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text-dim);
}

.rank-item.reached { color: var(--text); border-color: var(--accent); }

.rank-item.current { background: var(--surface-alt); font-weight: 700; }

.rank-item .rank-xp { margin-inline-start: auto; font-size: 12px; }

.you-chip {
  background: var(--accent);
  color: var(--accent-contrast);
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  border-radius: 999px;
  padding: 1px 8px;
}

/* Class mode */

.course-progress {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 0 0 22px;
  padding: 14px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface-alt);
}

.course-progress .btn { align-self: flex-start; margin-top: 6px; }

.class-part {
  border: 1px solid var(--border);
  border-radius: 10px;
  margin-top: 12px;
  background: var(--surface);
}

.class-part > summary {
  cursor: pointer;
  padding: 12px 14px;
  border-radius: 10px;
  list-style-position: inside;
}

.class-part > summary:hover h3 { color: var(--accent); }

.class-part[open] > summary { border-bottom: 1px solid var(--border-subtle); }

.class-part .part-body { padding: 14px; }

/* The WCAG reference reuses this card as a plain <section>: no <summary> and no
   .part-body, so neither of the rules above applied and its heading and prose sat
   directly on the border. Logical padding, so the gutter follows the writing direction
   rather than always landing on the left. */
section.class-part { padding: 14px; }
section.class-part > h3 { display: block; }

.class-part h3 { font-size: 16px; margin: 0; display: inline; }

.part-count {
  display: block;
  font-size: 12px;
  font-weight: 400;
  color: var(--text-dim);
  margin-top: 3px;
}

.how-it-works {
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface-alt);
  margin: 0 0 16px;
}

.how-it-works summary {
  cursor: pointer;
  padding: 11px 14px;
  font-size: 13.5px;
  font-weight: 600;
  border-radius: 10px;
}

.how-it-works summary:hover { color: var(--accent); }

.how-it-works[open] summary { border-bottom: 1px solid var(--border-subtle); }

.how-it-works p { margin: 10px 14px; font-size: 13.3px; line-height: 1.6; }

.part-blurb { font-size: 13px; color: var(--text-dim); margin: 0 0 12px; }

.part-done-chip {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  color: var(--correct);
  border: 1px solid var(--correct);
  border-radius: 999px;
  padding: 1px 8px;
  vertical-align: middle;
}

.class-topic { margin-bottom: 14px; }

.class-topic h4 { font-size: 14px; margin: 0 0 6px; }

.unit-pending { font-size: 12.5px; color: var(--text-dim); font-style: italic; margin: 0 0 8px; }

.unit-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.unit-btn {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  text-align: start;
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 9px 12px;
  font-size: 13.5px;
  color: var(--text);
  cursor: pointer;
}

.unit-btn:hover { border-color: var(--accent); }

.unit-row.done .unit-status { color: var(--correct); font-weight: 700; }

.unit-row.next .unit-btn { border-color: var(--accent); }

.unit-row.next .unit-status { color: var(--accent); }

.unit-status { flex-shrink: 0; width: 18px; text-align: center; }

/* min-inline-size:0 or the title refuses to shrink below its longest word and shoves
   the count clean off the inline-end edge. Two extra levels of nesting inside the
   home part panels cost the row 20px, and a narrow RTL viewport spent it: at 320px
   in Arabic the question count sat outside the button entirely. Let the title wrap
   instead — the row grows a line, which is what the rest of the card already does. */
.unit-title { flex: 1; min-inline-size: 0; }

.unit-count { font-size: 11.5px; color: var(--text-dim); flex-shrink: 0; }

.unit-kicker { font-size: 12.5px; color: var(--text-dim); margin: 0 0 2px; }

.unit-done-chip {
  color: var(--correct);
  border: 1px solid var(--correct);
  border-radius: 999px;
  padding: 0 8px;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
}

.celebrate-line.not-passed { color: var(--wrong); }

/* Figures (unit + WCAG illustrations) */

.unit-figure {
  margin: 18px 0;
  /* Roomier than it looks: a diagram's longest caption is placed by absolute geometry
     authored against the ENGLISH wording, and a translation of the same line is often
     wider — French runs longest, but English overflows two of its own figures too. The
     padding is where that extra width goes (see `overflow` on the svg below). */
  padding: 22px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface-alt);
  /* Backstop: whatever the svg paints outside itself stops at the figure, so a long
     label can never widen the page and break reflow at 320px (1.4.10). */
  overflow: hidden;
}

.unit-figure svg {
  display: block;
  max-width: 100%;
  height: auto;
  margin: 0 auto;

  /* The drawings are absolute geometry: each <text> is placed by its own x, and most
     of them take the default text-anchor of "start". "start" is defined against the
     inline base direction, so on an Arabic or Farsi page — where dir="rtl" inherits
     into the SVG — every label anchors its RIGHT edge at x and grows the other way,
     out of the box it belongs to and across its neighbour. Pinning the SVG to ltr puts
     the anchors back where the drawing was authored; it is inert on an LTR page. */
  direction: ltr;

  /* A label whose translation is wider than the English it replaced would otherwise be
     cut off mid-word — French lost the end of "un tiroir fermé". Let it paint into the
     figure's padding; .unit-figure clips, so nothing can reach the page. */
  overflow: visible;
}

/* 1.4.12 asks that nothing be LOST when a reader sets their own text spacing; it does not
   ask a drawing to reflow. These labels are placed by absolute x and y and cannot reflow:
   extra tracking does not space them out, it walks them into each other and out of the
   shapes they annotate, and the figure then clips what it cannot hold (F104). Measured over
   the fourteen figures in all seven languages with the standard text-spacing stylesheet
   applied and no reset: 39 labels pushed outside their viewBox, the worst by 194 user units,
   and 64 pairs of labels overlapping. With the reset: none of either. So turn off the two
   properties that actually move SVG text, and let the reader's spacing do its work where it
   helps — on the prose, including the <figcaption> that describes the same diagram as real,
   reflowable text. The !important is load-bearing, not habit: the spacing arrives as a `*`
   rule marked !important, and a plain declaration would lose to it.
   Do NOT reach for a scroller as a backstop here. SVG painted outside the svg's own box is
   INK overflow, not scrollable overflow: with overflow-x:auto on .unit-figure the container
   measures scrollWidth === clientWidth and scrollLeft pinned at 0, so it behaves exactly like
   the overflow:hidden above and recovers nothing. Against a genuine user-origin !important
   stylesheet, which outranks this rule, the figcaption is the fallback — not a scrollbar. */
.unit-figure svg text {
  letter-spacing: normal !important;
  word-spacing: normal !important;
}

/* Pinning the drawing to ltr would otherwise make every label an ltr line, so each one
   takes its OWN base direction here instead — and this rule IS the mechanism, not a
   fallback. `plaintext` runs the UAX #9 first-strong scan on the label's own content,
   and that scan skips whatever sits inside an isolate. svgForDisplay() in
   js/content-overlay.js isolates the left-to-right runs and NOTHING else, precisely so
   the scan steps over the code and lands on the first character of real prose. That is
   what makes "viewport 1280 px: سه ستون" a Persian line rather than an English one
   despite opening on a Latin word. Wrapping the whole label in an outer isolate — an
   earlier attempt at saying the direction outright — hides every character from the
   scan, drops the line back to left-to-right, and reorders the pieces. */
.unit-figure svg text,
.fig-thumb-svg svg text {
  unicode-bidi: plaintext;
}

.fig-thumb-svg svg {
  direction: ltr;
}

.unit-figure img {
  display: block;
  max-width: 100%;
  height: auto;
  margin: 0 auto;
  border-radius: 6px;
}

/* The authoring form's per-image rows: thumbnail, description fields, remove. */
.fig-list {
  list-style: none;
  padding: 0;
  margin: 8px 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.fig-item {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: flex-start;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px;
  background: var(--surface-alt);
}

.fig-thumb {
  max-inline-size: 96px;
  max-block-size: 96px;
  border-radius: 6px;
  flex-shrink: 0;
}

.fig-thumb-svg {
  display: block;
  inline-size: 96px;
  overflow: hidden;
}

.fig-thumb-svg svg { max-inline-size: 100%; block-size: auto; }

.fig-fields { flex: 1 1 240px; min-inline-size: 0; }

.fig-dec {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-block: 6px;
  font-size: 13.5px;
}

.fig-dec input[type="checkbox"] { inline-size: 18px; block-size: 18px; flex-shrink: 0; }

/* Formatting toolbar over the body editor. */
.fmt-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-block: 6px 8px;
}

/* WCAG 2.5.8: every control keeps at least a 24px target; small buttons sit at 28. */
.btn { min-block-size: 32px; }
.btn.small { min-block-size: 28px; }

/* Authored/unit body structure: real lists, headings and quotes. */
.unit-body ul, .unit-body ol {
  margin: 10px 0;
  padding-inline-start: 24px;
}

.unit-body li { margin-block: 4px; line-height: 1.6; }

.unit-body h3 { font-size: 17px; margin: 20px 0 6px; }

.unit-body h4 { font-size: 15px; margin: 16px 0 4px; }

.unit-body h5 { font-size: 14px; margin: 14px 0 4px; }

.unit-body h6 { font-size: 13px; margin: 12px 0 4px; color: var(--text-dim); }

.fmt-h { min-inline-size: 40px; }

/* The editing surface IS the page: contenteditable, styled by the same .unit-body
   rules learners see, with an input-like frame so it still reads as editable. */
.rich-editor {
  min-block-size: 220px;
  max-block-size: 70vh;
  overflow: auto;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  line-height: 1.6;
  cursor: text;
}

.rich-editor:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

.rich-editor > :first-child { margin-block-start: 0; }

/* Where a translation draft came from, shown beside its review heading. */
.review-badge {
  display: inline-block;
  font-size: 11.5px;
  font-weight: 500;
  padding: 2px 8px;
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--text-dim);
  vertical-align: middle;
}

/* Unavailable heading levels: still focusable and announced, visibly set apart.
   NOT opacity — these buttons keep focus and answer when pressed, and opacity fades
   the element's whole rendering INCLUDING its focus ring: at 0.45 the 3px --accent
   outline dropped to about 2.2:1, under the 3:1 a focus indicator owes (1.4.11), and
   the label to about 2.8:1. A dimmer token colour plus a dashed edge says the same
   thing at full strength, and the dashed edge survives forced colours. */
.fmt-toolbar button.is-disabled {
  color: var(--text-dim);
  border-style: dashed;
  cursor: not-allowed;
}

/* The caret's current formatting, mirrored on the toolbar (not colour-only:
   aria-pressed carries it programmatically and the inset border adds shape). */
.fmt-toolbar button[aria-pressed="true"] {
  background: var(--accent-soft);
  border-color: var(--accent);
  box-shadow: inset 0 -2px 0 var(--accent);
}

/* Windows High Contrast replaces background-color and border-color, and computes
   box-shadow to none — all three cues above at once, leaving Bold-on and Bold-off
   identical. Re-state the pressed state in system colours, which forced colours
   keeps, and give unavailable buttons a border STYLE (not a forced property). */
@media (forced-colors: active) {
  .fmt-toolbar button[aria-pressed="true"] {
    forced-color-adjust: none;
    background: Highlight;
    color: HighlightText;
    border: 2px solid HighlightText;
  }
  .fmt-toolbar button.is-disabled {
    border-style: dashed;
  }
}

.unit-body blockquote {
  margin: 14px 0;
  padding-inline-start: 14px;
  border-inline-start: 3px solid var(--accent);
  color: var(--text-dim);
}

.unit-body blockquote p { margin: 0; }

/* Translation workbench: source and target side by side, stacking when narrow. */
.wb-fields { display: flex; flex-direction: column; gap: 16px; margin-block: 12px; }

.wb-label { display: block; font-weight: 600; font-size: 13.5px; margin-block-end: 4px; }

.wb-pair {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 10px;
}

.wb-source {
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px;
  background: var(--surface-alt);
  font-size: 14px;
  line-height: 1.55;
  overflow-wrap: break-word;
}

.wb-pair textarea, .wb-pair input { inline-size: 100%; }

.unit-figure figcaption {
  margin-top: 10px;
  font-size: 12.5px;
  color: var(--text-dim);
  line-height: 1.5;
}

/* WCAG reference */

/* Each control is a label, its field and its hint stacked together, so the hint stays
   with the field it describes when the row wraps. Sizes are in rem/ch so the whole row
   scales with the user's font size instead of ignoring it — a 200% zoom or a larger
   default body size grows these controls too (WCAG 1.4.4). */
.wcag-controls {
  display: flex;
  /* flex-end aligned the BOTTOMS of two stacks of different heights — the search field
     carries a hint underneath and the level field does not — so the two labels sat on
     different lines. Align the tops instead and the labels line up, whatever hangs
     below them. */
  align-items: flex-start;
  gap: 0.75rem;
  row-gap: 0.5rem;
  flex-wrap: wrap;
  margin: 0 0 1.125rem;
  font-size: 0.875rem;
}

/* The level picker holds four short options; it does not need the same width as a
   free-text search box, and taking less leaves the search field room to breathe. */
.wcag-level-field { flex: 0 1 11rem; }

/* Its own line. The count is a running status, not a third field, and sharing a line
   with the search hint made two unrelated things look like one caption. */
.wcag-count { flex: 1 0 100%; margin: 0; }

.wcag-field {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  flex: 1 1 14rem;
  min-inline-size: 0;
}

.wcag-controls select,
.wcag-controls input[type="search"] {
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 0.5rem;
  padding: 0.5em 0.65em;
  font-size: 1em;
  font-family: inherit;
  inline-size: 100%;
  min-inline-size: 0;
}

.wcag-controls .wcag-level-field { flex: 0 1 10rem; }

/* Hint text under a field. --text-dim is 5.15:1 on the lightest surface and 5.92:1 in
   dark, so it clears 4.5:1 as body text everywhere — measured, not assumed. */
.field-hint {
  font-size: 0.8125rem;
  line-height: 1.4;
  color: var(--text-dim);
  margin: 0.25rem 0 0;
}

/* Sizing and colour only — the layout half is with the other .wcag-controls rules. */
.wcag-count { font-size: 0.8125rem; color: var(--text-dim); }

.wcag-guideline { margin: 0 0 18px; }

.wcag-guideline h4 { font-size: 14.5px; margin: 14px 0 4px; }

.sc-card {
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface-alt);
  margin-bottom: 8px;
}

.sc-card summary {
  cursor: pointer;
  padding: 11px 14px;
  font-size: 13.8px;
  font-weight: 600;
  border-radius: 10px;
  list-style-position: inside;
}

.sc-card summary:hover { color: var(--accent); }

.sc-card[open] summary { border-bottom: 1px solid var(--border-subtle); }

.sc-num { color: var(--accent); font-variant-numeric: tabular-nums; }

.level-chip {
  font-size: 10.5px;
  font-weight: 700;
  border-radius: 999px;
  padding: 1px 8px;
  border: 1px solid var(--border);
  vertical-align: middle;
}

.level-chip.level-A { color: var(--info-text); border-color: var(--info-text); }
.level-chip.level-AA { color: var(--accent); border-color: var(--accent); }
.level-chip.level-AAA { color: var(--warn-text); border-color: var(--warn-text); }

.new-chip {
  font-size: 10.5px;
  font-weight: 700;
  border-radius: 999px;
  padding: 1px 8px;
  background: var(--accent);
  color: var(--accent-contrast);
  vertical-align: middle;
}

.sc-body { padding: 12px 14px 14px; font-size: 13.5px; line-height: 1.6; }

.sc-body h5 {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  margin: 14px 0 6px;
  color: var(--text-dim);
}

.sc-body ul { margin: 0; padding-inline-start: 20px; }

.sc-body li { margin-bottom: 5px; }

.fail-list li::marker { color: var(--wrong); }

.sc-sub { color: var(--text); margin: 8px 0; }

/* Quoted W3C normative text. It has to read as somebody else's words, not ours, so it
   gets a rule, a tint and an attribution caption rather than sitting in the body prose.
   The quotation itself stays on --text (it is a legal requirement, not an aside); only
   the caption is dimmed, matching .unit-figure figcaption. */

.wcag-normative {
  margin: 0 0 12px;
  padding: 10px 12px;
  border-inline-start: 3px solid var(--accent);
  border-radius: 0 8px 8px 0;
  background: var(--surface-alt);
}

.wcag-normative figcaption {
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--text-dim);
  margin-block-end: 6px;
}

.wcag-normative blockquote { margin: 0; }

.wcag-normative blockquote p {
  margin: 0;
  color: var(--text);
  font-size: 13.5px;
  line-height: 1.6;
}

/* The W3C Document Licence conditions its permission on this notice travelling with the
   text, so the footer renders on every state of the view — filtered, searched or empty. */

.wcag-attribution {
  margin-block-start: 24px;
  padding-block-start: 14px;
  border-block-start: 1px solid var(--border-subtle);
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--text-dim);
}

.wcag-attribution h3 {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  margin: 0 0 8px;
  color: var(--text-dim);
}

.wcag-attribution p { margin: 0 0 8px; }

.wcag-attribution p:last-child { margin-block-end: 0; }

/* There is no global `a` colour in this stylesheet, so an unstyled link here falls back
   to the UA default blue — which is 2.2:1 on the dark surface and unreadable. :visited is
   pinned to the same value for the same reason. The underline stays: colour is not the
   only thing marking these as links (1.4.1). */

.wcag-attribution a,
.wcag-attribution a:visited {
  color: var(--accent);
  font-weight: 600;
  text-decoration: underline;
}

/* Projects & path */

.step-num {
  flex-shrink: 0;
  min-width: 26px;
  font-size: 11px;
  font-weight: 700;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}

/* "Ahead of your path" rows. Dim the ○ glyph only — it is aria-hidden decoration.
   This was opacity: 0.85 on the whole button, which is the trap documented on
   .fmt-toolbar button.is-disabled: it faded the border to 2.88:1 against its own
   fill (1.4.11 wants 3:1), the .ahead-note / .unit-count text to 3.76:1 in the light
   theme (1.4.3 wants 4.5:1), and the focus ring with them — on a button that is
   fully enabled. The row is already marked ahead by the glyph and by the visible,
   translated "· ahead of your path" words, so the fade was carrying nothing. */
.unit-row.ahead .unit-status { opacity: 0.75; }

/* Real text rendered by views-class.js (translated via t()), not CSS-generated
   content — generated content can't follow the interface language. */
.unit-row.ahead .ahead-note {
  font-size: 11px;
  color: var(--text-dim);
  font-style: italic;
}

.part-prereq {
  font-size: 12px;
  color: var(--text-dim);
  font-style: italic;
  margin: -6px 0 12px;
}

.project-milestone {
  margin-top: 4px;
  border-style: dashed;
  border-color: var(--accent);
  width: 100%;
}

.project-milestone.done { border-style: solid; }

.proj-meta {
  display: block;
  font-size: 11.5px;
  color: var(--text-dim);
  font-weight: 400;
}

.paste-wrap { margin: 16px 0 0; }

.paste-wrap label {
  display: block;
  font-size: 13.5px;
  font-weight: 600;
  margin-bottom: 6px;
}

.paste-wrap textarea {
  width: 100%;
  box-sizing: border-box;
  background: var(--code-bg);
  color: var(--code-text);
  /* WCAG 1.4.11: the field boundary needs 3:1 against the card behind it. */
  border: 1px solid var(--field-border);
  border-radius: 10px;
  padding: 12px;
  font-family: "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
  font-size: 13px;
  line-height: 1.5;
  resize: vertical;
}

.paste-note { font-size: 12px; color: var(--text-dim); margin: 6px 0 0; }

.rubric-fieldset {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 14px;
  margin: 16px 0 0;
}

.rubric-fieldset legend { font-size: 13.5px; font-weight: 700; padding: 0 6px; }

.rubric-item { margin-bottom: 12px; }

.rubric-item label { font-size: 13.8px; display: flex; gap: 8px; align-items: flex-start; }

.rubric-item input[type="checkbox"] { width: 18px; height: 18px; flex-shrink: 0; margin-top: 1px; }

.rubric-explain { font-size: 12.5px; color: var(--text-dim); margin: 4px 0 0 26px; }

.feedback-heading { font-size: 15px; margin: 20px 0 10px; }

.feedback-list { list-style: none; margin: 0; padding: 0; }

.feedback-item {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
  margin-bottom: 8px;
  font-size: 13.5px;
}

.feedback-item.pass { border-color: var(--correct); }

.feedback-item.pass > span[aria-hidden] { color: var(--correct); font-weight: 700; }

.feedback-item.fail { border-color: var(--wrong); background: var(--wrong-bg); }

.feedback-item.fail > span[aria-hidden] { color: var(--wrong); font-weight: 700; }

.feedback-explain { font-size: 12.5px; color: var(--text); margin: 4px 0 0; }

/* Mastery */

.mastery-chip {
  display: inline-block;
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  border-radius: 999px;
  padding: 2px 9px;
  border: 1px solid var(--border);
  color: var(--text-dim);
}

/* Theme-aware: the previous hard-coded hexes failed 4.5:1 — blue in dark, amber in light. */
.mastery-chip.learning { color: var(--info-text); border-color: var(--info-text); }
.mastery-chip.practised { color: var(--warn-text); border-color: var(--warn-text); }

.mastery-chip.mastered {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-contrast);
}

.topic-mastery-line { margin: 0 0 8px; font-size: 12.5px; }

.mastery-gaps { color: var(--text-dim); font-size: 12px; }

.challenge-row {
  margin-top: 4px;
  border-style: dashed;
  border-color: var(--accent);
  width: 100%;
}

.challenge-row.done { border-style: solid; }

.challenge-row:disabled {
  cursor: not-allowed;
  border-style: dotted;
  border-color: var(--border);
  color: var(--text-dim);
}

.challenge-row:disabled:hover { border-color: var(--border); }

.unit-time { font-size: 12.5px; color: var(--text-dim); margin: 4px 0 0; }

.exemplar {
  margin-top: 20px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface-alt);
}

.exemplar summary {
  cursor: pointer;
  padding: 11px 14px;
  font-size: 13.5px;
  font-weight: 600;
  border-radius: 10px;
}

.exemplar summary:hover { color: var(--accent); }

.exemplar[open] summary { border-bottom: 1px solid var(--border-subtle); }

.exemplar > *:not(summary) { margin-inline-start: 14px; margin-inline-end: 14px; }

/* ---------- profile: the numbers, as a description list ---------- */

/* One <div> per term/definition pair inside the <dl> — valid HTML5, and the only
   way to keep a label welded to its value while the list wraps. A grid of pairs,
   not a table: there is one column of data, so a table would be a lie. */
.profile-stats {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 22px;
  margin: 10px 0 14px;
}

.profile-stats > div {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-inline-size: 0;
}

.profile-stats dt {
  font-size: 11.5px;
  color: var(--text-dim);
}

.profile-stats dd {
  margin: 0;
  font-size: 16px;
  font-weight: 700;
}

/* "12 / 246" inside an RTL page reorders to "246 / 12" without isolation, and the
   digits jump about between renders without tabular figures. */
.profile-stat-value {
  unicode-bidi: isolate;
  font-variant-numeric: tabular-nums;
}

/* At 320px the pairs stack rather than squeezing a label onto two lines. */
@media (max-width: 380px) {
  .profile-stats { gap: 10px 14px; }
  .profile-stats > div { flex-basis: 100%; }
}

/* Import / export */

.io-block {
  border-top: 1px solid var(--border-subtle);
  padding-top: 16px;
  margin-top: 20px;
}

.io-block h3 { font-size: 16px; margin: 0 0 4px; }

.io-fieldset {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px 14px;
  margin: 12px 0;
}

.io-fieldset legend { font-size: 13px; font-weight: 700; padding: 0 6px; }

.io-fieldset label {
  display: flex;
  gap: 8px;
  align-items: center;
  font-size: 13.5px;
  padding: 4px 0;
}

.io-fieldset input[type="checkbox"] { width: 18px; height: 18px; flex-shrink: 0; }

.io-row { margin: 12px 0; }

.io-row label {
  display: block;
  font-size: 13.5px;
  font-weight: 600;
  margin-bottom: 6px;
}

.io-row select,
.io-row input[type="file"] {
  width: 100%;
  box-sizing: border-box;
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--field-border);
  border-radius: 8px;
  padding: 8px 10px;
  font-size: 13.5px;
  font-family: inherit;
}

.io-status { font-size: 13.5px; margin: 10px 0 0; }
.io-status.good { color: var(--correct); font-weight: 600; }
.io-status.bad { color: var(--wrong); font-weight: 600; }

.io-errors {
  margin: 8px 0;
  padding-inline-start: 20px;
  font-size: 13px;
  color: var(--wrong);
}

.io-warnings { margin: 8px 0; font-size: 13px; }
.io-warnings summary { cursor: pointer; font-weight: 600; color: var(--warn-text); }
.io-warnings ul { padding-inline-start: 20px; margin: 6px 0; }

.io-diff {
  list-style: none;
  margin: 8px 0;
  padding: 0;
  font-size: 13.3px;
}

.io-diff li {
  padding: 7px 10px;
  border-radius: 8px;
  border: 1px solid var(--border);
  margin-bottom: 5px;
  border-inline-start-width: 4px;
}

.io-diff li.io-new { border-inline-start-color: var(--correct); }
.io-diff li.io-replace { border-inline-start-color: var(--warn-text); }

#import-report h4 { font-size: 14.5px; margin: 16px 0 4px; }
#import-report h5 { font-size: 13px; text-transform: uppercase; letter-spacing: 0.03em; color: var(--text-dim); margin: 14px 0 6px; }

.custom-banner {
  border: 1px solid var(--accent);
  border-radius: 10px;
  background: var(--surface-alt);
  padding: 12px 14px;
  margin: 0 0 16px;
  font-size: 13.5px;
}

.custom-banner .btn { margin-inline-start: 8px; }

/* Visible "course content is still in English" note on Home — the SR announcement
   at language-switch time gets a persistent sighted-user counterpart. */
.content-lang-note {
  border: 1px solid var(--border);
  border-inline-start: 3px solid var(--accent);
  border-radius: 10px;
  background: var(--surface-alt);
  color: var(--text);
  padding: 10px 14px;
  margin: 0 0 16px;
  font-size: 13.5px;
}

/* Pages within a unit, and authoring */

.page-indicator {
  font-size: 12.5px;
  color: var(--text-dim);
  font-weight: 600;
  margin: 0 0 10px;
}

.page-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: 18px 0 6px;
  padding-top: 14px;
  border-top: 1px solid var(--border-subtle);
}

.page-nav .btn:disabled { opacity: 0.5; cursor: not-allowed; }
.page-nav .btn:disabled:hover { border-color: var(--border); }

.page-dots { display: flex; gap: 6px; }

.page-dot {
  width: 8px;
  height: 8px;
  border-radius: 999px;
  background: var(--border);
  display: inline-block;
}

.page-dot.on { background: var(--accent); }

.author-row { margin-top: 14px; text-align: end; }

.io-row textarea {
  width: 100%;
  box-sizing: border-box;
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--field-border);
  border-radius: 8px;
  padding: 10px 12px;
  font-size: 13.5px;
  line-height: 1.55;
  font-family: inherit;
  resize: vertical;
}

.io-row #a-code, .io-row #doc-paste {
  font-family: "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
  font-size: 13px;
}

.io-row input[type="text"] {
  width: 100%;
  box-sizing: border-box;
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--field-border);
  border-radius: 8px;
  padding: 9px 12px;
  font-size: 13.5px;
  font-family: inherit;
}

.io-fieldset input[type="radio"] { width: 18px; height: 18px; flex-shrink: 0; }

.author-preview-box {
  border: 1px dashed var(--accent);
  border-radius: 10px;
  padding: 14px;
  margin-top: 8px;
  background: var(--surface-alt);
}

/* Drill view */

.drill-card {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 22px 22px 20px;
  box-shadow: var(--shadow);
}

.drill-top-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 14px;
  gap: 10px;
  flex-wrap: wrap;
}

.progress-track {
  height: 6px;
  border-radius: 999px;
  background: var(--surface-alt);
  overflow: hidden;
  margin-bottom: 18px;
}

.progress-track > div {
  height: 100%;
  background: var(--accent);
  transition: width 0.25s ease;
}

.type-badge {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--text-dim);
  background: var(--surface-alt);
  border: 1px solid var(--border);
  padding: 2px 8px;
  border-radius: 999px;
}

.question-prompt {
  font-size: 16.5px;
  font-weight: 600;
  line-height: 1.45;
  margin: 4px 0 14px;
}

pre.code-block {
  background: var(--code-bg);
  color: var(--code-text);
  border-radius: 10px;
  padding: 14px 16px;
  overflow-x: auto;
  font-size: 13px;
  line-height: 1.55;
  margin: 0 0 16px;
  font-family: "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
}

.options {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 14px;
}

.option-btn {
  text-align: start;
  border: 1px solid var(--border);
  background: var(--surface-alt);
  color: var(--text);
  border-radius: 10px;
  padding: 11px 14px;
  font-size: 13.8px;
  cursor: pointer;
  display: flex;
  gap: 10px;
  align-items: flex-start;
  line-height: 1.4;
  transition: border-color 0.1s ease, background 0.1s ease;
}

.option-btn:hover:not(:disabled) { border-color: var(--accent); }

.option-btn .key-hint {
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  border-radius: 6px;
  background: var(--surface);
  border: 1px solid var(--border);
  font-size: 11px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-dim);
}

.option-btn code {
  font-family: "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
  font-size: 12.8px;
}

.option-btn:disabled { cursor: default; }

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

.option-btn.wrong {
  border-color: var(--wrong);
  background: var(--wrong-bg);
  color: var(--wrong);
}

.option-btn.reveal-correct-muted {
  border-color: var(--correct);
}

/* The options you did NOT pick, once the question is answered. They stay readable —
   comparing your answer against the others is the whole point of the reveal.
   NOT opacity: `opacity` composites the element's entire rendering, so 0.65 dragged
   the 1px --border down to 2.15:1 against its own fill (1.4.11 wants 3:1) and faded
   the focus ring to 3.32:1 — and these rows keep focus, because they are
   aria-disabled, never disabled. A dimmer token colour says "spent" at full
   strength: label 5.15:1, border 3.68:1, ring 6.67:1. Same remedy as
   .fmt-toolbar button.is-disabled above. */
.option-btn[aria-disabled="true"]:not(.correct):not(.wrong) {
  color: var(--text-dim);
  cursor: default;
}

.option-marker {
  font-weight: 700;
  flex-shrink: 0;
}

.question-prompt:focus,
.view-heading:focus {
  outline: none;
}

.lesson-header {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 14px 0 6px;
}

.lesson-header .icon { font-size: 30px; }
.lesson-header h2 { margin: 0; font-size: 21px; letter-spacing: -0.01em; }

.lesson-intro {
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--text-dim);
  border-inline-start: 3px solid var(--accent);
  padding-inline-start: 12px;
  margin: 12px 0 20px;
}

.lesson-section h3 {
  font-size: 16px;
  margin: 26px 0 8px;
  letter-spacing: -0.01em;
}

.lesson-section p {
  font-size: 14px;
  line-height: 1.65;
  margin: 0 0 12px;
}

.lesson-section code,
.lesson-intro code,
.takeaways code,
.explanation code,
.option-btn code,
.question-prompt code {
  background: var(--surface-alt);
  /* Typographic frame around styled text, not a control and not a graphic that
     carries meaning — the characters do that. Stays on the soft hairline. */
  border: 1px solid var(--border-subtle);
  border-radius: 5px;
  padding: 1px 5px;
  font-size: 0.9em;
  font-family: "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
}

pre.code-block code {
  background: none;
  border: none;
  padding: 0;
  font-size: inherit;
}

.code-caption {
  font-size: 12.5px;
  color: var(--text-dim);
  margin: -8px 0 12px;
  font-style: italic;
}

.takeaways {
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 14px 18px;
  margin-top: 26px;
}

.takeaways h3 { font-size: 14px; margin: 0 0 8px; }

.takeaways ul { margin: 0; padding-inline-start: 18px; }

.takeaways li { font-size: 13.5px; line-height: 1.6; margin-bottom: 6px; }

.resources { margin-top: 22px; }

.resources h3 { font-size: 14px; margin: 0 0 8px; }

.resources ul { margin: 0; padding-inline-start: 18px; }

.resources li { font-size: 13.5px; line-height: 1.7; margin-bottom: 6px; }

.resources a { color: var(--accent); font-weight: 600; }

.res-note { color: var(--text-dim); }

.explanation {
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px 14px;
  font-size: 13.3px;
  line-height: 1.55;
  color: var(--text);
  margin-bottom: 16px;
}

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

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

.streak-pill {
  font-size: 12.5px;
  color: var(--text-dim);
}

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

/* Summary view */

.summary-hero {
  text-align: center;
  padding: 18px 10px 6px;
}

.summary-hero .big-score {
  font-size: 42px;
  font-weight: 700;
  color: var(--accent);
  margin: 6px 0 2px;
}

.summary-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
  gap: 10px;
  margin: 18px 0;
}

.summary-stat {
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
  text-align: center;
}

.summary-stat .val { font-size: 19px; font-weight: 700; }
.summary-stat .lbl { font-size: 11.5px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.02em; }

.summary-actions {
  display: flex;
  gap: 10px;
  justify-content: center;
  margin-top: 20px;
  flex-wrap: wrap;
}

.missed-list {
  margin-top: 20px;
  border-top: 1px solid var(--border-subtle);
  padding-top: 14px;
}

.missed-list h3 { font-size: 13.5px; margin: 0 0 10px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.02em; }

.missed-item {
  font-size: 13px;
  padding: 8px 0;
  border-bottom: 1px dashed var(--border-subtle);
}

.missed-item:last-child { border-bottom: none; }

@media (max-width: 480px) {
  .drill-card { padding: 16px 14px; }
  .summary-hero .big-score { font-size: 34px; }
}

/* ---------- content protection ---------- */

.app-dialog {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  padding: 20px 22px;
  max-inline-size: 30rem;
  inline-size: calc(100vw - 32px);
  box-shadow: var(--shadow);
}

.app-dialog::backdrop { background: rgba(10, 16, 24, 0.55); }

.app-dialog h2 { margin: 0 0 10px; font-size: 19px; }
.app-dialog .io-row { margin-block: 12px; }
.app-dialog label { display: block; font-size: 14px; margin-block-end: 4px; }

.app-dialog input[type="password"] {
  inline-size: 100%;
  padding: 9px 10px;
  font: inherit;
  color: var(--text);
  background: var(--surface-alt);
  border: 1px solid var(--field-border);
  border-radius: 8px;
}

.locked-note {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  font-size: 13px;
  color: var(--text-dim);
}

.protect-state {
  margin: 0 0 12px;
  padding: 10px 12px;
  border-radius: 10px;
  font-size: 14px;
  border-inline-start: 4px solid var(--border);
  background: var(--surface-alt);
}

.protect-state.on { border-inline-start-color: var(--correct); }
.protect-state.off { border-inline-start-color: var(--warn-text); }

.overwrite-warn {
  margin-block: 14px;
  padding: 12px 14px;
  border: 1px solid var(--warn-text);
  border-inline-start-width: 4px;
  border-radius: 10px;
  background: var(--surface-alt);
}

.overwrite-warn h5 { margin: 0 0 8px; font-size: 14.5px; color: var(--warn-text); }
.overwrite-warn ul { margin: 0 0 10px; padding-inline-start: 20px; font-size: 13.5px; }
.overwrite-warn p { margin: 0 0 10px; font-size: 13.5px; color: var(--text-dim); }

.overwrite-ack {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  font-size: 14px;
  font-weight: 600;
}

/* ---------- on-device translation of your own content ---------- */

.autotr-list {
  list-style: none;
  margin: 12px 0;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
}

.autotr-list li {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 9px 12px;
  border-block-end: 1px solid var(--border-subtle);
  background: var(--surface);
}

.autotr-list li:last-child { border-block-end: none; }

.autotr-name { font-weight: 600; min-inline-size: 7rem; }

.autotr-state {
  flex: 1 1 8rem;
  font-size: 13px;
  color: var(--text-dim);
}

.autotr-state.on { color: var(--correct); font-weight: 600; }
.autotr-state.off { color: var(--text-dim); font-style: italic; }

.warn-note { color: var(--text-dim); }

@media (max-width: 480px) {
  .autotr-name { min-inline-size: 0; }
  .autotr-list li { align-items: flex-start; }
}

/* ---------- organisation logo ---------- */

.brand {
  display: flex;
  align-items: center;
  gap: 14px;
  min-inline-size: 0;
}

.app-logo {
  block-size: 44px;
  inline-size: auto;
  max-inline-size: 180px;
  object-fit: contain;
  flex: none;
}

@media (max-width: 480px) {
  .app-logo { block-size: 34px; max-inline-size: 120px; }
}

.logo-preview {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-block: 10px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface-alt);
}

.logo-preview img {
  block-size: 40px;
  inline-size: auto;
  max-inline-size: 160px;
  object-fit: contain;
}

/* ---------- translation review ---------- */

.review-item {
  margin-block: 16px;
  padding: 14px 16px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
}

.review-item h3 { margin: 0 0 10px; font-size: 15px; }
.review-item dl { margin: 0; }

.review-pair {
  display: grid;
  gap: 4px;
  margin-block-end: 12px;
  padding-block-end: 12px;
  border-block-end: 1px dashed var(--border-subtle);
}

.review-pair:last-child { border-block-end: none; margin-block-end: 0; padding-block-end: 0; }

.review-pair dt {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-dim);
}

.review-pair dd { margin: 0; font-size: 14.5px; line-height: 1.5; }

/* The English sits back so the eye lands on what needs judging. */
.review-en { color: var(--text-dim); }
.review-tr { color: var(--text); }

/* Two columns once there is room to compare them side by side. */
@media (min-width: 760px) {
  .review-pair { grid-template-columns: 1fr 1fr; }
  .review-pair dt { grid-column: 1 / -1; }
}

/* ---------- skip link ---------- */

.skip-link {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  z-index: 100;
  padding: 10px 16px;
  background: var(--accent);
  color: var(--accent-contrast);
  font-weight: 600;
  text-decoration: none;
  border-end-end-radius: 10px;
  /* Off-screen until focused, but never display:none — it must stay in the tab order. */
  transform: translateY(-120%);
  transition: transform 120ms ease-out;
}

.skip-link:focus {
  transform: translateY(0);
  outline: 3px solid var(--text);
  outline-offset: 2px;
}

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