/* ============================================
   LAYOUT — App shell, header, nav, content area
============================================ */

.app {
  max-width: 500px;
  margin: 0 auto;
  min-height: 100vh;
  /* 100vh is the *largest* possible viewport on Android Chrome — it doesn't
     shrink when the on-screen keyboard opens, so the app shell stayed full
     height while the keyboard covered whatever was in the space it now
     occupies (reported as the keyboard "covering the whole screen" while
     typing). 100dvh tracks the actual visible viewport instead; browsers
     that don't support it ignore the line and keep the 100vh above. */
  min-height: 100dvh;
  background: var(--bg);
  display: flex;
  flex-direction: column;
  /* The header used to supply its own padding. Now that it is part of the
     page, the shell insets everything to one consistent margin. */
  padding: 0 20px;
}

/* ---- Page header ---- */
/* Editorial, not a banner: the cream background runs unbroken from the top
   of the screen into the content, and typography carries the weight. */

.page-header {
  position: relative;
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: start;
  column-gap: 16px;
  padding: 28px 0 12px;
}

.page-header-text {
  min-width: 0;
}

.page-header-title {
  font-family: var(--font-display);
  font-size: 38px;
  font-weight: 400;
  line-height: 1.05;
  letter-spacing: -0.01em;
  color: var(--text);
}

.page-header-sub {
  margin-top: 4px;
  font-size: 15px;
  color: var(--muted);
}

.page-header-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* A frontispiece, not a watermark: full strength but mostly empty, sitting
   under the title across the column. */
.page-header-art {
  grid-column: 1 / -1;
  width: 100%;
  height: auto;
  max-height: 108px;
  margin-top: 14px;
  pointer-events: none;
}

/* Illustration ink. Six classes and no more, so a composition can never
   quietly introduce a seventh colour.

   Scoped to .art rather than to the page header, because the same registry
   draws the nav icons and the story-card marks. While these lived under
   .page-header-art, an illustration used anywhere else inherited no fill at
   all and rendered as invisible black shapes — which looks like a broken
   asset rather than an unstyled one. .page-header-art keeps the class so
   existing markup and the frontispiece sizing above still apply. */
.art .ink-solid,
.page-header-art .ink-solid { fill: var(--primary); }

.art .ink-line,
.page-header-art .ink-line {
  fill: none;
  stroke: var(--primary);
  stroke-width: 1.5;
}

.art .ink-rule,
.page-header-art .ink-rule {
  fill: none;
  stroke: var(--primary);
  stroke-width: 2;
  stroke-linecap: round;
}

/* Thin architectural lines — direction and movement, not outlines */
.art .ink-hair,
.art .ink-hair line,
.page-header-art .ink-hair,
.page-header-art .ink-hair line {
  fill: none;
  stroke: var(--primary);
  stroke-width: .75;
  opacity: .38;
}

.art .accent-solid,
.page-header-art .accent-solid { fill: var(--accent); }

.art .accent-line,
.page-header-art .accent-line {
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.5;
}

/* Icons are drawn small, so they are stroked rather than filled and the
   hairlines that give a frontispiece its depth would disappear. One weight,
   sized by the element that holds them. */
.icon {
  width: 1.25em;
  height: 1.25em;
  flex: 0 0 auto;
  vertical-align: -0.2em;
}

.icon .ink-line,
.icon .ink-rule {
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.icon .ink-solid { fill: var(--primary); }
.icon .accent-solid { fill: var(--accent); }

/* A card mark sits behind a story title: small, square, and quiet. */
.card-mark {
  width: 100%;
  height: auto;
  display: block;
  pointer-events: none;
}

/* Entering a section: a short fade and rise, restarted on every change */
.page-header.is-entering {
  animation: pageHeaderIn 200ms ease-out both;
}

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

@media (prefers-reduced-motion: reduce) {
  .page-header.is-entering {
    animation: none;
  }
}

@media (max-width: 420px) {
  .page-header {
    padding-top: 20px;
  }

  .page-header-title {
    font-size: 29px;
  }

  .page-header-art {
    max-height: 88px;
    margin-top: 10px;
  }
}

/* ---- Daily activities (Review / Read / Learn) ---- */
/* On Home, the screen that asks what to do today. They sit on cream rather
   than in the header, which is editorial and carries no running state. */

.today {
  margin-bottom: 16px;
}

.today-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  font-size: 14px;
  color: var(--muted);
}

