/* =====================================================================
   ديوان التلاوة -- design system (2026-08-13 design/UX round)

   Direction: an elegant warm mushaf-inspired identity. Ivory/cream
   surfaces, deep ink text, ONE dignified accent -- deep mushaf green,
   chosen over warm gold to read as calm and authoritative rather than
   decorative (gold stays reserved for the one traditional touch: the
   ﴿ ﴾ ayah-end medallion glyph, which was already gold-ish and is left
   alone on purpose -- an ornamental convention, not a second competing
   interactive accent). Dark mode is a full second pass below (prefers-
   color-scheme, PLUS a `data-theme` attribute override -- see the
   in-app theme toggle further down -- so it's reachable even on a
   system that's never going to report prefers-color-scheme:dark itself), same
   token names, never an afterthought grey-on-black. Every new motion
   added this round is a fade/settle, <=200ms, no bounce/overshoot --
   see the transition additions further down.
   ===================================================================== */
:root {
  color-scheme: light;

  --bg: #f7f2e6;
  --bg-soft: #f1ead9;
  --card-bg: #fffdf8;
  --card-bg-raised: #ffffff;
  --ink: #241f19;
  --ink-soft: #6d6252;
  --ink-faint: #9a8f7a;
  --border: #e7dcc4;
  --border-soft: #efe6d3;

  /* the one committed accent -- deep mushaf green */
  --accent: #1f6b46;
  --accent-strong: #164f34;
  --accent-tint: #e6f1ea;
  --accent-ink: #ffffff;

  --ok: #1f8a52;
  --ok-bg: #e6f5ec;
  --sub: #b3453a;
  --sub-bg: #fbeae7;
  --miss: #b07a1f;
  --miss-bg: #faf0da;
  --extra: #6d6252;
  --extra-bg: #efe7d6;
  --record: #ac3a35;
  --unchecked: #8c8171;
  --unchecked-bg: #ece4d4;
  --close: #6a55a8;
  --close-bg: #eee9fa;
  --tashkeel: #8a6a12;
  --tashkeel-bg: #f5ecd0;
  --stale: #ac3a35;
  --slow: #b3453a;

  --shadow-sm: 0 1px 2px rgba(36, 31, 25, 0.05);
  --shadow-md: 0 10px 28px rgba(36, 31, 25, 0.08);
  --shadow-lg: 0 18px 44px rgba(36, 31, 25, 0.12);
  --radius-sm: 0.55rem;
  --radius-md: 0.9rem;
  --radius-lg: 1.25rem;

  --ease: cubic-bezier(0.4, 0, 0.2, 1);
  --motion-fast: 130ms;
  --motion-base: 190ms;

  /* Quran-text font size control (feature: A-/A+, persisted in
     localStorage -- see static/app.js's FONT_SCALES/applyFontScale).
     Every Quran-text rule below multiplies its base size by this
     instead of a fixed rem, so one control resizes every reading
     surface (المصحف/آية بآية/آية عشوائية/تسميع/آية واحدة) together. */
  --quran-scale: 1;
}

/* ---------------------------------------------------------------------
   Dark mode -- equal care, not an inversion filter. Warm near-black
   (never blue-black) to stay in the same ivory/ink family as light
   mode, just flipped; the green accent is lifted for AA contrast on
   dark surfaces. This prefers-color-scheme block is what "تلقائي"
   (auto) falls back to; the explicit `:root[data-theme]` override
   blocks right below this one are what make داكن/فاتح reachable
   regardless of the system's own scheme -- see that section's comment.
   --------------------------------------------------------------------- */
@media (prefers-color-scheme: dark) {
  :root {
    color-scheme: dark;

    --bg: #17140f;
    --bg-soft: #1d1a14;
    --card-bg: #211d17;
    --card-bg-raised: #27221b;
    --ink: #f2ecdd;
    --ink-soft: #b7ab93;
    --ink-faint: #8a7f6a;
    --border: #362f24;
    --border-soft: #2c2620;

    --accent: #4fae7c;
    --accent-strong: #6cc794;
    --accent-tint: rgba(79, 174, 124, 0.16);
    --accent-ink: #10241a;

    --ok: #4fae7c;
    --ok-bg: rgba(79, 174, 124, 0.15);
    --sub: #e08072;
    --sub-bg: rgba(224, 128, 114, 0.15);
    --miss: #e0b262;
    --miss-bg: rgba(224, 178, 98, 0.15);
    --extra: #b7ab93;
    --extra-bg: rgba(183, 171, 147, 0.13);
    --record: #e07264;
    --unchecked: #a4977e;
    --unchecked-bg: rgba(164, 151, 126, 0.15);
    --close: #ab94e6;
    --close-bg: rgba(171, 148, 230, 0.15);
    --tashkeel: #d1a83d;
    --tashkeel-bg: rgba(209, 168, 61, 0.15);
    --stale: #e07264;
    --slow: #e08072;

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
    --shadow-md: 0 10px 28px rgba(0, 0, 0, 0.35);
    --shadow-lg: 0 18px 44px rgba(0, 0, 0, 0.45);
  }
}

/* ---------------------------------------------------------------------
   In-app theme toggle (2026-08-13, coordinator addition -- Ahmed's own
   system is light, so a prefers-color-scheme-only dark mode was simply
   unreachable for him). داكن / فاتح / تلقائي, see static/app.js's
   applyTheme/setTheme -- sets/clears a `data-theme` attribute on <html>.
   These two blocks are DUPLICATES of the default :root block and the
   dark media-query block above on purpose, not a refactor of them:
   an attribute selector on :root (`:root[data-theme="dark"]`) has
   strictly higher specificity than a bare `:root` selector regardless
   of which media query it sits inside, so whichever of these two blocks
   matches the current `data-theme` value wins over prefers-color-scheme
   in BOTH directions (forces dark on a light system, forces light on a
   dark system) -- "تلقائي" (auto) is simply the attribute being absent,
   at which point the plain prefers-color-scheme blocks above resume
   deciding, unchanged. A tiny inline script in index.html's <head> sets
   the attribute before first paint (default "dark", per Ahmed's
   explicit request) so there's no flash of the wrong theme on load.
   --------------------------------------------------------------------- */
:root[data-theme="light"] {
  color-scheme: light;

  --bg: #f7f2e6;
  --bg-soft: #f1ead9;
  --card-bg: #fffdf8;
  --card-bg-raised: #ffffff;
  --ink: #241f19;
  --ink-soft: #6d6252;
  --ink-faint: #9a8f7a;
  --border: #e7dcc4;
  --border-soft: #efe6d3;

  --accent: #1f6b46;
  --accent-strong: #164f34;
  --accent-tint: #e6f1ea;
  --accent-ink: #ffffff;

  --ok: #1f8a52;
  --ok-bg: #e6f5ec;
  --sub: #b3453a;
  --sub-bg: #fbeae7;
  --miss: #b07a1f;
  --miss-bg: #faf0da;
  --extra: #6d6252;
  --extra-bg: #efe7d6;
  --record: #ac3a35;
  --unchecked: #8c8171;
  --unchecked-bg: #ece4d4;
  --close: #6a55a8;
  --close-bg: #eee9fa;
  --tashkeel: #8a6a12;
  --tashkeel-bg: #f5ecd0;
  --stale: #ac3a35;
  --slow: #b3453a;

  --shadow-sm: 0 1px 2px rgba(36, 31, 25, 0.05);
  --shadow-md: 0 10px 28px rgba(36, 31, 25, 0.08);
  --shadow-lg: 0 18px 44px rgba(36, 31, 25, 0.12);
}

