:root {
  --green: #58cc02;
  --green-dark: #46a302;
  --blue: #1cb0f6;
  --blue-dark: #1899d6;
  --red: #ff4b4b;
  --red-dark: #ea2b2b;
  --yellow: #ffc800;
  --gray-bg: #f7f7f7;
  --gray-line: #e5e5e5;
  --text: #3c3c3c;
  --text-light: #777;
  --purple: #ce82ff;

  /* Surface tokens: swapped in dark mode below. Everything that used to
     hardcode #fff/#fafafa for card/button/input backgrounds now goes
     through these so the whole app repaints for dark mode in one place. */
  --surface: #fff;
  --surface-alt: #fafafa;
  --overlay-chip: rgba(20, 22, 27, 0.95);

  /* Pastel state tints (correct/wrong/selected) computed from the brand
     color mixed into the current surface, so they stay legible on dark
     surfaces instead of showing up as a bright pastel block. */
  --tint-blue: color-mix(in srgb, var(--blue) 15%, var(--surface));
  --tint-green: color-mix(in srgb, var(--green) 22%, var(--surface));
  --tint-red: color-mix(in srgb, var(--red) 18%, var(--surface));
  --tint-yellow: color-mix(in srgb, var(--yellow) 28%, var(--surface));

  /* Neumorphic dual-tone shadow pair: dark shadow bottom-right, light
     "highlight" top-left, both against the element's own surface color
     (not a distinct neumorphic background) — see dark-mode override
     below. Used only on the buttons/daily-goal-card/unit-circle surfaces
     that opted into the soft-UI treatment, not on answer-correctness
     surfaces (option-btn/match-item/etc.), which stay bordered for
     contrast. */
  --neu-dark: rgba(163, 177, 198, 0.5);
  --neu-light: rgba(255, 255, 255, 0.85);
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
  background: var(--gray-bg);
  color: var(--text);
  font-family: 'Vazirmatn', Tahoma, sans-serif;
  min-height: 100%;
}

#app {
  max-width: 480px;
  margin: 0 auto;
  padding: 16px 16px 76px;
  min-height: 100vh;
}

.boot {
  text-align: center;
  padding: 60px 0;
  color: var(--text-light);
}

.boot-mascot {
  display: block;
  width: 96px;
  height: 96px;
  object-fit: contain;
  margin: 0 auto 8px;
  animation: mascotBob 2.8s ease-in-out infinite;
}

a {
  color: inherit;
  text-decoration: none;
}

button {
  font-family: inherit;
  cursor: pointer;
  border: none;
}

/* ---------- Buttons ---------- */
/* Neumorphic soft-UI: raised dual shadow instead of the old offset
   border-bottom "3D" trick; :active presses the shadow inward instead
   of translating the button down. */
.btn {
  display: block;
  width: 100%;
  padding: 14px 16px;
  border-radius: 16px;
  font-weight: 700;
  font-size: 16px;
  text-align: center;
  border: none;
  box-shadow: 6px 6px 14px var(--neu-dark), -6px -6px 14px var(--neu-light);
  transition: box-shadow .1s ease;
}

.btn:active {
  box-shadow: inset 4px 4px 10px var(--neu-dark), inset -4px -4px 10px var(--neu-light);
}

.btn-green {
  background: linear-gradient(145deg, var(--green), var(--green-dark));
  color: #fff;
}

.btn-blue {
  background: linear-gradient(145deg, var(--blue), var(--blue-dark));
  color: #fff;
}

.btn-red {
  background: linear-gradient(145deg, var(--red), var(--red-dark));
  color: #fff;
}

.btn-outline {
  background: var(--surface);
  color: var(--blue);
}

.btn-gray {
  background: var(--gray-line);
  color: var(--text-light);
  box-shadow: inset 3px 3px 8px var(--neu-dark), inset -3px -3px 8px var(--neu-light);
  cursor: not-allowed;
}

.btn-sm {
  padding: 10px 14px;
  font-size: 14px;
  border-radius: 12px;
  width: auto;
  display: inline-block;
}

/* ---------- Cards ---------- */
.card {
  background: var(--surface);
  border: 2px solid var(--gray-line);
  border-radius: 16px;
  padding: 16px;
  margin-bottom: 14px;
}

.card-clickable:active {
  background: var(--surface-alt);
}

/* ---------- Skill detail (Reading/Listening/Speaking/Writing bar) ---------- */
.skill-bar {
  display: flex;
  gap: 8px;
  overflow-x: auto;
}

.skill-bar-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  flex: 1;
  min-width: 72px;
  padding: 10px 6px;
  border-radius: 14px;
  border: 2px solid var(--gray-line);
  background: var(--surface);
  color: var(--text);
  text-decoration: none;
}

.skill-bar-item.active {
  border-color: var(--blue);
  background: color-mix(in srgb, var(--blue) 10%, var(--surface));
}

.skill-bar-emoji {
  font-size: 22px;
}

.skill-bar-label {
  font-size: 12px;
  font-weight: 600;
}

/* ---------- Concept graph (home) ---------- */
/* Full-bleed: breaks out of #app's centered/padded column so the graph
   fills the entire viewport width regardless of where it sits in the DOM. */
.graph-card {
  position: relative;
  left: 50%;
  right: 50%;
  width: 100vw;
  margin-left: -50vw;
  margin-right: -50vw;
  background: var(--surface);
  border-top: 2px solid var(--gray-line);
  border-bottom: 2px solid var(--gray-line);
  overflow: hidden;
  touch-action: none;
}

/* When the graph is the first thing in #app (no daily-goal card above it),
   cancel #app's own top padding so the graph sits flush under the header
   instead of leaving a blank strip. */
.graph-card.flush-top {
  margin-top: -16px;
}

body {
  overflow-x: hidden;
}

.graph-canvas {
  display: block;
  cursor: grab;
}

/* Fixed to the physical right regardless of locale direction — see
   .graph-mute-btn below for why these two don't use logical inline-start/end
   like the rest of the app. */
.graph-greeting {
  position: absolute;
  top: 14px;
  right: 16px;
  /* leave room for the mute button (44px wide, 14px inset) plus a gap,
     so the fact line below can use the rest of the card's width */
  width: calc(100% - 16px - 14px - 44px - 8px);
  /* The box spans almost the full card width, so text-align (which follows
     dir: start = left in LTR) decides which side it visually reads on, not
     the box's own position. Pinned to physical right so the message reads
     on the right in every language, matching the fixed `right: 16px` above. */
  text-align: right;
  font-size: 18px;
  font-weight: 800;
  color: var(--text);
  pointer-events: none;
  z-index: 2;
}

.graph-fact {
  margin-top: 4px;
  font-size: 12px;
  font-weight: 400;
  color: var(--text-light);
  line-height: 1.4;
  /* The greeting box spans nearly the whole card, so on a wide viewport a
     fact renders as one long line that reaches left across the branch photo
     (drawn anchored to the card's top-left corner). Cap the measure so the
     longest fact (~100 chars at this size) breaks into two lines and both
     stay in the right-hand column, clear of the branch; margin-left: auto
     keeps the narrowed box against the physical right edge in either dir,
     matching .graph-greeting's fixed `right: 16px`. */
  max-width: 21em;
  margin-left: auto;
  /* and never more than two lines, in case a longer fact is added later */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
}