.daily-activities {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 10px;
}

.daily-activity {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 11px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--bg);
  font-size: 13px;
  line-height: 1.4;
  color: var(--muted);
}

.daily-activity.is-done {
  border-color: var(--primary);
  color: var(--text);
}

/* The mark is drawn from the art registry rather than set as an emoji, so it
   needs a box: the icon default is 1.25em, which at this 13px chip would come
   out at sixteen pixels and sit low. */
.daily-activity-icon {
  display: inline-flex;
}

.daily-activity-icon .icon {
  width: 15px;
  height: 15px;
  vertical-align: baseline;
}

.daily-activity-mark {
  font-weight: bold;
}

/* ---- Navigation ---- */

/* A tab bar, not a row of pills. Six sections set side by side as text
   overflow 460px — the widest the shell ever is — and wrapping the nav onto
   two rows costs more height than stacking each label under its mark. Equal
   columns also mean adding a seventh section changes nothing here. */

.nav {
  display: flex;
  gap: 2px;
  padding: 8px 0;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: 10;
  margin-bottom: 0;
}

.nav button {
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  background: none;
  border: none;
  border-radius: 0;
  font-size: 11px;
  color: var(--secondary);
  cursor: pointer;
  padding: 6px 2px 9px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  transition: color var(--dur-base) var(--ease-standard);
}

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

/* The selected mark: a thin line growing in under the tab. Shape and
   colour carry the state, not a filled pill — see the visual-overhaul
   spec, section 10. */
.nav button::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 2px;
  width: 60%;
  height: 2px;
  background: var(--accent-dark);
  transform: translateX(-50%) scaleX(0);
  transition: transform var(--dur-base) var(--ease-standard);
}

.nav button.active::after {
  transform: translateX(-50%) scaleX(1);
}

/* Fixed rather than the 1.25em the icon defaults to: at an 11px label that
   would come out at fourteen pixels and the mark would vanish. */
.nav button .icon {
  width: 22px;
  height: 22px;
  vertical-align: baseline;
}