:root[data-theme="dark"] {
  color-scheme: dark;

  --bg: #17140f;
  --bg-soft: #1d1a14;
  --card-bg: #211d17;
  --card-bg-raised: #27221b;
  --ink: #f2ecdd;
  --ink-soft: #b7ab93;
  --ink-faint: #8a7f6a;
  --border: #362f24;
  --border-soft: #2c2620;

  --accent: #4fae7c;
  --accent-strong: #6cc794;
  --accent-tint: rgba(79, 174, 124, 0.16);
  --accent-ink: #10241a;

  --ok: #4fae7c;
  --ok-bg: rgba(79, 174, 124, 0.15);
  --sub: #e08072;
  --sub-bg: rgba(224, 128, 114, 0.15);
  --miss: #e0b262;
  --miss-bg: rgba(224, 178, 98, 0.15);
  --extra: #b7ab93;
  --extra-bg: rgba(183, 171, 147, 0.13);
  --record: #e07264;
  --unchecked: #a4977e;
  --unchecked-bg: rgba(164, 151, 126, 0.15);
  --close: #ab94e6;
  --close-bg: rgba(171, 148, 230, 0.15);
  --tashkeel: #d1a83d;
  --tashkeel-bg: rgba(209, 168, 61, 0.15);
  --stale: #e07264;
  --slow: #e08072;

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
  --shadow-md: 0 10px 28px rgba(0, 0, 0, 0.35);
  --shadow-lg: 0 18px 44px rgba(0, 0, 0, 0.45);
}

/* Theme-switcher pills (reuses .mode-switcher/.mode-btn's own pill
   styling verbatim, same as the strictness/view-mode switchers) --
   only its own container needs a rule at all, for topbar placement. */
.theme-switcher { flex-wrap: nowrap; }

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: "IBM Plex Sans Arabic", "SF Arabic", "Geeza Pro", "Traditional Arabic", -apple-system, sans-serif;
  min-height: 100vh;
  padding-bottom: 3rem;
  transition: background var(--motion-base) var(--ease), color var(--motion-base) var(--ease);
}

/* Stale-server guard banner (2026-08-12 field failure fix). Shown
   whenever /api/version reports the running backend process is out of
   sync with what's currently on disk, or when the version check itself
   fails to reach the server at all -- see static/app.js checkServerVersion.
   Sticky at the very top, above everything, on purpose: this is the one
   condition where "keep going anyway" produces untrustworthy results. */
.stale-banner {
  position: sticky;
  top: 0;
  z-index: 20;
  background: var(--stale);
  color: #fff;
  text-align: center;
  font-weight: 700;
  font-size: 0.85rem;
  padding: 0.6rem 1rem;
}

/* First-launch identity + consent screen (2026-08-14 friends-and-family
   round). Full-screen overlay -- shown before anything else on a
   device/browser profile with no saved tester name, blocking the app
   underneath until a name is entered (consent itself is optional, see
   index.html's comment). Same card/accent language as the rest of the
   app, just centered and modal. */
.identity-gate {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  background: var(--bg);
}

.identity-card {
  width: 100%;
  max-width: 420px;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: 1.75rem 1.5rem;
  text-align: center;
}

.identity-title {
  font-size: 1.3rem;
  font-weight: 700;
  color: var(--ink);
  margin: 0 0 0.5rem;
}

.identity-copy {
  font-size: 0.92rem;
  color: var(--ink-soft);
  margin: 0 0 1.25rem;
  line-height: 1.6;
}

.identity-label {
  display: block;
  text-align: start;
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--ink-soft);
  margin-bottom: 0.4rem;
}

.identity-name-input {
  width: 100%;
  box-sizing: border-box;
  padding: 0.7rem 0.9rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--ink);
  font-family: inherit;
  font-size: 1rem;
  text-align: center;
}

.identity-name-input:focus {
  outline: none;
  border-color: var(--accent);
}

.identity-error {
  color: var(--sub);
  font-size: 0.8rem;
  margin-top: 0.5rem;
}

.identity-consent-row {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  text-align: start;
  margin: 1.25rem 0 0.5rem;
  font-size: 0.85rem;
  color: var(--ink);
  line-height: 1.5;
  cursor: pointer;
}

.identity-consent-row input[type="checkbox"] {
  margin-top: 0.2rem;
  width: 1.1rem;
  height: 1.1rem;
  accent-color: var(--accent);
  flex-shrink: 0;
}

.identity-consent-note {
  font-size: 0.78rem;
  color: var(--ink-faint);
  margin: 0 0 1.4rem;
  text-align: start;
  line-height: 1.5;
}

.identity-continue-btn {
  width: 100%;
  padding: 0.75rem 1rem;
  border-radius: var(--radius-sm);
  border: none;
  background: var(--accent);
  color: var(--accent-ink);
  font-family: inherit;
  font-size: 1rem;
  font-weight: 700;
  cursor: pointer;
  transition: opacity var(--motion-fast) var(--ease);
}

.identity-continue-btn:hover { opacity: 0.9; }

/* Explicit mic-permission preflight (2026-08-12 iPhone field report):
   shown until mic access is confirmed. Sticky-ish prominence (not
   position:sticky like the stale banner -- it shouldn't follow scroll,
   just be unmissable at the top of the page on load) with its own
   accent color (distinct from the red stale-banner/error tones -- this
   isn't an error state, it's an expected first-run step). */
.mic-gate {
  background: var(--bg-soft);
  border-bottom: 1px solid var(--border);
  padding: 1rem 1.25rem;
  text-align: center;
  transition: background var(--motion-base) var(--ease), border-color var(--motion-base) var(--ease);
}

.mic-gate-title {
  font-weight: 700;
  font-size: 1rem;
  color: var(--accent);
  margin-bottom: 0.25rem;
}

.mic-gate-message {
  font-size: 0.85rem;
  color: var(--ink-soft);
  margin-bottom: 0.6rem;
}

.mic-gate-link {
  display: inline-block;
  direction: ltr;
  font-size: 0.9rem;
  font-weight: 700;
  color: var(--accent);
  text-decoration: underline;
  word-break: break-all;
}

.mic-gate-button {
  padding: 0.55rem 1.4rem;
  border-radius: 0.6rem;
  border: none;
  background: var(--accent);
  color: #fff;
  font-family: inherit;
  font-size: 0.9rem;
  font-weight: 700;
  cursor: pointer;
}

.mic-gate-button:hover { opacity: 0.9; }
.mic-gate-button:disabled { opacity: 0.6; cursor: not-allowed; }

/* Small inline reason under a disabled record button (one per tab) --
   see static/app.js's MIC_GATE_IDS. Separate from .mic-error (transient
   upload/decode errors) so the two never fight over the same element:
   this one is persistent as long as mic access isn't granted. */
.mic-gate-reason {
  color: var(--ink-soft);
  font-size: 0.8rem;
  text-align: center;
  max-width: 480px;
}

/* Honest-failure conclusion (2026-08-12 field failure fix): shown
   INSTEAD OF a percentage whenever a session/attempt didn't actually
   gather enough real evidence to trust one -- see backend/policy.py's
   evaluate_honesty / evaluate_single_honesty. */
.inaudible-message {
  color: var(--unchecked);
  background: var(--unchecked-bg);
  border: 1px solid var(--border);
  border-radius: 0.75rem;
  padding: 1rem 1.25rem;
  text-align: center;
  font-weight: 700;
  font-size: 1.05rem;
  margin-bottom: 1rem;
}

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.75rem;
  padding: 1rem 1.25rem;
  background: var(--card-bg);
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: 10;
  transition: background var(--motion-base) var(--ease), border-color var(--motion-base) var(--ease);
}