.graph-tip {
  position: absolute;
  transform: translate(-50%, -100%);
  background: rgba(20, 22, 27, 0.95);
  color: #fff;
  border: 1px solid rgba(255, 255, 255, 0.15);
  padding: 4px 10px;
  border-radius: 8px;
  font-size: 12px;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity .1s;
  z-index: 2;
}

.graph-tip.show {
  opacity: 1;
}

/* Fixed to the physical left regardless of locale direction — the graph
   itself (canvas, avatar placement) isn't mirrored for RTL either, so
   pinning the greeting/mute-button pair to right/left keeps them consistent
   with the graph rather than flipping independently of it. */
.graph-mute-btn {
  position: absolute;
  top: 14px;
  left: 14px;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: none;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 20px;
  line-height: 1;
  background: rgba(255, 255, 255, 0.85);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12), 0 0 0 2px rgba(0, 0, 0, 0.05) inset;
  cursor: pointer;
  z-index: 3;
  transition: transform .12s, box-shadow .12s, background .15s;
}

.graph-mute-btn:hover {
  transform: scale(1.08);
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.16), 0 0 0 2px rgba(0, 0, 0, 0.06) inset;
}

.graph-mute-btn:active {
  transform: scale(0.94);
}

.graph-mute-btn.is-muted {
  background: rgba(255, 235, 235, 0.9);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12), 0 0 0 2px rgba(255, 71, 87, 0.25) inset;
}

.empty-state.small-empty {
  padding: 16px 20px;
  font-size: 14px;
}

.row {
  display: flex;
  align-items: center;
  gap: 10px;
}

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

.col {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.grow {
  flex: 1;
}

.mt8 {
  margin-top: 8px;
}

.mt12 {
  margin-top: 12px;
}

.mt16 {
  margin-top: 16px;
}

.mt24 {
  margin-top: 24px;
}

.mb8 {
  margin-bottom: 8px;
}

.mb12 {
  margin-bottom: 12px;
}

.mb16 {
  margin-bottom: 16px;
}

.center {
  text-align: center;
}

.muted {
  color: var(--text-light);
  font-size: 14px;
}

.small {
  font-size: 13px;
}

h1 {
  font-size: 22px;
  font-weight: 800;
  margin: 4px 0 16px;
}

h2 {
  font-size: 18px;
  font-weight: 700;
  margin: 0 0 8px;
}

h3 {
  font-size: 15px;
  font-weight: 700;
  margin: 0;
}

.stat-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: var(--surface);
  border-radius: 20px;
  padding: 6px 12px;
  font-weight: 700;
  font-size: 14px;
  box-shadow: 4px 4px 9px var(--neu-dark), -4px -4px 9px var(--neu-light);
}

.hearts-pill {
  cursor: pointer;
}

.disclosure-btn {
  display: flex;
  width: 100%;
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  text-decoration: none;
}

.disclosure-chevron {
  color: var(--gray-line);
  transition: transform 0.15s ease;
}

.disclosure-btn.open .disclosure-chevron {
  transform: rotate(180deg);
}

#daily-goal-picker.hidden {
  display: none;
}

.plan-option {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  border: 2px solid var(--gray-line);
  border-radius: 14px;
  padding: 12px 14px;
}

.plan-option+.plan-option {
  margin-top: 10px;
}

.topbar {
  position: sticky;
  top: 0;
  background: var(--gray-bg);
  padding: 10px 0;
  z-index: 5;
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 4px;
}

.back-btn {
  background: var(--surface);
  border-radius: 50%;
  width: 36px;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 18px;
  flex-shrink: 0;
  box-shadow: 3px 3px 8px var(--neu-dark), -3px -3px 8px var(--neu-light);
}

.back-btn:active {
  box-shadow: inset 2px 2px 6px var(--neu-dark), inset -2px -2px 6px var(--neu-light);
}

.progress-bar-track {
  background: var(--gray-line);
  border-radius: 10px;
  height: 14px;
  overflow: hidden;
}

.progress-bar-fill {
  background: var(--green);
  height: 100%;
  border-radius: 10px;
  transition: width .3s;
}

/* ---------- Auth ---------- */
.auth-wrap {
  padding-top: 40px;
}

.auth-title {
  text-align: center;
  font-weight: 800;
  font-size: 22px;
  margin-bottom: 8px;
}

.auth-logo-wrap {
  text-align: center;
  margin-bottom: 20px;
}

.auth-logo {
  height: 270px;
  width: auto;
}

/* Light/dark logo variants: only the one matching the browser's color
   scheme is shown; the other stays hidden (see prefers-color-scheme block). */
.logo-dark {
  display: none;
}

.auth-subtitle {
  text-align: center;
  color: var(--text-light);
  font-size: 14px;
  line-height: 1.6;
  max-width: 320px;
  margin: 0 auto 24px;
}

.auth-divider {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--text-light);
  font-size: 13px;
}

.auth-divider::before,
.auth-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--gray-line);
}

.google-btn-wrap {
  display: flex;
  justify-content: center;
  overflow: hidden;
}

.turnstile-wrap {
  display: flex;
  justify-content: center;
}

input[type=text],
input[type=password],
input[type=tel],
input[type=number] {
  width: 100%;
  padding: 14px;
  border-radius: 14px;
  border: 2px solid var(--gray-line);
  font-size: 16px;
  font-family: inherit;
  margin-bottom: 12px;
  background: var(--surface);
  color: var(--text);
}

input:focus {
  outline: none;
  border-color: var(--blue);
}

.field-error {
  color: var(--red);
  font-size: 13px;
  margin: -6px 0 10px;
}

textarea,
select {
  width: 100%;
  padding: 14px;
  border-radius: 14px;
  border: 2px solid var(--gray-line);
  font-size: 16px;
  font-family: inherit;
  margin-bottom: 12px;
  background: var(--surface);
  color: var(--text);
  resize: vertical;
}

textarea:focus,
select:focus {
  outline: none;
  border-color: var(--blue);
}

.field-label {
  font-weight: 700;
  font-size: 14px;
  margin: 4px 0 8px;
}

.feedback-category-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
  margin-bottom: 16px;
}

.feedback-category-option {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 16px 8px;
  border-radius: 14px;
  border: 2px solid var(--gray-line);
  background: var(--surface);
  cursor: pointer;
  font-weight: 700;
  font-size: 14px;
  transition: transform 0.1s ease;
}

.feedback-category-option:active {
  transform: scale(0.96);
}

.feedback-category-option .emoji {
  font-size: 26px;
}

.feedback-category-option.selected {
  border-color: var(--blue);
  background: var(--blue-light, var(--tint-blue));
}

#fb-problem-fields.hidden {
  display: none;
}

/* ---------- Account settings page ---------- */
.account-section-label {
  font-size: 13px;
  font-weight: 700;
  color: var(--text-light);
  margin: 14px 4px 6px;
}

.account-section-label:first-of-type {
  margin-top: 4px;
}

.account-danger-label {
  color: var(--red);
}

.card.account-card {
  padding: 14px 14px 12px;
}

.account-section-hint {
  margin: 0 0 12px;
  font-size: 12px;
  line-height: 1.5;
  color: var(--text-light);
}

.af-field {
  margin-bottom: 10px;
}

.af-field.mb0 {
  margin-bottom: 0;
}

/* Pairs up short fields (name/name, sex/age) side by side on anything but
   the narrowest phones, instead of every field taking the full card width —
   two rows saved is most of what keeps this form on one screen, and on
   desktop a lone "25" in a 640px-wide input reads as an oversized, mostly-
   empty box. Below 380px they stack, where half a card is too cramped. */