.nav-label {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The icon takes the button's colour, so the selected tab's mark turns white
   with its label rather than staying navy on navy. */
.nav button .icon .ink-line,
.nav button .icon .ink-rule { stroke: currentColor; }
.nav button .icon .ink-solid { fill: currentColor; }

/* Orange marks exactly one tab at a time — icon and label both take
   accent-dark, which is the text-safe orange (see base.css), so the label
   stays readable rather than merely vivid. */
.nav button.active {
  color: var(--accent-dark);
}

@media (max-width: 420px) {
  .nav button {
    font-size: 10px;
    gap: 3px;
  }

  .nav button .icon {
    width: 20px;
    height: 20px;
  }
}

/* ---- Main content ---- */

.content {
  flex: 1;
  padding: 20px 0;
  overflow-y: auto;
  display: block;
}

/* ============================================
   LEARN TAB — level list and level detail
============================================ */

/* One ink for every level. Five hues read as a category scale and there are
   no categories here — the levels are a sequence, and depth carries that
   better than colour. The accent stays reserved for the live lesson. */
.level-card,
.level-detail {
  --level: var(--primary);
}

/* Reversed 2026-08-14 (visual identity v2) — back to a stack of individual
   rounded, shadowed cards rather than hairline-divided open rows, matching
   the current reference mockups. The spine and ordinal are kept as an
   accent inside each card rather than dropped. */
.level-list {
  display: flex;
  flex-direction: column;
}

/* An archive of open rows on cream, divided by hairlines — not a stack of
   lifted white cards. No spine bar either: it was a fixed navy accent
   regardless of level, so it carried no information. */
.level-card {
  position: relative;
  display: flex;
  align-items: center;
  gap: 18px;
  width: 100%;
  padding: 20px 4px;
  background: none;
  border: none;
  border-bottom: 1px solid var(--border-light);
  border-radius: 0;
  text-align: left;
  cursor: pointer;
}

.level-card:last-child {
  border-bottom: none;
}

.level-card:hover .level-card-code,
.level-card:hover .level-card-title {
  color: var(--accent-dark);
}

/* Fill comes from .ps-wash/.ps-ink/.ps-accent on the shapes themselves
   (see LEVEL_ICONS, engine/curriculum.js) — this is sizing only. */
.level-icon {
  width: 28px;
  height: 28px;
  flex: none;
}

.level-icon--sm {
  width: 18px;
  height: 18px;
}

/* The top-level Lessons list only — matches the reference, where each
   level's icon is the dominant object in its row rather than a small
   marker beside the title. */
.level-icon--lg {
  width: 48px;
  height: 48px;
}

/* A unit's own position within the level — units are a sequence, not a
   category, so a number carries that better than a distinct icon set
   would. The level's own icon (smaller) sits beside it instead, tying the
   unit back to its level rather than giving the unit its own identity. */
.unit-card-num {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  flex: none;
  border-radius: 50%;
  background: var(--bg);
  border: 1.5px solid var(--level);
  color: var(--level);
  font-family: var(--font-display);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}

.unit-detail-num {
  width: 34px;
  height: 34px;
  font-size: 15px;
}

.level-card-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1;
  min-width: 0;
}

/* The level code is the heading here — this is the list of levels, and the
   code is what identifies one. The name sits under it as the gloss. */
.level-card-code {
  font-family: var(--font-display);
  font-size: 26px;
  font-weight: 400;
  line-height: 1.1;
  color: var(--text);
}

.level-card-title {
  font-family: var(--font-display);
  font-size: 16px;
  font-weight: 400;
  color: var(--text);
}

.level-card-sub {
  margin-top: 1px;
  font-size: 12.5px;
  line-height: 1.35;
  color: var(--muted);
}

/* Sits short and right-aligned under the name rather than spanning the
   whole row — progress is a footnote to the level, not its headline. */
.level-card-meter {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 10px;
  align-self: flex-end;
  width: 72%;
}

.level-card-track {
  flex: 1;
  height: 3px;
  min-width: 50px;
  border-radius: var(--radius-pill);
  background: var(--border);
  overflow: hidden;
}

.level-card-fill {
  display: block;
  height: 100%;
  border-radius: var(--radius-pill);
  background: var(--accent);
}

.level-card-count {
  font-size: 12px;
  color: var(--muted);
  white-space: nowrap;
}

/* A drawn chevron rather than a solid triangle — the reference's rows end
   in a thin open mark. Nudges forward and darkens on hover/focus, so the
   directional cue itself is the interaction. */
.level-card-arrow {
  flex: none;
  width: 9px;
  height: 9px;
  border-right: 1.5px solid var(--muted);
  border-top: 1.5px solid var(--muted);
  transform: rotate(45deg);
  transition: transform var(--dur-fast) var(--ease-standard),
              border-color var(--dur-fast) var(--ease-standard);
}

.level-card:hover .level-card-arrow,
.level-card:focus-visible .level-card-arrow {
  border-color: var(--accent-dark);
  transform: rotate(45deg) translate(2px, -2px);
}

/* ---- Level detail head (visual identity v2) ----
   Names the level you drilled into, which this screen never did — the page
   header still reads "Lessons". The name only: the description repeats the
   level list you just came from, and the count is in the summary below. */
.unit-path-head {
  margin-bottom: var(--space-4);
}