.topbar h1 {
  font-size: 1.2rem;
  margin: 0;
  font-weight: 700;
  color: var(--accent);
  letter-spacing: 0.01em;
}

.topbar-controls {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}

.stats {
  display: flex;
  gap: 1.25rem;
}

/* ---------------- font-size stepper (feature: A-/A+, topbar-global) ----------------
   Resizes every Quran-text reading surface at once via --quran-scale
   (see loadFontScale/applyFontScale in app.js). Accessibility control
   for older readers, per the design brief -- kept small and out of the
   way in the topbar rather than duplicated per tab. */
.font-stepper {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  background: var(--bg-soft);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.2rem;
}

.font-stepper-btn {
  width: 1.7rem;
  height: 1.7rem;
  border-radius: 50%;
  border: none;
  background: transparent;
  color: var(--ink-soft);
  font-family: inherit;
  font-weight: 700;
  font-size: 0.85rem;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background var(--motion-fast) var(--ease), color var(--motion-fast) var(--ease);
}

.font-stepper-btn:hover:not(:disabled) { background: var(--card-bg); color: var(--accent); }
.font-stepper-btn:disabled { opacity: 0.35; cursor: not-allowed; }
.font-stepper-btn.dec { font-size: 0.7rem; }
.font-stepper-btn.inc { font-size: 1rem; }

.font-stepper-label {
  font-size: 0.68rem;
  color: var(--ink-soft);
  min-width: 1.6rem;
  text-align: center;
  direction: ltr;
}

/* Narrow-viewport topbar (390px verification pass): flex-wrap on a
   nested flex row doesn't reliably stack BEFORE overflowing the
   viewport once the widest single child (.stats) still exceeds the
   remaining row width -- explicit stacking is more robust than relying
   on wrap heuristics here. Title gets its own row; the controls row
   (stats/reciter/font-stepper) wraps freely underneath, each control
   free to break onto its own line without ever pushing the page wider
   than the viewport (the actual bug: content silently overflowing to
   the left/right in an RTL layout is very easy to miss visually). */
@media (max-width: 640px) {
  .topbar { flex-direction: column; align-items: stretch; gap: 0.6rem; }
  .topbar h1 { text-align: center; font-size: 1.05rem; }
  .topbar-controls { justify-content: center; gap: 0.6rem; }
  .stats { gap: 0.85rem; }
}

/* ---------------- reciter selector (feature: اسمع القارئ, topbar-global) ---------------- */
.reciter-picker {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.75rem;
  color: var(--ink-soft);
}

.reciter-picker select {
  padding: 0.3rem 0.5rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--bg-soft);
  color: var(--ink);
  font-family: inherit;
  font-size: 0.75rem;
  cursor: pointer;
}

.stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 3.5rem;
}

.stat-value {
  font-size: 1.1rem;
  font-weight: 700;
  color: var(--ink);
}

.stat-label {
  font-size: 0.7rem;
  color: var(--ink-soft);
}

main {
  max-width: 760px;
  margin: 0 auto;
  padding: 1.25rem;
}

.picker {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 1rem;
}

/* 390px verification pass finding: .picker's children (passage picker +
   two nowrap diagnostics-toggle labels) fit their OWN min-content widths
   fine individually, but flex-wrap alone left them cramming onto one
   overflowing row rather than actually breaking -- same root cause as
   .tabs above (a flex item's default min-width:auto resists shrinking
   below its content, so "wrap" only helps once a row is already at
   capacity, and three wide-ish items rarely hit that boundary cleanly).
   Explicit full-width stacking removes the ambiguity at phone widths. */
@media (max-width: 480px) {
  .picker, .mode-switcher-row {
    flex-direction: column;
    align-items: stretch;
  }
  .mode-switcher-row .mode-switcher { justify-content: center; }
}

#passageSelect {
  flex: 1 1 220px;
  padding: 0.55rem 0.75rem;
  font-size: 1rem;
  border-radius: 0.6rem;
  border: 1px solid var(--border);
  background: var(--card-bg);
  color: var(--ink);
  font-family: inherit;
}

.ayah-nav {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.nav-btn {
  padding: 0.5rem 0.8rem;
  border-radius: 0.6rem;
  border: 1px solid var(--border);
  background: var(--card-bg);
  color: var(--ink);
  font-family: inherit;
  font-size: 0.9rem;
  cursor: pointer;
}

.nav-btn:hover { background: var(--bg-soft); }
.nav-btn:disabled { opacity: 0.4; cursor: not-allowed; }

.ayah-position {
  font-size: 0.85rem;
  color: var(--ink-soft);
  min-width: 3.5rem;
  text-align: center;
}

.ayah-card {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 2rem 1.5rem;
  text-align: center;
  box-shadow: var(--shadow-sm);
  transition: background var(--motion-base) var(--ease), border-color var(--motion-base) var(--ease);
}

.ayah-key {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  font-size: 0.8rem;
  color: var(--ink-soft);
  margin-bottom: 0.75rem;
  letter-spacing: 0.02em;
}

.ayah-text {
  font-family: "KFGQPC HAFS Uthmanic Script", "KFGQPC Uthman Taha Naskh", "Traditional Arabic", "Geeza Pro", "Amiri", serif;
  font-size: calc(2.15rem * var(--quran-scale));
  line-height: 2.5;
  color: var(--ink);
  word-spacing: 0.15em;
}

.tok {
  display: inline-block;
  border-radius: 0.35rem;
  padding: 0.05em 0.15em;
  transition: background var(--motion-fast) var(--ease), color var(--motion-fast) var(--ease);
}

.tok.ok { color: var(--ok); background: var(--ok-bg); }
.tok.substituted { color: var(--sub); background: var(--sub-bg); text-decoration: underline wavy var(--sub); }
.tok.missing { color: var(--miss); background: var(--miss-bg); text-decoration: line-through; text-decoration-color: var(--miss); opacity: 0.85; }
.tok.mark { color: var(--ink-soft); font-size: 0.75em; opacity: 0.7; }
/* "unchecked" -- stall-breaker force-committed this word without real
   two-tick agreement (see backend/live.py). Deliberately dim/gray, NOT
   green: no evidence this was actually heard correctly, so it must not
   read as "verdict: ok" at a glance. Excluded from the accuracy math
   (backend/policy.py's count_checked). */
.tok.unchecked { color: var(--unchecked); background: var(--unchecked-bg); border-bottom: 1px dashed var(--unchecked); }

.extra-chip {
  display: inline-block;
  font-size: 0.55em;
  vertical-align: middle;
  color: var(--extra);
  background: var(--extra-bg);
  border-radius: 999px;
  padding: 0.15em 0.6em;
  margin: 0 0.2em;
  border: 1px dashed var(--border);
}

.record-area {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
  margin: 1.75rem 0 1rem;
}

.record-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  width: 108px;
  height: 108px;
  border-radius: 50%;
  border: none;
  background: var(--record);
  color: #fff;
  font-family: inherit;
  font-size: 0.95rem;
  font-weight: 700;
  cursor: pointer;
  box-shadow: 0 6px 18px rgba(172, 58, 53, 0.35);
  transition: transform var(--motion-fast) var(--ease), box-shadow var(--motion-fast) var(--ease), background var(--motion-base) var(--ease);
}

.record-btn:hover { transform: scale(1.03); }
.record-btn:active { transform: scale(0.97); }

.record-btn.recording {
  background: #7a1f1f;
  animation: pulse 1.2s ease-in-out infinite;
}

@keyframes pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(178,59,59,0.45); }
  50% { box-shadow: 0 0 0 14px rgba(178,59,59,0); }
}

.record-btn:disabled { opacity: 0.5; cursor: not-allowed; }

.spinner {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--ink-soft);
  font-size: 0.9rem;
}

