/* Digital Bricks Training — the lesson kit's stylesheet (TASK-L154 §2, sprint 44).
 *
 * THE PRODUCT'S TOKEN NAMES, AS ALIASES OF NODEGX'S. The 8,271-line sheet this
 * template is ported from reads --paper, --ink, --thread, --edge; NodeGX's
 * built-in nodes and its Styles panel read --background, --foreground,
 * --primary, --border. Both vocabularies resolve to ONE value, which lives in
 * nodegx.project.json (metadata.designTokens.customTokens). Change a colour in
 * the Styles panel and every kit node follows.
 *
 * Four literals have no NodeGX role and carry their globals.css comment.
 * Everything below the alias block is LIFTED from globals.css by line range,
 * not rewritten: the class names are the contract the kit's nodes emit.
 */
:root {
  --paper:        var(--background);
  --paper-raised: var(--surface-raised);
  --ink:          var(--foreground);
  --ink-soft:     var(--muted-foreground);
  /* Captions, timestamps, meta — 4.57 / 5.27. No NodeGX role. */
  --ink-mute:     #786858;
  --thread:       var(--primary);
  /* The accent AS TEXT — links, eyebrows, accent words. 4.65 / 5.35. Coral itself is 2.92:1 as text. */
  --thread-deep:  #B8431A;
  --thread-tint:  color-mix(in srgb, var(--primary) 12%, var(--surface-raised));
  /* Amber, text-safe — encouragement + human recording ONLY. 4.68 / 5.39. */
  --warm:         #A3540A;
  --warm-tint:    var(--accent);
  --go:           var(--secondary);
  --go-tint:      color-mix(in srgb, var(--secondary) 12%, var(--surface-raised));
  --line:         var(--border-subtle);
  --edge:         3px solid var(--foreground);
  --edge-thin:    2px solid var(--foreground);
  /* Code surfaces: an ink block with ivory text — 15.50. */
  --code-bg:      #2A211B;
  --code-ink:     #FFFDF7;
  --scrim:        rgba(42, 33, 27, 0.5);
  /* ── THE DATAVIZ PALETTE (TASK-L159) ──────────────────────────────────────
     No NodeGX role, and DELIBERATELY NOT in the Styles panel: this is a
     VALIDATED SET, not eight independent colours. L88's finding is that every
     hand-built palette in this repo failed the dataviz validator, and sprint 43
     re-ran this one on the sticker book's own grounds — ALL PASS on ivory, with
     a contrast WARN on bare cream that every chart meets with a legend. Putting
     them in the Styles panel would invite the one edit that reliably breaks
     them: changing a hue by eye.

     Only two are read today, and both were chosen by measurement rather than by
     taste (sprint 37 L127): --series-1 for a coach's rating, --series-5 for the
     learner's own confidence. --series-2 passed the validator outright and was
     REJECTED on the rendered page, because #D95926 sits at hue 17 degrees,
     inside any honest red band - the learner's own confidence was being drawn
     in an alarm colour on a chart about how they are doing. The rest are here
     so that adding a third series is a choice from a validated set rather than
     a var() that silently resolves to nothing. */
  --series-1:     #3987E5;
  --series-2:     #D95926;
  --series-3:     #199E70;
  --series-4:     #C98500;
  --series-5:     #D55181;
  --series-6:     #008300;
  --series-7:     #9085E9;
  --series-8:     #E66767;
  --shadow-lifted: var(--shadow-md);
  --radius:       var(--radius-lg);
  --radius-sm:    var(--radius-md);
  --text-xs:      0.8125rem;
  --text-sm:      0.9375rem;
  --text-base:    1.0625rem;
  --text-lg:      1.3125rem;
  --text-xl:      1.75rem;
  --text-2xl:     2.25rem;
  --font-prose:   var(--font-sans);
  --font-ui:      var(--font-sans);
}

/* ── LIFTED from globals.css 163–182: the focus ring ──────────────────────── */
/* ── Focus-visible ring (all interactive elements) ──────────────────────────── */

/* Ink, not coral: the outline is the language now, and a coral ring on a coral
   button is invisible. */
:focus-visible {
  outline: 3px solid var(--ink);
  outline-offset: 3px;
  border-radius: 6px;
}

/* ── Reduced motion ─────────────────────────────────────────────────────────── */

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


/* ── LIFTED from globals.css 183–270: reading, callout body, step markdown ── */
/* ── Global layout helpers ──────────────────────────────────────────────────── */

/* Reading body typography — serif, generous measure, breathing line-height */
.reading p,
.reading li,
.reading blockquote {
  font-family: var(--font-prose);
  /* 16px, not --text-base (sprint 43 §2.5): Nunito's x-height is larger than
     the serif it replaced, so the same size read bigger. */
  font-size: 1rem;
  line-height: 1.68;
  color: var(--ink);
  max-width: 64ch;
  margin: 0 0 1em;
}

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

.reading h1, .reading h2, .reading h3 {
  font-family: var(--font-display);
  font-weight: 800;
  letter-spacing: 0;
  color: var(--ink);
  line-height: 1.25;
}

/* Prose links: a hairline underneath, promoting to the accent — never
   text-decoration (the landing page's rule). */
.reading a {
  color: var(--thread-deep);
  text-decoration: none;
  border-bottom: 1px solid var(--line);
}

.reading a:hover {
  border-bottom-color: var(--thread);
}

.reading code {
  font-family: var(--font-mono);
  font-size: 0.92em;
  background: var(--thread-tint);
  color: var(--thread-deep);
  padding: 1px 6px;
  border-radius: var(--radius-sm);
}

.reading pre {
  font-family: var(--font-mono);
  background: var(--thread-tint);
  border-radius: var(--radius-sm);
  padding: 1rem;
  overflow-x: auto;
  font-size: 0.92em;
}

/* Callout body — same treatment in a smaller context */
.callout-body p {
  font-size: 0.96rem;
  line-height: 1.55;
  margin: 0 0 0.5em;
}
.callout-body p:last-child { margin-bottom: 0; }
.callout-body code {
  font-family: var(--font-mono);
  font-size: 0.9em;
  background: var(--thread-tint);
  padding: 1px 5px;
  border-radius: var(--radius-sm);
}

/* Activity step markdown — tight, no extra paragraph margin inside list item */
.step-md p {
  margin: 0;
  line-height: 1.6;
}
.step-md strong { font-weight: 600; }
.step-md code {
  font-family: var(--font-mono);
  font-size: 0.88em;
  background: var(--thread-tint);
  color: var(--thread-deep);
  padding: 1px 5px;
  border-radius: var(--radius-sm);
}


/* ── LIFTED from globals.css 271–360: the workspace shell, the thread, the knots ── */
/* ── The workspace shell (lesson page, TASK-L59) ─────────────────────────────
 * The lesson stopped being a document and became a workshop: the teaching on
 * the left, the thing they are building on the right. ONE wrapper, ONE new
 * column — `.lesson-layout` inside it is unchanged, which is why the fragment
 * contract and every `grid-column: 2` rule below still hold.
 */
.lesson-workspace {
  max-width: 1360px;
  margin: 0 auto;
  padding: 40px 28px 140px;
  display: grid;
  grid-template-columns: minmax(0, 1fr) 340px;
  column-gap: 40px;
  align-items: start;
}

/* ── Thread layout (lesson page) ────────────────────────────────────────────── */

/*
 * .lesson-layout — the 2-column grid: 32px thread gutter + content column.
 * Padding/max-width now live on `.lesson-workspace`; the grid itself is
 * exactly what it has been since Sprint 8.
 */
.lesson-layout {
  position: relative;
  max-width: 920px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: 32px 1fr;
  column-gap: 20px;
}

/*
 * The thread — the design-language signature, and the fix for the Sprint-15
 * bug recorded in CLAUDE.md. The old `.thread-rail` div was a grid ITEM, so it
 * was auto-placed into row 1 and stood ~26px tall against a lesson thousands
 * of pixels long; `grid-row: 1 / -1` would not have saved it either, because
 * with no explicit rows line -1 resolves back to line 1. An absolutely
 * positioned pseudo-element does not care how many rows the grid has.
 */
.lesson-layout::before {
  content: "";
  position: absolute;
  top: 8px;
  bottom: 0;
  left: 16px;
  width: 2px;
  background: var(--line);
  transform: translateX(-50%);
  pointer-events: none;
}

/* Each section is a knot: positioned in column 2 with the knot dot reaching
   back into column 1 via a ::before pseudo-element. */
.lesson-section {
  grid-column: 2;
  position: relative;
  margin-bottom: 34px;
}

.lesson-section::before {
  content: "";
  position: absolute;
  left: -36px;
  top: 6px;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  background: var(--paper);
  border: var(--edge-thin);
  z-index: 1;
}

/* Tied knot (completed section — manually marked, never auto-set) */
.lesson-section.knot-tied::before {
  background: var(--go);
  border-color: var(--ink);
}

/* Warm knot (human recording) */
.lesson-section.knot-warm::before {
  border-color: var(--warm);
}

/* Current knot (section nearest the top of the viewport right now) —
   a quiet ring, same treatment as .path-step.active on the dashboard. */
.lesson-section.knot-current::before {
  box-shadow: 0 0 0 4px var(--thread-tint);
}


/* ── LIFTED from globals.css 795–914: the stepper and the step close ── */
/* ── The stepper (TASK-L48) ─────────────────────────────────────────────────
 * One step at a time. Both surfaces are grid-column: 2 SIBLINGS of the
 * .lesson-section knots, never a wrapper around them — a container would
 * break the thread rail's full-height span (Sprint 8 fragment invariant).
 *
 * Coaching invariants, expressed here as well as in LessonStepper.tsx:
 *   - Nothing is disabled or locked; every chip is a live button.
 *   - No numerals on screen. Position is order + .active styling; "Step n of N"
 *     is aria-label only.
 *   - --go means what it already means on a tied knot: done. Nothing
 *     accumulates, nothing scores.
 */
.lesson-stepper {
  grid-column: 2;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 30px;
}

.lesson-step-chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  max-width: 100%;
  padding: 7px 13px;
  border: var(--edge-thin);
  border-radius: 999px;
  background: var(--paper-raised);
  color: var(--ink-soft);
  font-family: var(--font-ui);
  font-size: 0.8125rem;
  line-height: 1.3;
  text-align: left;
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s, color 0.15s;
}

.lesson-step-chip:hover {
  background: var(--thread-tint);
  color: var(--ink);
}

.lesson-step-chip.active {
  background: var(--thread);
  color: var(--ink);
  font-weight: 600;
}

.lesson-step-chip.done {
  color: var(--ink);
}

.lesson-step-chip-dot {
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  border: var(--edge-thin);
  background: var(--paper);
}

.lesson-step-chip.active .lesson-step-chip-dot {
  background: var(--paper-raised);
}

.lesson-step-chip-check {
  flex: none;
  color: var(--go);
  font-size: 0.8125rem;
  line-height: 1;
}

