/* 17-learn.css — the new Learn (interactive exercise) page.
 *
 * Page-scoped (loaded only by learn/subject.html and learn/lesson.html). The lesson
 * page also links 16-code.css for the shared .code-* step scaffold (steps / Continue
 * / prose); this file adds the subject-page accordion and the exercise widgets (.lx-*).
 * Theme: the site cyan accent, to stay optically close to the Know page. */

:root {
  --lx-accent: 34, 211, 238;   /* cyan-400 */
  --lx-pass: 16, 185, 129;     /* emerald */
  --lx-fail: 244, 63, 94;      /* rose */
}

/* ============================ Subject page: accordion ============================ */

/* Transparent topic container (a bordered frame, no background lightening). */
.lx-topic { overflow: hidden; }
.lx-topic-summary:hover { background: rgba(255, 255, 255, 0.02); }
.lx-topic[open] .lx-chevron { transform: rotate(90deg); }

.lx-topic-num {
  background: rgba(var(--lx-accent), 0.14);
  color: rgb(var(--lx-accent));
}
.lx-topic-count { font-variant-numeric: tabular-nums; }
.lx-sub-num { color: rgba(var(--lx-accent), 0.85); }

/* Lesson row: a flat, full-width list item (no inner box) — hover reveals it. */
.lx-lesson-link {
  border-radius: 0.6rem;
  transition: background 0.12s ease;
}
.lx-lesson-link:hover { background: rgba(var(--lx-accent), 0.08); }
.lx-lesson-link:focus-visible { outline: 2px solid rgba(var(--lx-accent), 0.55); outline-offset: 2px; }
.lx-lesson-badge {
  background: rgba(var(--lx-accent), 0.14);
  color: rgb(var(--lx-accent));
}

/* Flat one-row-per-leaf list. A single-lesson leaf and a coming-soon leaf share this
   rail (number · title · affordance) so titles align into one column; the grouped
   .lx-sub / .lx-lesson-link markup is kept only for the rare >1-lesson / topic_level
   branches. */
.lx-leaf-num {
  min-width: 1.6rem;             /* fixed column so the widest leaf ("10.1") stays aligned */
  flex-shrink: 0;
  color: rgba(var(--lx-accent), 0.85);
  font-variant-numeric: tabular-nums;
}

/* Subtopic rows sit slightly inset from the topic header — a quiet hierarchy indent.
   Applied to the whole row (margin, not padding) so its rounded hover highlight shifts
   in with the number rather than opening a left gutter. */
.lx-leaf { margin-left: 1.5rem; }

/* Ready leaf — the call-to-action: an accent chevron pulls the few real lessons
   forward out of the dim coming-soon tail. */
.lx-leaf--ready { border-radius: 0.6rem; transition: background 0.12s ease; }
.lx-leaf--ready .lx-leaf-go { color: rgb(var(--lx-accent)); transition: transform 0.12s ease; }
.lx-leaf--ready:hover { background: rgba(var(--lx-accent), 0.09); }
.lx-leaf--ready:hover .lx-leaf-go { transform: translateX(2px); }
.lx-leaf--ready:focus-visible { outline: 2px solid rgba(var(--lx-accent), 0.55); outline-offset: 2px; }

/* Coming-soon leaf: present but inactive. */
.lx-leaf--soon { opacity: 0.8; }
.lx-leaf--soon .lx-leaf-num { color: rgba(var(--lx-accent), 0.5); }

/* "Has an interactive visualization" badge — sits at the right of a lesson row, just before the
   go-chevron. Violet to match the /know + /learn "Explore" widgets; its PRESENCE is the marker
   (a lesson with no visualization shows none). */
.lx-viz-badge {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.4rem; height: 1.4rem; flex-shrink: 0;
  border-radius: 0.45rem; color: #c4b5fd;
  background: rgba(167, 139, 250, 0.14);
  border: 1px solid rgba(167, 139, 250, 0.30);
  transition: background 0.12s ease, border-color 0.12s ease;
}
.lx-viz-badge svg { width: 0.92rem; height: 0.92rem; }
.lx-leaf--ready:hover .lx-viz-badge,
.lx-lesson-link:hover .lx-viz-badge { background: rgba(167, 139, 250, 0.22); border-color: rgba(167, 139, 250, 0.46); }

/* Recede a fully-unbuilt topic so the one with live lessons is the anchor (mirrors the
   landing page's dim "Coming soon" subjects); lit border on the open (live) topic. */
.lx-topic--empty { opacity: 0.6; }
.lx-topic--empty .lx-topic-num { background: rgba(255, 255, 255, 0.05); color: #94a3b8; }
.lx-topic[open] { border-color: rgba(var(--lx-accent), 0.18); }

/* Tiny per-topic progress meter beside the "available / total" count. */
.lx-topic-meter {
  display: inline-block; width: 2.5rem; height: 4px;
  border-radius: 999px; background: rgba(255, 255, 255, 0.08); overflow: hidden;
}
.lx-topic-meter-fill { display: block; height: 100%; border-radius: 999px; background: rgb(var(--lx-accent)); }

@media (prefers-reduced-motion: reduce) {
  .lx-leaf--ready, .lx-leaf--ready .lx-leaf-go { transition: none; }
  .lx-leaf--ready:hover .lx-leaf-go { transform: none; }
}

/* ===================== Lesson page: steps + nav ===================== */

.lx-lesson { padding-bottom: 1.5rem; }

/* Prose reads directly on the dark background — NO box (boxes lighten the bg and
   hurt readability). */
.lx-prose > :first-child { margin-top: 0; }
.lx-prose > :last-child { margin-bottom: 0; }

/* Prose font matches the Code lessons exactly — text-lg / leading-8 (18px / 32px,
   Outfit) is set on the .lx-prose div in the template, so do NOT set a font-size
   here (it would override the shared size and make Learn smaller than /code). */
/* List markers sit INSIDE the content rail (padding, not a negative-hanging margin),
   so the bullet dots never poke out left of the headings + figure cards. */
.lx-prose :is(ul, ol) { margin: 0.7rem 0; padding-left: 1.3em; list-style-position: outside; }
.lx-prose li { margin: 0.45rem 0; padding-left: 0.15em; }

/* Lesson images: a mounted figure card — matte mat, padded, softly shadowed, so a
   figure reads as a deliberate panel rather than a photo floating on the dark page.
   Convention (matches handouts): an image on its own line, optionally followed by a
   lone *italic* caption line. */
/* A figure (image + optional caption), wrapped by render_learn_md. The figure is
   left-aligned (shares the text rail); the caption is centred UNDER the image.
   Self-drawn diagrams (PNG) fill the column; photos (JPG) shrink to the image so
   the caption centres under the photo, not the whole column. */
.lx-figure { margin: 1.15rem 0; }
/* NB: media srcs carry a `?v=<mtime>` cache-buster (web/media_urls.py), so the type
   sniff must be substring (`*=`), not ends-with (`$=`) — `$=` never matches a busted
   src and silently killed these sizing rules. */
.lx-figure:has(img[src*=".png"]) { width: 100%; }
.lx-figure:has(img[src*=".jpg"]),
.lx-figure:has(img[src*=".jpeg"]) { width: fit-content; max-width: 100%; }
.lx-figure img {
  display: block;
  max-width: 100%;
  margin: 0;
  padding: 0;                     /* the image IS the box — fills it edge-to-edge */
  border-radius: 0.85rem;         /* rounded to match the box corners */
  border: 1px solid rgba(255, 255, 255, 0.10);
  background: #0b1020;            /* matte shows only behind transparent diagrams */
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.30);
}
/* Diagrams fill the column with their aspect ratio LOCKED. height:auto + max-height:none
   override the inherited `.code-prose img { max-height: 18rem }`, which otherwise caps
   the height while width:100% forces the width — squashing the diagram (stretched). */