.spin {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 2px solid var(--border);
  border-top-color: var(--accent);
  animation: spin 0.8s linear infinite;
}

@keyframes spin { to { transform: rotate(360deg); } }

.hidden { display: none !important; }

.mic-error {
  color: var(--sub);
  font-size: 0.85rem;
  text-align: center;
  max-width: 480px;
}

.result-area {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 1.5rem 1.5rem 1.25rem;
  margin-top: 0.5rem;
  box-shadow: var(--shadow-sm);
  animation: settle-in var(--motion-base) var(--ease);
}

/* Shared "fade/settle" entrance -- every newly-shown card this round
   (result cards, dropdown panels, toasts) uses this ONE keyframe:
   a short opacity+translateY settle, <=200ms, no overshoot/bounce. */
@keyframes settle-in {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: translateY(0); }
}

.transcript-label {
  font-size: 0.75rem;
  color: var(--ink-soft);
  margin-bottom: 0.3rem;
}

.transcript-text {
  font-size: 0.95rem;
  color: var(--ink);
  line-height: 1.8;
}

.latency-line {
  margin-top: 0.4rem;
  font-size: 0.75rem;
  color: var(--ink-soft);
}

.feedback-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 1px dashed var(--border);
}

.feedback-buttons {
  display: flex;
  gap: 0.5rem;
}

.fb-btn {
  padding: 0.5rem 0.9rem;
  border-radius: 0.6rem;
  border: 1px solid var(--border);
  font-family: inherit;
  font-size: 0.85rem;
  cursor: pointer;
  background: var(--card-bg);
}

.fb-correct { color: var(--ok); border-color: var(--ok); }
.fb-correct.active { background: var(--ok-bg); }
.fb-wrong { color: var(--sub); border-color: var(--sub); }
.fb-wrong.active { background: var(--sub-bg); }

.fb-note {
  flex: 1 1 180px;
  padding: 0.5rem 0.7rem;
  border-radius: 0.6rem;
  border: 1px solid var(--border);
  font-family: inherit;
  font-size: 0.85rem;
}

.fb-saved {
  font-size: 0.8rem;
  color: var(--ok);
}

.legend {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 1rem;
  margin-top: 1.5rem;
  font-size: 0.75rem;
  color: var(--ink-soft);
}

.legend-item {
  display: flex;
  align-items: center;
  gap: 0.35rem;
}

.dot {
  width: 0.6rem;
  height: 0.6rem;
  border-radius: 50%;
  display: inline-block;
}

.dot.ok { background: var(--ok); }
.dot.sub { background: var(--sub); }
.dot.miss { background: var(--miss); }
.dot.extra { background: var(--extra); }
.dot.unchecked { background: var(--unchecked); }

.history-inaudible-cell { color: var(--unchecked); font-weight: 700; }

/* Optional استعاذة/بسملة prefix note (2026-08-12 field failure fix) --
   small and friendly on purpose: absent prefixes show nothing at all
   (see static/app.js showLiveSummary), recited ones get a quiet
   checkmark chip. Never affects the accuracy number above it. */
.prefix-status-row {
  display: flex;
  justify-content: center;
  gap: 0.5rem;
  margin: 0.5rem 0 0.75rem;
}

.prefix-status-chip {
  font-size: 0.75rem;
  color: var(--ok);
  background: var(--ok-bg);
  border-radius: 999px;
  padding: 0.2em 0.75em;
}

/* ---------------- tabs ---------------- */

.tabs {
  display: flex;
  gap: 0.4rem;
  max-width: 760px;
  margin: 1rem auto 0;
  padding: 0 1.25rem;
}

/* 390px verification pass finding (pre-existing, not new this round):
   5 flex:1 tabs with un-abbreviated Arabic labels don't fit 350-ish px
   of real content width and, since .tabs never declared flex-wrap, they
   silently overflowed the viewport instead of wrapping -- "آية عشوائية"
   pushed clean off the left edge in RTL, easy to miss without an actual
   390px screenshot. Fixed as a horizontally-scrollable pill strip
   (a standard, expected mobile tab pattern) rather than wrapping to a
   second row, which would push all page content down inconsistently
   tab to tab. */
@media (max-width: 640px) {
  .tabs {
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    padding-bottom: 0.15rem;
  }
  .tabs::-webkit-scrollbar { display: none; }
  .tab-btn {
    flex: 0 0 auto;
    white-space: nowrap;
    padding: 0.55rem 0.9rem;
  }
}

.tab-btn {
  flex: 1;
  padding: 0.6rem 1rem;
  border: 1px solid var(--border);
  background: var(--card-bg);
  color: var(--ink-soft);
  font-family: inherit;
  font-size: 0.95rem;
  font-weight: 700;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background var(--motion-fast) var(--ease), color var(--motion-fast) var(--ease), border-color var(--motion-fast) var(--ease);
}

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

.tab-btn.active {
  color: var(--accent-ink);
  border-color: var(--accent);
  background: var(--accent);
}

.tab-panel { display: none; }
.tab-panel.active { display: block; }

/* ---------------- live mode ---------------- */

.hint {
  text-align: center;
  font-size: 0.8rem;
  color: var(--ink-soft);
  margin: 0.75rem 0 1rem;
}

.diagnostics-hint {
  color: var(--miss);
  background: var(--miss-bg);
  border-radius: 0.6rem;
  padding: 0.5rem 0.75rem;
  margin-top: -0.5rem;
}

.diagnostics-toggle {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.8rem;
  color: var(--ink-soft);
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
}

.diagnostics-toggle input {
  accent-color: var(--record);
  width: 1rem;
  height: 1rem;
  cursor: pointer;
}

.live-reading-wrap {
  position: relative;
}

.live-reading {
  max-height: 48vh;
  overflow-y: auto;
  text-align: start;
  scroll-behavior: smooth;
}

/* ---------------- no-scroll reading (المباشر + آية عشوائية) ----------------
   2026-08-12: the reciter never scrolls. Only the CURRENT ayah is shown,
   large and centered; the next ayah appears as a small, dimmed preview
   underneath so its arrival is expected rather than jarring. The instant
   the boundary is actually crossed (currentAyahIndex advances), the new
   current ayah fades/slides in via .ns-enter -> .ns-enter-active (see
   renderNoScrollReading in app.js) -- the READING AREA changes, the PAGE
   never does. */
.live-reading.noscroll {
  max-height: none;
  overflow: visible;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 2.5rem 1.5rem 2rem;
}

.noscroll-progress {
  text-align: center;
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--ink-soft);
  margin-bottom: 0.5rem;
}

.noscroll-current {
  width: 100%;
  opacity: 1;
  transform: translateY(0);
  transition: opacity 0.4s ease, transform 0.4s ease;
}

.noscroll-current.ns-enter {
  opacity: 0;
  transform: translateY(10px);
}

.noscroll-current.ns-enter-active {
  opacity: 1;
  transform: translateY(0);
}

.noscroll-current .live-ayah-key {
  font-size: 0.85rem;
}

.noscroll-current .live-ayah-words {
  font-size: calc(2.3rem * var(--quran-scale));
  line-height: 2.6;
}

.noscroll-next {
  width: 100%;
  margin-top: 1.75rem;
  padding-top: 1.5rem;
  border-top: 1px dashed var(--border);
  opacity: 0.4;
  transform: scale(0.94);
  transition: opacity 0.4s ease, transform 0.4s ease;
}

.noscroll-next .live-ayah-words {
  font-size: calc(1.3rem * var(--quran-scale));
  line-height: 2;
}

.noscroll-empty {
  color: var(--ink-soft);
  font-size: 0.9rem;
  padding: 2.5rem 0;
}