/* The micro-win: closure at the end of the active step, then one way forward.
   A state change and a mark — never a score, a badge or a running total. */
.lesson-step-close {
  grid-column: 2;
  border: var(--edge);
  box-shadow: var(--shadow-lifted);
  border-radius: var(--radius);
  background: var(--paper-raised);
  padding: 20px 22px;
  margin: 6px 0 34px;
}

.lesson-step-close.done {
    background: var(--go-tint);
}

.lesson-step-close-eyebrow {
  font-family: var(--font-ui);
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  font-weight: 600;
  color: var(--ink-soft);
  margin: 0 0 8px;
}

.lesson-step-close.done .lesson-step-close-eyebrow {
  color: var(--go);
}

.lesson-step-close-goal {
  font-family: var(--font-prose);
  font-size: 1.0625rem;
  line-height: 1.5;
  color: var(--ink);
  margin: 0;
}

.lesson-step-close-check {
  color: var(--go);
}

.lesson-step-next {
  margin-top: 16px;
  font-size: 0.9375rem;
  padding: 12px 24px;
}

/* ── LIFTED from globals.css 915–955: opt-in depth under a reading ── */

/* Opt-in depth under a reading's gist (TASK-L46 authored, L48 renders).
   Native <details>: keyboard-reachable and self-announcing. Collapsed on
   every render — opening it is reading, not progress, and is never recorded. */
.reading-detail {
  margin-top: 14px;
  border-left: var(--edge-thin);
  padding: 4px 0 4px 16px;
}

.reading-detail-summary {
  display: inline-block;
  font-family: var(--font-ui);
  font-size: 0.8125rem;
  color: var(--ink-soft);
  cursor: pointer;
  list-style: none;
  padding: 2px 0;
}

.reading-detail-summary::-webkit-details-marker {
  display: none;
}

.reading-detail-summary::before {
  content: "▸ ";
  color: var(--thread-deep);
}

.reading-detail[open] > .reading-detail-summary::before {
  content: "▾ ";
}

.reading-detail-summary:hover {
  color: var(--thread-deep);
}

.reading-detail-body {
  margin-top: 10px;
  color: var(--ink-soft);
}

/* ── LIFTED from globals.css 2615–2690: the buttons ── */
/* ── Buttons ─────────────────────────────────────────────────────────────────── */

/* The sticker book's button (TASK-L150 §3): Grandstander 800, sentence case, an
   ink edge and the pop. Pressing it pushes it INTO the page — a 2px translate
   that swallows the shadow — and that is the only motion in the language;
   reduced-motion zeroes the transition below with everything else.

   NO LONGER UPPERCASE, which retires half of a recorded driver trap: a `.btn`'s
   `innerText` is now the copy as written (L120 hit `DRAFT THIS FOR ME`). Eyebrows
   and tabs are still transformed and the trap still bites there. */
.btn {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 0.9375rem;
  letter-spacing: 0;
  border: var(--edge);
  border-radius: 999px;
  box-shadow: var(--shadow-lifted);
  padding: 9px 20px;
  cursor: pointer;
  line-height: 1.4;
  display: inline-flex;
  align-items: center;
  transition: transform 0.08s ease, box-shadow 0.08s ease, filter 0.18s ease, background 0.18s ease;
}

.btn:active:not(:disabled) {
  transform: translate(2px, 2px);
  box-shadow: 2px 2px 0 var(--ink);
}

.btn:disabled {
  cursor: default;
  opacity: 0.55;
  box-shadow: none;
}

/* Rocket School's own button: ink on coral (4.6:1). */
.btn-thread {
  background: var(--thread);
  color: var(--ink);
}

.btn-thread:hover {
  filter: brightness(0.94);
}

/* Paper on teal: 4.67:1. */
.btn-go {
  background: var(--go);
  color: var(--paper);
}

.btn-go:hover {
  filter: brightness(0.94);
}

/* The quiet one is a ghost on ivory with NO pop — it is the second choice on a
   page, and a second shadow would make it compete with the first. It fills
   coral on hover, with ink text. */
.btn-quiet {
  background: var(--paper-raised);
  color: var(--ink);
  border: var(--edge);
  box-shadow: none;
}

.btn-quiet:hover {
  background: var(--thread);
  color: var(--ink);
}

.btn-quiet:active:not(:disabled) {
  box-shadow: none;
}


/* ── KIT-OWNED RULES (TASK-L155 §2) for the kinds whose source renderer styled
   inline (L151 §A listed nine). Tokens only; a node emits a class and the sheet
   decides. Coaching invariants: warm is encouragement and human recording,
   go is done, and nothing here is red. ──────────────────────────────────── */
.dbt-callout { display: flex; gap: 0.75rem; align-items: flex-start; padding: 1rem 1.125rem; border-radius: 12px; margin-bottom: 1.5rem; background: var(--thread-tint); }
.dbt-callout.tone-warning { background: var(--warm-tint); }
.dbt-callout.tone-reassurance { background: var(--go-tint); }
.dbt-callout-chip { font-family: var(--font-ui); font-size: 0.7rem; font-weight: 700; line-height: 1; text-transform: uppercase; letter-spacing: 0.08em; border-radius: 20px; padding: 5px 10px; white-space: nowrap; margin-top: 2px; flex-shrink: 0; background: var(--thread); color: var(--ink); }
.dbt-callout.tone-warning .dbt-callout-chip { background: var(--warm); color: var(--paper); }
.dbt-callout.tone-reassurance .dbt-callout-chip { background: var(--go); color: var(--paper); }
.dbt-callout .callout-body { flex: 1; color: var(--ink); line-height: 1.55; }

.dbt-capsule { background: var(--paper-raised); border: 1px solid var(--line); border-left: 3px solid var(--thread); border-radius: var(--radius); padding: 22px 24px; box-shadow: var(--shadow-lifted); }
.dbt-capsule-label { font-family: var(--font-ui); font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.1em; color: var(--thread-deep); font-weight: 600; margin: 0 0 8px; }
.dbt-capsule-text { font-family: var(--font-display); font-size: var(--text-lg); line-height: 1.5; margin: 0; color: var(--ink); }

.dbt-code { border-radius: var(--radius); overflow: hidden; margin-bottom: 1.5rem; border: 1px solid var(--line); }
.dbt-code-head { background: var(--code-bg); border-bottom: 1px solid var(--line); padding: 0.5rem 1rem; display: flex; align-items: center; }
.dbt-code-lang { font-family: var(--font-ui); font-size: 0.6875rem; font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-mute); }
.dbt-code-pre { background: var(--code-bg); margin: 0; padding: 1.25rem 1.5rem; overflow-x: auto; }
.dbt-code-pre code { color: var(--code-ink); font-family: var(--font-mono); font-size: 0.9rem; line-height: 1.6; white-space: pre; }
.dbt-code-explain { padding: 1rem 1.25rem; background: var(--paper-raised); border-top: 1px solid var(--line); font-size: 0.9rem; color: var(--ink-soft); line-height: 1.6; }
.dbt-code-explain p { margin: 0 0 0.5em; } .dbt-code-explain p:last-child { margin-bottom: 0; }

.dbt-quiz { background: var(--paper); border-radius: 12px; padding: 1.5rem; margin-bottom: 1.5rem; border: 1px solid var(--line); }
.dbt-quiz-question { font-size: 1rem; font-weight: 500; color: var(--ink); line-height: 1.55; margin: 0 0 1rem; }
.dbt-quiz-options { display: flex; flex-direction: column; gap: 0.625rem; }
.dbt-quiz-row { display: flex; gap: 0.75rem; }
.dbt-quiz-option { text-align: left; padding: 0.875rem 1rem; border-radius: 8px; border: 2px solid var(--line); background: var(--paper); cursor: pointer; font: inherit; color: var(--ink); line-height: 1.5; transition: border-color 0.15s, background 0.15s; }
.dbt-quiz-option.is-answered { cursor: default; }
.dbt-quiz-option.is-go { border-color: var(--go); background: var(--go-tint); }
.dbt-quiz-option.is-warm { border-color: var(--warm); background: var(--warm-tint); }
.dbt-quiz-feedback { margin-top: 0.5rem; font-size: 0.875rem; line-height: 1.5; color: var(--warm); }
.dbt-quiz-feedback.is-go { color: var(--go); }
.dbt-quiz-textarea, .dbt-textarea { width: 100%; min-height: 7rem; padding: 0.875rem; border-radius: 8px; border: 2px solid var(--line); font-size: 0.9375rem; line-height: 1.6; color: var(--ink); resize: vertical; font-family: inherit; box-sizing: border-box; margin-bottom: 0.75rem; background: var(--paper-raised); }
.dbt-btn { padding: 0.625rem 1.25rem; background: var(--thread); color: var(--ink); border: none; border-radius: var(--radius-sm); font: inherit; font-size: 0.9rem; font-weight: 500; cursor: pointer; }
.dbt-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.dbt-btn-quiet { background: none; border: 1px solid var(--line); border-radius: 6px; padding: 0.375rem 0.875rem; font: inherit; font-size: 0.8125rem; color: var(--ink-soft); cursor: pointer; }
.dbt-note { font-size: 0.875rem; color: var(--ink-soft); line-height: 1.5; margin: 0.5rem 0 0; }
.dbt-sent { color: var(--go); font-size: 0.9rem; font-weight: 500; margin: 0 0 0.625rem; }

.dbt-activity { background: var(--paper-raised); border-radius: 12px; padding: 1.5rem; margin-bottom: 1.5rem; border: 1px solid var(--line); }
.dbt-activity-title { font-weight: 600; font-size: 1rem; color: var(--ink); margin: 0 0 1.125rem; }
.dbt-activity-steps { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 0.75rem; }
.dbt-activity-step { display: flex; gap: 0.875rem; align-items: flex-start; cursor: pointer; transition: opacity 0.15s; }
.dbt-activity-step.is-done { opacity: 0.55; }
.dbt-activity-num { width: 1.75rem; height: 1.75rem; border-radius: 50%; background: var(--thread); color: var(--ink); font-size: 0.75rem; font-weight: 600; display: flex; align-items: center; justify-content: center; flex-shrink: 0; margin-top: 0.125rem; }
.dbt-activity-step.is-done .dbt-activity-num { background: var(--go); color: var(--paper); }
.dbt-activity-step.is-done .step-md { text-decoration: line-through; }
.dbt-activity-text { color: var(--ink); line-height: 1.6; font-size: 0.9375rem; }
.dbt-activity-fact { margin-top: 1.25rem; padding: 1rem; background: var(--thread-tint); border-radius: var(--radius-sm); border: 1px solid var(--line); }
.dbt-activity-fact-prompt { color: var(--thread-deep); font-size: 0.9rem; margin-bottom: 0.75rem; font-weight: 500; }