@media (min-width: 380px) {
  .af-row {
    display: flex;
    gap: 10px;
  }

  .af-row .af-field {
    flex: 1;
    min-width: 0;
  }
}

.af-label {
  display: block;
  font-weight: 700;
  font-size: 13px;
  margin-bottom: 4px;
  color: var(--text);
}

input.af-input,
select.af-input {
  width: 100%;
  padding: 9px 12px;
  border-radius: 10px;
  border: 1.5px solid var(--gray-line);
  font-size: 15px;
  font-family: inherit;
  background: var(--gray-bg);
  color: var(--text);
  transition: border-color .15s ease, background-color .15s ease, box-shadow .15s ease;
}

.af-input::placeholder {
  color: var(--text-light);
}

.af-input:hover {
  border-color: var(--gray-line);
}

.af-input:focus {
  outline: none;
  border-color: var(--green);
  background: var(--surface);
  box-shadow: 0 0 0 4px rgba(88, 204, 2, 0.15);
}

.af-input:disabled {
  opacity: .6;
  cursor: not-allowed;
}

select.af-select {
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%238B93A1' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
  background-repeat: no-repeat;
  /* The dropdown arrow belongs at the END of the field, opposite the label
     text — physically left in RTL, right in LTR. background-position has no
     logical keyword, so the two directions are spelled out; padding uses the
     logical property so the text never runs under the arrow. */
  background-position: left 12px center;
  padding-inline-end: 34px;
}

:root[dir="ltr"] select.af-select {
  background-position: right 12px center;
}

.account-save-btn {
  margin-top: 4px;
  padding: 11px 16px;
  font-size: 15px;
}

.account-save-btn:disabled {
  opacity: .7;
  cursor: not-allowed;
}

.card.account-danger-card {
  background: rgba(242, 84, 91, 0.05);
  border-color: rgba(242, 84, 91, 0.3);
}

.account-danger-copy {
  margin: 0 0 10px;
  font-size: 12px;
  line-height: 1.5;
  color: var(--text-light);
}

.account-buttons-row {
  display: flex;
  gap: 10px;
}

.account-buttons-row .btn {
  width: auto;
  flex: 1;
}

.btn-sm {
  padding: 10px 12px;
  font-size: 13px;
  border-radius: 12px;
}

.btn:focus-visible,
.af-input:focus-visible {
  outline: 3px solid rgba(88, 204, 2, 0.5);
  outline-offset: 2px;
}

/* ---------- Tracks / levels ---------- */
.track-card {
  border-color: var(--gray-line);
}

.level-badge {
  width: 46px;
  height: 46px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 800;
  color: #fff;
  font-size: 14px;
  flex-shrink: 0;
}

.unit-header {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 22px 0 10px;
}

.unit-icon {
  font-size: 26px;
}

.lesson-node {
  display: flex;
  align-items: center;
  gap: 12px;
  background: var(--surface);
  border: 2px solid var(--gray-line);
  border-radius: 14px;
  padding: 12px 14px;
  margin-bottom: 10px;
}

.lesson-circle {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 18px;
  color: #fff;
  background: var(--green);
}

.lesson-circle.done {
  background: var(--yellow);
}

/* ---------- Exercise player ---------- */
/* The viewport's vertical space is shared with two things outside #app:
   the sticky .app-header (56px, now a fixed height) above it, and the
   fixed .tabbar below it (which #app reserves 76px of bottom padding
   for, on top of its own 16px top padding = 92px). This wrap's
   min-height must subtract all of that (56 + 92 = 148px), otherwise it
   overshoots the real visible viewport and the check button sits below
   the fold. */
.exercise-wrap {
  display: flex;
  flex-direction: column;
  min-height: calc(100vh - 148px);
  min-height: calc(100dvh - 148px);
}

.hearts-row {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 15px;
  font-weight: 700;
}

.exercise-body {
  flex: 1;
  padding-top: 18px;
}

.prompt-text {
  font-size: 20px;
  font-weight: 700;
  margin-bottom: 18px;
  line-height: 1.8;
}

/* A prompt with a Persian cue line and a separate target-language (e.g.
   English) line (see FILL_BLANK's "{from_text}\n{blanked}" prompt) is
   split client-side into one <div dir="auto"> per line — dir="auto" lets
   the browser's own bidi algorithm pick ltr/rtl per line instead of the
   whole block inheriting the page's dir="rtl", so an embedded English
   sentence reads left-to-right on its own line instead of being
   bidi-mangled inside the surrounding Persian paragraph. */
.prompt-line+.prompt-line {
  margin-top: 6px;
}

/* Dialogue/passage context shown above a subunit's exercises for the
   conversation/read kinds — same card language as .card, styled like a
   quote block so it reads as reference text, not the question itself. */
.subunit-context-block {
  border: 2px solid var(--gray-line);
  /* Quote-block accent bar: belongs on the side the text starts from, which
     flips with the app language — physically right in RTL, left in LTR. */
  border-inline-start: 4px solid var(--blue);
  border-radius: 14px;
  padding: 14px 16px;
  margin-bottom: 18px;
  font-size: 15px;
  line-height: 1.9;
  color: var(--text-light);
  background: var(--surface-alt);
}

.exercise-report-row {
  /* The flag sits at the FAR end of the row, away from where the eye starts
     reading. In RTL that end is physically left, in LTR it is right, so this
     has to be logical: a hardcoded `left` put the button under the start of
     the prompt in an LTR locale. */
  text-align: end;
  margin-bottom: 4px;
}

.exercise-report-btn {
  border: none;
  background: none;
  color: var(--text-light);
  font-size: 13px;
  padding: 4px 2px;
  opacity: .6;
}

.exercise-report-btn:hover {
  opacity: 1;
}

.play-audio-btn {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  background: var(--blue);
  color: #fff;
  font-size: 26px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-bottom: 4px solid var(--blue-dark);
  margin-bottom: 20px;
}

.option-btn {
  display: block;
  width: 100%;
  text-align: right;
  padding: 14px 16px;
  border: 2px solid var(--gray-line);
  border-bottom-width: 4px;
  border-radius: 14px;
  background: var(--surface);
  font-size: 16px;
  font-weight: 600;
  margin-bottom: 10px;
}

.option-btn.selected {
  border-color: var(--blue);
  background: var(--tint-blue);
}

.option-btn.correct {
  border-color: var(--green);
  background: var(--tint-green);
  color: var(--green-dark);
}

.option-btn.wrong {
  border-color: var(--red);
  background: var(--tint-red);
  color: var(--red-dark);
}

.image-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 12px;
  margin-bottom: 20px;
}

.image-option {
  border: 2px solid var(--gray-line);
  border-bottom-width: 4px;
  border-radius: 14px;
  background: var(--surface);
  padding: 8px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
}

.image-option img {
  width: 100%;
  max-width: 140px;
  aspect-ratio: 1 / 1;
  object-fit: contain;
  display: block;
  margin: 0 auto;
}

.image-option span {
  font-size: 14px;
  color: var(--text-light);
  text-align: center;
}

/* The emoji tile stands in for a photo (see Meaning.emoji), so it occupies the
   same square as .image-option img rather than flowing as text — the grid must
   not reshuffle when one option has a picture and the next only has a glyph.
   The font-size rule comes after `.image-option span` on purpose: the label is
   also a span. */
.image-option .image-option-emoji {
  width: 100%;
  max-width: 140px;
  aspect-ratio: 1 / 1;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 56px;
  line-height: 1;
}