@media (max-width: 420px) {
  .live-reading.noscroll { padding: 1.5rem 1rem 1.25rem; }
  .noscroll-current .live-ayah-words { font-size: 1.65rem; line-height: 2.15; }
  .noscroll-next .live-ayah-words { font-size: 1.05rem; line-height: 1.8; }
  .noscroll-next { margin-top: 1.25rem; padding-top: 1rem; }
}

/* ---------------- المصحف (mushaf view) -- 2026-08-12, new default ----------------
   The WHOLE passage as one flowing, scrollable block (see
   static/app.js's buildMushafHtml/renderMushafReading) instead of one
   ayah per screen -- toggled via .view-mode-btn (reuses .mode-btn's
   pill styling as-is, see index.html). The reading area's OWN class
   flips between "mushaf" and "noscroll" (renderReading in app.js) so
   the two layouts never fight over the same element. */
.live-reading.mushaf {
  max-height: 64vh;
  overflow-y: auto;
  display: block;
  text-align: right;
  padding: 1.75rem 1.5rem;
  scroll-behavior: smooth;
  font-family: "KFGQPC HAFS Uthmanic Script", "KFGQPC Uthman Taha Naskh", "Traditional Arabic", "Geeza Pro", "Amiri", serif;
  font-size: calc(1.55rem * var(--quran-scale));
  line-height: 2.7;
  word-spacing: 0.12em;
}

.mushaf-ayah-end {
  display: inline-block;
  font-family: "IBM Plex Sans Arabic", "SF Arabic", "Geeza Pro", "Traditional Arabic", -apple-system, sans-serif;
  font-size: 0.62em;
  font-weight: 700;
  color: var(--accent);
  margin: 0 0.2em;
  vertical-align: middle;
}

/* ---------------- اسمع القارئ (feature: per-ayah reciter playback) ----------------
   Small icon-only button after every ayah-end marker (mushaf view) and
   inline wherever a single verse_key is in play (mistake rows, single-
   ayah result). Deliberately quiet -- an outline glyph, not a filled
   button -- so a page full of ayah markers doesn't turn into a page
   full of buttons; it earns color only on hover/playing. */
.qari-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.6em;
  height: 1.6em;
  vertical-align: middle;
  border: none;
  background: transparent;
  color: var(--ink-faint);
  border-radius: 50%;
  cursor: pointer;
  padding: 0;
  transition: color var(--motion-fast) var(--ease), background var(--motion-fast) var(--ease);
}

.qari-btn svg { width: 60%; height: 60%; }
.qari-btn:hover { color: var(--accent); background: var(--accent-tint); }
.qari-btn.playing { color: var(--accent); }
.qari-btn.playing svg { animation: qari-pulse 1s ease-in-out infinite; }

@keyframes qari-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.5; }
}

.qari-btn-inline {
  font-size: 0.62em;
  margin: 0 0.1em;
}

/* آية عشوائية visual separation (2026-08-12): each drawn ayah as its
   own card instead of the continuous mushaf flow -- unrelated ayahs
   (often different surahs) reading as one garbled block was Ahmed's
   field feedback. Same container (.live-reading.mushaf) and word-level
   coloring/active-ring classes as the flowing mushaf view, just with
   per-ayah card chrome layered on top via JS's random-ayah-card branch
   in buildMushafHtml. */
.random-ayah-card {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 0.9rem;
  padding: 1rem 1.25rem 1.25rem;
  margin-bottom: 1rem;
}
.random-ayah-card:last-child { margin-bottom: 0; }

.random-ayah-card-head {
  font-family: "IBM Plex Sans Arabic", "SF Arabic", "Geeza Pro", "Traditional Arabic", -apple-system, sans-serif;
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--accent);
  margin-bottom: 0.6rem;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--border);
}

.random-ayah-card-words {
  font-family: "KFGQPC HAFS Uthmanic Script", "KFGQPC Uthman Taha Naskh", "Traditional Arabic", "Geeza Pro", "Amiri", serif;
  font-size: calc(1.55rem * var(--quran-scale));
  line-height: 2.5;
  word-spacing: 0.12em;
}

@media (max-width: 420px) {
  .random-ayah-card { padding: 0.85rem 0.9rem 1rem; margin-bottom: 0.75rem; }
  .random-ayah-card-words { font-size: 1.15rem; line-height: 2.1; }
}

/* Active word (2026-08-12): the first not-yet-committed position --
   where the reciter actually IS right now. A ring, not a color (color
   is reserved for the verdict classes -- .tok.ok/.substituted/etc. --
   the active marker has to layer on top of ANY of those, including
   "pending", without implying a verdict of its own). Gently pulses so
   it stays findable in a full-passage block without being distracting. */
.tok.mushaf-active {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 0.4rem;
  animation: mushaf-active-pulse 1.4s ease-in-out infinite;
}

@keyframes mushaf-active-pulse {
  0%, 100% { outline-color: var(--accent); }
  50% { outline-color: transparent; }
}

@media (max-width: 420px) {
  .live-reading.mushaf {
    font-size: 1.15rem;
    line-height: 2.2;
    padding: 1.25rem 1rem;
    max-height: 58vh;
  }
}

/* ---------------- آية عشوائية (random ayahs) picker ---------------- */

#randomCountSelect,
#randomLengthSelect {
  flex: 1 1 140px;
  padding: 0.55rem 0.75rem;
  font-size: 0.95rem;
  border-radius: 0.6rem;
  border: 1px solid var(--border);
  background: var(--card-bg);
  color: var(--ink);
  font-family: inherit;
}

/* Per-tick latency, shown small in a corner during a live session (see
   static/app.js applyLiveUpdate). Not meant to demand attention -- just
   visible if Ahmed wants to check whether a tick felt slow. Lives as a
   sibling of #liveReadingArea (in .live-reading-wrap), NOT inside it --
   renderLiveReading/renderLivePreview overwrite that element's innerHTML
   wholesale on every render, which would silently wipe this out. */
.live-latency-corner {
  position: absolute;
  top: 0.5rem;
  inset-inline-end: 0.6rem;
  font-size: 0.68rem;
  color: var(--ink-soft);
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.1em 0.6em;
  direction: ltr;
  z-index: 2;
}

.live-latency-corner.slow {
  color: #fff;
  background: var(--slow);
  border-color: var(--slow);
  font-weight: 700;
}

.live-ayah {
  padding: 0.75rem 0;
  border-bottom: 1px dashed var(--border);
  transition: background 0.4s;
  border-radius: 0.5rem;
}

.live-ayah:last-child { border-bottom: none; }

.live-ayah.completed {
  background: var(--ok-bg);
}

.live-ayah.active {
  background: var(--bg-soft);
}

.tok.pending { color: var(--ink); opacity: 0.55; }

.live-ayah-key {
  font-size: 0.7rem;
  color: var(--ink-soft);
  margin-bottom: 0.35rem;
}

.live-ayah-words {
  font-family: "KFGQPC HAFS Uthmanic Script", "KFGQPC Uthman Taha Naskh", "Traditional Arabic", "Geeza Pro", "Amiri", serif;
  font-size: 1.7rem;
  line-height: 2.3;
  word-spacing: 0.12em;
}

.live-status {
  font-size: 0.85rem;
  color: var(--accent);
  font-weight: 700;
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.live-status .rec-dot {
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  background: var(--record);
  animation: pulse-dot 1.2s ease-in-out infinite;
}

@keyframes pulse-dot {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.3; }
}

.live-summary-head {
  font-size: 0.95rem;
  margin-bottom: 0.75rem;
  color: var(--ink);
}