.dbt-video { margin-bottom: 1.5rem; }
.dbt-video-context { font-size: 0.9375rem; color: var(--ink-soft); line-height: 1.6; margin: 0 0 0.875rem; font-style: italic; }
.dbt-video-frame { position: relative; width: 100%; aspect-ratio: 16 / 9; border-radius: var(--radius); overflow: hidden; border: 1px solid var(--line); background: var(--ink); }
.dbt-video-frame iframe { display: block; width: 100%; height: 100%; border: none; }
.dbt-video-provenance { margin: 0.5rem 0 0; font-size: 0.8125rem; color: var(--ink-mute); }
.dbt-link { color: var(--thread-deep); font-weight: 500; font-size: 0.9375rem; text-decoration: none; border-bottom: 1px solid var(--line); }
.dbt-video-missing { font-size: 0.9375rem; color: var(--ink-mute); margin: 0; }

.dbt-figure { margin: 0 0 1.5rem; padding: 0; }
.dbt-figure-box { background: var(--paper-raised); border-radius: var(--radius); padding: 1.5rem; border: 1px solid var(--line); overflow-x: auto; display: flex; justify-content: center; }
.dbt-figure-box svg { max-width: 100%; height: auto; }
.dbt-figure-caption { margin-top: 0.5rem; text-align: center; font-size: 0.8125rem; color: var(--ink-mute); font-style: italic; }
.dbt-shot { position: relative; display: block; width: 100%; border-radius: var(--radius); overflow: hidden; border: 1px solid var(--line); }
.dbt-shot img { width: 100%; height: auto; display: block; }
.dbt-shot-overlay { position: absolute; inset: 0; pointer-events: none; }
.dbt-shot-overlay svg { width: 100%; height: 100%; }

/* A placeholder is an absence, not an alarm: plain ink, a dashed hairline, and the data it carries. */
.dbt-deferred { background: var(--paper-raised); border: 1px dashed var(--line); border-radius: var(--radius); padding: 1rem 1.25rem; margin-bottom: 1.5rem; color: var(--ink-soft); font-size: 0.875rem; }
.dbt-deferred-title { font-weight: 600; color: var(--ink-soft); margin: 0 0 0.25rem; }
.dbt-deferred pre { font-family: var(--font-mono); font-size: 0.8rem; background: var(--thread-tint); border-radius: var(--radius-sm); padding: 0.75rem; overflow-x: auto; margin: 0.5rem 0 0; color: var(--ink); }
.dbt-table { border-collapse: collapse; width: 100%; margin-top: 0.5rem; font-family: var(--font-ui); font-size: 0.8125rem; }
.dbt-table th, .dbt-table td { text-align: left; padding: 4px 8px; border-bottom: 1px solid var(--line); color: var(--ink); }
.dbt-table th { color: var(--ink-soft); font-weight: 600; }

.dbt-audio { background: var(--paper-raised); border-radius: var(--radius); padding: 1rem 1.25rem; margin-bottom: 1.5rem; border: 1px solid var(--line); }
.dbt-audio audio { width: 100%; }
.dbt-audio details { margin-top: 0.75rem; } .dbt-audio summary { cursor: pointer; font-size: 0.8125rem; color: var(--ink-mute); }
.dbt-audio-transcript { margin: 0.5rem 0 0; font-size: 0.9rem; color: var(--ink-soft); line-height: 1.6; }

.dbt-recording { background: var(--warm-tint); border: 1px solid var(--warm); border-radius: var(--radius); padding: 1.125rem 1.25rem; margin-bottom: 0.25rem; }
.dbt-recording-chip { display: inline-block; font-family: var(--font-ui); font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--paper); background: var(--warm); border-radius: var(--radius-sm); padding: 4px 10px; margin-bottom: 0.625rem; }
.dbt-recording-intro { font-size: 0.9375rem; line-height: 1.55; color: var(--ink-soft); margin: 0 0 0.875rem; }
.dbt-recording video { width: 100%; border-radius: var(--radius-sm); max-height: 340px; background: var(--ink); display: block; }

.dbt-voice { background: var(--paper-raised); border-radius: var(--radius); padding: 1.5rem; margin-bottom: 1.5rem; border: 1px solid var(--line); }
.dbt-voice-prompt { font-weight: 500; color: var(--ink); line-height: 1.55; margin: 0 0 1rem; font-size: 1rem; }
.dbt-voice-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 0.625rem 1rem; margin-bottom: 0.875rem; }
.dbt-voice-or { color: var(--ink-mute); font-size: 0.875rem; }
.dbt-voice-live { color: var(--warm); font-weight: 500; }
.dbt-voice-live::before { content: ""; display: inline-block; width: 0.75rem; height: 0.75rem; border-radius: 50%; background: var(--warm); margin-right: 0.5rem; vertical-align: middle; }
.dbt-btn.is-warm { background: var(--warm); color: var(--paper); }

/* A grid item's min-width is `auto`, so a <pre> or a 16:9 frame inside a knot
   widened the whole column to 5,561px (measured, TASK-L156). The source page
   never hit it because Next's own reset sets it; this template's does not. */
.lesson-layout { grid-template-columns: 32px minmax(0, 1fr); }
.lesson-layout > * { min-width: 0; max-width: 100%; }
.dbt-section, .lesson-section { min-width: 0; }
.dbt-deferred pre, .handover-pack-contents, .artifact-history-content { white-space: pre-wrap; word-break: break-word; }

/* On a phone the chip took most of the callout's width and the body wrapped in a
   sliver (seen at 390px, TASK-L156). The chip goes above the body there. */
@media (max-width: 520px) { .dbt-callout { flex-direction: column; gap: 0.5rem; } }

/* The stepper's chip is a built-in Button wearing the lifted .lesson-step-chip
   rules; the dot the source draws as a child span is a ::before here, because a
   Button control has no children. Position is order + .active — no numeral. */
.lesson-step-chip.dbt-chip::before { content: ""; flex: none; width: 8px; height: 8px; border-radius: 50%; border: var(--edge-thin); background: var(--paper); }
.lesson-step-chip.dbt-chip.active::before { background: var(--paper-raised); }

/* ── LIFTED from globals.css 3493–4305: capture, prep pack, artifact challenge, handover pack ── */
/* ── Capture section (LX14) — inline dossier-fact capture ───────────────────── */

.capture-box {
  background: var(--paper-raised);
  border: var(--edge);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lifted);
  padding: 1rem 1.125rem;
}

.capture-prompt {
  color: var(--thread-deep);
  font-size: 0.9rem;
  font-weight: 500;
  margin: 0 0 0.75rem;
}

.capture-row {
  display: flex;
  gap: 8px;
  align-items: flex-end;
}

.capture-input {
  flex: 1;
  min-width: 0;
  font-family: var(--font-prose);
  font-size: 1rem;
  line-height: 1.5;
  color: var(--ink);
  background: var(--paper);
  border: var(--edge-thin);
  border-radius: var(--radius-sm);
  padding: 10px 14px;
}

.capture-input:hover {
  background: var(--paper-raised);
}

.capture-input::placeholder {
  color: var(--ink-mute);
}

.capture-mic-btn {
  flex: none;
  border: var(--edge-thin);
  background: var(--paper-raised);
  cursor: pointer;
  border-radius: var(--radius-sm);
  padding: 10px 12px;
  font-size: 1rem;
  line-height: 1;
  color: var(--ink-soft);
  transition: color 0.12s, border-color 0.12s;
}

.capture-mic-btn:hover:not(:disabled) {
  color: var(--thread-deep);
  background: var(--thread-tint);
}

.capture-mic-btn.recording {
  color: var(--warm);
  border-color: var(--warm);
}

.capture-mic-btn:disabled {
  opacity: 0.45;
  cursor: default;
}

.capture-save-btn {
  flex: none;
  border: var(--edge);
  box-shadow: var(--shadow-lifted);
  cursor: pointer;
  font-family: var(--font-ui);
  font-weight: 600;
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink);
  background: var(--thread);
  border-radius: 999px;
  padding: 12px 16px;
  transition: background 0.18s ease;
}

.capture-save-btn:hover:not(:disabled) {
  filter: brightness(0.94);
}

.capture-save-btn:disabled {
  opacity: 0.45;
  cursor: default;
}

.capture-hint,
.capture-error,
.capture-mic-status {
  font-size: 0.8rem;
  color: var(--ink-soft);
  margin: 6px 0 0;
}

.capture-saved {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}

.capture-saved-value {
  color: var(--ink);
  font-size: 0.9375rem;
  line-height: 1.5;
  margin: 0;
}

.capture-saved-check {
  color: var(--go);
  font-size: 0.8rem;
  font-weight: 500;
  display: block;
  margin-top: 4px;
}

.capture-edit-btn {
  flex: none;
  border: none;
  background: none;
  cursor: pointer;
  font-family: var(--font-ui);
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--thread-deep);
  padding: 0;
}

.capture-edit-btn:hover {
  text-decoration: underline;
}

.capture-share-btn {
  display: inline-block;
  margin-top: 6px;
  border: none;
  background: none;
  cursor: pointer;
  font-family: var(--font-ui);
  font-size: 0.8rem;
  font-weight: 500;
  color: var(--ink-soft);
  padding: 0;
}

.capture-share-btn:hover {
  color: var(--thread-deep);
  text-decoration: underline;
}

.capture-shared-note {
  display: inline-block;
  margin-top: 6px;
  font-size: 0.8rem;
  font-weight: 500;
  color: var(--go);
  text-decoration: none;
}

.capture-shared-note:hover {
  text-decoration: underline;
}

.capture-share-compose {
  margin-top: 0.75rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--line);
}

.capture-share-textarea {
  width: 100%;
  font-family: var(--font-prose);
  font-size: 0.95rem;
  color: var(--ink);
  background: var(--paper-raised);
  border: var(--edge-thin);
  border-radius: var(--radius-sm);
  padding: 0.625rem 0.75rem;
  resize: vertical;
}

.capture-share-actions {
  display: flex;
  gap: 8px;
  margin-top: 0.5rem;
  align-items: center;
}

.capture-share-confirm-btn {
  flex: none;
  border: var(--edge);
  box-shadow: var(--shadow-lifted);
  cursor: pointer;
  font-family: var(--font-ui);
  font-weight: 600;
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink);
  background: var(--thread);
  border-radius: 999px;
  padding: 10px 14px;
  transition: background 0.18s ease;
}

.capture-share-confirm-btn:hover:not(:disabled) {
  filter: brightness(0.94);
}

.capture-share-confirm-btn:disabled {
  opacity: 0.45;
  cursor: default;
}

.capture-share-skip-btn {
  flex: none;
  border: none;
  background: none;
  cursor: pointer;
  font-family: var(--font-ui);
  font-size: 0.8125rem;
  color: var(--ink-soft);
  padding: 8px 4px;
}

.capture-share-skip-btn:hover:not(:disabled) {
  color: var(--ink);
}

.capture-share-skip-btn:disabled {
  opacity: 0.45;
  cursor: default;
}

/* ── Prep pack section (LX14) — end-of-lesson hand-off document ─────────────── */