.image-option.selected {
  border-color: var(--blue);
  background: var(--tint-blue);
}

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

.image-option.wrong {
  border-color: var(--red);
  background: var(--tint-red);
}

.flashcard {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 10px;
  padding: 16px 0 24px;
}

.flashcard-image {
  width: 100%;
  max-width: 160px;
  aspect-ratio: 1 / 1;
  object-fit: contain;
  border-radius: 16px;
  background: var(--surface-alt);
  margin-bottom: 8px;
}

.flashcard-emoji {
  width: 100%;
  max-width: 160px;
  aspect-ratio: 1 / 1;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 80px;
  line-height: 1;
  border-radius: 16px;
  background: var(--surface-alt);
  margin-bottom: 8px;
}

.flashcard-image-playable {
  cursor: pointer;
  transition: transform 0.15s ease;
}

.flashcard-image-playable:active {
  transform: scale(0.96);
}

.flashcard-word {
  font-size: 28px;
  font-weight: 800;
}

.flashcard-translation {
  font-size: 17px;
  color: var(--text-light);
}

.wordbank-area {
  min-height: 56px;
  border-bottom: 2px solid var(--gray-line);
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 8px 2px;
  margin-bottom: 20px;
  /* The sentence being built (word-bank/word-order exercises) is always
     in the target language (e.g. English), so it must lay out left-to-
     right and fill left-to-right, even on the page's dir="rtl" layout —
     otherwise tapped chips stack right-to-left and the assembled
     sentence reads backwards. */
  direction: ltr;
  justify-content: flex-start;
}

.wordbank-pool {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  direction: ltr;
  justify-content: flex-start;
}

.chip {
  padding: 10px 14px;
  border-radius: 10px;
  border: 2px solid var(--gray-line);
  border-bottom-width: 3px;
  background: var(--surface);
  font-weight: 600;
  font-size: 15px;
}

.chip.used {
  opacity: .35;
  pointer-events: none;
}

.match-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
  margin-bottom: 20px;
}

.match-col {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.match-item {
  padding: 12px 10px;
  border: 2px solid var(--gray-line);
  border-bottom-width: 4px;
  border-radius: 12px;
  background: var(--surface);
  font-size: 15px;
  font-weight: 600;
  text-align: center;
}

.match-item.selected {
  border-color: var(--blue);
  background: var(--tint-blue);
}

.match-item.correct {
  border-color: var(--green);
  background: var(--tint-green);
  color: var(--green-dark);
  opacity: .6;
}

.match-item.wrong {
  border-color: var(--red);
  background: var(--tint-red);
  color: var(--red-dark);
}

.match-item:disabled {
  pointer-events: none;
}

.speak-recorder {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding: 16px 0;
}

.record-btn {
  padding: 14px 28px;
  border: 2px solid var(--gray-line);
  border-bottom-width: 4px;
  border-radius: 999px;
  background: var(--surface);
  font-size: 16px;
  font-weight: 700;
}

.record-btn.recording {
  border-color: var(--red);
  background: var(--tint-red);
  color: var(--red-dark);
  animation: record-pulse 1.2s ease-in-out infinite;
}

@keyframes record-pulse {

  0%,
  100% {
    opacity: 1;
  }

  50% {
    opacity: .6;
  }
}

.playback-audio {
  width: 100%;
  max-width: 320px;
}

.feedback-bar {
  position: sticky;
  bottom: 0;
  margin: 0 -16px -16px;
  padding: 16px;
  border-top: 2px solid var(--gray-line);
}

.feedback-bar.ok {
  background: var(--tint-green);
  border-color: #b6e88d;
}

.feedback-bar.bad {
  background: var(--tint-red);
  border-color: #ffb3b6;
}

.feedback-title {
  font-weight: 800;
  font-size: 17px;
  margin-bottom: 2px;
}

.feedback-title.ok-text {
  color: var(--green-dark);
}

.feedback-title.bad-text {
  color: var(--red-dark);
}

.feedback-answer {
  font-size: 14px;
  color: var(--text);
  margin-bottom: 10px;
}

/* ---------- Result screens ---------- */
.result-wrap {
  text-align: center;
  padding-top: 40px;
}

.result-emoji {
  font-size: 72px;
  margin-bottom: 8px;
}

.result-mascot {
  width: 170px;
  height: 170px;
  object-fit: contain;
  margin: 0 auto 8px;
  display: block;
}

.result-stat {
  display: flex;
  justify-content: space-between;
  align-items: center;
  background: var(--surface);
  border: 2px solid var(--gray-line);
  border-radius: 14px;
  padding: 14px 18px;
  margin-bottom: 10px;
  font-weight: 700;
}

/* ---------- Media ---------- */
.media-filter {
  display: flex;
  gap: 8px;
  margin-bottom: 14px;
}

.filter-chip {
  padding: 8px 16px;
  border-radius: 20px;
  border: 2px solid var(--gray-line);
  background: var(--surface);
  font-weight: 700;
  font-size: 14px;
}

.filter-chip.active {
  background: var(--blue);
  border-color: var(--blue-dark);
  color: #fff;
}

.media-thumb {
  width: 100%;
  aspect-ratio: 40/10;
  border-radius: 12px;
  background: linear-gradient(135deg, var(--purple), var(--blue));
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 34px;
  color: #fff;
  margin-bottom: 10px;
}

.media-prose {
  line-height: 1.8;
}

.media-prose p {
  margin: 0 0 12px;
}

.media-prose p:last-child {
  margin-bottom: 0;
}

/* One stanza of the transcript: the number in the gutter, the lines, then
   the translation of the stanza as a whole (translation lives on the
   segment, not the line — see data/media-authoring-format.md). */
.media-segment {
  padding: 12px 8px;
  border-radius: 12px;
  margin-bottom: 8px;
  cursor: pointer;
}

.media-segment:hover {
  background: var(--bg-soft, rgba(0, 0, 0, 0.03));
}

.media-segment-num {
  float: inline-start;
  min-width: 24px;
}

.media-line {
  font-size: 16px;
  padding: 4px 6px;
  border-radius: 8px;
  line-height: 1.9;
}

.media-line.active {
  background: var(--tint-yellow);
}

.media-translation {
  color: var(--text-light);
  font-size: 14px;
  margin-top: 6px;
  line-height: 1.7;
}

/* Session view: the stanza is the whole screen, so the lines get room. */
.media-lines-lg .media-line {
  font-size: 22px;
  line-height: 2.1;
}

.media-session-card {
  min-height: 180px;
  display: flex;
  align-items: center;
  cursor: pointer;
}

.media-session-nav {
  display: flex;
  gap: 12px;
}

.media-session-nav .btn {
  flex: 1;
}

@media (max-width: 480px) {
  .media-lines-lg .media-line {
    font-size: 19px;
  }
}

/* ---------- Leaderboard ---------- */
/* Shared by the full /leaderboard page and the desktop sidebar widget, so
   both read as the same list. A standings table is scanned, not read: the
   name carries the weight and rank/XP sit back a step. */
.lb-league {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font-size: 14px;
  font-weight: 700;
  color: var(--text-light);
  margin-bottom: 10px;
}

.lb-card {
  padding: 6px 10px;
}

.lb-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 6px;
  border-radius: 10px;
  font-size: 14px;
  /* Rows are <a>s into profiles — they should read as list rows, not links. */
  color: inherit;
  text-decoration: none;
}

/* Dividers go BETWEEN rows, never after the last one — a border-bottom on
   every row leaves a stray line floating above the card's padding. */