.mistakes-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.mistake-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.5rem 0.75rem;
  border-radius: 0.5rem;
  background: var(--bg);
  font-size: 0.85rem;
}

.mistake-row .mistake-word {
  font-family: "KFGQPC HAFS Uthmanic Script", "KFGQPC Uthman Taha Naskh", "Traditional Arabic", "Geeza Pro", serif;
  font-size: 1.2rem;
}

.mistake-row.substituted .mistake-word { color: var(--sub); }
.mistake-row.missing .mistake-word { color: var(--miss); text-decoration: line-through; }

.mistake-row .mistake-meta {
  color: var(--ink-soft);
  font-size: 0.75rem;
  white-space: nowrap;
}

.mistake-row {
  cursor: pointer;
}

.mistake-row:hover {
  filter: brightness(0.97);
}

/* ---------------- "close" (near-miss, مبتدئ leniency) status ---------------- */

:root {
  --close: #6d5bb3;
  --close-bg: #efeafb;
}

.tok.close { color: var(--close); background: var(--close-bg); text-decoration: underline dotted var(--close); }
.dot.close { background: var(--close); }

/* ---------------- "tashkeel" (التشكيل level, 2026-08-14) status ----------------
   Letters-correct word, but t1's own diacritized output disagrees with
   the voweled reference on a REAL haraka (not notation noise -- see
   backend/policy.py's evaluate_tashkeel_word). Deliberately distinct
   from .tok.substituted (red, wavy) and .tok.missing (amber-red,
   line-through): amber/gold, dashed underline -- a softer, "check the
   vowel" signal, never confused with a hard letters-level mistake. */
.tok.tashkeel { color: var(--tashkeel); background: var(--tashkeel-bg); text-decoration: underline dashed var(--tashkeel); }
.dot.tashkeel { background: var(--tashkeel); }
.mistake-row.tashkeel .mistake-word { color: var(--tashkeel); }
.mistake-row.close .mistake-word { color: var(--close); }

/* ---------------- mode switcher ---------------- */

.mode-switcher-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 0.5rem;
}

.mode-switcher {
  display: flex;
  gap: 0.3rem;
}

/* عرض: السورة كاملة / آية بآية (2026-08-12) -- reuses .mode-switcher/
   .mode-btn's pill styling verbatim (see index.html), just needs its
   own bottom margin since it sits alone as its own row above the
   reading area, not inline in .mode-switcher-row alongside the
   strictness switcher. */
.view-mode-switcher {
  justify-content: center;
  margin-bottom: 0.85rem;
}

.mode-btn {
  padding: 0.4rem 0.85rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--card-bg);
  color: var(--ink-soft);
  font-family: inherit;
  font-size: 0.8rem;
  cursor: pointer;
}

.mode-btn.active {
  color: #fff;
  background: var(--accent);
  border-color: var(--accent);
  font-weight: 700;
}

/* ---------------- accuracy, retry, tap hint ---------------- */

.accuracy-big {
  text-align: center;
  font-size: 2.4rem;
  font-weight: 800;
  color: var(--ok);
  margin-bottom: 0.25rem;
}

.tap-hint {
  margin: 0 0 0.75rem;
}

.retry-btn {
  display: block;
  margin: 0.5rem auto 1rem;
  padding: 0.55rem 1.4rem;
  border-radius: 999px;
  border: 1px solid var(--accent);
  background: var(--card-bg);
  color: var(--accent);
  font-family: inherit;
  font-size: 0.9rem;
  font-weight: 700;
  cursor: pointer;
  transition: background var(--motion-fast) var(--ease), transform var(--motion-fast) var(--ease);
}

.retry-btn:hover { background: var(--accent-tint); }
.retry-btn:active { transform: scale(0.97); }

/* ---------------- result-card actions row (feature: reveal redesign) ----------------
   أعد المحاولة / اسمع القارئ / التالي -- one row, same weight, replacing
   the single lone retry button the summary card used to end on. See
   showLiveSummary/showRandomSummary/showTasmeeSummary's renderResultActions. */
.result-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem;
  margin: 0.5rem 0 1rem;
}

.result-actions .retry-btn,
.result-actions .qari-btn-labeled,
.result-actions .next-btn {
  margin: 0;
}

.next-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.55rem 1.4rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--accent);
  color: var(--accent-ink);
  font-family: inherit;
  font-size: 0.9rem;
  font-weight: 700;
  cursor: pointer;
  transition: background var(--motion-fast) var(--ease), transform var(--motion-fast) var(--ease);
}

.next-btn:hover { background: var(--accent-strong); }
.next-btn:active { transform: scale(0.97); }

.qari-btn-labeled {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.55rem 1.2rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--card-bg);
  color: var(--ink);
  font-family: inherit;
  font-size: 0.9rem;
  font-weight: 700;
  cursor: pointer;
  transition: background var(--motion-fast) var(--ease), border-color var(--motion-fast) var(--ease);
}

.qari-btn-labeled:hover { border-color: var(--accent); color: var(--accent); }
.qari-btn-labeled svg { width: 16px; height: 16px; }
.qari-btn-labeled.playing { color: var(--accent); border-color: var(--accent); background: var(--accent-tint); }

/* ---------------- دفء مقتصد line (feature: reveal redesign) ----------------
   The one warm, specific sentence under the big accuracy number -- see
   app.js's warmthLine()/mistakeWarmthLine helpers. Quiet, not shouty:
   normal weight, ink-soft, small vertical rhythm, no icon/emoji. */
.warmth-line {
  text-align: center;
  font-size: 0.95rem;
  color: var(--ink-soft);
  margin: 0 0 0.9rem;
  line-height: 1.6;
}

/* ---------------- لحن badges (feature: reveal redesign) ----------------
   Classical mistake-category label, shown before the ص/س-style detail
   already in .mistake-meta -- see app.js's mistakeLahnLabel(). Distinct
   from severity color (which the word itself already carries via
   .mistake-row.substituted/.missing) -- this is a category, not a
   verdict, so it stays neutral ink-soft/bg rather than red/orange. */
.lahn-badge {
  display: inline-block;
  font-size: 0.68rem;
  font-weight: 700;
  color: var(--accent);
  background: var(--accent-tint);
  border-radius: 999px;
  padding: 0.12em 0.65em;
  margin-inline-end: 0.4em;
}

.mistake-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.mistake-row-main {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
}

/* ---------------- word feedback popover ---------------- */

.word-popover {
  position: fixed;
  z-index: 50;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 0.75rem;
  box-shadow: 0 10px 30px rgba(0,0,0,0.18);
  padding: 0.75rem;
  width: min(280px, 90vw);
}

.word-popover-word {
  font-family: "KFGQPC HAFS Uthmanic Script", "KFGQPC Uthman Taha Naskh", "Traditional Arabic", "Geeza Pro", serif;
  font-size: 1.4rem;
  text-align: center;
  margin-bottom: 0.15rem;
}

.word-popover-meta {
  text-align: center;
  font-size: 0.72rem;
  color: var(--ink-soft);
  margin-bottom: 0.6rem;
}

.word-popover-buttons {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.4rem;
  margin-bottom: 0.5rem;
}

.word-popover-buttons button {
  padding: 0.45rem 0.3rem;
  border-radius: 0.5rem;
  border: 1px solid var(--border);
  background: var(--bg);
  font-family: inherit;
  font-size: 0.78rem;
  cursor: pointer;
}

.word-popover-buttons button:hover { background: var(--bg-soft); }
.word-popover-buttons button:active { transform: scale(0.97); }

.word-popover #wpNote {
  width: 100%;
  padding: 0.4rem 0.6rem;
  border-radius: 0.5rem;
  border: 1px solid var(--border);
  font-family: inherit;
  font-size: 0.8rem;
}