.lx-figure img[src*=".png"] { width: 100%; height: auto; max-height: none; }
/* Match /know handout photos (30rem) so teaching detail stays legible. */
.lx-figure img[src*=".jpg"],
.lx-figure img[src*=".jpeg"],
.lx-figure img[src*=".webp"] {
  max-height: 30rem;
  width: auto;
  margin-inline: auto;
  object-fit: contain;
}

/* Inline concept animations (Manim clips authored as ![…](….mp4) — web/learn_render.py
   turns them into a muted looping <video>). Same treatment as a full-width diagram:
   the clip is rendered on the diagrams' #0b1020 matte, so it reads as a live figure.
   Tap toggles pause; prefers-reduced-motion swaps autoplay for controls
   (learn-lesson.js). */
.lx-figure:has(video) { width: 100%; }
.lx-anim {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 0.85rem;
  border: 1px solid rgba(255, 255, 255, 0.10);
  background: #0b1020;
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.30);
  cursor: pointer;
}
.lx-figure figcaption {
  text-align: center;
  color: #a8b8cc;          /* match handout caption contrast */
  font-size: 0.84rem;
  font-style: italic;
  line-height: 1.5;
  margin-top: 0.45rem;
  max-width: 40rem;
  margin-inline: auto;
  padding-inline: 0.35rem;
  text-wrap: pretty;
}

/* Subtle hover lift (mirrors /know hz-figures). */
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .lx-figure img,
  .lx-anim {
    transition: transform 160ms ease, box-shadow 160ms ease;
  }
  .lx-figure:hover img,
  .lx-figure:hover .lx-anim {
    transform: translateY(-2px);
    box-shadow: 0 14px 32px rgba(0, 0, 0, 0.38);
  }
}

/* Keyboard focus for figure images and animations (match /know cyan ring). */
.lx-figure a:focus-visible img,
.lx-figure img:focus-visible,
.lx-anim:focus-visible {
  outline: 2px solid #38bdf8;
  outline-offset: 3px;
}

/* Exercise step: a colour-tinted "your turn" panel. Every practice box in a lesson is
   given a DIFFERENT hue by a server-rendered .lx-tint-N class (below) that overrides
   --lx-accent — so the whole box (border, PRACTICE label, options, Check button, chips,
   inputs, explanation) re-tints to that colour in one move. The tint reads through a very
   light gradient wash + a faint same-hue glow; the wash stays at callout strength (~0.06)
   so the prompt text is as readable as the prose. */
.code-step.lx-exercise {
  border: 1px solid rgba(var(--lx-accent), 0.36);
  border-radius: 0.9rem;
  padding: 1.15rem 1.35rem;
  background:
    radial-gradient(135% 100% at 0% 0%, rgba(var(--lx-accent), 0.10), transparent 55%),
    linear-gradient(180deg, rgba(var(--lx-accent), 0.055), rgba(var(--lx-accent), 0.015));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.045),
    0 0 26px rgba(var(--lx-accent), 0.05);
}

/* Scrambled-rainbow tint palette. Each successive practice box jumps far across the colour
   wheel so neighbours strongly contrast — a rainbow, but NOT in spectral order — and the set
   as a whole reads multicolour. Values reuse the .lx-ico--* design tokens; the sequence cycles
   every 12 boxes (lesson exercises are counted in the template, so prose/viz steps don't shift
   it). --lx-accent is the ONE lever the whole exercise box already themes from. */
.lx-tint-0  { --lx-accent: 34, 211, 238; }   /* cyan */
.lx-tint-1  { --lx-accent: 251, 146, 60; }   /* orange */
.lx-tint-2  { --lx-accent: 167, 139, 250; }  /* violet */
.lx-tint-3  { --lx-accent: 163, 230, 53; }   /* lime */
.lx-tint-4  { --lx-accent: 251, 113, 133; }  /* rose */
.lx-tint-5  { --lx-accent: 56, 189, 248; }   /* sky */
.lx-tint-6  { --lx-accent: 251, 191, 36; }   /* amber */
.lx-tint-7  { --lx-accent: 232, 121, 249; }  /* fuchsia */
.lx-tint-8  { --lx-accent: 52, 211, 153; }   /* emerald */
.lx-tint-9  { --lx-accent: 244, 114, 182; }  /* pink */
.lx-tint-10 { --lx-accent: 96, 165, 250; }   /* blue */
.lx-tint-11 { --lx-accent: 250, 204, 21; }   /* yellow */