.lb-row+.lb-row {
  border-top: 1px solid var(--gray-line);
}

.lb-rank {
  width: 20px;
  flex-shrink: 0;
  text-align: center;
  font-size: 13px;
  font-weight: 700;
  color: var(--text-light);
  font-variant-numeric: tabular-nums;
}

.lb-row .grow {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.lb-xp {
  flex-shrink: 0;
  font-size: 12px;
  color: var(--text-light);
  font-variant-numeric: tabular-nums;
}

.lb-xp b {
  font-size: 14px;
  color: var(--text);
}

.lb-row.you {
  background: var(--tint-blue);
  font-weight: 700;
}

/* The tint is a solid block: no divider may cut across its top or bottom. */
.lb-row.you,
.lb-row.you+.lb-row {
  border-top-color: transparent;
}

/* ---------- Tab bar ---------- */
.tabbar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  background: var(--surface);
  border-top: 2px solid var(--gray-line);
  display: flex;
  z-index: 10;
  padding-bottom: env(safe-area-inset-bottom);
}

.tabbar.hidden {
  display: none;
}

.tab {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 8px 0 6px;
  font-size: 8.25px;
  font-weight: 700;
  color: var(--text-light);
}

.tab.active {
  color: var(--blue);
}

.tab-icon {
  width: 39px;
  height: 39px;
  object-fit: contain;
}

.toast {
  position: fixed;
  top: 14px;
  left: 50%;
  transform: translateX(-50%) translateY(-60px);
  background: #333;
  color: #fff;
  padding: 10px 18px;
  border-radius: 12px;
  font-size: 14px;
  z-index: 100;
  transition: transform .25s;
  max-width: 90%;
}

.toast.show {
  transform: translateX(-50%) translateY(0);
}

.empty-state {
  text-align: center;
  padding: 40px 20px;
  color: var(--text-light);
}

.empty-emoji {
  font-size: 44px;
  margin-bottom: 10px;
}

.empty-mascot {
  display: block;
  width: 120px;
  height: 120px;
  object-fit: contain;
  margin: 0 auto 10px;
}

hr.sep {
  border: none;
  border-top: 2px solid var(--gray-line);
  margin: 18px 0;
}



/* ===== Brand tokens (Azerbaijani-flag inspired, not Duolingo-green) ===== */
:root {
  --paper: #FAFAF7;
  --ink: #1B2430;
  --teal: #049A8F;
  --teal-dark: #037A71;
  --red: #F2545B;
  --leaf: #4CAF7D;
  --leaf-dark: #3C9C68;
  --gold: #FFB020;
  --purple: #7C5CFC;
  /* kept for existing track badges */
  --text-light: #8B93A1;
}

body {
  background: var(--paper);
  color: var(--ink);
  font-family: "Vazirmatn", sans-serif;
}

/* ===== Header + logo ===== */
.app-header {
  position: sticky;
  top: 0;
  z-index: 20;
  height: 56px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 16px;
  background: var(--paper);
  border-bottom: 2px solid var(--gray-line);
}

.logo {
  display: flex;
  align-items: center;
  text-decoration: none;
}

.header-logo {
  height: 28px;
  width: auto;
}

.header-stats {
  display: flex;
  gap: 8px;
}

/* Desktop-only: the nav column's own logo, shown instead of the top header
   (see .app-header/.nav-logo rules in the min-width: 900px block below). */
.nav-logo {
  display: none;
}

/* ===== Stat pills ===== */
.stat-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 9px;
  border-radius: 999px;
  background: var(--surface);
  font-weight: 700;
  font-size: 12px;
  box-shadow: 4px 4px 9px var(--neu-dark), -4px -4px 9px var(--neu-light);
}

.stat-pill.streak-pill {
  cursor: pointer;
  transition: transform .15s ease;
}

.stat-pill.streak-pill:active {
  transform: scale(.92);
}

/* ===== Admin monitoring dashboard ===== */
.stat-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
  margin-bottom: 14px;
}

.stat-tile {
  background: var(--surface);
  border: 2px solid var(--gray-line);
  border-bottom-width: 5px;
  border-radius: 16px;
  padding: 14px;
  text-align: center;
}

.stat-tile-emoji {
  font-size: 22px;
}

.stat-tile-value {
  font-size: 20px;
  font-weight: 800;
  color: var(--ink);
  margin-top: 2px;
}

.stat-tile-label {
  font-size: 13px;
  color: var(--text-light);
  margin-top: 2px;
}

.monitoring-card-title {
  margin: 0 0 10px;
  font-size: 15px;
}

.dataset-grid {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.dataset-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 6px 0;
  border-bottom: 1px solid var(--gray-line);
}

.dataset-row:last-child {
  border-bottom: none;
}

/* ===== Chunky "3D button" cards (Duolingo's actual trick: an offset
   bottom border you can press, not a soft drop shadow) ===== */
.card {
  background: var(--surface);
  border: 2px solid var(--gray-line);
  border-bottom-width: 5px;
  border-radius: 16px;
  padding: 16px;
  margin-bottom: 12px;
  transition: transform 0.05s ease, border-bottom-width 0.05s ease;
}

.card-clickable:active {
  transform: translateY(3px);
  border-bottom-width: 2px;
}

.track-card {
  border-color: var(--track-color, var(--teal));
  border-bottom-color: var(--track-color, var(--teal));
}

.level-badge {
  width: 44px;
  height: 44px;
  border-radius: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 22px;
  flex-shrink: 0;
}

/* ===== Daily goal progress ===== */
.card.daily-goal-card {
  border: none;
  box-shadow: 8px 8px 18px var(--neu-dark), -8px -8px 18px var(--neu-light);
}

.goal-track {
  height: 12px;
  border-radius: 999px;
  background: var(--gray-line);
  overflow: hidden;
  margin-top: 8px;
}

.daily-goal-card .goal-track {
  background: var(--surface);
  box-shadow: inset 3px 3px 6px var(--neu-dark), inset -3px -3px 6px var(--neu-light);
}

.goal-fill {
  height: 100%;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--leaf), var(--leaf-dark));
  transition: width 0.6s ease;
}

.streak-risk-row {
  align-items: center;
  gap: 10px;
  margin-top: 12px;
}

.streak-risk-mascot {
  width: 40px;
  height: 40px;
  object-fit: contain;
  flex-shrink: 0;
}

/* ===== Section headings use the display face too ===== */
h1,
h2 {
  font-family: "Vazirmatn", sans-serif;
  font-weight: 700;
}

h1 {
  font-size: 26px;
}

h2 {
  font-size: 20px;
  color: var(--ink);
}

.mt24 {
  margin-top: 24px;
}

.row {
  display: flex;
  align-items: center;
  gap: 12px;
}

.col {
  display: flex;
  flex-direction: column;
}

.grow {
  flex: 1;
}

.muted {
  color: var(--text-light);
}

.small {
  font-size: 13px;
}

.empty-state {
  text-align: center;
  padding: 40px 20px;
  color: var(--text-light);
}

.empty-emoji {
  font-size: 40px;
  margin-bottom: 8px;
}

/* ===== Bottom tab bar, chunkier ===== */
.tabbar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  display: flex;
  background: var(--surface);
  border-top: 2px solid var(--gray-line);
  padding: 3px 0 max(3px, env(safe-area-inset-bottom));
  z-index: 20;
}

.tab {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  padding: 4px 0;
  text-decoration: none;
  color: var(--text-light);
  font-size: 7.5px;
  font-weight: 600;
}