.prep-pack {
  background: var(--paper-raised);
  border: var(--edge);
  box-shadow: var(--shadow-lifted);
  border-radius: var(--radius);
  padding: 1.5rem;
}

.prep-pack-title {
  font-family: var(--font-display);
  font-size: 1.15rem;
  color: var(--ink);
  margin: 0 0 0.75rem;
}

.prep-pack-assembling {
  font-size: 0.85rem;
  color: var(--ink-soft);
  font-style: italic;
  margin: 0 0 1rem;
}

.prep-pack-instructions {
  background: var(--thread-tint);
  border-radius: var(--radius-sm);
  padding: 0.875rem 1rem;
  margin: 0 0 1.125rem;
  font-size: 0.9rem;
  color: var(--ink);
  line-height: 1.55;
}

.prep-pack-fields {
  display: flex;
  flex-direction: column;
  gap: 0.875rem;
  margin: 0 0 1.25rem;
}

.prep-pack-field dt {
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--ink-soft);
  margin: 0 0 2px;
}

.prep-pack-field dd {
  font-size: 0.92rem;
  color: var(--ink);
  line-height: 1.5;
  margin: 0;
}

.prep-pack-field dd.prep-pack-missing {
  color: var(--ink-soft);
  font-style: italic;
}

.prep-pack-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.prep-pack-copied {
  font-size: 0.8rem;
  color: var(--go);
  font-weight: 500;
}

/* ── Artifact challenge (TASK-L52) — the produce beat ────────────────────────
   Built entirely from existing tokens; no new ones were needed. Status is
   carried by glyph + word as well as colour (see ArtifactChallenge.tsx's
   coaching invariants): `met` takes --go, `partly` takes --warm, and `not_yet`
   is deliberately NEUTRAL with a dashed edge — an absence, never a failure.
   Nothing here is red and nothing renders a count. */

.artifact-challenge {
  background: var(--paper-raised);
  border: var(--edge);
  box-shadow: var(--shadow-lifted);
  border-radius: var(--radius);
  padding: 1.5rem;
}

.artifact-challenge-title {
  font-family: var(--font-display);
  font-size: 1.15rem;
  color: var(--ink);
  margin: 0 0 0.75rem;
}

.artifact-challenge-brief {
  font-size: 0.95rem;
  color: var(--ink);
  line-height: 1.6;
  margin: 0 0 1rem;
}

.artifact-challenge-pack-note {
  font-size: 0.85rem;
  color: var(--ink-soft);
  font-style: italic;
  margin: 0 0 1rem;
}

.artifact-rubric {
  background: var(--thread-tint);
  border-radius: var(--radius-sm);
  padding: 0.875rem 1rem;
  margin: 0 0 1.25rem;
}

.artifact-rubric-label {
  font-family: var(--font-ui);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--ink-soft);
  margin: 0 0 0.5rem;
}

.artifact-rubric-list {
  margin: 0;
  padding-left: 1.1rem;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  font-size: 0.9rem;
  color: var(--ink);
  line-height: 1.5;
}

/* ── Composer ── */

.artifact-composer {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.artifact-composer-label {
  font-family: var(--font-ui);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--ink);
}

.artifact-textarea {
  width: 100%;
  box-sizing: border-box;
  font-family: var(--font-mono);
  font-size: 0.875rem;
  line-height: 1.55;
  color: var(--ink);
  background: var(--paper);
  border: var(--edge-thin);
  border-radius: var(--radius-sm);
  padding: 0.875rem 1rem;
  resize: vertical;
}

.artifact-textarea:focus {
  outline: 3px solid var(--ink);
  outline-offset: 1px;
}

.artifact-file-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}

.artifact-file-hint {
  font-size: var(--text-xs);
  color: var(--ink-soft);
}

.artifact-composer-actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-top: 0.25rem;
}

/* "Move on" is always available and never disabled. Quiet, but never hidden —
   it must not read as the lesser of two choices. */
.artifact-move-on {
  background: none;
  border: none;
  padding: 0;
  font-family: var(--font-ui);
  font-size: var(--text-sm);
  color: var(--ink-soft);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

.artifact-move-on:hover {
  color: var(--thread-deep);
}

/* ── Verdict ── */

.artifact-verdict {
  margin: 0 0 1.25rem;
}

.artifact-verdict-summary {
  font-family: var(--font-prose);
  font-size: var(--text-base);
  color: var(--ink);
  line-height: 1.6;
  margin: 0 0 1rem;
}

.artifact-criteria {
  list-style: none;
  margin: 0 0 1.125rem;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
}

.artifact-criterion {
  border-radius: var(--radius-sm);
  padding: 0.75rem 0.875rem;
  border: var(--edge-thin);
  background: var(--paper);
}

.artifact-criterion.is-met {
  background: var(--go-tint);
  }

.artifact-criterion.is-partly {
  background: var(--warm-tint);
  }

/* Neutral and dashed: nothing there YET. Not a failure, so not warm and
   emphatically not red. */
.artifact-criterion.is-not_yet {
  background: var(--paper);
  border-style: dashed;
  }

.artifact-criterion-head {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin: 0 0 0.35rem;
}

.artifact-criterion-glyph {
  font-size: 0.8rem;
  line-height: 1;
  color: var(--ink-soft);
}

.artifact-criterion.is-met .artifact-criterion-glyph {
  color: var(--go);
}

.artifact-criterion.is-partly .artifact-criterion-glyph {
  color: var(--warm);
}

.artifact-criterion-name {
  font-family: var(--font-ui);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--ink);
  /* TASK-L182, found by rendering: with no basis, a criterion whose name is a
     full sentence wrapped WHOLE under its glyph, leaving the glyph alone on a
     line. A basis lets the name take the row and wrap inside itself, so the
     glyph stays beside the words it marks. The product's rule has no basis
     either; its rubrics are shorter than a hand-authored lesson's. */
  flex: 1 1 12rem;
  min-width: 0;
}

.artifact-criterion-status {
  font-family: var(--font-ui);
  font-size: var(--text-xs);
  color: var(--ink-soft);
}

.artifact-criterion.is-met .artifact-criterion-status {
  color: var(--go);
}

.artifact-criterion.is-partly .artifact-criterion-status {
  color: var(--warm);
}

.artifact-criterion-note {
  font-size: 0.9rem;
  color: var(--ink);
  line-height: 1.55;
  margin: 0;
}

.artifact-next-edit {
  background: var(--thread-tint);
  border-radius: var(--radius-sm);
  padding: 0.875rem 1rem;
  margin: 0 0 1rem;
}

.artifact-next-edit-label {
  font-family: var(--font-ui);
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--ink-soft);
  margin: 0 0 0.35rem;
}

.artifact-next-edit-text {
  font-size: 0.95rem;
  color: var(--ink);
  line-height: 1.55;
  margin: 0;
}

/* ── Share — unemphasised on purpose, and off by default ── */

/* ── WHAT THEY SENT, AND WHAT CAME BACK (TASK-L182) ─────────────────────────
   LIFTED from globals.css 4117–4166 (.artifact-history*) and 8150–8190
   (.assignment-answer*) by line range, like the rest of this file. Two rules
   are new: `.artifact-latest`, the latest attempt shown open above its verdict
   (the product shows the verdict and hides the text; here the text is the point
   a visitor came to read), and `.artifact-not-read`, the quiet line an attempt
   with no verdict yet carries. No `--warm` added here: the verdict's own rules
   above keep the product's single use of it, on "Partly there". */
.artifact-latest {
  margin-top: 1.25rem;
  border-top: 1px solid var(--line);
  padding-top: 0.875rem;
}

.artifact-not-read {
  font-family: var(--font-ui);
  font-size: var(--text-sm);
  color: var(--ink-soft);
  margin: 0.5rem 0 0;
}

.artifact-history {
  margin-top: 1.25rem;
  border-top: 1px solid var(--line);
  padding-top: 0.875rem;
}

.artifact-history-toggle {
  background: none;
  border: none;
  padding: 0;
  font-family: var(--font-ui);
  font-size: var(--text-sm);
  color: var(--ink-soft);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

.artifact-history-list {
  list-style: none;
  margin: 0.875rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}

.artifact-history-label {
  font-family: var(--font-ui);
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--ink-soft);
  margin: 0 0 0.35rem;
}

.artifact-history-content {
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 0.75rem 0.875rem;
  margin: 0 0 0.625rem;
  max-height: 14rem;
  overflow: auto;
  white-space: pre-wrap;
  word-break: break-word;
}

.assignment-answer-history {
  margin: 14px 0 0;
}

.assignment-answer-list {
  list-style: none;
  padding: 0;
  margin: 6px 0 0;
}

.assignment-answer-attempt {
  border-left: var(--edge-thin);
  padding: 2px 0 2px 12px;
  margin: 0 0 10px;
}

.assignment-answer-meta {
  font-family: var(--font-ui);
  font-size: var(--text-xs);
  color: var(--ink-mute);
  margin: 0 0 4px;
}

.assignment-answer-link a {
  font-family: var(--font-ui);
  font-size: var(--text-sm);
  color: var(--thread-deep);
  word-break: break-all;
}

.assignment-answer-note {
  font-family: var(--font-prose);
  font-size: var(--text-sm);
  line-height: 1.6;
  color: var(--ink-soft);
  max-width: 62ch;
  margin: 4px 0 10px;
}

.artifact-share {
  margin: 0 0 0.25rem;
}