/* Step nav: Back (hides the last step) left, Continue right. */
.lx-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  margin-top: 1.6rem;
}
/* Back + Continue: icon-only circular buttons; Reset sits between them, smaller. */
.lx-back {
  display: inline-flex; align-items: center; justify-content: center;
  width: 2.5rem; height: 2.5rem;
  border-radius: 999px;
  color: #94a3b8;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.1);
  cursor: pointer;
  transition: color 0.12s, background 0.12s, border-color 0.12s;
}
.lx-back:hover:not(:disabled) { color: #e2e8f0; background: rgba(255, 255, 255, 0.09); border-color: rgba(255, 255, 255, 0.18); }
.lx-back:disabled { opacity: 0; pointer-events: none; }   /* nothing to go back to on step 1 */
.lx-continue { width: 3rem; height: 3rem; padding: 0; gap: 0; justify-content: center; }   /* forward = primary CTA, bigger than Back */
.lx-continue:disabled { opacity: 0.4; cursor: default; }
.lx-back svg { width: 1.05rem; height: 1.05rem; }
.lx-continue svg { width: 1.3rem; height: 1.3rem; }
.lx-reset {
  display: inline-flex; align-items: center; justify-content: center;
  width: 2rem; height: 2rem;
  border-radius: 999px;
  color: #7c8aa0;
  background: transparent;
  border: 1px solid rgba(255, 255, 255, 0.08);
  cursor: pointer;
  transition: color 0.12s, background 0.12s, border-color 0.12s;
}
.lx-reset svg { width: 0.95rem; height: 0.95rem; }
.lx-reset:hover { color: #cbd5e1; background: rgba(255, 255, 255, 0.06); border-color: rgba(255, 255, 255, 0.16); }
.lx-reset:disabled { opacity: 0; pointer-events: none; }   /* nothing to reset on step 1 */

/* ============================ Lesson page: exercises ============================ */

.lx-practice-label {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgb(var(--lx-accent));
  margin-bottom: 0.5rem;
}
.lx-practice-ico { width: 0.95rem; height: 0.95rem; flex-shrink: 0; }
.lx-prompt :is(p, ul, ol) { margin: 0.25rem 0; }

/* Options (multiple-choice / multi-select / true-false) */
.lx-options { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }
.lx-option {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  padding: 0.6rem 0.8rem;
  border: 1px solid rgba(255, 255, 255, 0.09);
  border-radius: 0.75rem;
  background: rgba(255, 255, 255, 0.02);
  cursor: pointer;
  transition: border-color 0.12s ease, background 0.12s ease;
}
.lx-option:hover { border-color: rgba(var(--lx-accent), 0.4); background: rgba(var(--lx-accent), 0.05); }
.lx-option input { accent-color: rgb(var(--lx-accent)); flex-shrink: 0; }
.lx-option:has(input:checked) { border-color: rgba(var(--lx-accent), 0.7); background: rgba(var(--lx-accent), 0.1); }
.lx-option-label { color: #e2e8f0; }
.lx-option-label :is(p) { margin: 0; display: inline; }

/* Text + numeric inputs */
.lx-input {
  width: 100%;
  max-width: 22rem;
  padding: 0.55rem 0.8rem;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 0.6rem;
  background: #0b1020;
  color: #e2e8f0;
  font-size: 1rem;
}
.lx-input:focus { outline: none; border-color: rgba(var(--lx-accent), 0.7); box-shadow: 0 0 0 3px rgba(var(--lx-accent), 0.15); }
.lx-numeric { display: inline-flex; align-items: center; gap: 0.5rem; }
.lx-input-num { max-width: 9rem; }
.lx-unit { color: #94a3b8; font-size: 0.95rem; }

/* Match: drag a chip from the bank onto each row's slot (click/tap-to-place too). */
.lx-match { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }
.lx-match-row {
  display: flex; align-items: center; gap: 0.75rem;
  padding: 0.5rem 0.7rem;
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 0.7rem;
  background: rgba(255, 255, 255, 0.02);
}
.lx-match-left { flex: 1; min-width: 0; color: #e2e8f0; }
.lx-match-left :is(p) { margin: 0; display: inline; }

/* drop slot on each row */
.lx-match-slot {
  flex-shrink: 0; min-width: 8.5rem; min-height: 2.3rem;
  display: flex; align-items: center; justify-content: center;
  padding: 0.2rem;
  border: 1px dashed rgba(255, 255, 255, 0.20);
  border-radius: 0.6rem;
  background: rgba(255, 255, 255, 0.015);
  transition: border-color 0.12s, background 0.12s;
}
.lx-match-slot.lx-drop-over { border-color: rgba(var(--lx-accent), 0.75); background: rgba(var(--lx-accent), 0.12); }

/* the pool of unplaced chips */
.lx-match-bank {
  display: flex; flex-wrap: wrap; gap: 0.5rem;
  margin-bottom: 0.75rem; padding: 0.65rem;
  border: 1px solid rgba(255, 255, 255, 0.07);
  border-radius: 0.7rem;
  background: rgba(255, 255, 255, 0.015);
  min-height: 2.8rem;
}
.lx-match-bank.lx-drop-over { border-color: rgba(var(--lx-accent), 0.5); background: rgba(var(--lx-accent), 0.05); }

/* draggable chip */
.lx-match-chip {
  display: inline-flex; align-items: center; margin: 0;
  padding: 0.38rem 0.75rem;
  border: 1px solid rgba(var(--lx-accent), 0.4);
  border-radius: 999px;
  background: rgba(var(--lx-accent), 0.1);
  color: #e2e8f0; font-size: 0.92rem;
  cursor: grab; user-select: none;
  transition: background 0.12s, border-color 0.12s, box-shadow 0.12s;
}
.lx-match-chip :is(p) { margin: 0; display: inline; }
.lx-match-chip:hover { background: rgba(var(--lx-accent), 0.17); }
.lx-match-chip.lx-dragging { opacity: 0.45; cursor: grabbing; }
.lx-match-chip.lx-selected { border-color: rgba(var(--lx-accent), 0.9); background: rgba(var(--lx-accent), 0.24); box-shadow: 0 0 0 2px rgba(var(--lx-accent), 0.28); }

/* Order: a reorderable list with up/down controls */
.lx-order { list-style: none; margin: 0; padding: 0; counter-reset: lx-ord; display: flex; flex-direction: column; gap: 0.4rem; }
.lx-order-item {
  display: flex; align-items: center; gap: 0.75rem;
  padding: 0.55rem 0.7rem;
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 0.7rem;
  background: rgba(255, 255, 255, 0.025);
  cursor: grab;
  transition: border-color 0.12s, box-shadow 0.12s, opacity 0.12s;
}
.lx-order-item.lx-dragging { opacity: 0.5; cursor: grabbing; border-color: rgba(var(--lx-accent), 0.6); box-shadow: 0 6px 18px rgba(0, 0, 0, 0.3); }
.lx-order-item::before {
  counter-increment: lx-ord; content: counter(lx-ord);
  flex-shrink: 0; width: 1.5rem; height: 1.5rem;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 0.4rem; font-size: 0.75rem; font-weight: 700;
  background: rgba(var(--lx-accent), 0.12); color: rgb(var(--lx-accent));
}
.lx-order-text { flex: 1; min-width: 0; color: #e2e8f0; }
.lx-order-text :is(p) { margin: 0; display: inline; }
.lx-order-controls { display: inline-flex; gap: 0.25rem; flex-shrink: 0; }
.lx-order-up, .lx-order-down {
  width: 1.8rem; height: 1.8rem; border-radius: 0.4rem;
  border: 1px solid rgba(255, 255, 255, 0.12);
  background: rgba(255, 255, 255, 0.04); color: #cbd5e1; cursor: pointer;
  font-size: 0.7rem; line-height: 1;
}
.lx-order-up:hover, .lx-order-down:hover { border-color: rgba(var(--lx-accent), 0.5); color: #fff; }
.lx-order-up:disabled, .lx-order-down:disabled { opacity: 0.4; cursor: default; }

/* Check button */
.lx-check {
  display: inline-flex; align-items: center; gap: 0.4rem;
  padding: 0.5rem 1.1rem; border-radius: 999px;
  font-size: 0.85rem; font-weight: 600;
  background: rgba(var(--lx-accent), 0.16);
  color: rgb(var(--lx-accent));
  border: 1px solid rgba(var(--lx-accent), 0.35);
  cursor: pointer; transition: background 0.12s ease;
}
.lx-check:hover { background: rgba(var(--lx-accent), 0.26); }
.lx-check:disabled { opacity: 0.5; cursor: default; }

/* Result + explanation */
.lx-result { font-size: 0.9rem; font-weight: 600; }
.lx-result-pass { color: rgb(var(--lx-pass)); }
.lx-result-fail { color: rgb(var(--lx-fail)); }
.lx-result-hint { color: #fcd34d; }
.lx-explain {
  border-left: 3px solid rgba(var(--lx-accent), 0.5);
  background: rgba(var(--lx-accent), 0.06);
  border-radius: 0 0.5rem 0.5rem 0;
  padding: 0.7rem 0.9rem;
  color: #cbd5e1; font-size: 0.92rem;
}
.lx-explain :is(p) { margin: 0.2rem 0; }

/* Completed exercise: keeps its OWN tint (so the lesson stays multicolour even after every box
   is solved), just firming the border in that hue. The green "✓" result line + the locked
   controls are the done signal — the box colour is its identity, not a status. */
.lx-exercise.lx-done { border-color: rgba(var(--lx-accent), 0.6); }
/* a finished exercise stops responding to drag / click */
.lx-exercise.lx-done :is(.lx-order-item, .lx-match-chip) { pointer-events: none; cursor: default; }
.lx-exercise.lx-done .lx-match-chip { border-color: rgba(var(--lx-accent), 0.6); background: rgba(var(--lx-accent), 0.16); }

/* ============================ Lesson page: visualizations ============================ */

.code-step.lx-visualization {
  border: 1px solid rgba(104, 130, 176, 0.34);
  border-radius: 0.9rem;
  padding: 1.15rem 1.35rem;
  background:
    radial-gradient(120% 90% at 50% -8%, rgba(48, 78, 142, 0.12), transparent 60%),
    rgba(12, 20, 38, 0.4);
  box-shadow: 0 0 36px rgba(30, 58, 124, 0.16), inset 0 1px 0 rgba(255, 255, 255, 0.04);
}
.lx-visualization .lx-practice-label { color: rgb(143, 168, 207); }
.lx-viz-goal :is(p) { margin: 0; }

.lx-collision-lab {
  display: flex;
  flex-direction: column;
  gap: 0.95rem;
}
.lx-collision-stage {
  border: 1px solid rgba(255, 255, 255, 0.09);
  border-radius: 0.8rem;
  background:
    linear-gradient(180deg, rgba(15, 23, 42, 0.5), rgba(15, 23, 42, 0.12)),
    radial-gradient(circle at 50% 20%, rgba(103, 232, 249, 0.09), transparent 42%);
  padding: 1rem 0.85rem;
}
.lx-collision-track {
  position: relative;
  height: 7.25rem;
  overflow: hidden;
  border-radius: 0.7rem;
  background:
    linear-gradient(90deg, rgba(255, 255, 255, 0.04) 1px, transparent 1px) 0 0 / 12.5% 100%,
    linear-gradient(180deg, transparent 0 52%, rgba(148, 163, 184, 0.25) 52% 54%, transparent 54%);
}
.lx-collision-centre {
  position: absolute;
  inset: 0 auto 0 50%;
  width: 1px;
  background: rgba(103, 232, 249, 0.45);
  transform: translateX(-50%);
}
.lx-cart {
  position: absolute;
  top: 50%;
  width: 4.9rem;
  height: 3.2rem;
  transform: translate(-50%, -50%);
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 0.65rem 0.65rem 0.45rem 0.45rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.15rem;
  box-shadow: 0 12px 24px rgba(0, 0, 0, 0.28);
}
.lx-cart::before,
.lx-cart::after {
  content: "";
  position: absolute;
  bottom: -0.42rem;
  width: 0.7rem;
  height: 0.7rem;
  border-radius: 999px;
  background: #0f172a;
  border: 2px solid rgba(226, 232, 240, 0.6);
}
.lx-cart::before { left: 0.75rem; }
.lx-cart::after { right: 0.75rem; }
.lx-cart-a { background: linear-gradient(160deg, rgba(34, 211, 238, 0.34), rgba(14, 165, 233, 0.16)); }
.lx-cart-b { background: linear-gradient(160deg, rgba(251, 146, 60, 0.32), rgba(244, 63, 94, 0.14)); }
.lx-cart-joined { border-color: rgba(52, 211, 153, 0.48); }
.lx-cart-name {
  font-weight: 800;
  color: #fff;
  line-height: 1;
}
.lx-cart-speed {
  max-width: 4.1rem;
  color: #dbeafe;
  font-size: 0.72rem;
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
  text-align: center;
  white-space: nowrap;
}

.lx-collision-readout {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.6rem;
}
.lx-readout-card {
  min-height: 4.05rem;
  padding: 0.65rem 0.75rem;
  border: 1px solid rgba(255, 255, 255, 0.09);
  border-radius: 0.7rem;
  background: rgba(255, 255, 255, 0.025);
}
.lx-readout-card--accent {
  border-color: rgba(103, 232, 249, 0.24);
  background: rgba(103, 232, 249, 0.055);
}
.lx-readout-label {
  display: block;
  margin-bottom: 0.25rem;
  color: #91a2b6;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.lx-readout-card strong {
  color: #eef7ff;
  font-size: 0.95rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

.lx-collision-controls {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.75rem 0.9rem;
}
.lx-viz-control {
  display: grid;
  gap: 0.35rem;
  min-width: 0;
}
.lx-viz-control-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
  color: #cbd5e1;
  font-size: 0.86rem;
}
.lx-viz-control-top strong {
  color: #fff;
  font-variant-numeric: tabular-nums;
}
.lx-viz-range {
  width: 100%;
  accent-color: rgb(34, 211, 238);
}
.lx-viz-unit {
  color: #7f8fa4;
  font-size: 0.75rem;
  line-height: 1;
}
.lx-collision-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}
.lx-viz-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  min-height: 2.25rem;
  padding: 0.48rem 0.9rem;
  border: 1px solid rgba(34, 211, 238, 0.36);
  border-radius: 999px;
  background: rgba(34, 211, 238, 0.14);
  color: rgb(165, 243, 252);
  font-size: 0.86rem;
  font-weight: 650;
  cursor: pointer;
}
.lx-viz-button:hover:not(:disabled) { background: rgba(34, 211, 238, 0.22); }
.lx-viz-button:disabled { opacity: 0.58; cursor: default; }
.lx-viz-button--quiet {
  border-color: rgba(255, 255, 255, 0.12);
  background: rgba(255, 255, 255, 0.045);
  color: #cbd5e1;
}
.lx-viz-button--quiet:hover:not(:disabled) { background: rgba(255, 255, 255, 0.08); }
.lx-viz-button svg {
  width: 1rem;
  height: 1rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.lx-viz-button svg[data-icon="play"] { fill: currentColor; stroke: none; }
.lx-collision-equation {
  margin: 0;
  color: #aebdce;
  font-size: 0.88rem;
  line-height: 1.55;
}
.lx-collision-equation span {
  color: #e8eef6;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
}

@media (max-width: 640px) {
  .code-step.lx-visualization { padding: 1rem; }
  .lx-collision-readout,
  .lx-collision-controls { grid-template-columns: 1fr; }
  .lx-collision-track { height: 6.5rem; }
  .lx-cart { width: 4.25rem; }
  .lx-cart-speed { max-width: 3.7rem; font-size: 0.68rem; }
}

/* ===================== Rich content: icons · callouts · grids ===================== */

/* Inline icon ({icon:NAME} → Lucide svg). Sized in em so it tracks the text, and
   coloured with the accent. --lx-accent is contextual, so an icon inside a callout
   inherits that callout's accent (amber / violet) while body icons stay cyan. */
.lx-ico {
  display: inline-block;
  width: 1.15em; height: 1.15em;
  vertical-align: -0.2em;
  flex-shrink: 0;
  color: rgb(var(--lx-accent));
}

/* A Lucide icon leading a step heading: cyan accent (inherits --lx-accent from
   :root), sized to the heading and centred on one line with the text. */
.lx-prose h2:has(.lx-ico) { display: flex; align-items: center; gap: 0.55rem; }
.lx-prose h2 .lx-ico { width: 1.05em; height: 1.05em; vertical-align: 0; }
/* A heading that mixes Latin + interlinear 汉字 (e.g. "Adjective + 极了"): once the heading is
   `display:flex` (it has an icon), the 汉字's reserved pinyin <rt> row — which sits IN FLOW above
   the 汉字 — pushes the 汉字 BELOW the Latin and inflates the heading height. In headings, drop the
   reserved pinyin so the 汉字 renders inline with the Latin text. (The lesson BODY keeps its hover
   pinyin; the English gloss is absolute, so it still reveals on hover without shifting anything.) */
.lx-prose h2 .lx-w { padding-top: 0; }
.lx-prose h2 .lx-w rt { display: none; }

/* Per-icon colour palette — a heading icon authored as {icon:NAME colour} gets an
   lx-ico--<colour> class; these override the default cyan (--lx-accent). */
.lx-ico--cyan { color: rgb(34, 211, 238); }
.lx-ico--sky { color: rgb(56, 189, 248); }
.lx-ico--blue { color: rgb(96, 165, 250); }
.lx-ico--indigo { color: rgb(129, 140, 248); }
.lx-ico--violet { color: rgb(167, 139, 250); }
.lx-ico--fuchsia { color: rgb(232, 121, 249); }
.lx-ico--pink { color: rgb(244, 114, 182); }
.lx-ico--rose { color: rgb(251, 113, 133); }
.lx-ico--orange { color: rgb(251, 146, 60); }
.lx-ico--amber { color: rgb(251, 191, 36); }
.lx-ico--yellow { color: rgb(250, 204, 21); }
.lx-ico--lime { color: rgb(163, 230, 53); }
.lx-ico--green { color: rgb(74, 222, 128); }
.lx-ico--emerald { color: rgb(52, 211, 153); }
.lx-ico--teal { color: rgb(45, 212, 191); }

/* Step-heading TEXT: ONE consistent colour (a different colour per heading reads as a chaotic
   rainbow). A soft rose — distinct from every body accent: cyan = key terms, yellow = target
   language, violet = "…" meanings, and the harder rose-red #f43f5e = the ✗ mark (this is the
   lighter rose-300, so it reads as a soft heading tint, not an error). The icon keeps its own
   per-step colour. Scoped to the language lessons (HSK + English pilot). */
.lx-lesson[data-slug^="hsk"] .lx-prose h2,
.lx-lesson[data-slug^="en_"] .lx-prose h2 { color: #fda4af; }

/* Tick / cross marks ({✓}/{✗} → coloured by render_learn_md). */
.lx-yes { color: rgb(var(--lx-pass)); font-weight: 700; }
.lx-no { color: rgb(var(--lx-fail)); font-weight: 700; }

/* ── HSK Chinese lessons — STRUCTURED font colours ──────────────────────────────────────────
   A small, SEMANTIC palette: each colour means ONE thing; the English prose stays the neutral
   slate base so readability stays high. Scoped to hsk* via the lesson root's data-slug.
     • 汉字 (Chinese)          → light yellow — the language itself, always (even when bold)
     • key English term (bold) → cyan        — the meta-vocabulary ("Adjective", "extremely"…);
                                               inside a callout it adopts that callout's hue (--lx-accent)
     • English MEANING in "…"  → soft violet — the gloss / translation of a term (.lx-q span,
                                               wrapped in web/learn_render.py)
     • pinyin                  → per-tone colours (.lx-tN, on reveal)
     • ✓ correct / ✗ wrong     → green / red (.lx-yes / .lx-no)
     • English explanation      → slate (unchanged — the readable base) */
.lx-lesson[data-slug^="hsk"] .lx-prose strong { color: rgb(var(--lx-accent)); }   /* bold English → cyan / callout hue */
.lx-lesson[data-slug^="hsk"] .lx-prose .lx-w ruby { color: #fde68a; }   /* every 汉字 → light yellow (a DIRECT colour on the ruby beats bold's inherited cyan, so 汉字 stay yellow even when bold) */
.lx-lesson[data-slug^="hsk"] .lx-prose .lx-q { color: #c4b5fd; }        /* English meaning in "…" → soft violet */
/* The big standalone EXAMPLE sentences (a {zh} on its own line) follow a bullet point, so indent
   them to sit under the bullet text they illustrate. `p:has(> .lx-zh)` hits only the standalone
   showcase, not the inline .lx-auto 汉字. */
.lx-lesson[data-slug^="hsk"] .lx-prose p:has(> .lx-zh) { padding-left: 1.45em; }

/* ── English-for-Chinese lessons — STRUCTURED font colours (mirror of the HSK palette) ───────
   Same idea, opposite direction: the ENGLISH the learner is studying stands out; the Chinese
   explanation stays the neutral slate base so readability stays high. Same palette as the HSK
   rules above (light yellow / cyan / violet). Scoped via the lesson root's data-slug (+ data-nn
   while previewing a single lesson).
     • English being taught        → light yellow — the {en:…} word (.lx-en-t) + italic *example*
     • key term (bold)             → cyan / callout hue — the grammar word taught ("will", "be going to")
     • meaning / form in "…"       → soft violet (.lx-q span, wrapped in web/learn_render.py)
     • Chinese gloss of a word     → cyan  (.lx-en-zh, unchanged)
     • Chinese explanation         → slate (unchanged — the readable base) */
.lx-lesson[data-slug^="en_"] .lx-prose strong { color: rgb(var(--lx-accent)); }
/* Italic = the target English ONLY in English mode (.en.md, rendered by en + gloss modes). The
   Chinese-mode body (.zh.md, rendered only when mode-zh) uses italic for CHINESE grammar terms
   (祈使句, 疑问词, 地点介词…), which must stay slate — never yellow. So scope to `:not(.mode-zh)`. */
html:not(.mode-zh) .lx-lesson[data-slug^="en_"] .lx-prose em { color: #fde68a; font-style: normal; font-weight: 600; }
.lx-lesson[data-slug^="en_"] .lx-prose .lx-en .lx-en-t { color: #fde68a; }
.lx-lesson[data-slug^="en_"] .lx-prose .lx-q { color: #c4b5fd; }

/* Callouts ({callout:key|warn|example} … {/callout}): a left-accent panel with a
   small labelled header. The accent colour is the only per-type difference. */
.lx-callout {
  margin: 1.1rem 0;
  padding: 0.9rem 1.05rem;
  border-radius: 0.8rem;
  border: 1px solid rgba(var(--lx-accent), 0.45);   /* a full box, like the practice cards */
  background: rgba(var(--lx-accent), 0.07);
}
.lx-callout--key { --lx-accent: 34, 211, 238; }      /* cyan */
.lx-callout--warn { --lx-accent: 245, 158, 11; }     /* amber */
.lx-callout--example { --lx-accent: 167, 139, 250; } /* violet */
.lx-callout--tip { --lx-accent: 52, 211, 153; }      /* emerald — pronunciation / culture tips */
/* Header row: the icon and the label sit on ONE line, vertically centred together. */
.lx-callout-head { display: flex; align-items: center; gap: 0.45rem; margin-bottom: 0.5rem; }
.lx-callout-head .lx-ico { flex-shrink: 0; width: 1.15rem; height: 1.15rem; color: rgb(var(--lx-accent)); }
.lx-callout-label {
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.09em; text-transform: uppercase;
  color: rgb(var(--lx-accent)); line-height: 1;
}
.lx-callout :is(p) { margin: 0.3rem 0; color: #d2dbe8; }
.lx-callout :is(p):first-of-type { margin-top: 0; }
.lx-callout :is(p):last-child { margin-bottom: 0; }
.lx-callout :is(ul, ol) { margin: 0.3rem 0; padding-left: 1.2rem; color: #d2dbe8; }

/* The five SI base units, authored as raw HTML — all five on ONE row, wrapping
   only on a narrow screen. */
.lx-grid {
  display: grid;
  gap: 0.6rem;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  margin: 1.1rem 0;
}
.lx-grid--two { grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr)); }
@media (max-width: 600px) { .lx-grid { grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr)); } }

.lx-unit-card {
  --lx-accent: 34, 211, 238;
  display: flex; flex-direction: column; align-items: center; gap: 0.15rem;
  padding: 0.9rem 0.6rem;
  border: 1px solid rgba(255, 255, 255, 0.09);
  border-radius: 0.8rem;
  background: rgba(255, 255, 255, 0.025);
  text-align: center;
}
.lx-unit-card .lx-ico { width: 1.7rem; height: 1.7rem; color: rgb(var(--lx-accent)); margin-bottom: 0.2rem; vertical-align: 0; }
.lx-unit-card .lx-unit-name { color: #93a3b6; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.05em; }
.lx-unit-card .lx-unit-sym { color: #fff; font-size: 1.2rem; font-weight: 700; }
.lx-unit-card .lx-unit-meas { color: #94a3b8; font-size: 0.74rem; }

/* Derived-units recap: a 2×2 grid of cards, each naming a unit, the quantity relation
   it comes from, and its form in SI base units — typeset with KaTeX (crisp + scalable)
   rather than a rasterised diagram. Authored as raw HTML in the lesson markdown. */
.lx-deriv {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.7rem;
  margin: 1.15rem 0 0.3rem;
}
.lx-deriv-card {
  padding: 0.85rem 1rem 0.95rem;
  border: 1px solid rgba(103, 232, 249, 0.22);
  border-radius: 0.85rem;
  background: rgba(103, 232, 249, 0.045);
}
.lx-deriv-head { font-size: 0.86rem; color: #93a3b6; }
.lx-deriv-head b { margin-right: 0.2rem; font-size: 1.3rem; font-weight: 700; color: #67e8f9; }
.lx-deriv-from { margin-top: 0.5rem; font-size: 0.8rem; color: #8b9bb0; }
.lx-deriv-eq { margin-top: 0.3rem; color: #e8eef6; font-size: 1.02rem; line-height: 1; }
.lx-deriv-eq .katex { font-size: 1.05em; }
@media (max-width: 560px) { .lx-deriv { grid-template-columns: 1fr; } }

/* Estimate stat-cards — native icon cards (KaTeX values) in the same visual
   language as the base-unit cards; replaces a rasterised reference image. The
   widest fact (the human) spans the full row; the rest sit in a 2×2. */
.lx-estimates {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.6rem;
  margin: 1.1rem 0 0.3rem;
}
.lx-est {
  display: flex; align-items: center; gap: 0.75rem;
  padding: 0.72rem 0.95rem;
  border: 1px solid rgba(255, 255, 255, 0.09);
  border-radius: 0.8rem;
  background: rgba(255, 255, 255, 0.025);
}
.lx-est--wide { grid-column: 1 / -1; }
.lx-est .lx-ico { flex: none; width: 1.7rem; height: 1.7rem; }
.lx-est-text { display: flex; flex-direction: column; gap: 0.12rem; min-width: 0; }
.lx-est-name { color: #93a3b6; font-size: 0.74rem; text-transform: uppercase; letter-spacing: 0.05em; }
.lx-est-val { color: #e8eef6; font-size: 0.96rem; }
@media (max-width: 560px) { .lx-estimates { grid-template-columns: 1fr; } }

/* HSK vocabulary word cards (Learn lessons). Authored as raw HTML in the markdown:
   <div class="lx-vocab"><div class="lx-word"><span class="lx-hanzi">阿姨</span>
     <span class="lx-word-text"><span class="lx-py">ā yí</span>
     <span class="lx-en">auntie — …</span></span></div></div>
   Large 汉字 + pinyin + English; add .lx-word--wide for a full-row card. */
.lx-vocab { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.6rem; margin: 1.1rem 0 0.3rem; }
.lx-word {
  display: flex; align-items: center; gap: 0.85rem;
  padding: 0.7rem 0.95rem;
  border: 1px solid rgba(255, 255, 255, 0.09);
  border-radius: 0.8rem;
  background: rgba(255, 255, 255, 0.025);
}
.lx-word--wide { grid-column: 1 / -1; }
.lx-hanzi { flex: none; font-size: 2.1rem; line-height: 1; font-weight: 600; color: #f4d27a; }
.lx-word-text { display: flex; flex-direction: column; gap: 0.14rem; min-width: 0; }
.lx-py { font-size: 0.86rem; letter-spacing: 0.01em; color: #67e8f9; }
.lx-en { font-size: 0.95rem; color: #e8eef6; }
.lx-en b { color: #f4d27a; font-weight: 600; }
/* Optional per-syllable Mandarin tone colours (wrap each syllable): 1 high · 2 rising
   · 3 dip · 4 falling · 0 neutral. */
.lx-t1 { color: #f87171; } .lx-t2 { color: #fbbf24; } .lx-t3 { color: #34d399; }
.lx-t4 { color: #60a5fa; } .lx-t0 { color: #94a3b8; }
@media (max-width: 560px) { .lx-vocab { grid-template-columns: 1fr; } }

/* HSK course overview → "drill the whole level in the flashcard trainer" link. */
.lx-practice-all {
  display: inline-flex; align-items: center; gap: 0.5rem;
  margin: 0.1rem 0 1.2rem;
  padding: 0.6rem 0.95rem;
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 0.75rem;
  background: rgba(255, 255, 255, 0.03);
  color: #dbe3ee; font-size: 0.9rem; font-weight: 500; text-decoration: none;
  transition: background 0.15s, border-color 0.15s;
}
.lx-practice-all:hover { background: rgba(103, 232, 249, 0.08); border-color: rgba(103, 232, 249, 0.35); }
.lx-pa-ico { width: 1.05rem; height: 1.05rem; color: #67e8f9; flex: none; }
.lx-pa-go { width: 1rem; height: 1rem; color: #8aa0b6; flex: none; }

/* Interlinear annotated Chinese ({zh:…} → web/learn_render.py). Each .lx-w stacks an English
   gloss (top) over ruby pinyin over the 汉字; pinyin syllables are tone-coloured (.lx-tN above).
   Reveal model: gloss + pinyin hidden until per-word hover/tap; the pinyin row stays RESERVED
   (visibility, not display) so revealing shifts nothing. Always-on inside a .lx-zh-stage card. */
.lx-zh { cursor: default; }   /* no "expandable" underline on the Learn page (per user) */
.lx-zh:focus { outline: none; }
.lx-w {
  position: relative; display: inline-block; text-align: center; vertical-align: bottom;
  padding: 0.1rem 0.05em 0; border-radius: 0.3rem;   /* compact: the English overlay sits ABOVE the
     word (bottom:100%) and grows upward on hover, so NO tall reserved band is needed — hanzi lines
     stay tight. Pinyin (rt) is still reserved inside the ruby. Mirrors the news reader (24-reading). */
}
/* On hover/tap, a SUBTLE 1px ring (no fill) around the word — ties its pinyin + English to this
   汉字. box-shadow → no layout shift; follows the .lx-w border-radius. (user steer 2026-06-19) */
.lx-w.is-open { box-shadow: 0 0 0 1px rgba(148, 163, 184, 0.4); }
.lx-w ruby { font-size: 1.55rem; line-height: 1.5; color: #eef2f7; }     /* readable hanzi */
.lx-w rt { font-size: 0.46em; font-weight: 600; line-height: 1; padding-bottom: 0.3em; visibility: hidden; }  /* reserve the row + a little gap above the 汉字 */
.lx-w-x { color: #cbd5e1; font-size: 1.55rem; vertical-align: bottom; }   /* punctuation between words — sized to the 汉字, not the small body text */
.lx-w-en {   /* plain English ABOVE the word, revealed on hover. Absolutely positioned + centred so
                its width never stretches the word (no inter-word gaps); reserves NO vertical space —
                it grows UPWARD (bottom:100%), so hanzi lines stay compact. A SOLID deep-blue box keeps
                it readable where it overlaps the line above; long glosses wrap within a bounded width
                and never run off the column edge. Same model as the news reader (24-reading.css). */
  position: absolute; left: 50%; bottom: 100%; transform: translateX(-50%);
  margin-bottom: 0.3rem;   /* gap between the English and the pinyin below it */
  visibility: hidden; pointer-events: none;
  width: max-content; max-width: min(15rem, 80vw); white-space: normal; overflow-wrap: break-word;
  text-align: center; line-height: 1.2; font-size: 0.64rem; color: #c4d0e6;
  background: #172546; border: 1px solid rgba(96, 165, 250, 0.4);
  border-radius: 0.3rem; padding: 0.1rem 0.4rem; box-shadow: 0 1px 5px rgba(0, 0, 0, 0.6);
}
/* reveal: pinyin + English per word on hover/tap, and all on keyboard focus */
/* Reveal — EXACTLY ONE word at a time. `.is-open` is set by the shared engine
   (web/static/js/annotate-reveal.js) for tap/click AND keyboard walking (Tab onto the sentence, ←/→).
   No `.lx-zh:focus` rule: a touch tap focuses the sentence too (tabindex=0), so a focus-based
   reveal opened every word in the sentence at once on a phone (bug 2026-07-16); `:focus-visible`
   is no fix either — Chrome matches it on programmatic focus. */
.lx-w.is-open rt { visibility: visible; }
.lx-w.is-open .lx-w-en { visibility: visible; }
/* Mouse hover, pointer devices ONLY — on touch, :hover sticks to the tapped word and would leave
   it revealed next to the newly-tapped one. */
@media (hover: hover) {
  .lx-w:hover { box-shadow: 0 0 0 1px rgba(148, 163, 184, 0.4); }
  .lx-w:hover rt, .lx-w:hover .lx-w-en { visibility: visible; }
}
/* Copy yields ONLY the 汉字 — the pinyin (rt) + English gloss are never selected/copied,
   so selecting an annotated sentence (even while revealed/hovered) copies clean Chinese.
   The hanzi (ruby base) and Chinese punctuation (.lx-w-x) stay selectable. */
.lx-w rt, .lx-w-en { -webkit-user-select: none; user-select: none; }

/* Showcase: a centred card with all three layers always shown (interlinear).
   ⚠️ DEAD — no renderer or template emits .lx-zh-stage (checked 2026-07-16); it is the ONLY
   remaining rule that would show more than one word's annotation at a time. Reveal is
   word-by-word only (user rule), so do NOT start emitting this without asking first. */
.lx-zh-stage {
  margin: 1.15rem 0; padding: 1rem 1.1rem 0.85rem;
  border: 1px solid rgba(255, 255, 255, 0.09); border-radius: 0.9rem;
  background: rgba(255, 255, 255, 0.02); text-align: center;
}
.lx-zh-stage .lx-zh { border-bottom: 0; cursor: default; }
.lx-zh-stage .lx-w { vertical-align: top; padding: 0 0.3em; }
.lx-zh-stage .lx-w rt { visibility: visible; }
.lx-zh-stage .lx-w-en { visibility: visible; }
.lx-zh-legend { margin-top: 0.7rem; font-size: 0.68rem; color: #7c8aa0; letter-spacing: 0.04em; }

/* Remove the twinkling starfield on a Learn LESSON page — a lesson shows one short step, so the
   space below it fills with distracting stars (and they sit behind the content too). The aurora
   colour wash + gradient stay for ambiance; only the stars go. Scoped via :has(.lx-lesson)
   (17-learn.css also loads on the subject/gallery/reference pages, which keep the full starfield).
   (User: stars below the lesson content are distracting.) */
body:has(.lx-lesson) .space-stars { display: none; }

/* Auto-annotated inline Chinese in lesson PROSE (web/lesson_annotate.py) emits the SAME interlinear
   .lx-zh / .lx-w markup as the authored {zh: …} showcase (via render_zh), wrapped per run in
   .lx-auto. It uses the base .lx-w design — RESERVED pinyin + English revealed on hover, cyan
   highlight — so the reveal always has clear space and never overlaps the line above (works
   everywhere). The taller Chinese-containing line ("big line distance") is the accepted trade-off.
   The ONLY override: inline 汉字 render at the SURROUNDING PROSE size (not the big 1.55rem showcase
   size). The {zh:…} showcase (not .lx-auto) keeps the big interlinear on its own line. */
.lx-auto .lx-w ruby { font-size: inherit; }   /* 汉字 at the prose text size, not the big showcase size */
.lx-auto .lx-w rt { font-size: 0.6em; }        /* keep pinyin readable at the smaller size */

/* Derived-unit "builder": equation → base units → named unit (native + KaTeX),
   replacing a rasterised flow diagram. Authored as raw HTML in the markdown. */
.lx-build { display: flex; flex-direction: column; gap: 0.7rem; margin: 1.1rem 0 0.3rem; }
.lx-build-row {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem;
  padding: 0.7rem 0.85rem;
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 0.8rem;
  background: rgba(255, 255, 255, 0.022);
}
.lx-build-eq {
  color: #c6d2e1; font-size: 0.88rem;
  padding: 0.16rem 0.55rem;
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 0.5rem;
  background: rgba(255, 255, 255, 0.03);
}
.lx-build-base { color: #e8eef6; }
.lx-build-arrow { color: #67e8f9; font-weight: 700; }
.lx-build-unit {
  padding: 0.16rem 0.6rem;
  border: 1px solid rgba(103, 232, 249, 0.28);
  border-radius: 0.55rem;
  background: rgba(103, 232, 249, 0.06);
  color: #cfe9ef; font-size: 0.9rem; white-space: nowrap;
}
.lx-build-unit b { color: #67e8f9; font-size: 1.15rem; margin-right: 0.12rem; }

/* Markdown tables (truth tables, comparisons, trade-offs) on the dark page.
   commonmark renders a real <table>; these give it borders + a header accent and
   let a wide one scroll on narrow screens. Authored as plain markdown tables. */
.lx-prose table {
  border-collapse: collapse;
  margin: 1.1rem 0;
  font-size: 0.94rem;
  color: #cdd8e6;
  display: block;
  width: max-content;
  max-width: 100%;
  overflow-x: auto;
}
.lx-prose th,
.lx-prose td {
  border: 1px solid rgba(255, 255, 255, 0.12);
  padding: 0.4rem 0.8rem;
  text-align: left;
  vertical-align: top;
}
.lx-prose th { background: rgba(103, 232, 249, 0.09); color: #e8eef6; font-weight: 600; }
.lx-prose tbody tr:nth-child(even) td { background: rgba(255, 255, 255, 0.02); }
.lx-prose table code { white-space: nowrap; }

@media (prefers-reduced-motion: reduce) {
  .lx-lesson, .lx-option, .lx-check { transition: none; }
  .lx-lesson:hover { transform: none; }
}

/* ─── Vocabulary step (first step) ─────────────────────────────────────────────────
   The lesson's key words, shown as the FIRST .code-step (was a bottom-of-lesson table)
   so the learner previews them once and then reads the content uninterrupted. Mirrors
   the /know vocab table; its .learn-vocab-table rules live in the page-scoped, un-bundled
   06-landing.css that this page doesn't load — so they're duplicated below (kept in sync
   by eye; both trivial fixed-column tables). Page-scoped → no bundle rebuild. */
.lx-vocab-label { color: rgba(103, 232, 249, 0.85); }   /* cyan — book icon, like the Handout button */
.learn-vocab-table {
  width: auto;                                     /* hug content — don't stretch columns to fill the box */
  max-width: 100%;
  border-collapse: collapse;
  table-layout: auto;                              /* each column sizes to its own content (below) */
  font-size: 1rem;
  line-height: 1.6;
}
/* Every column hugs its own content — the natural gap comes from the cell padding, and the table
   (width:auto above) never stretches a column wider than necessary. Matches the /know copy in
   06-landing.css; the old `col-py: 99%` + `overflow-wrap: anywhere` pair collapsed English/中文 to
   ~1 char per line ("comically narrow"). Long terms wrap (break-word below), not collapse. */
.learn-vocab-table col.col-en,
.learn-vocab-table col.col-zh,
.learn-vocab-table col.col-py { width: auto; }
.learn-vocab-table thead th {
  text-align: left;
  font-size: 0.78rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: rgb(165, 243, 252);          /* cyan-200 */
  padding: 0 1.5rem 0.7rem 0;         /* airier column gap + a touch more room under the header */
  border-bottom: 1px solid rgba(255, 255, 255, 0.12);
}
/* Roomier rows than the /know copy: this is a first-step preview the learner dwells on, so give
   it a little more vertical + inter-column breathing room (column-width logic still matches /know). */
.learn-vocab-table tbody td {
  vertical-align: top;
  padding: 0.75rem 1.5rem 0.75rem 0;
  color: rgb(241, 245, 249);          /* slate-100 */
  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
  overflow-wrap: break-word;          /* long terms wrap when squeezed, but never collapse the column's min width */
}
.learn-vocab-table thead th:last-child,
.learn-vocab-table tbody td:last-child { padding-right: 0; }
.learn-vocab-table tbody tr:nth-child(odd) td { background: rgba(255, 255, 255, 0.015); }
.learn-vocab-table tbody tr:last-child td { border-bottom: none; }
.learn-vocab-table tbody td:nth-child(2) { font-size: 1.075rem; }       /* CJK optically larger */
.learn-vocab-table tbody td:nth-child(3) { color: rgb(148, 163, 184); } /* pinyin muted (slate-400) */
/* Hand-picked concept icon before the English term (same map as Morning Reading; built by
   web/templating.py:vocab_icon). The per-row COLOUR is set INLINE by that helper (mixed hues,
   cycled by row index), so it can't be lost if this sheet is edited; here we only size/space
   the glyph. Web only; the vocab PDFs render greyscale on a separate path. */
.learn-vocab-table .vocab-icon {
  display: inline-block;
  width: 1.25em; height: 1.25em;      /* a touch bigger for legibility */
  margin-right: 0.6em;                /* a bit more breathing room before the word */
  vertical-align: -0.25em;            /* centre the larger glyph on the text */
}
.learn-vocab-table .vocab-icon svg { display: block; width: 100%; height: 100%; }
/* Hanging indent on the term column. The icon is an inline-block, so a two-word term that
   wraps ("physical quantity" on a phone) sent its second line back to the cell's left edge
   — i.e. UNDERNEATH the glyph — instead of aligning with the first. Indent the cell by the
   glyph's full advance (1.25em + 0.6em) and pull the first line back out by the same
   amount, so line 1 still starts at the icon and every continuation lines up with the
   word. Done with padding/text-indent rather than display:flex because a `td` must keep
   its table-cell display to take part in column sizing. */
.learn-vocab-table tbody td:first-child {
  padding-left: 1.85em;
  text-indent: -1.85em;
}