.tab.active {
  color: var(--teal-dark);
}

.tab-icon {
  width: 39px;
  height: 39px;
  object-fit: contain;
}



.avatar-circle {
  width: 84px;
  height: 84px;
  border-radius: 50%;
  border: none;
  font-size: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 auto 8px;
  cursor: pointer;
  transition: transform 0.1s ease;
}

.avatar-circle:active {
  transform: scale(0.94);
}

.link-btn {
  background: none;
  border: none;
  color: var(--teal-dark);
  font-weight: 700;
  font-size: 14px;
  cursor: pointer;
  padding: 4px;
}

.avatar-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 10px;
  margin-top: 14px;
}

.avatar-grid.hidden {
  display: none;
}

.avatar-option {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 10px 4px;
  border-radius: 14px;
  border: 2px solid transparent;
  cursor: pointer;
  transition: transform 0.1s ease;
}

.avatar-option:active {
  transform: scale(0.95);
}

.avatar-option.selected {
  border-width: 3px;
  box-shadow: 0 0 0 2px var(--surface) inset;
}

.toast {
  position: fixed;
  top: -60px;
  left: 50%;
  transform: translateX(-50%);
  background: var(--overlay-chip);
  color: #fff;
  padding: 10px 18px;
  border-radius: 12px;
  font-size: 14px;
  font-weight: 600;
  transition: top 0.3s ease;
  z-index: 50;
}

.toast.show {
  top: 16px;
}







/* ===== Circle path (one circle per unit, each a ring of levels) ===== */
.trail-wrap {
  position: relative;
  padding: 12px 0 40px;
}

#trail-svg {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.node-layer {
  position: relative;
  z-index: 2;
}

/* Course-name divider inside a Phase's continuous multi-course path (see
   renderPhasePath in app.js) — height must match its headerSlotHeight JS
   constant (64px) so the SVG curve overlay stays in sync with the DOM. */