.word-popover-close {
  position: absolute;
  top: 0.35rem;
  left: 0.5rem;
  border: none;
  background: none;
  color: var(--ink-soft);
  font-size: 1.1rem;
  cursor: pointer;
  line-height: 1;
}

.word-feedback-toast {
  position: fixed;
  bottom: 1.5rem;
  left: 50%;
  transform: translateX(-50%);
  /* self-inverting pair (not a hardcoded #fff): var(--ink)/var(--bg)
     swap polarity together in dark mode, so this stays high-contrast
     in both instead of silently becoming light-text-on-light-ink. */
  background: var(--ink);
  color: var(--bg);
  padding: 0.5rem 1.1rem;
  border-radius: 999px;
  font-size: 0.85rem;
  z-index: 60;
  box-shadow: var(--shadow-md);
}

/* ---------------- history (السجل) ---------------- */

.history-trends {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  margin-bottom: 1.25rem;
}

.history-trend-card {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 0.8rem;
  padding: 0.85rem 1rem;
}

.history-trend-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 0.5rem;
  font-size: 0.85rem;
}

.history-trend-name {
  font-weight: 700;
  color: var(--ink);
}

.history-trend-latest {
  color: var(--ink-soft);
  font-size: 0.78rem;
}

.history-trend-svg {
  width: 100%;
  height: 46px;
  display: block;
}

.history-table {
  width: 100%;
  border-collapse: collapse;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 0.8rem;
  overflow: hidden;
  font-size: 0.82rem;
}

.history-table th, .history-table td {
  padding: 0.5rem 0.6rem;
  text-align: start;
  border-bottom: 1px solid var(--border);
}

.history-table th {
  color: var(--ink-soft);
  font-weight: 700;
  font-size: 0.75rem;
  background: var(--bg);
}

.history-table tr:last-child td { border-bottom: none; }

/* ---------------- مقارنة المستويات (mode comparison, 2026-08-12) ---------------- */

.mode-comparison {
  margin-top: 1.25rem;
  padding-top: 1rem;
  border-top: 1px dashed var(--border);
}

.mode-comparison-title {
  font-size: 0.95rem;
  margin: 0 0 0.15rem;
  color: var(--ink);
}

.mode-comparison-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.6rem;
  margin-top: 0.5rem;
}

.mc-col {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 0.7rem;
  padding: 0.65rem 0.6rem;
  text-align: center;
}

.mc-col-active {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent) inset;
}

.mc-col-head {
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--ink-soft);
  margin-bottom: 0.15rem;
}

.mc-col-pct {
  font-size: 1.35rem;
  font-weight: 700;
  color: var(--ink);
}

.mc-col-flags {
  font-size: 0.72rem;
  color: var(--ink-soft);
  margin-top: 0.1rem;
}

.mc-col-details {
  margin-top: 0.5rem;
  text-align: start;
}

.mc-col-details summary {
  cursor: pointer;
  font-size: 0.72rem;
  color: var(--accent);
  text-align: center;
}

.mc-col-details ul {
  list-style: none;
  margin: 0.5rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.mc-word {
  display: flex;
  flex-direction: column;
  gap: 0.05rem;
  font-size: 0.78rem;
  padding: 0.3rem 0.45rem;
  border-radius: 0.4rem;
  background: var(--bg);
}

.mc-word-text {
  font-family: "KFGQPC HAFS Uthmanic Script", "KFGQPC Uthman Taha Naskh", "Traditional Arabic", "Geeza Pro", serif;
  font-size: 1rem;
}

.mc-word.substituted .mc-word-text { color: var(--sub); }
.mc-word.missing .mc-word-text { color: var(--miss); text-decoration: line-through; }

.mc-word-meta {
  color: var(--ink-soft);
  font-size: 0.68rem;
}

.mc-word-empty {
  color: var(--ink-soft);
  font-size: 0.75rem;
  text-align: center;
  list-style: none;
}

@media (max-width: 560px) {
  .mode-comparison-grid { grid-template-columns: 1fr; }
}

/* ---------------- سجلّ تسجيلاتي (recordings archive manager, 2026-08-12) ---------------- */

.recordings-manager {
  margin-top: 1.5rem;
  padding-top: 1rem;
  border-top: 1px dashed var(--border);
}

.recordings-title {
  font-size: 1rem;
  margin: 0 0 0.15rem;
  color: var(--ink);
}

.recordings-list {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  margin-top: 0.75rem;
}

.recording-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 0.7rem;
  padding: 0.6rem 0.75rem;
}

.recording-meta {
  flex: 1 1 200px;
  min-width: 0;
}

.recording-meta-top {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  align-items: baseline;
  font-size: 0.85rem;
}

.recording-passage {
  font-weight: 700;
  color: var(--ink);
}

.recording-mode, .recording-strictness {
  font-size: 0.72rem;
  color: var(--ink-soft);
  background: var(--bg);
  border-radius: 999px;
  padding: 0.05em 0.55em;
}

.recording-meta-bottom {
  font-size: 0.72rem;
  color: var(--ink-soft);
  margin-top: 0.2rem;
}

.recording-player {
  height: 2.2rem;
  max-width: 220px;
  flex: 1 1 200px;
}

.recording-delete-btn {
  background: var(--sub-bg);
  color: var(--sub);
  border: 1px solid var(--sub);
  border-radius: 0.5rem;
  padding: 0.35rem 0.75rem;
  font-family: inherit;
  font-size: 0.78rem;
  cursor: pointer;
  white-space: nowrap;
}

.recording-delete-btn:hover {
  background: var(--sub);
  color: #fff;
}

/* ==================================================================
   تسميع (hidden-text memorization) -- "احفظ وسمِّع" journey.

   2026-08-13: the progressive mushaf SKELETON (Ahmed's explicit spec,
   replacing the earlier breathing-pulse stage entirely -- see
   static/app.js's renderTasmeeSkeleton/buildTasmeeSkeletonHtml). The
   reading area renders the REAL mushaf layout from the start (ayah
   medallions included -- position is never a spoiler, only the WORDS
   are), reusing .live-reading.mushaf/.mushaf-ayah/.mushaf-ayah-end/
   .random-ayah-card as-is. Only the WORD level differs while hidden:
   .tok-skel (not yet committed), .tok-peek (استراق نظر's 2s inline
   ghost), and the one-shot .tok-materialize-ok/-flag entrance classes
   for a word the instant it commits. Once revealed (finish or "اكشف"),
   the SAME .live-reading.mushaf styling takes back over unchanged --
   no separate reveal-state CSS needed there, same as it always was.
   ================================================================== */

/* Skeleton placeholder -- the word's REAL text sits in the DOM
   (color:transparent, not display:none/content-less), so the box is
   exactly the right width for what it'll become -- no reflow on
   materialize. aria-hidden in the markup (see tasmeeSkeletonTokenHtml)
   keeps screen readers from announcing invisible text. */
.tok-skel {
  color: transparent;
  background: var(--border-soft);
  border-radius: 0.35rem;
  user-select: none;
  animation: tasmee-skel-shimmer 1.8s ease-in-out infinite;
}

@keyframes tasmee-skel-shimmer {
  0%, 100% { opacity: 0.55; }
  50% { opacity: 1; }
}

/* استراق نظر, inline (2026-08-13 -- replaces the old floating overlay):
   the ghosted word's own slot shows its real text in a clearly
   "preview, not committed" treatment -- accent-tinted, not a verdict
   color -- for the fixed 2s window static/app.js's peekTasmee times. */
.tok-peek {
  color: var(--accent);
  background: var(--accent-tint);
  border-radius: 0.35rem;
  padding: 0.05em 0.15em;
  animation: settle-in var(--motion-base) var(--ease);
}