.artifact-share-btn,
.artifact-shared-note {
  background: none;
  border: none;
  padding: 0;
  font-family: var(--font-ui);
  font-size: var(--text-sm);
  color: var(--thread-deep);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

.artifact-shared-note {
  color: var(--go);
  cursor: default;
}

.artifact-share-trim-note {
  font-size: var(--text-xs);
  color: var(--ink-soft);
  margin: 0.35rem 0 0;
}

/* ── Errors — plain, never alarming, and the work is never lost ── */

.artifact-error {
  background: var(--warm-tint);
  border: var(--edge-thin);
  border-radius: var(--radius-sm);
  padding: 0.75rem 0.875rem;
  margin-top: 0.875rem;
  font-size: 0.9rem;
  color: var(--ink);
  line-height: 1.55;
}

.artifact-error p {
  margin: 0 0 0.5rem;
}

.artifact-error p:last-child {
  margin-bottom: 0;
}

/* ── Attempt history — the learner watching themselves improve ── */

.artifact-history {
  margin-top: 1.25rem;
  border-top: 1px solid var(--line);
  padding-top: 0.875rem;
}

.artifact-history-toggle {
  background: none;
  border: none;
  padding: 0;
  font-family: var(--font-ui);
  font-size: var(--text-sm);
  color: var(--ink-soft);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

.artifact-history-list {
  list-style: none;
  margin: 0.875rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}

.artifact-history-label {
  font-family: var(--font-ui);
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--ink-soft);
  margin: 0 0 0.35rem;
}

.artifact-history-content {
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 0.75rem 0.875rem;
  margin: 0 0 0.625rem;
  max-height: 14rem;
  overflow: auto;
  white-space: pre-wrap;
  word-break: break-word;
}

/* ── Handover pack (TASK-L75) — the documents they carry across ──────────────
   Built entirely from existing tokens; no new ones were needed. Paths use
   --font-mono because they ARE file paths (the code-only rule). Nothing here
   is red, nothing counts, and --warm is deliberately not borrowed: this is a
   hand-off, not encouragement. */

.handover-pack {
  background: var(--paper-raised);
  border: var(--edge);
  box-shadow: var(--shadow-lifted);
  border-radius: var(--radius);
  padding: 1.5rem;
}

.handover-pack-title {
  font-family: var(--font-display);
  font-size: 1.15rem;
  color: var(--ink);
  margin: 0 0 0.75rem;
}

.handover-pack-intro {
  font-size: 0.95rem;
  color: var(--ink);
  line-height: 1.55;
  margin: 0 0 1.25rem;
}

.handover-pack-files-label {
  font-family: var(--font-ui);
  font-size: 0.6875rem;
  font-weight: 600;
  color: var(--ink-soft);
  text-transform: uppercase;
  letter-spacing: 0.18em;
  margin: 0 0 0.625rem;
}

.handover-pack-files {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.handover-pack-file {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  background: var(--thread-tint);
  border-radius: var(--radius-sm);
  padding: 0.75rem 0.875rem;
}

.handover-pack-path {
  font-family: var(--font-mono);
  font-size: 0.85rem;
  color: var(--thread-deep);
  word-break: break-all;
}

.handover-pack-purpose {
  font-size: 0.9rem;
  color: var(--ink);
  line-height: 1.5;
}

/* The written pack (TASK-L76). A file card becomes a disclosure: the head is a
   button, the contents scroll INSIDE the card. The LX27 finding — a long
   document expanded inline makes the column unusably tall — applies here at
   three times the length. */

.handover-pack-file-head {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.2rem;
  width: 100%;
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  text-align: left;
  cursor: pointer;
  font: inherit;
  color: inherit;
}

.handover-pack-file-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 0.625rem;
}

.handover-pack-copied {
  font-size: 0.8rem;
  color: var(--go);
  font-weight: 500;
}

.handover-pack-contents {
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 0.75rem 0.875rem;
  margin: 0.75rem 0 0;
  max-height: 22rem;
  overflow: auto;
  white-space: pre-wrap;
  word-break: break-word;
}

.handover-pack-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 1.25rem;
}

/* Direction text, never red — the coaching invariant applies to errors too. */
.handover-pack-waiting,
.handover-pack-where,
.handover-pack-error p {
  font-size: 0.85rem;
  color: var(--ink-soft);
  line-height: 1.5;
  margin: 0.75rem 0 0;
}


/* ═══════════════════════════════════════════════════════════════════════════
   THE PROGRAMME'S ROWS (TASK-L158, sprint 45)

   LIFTED from globals.css, by line range, in the blocks below. The class names
   are the contract `dbt-lesson.TimelineRow` emits and a paraphrase would be a
   second design (TASK-L154 §4's rule, one surface over).

   TWO BLOCKS INSIDE THOSE RANGES ARE DELIBERATELY LEFT BEHIND, because this kit
   cannot emit their classes and orphaned CSS rots invisibly where an orphaned
   module does not (TASK-L152): `.path-show-in-log` (staff-only — it switches to
   a surface a learner does not have, and this template has no staff surface),
   and `.path-card-block .coach-corner-*` (the coach's own card components, not
   ported here). Everything skipped is named, never quietly dropped.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── LIFTED from globals.css 1459–1563: the path, the knots, the card, the two-voice wash ── */
/* ── Course path (thread + knots on dashboard) ──────────────────────────────── */

.path {
  position: relative;
  padding-left: 8px;
}

.path::before {
  content: "";
  position: absolute;
  left: 15px;
  top: 14px;
  bottom: 24px;
  width: 2px;
  background: var(--line);
}

.path-step {
  position: relative;
  padding-left: 44px;
  margin-bottom: 18px;
}

.path-knot {
  position: absolute;
  left: 8px;
  top: 18px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--paper);
  border: var(--edge-thin);
  z-index: 1;
}

.path-step.complete .path-knot {
  background: var(--go);
  border-color: var(--ink);
}

.path-step.active .path-knot {
  border-color: var(--ink);
  background: var(--thread);
  box-shadow: 0 0 0 4px var(--thread-tint);
}

.path-step.available .path-knot {
  border-color: var(--ink);
  background: var(--go-tint);
}

/* locked: default open --line knot */

.path-card {
  background: var(--paper-raised);
  border: var(--edge);
  box-shadow: var(--shadow-lifted);
  border-radius: var(--radius);
  padding: 16px 20px;
  display: flex;
  align-items: center;
  gap: 18px;
}

.path-step.locked .path-card {
  background: transparent;
  border: var(--edge-thin);
  border-style: dashed;
  box-shadow: none;
}

/* ── The timeline has a shape (TASK-L130) ───────────────────────────────────
   TWO VOICES, NOT SEVEN COLOURS. Three kinds are the coach speaking, three are
   the learner's own work; that is a difference a reader can USE and it survives
   a kind being added. Seven categorical hues on a page of prose would fight
   Sprint 28's one-accent language, and `--series-*` are for marks in a plot,
   where a legend and a shape carry identity alongside the colour.

   `timeline-${kind}` has been on every row since L117 and NOTHING used it. This
   is the first rule that does. No new hue, no `--warm`, and `--go` still only
   ever marks a completed step. */

.timeline-voice-coach > .path-card {
  background: var(--thread-tint);
}

/* THE QUIET CARD (sprint 43 §2.4, TASK-L151). A coach's review of a person is
   not a sticker: "where we're starting from" inside a 4px pop reads as a joke
   about the review. Thin edge, NO pop. The coach-voice wash is kept — the voice
   is what tells a reader who is speaking, and quiet is about weight, not voice.
   Applied by SELECTOR rather than as a `.card-quiet` class, because the class
   would be a markup change and this sprint changes none. */
.timeline-evaluation > .path-card {
  border: var(--edge-thin);
  box-shadow: none;
}

/* The knot carries the kind's glyph in the ink of the row it belongs to, so a
   completed step's filled knot still reads against `--paper` and an open one
   against the page. */
.timeline-voice-coach > .path-knot { color: var(--thread-deep); }
.timeline-voice-learner > .path-knot { color: var(--ink-mute); }
.path-step.complete > .path-knot { color: var(--paper); }
.path-step.active > .path-knot { color: var(--thread-deep); }


/* ── LIFTED from globals.css 1752–1851: a folded row is one line the reader can open ── */
/* ── A folded row is ONE LINE THE READER CAN OPEN ───────────────────────────
   Not hidden, not filtered, nothing disappears. It is a button because it
   changes what is on screen rather than navigating, and it is quiet enough that
   eighteen of them read as a history rather than as eighteen demands. */

.path-folded {
  display: flex;
  align-items: baseline;
  gap: 12px;
  width: 100%;
  text-align: left;
  background: transparent;
  border: var(--edge-thin);
  border-style: dashed;
  border-radius: var(--radius-sm);
  padding: 7px 12px;
  cursor: pointer;
  font-family: var(--font-ui);
  font-size: var(--text-sm);
  color: var(--ink-mute);
  line-height: 1.4;
}

.path-folded:hover,
.path-folded:focus-visible {
  color: var(--ink);
  border-style: solid;
  background: var(--paper-raised);
}

.path-folded-kind {
  flex: none;
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-mute);
}

/* The thing that identifies THIS row, in the reading face and in full ink: a
   preview that is the same for every card of a kind has not solved anything. */
.path-folded-title {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: var(--font-prose);
  font-size: var(--text-base);
  color: var(--ink);
}

/* WHERE A SIGNAL HAPPENED, inside the folded title (TASK-L140 §1).
   Recessive, so the row still reads as its own sentence first and the location
   after it — the same weight the activity log gives its own "· about". No
   colour of its own: L130 §2 is two voices, not seven hues, and a signal must
   never be the brightest thing on a page about how somebody is getting on. */
.path-folded-about {
  color: var(--ink-mute);
}

.path-folded-when {
  flex: none;
  font-variant-numeric: tabular-nums;
  color: var(--ink-mute);
}

/* WHAT THE CARD CARRIES, ON THE ONE LINE (TASK-L138 §1).
   Words and a number — `1 note · 2 comments` — in the same recessive ink as the
   date beside it. NOT a badge, NOT a dot, NO --warm and no red: sprint 39 §0.2
   reversed L118 §4's rendering ban narrowly, and a coloured pill is the thing it
   did not reverse. It reads as a fact about the card, not as a demand. */
.path-folded-carries {
  flex: none;
  font-family: var(--font-ui);
  font-size: 0.6875rem;
  letter-spacing: 0.04em;
  color: var(--ink-mute);
}

/* A folded row has no card, so the 18px gap the card needed is wrong for it. */
.path-step.is-folded { margin-bottom: 0; }
.path-step.is-folded > .path-knot { top: 11px; }

/* Re-folding is reversible, and only offered on rows that started folded. */
.path-fold-again {
  display: block;
  margin: 6px 0 0;
  background: transparent;
  border: 0;
  padding: 2px 0;
  cursor: pointer;
  font-family: var(--font-ui);
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-mute);
}

.path-fold-again:hover,
.path-fold-again:focus-visible { color: var(--thread-deep); }

/* ── LIFTED from globals.css 1873–1911: the folded row at phone width, and the card's label, title and why ── */
@media (max-width: 620px) {
  /* The date drops below the title rather than squeezing it to nothing. */
  .path-folded { flex-wrap: wrap; gap: 4px 10px; }
  .path-folded-title { flex-basis: 100%; white-space: normal; }
}

.path-card-meta {
  flex: 1;
  min-width: 0;
}

.path-step-label {
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  color: var(--ink-soft);
  font-weight: 600;
}

.path-step-title {
  font-family: var(--font-display);
  font-size: 1.18rem;
  font-weight: 800;
  letter-spacing: 0;
  margin: 2px 0 4px;
  color: var(--ink);
}

.path-step.locked .path-step-title {
  color: var(--ink-soft);
}

.path-step-why {
  font-size: 0.9rem;
  color: var(--ink-soft);
  line-height: 1.45;
  margin: 0;
}


/* ── LIFTED from globals.css 1961–1964: the card at phone width. Its third line,
   `.dashboard-wrap`, is the PAGE's class rather than the kit's and is left behind. ── */
@media (max-width: 600px) {
  .path-card { flex-direction: column; align-items: flex-start; gap: 12px; }
}