.path-section-header {
  height: 64px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.path-section-header span {
  font-size: 13px;
  font-weight: 700;
  color: var(--text-light);
  background: var(--gray-bg);
  padding: 4px 16px;
  border: 2px solid var(--gray-line);
  border-radius: 999px;
}

.circle-slot {
  position: relative;
  height: 140px;
}

.mascot-decor {
  position: absolute;
  top: 24px;
  width: 120px;
  height: 120px;
  object-fit: contain;
  filter: drop-shadow(0 4px 6px rgba(0, 0, 0, .18));
  animation: mascotBob 2.8s ease-in-out infinite;
  pointer-events: none;
  z-index: 1;
}

@keyframes mascotBob {

  0%,
  100% {
    transform: translateY(0) rotate(var(--mascot-rot, 0deg));
  }

  50% {
    transform: translateY(-10px) rotate(var(--mascot-rot, 0deg));
  }
}

.unit-circle {
  position: absolute;
  top: 12px;
  width: 88px;
  height: 88px;
  border-radius: 50%;
  padding: 6px;
  cursor: pointer;
  transition: transform .05s ease;
}

.unit-circle:active {
  transform: scale(.95);
}

.unit-circle-inner {
  width: 100%;
  height: 100%;
  border-radius: 50%;
  background: var(--surface);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 34px;
  box-shadow: 6px 6px 14px var(--neu-dark), -6px -6px 14px var(--neu-light);
}

.unit-circle.locked {
  cursor: default;
}

.unit-circle.locked .unit-circle-inner {
  color: var(--text-light);
  /* Locked reads as "pressed in" — the shadow direction reverses to
     look recessed rather than raised, which happens to double as a
     legible locked affordance for free. */
  box-shadow: inset 4px 4px 10px var(--neu-dark), inset -4px -4px 10px var(--neu-light);
}

.unit-circle.locked.shake {
  animation: nodeShake .35s;
}

@keyframes nodeShake {

  0%,
  100% {
    transform: translateX(0);
  }

  25% {
    transform: translateX(-6px);
  }

  75% {
    transform: translateX(6px);
  }
}

.unit-circle.solved .unit-circle-inner {
  background: var(--tint-yellow);
}

.unit-circle-label {
  position: absolute;
  top: 100px;
  transform: translateX(-50%);
  width: 150px;
  text-align: center;
  font-size: 13px;
  font-weight: 700;
  color: var(--text-light);
}

.exam-slot {
  display: flex;
  flex-direction: column;
  align-items: center;
  position: relative;
  z-index: 2;
}

.exam-slot-top {
  margin-bottom: 16px;
}

.exam-node {
  width: 96px;
  height: 96px;
  border-radius: 50%;
  cursor: pointer;
  background: linear-gradient(160deg, #FFCA5C, var(--gold));
  border: 2px solid #C98600;
  border-bottom-width: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 42px;
  animation: trophyFloat 3s ease-in-out infinite;
  transition: transform .05s ease, border-bottom-width .05s ease;
}

.exam-node:active {
  transform: translateY(4px);
  border-bottom-width: 2px;
}

.exam-node.locked {
  filter: grayscale(1) opacity(.6);
  animation: none;
}

.exam-node.locked.shake {
  animation: nodeShake .35s;
}

@keyframes trophyFloat {

  0%,
  100% {
    transform: translateY(0) rotate(0deg);
  }

  50% {
    transform: translateY(-6px) rotate(-3deg);
  }
}

.checkpoint-slot {
  display: flex;
  flex-direction: column;
  align-items: center;
  position: relative;
  z-index: 2;
  margin-bottom: 8px;
}

.checkpoint-node {
  width: 76px;
  height: 76px;
  border-radius: 50%;
  cursor: pointer;
  background: linear-gradient(160deg, var(--tint-blue), var(--blue));
  border: 2px solid var(--blue-dark);
  border-bottom-width: 5px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 32px;
  transition: transform .05s ease, border-bottom-width .05s ease;
}

.checkpoint-node:active {
  transform: translateY(3px);
  border-bottom-width: 2px;
}

.checkpoint-node.passed {
  background: linear-gradient(160deg, var(--tint-green), var(--green));
  border-color: var(--green-dark);
}

/* ---- unit click popover — small box anchored next to the clicked circle ---- */
.unit-popover-overlay {
  position: fixed;
  inset: 0;
  background: transparent;
  opacity: 0;
  pointer-events: none;
  transition: opacity .15s ease;
  z-index: 50;
}

.unit-popover-overlay.open {
  opacity: 1;
  pointer-events: auto;
}

.unit-popover {
  position: absolute;
  width: 240px;
  background: var(--paper);
  border-radius: 18px;
  padding: 20px 16px 16px;
  text-align: center;
  box-shadow: 0 10px 34px rgba(27, 36, 48, .28);
  transform: scale(.92);
  transition: transform .15s ease;
}

.unit-popover-overlay.open .unit-popover {
  transform: scale(1);
}

.unit-popover-icon {
  width: 60px;
  height: 60px;
  border-radius: 50%;
  margin: -44px auto 10px;
  background: var(--teal);
  color: #fff;
  font-size: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 4px solid var(--paper);
}

.unit-popover h2 {
  margin: 0 0 4px;
  font-size: 16px;
}

.unit-popover p.muted {
  margin: 0 0 12px;
  font-size: 13px;
}

.unit-popover .btn {
  padding: 8px 12px;
  font-size: 14px;
}

/* ---- centered modal — for congrats / message-style overlays, room for a gif ---- */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(27, 36, 48, .45);
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity .2s ease;
  z-index: 60;
  padding: 20px;
}

.modal-overlay.open {
  opacity: 1;
  pointer-events: auto;
}

.modal-box {
  width: 100%;
  max-width: 380px;
  background: var(--paper);
  border-radius: 24px;
  padding: 28px 24px 24px;
  text-align: center;
  transform: scale(.9);
  opacity: 0;
  transition: transform .25s ease, opacity .25s ease;
}

.modal-overlay.open .modal-box {
  transform: scale(1);
  opacity: 1;
}

.modal-box img,
.modal-box video {
  max-width: 100%;
  border-radius: 16px;
  margin-bottom: 12px;
}

.modal-mascot {
  width: 140px;
  height: 140px;
  object-fit: contain;
  border-radius: 0;
  margin: 0 auto 8px;
}

/* The hearts/XP modal is a status read-out, not a celebration: it carries a
   heading, a note, one action and the plan list, so the congrats sizing
   (72px emoji, 26px h1) pushed it past the fold on a phone. .modal-compact
   keeps the same box at informational scale. */
.modal-compact {
  max-width: 320px;
  padding: 18px 18px 16px;
}

.modal-compact .result-emoji {
  font-size: 40px;
  line-height: 1.1;
  margin-bottom: 4px;
}

.modal-compact h1 {
  font-size: 19px;
  margin: 0 0 6px;
}

.modal-compact p {
  margin: 0 0 8px;
}

.modal-compact .btn {
  padding: 10px 14px;
  font-size: 15px;
}

.modal-compact .mt16 {
  margin-top: 12px;
}

.modal-compact .plan-option {
  padding: 8px 10px;
  border-radius: 12px;
}

.modal-compact .plan-option+.plan-option {
  margin-top: 8px;
}

/* ===========================================================================
   Desktop layout (>=900px): three columns, addressed LOGICALLY. Column 1 is
   the inline-start edge, which is physically right in an RTL locale and left
   in an LTR one — so the nav is column 1 and the leaderboard/blog sidebar is
   column 3, and both land on the correct side in either direction. <html dir>
   now follows the learner's app language (see applyLocaleChrome in app.js),
   so nothing here may assume RTL. Below 900px, none of this applies: phone
   layout (top hearts/xp bar, bottom tab bar) is untouched.
   Which routes get the sidebar is decided in JS — see
   SIDEBAR_ROUTE_SEGMENTS in app.js — via the body.has-sidebar class.
   =========================================================================== */
.sidebar-left {
  display: none;
}

@media (min-width: 900px) {
  body {
    display: grid;
    grid-template-columns: 286px 1fr 390px;
    min-height: 100vh;
  }

  body:not(.has-sidebar) {
    grid-template-columns: 286px 1fr 0;
  }

  /* Logged-out screens (login/register) hide the tab bar entirely — collapse
     its column too instead of leaving an empty gutter on the right. */
  body:has(#tabbar.hidden) {
    grid-template-columns: 0 1fr 0;
  }

  /* No shared top bar on desktop — the logo lives at the top of the nav
     column instead (see .nav-logo below), and the header-stats pills move
     into the sidebar's top (or stay hidden on routes with no sidebar). */
  .app-header {
    display: none;
  }

  #app {
    grid-column: 2;
    grid-row: 1;
    max-width: 640px;
    width: 100%;
    margin: 0 auto;
    padding: 24px 24px 40px;
  }

  .sidebar-left {
    grid-column: 3;
    grid-row: 1;
    position: sticky;
    top: 0;
    align-self: start;
    height: 100vh;
    overflow-y: auto;
    padding: 20px 16px;
    /* Logical, not physical: this column is grid-column 3, so the edge facing
       the content is its inline-START side — physically left in LTR, right in
       RTL. A hardcoded border-right drew it on the outer edge in LTR. */
    border-inline-start: 2px solid var(--gray-line);
  }

  body.has-sidebar .sidebar-left {
    display: block;
  }

  .sidebar-stats {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 20px;
  }

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

  /* Daily goal card sits at the top of this column, above the leaderboard.
     :empty covers the routes/accounts where the API sends no goal fields —
     no card, no gap. */
  .sidebar-goal {
    margin-bottom: 20px;
  }

  .sidebar-goal:empty {
    margin-bottom: 0;
  }

  /* ...which makes the inline copy on /home a duplicate up here. Hide it and
     drop the margin it was carrying for the graph below it. */
  .home-goal-slot {
    display: none;
  }

  .home-goal-slot+#concept-graph-wrap.mt24 {
    margin-top: 0;
  }

  /* Nav becomes a vertical column pinned to the inline-start edge instead
     of a bar fixed to the bottom of the viewport — physically right in RTL,
     left in LTR. */
  .tabbar {
    position: sticky;
    top: 0;
    align-self: start;
    grid-column: 1;
    grid-row: 1;
    bottom: auto;
    left: auto;
    right: auto;
    height: 100vh;
    flex-direction: column;
    justify-content: flex-start;
    gap: 6px;
    padding: 16px 0 24px;
    border-top: none;
    /* grid-column 1, so the edge facing the content is inline-END. */
    border-inline-end: 2px solid var(--gray-line);
    /* Mobile's tab bar sits on --surface (a card-like white); on desktop
       it's a plain column like .sidebar-left, so match its background
       (--paper, i.e. same as the page) instead. */
    background: var(--paper);
  }

  .nav-logo {
    display: flex;
    justify-content: center;
    padding: 18px 0 30px;
  }

  .nav-logo .header-logo {
    height: 42px;
  }

  .tab {
    flex: none;
    flex-direction: row;
    justify-content: flex-start;
    gap: 18px;
    padding: 18px 36px;
    font-size: 15.75px;
  }

  .tab-icon {
    width: 67.5px;
    height: 67.5px;
    object-fit: contain;
  }

  /* The mobile graph goes full-bleed via negative viewport margins; on
     desktop it should just fill its own (centered) column instead. Kept
     position: relative (not static) — .graph-greeting/.graph-mute-btn are
     positioned absolute against this element; losing that containing block
     sends them to the page's top-left corner instead, which lands them
     inside the left sidebar column. */
  .graph-card {
    position: relative;
    left: auto;
    right: auto;
    width: 100%;
    margin-left: 0;
    margin-right: 0;
    /* The graph is always the last thing in #app on /home now (the
       "more courses" list that used to follow it is gone) — cancel #app's
       bottom padding so its bottom edge reaches the same y as the nav/
       sidebar columns, which are a flat 100vh with no such padding. */
    margin-bottom: -40px;
    border-radius: 16px;
    border-left: 2px solid var(--gray-line);
    border-right: 2px solid var(--gray-line);
  }

  .graph-card.flush-top {
    /* Cancel #app's top padding too, so with no daily-goal card above it
       the graph starts at the same y as the nav/sidebar columns instead of
       being inset — otherwise it reads as shorter than them by that gap. */
    margin-top: -24px;
  }
}

@media (prefers-reduced-motion: reduce) {

  .unit-circle.locked.shake,
  .exam-node {
    animation: none !important;
  }
}

/* ===== Dark mode: follows the browser/OS color-scheme preference,
   no in-app toggle. Everything above is themed through these tokens, so
   this block only needs to redefine the tokens, not re-style components. */