.unit-path-head-title {
  font-family: var(--font-display);
  font-size: 26px;
  font-weight: 400;
  line-height: 1.15;
  color: var(--text);
}

/* ---- Unit path (visual identity v2, 2026-08-14) ----
   A hand-drawn-feeling line connecting each unit, replacing the card list
   for the unit-list screen only — the level list one step up stays cards.
   See engine/curriculum.js unitPathHtml/unitPathPoints/smoothPathD; --x/--y/
   --path-w/--path-h are set inline per node/container from that layout. */
/* Mobile-first, and the same disc-on-the-line composition as the dual-track
   path (.dtp-*) — unified 2026-08-14 at the user's preference; see .dtp for
   the parallel set of variables. --path-amp is how far the line swings
   either side of centre; the art disc is centred directly on it rather
   than sitting beside a separate dot marker, which was the pre-unification
   version. A node is art + gap + label, and because units sit at
   alternating extremes each one has half the column *plus* the swing:
     375px viewport → 335 content → 167 half + 70 swing = 237 for 96+8+96=200
     500px viewport → 460 content → 230 half + 96 swing = 326 for 128+12+138=278 */
.unit-path {
  --path-amp: 70px;
  --path-art: 96px;
  --path-gap: 8px;
  --path-text: 96px;
  position: relative;
  width: 100%;
  height: var(--path-h);
  margin: var(--space-6) auto;
}

@media (min-width: 421px) {
  .unit-path {
    --path-amp: 96px;
    --path-art: 128px;
    --path-gap: 12px;
    --path-text: 138px;
  }
}

/* The curve keeps its own narrow width and sits centred, rather than
   stretching to the container — preserveAspectRatio="none" on a container-
   width box would flatten the wave out of shape. */
.unit-path-line {
  position: absolute;
  top: 0;
  left: 50%;
  width: calc(var(--path-amp) * 2);
  height: 100%;
  transform: translateX(-50%);
  overflow: visible;
}

/* A confident navy route, not a hairline divider — at --border's 0.18 alpha
   the line all but vanished, and the path is the main object on this
   screen. preserveAspectRatio="none" would scale the stroke with the box,
   so vector-effect keeps it an even weight. */