/* ── LIFTED from globals.css 1966–1986: the timeline rows' block layout ── */
/* ── Timeline rows (TASK-L117 §2) ───────────────────────────────────────────
   The path is the whole programme now: a coaching session, a brief, work sent
   in and a note from the coach sit BETWEEN the lessons at their real position
   in time. The knot and the card are Sprint 8's, unchanged; what these classes
   add is the block layout a card carries when it is not a lesson (a lesson's
   card is a row with a call to action on the right; everything else is prose
   that runs the full width).

   THERE IS NO COLOUR BY KIND. State — done / in play / ahead — is what the
   knot says, exactly as it always has; the kind is said in words on the row.
   Colouring by kind would put a palette on somebody's programme and make a
   coaching session look like a category rather than a meeting. */

.path-card-block {
  display: block;
}

.path-card-block .path-step-title {
  margin-top: 2px;
}


/* ── KIT-OWNED RULES for the timeline row (TASK-L158) ────────────────────────
   The classes above are the product's, lifted. These eight are this kit's own,
   for the three places the product does it some other way:

   - `.path-knot-glyph` — the product positions the glyph with an inline `style`
     on the <svg> (`TimelineKind.tsx`). A class instead, because this kit's rule
     is that the stylesheet owns every appearance and there is no colour in
     `kit.js`. The geometry is the same: centred in the 16px knot.
   - the card's inner text — `renderEntry` hands each kind to its own component
     (`LessonCard`, `SessionCard`, `NoteCard`, …), and those are not ported here
     (L158's status records what the card does and does not draw). These four
     rules give the ported card the same register those components read in.
   - `.path-ask` — the product's control opens a conversation panel, which is
     out of scope this sprint (it would be a control that cannot be answered).
     It EMITS, so it wears the quiet weight `.path-fold-again` wears rather than
     borrowing a panel's styling for a thing that is not a panel.

   No colour outside the token set, no `--warm` except on a carried note — which
   is the product's own use of it (`.ask-about-note`), and the use this file's
   header reserves it for: encouragement and a human's recording. ── */

.dbt-timeline-row {
  display: block;
  width: 100%;
}

.path-knot-glyph {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

/* A quiet fact about the card — how long a meeting was, that a brief has been
   answered, that a review is not written up yet. Never a verdict, never a
   count, and never in an alarming ink. */
.path-card-note {
  font-family: var(--font-ui);
  font-size: var(--text-xs);
  color: var(--ink-mute);
  margin: 0 0 6px;
}

/* Names a part of the card — "What it asks for", "Before this session". The
   micro-label register the rest of the product uses for naming a section. */
.path-card-label {
  font-family: var(--font-ui);
  font-size: var(--text-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-mute);
  margin: 10px 0 4px;
}

.path-card-body {
  font-family: var(--font-prose);
  font-size: var(--text-base);
  line-height: 1.55;
  color: var(--ink);
}

.path-card-body p {
  margin: 0 0 0.6em;
}

.path-card-body p:last-child {
  margin-bottom: 0;
}

/* The criteria a brief asks for, or what a session asked them to look at first.
   A list of things, never a checklist: nothing here has a state, a tick or a
   count, because none of them is something this surface can know the answer to. */
.path-card-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-family: var(--font-prose);
  font-size: 0.95rem;
  color: var(--ink);
}

.path-card-list li::before {
  content: "·";
  color: var(--ink-mute);
  margin-right: 8px;
}

/* A coach's note, under the card it was written about. `--warm-tint` is the
   product's own treatment for one (`.ask-about-note`) and is the reserved use:
   a human's words about a person. */
.path-carried-note {
  border: var(--edge-thin);
  background: var(--warm-tint);
  border-radius: var(--radius-sm);
  padding: 8px 12px;
  margin-top: 8px;
}

.path-carried-note .path-card-label {
  margin-top: 0;
}

/* THEIR OWN WORDS ON A SIGNAL (TASK-L163), lifted from the product's
   `.people-signal-utterance`. The thread tint and NOT `--warm-tint`: warm is
   reserved for a human's words about a person, and a signal is the platform
   noticing. No red, no ✗, no count. */
.path-signal-utterance {
  font-family: var(--font-prose);
  font-size: var(--text-base);
  line-height: 1.55;
  color: var(--ink-soft);
  background: var(--thread-tint);
  border-radius: var(--radius-sm);
  padding: 10px 14px;
  max-width: 60ch;
  margin: 8px 0 0;
}

/* LIFTED from globals.css 1856–1871 (TASK-L165): the coach's way from a
   programme card to the same entry in the activity log. The ask control's
   quiet weight, because it is the same kind of thing: a word that points. */
.path-show-in-log {
  display: block;
  margin: 6px 0 0;
  background: transparent;
  border: 0;
  padding: 2px 0;
  cursor: pointer;
  font-family: var(--font-ui);
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-mute);
}
.path-show-in-log:hover,
.path-show-in-log:focus-visible { color: var(--thread-deep); }

.path-ask {
  display: block;
  margin: 8px 0 0;
  background: transparent;
  border: 0;
  padding: 2px 0;
  cursor: pointer;
  font-family: var(--font-ui);
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-mute);
}

.path-ask:hover,
.path-ask:focus-visible { color: var(--thread-deep); }

/* ── A CARD OPENS WHAT IT IS ABOUT (TASK-L181 §4) ──────────────────────────
   LIFTED from globals.css 1946–1959 (`.path-cta`, `.path-check`), plus the two
   controls the product draws as links and this kit draws as buttons, because
   they EMIT and the graph navigates. `--go` on the tick only: the product's own
   use of it, the learner's finished work — never on a gap. */
.path-cta {
  flex: none;
  margin-top: 12px;
}

.path-check {
  color: var(--go);
  font-weight: 700;
}

.path-review,
.path-open-lesson {
  background: transparent;
  border: 0;
  padding: 2px 0;
  cursor: pointer;
  font-family: var(--font-ui);
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--thread-deep);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.path-review:hover,
.path-review:focus-visible,
.path-open-lesson:hover,
.path-open-lesson:focus-visible { color: var(--ink); }

/* ── WHERE YOU'RE AT (TASK-L159, sprint 45) ─────────────────────────────────
   Three panels above the programme: two counts in a sentence, the pace line,
   and one gauge per agreed objective. LIFTED from globals.css by line range,
   like everything above — the class names are the contract the kit's nodes
   emit, and the maths behind them lives in Logic/Standing.

   FOUR BLOCKS INSIDE THESE RANGES ARE DELIBERATELY LEFT BEHIND, and named here
   because orphaned CSS rots invisibly where an orphaned module does not (L152):

     .up-next (2036-2038)               a margin on a wrapper this template does
                                        not have: the block is one `.standing`
                                        card, which the product's own markup
                                        also is.
     .up-next-lesson* / .up-next-cta    the next-lesson card and its button.
       (2065-2094)                      Pages/Course already carries the one
                                        fixture lesson as a card of its own, and
                                        a second would promise a lesson list this
                                        template does not have.
     .trajectory-plot / -svg / -line    already retired in the source by L133.
       (2283-2293)
     .trajectory-readout* (2527-2558)   DEAD CSS in the source too: L133 deleted
                                        the readout list and left its rules
                                        behind. Copying it forward would put a
                                        retired surface in a template as though
                                        it were current (TASK-L159 §5).        */

/* ── LIFTED from globals.css 1997–2034: where you're at ── */
/* ── Where you're at (TASK-L129 §3) + Up next (TASK-L117 §1) ────────────────
   Two counts, the agreed end if there is one, one sentence naming the order,
   then what the dated thing asks for, then the next lesson. With no coach this
   is the counts and the lesson alone — there is no empty coaching state and no
   placeholder anywhere in it.

   NO BAR, NO RING, NO PERCENTAGE AND NO NUMERALS SET LARGE. The counts are a
   sentence in the reading face, with the two numbers bolded exactly as
   `.dashboard-counter` has bolded its own since LX28. A stat tile here would be
   a scoreboard, which is §4's ban wearing a layout instead of a word. */

.standing {
  margin: 0 0 30px;
  background: var(--warm-tint);
  border: var(--edge);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lifted);
  padding: 22px 24px;
}

.standing-counts {
  font-family: var(--font-prose);
  font-size: var(--text-lg);
  color: var(--ink);
  line-height: 1.5;
  margin: 0 0 6px;
  max-width: 62ch;
  text-wrap: pretty;
}

/* The agreed end. Ordinary ink and a smaller size: it is context for the
   sentence above, never a deadline being announced. No countdown (§4). */
.standing-when {
  font-family: var(--font-ui);
  font-size: var(--text-sm);
  color: var(--ink-mute);
  margin: 0 0 18px;
}

/* ── LIFTED from globals.css 2040–2063: what the next dated thing asks for ── */
.up-next-order {
  font-family: var(--font-prose);
  font-size: var(--text-base);
  color: var(--ink);
  line-height: 1.6;
  margin: 0 0 16px;
  max-width: 62ch;
  text-wrap: pretty;
}

.up-next-requires {
  margin: 0 0 18px;
}

.up-next-requires-list {
  list-style: none;
  padding: 0;
  margin: 4px 0 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: var(--text-sm);
  color: var(--ink-soft);
}

/* ── LIFTED from globals.css 2096–2190: the pace tracker ── */
/* ── Pace tracker (TASK-L117 §3) ────────────────────────────────────────────
   Two lines and a horizon marker. THE GEOMETRY CARRIES THE GAP AND COLOUR ONLY
   EVER CELEBRATES (§3d): `--go` when the learner is ahead of par, and the
   ordinary `--thread` accent otherwise. There is no red here and there is no
   `--warm` — that token is encouragement and human recording, and being short
   of pace is neither; borrowing it would make the gap read as an encouragement
   sticker, which is what Sprint 28's reservation exists to stop.

   The par line never changes colour with how somebody is doing. */

.pace {
  margin: 0 0 32px;
}

.pace-headline {
  font-family: var(--font-prose);
  font-size: var(--text-base);
  color: var(--ink);
  line-height: 1.55;
  margin: 0 0 12px;
  max-width: 56ch;
}

.pace-chart {
  border: var(--edge);
  box-shadow: var(--shadow-lifted);
  border-radius: var(--radius);
  background: var(--paper-raised);
  padding: 10px 12px;
}

/* preserveAspectRatio="none" stretches the box to the column; the strokes are
   held at their real width by vector-effect on each mark. */
.pace-svg {
  display: block;
  width: 100%;
  height: 120px;
}

.pace-par {
  stroke: var(--ink-mute);
  stroke-width: 1.5;
  stroke-dasharray: 4 4;
}

.pace-actual {
  stroke: var(--thread);
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
}

.pace-actual.is-ahead {
  stroke: var(--go);
}

.pace-horizon {
  stroke: var(--line);
  stroke-width: 1;
}

.pace-legend {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 18px;
  padding: 0;
  margin: 10px 0 0;
  font-family: var(--font-ui);
  font-size: var(--text-xs);
  /* Text wears TEXT tokens, never the series colour (the dataviz rule). The
     coloured marker beside it carries the identity. */
  color: var(--ink-soft);
}

.pace-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 7px;
}

.pace-swatch {
  width: 18px;
  height: 0;
  flex: none;
  border-top-width: 2px;
  border-top-style: solid;
}