@media (prefers-color-scheme: dark) {
  :root {
    --gray-bg: #1c2027;
    --gray-line: #2c323c;
    --text: #e7e9ec;
    --text-light: #97a1b0;

    --surface: #20242c;
    --surface-alt: #272c35;

    --paper: #14171c;
    --ink: #eceef0;

    /* Dark surfaces are close in lightness to their own shadows, so the
       light/dark split has to come from near-black vs. a faint white
       glow rather than the light-mode gray/white pairing. */
    --neu-dark: rgba(0, 0, 0, 0.55);
    --neu-light: rgba(255, 255, 255, 0.06);
  }

  .logo-light {
    display: none;
  }

  .logo-dark {
    display: inline-block;
  }
}

/* ===== Profile page — scoped shrink =====
   .card/.avatar-circle/.disclosure-btn/.stat-pill/h2 are shared across the
   whole app (home, lessons, etc.), so the size reduction is scoped to
   .profile-page rather than changing those rules globally. */
.profile-page .card {
  padding: 12px;
  margin-bottom: 8px;
  border-bottom-width: 4px;
}

.profile-page .avatar-circle {
  width: 64px;
  height: 64px;
  font-size: 30px;
}

.profile-page h2 {
  font-size: 16px;
}

.profile-page .stat-pill {
  padding: 4px 9px;
  font-size: 12px;
}

.profile-page .disclosure-btn {
  font-size: 14px;
}

.profile-page .row-between {
  font-size: 14px;
}

/* ===========================================================================
   SOCIAL — profile header, statistics tiles, achievements, the XP week
   chart, friend lists and the language snapshot.

   Sizes are deliberately a touch smaller than the app's defaults because the
   profile stacks six sections; the `.profile-page` scoped shrink above does
   the same for the shared components.
   =========================================================================== */

.profile-header {
  text-align: center;
  padding-top: 0 !important;
  overflow: hidden;
}

/* The tinted band the avatar sits on, as on the reference profiles — it also
   gives the card a horizon so the name does not float. */
.profile-header-banner {
  background: linear-gradient(180deg, var(--tint-yellow), var(--surface));
  margin: -12px -12px 8px;
  padding: 20px 12px 12px;
}

/* On your own profile the avatar is the way into account details — only
   there, so a stranger's picture stays inert. */
.profile-avatar-link {
  display: inline-block;
  border-radius: 50%;
  text-decoration: none;
  transition: transform .15s ease;
}

.profile-avatar-link:active {
  transform: scale(.96);
}

.profile-name {
  margin: 0;
}

.profile-meta-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-top: 10px;
}

.profile-friends-link {
  color: var(--blue);
  font-weight: 700;
  font-size: 14px;
  text-decoration: none;
}

.profile-flags {
  display: flex;
  gap: 4px;
  align-items: center;
}

.profile-flag {
  font-size: 18px;
  line-height: 1;
}

.profile-flag-more {
  font-size: 12px;
  font-weight: 700;
  color: var(--text-light);
  background: var(--surface-alt);
  border: 1px solid var(--gray-line);
  border-radius: 6px;
  padding: 2px 5px;
}

.profile-actions {
  display: flex;
  gap: 8px;
  margin-top: 12px;
}

.section-heading {
  font-size: 15px;
  margin: 18px 0 8px;
}

/* ----- Statistics tiles ----- */
.stat-tile-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin-bottom: 8px;
}

.stat-tile {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--surface);
  border: 2px solid var(--gray-line);
  border-radius: 12px;
  padding: 10px;
}

.stat-tile-emoji {
  font-size: 20px;
  line-height: 1;
}

.stat-tile-body {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.stat-tile-body b {
  font-size: 16px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ----- Achievements ----- */
.achievement-row {
  display: flex;
  gap: 10px;
  justify-content: center;
}

.achievement-badge {
  /* --badge-color is set per badge from the catalog (social/achievements.py),
     so one rule styles all nine and a new badge needs no CSS. */
  width: 84px;
  border-radius: 12px;
  padding: 10px 6px 6px;
  background: linear-gradient(160deg, var(--badge-color), color-mix(in srgb, var(--badge-color) 70%, #000));
  color: #fff;
  text-align: center;
}

.achievement-badge.locked {
  background: var(--gray-line);
  color: var(--text-light);
  filter: grayscale(1);
}

.achievement-emoji {
  font-size: 30px;
  line-height: 1.1;
}

.achievement-level {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  margin-top: 6px;
  opacity: 0.9;
}

.achievement-more {
  margin-top: 10px;
  border-top: 1px solid var(--gray-line);
  padding-top: 10px;
}

.achievement-card-head {
  display: flex;
  align-items: center;
  gap: 12px;
}

.achievement-progress {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 10px;
}

.achievement-bar,
.division-bar {
  flex: 1;
  height: 8px;
  border-radius: 4px;
  background: var(--gray-line);
  overflow: hidden;
}

.achievement-fill {
  display: block;
  height: 100%;
  border-radius: 4px;
  background: var(--badge-color, var(--green));
}

/* ----- XP this week ----- */
.xp-legend-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  padding: 2px 0;
}

.xp-legend-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  border: 2px solid var(--blue);
  background: var(--blue);
}

.xp-legend-dot.you {
  background: transparent;
  border-color: var(--text-light);
}

.xp-chart {
  width: 100%;
  height: auto;
  margin-top: 8px;
  /* The chart is drawn left-to-right from oldest to newest; that is a time
     axis, not text, so it must not mirror in RTL. */
  direction: ltr;
}

.xp-grid {
  stroke: var(--gray-line);
  stroke-width: 1;
}

.xp-axis {
  fill: var(--text-light);
  font-size: 9px;
}

.xp-line {
  fill: none;
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.xp-line.them {
  stroke: var(--blue);
}

.xp-line.you {
  stroke: var(--gray-line);
}

.xp-dot.them {
  fill: var(--blue);
}

.xp-dot.you {
  fill: var(--surface);
  stroke: var(--text-light);
  stroke-width: 2;
}

/* ----- Friends ----- */
.friend-tabs {
  display: flex;
  border-bottom: 2px solid var(--gray-line);
  margin-bottom: 8px;
}

.friend-tab {
  flex: 1;
  background: none;
  border: none;
  border-bottom: 3px solid transparent;
  margin-bottom: -2px;
  padding: 10px 4px;
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  color: var(--text-light);
  cursor: pointer;
}

.friend-tab.active {
  color: var(--blue);
  border-bottom-color: var(--blue);
}

.person-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 0;
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid var(--gray-line);
}

.person-row:last-child {
  border-bottom: none;
}

.person-row .avatar-circle {
  margin: 0;
  cursor: inherit;
}

.invite-link {
  background: var(--surface-alt);
  border: 1px solid var(--gray-line);
  border-radius: 8px;
  padding: 8px;
  font-size: 12px;
  word-break: break-all;
  direction: ltr;
  text-align: start;
}

.link-btn.danger {
  color: var(--text-light);
}

/* ----- Language snapshot ----- */
.lang-snapshot+.lang-snapshot {
  margin-top: 12px;
  border-top: 1px solid var(--gray-line);
  padding-top: 12px;
}

.lang-snapshot-head {
  display: flex;
  align-items: center;
  gap: 8px;
}

.lang-snapshot-flag {
  font-size: 20px;
}

.lang-snapshot-divisions {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 8px;
}

.division-chip {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
}

.division-chip.locked {
  opacity: 0.45;
}

.division-label {
  width: 72px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.division-fill {
  display: block;
  height: 100%;
  border-radius: 4px;
  background: var(--green);
}

@media (min-width: 640px) {
  .stat-tile-grid {
    grid-template-columns: repeat(4, 1fr);
  }
}