/* Materialize, correct word (2026-08-13): a brief green confirmation
   flash that settles into plain ink -- تسميع's "does this look like
   clean mushaf text" framing means a correct word doesn't need to stay
   permanently tinted the way it does in the audited tabs (المباشر/آية
   عشوائية/آية واحدة); the flash IS the confirmation, then it gets out
   of the way. One-shot: only ever applied the render a position first
   crosses into "committed" (see tasmeeSkeletonTokenHtml's
   ns._materialized bookkeeping), never replayed on a later re-render. */
@keyframes tasmee-materialize-ok {
  0% { background: var(--ok-bg); color: var(--ok); }
  55% { background: var(--ok-bg); color: var(--ok); }
  100% { background: transparent; color: var(--ink); }
}

.tok-materialize-ok {
  animation: tasmee-materialize-ok 900ms var(--ease) forwards;
}

/* Materialize, flagged word (substituted/missing/close): appears
   directly in its persistent flag color/background (already carried by
   the normal .tok.substituted/.tok.missing/.tok.close rules this class
   is layered ON TOP of -- see tasmeeMaterializeClass) and STAYS that
   color, unlike the ok case above -- these need to stay visible for the
   end-of-session review, so there's nothing to settle away from. Just a
   quiet fade+settle entrance, no color animation of its own. */
@keyframes tasmee-materialize-flag {
  0% { opacity: 0; transform: translateY(3px); }
  100% { opacity: 1; transform: translateY(0); }
}

.tok-materialize-flag {
  animation: tasmee-materialize-flag 220ms var(--ease) forwards;
}

.tasmee-actions {
  position: relative;
}

.peek-btn,
.reveal-btn {
  border: 1px solid var(--accent);
  background: var(--card-bg);
  color: var(--accent);
  border-radius: 999px;
  padding: 0.5rem 1.1rem;
  font-family: inherit;
  font-size: 0.85rem;
  font-weight: 700;
  cursor: pointer;
}

.peek-btn:hover,
.reveal-btn:hover { background: var(--bg-soft); }
.peek-btn:disabled { opacity: 0.5; cursor: not-allowed; }

.reveal-btn {
  border-color: var(--ink-soft);
  color: var(--ink-soft);
}

.tasmee-peek-count {
  font-size: 0.78rem;
  color: var(--ink-soft);
}

.tasmee-encouragement {
  text-align: center;
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--accent);
  margin: 0.25rem 0 0.75rem;
}

.tasmee-peek-summary {
  text-align: center;
  font-size: 0.85rem;
  color: var(--ink-soft);
  margin-bottom: 0.5rem;
}

/* ==================================================================
   Searchable passage picker (feature: surah picker upgrade, 2026-08-13)

   Progressive enhancement over the existing native <select> (kept in
   the DOM, hidden, still the source of truth for .value/"change" --
   see app.js's enhancePassagePicker): a trigger button opens a panel
   with a type-to-filter search box and grouped results (الأخيرة /
   مقاطع شائعة). Used identically in المباشر, آية واحدة and تسميع.
   ================================================================== */
.passage-picker {
  position: relative;
  flex: 1 1 220px;
}

.passage-picker-native-hidden { display: none; }

.passage-picker-trigger {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.6rem 0.85rem;
  font-size: 0.95rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--card-bg);
  color: var(--ink);
  font-family: inherit;
  cursor: pointer;
  text-align: start;
  transition: border-color var(--motion-fast) var(--ease);
}

.passage-picker-trigger:hover,
.passage-picker-trigger[aria-expanded="true"] {
  border-color: var(--accent);
}

.passage-picker-value {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.passage-picker-chevron {
  color: var(--ink-faint);
  font-size: 0.75rem;
  flex-shrink: 0;
  transition: transform var(--motion-fast) var(--ease);
}

.passage-picker-trigger[aria-expanded="true"] .passage-picker-chevron {
  transform: rotate(180deg);
}

.passage-picker-panel {
  position: absolute;
  top: calc(100% + 0.4rem);
  inset-inline-start: 0;
  width: min(340px, 90vw);
  max-height: 60vh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  background: var(--card-bg-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  z-index: 30;
  animation: settle-in var(--motion-base) var(--ease);
}

.passage-picker-search {
  margin: 0.6rem;
  padding: 0.5rem 0.75rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--bg-soft);
  color: var(--ink);
  font-family: inherit;
  font-size: 0.88rem;
}

.passage-picker-search:focus {
  outline: none;
  border-color: var(--accent);
}

.passage-picker-groups {
  overflow-y: auto;
  padding: 0 0.4rem 0.5rem;
}

.passage-picker-group + .passage-picker-group {
  margin-top: 0.5rem;
  padding-top: 0.5rem;
  border-top: 1px dashed var(--border);
}

.passage-picker-group-label {
  font-size: 0.68rem;
  font-weight: 700;
  color: var(--ink-faint);
  padding: 0.2rem 0.5rem 0.35rem;
}

.passage-picker-option {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  width: 100%;
  padding: 0.5rem 0.6rem;
  border: none;
  background: transparent;
  color: var(--ink);
  font-family: inherit;
  font-size: 0.9rem;
  text-align: start;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background var(--motion-fast) var(--ease);
}

.passage-picker-option:hover { background: var(--bg-soft); }
.passage-picker-option.active { color: var(--accent); font-weight: 700; background: var(--accent-tint); }

.passage-picker-count {
  font-size: 0.72rem;
  color: var(--ink-faint);
  flex-shrink: 0;
}

.passage-picker-empty {
  padding: 1rem 0.6rem;
  text-align: center;
  font-size: 0.85rem;
  color: var(--ink-faint);
}

/* ==================================================================
   Keyboard shortcuts hint (feature: session flow actions, 2026-08-13)
   ================================================================== */
.keyboard-hint {
  direction: ltr;
  font-size: 0.72rem !important;
  color: var(--ink-faint) !important;
  letter-spacing: 0.01em;
}

.keyboard-hint kbd {
  display: inline-block;
  font-family: inherit;
  font-size: 0.68rem;
  background: var(--bg-soft);
  border: 1px solid var(--border);
  border-bottom-width: 2px;
  border-radius: 0.3rem;
  padding: 0.05em 0.4em;
  margin: 0 0.15em;
  color: var(--ink-soft);
}

/* ==================================================================
   السجل (history) stat cards -- 2026-08-13 history tab upgrade. Sits
   above the existing per-passage trend cards (kept as-is below, per
   the design brief's "per-passage drill-down kept"). Four honest,
   client-computed summary numbers -- see app.js's computeHistoryStats.
   ================================================================== */
.history-stats-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0.75rem;
  margin-bottom: 1.5rem;
}

@media (max-width: 640px) {
  .history-stats-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.history-stat-card {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 0.9rem 1rem;
  box-shadow: var(--shadow-sm);
}

.history-stat-label {
  font-size: 0.7rem;
  color: var(--ink-soft);
  margin-bottom: 0.3rem;
}

.history-stat-value {
  font-size: 1.3rem;
  font-weight: 800;
  color: var(--ink);
  line-height: 1.2;
}

.history-stat-value .unit {
  font-size: 0.7rem;
  font-weight: 600;
  color: var(--ink-soft);
  margin-inline-start: 0.2em;
}

.history-stat-sub {
  font-size: 0.72rem;
  color: var(--ink-faint);
  margin-top: 0.15rem;
}

.history-stat-trend-up { color: var(--ok); }
.history-stat-trend-down { color: var(--sub); }

.history-section-label {
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--ink-soft);
  margin: 0 0 0.6rem;
}