.unit-path-line path {
  fill: none;
  stroke: var(--primary);
  stroke-width: 2.5;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

.unit-path-cap {
  position: absolute;
  top: var(--y);
  left: calc(50% + var(--sx) * var(--path-amp));
  transform: translate(-50%, -50%);
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: var(--accent);
}

/* The disc is centred on its line — hence the negative margin of half its
   width, same as .dtp-node — and the label hangs outside it, never over
   it. Status used to be a colour-coded dot; there is no separate marker to
   colour-code any more, so (matching .dtp-node) only "upcoming" changes
   anything, by dimming the art and muting the text below. */
.unit-path-node {
  position: absolute;
  top: var(--y);
  left: calc(50% + var(--sx) * var(--path-amp));
  transform: translateY(-50%);
  margin-left: calc(var(--path-art) / -2);
  display: flex;
  align-items: center;
  gap: var(--path-gap);
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  text-align: left;
}

.unit-path-node.is-left {
  left: auto;
  right: calc(50% - var(--sx) * var(--path-amp));
  margin-left: 0;
  margin-right: calc(var(--path-art) / -2);
  flex-direction: row-reverse;
  text-align: right;
}

.unit-path-art {
  width: var(--path-art);
  height: var(--path-art);
  flex: none;
}

.unit-path-art.is-flip { transform: scaleX(-1); }

.ps-wash { fill: var(--wash); }
.ps-ink { fill: var(--primary); }
.ps-accent { fill: var(--accent); }
.ps-line { stroke: var(--primary); stroke-width: 3.5; fill: none; stroke-linecap: round; }
.ps-hair { stroke: var(--primary); stroke-width: 1.25; fill: none; opacity: .5; }
.ps-rules line { stroke: var(--primary); stroke-width: 3; stroke-linecap: round; }

.unit-path-node.is-upcoming .unit-path-art { opacity: .55; }

.unit-path-text {
  display: flex;
  flex-direction: column;
  gap: 1px;
  width: var(--path-text);
  flex: none;
}

.unit-path-num {
  font-size: 12px;
  font-weight: 600;
  color: var(--accent-dark);
  font-variant-numeric: tabular-nums;
}

.unit-path-title {
  font-family: var(--font-display);
  font-size: 17px;
  color: var(--text);
}

.unit-path-count {
  font-size: 12px;
  color: var(--muted);
}

.unit-path-node.is-upcoming .unit-path-title,
.unit-path-node.is-upcoming .unit-path-num,
.unit-path-node.is-upcoming .unit-path-count {
  color: var(--muted);
}

/* ---- Unit path summary (visual identity v2) ----
   The level-wide progress readout under the path — separate from the
   header's own count, matching the reference's footer bar. */
.unit-path-summary {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: var(--space-5) 0 var(--space-6);
  padding: 14px 16px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.unit-path-summary-dot {
  width: 10px;
  height: 10px;
  flex: none;
  border-radius: 50%;
  background: var(--accent);
}

.unit-path-summary-text {
  font-size: 13px;
  color: var(--muted);
  white-space: nowrap;
}

.unit-path-summary-track {
  flex: 1;
  height: 4px;
  min-width: 40px;
  border-radius: var(--radius-pill);
  background: var(--border);
  overflow: hidden;
}

.unit-path-summary-fill {
  display: block;
  height: 100%;
  background: var(--primary);
}

.unit-path-summary-pct {
  font-size: 13px;
  font-weight: 600;
  color: var(--primary);
  white-space: nowrap;
}

/* ---- Unit tracks (B1's Core Spanish + Latin America) ----
   One column per track, stacked on a narrow viewport — most of the app's
   width budget — and side by side from 600px, the breakpoint already used
   elsewhere for "widen up" layouts. Each column reuses .level-list/
   .level-card as-is: a track is not a new visual object, just a second
   archive next to the first. */
.unit-tracks {
  display: flex;
  flex-direction: column;
  gap: 28px;
}

.unit-track-column {
  min-width: 0;
}

.unit-track-heading {
  margin: 0 0 4px;
  padding-left: 26px;
  font-family: var(--font-display);
  font-size: 13px;
  font-weight: 400;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}

/* ---- Dual-track path (visual identity v2) ----
   Two tracks braided into one composition: a line per track, crossing
   between rows, with units interleaved down a single column. Superseded
   side-by-side columns (214px each in the 500px shell — nodes overflowed
   by 73px) and then stacked full-width paths. Mobile-first; every measure
   derives from --dtp-amp so the swing widens with the viewport.
     375px → 335 content → 167 half vs 22 + 38 + 8 + 92 = 160 used
     500px → 460 content → 230 half vs 34 + 52 + 12 + 128 = 226 used */
.dtp {
  --dtp-amp: 22px;
  --dtp-art: 76px;
  --dtp-gap: 8px;
  --dtp-text: 92px;
  margin: var(--space-5) 0 var(--space-6);
}

@media (min-width: 421px) {
  .dtp {
    --dtp-amp: 34px;
    --dtp-art: 104px;
    --dtp-gap: 12px;
    --dtp-text: 128px;
  }
}

.dtp-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 18px;
  margin-bottom: var(--space-5);
}

.dtp-key {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 11px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted);
}

.dtp-key-line {
  width: 22px;
  height: 2px;
  border-radius: 1px;
  background: var(--primary);
}

.dtp-key-line--t1 { background: var(--accent); }

.dtp-path {
  position: relative;
  width: 100%;
  height: var(--dtp-h);
}