.pace-swatch.is-actual { border-top-color: var(--thread); }
.pace-swatch.is-actual.is-ahead { border-top-color: var(--go); }
.pace-swatch.is-par {
  border-top-color: var(--ink-mute);
  border-top-style: dashed;
}

/* ── LIFTED from globals.css 2192–2281: the trajectory panel ── */
/* ── Trajectory panel (TASK-L127 §3) ────────────────────────────────────────
   THE SECOND PANEL, ON THE SAME TIME AXIS AS THE PACE TRACKER ABOVE. Activity
   is an unbounded count of things done; a rating is bounded 1–10, so the two
   never share a y-axis (§1 — the classic dataviz lie). They share the x-axis
   instead, and the horizon marker is redrawn at the same fraction in every row
   so that is visible rather than merely true. The plots are the same width and
   carry the same 12px inset as `.pace-chart`, which is what keeps them aligned.

   SMALL MULTIPLES: one narrow row per agreed dimension, each with its own
   target hairline and its NAME BESIDE IT IN INK. Identity is never carried by
   colour, which is what lets four dimensions be told apart honestly — the
   dataviz skill's own "a single series needs no legend, the title names it".

   COLOUR ONLY EVER CELEBRATES (§4): `--go` when the latest judgement is at or
   above the agreed target, the ordinary series ink otherwise. NO red, NO
   `--warm` — that token is encouragement and human recording, and a low rating
   is neither. No ✗, no alarm, and the region below a target is not shaded.

   THE TWO HUMAN SERIES WERE VALIDATED, NOT EYEBALLED (criterion 8; L88's
   finding that every hand-built palette in this repo failed that check). The
   coach's rating is `--series-1`, the learner's own confidence `--series-5`,
   run through the dataviz skill's `validate_palette.js` on BOTH surfaces:

     --series-1 + --series-5 on navy #0E2139  → ALL PASS
       adjacent CVD ΔE 15.9 (protan) / 30.5 (tritan), normal-vision 26.5,
       both ≥ 3:1 against the surface.
     --series-1 + --series-5 on mist #F1F5F9  → ALL PASS
       adjacent CVD ΔE 13.4 (protan) / 30.0 (tritan), normal-vision 27.0,
       both ≥ 3:1. No WARN on either surface, so L88's relief caveat — which
       applies to slots 3 and 4 on mist — does not arise.

   THREE OTHER CANDIDATES WERE MEASURED AND LOST, WHICH IS WHY THE CHOICE IS
   SLOT 5 RATHER THAN THE NEXT ONE ALONG:

     `--series-2` was the first choice and passed the validator outright
       (ΔE 26.8 / 25.4). **`pnpm drive` then reported `reds` on the rendered
       page and the tool was right, not blind** (L99's rule inverted): #D95926
       sits at hue 17°, inside the audit's red band, so the learner's own
       confidence line was being drawn in an alarm colour on a chart about how
       they are doing. Attributing the hits and keeping it would have been
       softening the check.
     `--series-7` (violet) FAILED hard on navy — adjacent CVD ΔE 1.9 (protan)
       and a normal-vision ΔE of 9.8, under the hard floor of 15. It is the
       obvious pick by eye and it is the one L88's finding is about.
     `--series-4` (amber) passes but WARNs under 3:1 on mist, and sits beside
       `--warm`'s own hue — a data colour that reads as the encouragement token
       is the confusion Sprint 28's reservation exists to stop.
     `--series-3`/`--series-6` (greens) pass but separate at only ΔE 4.0–5.5
       under tritanopia, and are the `--go` family, which here MEANS "at the
       agreed target".

   `--go` is a STATUS colour used as a state overlay on one series, never a
   ninth categorical hue — the validator's own scope note. Against the
   confidence series it separates at ΔE 13.9 (deutan, navy) and 8.6 (protan,
   mist), both above the ≥ 8 target, and it clears 3:1 on both surfaces. Its
   categorical band/chroma FAILs are out of that check's stated scope for a
   status colour and are not the check this mark owes. */

.trajectory {
  margin: 0 0 32px;
}

.trajectory-rows {
  border: var(--edge);
  box-shadow: var(--shadow-lifted);
  border-radius: var(--radius);
  background: var(--paper-raised);
  padding: 10px 12px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.trajectory-row-label {
  font-family: var(--font-ui);
  font-size: var(--text-xs);
  /* Text wears TEXT tokens, never the series colour (the dataviz rule). */
  color: var(--ink-soft);
  margin: 0 0 4px;
  line-height: 1.3;
}

/* An archived dimension is off the readout and still on the chart (§3b): the
   ratings were really given, and dropping them would be the chart omitting
   points without saying so. Quieter, never struck through. */
.trajectory-row.is-archived .trajectory-row-label {
  color: var(--ink-mute);
}

/* ── THE SPARKLINE'S RULES ARE RETIRED (TASK-L133 §3) ───────────────────────

/* ── LIFTED from globals.css 2294–2526: the rating gauge ── */
   A track that is the whole 1-10 scale, a fill whose LENGTH is the message, the
   score as a mark, and the agreed target as a LINE with the word "goal" beside
   it. Four marks, four different kinds of thing.

   COLOUR ONLY EVER CELEBRATES, AND THAT IS A DEPARTURE FROM WHAT WAS ASKED
   (sprint 39 SS0.1). Richard asked for "yellow to green". --warm IS the
   amber/saffron token and it is reserved for encouragement and human recording,
   so a literal ramp would borrow a coaching colour to dramatise a gap and make
   "you are a long way from your goal" the brightest thing on the page - which is
   what L127 SS4 exists to stop. The ramp therefore runs --series-1 -> --go and
   only ever improves; the distance is carried by the gap between the mark and
   the flag. The mix is inline in RatingGauge.tsx because its ratio is per-row
   data, not a token; both ends are tokens that already passed the validator.
   A literal --warm -> --go ramp is one color-mix away if that is overruled. */

.rating-gauge {
  /* The gauge is a MARK, not a container: no border, no background, nothing that
     makes a row of them read as a table of scores. */
  padding: 2px 0 0;
}

.rating-gauge-track {
  position: relative;
  /* 14px with an ink edge (TASK-L150 §3). The edge is what makes the ruler
     visible now — 13:1 against any ground — and the composited fill below is
     KEPT, so L141's no-collision guarantee holds twice rather than once. */
  height: 14px;
  border: var(--edge-thin);
  border-radius: 999px;
  /* The unfilled remainder of the scale. Recessive - it is the ruler, not a
     shortfall, and shading it would be the "below target" region L127 refused.

     ── TRANSLUCENT, AND THAT IS A GUARANTEE RATHER THAN A BETTER GUESS (L141) ─
     Richard, sprint 41 item 6: "the 'review' blocks still don't have clear lines
     from 0 to 10, just the line up to the dot of where you are, and then a blank
     until the goal mark." Measured: this painted --thread-tint, and L130's
     two-voice wash paints `.timeline-voice-coach > .path-card` with the IDENTICAL
     token. An evaluation is a coach-voice kind, so on a review card the ruler and
     the card resolved to one value - `color(srgb 0.103451 0.182039 0.266902)` on
     both, to the last decimal, contrast 1:1, in both themes. The fill was the only
     mark on screen, which is exactly what he described.

     The gauge (L133) and the wash (L130) shipped in the same sprint and nothing
     had rendered one INSIDE the other until now.

     A FLAT COLOUR IS THE WRONG SHAPE OF FIX. --line is what anyone reaches for
     first and it computes to 1.03 against the same wash - the same bug in
     different clothes - because every surface token here is a navy-family value a
     few points from its neighbours. A COMPOSITED colour is defined relative to
     whatever is painted underneath, so it cannot collide with any ground, present
     or future. Measured at 25%: 1.36 / 1.36 / 1.34 dark and 1.31 / 1.33 / 1.26
     light across --paper, --paper-raised and the coach wash - uniform to within
     0.1, and better than the 1.17 that already reads correctly on the trajectory
     panel.

     It is FURNITURE, not text: the bar is a ruler, so the requirement is a small
     reliable delta from its ground, not a 3:1 ratio. The fix is at the gauge and
     NOT at the wash - re-tinting L130's voice to accommodate a descendant would
     trade a visible gauge for an invisible voice. */
  background: color-mix(in srgb, var(--ink-mute) 14%, transparent);
  /* Room for the score mark and the goal word, which sit outside the bar. */
  margin: 16px 0 18px;
}

.rating-gauge-fill {
  position: absolute;
  inset: 0 auto 0 0;
  border-radius: 999px;
  /* Fallback for the low end; RatingGauge.tsx overrides with the ramped mix. */
  background: var(--series-1);
}

.rating-gauge.is-at-target .rating-gauge-fill { background: var(--go); }
.rating-gauge.is-confidence .rating-gauge-fill { background: var(--series-5); }

/* WHERE THEY HAVE BEEN. Smaller and quieter than the live mark, so which
   number is current is never in question, and on the same track so movement and
   position are read at one scale. Replaces L127's per-row sparkline, whose axis
   ran to the agreed horizon and left 90% of every row empty (SS3). */
.rating-gauge-ghost {
  position: absolute;
  top: 50%;
  width: 7px;
  height: 7px;
  margin: -3.5px 0 0 -3.5px;
  border-radius: 50%;
  background: var(--paper-raised);
  box-shadow: 0 0 0 1.5px var(--ink);
}

/* ── A MARK YOU CAN ACTUALLY HIT (TASK-L141 SS4b) ───────────────────────────
   Richard, 2026-09-04: "When I hovered them it didn't work, they should be
   tappable too if someone is on the phone."

   The data was never missing - every mark carries a title with the score, the
   goal, the date and the coach's own words - and there is no `pointer-events:
   none` anywhere. What was wrong is that the ENTIRE hit target was the 7px dot,
   which is smaller than most people's pointer tolerance and is not a touch
   target at all. (The other half: the wrapper is `role="img"` with its own
   title, so a near-miss resolved the tooltip against the ANCESTOR and showed
   the CURRENT rating's sentence - which reads as "it showed me the wrong
   thing" and is indistinguishable from "it did nothing".)

   THE MARK STAYS THE SIZE IT IS AND THE TARGET GROWS AROUND IT. A mark's
   POSITION is the only thing on this track that carries meaning, so the target
   is a transparent ::after that is centred on the mark and changes no layout.
   28px square: above the 24px floor, and it is invisible, so two adjacent
   judgements still LOOK like two small dots. */
/* NOTE: `.is-pressable` is LIFTED AND UNAPPLIED in this template. In the
   product a mark leads back to the entry the judgement was made at; there is no
   pointer on this page and nothing to lead to, so the marks are spans and the
   gauge stays `role="img"`. Kept rather than left behind because this is a
   MODIFIER on a class that does exist here — unlike .trajectory-readout, whose
   whole element is retired — so wiring a pointer later is a kit change and not a
   stylesheet archaeology exercise. */
.rating-gauge-ghost.is-pressable,
.rating-gauge-mark.is-pressable {
  padding: 0;
  border: 0;
  cursor: pointer;
  /* A button must not inherit a button's own chrome here: the mark IS the
     styling, and the two rules below already paint it. */
  appearance: none;
  -webkit-appearance: none;
}

.rating-gauge-ghost.is-pressable::after,
.rating-gauge-mark.is-pressable::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 28px;
  height: 28px;
  transform: translate(-50%, -50%);
  /* No paint of any kind - it exists only to be hit. */
  background: transparent;
}