.dtp-lines {
  position: absolute;
  top: 0;
  left: 50%;
  width: calc(var(--dtp-amp) * 2);
  height: 100%;
  transform: translateX(-50%);
  overflow: visible;
}

/* non-scaling-stroke because preserveAspectRatio="none" stretches x only,
   which would otherwise draw the two lines at different apparent weights. */
.dtp-line {
  fill: none;
  stroke-width: 2.5;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
  stroke: var(--primary);
}

.dtp-line--t1 { stroke: var(--accent); }

/* The disc is centred on its line — hence the negative margin of half its
   width — and the label hangs outside it, never over it. */
.dtp-node {
  position: absolute;
  top: var(--y);
  left: calc(50% + var(--sx) * var(--dtp-amp));
  transform: translateY(-50%);
  margin-left: calc(var(--dtp-art) / -2);
  display: flex;
  align-items: center;
  gap: var(--dtp-gap);
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  text-align: left;
}

.dtp-node.is-left {
  left: auto;
  right: calc(50% - var(--sx) * var(--dtp-amp));
  margin-left: 0;
  margin-right: calc(var(--dtp-art) / -2);
  flex-direction: row-reverse;
  text-align: right;
}

.dtp-art {
  width: var(--dtp-art);
  height: var(--dtp-art);
  flex: none;
}

.dtp-art.is-flip { transform: scaleX(-1); }

.dtp-node.is-upcoming .dtp-art { opacity: .5; }

.dtp-text {
  display: flex;
  flex-direction: column;
  gap: 1px;
  width: var(--dtp-text);
  flex: none;
}

.dtp-num {
  font-size: 12px;
  font-weight: 600;
  color: var(--accent-dark);
  font-variant-numeric: tabular-nums;
}

.dtp-title {
  font-family: var(--font-display);
  font-size: 17px;
  line-height: 1.2;
  color: var(--text);
}

.dtp-track {
  margin-top: 3px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--primary-mid);
}

.dtp-node.is-t1 .dtp-track { color: var(--accent-dark); }

.dtp-count {
  font-size: 12px;
  color: var(--muted);
}

.dtp-node.is-upcoming .dtp-title,
.dtp-node.is-upcoming .dtp-count {
  color: var(--muted);
}

/* ---- Unit detail (visual identity v2) ---- */
/* No hero illustration here — tried, didn't earn its place at this depth
   (the level list above already carries one). Straight from the back
   button into the unit's own heading. */

.ud-head {
  margin-top: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.ud-num {
  font-family: var(--font-display);
  font-size: 24px;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

.ud-title {
  font-family: var(--font-display);
  font-size: 30px;
  font-weight: 400;
  line-height: 1.1;
  color: var(--text);
}

.ud-track {
  margin-top: 8px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--primary);
}

.ud-count {
  font-size: 13px;
  color: var(--muted);
}

/* One mark per lesson: the unit's shape at a glance. */
.ud-dots {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: var(--space-5) 0;
  padding-bottom: var(--space-5);
  border-bottom: 1px solid var(--border-light);
}

.ud-dot {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  border: 1.5px solid var(--border);
  background: none;
}

.ud-dot.is-done {
  background: var(--primary);
  border-color: var(--primary);
}

.ud-section {
  font-family: var(--font-display);
  font-size: 19px;
  font-weight: 400;
  color: var(--text);
  margin-bottom: var(--space-3);
}

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

/* A run of steps, not a stack of rows: each carries a node on a shared
   spine so the unit reads as one path, matching the level view above it. */
.ud-lesson {
  position: relative;
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 14px 0 14px 28px;
  background: none;
  border: none;
  text-align: left;
  cursor: pointer;
}

/* The spine, drawn per row so it stops cleanly at the last one. */
.ud-lesson::before {
  content: "";
  position: absolute;
  left: 5px;
  top: 0;
  bottom: 0;
  width: 1.5px;
  background: var(--border);
}

.ud-lessons li:first-child .ud-lesson::before { top: 50%; }
.ud-lessons li:last-child .ud-lesson::before { bottom: 50%; }

.ud-lesson-node {
  position: absolute;
  left: 0;
  top: 50%;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--bg);
  border: 2px solid var(--border);
  transform: translateY(-50%);
}