.rating-gauge-ghost.is-pressable:focus-visible,
.rating-gauge-mark.is-pressable:focus-visible {
  outline: 3px solid var(--ink);
  outline-offset: 2px;
}

.rating-gauge-mark {
  position: absolute;
  top: 50%;
  width: 12px;
  height: 12px;
  margin: -6px 0 0 -6px;
  border-radius: 50%;
  background: var(--series-1);
  /* A surface ring, so the mark stays a mark when it sits on the flag. */
  box-shadow: 0 0 0 2px var(--ink);
}

.rating-gauge.is-at-target .rating-gauge-mark { background: var(--go); }
.rating-gauge.is-confidence .rating-gauge-mark { background: var(--series-5); }

/* The number rides above its own mark. Centred on it, so a reader never has to
   work out which mark a numeral belongs to. */
.rating-gauge-score {
  position: absolute;
  left: 50%;
  bottom: 14px;
  transform: translateX(-50%);
  font-family: var(--font-ui);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  white-space: nowrap;
}

/* WHAT THE FAINT MARKS ARE (TASK-L141 SS4). Richard: "there's now little clear
   points on the lines that don't say anything about what they are or why they're
   there, they seem arbitrary?" - which is the correct reading of an unlabelled
   dot. One quiet line, in words: it names a KIND of mark and does not summarise
   a trajectory, so there is no number, no percentage and no count. */
.trajectory-earlier-caption {
  font-family: var(--font-ui);
  font-size: var(--text-xs);
  color: var(--ink-mute);
  margin: 2px 0 0;
  max-width: 62ch;
}

/* THE ENDS OF THE SCALE, WRITTEN WHERE THE MARK IS READ AGAINST THEM.
   L129's root finding was that 1-10 was named in no learner-facing string at all.
   The gauge replaced that sentence on this panel, so it names the scale itself -
   and at the ends of the track it is stronger than a sentence, because it is in
   the same place as the thing it explains. aria-hidden: the accessible name
   already says all three numbers in words. */
.rating-gauge-end {
  position: absolute;
  top: 100%;
  margin-top: 3px;
  font-family: var(--font-ui);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  color: var(--ink-mute);
}

.rating-gauge-end.is-low  { left: 0; }
.rating-gauge-end.is-high { right: 0; }

/* WHAT THE TWO OF THEM AGREED. A full-height hairline: a different KIND of mark
   from the score, because two dots on one track is identity by position alone.

   IT TAKES NO CLICKS (TASK-L141 SS4b). It is painted AFTER the score mark, so
   when a judgement lands exactly ON the agreed target the flag's 2px border sits
   over the mark's centre and swallowed the press there - found by probing
   `elementFromPoint` at each mark's own centre, where one of them came back
   false while the same mark answered 13px either side. A decorative mark must
   never be the thing a finger lands on. */
.rating-gauge-flag {
  pointer-events: none;
  position: absolute;
  top: -5px;
  bottom: -5px;
  width: 8px;
  margin-left: -4px;
  background: var(--thread);
  border: var(--edge-thin);
  border-radius: 2px;
}

.rating-gauge-goal {
  position: absolute;
  left: 50%;
  top: 18px;
  transform: translateX(-50%);
  font-family: var(--font-ui);
  font-size: var(--text-xs);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-mute);
  white-space: nowrap;
}


/* ── A KIT NODE'S ROOT HAS NO WIDTH OF ITS OWN (TASK-L159) ──────────────────
   Measured 2026-09-20: `props.style` is EMPTY on every node in this kit — a
   `width`/`sizeMode` parameter set on a kit-node instance is silently
   discarded, and the validator cannot say so because a module node is not in
   the catalogue, so its "parameter values" check is skipped rather than run.

   Every one of these sits in a Group whose inline style is
   `align-items: flex-start`, which makes a flex item shrink to its CONTENT.
   The rating gauge's content is a zero-height track and some absolutely
   positioned marks, so it collapsed to 4px and every mark was placed as a
   percentage of four pixels — in the right position relative to each other,
   which is why nothing errored and the numbers read plausibly until the track
   itself was measured.

   `.dbt-timeline-row` (L158) is INCLUDED, and it was not broken: all 21 rows
   measured exactly their parent's width, because a folded row's title and an
   open row's card are both wider than the column. That is content, not
   declaration — a short folded title would have been narrow — so it is fixed
   here rather than left to be discovered. Verified: the page height and the
   fold counts are unchanged by this rule. */
.dbt-pace,
.dbt-rating-gauge,
.dbt-timeline-row {
  width: 100%;
}

/* ── WHAT THEY MUST PRODUCE: THE METER AND ITS REVEAL (TASK-L167) ────────────
   LIFTED BY LINE RANGE from the product's globals.css (1257-1457 and
   8214-8237), sprint 44's method: the class names are the contract the kit's
   DossierSegment and DossierReveal emit, so the rules are the product's own.
   Below them, the three rules the port needed and the product did not. */

/* ── Dossier assembly meter (TASK-LX17) ──────────────────────────────────────── */
/* Non-punitive by construction: no percentages/fractions in copy, empty
   segments are quiet (label only, --ink-soft) — see DossierMeter.tsx. */

.dossier-meter {
  margin: 0 0 32px;
  padding: 20px 22px;
  background: var(--paper-raised);
  border: var(--edge);
  box-shadow: var(--shadow-lifted);
  border-radius: var(--radius);
}

.dossier-meter-title {
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  color: var(--ink-soft);
  margin: 0 0 14px;
  font-weight: 600;
}

.dossier-segments {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 16px 20px;
}

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

.dossier-segment-label {
  font-size: 0.85rem;
  color: var(--ink-soft);
}

.dossier-segment.has-facts .dossier-segment-label {
  color: var(--ink);
  font-weight: 500;
}

.dossier-segment-bar {
  height: 5px;
  border-radius: var(--radius-sm);
  background: var(--paper);
  border: var(--edge-thin);
  overflow: hidden;
}

.dossier-segment-fill {
  height: 100%;
  background: var(--thread);
  border-radius: var(--radius-sm);
}

.dossier-segment-caption {
  font-size: 0.78rem;
  color: var(--ink-soft);
}

/* ── Fact reveal (LX27) — has-facts segments open a modal ─────────────────── */

/* A has-facts segment is a button that opens the reveal modal. It resets native
   button chrome to look exactly like the LX17 segment (label + bar + caption
   stacked). Empty segments stay plain divs (non-punitive invariant, LX17). */
.dossier-segment-trigger {
  width: 100%;
  padding: 4px 6px;
  margin: -4px -6px;
  background: transparent;
  border: none;
  border-radius: var(--radius-sm);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background 0.12s ease;
}

.dossier-segment-trigger:hover {
  background: var(--thread-tint);
}

/* Shared fact-row styling — used inside the modal body. */
.dossier-fact {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.dossier-fact-name {
  font-size: 0.6875rem;
  font-weight: 600;
  color: var(--ink-soft);
  text-transform: uppercase;
  letter-spacing: 0.18em;
}

.dossier-fact-value {
  font-size: 0.95rem;
  line-height: 1.5;
  color: var(--ink);
  white-space: pre-wrap;
}

/* ── Fact reveal modal (LX27) ─────────────────────────────────────────────── */

.dossier-modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: var(--scrim);
}

.dossier-modal {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 520px;
  max-height: min(80vh, 640px);
  background: var(--paper-raised);
  border: var(--edge);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lifted);
  outline: none;
}

.dossier-modal-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  padding: 20px 22px 12px;
  border-bottom: 1px solid var(--line);
}

.dossier-modal-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 1.25rem;
  color: var(--ink);
}

.dossier-modal-close {
  flex-shrink: 0;
  padding: 4px 8px;
  background: transparent;
  border: none;
  border-radius: var(--radius-sm);
  font-size: 1rem;
  line-height: 1;
  color: var(--ink-soft);
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease;
}

.dossier-modal-close:hover {
  background: var(--thread-tint);
  color: var(--ink);
}

.dossier-modal-body {
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 18px 22px;
  overflow-y: auto;
}

.dossier-modal-footer {
  display: flex;
  justify-content: flex-end;
  padding: 12px 22px 18px;
  border-top: 1px solid var(--line);
}

.dossier-modal-copy-btn {
  padding: 11px 18px;
  background: var(--thread);
  border: var(--edge);
  box-shadow: var(--shadow-lifted);
  border-radius: var(--radius-sm);
  font-family: var(--font-ui);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink);
  cursor: pointer;
  transition: background 0.18s ease;
}

.dossier-modal-copy-btn:hover {
  filter: brightness(0.94);
}


/* WHAT AN OBJECTIVE ASKS FOR (TASK-L144 SS2). Item 9: the meter was inert when
   every segment was empty, because LX17 made a segment a button only when it
   had captured facts. It opens now - to what the deliverable IS, never to a
   count of what is missing. A list of names in plain ink: no tick, no cross,
   no number, nothing that reads as a checklist. */
.dossier-asks-item {
  display: block;
  font-family: var(--font-ui);
  font-size: var(--text-sm);
  color: var(--ink-soft);
  padding: 1px 0;
}

.dossier-empty-note {
  font-family: var(--font-prose);
  font-size: var(--text-sm);
  line-height: 1.6;
  color: var(--ink-mute);
  max-width: 56ch;
  margin: 10px 0 0;
}

/* THE LEARNER'S OWN SCOPE, ON THEIR OWN PAGE (TASK-L144 SS2). It has existed
   since L59 and rendered only inside a lesson, so the project had a NAME here

/* The kit roots. A segment fills its grid cell; the reveal's root takes no
   space at all — the dialog itself is portalled to <body>, and an empty box in
   the page's column would still cost a row gap. */
.dbt-dossier-segment {
  display: block;
  width: 100%;
}

.dbt-dossier-reveal {
  display: contents;
}

/* THE ONE SUBMISSION THAT GOES SOMEWHERE. The product renders an <a> to the
   lesson; here it is a button (it navigates through the graph's router), so it
   needs the link's look without the native button chrome. The rest are plain
   text in the same ink, because they have nowhere to go in this app. */
.dossier-submission-link,
.dossier-submission-text {
  display: block;
  font-family: var(--font-ui);
  font-size: var(--text-sm);
  padding: 1px 0;
}

.dossier-submission-link {
  background: none;
  border: none;
  margin: 0;
  text-align: left;
  cursor: pointer;
  color: var(--thread-deep);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.dossier-submission-text {
  color: var(--ink-soft);
}