.ud-lesson.is-done .ud-lesson-node {
  background: var(--primary);
  border-color: var(--primary);
}

.ud-lesson.is-current .ud-lesson-node {
  border-color: var(--accent);
}

.ud-lesson-num {
  flex: none;
  font-size: 12px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.ud-lesson-title {
  flex: 1;
  min-width: 0;
  font-family: var(--font-display);
  font-size: 16px;
  color: var(--text);
}

.ud-lesson:hover .ud-lesson-title { color: var(--accent-dark); }

.ud-lesson.is-todo .ud-lesson-title { color: var(--muted); }

/* Ring at the end of the row: filled with a check when done, an open
   accent ring for the lesson you are on, hairline for the rest. */
.ud-lesson-mark {
  position: relative;
  flex: none;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 1.5px solid var(--border);
}

.ud-lesson.is-current .ud-lesson-mark { border-color: var(--accent); }

.ud-lesson.is-done .ud-lesson-mark {
  background: var(--primary);
  border-color: var(--primary);
}

.ud-lesson.is-done .ud-lesson-mark::after {
  content: "";
  position: absolute;
  left: 8px;
  top: 6px;
  width: 6px;
  height: 10px;
  border-right: 2px solid var(--surface);
  border-bottom: 2px solid var(--surface);
  transform: rotate(45deg);
}

/* ---- Level detail ---- */

.level-detail {
  position: relative;
  padding-left: 18px;
}

.level-detail::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 6px;
  border-radius: 6px;
  background: var(--level);
}

.level-back {
  background: none;
  padding: 0;
  margin-bottom: 16px;
  font-size: 14px;
  color: var(--muted);
}

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

/* ---- Grammar Guide ---- */
/* A dashed-border row matching .level-test-row's "opens a related sub-view"
   treatment, so both entry points into a unit's extra material read as the
   same kind of thing. */
.ud-grammar-guide-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  text-align: left;
  margin-top: var(--space-4);
  padding: 14px 18px;
  background: var(--bg);
  border: 1px dashed var(--border);
  border-radius: 8px;
}

.ud-grammar-guide-row:hover {
  border-color: var(--primary);
  border-style: solid;
}

.ud-grammar-guide-title {
  font-family: var(--font-display);
  font-size: 16px;
  color: var(--primary);
}

.ud-grammar-guide-arrow {
  margin-left: auto;
  color: var(--muted);
}

/* The unit's grammar topics as one line, up top — a preview of what the
   numbered list below expands on, in LingoDeer's Learning-Tips style. */
.gg-subtitle {
  margin-top: 6px;
  font-size: 13px;
  color: var(--muted);
}

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

.gg-topic {
  display: flex;
  gap: 14px;
  padding: var(--space-4) 0;
  border-bottom: 1px solid var(--border-light);
}

.gg-topic:last-child { border-bottom: none; }

.gg-topic-num {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 1.5px solid var(--border);
  font-family: var(--font-display);
  font-size: 13px;
  color: var(--primary);
}

.gg-topic-body { min-width: 0; }

.gg-topic-title {
  font-family: var(--font-display);
  font-size: 16px;
  font-weight: 400;
  color: var(--text);
  margin: 0 0 4px;
}

/* .level-hero, .level-detail-head/-titles/-progress, .progress-ring(-label)
   and .lesson-row* all removed 2026-08-14 — superseded by .unit-path-head
   (level list) and .ud-* (unit detail) earlier the same day, and no longer
   emitted by curriculum.js. The hero specifically: removed at the user's
   request ("i hate it") from both the unit-list and unit-detail screens. */

.level-empty {
  padding: 24px 12px;
}
