/* ═══════════════════════════════════════════════════════════════════════════
   LE CODEX · CODEX-V3.CSS — UI STANDARD v3 "Atelier Heritage"
   docs/design/UI-STANDARD-v3.md is the spec.

   WHAT THIS FILE IS. The v3 layer: type roles, the card skeleton every card
   inherits, and the shell. It consumes the --v3-* tokens defined in
   codex-tokens.css and defines NO colour of its own — every colour in this
   file is a var(), which is what tests/test_v3_token_only.py asserts.

   WHAT IT DELIBERATELY IS NOT. A replacement for codex-components.css. Not one
   existing rule is modified or overridden; nothing outside ui/v3/ links this
   sheet. The founder's instruction is to see the shell before any other screen
   is rebuilt, so v3 sits BESIDE the shipped product rather than on top of it.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── TYPE ROLES (spec §2) ───────────────────────────────────────────────────
   EB Garamond = hero words only. Inter = everything functional. Cinzel =
   wordmark and eyebrow caps only.

   The root is SANS. v2's rollout note records why this order matters: type
   inherits, so a serif root makes every unmarked element serif by accident and
   the hero roles become indistinguishable from the chrome. v3 surfaces start
   at zero serif debt and the hero roles declare themselves explicitly. */
.v3 {
  font-family: var(--v3-sans, system-ui, sans-serif);
  font-size: 16px;
  line-height: 1.55;
  color: var(--v3-ink);
  background: var(--v3-bg);
  -webkit-font-smoothing: antialiased;
}

/* The only three roles permitted a serif face. */
.v3-hero,
.v3-card__title,
.v3-screen__title {
  font-family: var(--v3-serif, Georgia, serif);
  font-weight: 500;
  letter-spacing: 0;
}

/* Cinzel is the wordmark and the eyebrow caps, and nothing else. */
.v3-wordmark,
/* THE EYEBROW IS FUNCTIONAL TEXT, SO IT IS SANS (RUN 101).
   This set the card eyebrow in Cinzel — a DISPLAY SERIF on an 11px uppercase
   label reading QUESTION, METHOD, DIAGNOSTIC, LE CONTROLE. That is the ONE
   RULE broken on the canonical app: serif for hero words, sans for everything
   functional, and an eyebrow is chrome by the standard's own list.

   IT WAS NEVER MEASURED. tests/test_type_law_rendered.py drives ROUTES, and
   until RUN 101 every route it drove was a v2 page; the v3 shell entered its
   sweep only when the retired routes started resolving to v3. Eight elements
   across four screens, in both themes.

   APP-SPEC.html agrees: its .eyebrow is Libre Franklin 11px/600, uppercase. */
.v3-card__eyebrow {
  font-family: var(--v3-sans, system-ui, sans-serif);
}


/* ═══════════════════════════════════════════════════════════════════════════
   UNIT 6 · 44px TARGETS ON THE SHELL.

   MEASURED IN THE BROWSER, on every v3 route, in both themes, by
   scripts/drive_v3_a11y.py — and this was the first time any accessibility
   instrument had looked at a v3 surface at all. render_harness.SURFACES has 22
   routes and test_accessibility_driven.ROUTES has 20, and neither list
   contains a single /v3 path.

   WHAT IT FOUND, ON EVERY SCREEN:

       a.v3-wordmark        159 x 27
       a.v3-nav__item        70 x 36   (and its two siblings)
       button.v3-nav__exit   26 x 17   <- the exit from a TIMED PAPER
       a.v3-nav__exit        26 x 22

   THE PATTERN WAS ALREADY IN THIS SHEET. `.v3-nav__you` carries
   min-width/min-height 44px and has since it was written; its four siblings
   never got it. So this is not a new rule being imposed on the design — it is
   the design's own rule, applied to the other four controls in the same bar.

   AND IT COSTS NOTHING VISUALLY. `.v3-nav__inner` is 62px tall and centres its
   children, so a 44px minimum fits inside it with room to spare: the glyphs do
   not move and the bar does not grow. The hit area grows; the drawing does not
   change.

   THE EXIT IS THE ONE THAT MATTERED. 26 x 17 is the control a student reaches
   for to leave a timed Contrôle paper, and it is the smallest thing on the
   screen. UI-STANDARD-v3 §6 makes it the ONLY navigation inside a session,
   which means there is nothing else to hit if you miss.
   ═══════════════════════════════════════════════════════════════════════════ */
.v3-wordmark,
.v3-nav__item,
.v3-nav__exit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
}

/* ── AND THE THIRD LAYER. ─────────────────────────────────────────────────
   Each pass of scripts/drive_v3_a11y.py uncovered controls the previous pass
   had been hiding: the shell's four appeared on every route, so the account
   rail's seven sat behind them, and those hid these. 20 findings, then 4,
   then 0 — and none of the later ones were new, they were simply not the
   loudest thing in the report.

   That is a property of sweeps rather than a note about these rules: a defect
   on every screen conceals a defect on one, and the second and third passes
   are not formalities.

   .v3-alink   23 x 15. Edit, Save, Cancel, Change, Sign out, Close — the
               action controls in every row of the account document. Not
               inline links inside prose, which is the one case the standard
               itself exempts; they are the buttons the rows exist to offer.
   .v3-btn--quiet  36 WIDE. The quiet half of every two-step, including the
               one that returns a student to a paper.
   .v3-seg button  34 tall. The theme control, which is the one place a
               student changes how everything else looks.
   ───────────────────────────────────────────────────────────────────────── */
.v3-alink {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
}

.v3-btn--quiet {
  min-width: 44px;
  min-height: 44px;
}

/* .v3-seg button's 44px rule is NOT here — see the end of this file. It was
   here, at the same specificity as the sheet's own .v3-seg button rule 1650
   lines below it, and lost the cascade in silence: the rule existed, the
   browser resolved 34px, and only the rendered measurement could tell the
   difference. A stylesheet check would have called it a pass. */

/* ── THE CARD SKELETON (spec §4) ────────────────────────────────────────────
   Zones in a fixed order: accent edge · eyebrow · title · why-line · reveal ·
   meta · primary. Omit freely, never rearrange. The order is expressed by
   source order here, so a card that rearranges them has to fight the sheet. */
.v3-card {
  position: relative;
  background: var(--v3-card);
  border: 1px solid var(--v3-hair);
  border-radius: 13px;
  padding: 22px 24px;
  max-width: 620px;              /* the reading column, spec §4 */
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* SHADOW ON THE ONE FOCUS CARD ONLY. Every other card is flat by default,
   which is what makes the focus card mean something. */
.v3-card--focus {
  box-shadow: var(--shadow);
}

/* Zone 1 — the 3px LEFT accent edge, on exactly ONE card per screen.
   tests/test_v3_one_nav.py's sibling check counts these per rendered screen. */
.v3-card--accent::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 3px;
  border-radius: 13px 0 0 13px;
  background: var(--v3-accent, var(--v3-signature));
}

/* THE FOCUS CARD IS NOT WASHED, AND THE FIRST DRAFT WASHED IT.
   The founder's card standard distinguishes the focus card by SHADOW — "shadow
   only on the one focus card" — and says nothing about a tint. A
   --signature-wash fill was added here anyway, and the browser showed what that
   costs: on the review rung the card wore a GREEN wash under an AMBER edge, two
   mode colours on one card, which reads as a bug and is one. --signature-wash
   stays defined because it is a founder-issued token, but it has no consumer
   until the founder assigns it one. Inventing a role for a token is the same
   class of mistake as inventing a value for it. */

/* Zone 2 — eyebrow. Inter caps, <= 24 characters (the length is a content
   rule and is gated in the voice lint, not truncated here: silently clipping
   copy is how a standard stops being read). */
.v3-card__eyebrow {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--v3-accent, var(--v3-signature));
  margin: 0;
}

/* Zone 3 — title. One line, no trailing punctuation. */
.v3-card__title {
  font-size: 27px;
  line-height: 1.2;
  margin: 0;
  color: var(--v3-ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Zone 4 — the why-line. Italic, <= 12 words.
   OMITTED MEANS ABSENT, not empty — the same rule the meta zone already
   carries. The controle rung has no founder-approved why-line and therefore
   ships without one (RUN 77 ruling 2), so this zone is now genuinely reachable
   empty and an empty flex item would leave a gap where a sentence used to be. */
.v3-card__why:empty { display: none; }

.v3-card__why {
  font-style: italic;
  font-size: 15px;
  color: var(--v3-ink2);
  margin: 0;
}

/* Zone 5 — the reveal. HEIGHT-RESERVED so it morphs in place: the box is
   already the size it will be, and only its contents change. A reveal that
   grows the card pushes the primary button under the reader's thumb between
   the decision to press it and the press. */
.v3-card__reveal {
  min-height: 44px;
  display: flex;
  align-items: center;
  color: var(--v3-ink2);
  font-size: 15px;
  /* THE ONE CURVE. This line read `ease` — a second easing in a sheet
     whose other six transitions all use the product's own bezier. Two
     curves in one interface do not read as two curves; they read as
     one interface that is slightly badly made, which is exactly the
     kind of defect nobody can point at. Found by RUN 81 Unit 9. */
  transition: opacity .18s cubic-bezier(.2, 0, .2, 1);
}

.v3-card__reveal[hidden] {
  display: flex;
  visibility: hidden;
  opacity: 0;
}

/* Zone 6 — meta. --ink2, tabular figures.
   THIS ZONE USED TO BE TAUPE, and spec §7 recorded it as the one open question
   in the standard: taupe meta is 13px, which is SMALL text, and neither
   candidate taupe value cleared AA-small in both themes. AMENDMENT V3-1 closes
   it by moving the ROLE rather than the hex — taupe is a non-text mark now, and
   every readable string resolves from --ink or --ink2. --ink2 measures 6.01:1
   at worst in light and 6.92:1 at worst in dark, so meta is AA-small at 13px
   with no upsizing and no declared debt. Law K5. */
/* AN OMITTED ZONE TAKES NO SPACE. The card standard says zones may be omitted
   but never rearranged; an empty <p> is still a flex item and still earns a
   gap, so on the cold-start rung — which has no meta — the card carried about
   twenty pixels of dead space under the reveal. Visible in the render, not in
   the markup. */
.v3-card__meta:empty { display: none; }

.v3-card__meta {
  font-size: 13px;
  color: var(--v3-ink2);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
  margin: 0;
}

/* --v3-TAUPE HAS NO CONSUMER IN THIS SHEET, AND THAT IS THE RULING, NOT AN
   OVERSIGHT. AMENDMENT V3-1 reserves it for non-text marks only — hairlines,
   dots, rules, the "de" in the wordmark. The v3 hairline is --v3-hair, the v3
   wordmark is "Le Codex" and has no "de", and no v3 surface has yet needed a
   dot that is neither a mode colour nor the signature. So the token is defined,
   flipped in both themes, refused on every text property by gate 2, and used
   nowhere. That is the same disclosure --v3-signature-wash carries eighty lines
   above, for the same reason: inventing a role for a token is the same class of
   mistake as inventing a value for one. */

/* Zone 7 — the primary. At most one per card, bottom-anchored, ink. */
.v3-card__actions {
  margin-top: auto;
  padding-top: 6px;
  display: flex;
  align-items: center;
  gap: 14px;
}

.v3-btn {
  font-family: var(--v3-sans, system-ui, sans-serif);
  font-size: 15px;
  font-weight: 600;
  padding: 11px 22px;
  border-radius: 9px;
  border: 1px solid transparent;
  background: var(--v3-btn);
  color: var(--v3-btntext);
  cursor: pointer;
  min-height: 44px;              /* the phone target floor RUN 71 measured */
}

/* A PRIMARY THAT IS A LINK IS STILL A BUTTON (RUN 79). The loud rule never
   set text-decoration, because until Phase 4 every loud .v3-btn was a <button>
   and only the QUIET ones were anchors — where the underline is deliberate.
   The funnel's "Create your account" is the first loud one that navigates, and
   it rendered with the UA underline straight through the label. Scoped to
   anchors so no existing <button> changes at all; inline-flex is what makes
   the 44px phone-target floor real on an element that is inline by default. */
a.v3-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
}

a.v3-btn--quiet {
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* The quiet override. NOT a second primary: no fill, no border, ink2. */
.v3-btn--quiet {
  background: none;
  color: var(--v3-ink2);
  font-weight: 500;
  padding: 11px 4px;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.v3-btn:focus-visible,
.v3-nav__item:focus-visible,
.v3-nav__you:focus-visible {
  outline: 2px solid var(--v3-signature);
  outline-offset: 2px;
}

/* ── THE SHELL (spec §6) ────────────────────────────────────────────────────
   Today · Work · Progress, plus You in the corner. ONE component per
   breakpoint: the top bar is desktop, the tab bar is mobile, and they are the
   same markup shown by media query. There is no sidebar in v3. */
.v3-nav {
  background: var(--v3-nav);
  border-bottom: 1px solid var(--v3-hair);
  position: sticky;
  top: 0;
  z-index: 40;
}

.v3-nav__inner {
  max-width: 980px;
  margin: 0 auto;
  padding: 0 20px;
  height: 62px;
  display: flex;
  align-items: center;
  gap: 26px;
}

.v3-wordmark {
  font-size: 17px;
  letter-spacing: .04em;
  color: var(--v3-ink);
  text-decoration: none;
  margin-right: 4px;
}

.v3-nav__items {
  display: flex;
  gap: 22px;
  list-style: none;
  margin: 0;
  padding: 0;
}

.v3-nav__item {
  font-size: 14.5px;
  font-weight: 500;
  color: var(--v3-ink2);
  text-decoration: none;
  padding: 20px 2px;
  display: inline-block;
  border-bottom: 2px solid transparent;
}

/* Exactly one active item. The 2px underline is the signature used
   STRUCTURALLY — wayfinding, not decoration (spec §3). */
.v3-nav__item[aria-current="page"] {
  color: var(--v3-ink);
  border-bottom-color: var(--v3-signature);
}

.v3-nav__you {
  margin-left: auto;
  font-size: 14.5px;
  font-weight: 500;
  color: var(--v3-ink2);
  text-decoration: none;
}

.v3-nav__you[aria-current="page"] {
  color: var(--v3-ink);
}

/* THE SESSION STATE. Nav is suppressed inside a session and replaced by one
   Exit. Not hidden-and-still-there: the items are not rendered at all, so
   there is nothing for a keyboard or a screen reader to reach past. */
.v3-nav--session .v3-nav__items,
.v3-nav--session .v3-nav__you {
  display: none;
}

.v3-nav__exit {
  margin-left: auto;
  font-size: 14.5px;
  font-weight: 600;
  color: var(--v3-ink);
  text-decoration: none;
}

/* THE GUARDED EXIT IS A BUTTON, and a button carries a UA background, border
   and font that an anchor does not. Reset here rather than on the element, so
   the two forms of Exit are visually the same control and only their behaviour
   differs — which is the whole claim: the student sees one Exit everywhere and
   the paper is the only place it stops to state a consequence. */
button.v3-nav__exit {
  border: none;
  background: none;
  padding: 0;
  font-family: inherit;
  cursor: pointer;
}

button.v3-nav__exit:focus-visible {
  outline: 2px solid var(--v3-signature);
  outline-offset: 3px;
  border-radius: 4px;
}

/* ── SCREEN ────────────────────────────────────────────────────────────── */
.v3-screen {
  max-width: 980px;
  margin: 0 auto;
  padding: 34px 20px 96px;
}

.v3-screen__title {
  font-size: 30px;
  margin: 0 0 22px;
  color: var(--v3-ink);
}

.v3-stack {
  display: flex;
  flex-direction: column;
  gap: 18px;
}

/* The read-only "where I am" strip. Meta only, no affordance, no link. */
.v3-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 26px;
  padding: 14px 0 0;
  border-top: 1px solid var(--v3-hair);
  max-width: 620px;
}

/* THE STATUS STRIP IS TEXT, AND ALL OF IT IS. The label was taupe and only
   the value was ink2, which made the half of each pair that says WHAT THE
   NUMBER MEANS the less legible half. Law K5 takes both to --ink2; the value
   keeps its weight, which is how the two are told apart now. */
.v3-strip__item {
  font-size: 13px;
  color: var(--v3-ink2);
  font-variant-numeric: tabular-nums;
}

.v3-strip__value {
  color: var(--v3-ink2);
  font-weight: 600;
}

/* ── MODE IDENTITY (spec §3) ────────────────────────────────────────────────
   These set --v3-accent, which the eyebrow and the accent edge read. Drill is
   deliberately absent from the TEXT path: .v3-mode-drill sets the edge colour
   only, and its eyebrow falls back to ink2, because #BE842A measures 3.01:1 on
   the page and the AMBER RULE forbids it as text. */
.v3-mode-diagnostic { --v3-accent: var(--v3-mode-diagnostic); }
.v3-mode-study      { --v3-accent: var(--v3-mode-study); }
.v3-mode-controle   { --v3-accent: var(--v3-mode-controle); }

.v3-mode-drill      { --v3-edge: var(--v3-mode-drill); }
.v3-mode-drill.v3-card--accent::before { background: var(--v3-mode-drill); }
.v3-mode-drill .v3-card__eyebrow { color: var(--v3-ink2); }

/* ── MOBILE: the tab bar replaces the top bar. No sidebar at any width. ──── */
@media (max-width: 720px) {
  .v3-nav {
    position: fixed;
    top: auto;
    bottom: 0;
    border-bottom: none;
    border-top: 1px solid var(--v3-hair);
    left: 0;
    right: 0;
  }

  /* ALL FOUR TABS SHARE THE BAR EQUALLY. The desktop layout pushes You to the
     corner with margin-left:auto, and carrying that into the tab bar left the
     three destinations evenly spaced and You jammed against the right edge —
     visible the moment it was rendered at 390px, invisible in the source.
     On mobile You is a tab like the others; "corner" is a desktop idea. */
  .v3-nav__inner {
    height: 58px;
    padding: 0 4px;
    gap: 0;
    justify-content: space-around;
  }

  .v3-wordmark { display: none; }

  /* ══ A SUPPRESSED NAV DOES NOT TAKE THE BOTTOM (fixed RUN 79) ══════════
     TWO FIXED BARS WERE STACKED ON EACH OTHER AT 390px, and the top one won.
     `.v3-nav` becomes a bottom tab bar here, and `.v3-footer` is also fixed to
     the bottom — so on every v3 SESSION screen at phone width, the Exit bar
     sat ON TOP of the one primary action. "Back to Today" and "Continue" were
     both clipped to a sliver. Shipped in RUN 78 and visible in its own phone
     screenshots; nothing caught it because the driver asked whether the page
     scrolled sideways, not whether its one button could be pressed.

     The bottom bar exists to hold FOUR TABS. A session has none: the items and
     the You avatar are not rendered at all, and what is left is a wordmark and
     one Exit. That belongs at the top, where it was on desktop, and the bottom
     belongs to the primary — which is the only thing on the screen a thumb is
     reaching for. The funnel shell is the same shape for the same reason.

     scripts/drive_v3_phase4.py now measures the two rects and fails on an
     overlap, so this cannot come back invisibly. */
  .v3-nav--session,
  .v3-nav--anon {
    position: sticky;
    top: 0;
    bottom: auto;
    border-top: none;
    border-bottom: 1px solid var(--v3-hair);
  }

  .v3-nav--session .v3-nav__inner,
  .v3-nav--anon .v3-nav__inner {
    justify-content: space-between;
    padding: 0 18px;
  }

  /* AND THE WORDMARK COMES BACK, because there are no tabs to make room for
     and a bar with one word in the corner is chrome that names nothing. */
  .v3-nav--session .v3-wordmark,
  .v3-nav--anon .v3-wordmark {
    display: inline;
  }

  /* flex 3 : 1 — the items block holds THREE tabs and You is the fourth, so
     giving the block three shares and You one makes all four exactly a quarter
     of the bar. `flex: 1` on the block alone let it swallow the whole width and
     left You with only its text width at the edge, which is what the 390px
     render showed twice before this was right. */
  .v3-nav__items {
    flex: 3;
    justify-content: space-around;
    gap: 0;
  }

  .v3-nav__item {
    padding: 8px 10px;
    border-bottom: none;
    border-top: 2px solid transparent;
    min-height: 44px;
    display: flex;
    align-items: center;
  }

  .v3-nav__item[aria-current="page"] {
    border-bottom-color: transparent;
    border-top-color: var(--v3-signature);
  }

  .v3-nav__you {
    flex: 1;
    margin-left: 0;
    padding: 8px 10px;
    min-height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-top: 2px solid transparent;
  }

  .v3-nav__you[aria-current="page"] { border-top-color: var(--v3-signature); }

  .v3-screen { padding: 24px 16px 110px; }
  .v3-card__title { font-size: 23px; white-space: normal; }
  .v3-screen__title { font-size: 25px; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   THE SESSION (RUN 77 Phase 2)

   ONE SHELL, FOUR MODES. Everything below is shared by Drill, Study and
   Diagnostic, and is built so Le Controle (Phase 4) adds exam chrome — a timer,
   a contract, a submit, a review — into slots that already exist, rather than
   re-architecting the screen. The two things that differ per mode are the
   ACCENT (a class on the shell, resolved through --v3-accent, which the mode
   blocks above already define) and the REVEAL TIMING (a JS policy in
   cx_session_v3.js, not a stylesheet concern).

   THE SESSION IS A CARD LIKE EVERY OTHER CARD. .v3-session__card inherits
   .v3-card, so the seven zones, the reading column and the one-primary rule
   apply to the question exactly as they apply to the Now card. A session that
   invented its own box would be the moment the standard stopped being one.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── THE SESSION SCREEN ───────────────────────────────────────────────────
   A COLUMN THAT RESERVES ITS FOOTER. The primary lives in a FIXED footer slot
   (the founder's instruction), so the scrolling body needs bottom padding equal
   to the footer or the last line of working hides behind the button. The
   padding is derived from the same value as the footer's height, so the two
   cannot drift. */
.v3-session {
  --v3-footer-h: 84px;
  max-width: 980px;
  margin: 0 auto;
  padding: 26px 20px calc(var(--v3-footer-h) + 26px);
}

/* ── THE HEADER ───────────────────────────────────────────────────────────
   An honest title and a step counter. NOT a second nav and NOT a second
   header: the shell's nav is suppressed inside a session and carries only Exit,
   so this is the one heading on the screen (Law N1). */
.v3-session__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  max-width: 620px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--v3-hair);
  margin-bottom: 20px;
}

/* THE MODE AND THE SKILL, IN ONE LINE. Inter, not the serif: this is a label
   that says where you are, not a hero word (Law K4). */
.v3-session__title {
  font-size: 15px;
  font-weight: 600;
  color: var(--v3-ink);
  margin: 0;
  letter-spacing: .01em;
}

/* The mode half of the title wears the mode's colour AS TEXT, which is exactly
   what Law C2 permits and Law C1 forbids for one of the four. Drill overrides
   it back to ink2 below, in the same place the eyebrow rule does. */
.v3-session__mode { color: var(--v3-accent, var(--v3-signature)); }
.v3-mode-drill .v3-session__mode { color: var(--v3-ink2); }

/* TABULAR FIGURES, because this number changes on every question and a
   proportional 1 is narrower than a proportional 8 — the counter would jitter
   sideways as the session advanced. */
.v3-session__step {
  font-size: 13px;
  color: var(--v3-ink2);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  white-space: nowrap;
}

/* ── THE QUESTION ─────────────────────────────────────────────────────────
   The stem is a HERO WORD in the card-standard sense: it is the thing the
   student is here to read, and it is the one place on this screen EB Garamond
   belongs (spec §2). */
.v3-stem {
  font-family: var(--v3-serif, Georgia, serif);
  font-size: 21px;
  line-height: 1.5;
  color: var(--v3-ink);
  margin: 0;
}

.v3-part {
  font-family: var(--v3-serif, Georgia, serif);
  font-size: 19px;
  line-height: 1.5;
  color: var(--v3-ink);
  margin: 0;
}

/* MATHS CAN BE WIDER THAN THE READING COLUMN AND MUST NOT WIDEN THE PAGE.
   RUN 76's render QA recorded the rule: a scroll container is the fix, not the
   defect, and KaTeX wraps at an operator but never inside a comma-separated
   list. So the stem may scroll inside itself; the body never scrolls sideways. */
.v3-stem,
.v3-part,
.v3-step__math {
  overflow-x: auto;
  overflow-y: hidden;
}

/* ── THE FIGURE THE QUESTION IS ABOUT ─────────────────────────────────────
   A diagram or a data table, between the stem that refers to it and the parts
   that ask about it. Out of the layout entirely when the item carries none,
   which is most items — a reserved box would put a permanent gap under every
   stem in the product to serve the 24 templates that draw something.

   THE SAME SCROLL RULE AS THE STEM, and for the same reason: a wide figure
   scrolls INSIDE its own container so the page body never scrolls sideways. A
   scroll container is the fix, not the defect. */
.v3-figure {
  margin: 18px 0 0;
}

.v3-figure[hidden] { display: none; }

.v3-figure__svg,
.v3-figure__table {
  overflow-x: auto;
  overflow-y: hidden;
  border: 1px solid var(--v3-hair);
  border-radius: 10px;
  padding: 16px;
  background: var(--v3-card);
  text-align: center;
}

.v3-figure__svg svg {
  max-width: 100%;
  height: auto;
}

/* TABULAR FIGURES, AND THIS IS NOT A PREFERENCE. A frequency column whose
   digits are proportionally spaced does not line up, and a column that does
   not line up is harder to read than the same numbers in a sentence. The
   render audit asserts this computed value on every rendered data table. */
.v3-figure__table .data-table {
  border-collapse: collapse;
  margin: 0 auto;
  font-family: var(--v3-sans, system-ui, sans-serif);
  font-size: 15px;
  font-variant-numeric: tabular-nums;
  color: var(--v3-ink);
}

.v3-figure__table .data-table th,
.v3-figure__table .data-table td {
  border: 1px solid var(--v3-hair);
  padding: 6px 14px;
  text-align: center;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.v3-figure__table .data-table th {
  font-weight: 600;
  text-align: left;
  color: var(--v3-ink);
  background: var(--v3-chip);
}

/* ── THE CALCULATOR STEPS, IN THE OVERLAY ─────────────────────────────────
   Plain rows rather than the live surface's keycap chrome: this is a dialog
   summoned mid-question and dismissed in place, and a rendered keyboard in it
   competes with the question for the screen. What it must carry is the model
   whose keystrokes these are, the menu path, the syntax, and — when the
   student's own model has no entry — the sentence that says so. */
.v3-gdc__model {
  font-family: var(--v3-sans, system-ui, sans-serif);
  font-size: 12px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--v3-ink2);
  margin: 0 0 12px;
}

.v3-gdc__step {
  padding: 10px 12px;
  background: var(--v3-chip);
  border-radius: 9px;
  margin-bottom: 8px;
}

.v3-gdc__label {
  font-family: var(--v3-sans, system-ui, sans-serif);
  font-size: 14px;
  font-weight: 600;
  color: var(--v3-ink);
  margin: 0 0 6px;
}

.v3-gdc__path {
  font-family: var(--v3-sans, system-ui, sans-serif);
  font-size: 13px;
  color: var(--v3-ink2);
  margin: 0 0 6px;
  overflow-x: auto;
}

.v3-gdc__syntax {
  display: block;
  font-family: ui-monospace, 'Cascadia Mono', Consolas, monospace;
  font-size: 13px;
  color: var(--v3-ink);
  background: var(--v3-card);
  border: 1px solid var(--v3-hair);
  border-radius: 6px;
  padding: 6px 10px;
  margin: 0 0 6px;
  overflow-x: auto;
  white-space: pre;
}

.v3-gdc__note,
.v3-gdc__none {
  font-size: 13px;
  color: var(--v3-ink2);
  margin: 0;
}

/* THE SUBSTITUTION NOTICE. Stated, never coloured as an alarm — the steps
   below it are correct steps, for another model. */
.v3-gdc__fallback {
  font-family: var(--v3-sans, system-ui, sans-serif);
  font-size: 13px;
  color: var(--v3-ink2);
  border-left: 2px solid var(--v3-hair);
  padding-left: 10px;
  margin: 0 0 8px;
}

/* ── THE PART BLOCK ───────────────────────────────────────────────────────
   One per part of the served item: the part's own text, then its own answer
   input. A single-part question renders one block and reads as one question,
   which is the common case; a two-part item reads as (a) and (b), which is how
   the IB sets it and how the engine grades it.

   NOT A CARD. The v2 session's own comment records what stacking a bordered
   panel per part cost — "stacked bands of chrome above a question" — so the
   block is spacing and nothing else: no border, no background, no radius. */
.v3-partblock {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.v3-partblock + .v3-partblock {
  margin-top: 6px;
  padding-top: 14px;
  border-top: 1px solid var(--v3-hair);
}

#q-body {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* ── THE ANSWER: BORDERED A/B/C/D KEYS ────────────────────────────────────
   A bordered row per option with the letter in its own key box. NOT a fill:
   the selected state is a signature-coloured BORDER and a signature key box,
   which is colour used structurally (Law C3). */
.v3-options {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.v3-option {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: 52px;                /* above the 44px phone target floor */
  padding: 10px 14px;
  text-align: left;
  background: var(--v3-card);
  border: 1px solid var(--v3-hair);
  border-radius: 10px;
  color: var(--v3-ink);
  font-family: var(--v3-sans, system-ui, sans-serif);
  font-size: 16px;
  cursor: pointer;
}

.v3-option__key {
  flex: 0 0 auto;
  width: 26px;
  height: 26px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--v3-hair);
  border-radius: 6px;
  font-size: 12px;
  font-weight: 600;
  color: var(--v3-ink2);
  font-variant-numeric: tabular-nums;
}

/* THE ANSWER VALUE. Measured non-tabular in RUN 81 on the public diagnostic,
   where three options read -35 / -54 / -42 in a proportional face: four
   figures of different widths in a column a student is comparing down. The
   card standard's "Inter tabular figures on every changing number" is exactly
   about this element, and it did not have it. */
.v3-option__value {
  flex: 1 1 auto;
  overflow-x: auto;
  overflow-y: hidden;
  font-variant-numeric: tabular-nums;
}

.v3-option[aria-pressed="true"] {
  border-color: var(--v3-signature);
  box-shadow: inset 0 0 0 1px var(--v3-signature);
}

.v3-option[aria-pressed="true"] .v3-option__key {
  border-color: var(--v3-signature);
  color: var(--v3-signature);
}

/* AFTER MARKING. The verdict is carried by the border and the key box, never
   by a filled row: a green or red block behind a student's own answer is the
   "colour as a fill" the standard forbids, and it makes the maths inside it
   harder to read at exactly the moment they need to re-read it. */
.v3-option[data-verdict="key"] {
  border-color: var(--v3-mode-diagnostic);
  box-shadow: inset 0 0 0 1px var(--v3-mode-diagnostic);
}

.v3-option[data-verdict="key"] .v3-option__key {
  border-color: var(--v3-mode-diagnostic);
  color: var(--v3-mode-diagnostic);
}

/* THE STUDENT'S WRONG PICK IS RINGED, NOT SHOUTED AT. The controle colour is
   the darkest of the four and clears AA on every surface (Law C2), so the ring
   reads as a mark rather than an alarm. */
.v3-option[data-verdict="picked"] {
  border-color: var(--v3-mode-controle);
  box-shadow: inset 0 0 0 1px var(--v3-mode-controle);
}

.v3-option[data-verdict="picked"] .v3-option__key {
  border-color: var(--v3-mode-controle);
  color: var(--v3-mode-controle);
}

.v3-option:disabled { cursor: default; }

.v3-option:focus-visible {
  outline: 2px solid var(--v3-signature);
  outline-offset: 2px;
}

/* ── NUMERIC ENTRY — the other answer shape the engine serves ─────────────
   A part with no distractors is a value to establish, not a choice to make. */
.v3-entry {
  display: flex;
  align-items: center;
  gap: 10px;
}

.v3-entry__field {
  flex: 1 1 auto;
  min-height: 52px;
  padding: 10px 14px;
  background: var(--v3-card);
  border: 1px solid var(--v3-hair);
  border-radius: 10px;
  color: var(--v3-ink);
  font-family: var(--v3-sans, system-ui, sans-serif);
  font-size: 17px;
  font-variant-numeric: tabular-nums;
}

.v3-entry__field:focus-visible {
  outline: 2px solid var(--v3-signature);
  outline-offset: 2px;
}

/* ── THE SOLUTION REVEAL ──────────────────────────────────────────────────
   HEIGHT-RESERVED FROM FIRST PAINT. This is the founder's instruction and it is
   the whole reason the reveal is built as a box that is already there rather
   than as content that arrives. The slot holds its height while it is empty, so
   pressing Check changes what is IN the box and moves nothing around it. A
   reveal that grows the page pushes the primary out from under the thumb
   between the decision to press it and the press.

   MORPHS IN PLACE. One easing curve, 200ms, on opacity and transform only.
   COLOUR IS NEVER ANIMATED: a verdict that fades from one hue to another reads
   as uncertainty about the verdict, and a student watching their answer change
   colour is watching the product make up its mind. */
.v3-reveal {
  /* SIZED FROM MEASUREMENTS, NOT CHOSEN. Driven across both modes, both themes
     and both widths, the filled reveal measured 192px and 248px on desktop and
     214/223/249/326px on the phone; the reserve is set near the median of each
     so the box is most of its final size before Check and the residual is a
     handful of pixels on a typical question.

     IT IS NOT SET TO THE MAXIMUM, and that is deliberate. The outliers are a
     long named misconception wrapping to five lines, and reserving for them
     would leave a third of a phone screen blank on every question that does
     not. What the founder's instruction protects is that nothing MOVES, and
     scripts/drive_v3_session.py asserts that to the pixel: the stem, every
     part, every option and the fixed primary have identical rects before and
     after Check. The reveal is the last zone in the card, so what it does past
     its reserve is fill downward, below everything the student is reading. */
  --v3-reveal-reserve: 176px;
  min-height: var(--v3-reveal-reserve);
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding-top: 16px;
  margin-top: 4px;
  border-top: 1px solid var(--v3-hair);
}

.v3-reveal__body {
  display: flex;
  flex-direction: column;
  gap: 10px;
  opacity: 1;
  transform: none;
  transition: opacity .2s cubic-bezier(.2, 0, .2, 1),
              transform .2s cubic-bezier(.2, 0, .2, 1);
}

/* EMPTY IS INVISIBLE, NOT ABSENT. `visibility: hidden` keeps the box in the
   layout; `display: none` would give back the reserved height and reintroduce
   the jump this whole component exists to prevent. */
.v3-reveal[data-state="waiting"] .v3-reveal__body {
  visibility: hidden;
  opacity: 0;
  transform: translateY(4px);
}

/* THE MOBILE RESERVE IS LARGER, because the same three lines wrap to more of
   them in a narrower column: at 390px the verdict, the named misconception and
   the key step take five lines where the desktop column takes three. */
@media (max-width: 720px) {
  .v3-reveal { --v3-reveal-reserve: 219px; }
}

.v3-reveal__verdict {
  font-size: 16px;
  font-weight: 600;
  color: var(--v3-ink);
  margin: 0;
}

/* The named misconception, from the engine. Never a raw error_type: the only
   door is cx_misconception.js's describe(). */
.v3-reveal__misconception {
  font-size: 15px;
  line-height: 1.55;
  color: var(--v3-ink2);
  margin: 0;
}

/* ── ONE WORKING STEP, THEN THE REST ON REQUEST ───────────────────────────
   The key step is the one the mark scheme awards the method mark for. The rest
   is behind "See full working", which expands IN PLACE and collapses again —
   never a navigation away from the question. */
.v3-step {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  padding: 10px 12px;
  background: var(--v3-chip);
  border-radius: 9px;
}

.v3-step__award {
  flex: 0 0 auto;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .06em;
  color: var(--v3-ink2);
  font-variant-numeric: tabular-nums;
  padding-top: 3px;
  min-width: 22px;
}

.v3-step__math {
  flex: 1 1 auto;
  font-size: 16px;
  color: var(--v3-ink);
}

.v3-step__note {
  display: block;
  font-size: 13px;
  color: var(--v3-ink2);
  margin-top: 3px;
}

.v3-working {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.v3-working[hidden] { display: none; }

/* ── THE QUIET OVERLAYS: METHOD AND GDC ───────────────────────────────────
   SUMMONED BY THE CURRENT QUESTION, DISMISSED IN PLACE, NEVER A NAVIGATION.
   A dialog rather than a page, so Exit still means Exit and the session's one
   way out stays the only one. */
.v3-overlay {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: var(--v3-scrim);
}

.v3-overlay[hidden] { display: none; }

.v3-overlay__panel {
  width: 100%;
  max-width: 620px;
  max-height: 78vh;
  overflow-y: auto;
  background: var(--v3-card);
  border: 1px solid var(--v3-hair);
  border-radius: 14px 14px 0 0;
  padding: 22px 24px 26px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

@media (min-width: 721px) {
  .v3-overlay { align-items: center; }
  .v3-overlay__panel { border-radius: 14px; }
}

.v3-overlay__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
}

.v3-overlay__title {
  font-family: var(--v3-serif, Georgia, serif);
  font-size: 24px;
  font-weight: 500;
  color: var(--v3-ink);
  margin: 0;
}

.v3-overlay__body {
  font-size: 15px;
  line-height: 1.6;
  color: var(--v3-ink2);
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.v3-overlay__body strong { color: var(--v3-ink); font-weight: 600; }

/* ── THE IN-TASK SUPPORT ROW ──────────────────────────────────────────────
   Quiet. These are not primaries and must not read as one: no fill, no border,
   ink2, and they sit ABOVE the footer so the one primary keeps the footer to
   itself. */
.v3-support {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 18px;
  padding-top: 4px;
}

/* ── THE FIXED FOOTER SLOT ────────────────────────────────────────────────
   ONE PRIMARY, BOTTOM-ANCHORED, ALWAYS IN THE SAME PLACE. The founder's
   instruction is a fixed slot rather than a button that follows the content:
   the press target does not move between questions, so the hand learns it once.

   ON MOBILE IT SITS ON THE BOTTOM EDGE ITSELF, because inside a session there
   IS no tab bar — the nav is suppressed and replaced by one Exit. So the footer
   owns the bottom edge at every width and carries no tab-bar offset. */
.v3-footer {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 30;
  background: var(--v3-nav);
  border-top: 1px solid var(--v3-hair);
}

.v3-footer__inner {
  max-width: 980px;
  margin: 0 auto;
  padding: 16px 20px;
  min-height: 84px;
  display: flex;
  align-items: center;
  gap: 14px;
}

.v3-footer .v3-btn { min-width: 132px; }

/* ── SESSION END: THE "WHAT MOVED" CARD ───────────────────────────────────
   The signature edge, not a mode edge: the session is over, so the card belongs
   to the product rather than to the mode that just finished. */
.v3-moved {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.v3-moved__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 20px;
  padding: 9px 0;
  border-bottom: 1px solid var(--v3-hair);
  font-size: 15px;
  color: var(--v3-ink2);
}

.v3-moved__row:last-child { border-bottom: none; }

/* TABULAR, so "6 → 5" lines up under "12 → 9" and the arrow does not wander. */
.v3-moved__value {
  color: var(--v3-ink);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  white-space: nowrap;
}

/* ── THE DIAGNOSTIC REPORT ────────────────────────────────────────────────
   Secure carries a filled forest DOT; needs-work carries an amber RING. Both
   are marks, never text — which is the AMBER RULE working as designed: the
   amber can say "this one" on a ring and may not say it in a word. */
.v3-map {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.v3-map__row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 0;
  border-bottom: 1px solid var(--v3-hair);
  font-size: 15px;
  color: var(--v3-ink);
}

.v3-map__row:last-child { border-bottom: none; }

.v3-map__mark {
  flex: 0 0 auto;
  width: 11px;
  height: 11px;
  border-radius: 50%;
}

.v3-map__row[data-band="secure"] .v3-map__mark {
  background: var(--v3-mode-diagnostic);
}

.v3-map__row[data-band="needs"] .v3-map__mark {
  border: 2px solid var(--v3-mode-drill);
}

.v3-map__group {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
  font-family: var(--display, Cinzel, Georgia, serif);
  color: var(--v3-ink2);
  margin: 14px 0 2px;
}

.v3-map__group:first-child { margin-top: 0; }

/* THE PLAIN LINE. Not a card, not a callout, not a banner — one sentence in the
   flow of the report, which is what "plain" means. */
.v3-plan-line {
  font-size: 15px;
  line-height: 1.6;
  color: var(--v3-ink);
  margin: 0;
  padding-top: 14px;
  border-top: 1px solid var(--v3-hair);
}

/* ── MOBILE ───────────────────────────────────────────────────────────── */
@media (max-width: 720px) {
  .v3-session { padding: 18px 16px calc(var(--v3-footer-h) + 20px); }
  .v3-session__head { flex-direction: column; align-items: flex-start; gap: 4px; }
  .v3-stem { font-size: 19px; }
  .v3-part { font-size: 18px; }
  .v3-footer__inner { padding: 14px 16px; }
  .v3-footer .v3-btn { flex: 1 1 auto; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   PHASE 3 (RUN 78) — Work · Progress · You · Method · GDC

   BUILT TO THE APPROVED MOCKUPS at docs/design/mockups/, which are the VISUAL
   SOURCE OF TRUTH, rendered through the v3 tokens rather than the mockups'
   hardcoded palette. Where a mockup's inline hex and a v3 token differ, the
   token wins and the difference is recorded in the run report; where a mockup's
   STRUCTURE and a v3 habit differ, the mockup wins, because that is what "the
   visual source of truth" means.

   THE ONE PLACE A MOCKUP IS NOT FOLLOWED is the Work primary, and it is
   measured: work.html fills the button with the mode colour and puts white text
   on it. On Drill that is #FFFFFF on #BE842A = 3.22:1, which fails AA. The card
   standard's zone 7 says "ink --btn fill" and UI-STANDARD-v3 §1 defines
   --btn as ink, so two of the three locked documents already say ink and the
   third is the one that fails. The button is ink; the mode still does its
   wayfinding job through the 4px accent edge and the eyebrow.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── THE NAV, BROUGHT TO THE MOCKUP ─────────────────────────────────────── */

/* The "de" in the lockup: italic EB Garamond, --taupe. This is AMENDMENT
   V3-1's own example of a permitted non-text mark, and it is the token's first
   consumer on any v3 surface (RUN 77 P-05 closes here). */
.v3-wordmark__de {
  font-family: var(--v3-serif, Georgia, serif);
  font-style: italic;
  font-weight: 500;
  color: var(--v3-taupe);
  letter-spacing: 0;
}

/* THE ACTIVE ITEM IS A FILLED PILL, not the underline RUN 77 shipped.
   codex-visual-system-heritage.md: "Active nav / 'you are here': a filled
   signature pill (white text). Inactive tabs = grey --ink2, no chrome."
   Measured 6.39:1. */
.v3-nav__item {
  border-radius: 8px;
  padding: 7px 14px;
  border-bottom: none;
}

.v3-nav__item[aria-current="page"] {
  background: var(--v3-signature);
  color: var(--v3-btntext);
  font-weight: 600;
  border-bottom-color: transparent;
}

.v3-nav__items { gap: 8px; }

/* The corner avatar. A circle in a signature wash with a signature ring — the
   one place --signature-wash finally has a consumer (RUN 77 P-09). */
.v3-nav__you {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
}

.v3-nav__avatar {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--v3-signature-wash);
  border: 1px solid var(--v3-signature);
  display: block;
}

.v3-nav__you[aria-current="page"] .v3-nav__avatar {
  background: var(--v3-signature);
}

/* ── SHARED SCREEN FURNITURE ────────────────────────────────────────────── */
.v3-kicker {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--v3-ink2);
  margin: 0;
}

.v3-lede {
  font-family: var(--v3-serif, Georgia, serif);
  font-weight: 500;
  font-size: 25px;
  line-height: 1.2;
  color: var(--v3-ink);
  margin: 6px 0 22px;
}

.v3-why {
  font-style: italic;
  font-size: 15px;
  color: var(--v3-ink2);
  margin: 0;
}

/* The "or" rule between the recommendation and the list. */
.v3-orline {
  display: flex;
  align-items: center;
  gap: 16px;
  margin: 34px 0 8px;
}

.v3-orline__l {
  height: 1px;
  background: var(--v3-hair);
  flex: 1;
}

.v3-orline__x {
  font-size: 10px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--v3-ink2);
  font-weight: 600;
}

/* ── THE MODE CHIP ──────────────────────────────────────────────────────
   The mode's NAME as text, on its own pre-composited fill. The fill is opaque
   on purpose: a translucent tint has no fixed contrast, it has whatever the
   backdrop leaves, and the claret label measured 4.12:1 that way in dark. */
.v3-modechip {
  display: inline-block;
  font-family: var(--v3-sans, system-ui, sans-serif);
  font-style: normal;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  margin-left: 9px;
  padding: 2px 7px;
  border-radius: 5px;
  white-space: nowrap;
}

.v3-mode-diagnostic .v3-modechip,
.v3-modechip--diagnostic {
  background: var(--v3-mode-diagnostic-chip);
  color: var(--v3-mode-diagnostic-ink);
}

.v3-mode-study .v3-modechip,
.v3-modechip--study {
  background: var(--v3-mode-study-chip);
  color: var(--v3-mode-study-ink);
}

.v3-mode-drill .v3-modechip,
.v3-modechip--drill {
  background: var(--v3-mode-drill-chip);
  color: var(--v3-mode-drill-ink);
}

.v3-mode-controle .v3-modechip,
.v3-modechip--controle {
  background: var(--v3-mode-controle-chip);
  color: var(--v3-mode-controle-ink);
}

/* ── WORK ───────────────────────────────────────────────────────────────
   THE ONE NON-NEGOTIABLE GUARDRAIL (codex-ia-panel-report.md): Work leads with
   the engine's auto-route suggestion and then a short outcome-named LIST.
   Never a grid of co-equal tiles. The list is flex-column and there is no
   grid-template-columns anywhere in this block, which is what the Progress
   colour gate's sibling check reads. */

/* The layered recommendation: a soft panel offset behind the card, which is the
   mockup's "gentle elevation" done with one pseudo-element rather than a
   gradient or a glow. */
.v3-rec { position: relative; }

.v3-rec::before {
  content: '';
  position: absolute;
  inset: 10px -6px -8px;
  background: var(--v3-chip);
  border-radius: 16px;
  z-index: 0;
}

.v3-rec > .v3-card {
  position: relative;
  z-index: 1;
  max-width: none;
}

/* The accent edge is 4px here, which is the card standard's own range
   ("a single 3px LEFT border", the visual system says 3-4px). It is the ONE
   place a Work card carries mode colour. */
.v3-rec .v3-card--accent::before { width: 4px; }

.v3-rectag {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  border-radius: 999px;
  padding: 4px 11px;
  align-self: flex-start;
}

.v3-rectag__dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}

/* The outcome rows. A LIST. The whole row is the tap target and there is no
   per-row button, which is the card standard's rule for this type. */
.v3-rows {
  display: flex;
  flex-direction: column;
}

.v3-row {
  display: flex;
  align-items: center;
  gap: 16px;
  width: 100%;
  text-align: left;
  padding: 18px 12px;
  min-height: 64px;
  border: none;
  border-bottom: 1px solid var(--v3-hair);
  border-radius: 12px;
  background: none;
  color: var(--v3-ink);
  font-family: var(--v3-sans, system-ui, sans-serif);
  cursor: pointer;
  transition: background .18s cubic-bezier(.2, 0, .2, 1);
}

.v3-row:last-child { border-bottom: none; }
.v3-row:hover { background: var(--v3-card); }

.v3-row:focus-visible {
  outline: 2px solid var(--v3-signature);
  outline-offset: 2px;
}

.v3-row__dot {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  flex: none;
}

.v3-row__m { flex: 1; }

.v3-row__nm {
  font-family: var(--v3-serif, Georgia, serif);
  font-size: 19px;
  color: var(--v3-ink);
  display: flex;
  align-items: center;
  flex-wrap: wrap;
}

.v3-row__d {
  font-size: 13px;
  color: var(--v3-ink2);
  margin-top: 2px;
}

/* A CHEVRON IS SOMETHING A STUDENT READS, not a hairline. It took --taupe
   because the mockup draws it in a faint grey, which is not a reason. Law K5
   applies and it takes --ink2. */
.v3-row__chev {
  color: var(--v3-ink2);
  font-size: 17px;
  flex: none;
}

/* ── PROGRESS-AS-PATH ───────────────────────────────────────────────────
   A PATH, NOT A DASHBOARD, and a READ-ONLY ALTITUDE — no second navigation
   axis. The spine is ink and taupe; a mode colour appears only as a small mark
   on a node or in the legend. tests/test_v3_phase3.py's colour gate fails if a
   mode colour reaches a `background` on anything wider than a mark. */
.v3-ready {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 16px 0;
  border-bottom: 1px solid var(--v3-hair);
  flex-wrap: wrap;
}

.v3-band {
  display: flex;
  gap: 4px;
}

/* THE GRADE CELLS ARE TEXT AND ARE READ AS TEXT. The mockup greys the inactive
   figures to #B7AD9C, which measures 1.88:1 on --chip. They are numbers a
   student reads, so they take --ink2 (6.01:1 worst). */
.v3-band__g {
  width: 26px;
  height: 30px;
  border-radius: 5px;
  background: var(--v3-chip);
  border: 1px solid var(--v3-hair);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: 600;
  color: var(--v3-ink2);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

.v3-band__g[data-state="on"] {
  background: var(--v3-mode-diagnostic-chip);
  border-color: var(--v3-signature);
  color: var(--v3-signature-ink);
}

.v3-band__g[data-state="now"] {
  background: var(--v3-signature);
  border-color: var(--v3-signature);
  color: var(--v3-btntext);
}

.v3-ready__txt {
  font-size: 13px;
  color: var(--v3-ink2);
  line-height: 1.45;
  flex: 1;
  min-width: 220px;
}

.v3-ready__txt b { color: var(--v3-ink); font-weight: 600; }

.v3-strandhead {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--v3-ink2);
  margin: 18px 0 2px;
}

/* THE SPINE. Ink/taupe only, per the founder's rule for this screen. */
.v3-strand {
  position: relative;
  margin-left: 8px;
  padding-left: 26px;
  border-left: 2px solid var(--v3-hair);
}

.v3-node {
  position: relative;
  padding: 14px 0 14px 4px;
  border-bottom: 1px solid var(--v3-hair);
}

.v3-node:last-child { border-bottom: none; }

/* A STATE DOT, NOT A BAR, and colour is NEVER alone: every node carries a word
   as well (.v3-node__st), which is what makes the faint "not yet" dot legible
   as a state rather than as an absence. */
.v3-node__dot {
  position: absolute;
  left: -35px;
  top: 16px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--v3-card);
}

.v3-node__dot[data-state="mastered"] {
  background: var(--v3-mode-diagnostic);
  border: 2px solid var(--v3-mode-diagnostic);
}

.v3-node__dot[data-state="developing"] {
  background: var(--v3-card);
  border: 2px solid var(--v3-ink2);
}

.v3-node__dot[data-state="needs"] {
  background: var(--v3-card);
  border: 2px solid var(--v3-mode-drill);
  box-shadow: 0 0 0 3px var(--v3-mode-drill-chip);
}

.v3-node__dot[data-state="notyet"] {
  background: var(--v3-hair);
  border: 2px solid var(--v3-hair);
}

.v3-node__nm { font-size: 14.5px; color: var(--v3-ink); }

.v3-node__st {
  font-size: 11.5px;
  color: var(--v3-ink2);
  margin-top: 2px;
}

.v3-node[data-here] .v3-node__nm { font-weight: 600; }

.v3-node__here {
  position: absolute;
  right: 0;
  top: 16px;
  font-size: 9.5px;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--v3-signature-ink);
  background: var(--v3-mode-diagnostic-chip);
  border: 1px solid var(--v3-signature);
  border-radius: 5px;
  padding: 2px 7px;
}

/* The needs-work strip: the one reward on the screen is this list getting
   shorter, so the count is tabular and the previous figure is stated. */
.v3-needs {
  margin: 18px 0 20px;
  background: var(--v3-card);
  border: 1px solid var(--v3-hair);
  border-left: 3px solid var(--v3-mode-drill);
  border-radius: 11px;
  padding: 14px 16px;
  max-width: 620px;
}

.v3-needs__h {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--v3-ink2);
  margin: 0 0 8px;
}

.v3-needs__row {
  font-size: 13.5px;
  color: var(--v3-ink2);
  padding: 4px 0;
  display: flex;
  align-items: center;
  gap: 9px;
}

.v3-needs__r {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  border: 2px solid var(--v3-mode-drill);
  flex: none;
}

.v3-needs__count {
  font-size: 12px;
  color: var(--v3-ink2);
  margin: 8px 0 0;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

.v3-needs__count b { color: var(--v3-signature-ink); font-weight: 600; }

.v3-legend {
  display: flex;
  gap: 16px;
  flex-wrap: wrap;
  padding: 12px 0 20px;
  border-top: 1px solid var(--v3-hair);
  margin-top: 8px;
}

.v3-legend__k {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 11px;
  color: var(--v3-ink2);
}

.v3-legend__d {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  flex: none;
}

/* ── THE YOU DRAWER ─────────────────────────────────────────────────────
   A CONTAINED CORNER DRAWER, one calm column, in the card language. No
   "Settings", no gear: the locked lexicon names each row for what it is. */
.v3-drawer {
  max-width: 400px;
  margin: 0 auto;
  background: var(--v3-bg);
  border: 1px solid var(--v3-hair);
  border-radius: 15px;
  overflow: hidden;
  box-shadow: var(--shadow);
}

.v3-drawer__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 18px 22px;
  background: var(--v3-card);
  border-bottom: 1px solid var(--v3-hair);
}

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

.v3-drawer__nm {
  font-family: var(--v3-serif, Georgia, serif);
  font-size: 20px;
  color: var(--v3-ink);
  display: flex;
  flex-direction: column;
  line-height: 1.15;
}

.v3-drawer__nm small {
  font-family: var(--v3-sans, system-ui, sans-serif);
  font-size: 11.5px;
  color: var(--v3-ink2);
  margin-top: 3px;
}

.v3-grp { padding: 6px 0 10px; }

.v3-grp__h {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--v3-ink2);
  padding: 12px 22px 4px;
  margin: 0;
}

.v3-drow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
  padding: 12px 22px;
  min-height: 48px;
  border: none;
  border-top: 1px solid var(--v3-hair);
  background: none;
  text-align: left;
  font-family: var(--v3-sans, system-ui, sans-serif);
  color: var(--v3-ink);
  cursor: pointer;
}

.v3-drow:focus-visible {
  outline: 2px solid var(--v3-signature);
  outline-offset: -2px;
}

.v3-drow__l { font-size: 14px; color: var(--v3-ink); }

.v3-drow__r {
  font-size: 13px;
  color: var(--v3-ink2);
  display: flex;
  align-items: center;
  gap: 9px;
}

.v3-drow__chev { color: var(--v3-ink2); font-size: 14px; }

/* The segmented control: the active segment is a filled signature pill, the
   same affordance the nav uses, so "selected" reads identically everywhere. */
.v3-seg {
  display: inline-flex;
  border: 1px solid var(--v3-hair);
  border-radius: 8px;
  overflow: hidden;
}

.v3-seg button {
  font-family: var(--v3-sans, system-ui, sans-serif);
  font-size: 11.5px;
  padding: 7px 10px;
  min-height: 34px;
  border: none;
  background: none;
  color: var(--v3-ink2);
  cursor: pointer;
}

.v3-seg button[aria-pressed="true"] {
  background: var(--v3-signature);
  color: var(--v3-btntext);
  font-weight: 600;
}

/* THE ACCOUNT TOGGLES. Measured at 38 x 22 by scripts/drive_v3_a11y.py — the
   last of five layers of sub-44px control this run uncovered, each one hidden
   behind the noisier findings above it.

   THE SWITCH IS NOT DRAWN ANY DIFFERENTLY. It was the BUTTON that carried the
   track, so a 44px box would have meant a 44px switch. The track moves to
   ::before at exactly its old size and exactly its old position — 38 x 22,
   same radius, same hairline, same fill — and the button becomes an invisible
   44 x 44 box centred on it. The knob's offsets shift by the track's new
   origin and nothing else.

   So the student sees the same switch and can hit six more pixels of it in
   each direction. That is the whole of this change, and it is why the 44px
   rule cost this product nothing visually anywhere it was applied. */
.v3-toggle {
  width: 44px;
  height: 44px;
  border: none;
  background: none;
  border-radius: 0;
  position: relative;
  cursor: pointer;
  padding: 0;
}

/* THE TRACK, at its original size, centred in the larger box.
   left 3 = (44 - 38) / 2      top 11 = (44 - 22) / 2 */
.v3-toggle::before {
  content: '';
  position: absolute;
  left: 3px;
  top: 11px;
  width: 38px;
  height: 22px;
  border-radius: 999px;
  border: 1px solid var(--v3-hair);
  background: var(--v3-signature);
  box-sizing: border-box;
}

/* THE KNOB, at its original offsets plus the track's new origin.
   top 13 = 11 + 2      left 21 = 3 + 18 */
.v3-toggle::after {
  content: '';
  position: absolute;
  top: 13px;
  left: 21px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--v3-btntext);
  transition: left .18s cubic-bezier(.2, 0, .2, 1);
}

.v3-toggle[aria-pressed="false"]::before { background: var(--v3-chip); }
.v3-toggle[aria-pressed="false"]::after {
  left: 5px;
  background: var(--v3-taupe);
}

.v3-toggle:focus-visible {
  outline: 2px solid var(--v3-signature);
  outline-offset: 2px;
}

.v3-signout {
  padding: 16px 22px 20px;
  border-top: 1px solid var(--v3-hair);
}

/* ── METHOD CARDS ───────────────────────────────────────────────────────
   PROGRESSIVE REVEAL: the formula first, then when-to-use, then worked. Each
   stage expands IN PLACE inside a footprint reserved from first paint, so
   opening one never shoves what is above it. */
.v3-method {
  --v3-method-reserve: 210px;
  max-width: 620px;
}

.v3-formula {
  background: var(--v3-chip);
  border: 1px solid var(--v3-hair);
  border-radius: 11px;
  padding: 20px 22px;
  text-align: center;
  font-family: var(--v3-serif, Georgia, serif);
  font-size: 23px;
  color: var(--v3-ink);
  overflow-x: auto;
}

/* THE STAGE BOX IS HEIGHT-RESERVED. Same rule as the session reveal, same
   reason: the Method card opens as an in-context OVERLAY over a question, and
   a stage that grew would move the question underneath it. */
.v3-stage {
  min-height: var(--v3-method-reserve);
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.v3-stage__body {
  display: flex;
  flex-direction: column;
  gap: 10px;
  transition: opacity .2s cubic-bezier(.2, 0, .2, 1),
              transform .2s cubic-bezier(.2, 0, .2, 1);
}

.v3-wrow {
  display: flex;
  gap: 12px;
  padding: 14px 0;
  border-bottom: 1px solid var(--v3-hair);
}

.v3-wrow:last-child { border-bottom: none; }

.v3-wtag {
  flex: none;
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  padding: 4px 9px;
  border-radius: 6px;
  height: fit-content;
  margin-top: 2px;
}

.v3-wtag[data-kind="use"] {
  background: var(--v3-mode-diagnostic-chip);
  color: var(--v3-mode-diagnostic-ink);
}

.v3-wtag[data-kind="not"] {
  background: var(--v3-mode-controle-chip);
  color: var(--v3-mode-controle-ink);
}

.v3-wtxt { font-size: 14px; line-height: 1.55; color: var(--v3-ink2); }
.v3-wtxt b { color: var(--v3-ink); font-weight: 600; }

.v3-mstep {
  display: flex;
  gap: 12px;
  padding: 9px 0;
  font-size: 14.5px;
  color: var(--v3-ink);
  align-items: flex-start;
}

.v3-mstep__no {
  flex: none;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--v3-chip);
  color: var(--v3-ink2);
  font-size: 11px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  font-variant-numeric: tabular-nums;
}

/* Tags and related methods are TEXT LINKS in the meta zone, never a chip
   cluster by the title — the card standard is explicit about that. */
.v3-tags {
  margin-top: 20px;
  padding-top: 16px;
  border-top: 1px solid var(--v3-hair);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

.v3-tags__l {
  font-size: 9.5px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--v3-ink2);
  font-weight: 700;
  margin-right: 2px;
}

.v3-tag {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  background: var(--v3-mode-study-chip);
  color: var(--v3-mode-study-ink);
  border: 1px solid var(--v3-hair);
  text-decoration: none;
  min-height: 32px;
}

.v3-tag--path {
  background: var(--v3-mode-diagnostic-chip);
  color: var(--v3-mode-diagnostic-ink);
}

.v3-mlist { display: flex; flex-direction: column; }

.v3-mrow {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  text-align: left;
  padding: 14px 10px;
  min-height: 56px;
  border: none;
  border-bottom: 1px solid var(--v3-hair);
  background: none;
  color: var(--v3-ink);
  font-family: var(--v3-sans, system-ui, sans-serif);
  cursor: pointer;
}

.v3-mrow:last-child { border-bottom: none; }
.v3-mrow:hover { background: var(--v3-card); }

.v3-mrow:focus-visible {
  outline: 2px solid var(--v3-signature);
  outline-offset: 2px;
}

.v3-mrow__i {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  flex: none;
  background: var(--v3-mode-study);
}

.v3-mrow__nm { flex: 1; font-size: 14.5px; }
.v3-mrow__rel { font-size: 12.5px; color: var(--v3-ink2); font-style: italic; }

/* ── GDC TRAINING ───────────────────────────────────────────────────────
   Skinned per model. The device bodies are DELIBERATELY not v3 tokens: they
   are a depiction of a physical object, and a charcoal Nspire that flipped to
   cream in light mode would stop being a picture of the calculator on the
   desk. They are declared here as their own named tokens so gate 1 still sees
   var() everywhere, and so the four skins are legibly distinct in source. */
.v3-sel {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 16px;
}

.v3-sel button {
  font-family: var(--v3-sans, system-ui, sans-serif);
  font-size: 13px;
  font-weight: 600;
  padding: 9px 16px;
  min-height: 44px;
  border-radius: 10px;
  background: var(--v3-card);
  border: 1px solid var(--v3-hair);
  color: var(--v3-ink2);
  cursor: pointer;
}

.v3-sel button[aria-pressed="true"] {
  background: var(--v3-btn);
  color: var(--v3-btntext);
  border-color: var(--v3-btn);
}

.v3-sel button:focus-visible {
  outline: 2px solid var(--v3-signature);
  outline-offset: 2px;
}

.v3-train {
  display: flex;
  gap: 22px;
  flex-wrap: wrap;
  align-items: flex-start;
  background: var(--v3-card);
  border: 1px solid var(--v3-hair);
  border-radius: 16px;
  padding: 24px;
}

.v3-train__left { flex: 1 1 320px; min-width: 0; }

.v3-kstep {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 0;
  border-bottom: 1px solid var(--v3-hair);
}

.v3-kstep:last-child { border-bottom: none; }

.v3-kstep__no {
  flex: none;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--v3-chip);
  color: var(--v3-ink2);
  font-size: 11px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  font-variant-numeric: tabular-nums;
}

.v3-kstep__d { font-size: 13.5px; color: var(--v3-ink); line-height: 1.6; }

/* A KEYCAP IS A QUOTATION OF THE DEVICE. It prints what is written on the
   physical key, so it is monospaced, and it is never restyled into prose:
   voice_canon_audit.py's DEVICE_LABEL_FILES ruling says a style rule must
   never be allowed to falsify an instruction. */
.v3-kcap {
  display: inline-block;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11.5px;
  font-weight: 700;
  padding: 3px 8px;
  border-radius: 6px;
  background: var(--v3-ink);
  color: var(--v3-bg);
  margin: 0 2px;
  white-space: nowrap;
}

.v3-kcap[data-hi] {
  background: var(--v3-dev-accent, var(--v3-signature));
  color: var(--v3-dev-accent-ink, var(--v3-btntext));
}

.v3-result {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--v3-hair);
  font-size: 13.5px;
  color: var(--v3-ink2);
}

.v3-result b { color: var(--v3-ink); font-variant-numeric: tabular-nums; }

.v3-devwrap { flex: 0 0 auto; }

.v3-device {
  width: 250px;
  border-radius: 20px;
  padding: 16px 16px 20px;
  box-shadow: var(--shadow);
  background: var(--v3-dev-body);
}

.v3-device__name {
  font-size: 10px;
  letter-spacing: .12em;
  text-transform: uppercase;
  text-align: center;
  margin: 0 0 10px;
  color: var(--v3-dev-name);
}

.v3-device__pad {
  width: 62px;
  height: 62px;
  border-radius: 50%;
  margin: 0 auto 10px;
  background: var(--v3-dev-pad);
}

.v3-device__screen {
  border-radius: 6px;
  overflow: hidden;
  border: 1px solid var(--v3-dev-bezel);
  margin-bottom: 10px;
}

.v3-device__fkeys {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 4px;
  margin-bottom: 10px;
}

.v3-device__keys {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 6px;
}

/* THE CALCULATOR KEYS. Tabular, and for the ordinary reason rather than a
   grudging one: this is a KEYPAD, a grid whose whole job is that 7 8 9 sit
   over 4 5 6. Proportional figures put them fractionally out of column, which
   is the one place in the product where that is not a subtlety.

   Measured non-tabular in RUN 81 and FIXED rather than exempted. The
   alternative was to mark them [data-nontabular-ok] on the reasoning that a
   calculator key is a device label — true, and it would have been a smaller
   diff, and the keypad would still have been very slightly crooked. */
.v3-k {
  height: 22px;
  border-radius: 5px;
  font-size: 9px;
  font-weight: 600;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--v3-dev-key);
  color: var(--v3-dev-keyink);
  font-variant-numeric: tabular-nums;
}

.v3-k[data-role="accent"] {
  background: var(--v3-dev-accent);
  color: var(--v3-dev-accent-ink);
}

.v3-k[data-role="enter"] {
  background: var(--v3-dev-enter);
  color: var(--v3-dev-enter-ink);
}

.v3-k[data-role="fkey"] {
  height: 14px;
  font-size: 8px;
  background: var(--v3-dev-fkey);
  color: var(--v3-dev-fkey-ink);
}

.v3-dcap {
  font-size: 12px;
  color: var(--v3-ink2);
  text-align: center;
  margin: 10px 0 0;
  max-width: 250px;
}

.v3-dcap b { color: var(--v3-ink); }

/* ── MOBILE ─────────────────────────────────────────────────────────── */
@media (max-width: 720px) {
  .v3-nav__item { padding: 8px 12px; border-top: none; }
  .v3-nav__item[aria-current="page"] { border-top-color: transparent; }
  .v3-node__here { position: static; display: inline-block; margin-top: 6px; }
  .v3-row { padding: 16px 8px; }
  .v3-row__nm { font-size: 17px; }
  .v3-train { padding: 18px; }
  .v3-device { width: 100%; max-width: 250px; margin: 0 auto; }
  .v3-dcap { max-width: none; }
  .v3-drawer { max-width: none; }
  .v3-method { --v3-method-reserve: 260px; }
}

/* THE THREE SKINS, mapped from the per-model tokens. Each block sets the same
   seven role variables from a different family, so .v3-device and .v3-k need no
   per-model rules of their own and a fourth skin is one block. */
.v3-device[data-model="ti"] {
  --v3-dev-body: var(--v3-dev-ti-body);
  --v3-dev-name: var(--v3-dev-ti-name);
  --v3-dev-pad: var(--v3-dev-ti-pad);
  --v3-dev-bezel: var(--v3-dev-ti-bezel);
  --v3-dev-screen: var(--v3-dev-ti-screen);
  --v3-dev-key: var(--v3-dev-ti-key);
  --v3-dev-keyink: var(--v3-dev-ti-keyink);
  --v3-dev-accent: var(--v3-dev-ti-accent);
  --v3-dev-accent-ink: var(--v3-dev-ti-accentink);
  --v3-dev-enter: var(--v3-dev-ti-enter);
  --v3-dev-enter-ink: var(--v3-dev-ti-enterink);
}

.v3-device[data-model="casio"] {
  --v3-dev-body: var(--v3-dev-casio-body);
  --v3-dev-name: var(--v3-dev-casio-name);
  --v3-dev-bezel: var(--v3-dev-casio-bezel);
  --v3-dev-screen: var(--v3-dev-casio-screen);
  --v3-dev-key: var(--v3-dev-casio-key);
  --v3-dev-keyink: var(--v3-dev-casio-keyink);
  --v3-dev-accent: var(--v3-dev-casio-accent);
  --v3-dev-accent-ink: var(--v3-dev-casio-accentink);
  --v3-dev-enter: var(--v3-dev-casio-enter);
  --v3-dev-enter-ink: var(--v3-dev-casio-enterink);
  --v3-dev-fkey: var(--v3-dev-casio-fkey);
  --v3-dev-fkey-ink: var(--v3-dev-casio-fkeyink);
}

.v3-device[data-model="hp"] {
  --v3-dev-body: var(--v3-dev-hp-body);
  --v3-dev-name: var(--v3-dev-hp-name);
  --v3-dev-bezel: var(--v3-dev-hp-bezel);
  --v3-dev-screen: var(--v3-dev-hp-screen);
  --v3-dev-key: var(--v3-dev-hp-key);
  --v3-dev-keyink: var(--v3-dev-hp-keyink);
  --v3-dev-accent: var(--v3-dev-hp-accent);
  --v3-dev-accent-ink: var(--v3-dev-hp-accentink);
  --v3-dev-enter: var(--v3-dev-hp-enter);
  --v3-dev-enter-ink: var(--v3-dev-hp-enterink);
}

/* The device screen: a flat lit panel with one monospaced readout. The mockup
   draws a plotted curve in inline SVG; that is a picture, and a picture whose
   fills cannot resolve from tokens has no place on a v3 surface. What the skin
   has to carry is WHICH DEVICE THIS IS, and the body, the keys and their
   printed labels carry that. */
.v3-device__panel {
  background: var(--v3-dev-screen);
  min-height: 74px;
  padding: 10px 12px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11px;
  line-height: 1.7;
  color: var(--v3-dev-name);
}

/* ═══════════════════════════════════════════════════════════════════════════
   PHASE 4 · LE CONTROLE — the claret exam shell
   Built to docs/design/mockups/le-controle.html.

   CLARET THROUGHOUT, AND THE PRIMARY IS STILL INK. The mockup draws Next and
   Submit as a claret fill (`.btn.claret{background:#8C3B4E}`); AMENDMENT V3-4
   rules the primary is `--v3-btn` in ALL FOUR MODES, so the claret does the
   whole wayfinding job through the accent edge, the eyebrow, the contract
   band, the option keys and the state dot, and never the button. This is the
   same deviation RUN 78 recorded on Work, applied to the mode the amendment
   was NOT written for — which is the point of "one rule".

   NOTHING HERE INTRODUCES A COLOUR. Every value is a var(), which is what
   tests/test_v3_token_only.py asserts about this sheet.
   ═══════════════════════════════════════════════════════════════════════════ */

/* THE TIMER. Tabular figures and NO ANIMATION, ever. Phase C.7 removed a
   blinking low-time state from the v2 exam and scripts/drive_le_controle.py
   exists to prove the blink stays gone; a v3 timer that re-introduced it would
   be the same defect on a new surface. `--v3-ink` at every remaining time:
   an exam clock that changes colour as it runs is telling a student to panic,
   which is the opposite of what the calm voice is for. */
.v3-timer {
  font-variant-numeric: tabular-nums;
  font-size: 16px;
  font-weight: 600;
  letter-spacing: .5px;
  color: var(--v3-ink);
  animation: none;
}

/* THE STRUCTURAL CONTRACT. One line, stated plainly, visible for the whole
   paper. It is the only claret-tinted band on the screen and it is not a card:
   Law C3 permits exactly one tinted fill, --v3-signature-wash, behind the one
   focus card, so this uses the mode CHIP token, which is the pre-composited
   opaque pair RUN 78 solved for and measured. */
.v3-contract {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 18px;
  align-items: baseline;
  justify-content: space-between;
  margin: 0 0 22px;
  padding: 9px 14px;
  border-radius: 9px;
  background: var(--v3-mode-controle-chip);
  color: var(--v3-mode-controle-ink);
  font-size: 12.5px;
  font-weight: 500;
}

.v3-contract__paper { font-weight: 600; }

/* THE PAPER CHOICE at the lobby. Two options, and they are not co-equal tiles
   in a grid: a two-row list, because the difference between them is a sentence
   and not a picture. */
.v3-papers {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 4px 0 4px;
}

.v3-paper {
  display: block;
  width: 100%;
  text-align: left;
  border: 1px solid var(--v3-hair);
  border-radius: 10px;
  background: var(--v3-card);
  padding: 13px 16px;
  cursor: pointer;
  font: inherit;
  color: var(--v3-ink);
}

.v3-paper[aria-pressed="true"] {
  border-color: var(--v3-mode-controle);
  box-shadow: inset 0 0 0 1px var(--v3-mode-controle);
}

.v3-paper__nm { font-size: 14.5px; font-weight: 600; }

.v3-paper__d {
  display: block;
  margin-top: 3px;
  font-size: 12.5px;
  color: var(--v3-ink2);
}

.v3-paper:focus-visible {
  outline: 2px solid var(--v3-mode-controle);
  outline-offset: 2px;
}

/* THE GUARD. The one place the product asks twice, and it names the
   consequence instead of asking "are you sure" — which the voice standard
   bans outright as a reflex. It is a CARD in the card language, with the
   claret accent edge, so the two-step is a considered screen and not a
   browser confirm() the student dismisses without reading. */
/* THE BODY VOICE ON A GUARD, AND IT IS NOT SERIF. The card standard puts
   EB Garamond on hero words only (Law K4) and the body zone on Inter 15px.
   The guard's consequence sentence is body copy — it is the most important
   functional sentence in the product, and setting it in the hero face made it
   read as a title with a second title above it. `.v3-lede` is the serif
   opening line a screen may carry ONE of; a two-sentence statement of what a
   button is about to do is not that. */
.v3-say {
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--v3-ink);
  margin: 0;
  max-width: 46ch;
}

.v3-guard__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
  margin-top: 18px;
}

/* THE RESULT ROWS. The same anatomy as the session-end "what moved" card:
   a label, a tabular figure, one hairline between. */
.v3-resrow {
  display: flex;
  justify-content: space-between;
  gap: 18px;
  padding: 11px 0;
  border-bottom: 1px solid var(--v3-hair);
  font-size: 14px;
}

.v3-resrow:last-of-type { border-bottom: none; }

.v3-resrow__v {
  font-variant-numeric: tabular-nums;
  color: var(--v3-ink2);
  text-align: right;
}

/* REVIEW. One question per block, in the reveal's own step language, reached
   only after the paper is submitted. */
.v3-review { margin-top: 4px; }

.v3-review__q {
  padding: 18px 0;
  border-bottom: 1px solid var(--v3-hair);
}

.v3-review__q:last-child { border-bottom: none; }

.v3-review__h {
  display: flex;
  justify-content: space-between;
  gap: 14px;
  align-items: baseline;
  margin-bottom: 8px;
}

.v3-review__no {
  font-family: var(--v3-sans, system-ui, sans-serif);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 1.8px;
  text-transform: uppercase;
  color: var(--v3-mode-controle-ink);
}

.v3-review__marks {
  font-size: 12.5px;
  font-variant-numeric: tabular-nums;
  color: var(--v3-ink2);
}

/* THE PAPER'S OWN NAVIGATION. Previous is a quiet text control on the left and
   the one loud primary stays on the right, which is where every other v3
   footer puts it. */
.v3-footer__inner--paper { justify-content: space-between; }

/* TIME IS UP, AND NOTHING WAS SUBMITTED FOR THE STUDENT. The clock reaching
   zero closes the paper to further answers and says so; it does not press the
   button. See ui/v3/controle.html for why that is a lifecycle rule and not a
   preference. */
.v3-timesup {
  margin: 0 0 18px;
  padding: 11px 14px;
  border-radius: 9px;
  border: 1px solid var(--v3-hair);
  background: var(--v3-card);
  font-size: 13.5px;
  color: var(--v3-ink);
}

/* ═══════════════════════════════════════════════════════════════════════════
   PHASE 4 · THE FIRST-VISIT FUNNEL — landing, anonymous diagnostic, reveal
   Built to docs/design/mockups/funnel.html.

   THE PUBLIC FACE IS LIGHT, ALWAYS. cx_prefs.js's allow-list ruling (founder,
   2026-08-06): dark mode is a signed-in personalisation, and a visitor with no
   session renders light on every surface. These two screens are deliberately
   absent from APP_SURFACES for that reason, so nothing here needs a dark
   branch of its own — the tokens flip, the pages do not.
   ═══════════════════════════════════════════════════════════════════════════ */

/* THE ANONYMOUS SHELL. The wordmark, and an aside the page fills with its own
   honest progress. No destinations, because a visitor with no account cannot
   open any of them, and no Exit, because there is no Today to exit to yet. */
.v3-nav--anon .v3-nav__aside {
  margin-left: auto;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--v3-ink2);
}

/* THE LANDING. One promise, one action. The wordmark sits IN the hero rather
   than in a bar, exactly as the mockup draws it, because a bar with nothing in
   it is chrome that names nothing. */
.v3-land {
  max-width: 620px;
  margin: 0 auto;
  padding: 64px 22px 72px;
  text-align: center;
}

.v3-land__wm {
  font-family: var(--display, Cinzel, Georgia, serif);
  font-weight: 600;
  font-size: 13px;
  letter-spacing: 2.4px;
  color: var(--v3-ink);
}

.v3-land__eyebrow {
  margin: 34px 0 0;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 1.8px;
  text-transform: uppercase;
  color: var(--v3-signature-ink);
}

.v3-land__hero {
  font-family: var(--serif, "EB Garamond", Georgia, serif);
  font-weight: 500;
  font-size: 32px;
  line-height: 1.18;
  color: var(--v3-ink);
  margin: 14px auto 12px;
  max-width: 460px;
}

.v3-land__sub {
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--v3-ink2);
  max-width: 430px;
  margin: 0 auto 26px;
}

.v3-land__fine {
  margin: 16px 0 0;
  font-size: 12px;
  color: var(--v3-ink2);
}

/* THE WAY BACK OUT OF THE PUBLIC FUNNEL (RUN 124, static/js/cx_funnel_home.js).
   Rendered only for a signed-in student, on /v3/start and /v3/diagnostic,
   which until this run offered one exit between them and it led deeper in.

   IT IS SIZED BY tests/test_v3_a11y.py, NOT BY EYE. The first version was a
   bare anchor inside the fine print and measured 120x15, which that gate
   failed on all four surfaces (both pages, both themes) for the 44px minimum
   target. A quiet link is still a target, and a student on a phone who cannot
   reliably hit the only way home is in the same trap with extra steps. */
.cx-funnel-home {
  margin-top: 20px;
}

a#cx-funnel-home {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 12px;
  color: var(--v3-ink2);
}

/* THE QUIET WAY IN, FROM THE PUBLIC LANDING (RUN 125, ui/v3/start.html).
   Same reasoning and the same shape as the control above: it shipped first as
   a link inside a sentence and measured 62x15, which tests/test_v3_a11y.py
   failed on both themes. A second loud button is not wanted here — the one
   loud action is "Begin" — so this is a quiet control that is still a real
   target. */
.v3-land__in {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 12px;
  margin-top: 4px;
  font-size: 12px;
  color: var(--v3-ink2);
}

@media (max-width: 720px) {
  .v3-land { padding: 42px 18px 56px; }
  .v3-land__hero { font-size: 27px; }
}

/* THE DIAGNOSIS. Two groups, each a plain list with one mark per row. The
   marks are the progress page's own state vocabulary: a filled signature dot
   for secure, an amber RING for needs-work. Law C1 is why the amber is a ring
   and never the label. */
.v3-diag__grp { margin-bottom: 16px; }

.v3-diag__h {
  margin: 0 0 8px;
  font-family: var(--v3-sans, system-ui, sans-serif);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--v3-ink2);
}

.v3-diag__row {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 7px 0;
  font-size: 14.5px;
  color: var(--v3-ink);
}

.v3-diag__mark {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  flex: none;
}

.v3-diag__row[data-band="secure"] .v3-diag__mark {
  background: var(--v3-signature);
}

.v3-diag__row[data-band="needs"] .v3-diag__mark {
  background: transparent;
  border: 2px solid var(--v3-mode-drill);
}

/* THE ACCOUNT ASK, WHICH ARRIVES AFTER THE PAYOFF AND NEVER BEFORE.
   It is a card with the signature accent edge, sitting BELOW the diagnosis
   the visitor already has. Nothing about it is a wall: the result is on
   screen, it stays on screen, and the way out is a plain quiet link. */
.v3-ask {
  /* THE SAME READING COLUMN AS THE CARD ABOVE IT. Appended to the screen
     rather than to the card, it took the screen's full 980px and sat visibly
     wider than the diagnosis it was asking about, which read as a banner under
     the content instead of the last card in one column. */
  max-width: 620px;
  margin-top: 22px;
  background: var(--v3-card);
  border: 1px solid var(--v3-hair);
  border-radius: 13px;
  padding: 20px 22px;
  position: relative;
}

.v3-ask::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 3px;
  border-radius: 13px 0 0 13px;
  background: var(--v3-signature);
}

.v3-ask__t {
  font-family: var(--serif, "EB Garamond", Georgia, serif);
  font-weight: 500;
  font-size: 19px;
  color: var(--v3-ink);
  margin: 0 0 5px;
}

.v3-ask__d {
  font-size: 13px;
  line-height: 1.5;
  color: var(--v3-ink2);
  margin: 0 0 16px;
}

.v3-ask__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px;
}

/* ═══════════════════════════════════════════════════════════════════════════
   PHASE 4 · THE ACCOUNT SURFACE — the deep room behind the You drawer
   Built to docs/design/mockups/you-account.html.

   TWO PANES ON DESKTOP, TOP TABS ON MOBILE. The rail is a SECTION INDEX for
   this one page, not a second product nav: every entry is a same-page anchor,
   it offers no destination, and the shell's own nav is still the single
   component that says where you are in the product. Law N1 forbids a second
   NAV — a second set of destinations, or a sidebar standing in for the top bar
   — and this is a table of contents. The distinction is asserted in
   tests/test_v3_account.py rather than left to a reader's judgement.

   NO ACCENT EDGE ON THIS SCREEN, and that is Law K2 read correctly. The edge
   belongs on "the one card that owns the primary action"; a settings surface
   has no primary action, it has thirty small ones. Zero is the honest count.
   ═══════════════════════════════════════════════════════════════════════════ */

.v3-acct { max-width: 1040px; }

.v3-acct__panes {
  display: grid;
  grid-template-columns: 226px 1fr;
  gap: 34px;
  align-items: start;
}

.v3-acct__rail {
  position: sticky;
  top: 78px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

/* THE ACCOUNT RAIL'S ENTRIES were 226 x 39 — five pixels under, on every one
   of the seven sections of the account document. Found by RUN 81 Unit 6 only
   AFTER the shell's four controls were fixed: the nav findings appeared on
   every route and these on one, so they sat behind them in the report.

   That is worth recording as a property of sweeps rather than as a note about
   this rule: a defect on every screen hides a defect on one, and the second
   pass of a sweep is not a formality. */
.v3-acct__link {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 12px;
  min-height: 44px;
  border-radius: 9px;
  font-size: 13.5px;
  font-weight: 500;
  color: var(--v3-ink2);
  text-decoration: none;
  white-space: nowrap;
}

.v3-acct__link:hover { background: var(--v3-card); color: var(--v3-ink); }

.v3-acct__link[aria-current="true"] {
  background: var(--v3-signature-wash);
  color: var(--v3-signature-ink);
  font-weight: 600;
}

.v3-acct__dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--v3-hair);
  flex: none;
}

.v3-acct__link[aria-current="true"] .v3-acct__dot {
  background: var(--v3-signature);
}

.v3-acct__link:focus-visible {
  outline: 2px solid var(--v3-signature);
  outline-offset: 2px;
}

@media (max-width: 860px) {
  .v3-acct__panes { grid-template-columns: 1fr; gap: 16px; }
  .v3-acct__rail {
    position: static;
    flex-direction: row;
    overflow-x: auto;
    gap: 4px;
    padding-bottom: 6px;
  }
}

.v3-acct__content { min-width: 0; }

/* A GROUP IS A CARD, in the shared skeleton: --card, one hairline, radius 14,
   no shadow (there is no focus card here). */
.v3-grpcard {
  background: var(--v3-card);
  border: 1px solid var(--v3-hair);
  border-radius: 14px;
  margin-bottom: 18px;
  overflow: hidden;
  scroll-margin-top: 84px;
}

.v3-grpcard__h { padding: 18px 22px 6px; }

.v3-grpcard__eyebrow {
  font-family: var(--display, Cinzel, Georgia, serif);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--v3-signature-ink);
  margin: 0;
}

.v3-grpcard__t {
  font-family: var(--serif, "EB Garamond", Georgia, serif);
  font-weight: 500;
  font-size: 20px;
  color: var(--v3-ink);
  margin: 6px 0 2px;
}

.v3-grpcard__d {
  font-size: 12.5px;
  color: var(--v3-ink2);
  margin: 0 0 8px;
}

/* ONE ROW, ONE SETTING. Label and hint left, value and control right; the
   whole row becomes a column while it is being edited. */
.v3-arow {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 13px 22px;
  border-top: 1px solid var(--v3-hair);
}

.v3-arow__l { flex: 1; min-width: 0; }

.v3-arow__k { font-size: 13.5px; color: var(--v3-ink); font-weight: 500; }

.v3-arow__h {
  font-size: 11.5px;
  color: var(--v3-ink2);
  margin-top: 2px;
  line-height: 1.4;
}

.v3-arow__v {
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 13.5px;
  color: var(--v3-ink2);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* EDIT IN PLACE, NEVER A MODAL. The row itself becomes the editor: the field
   opens where the value was, and Save and Cancel sit under it. Nothing is
   overlaid, nothing is dismissed by clicking away, and the surrounding rows
   stay readable — which is the whole reason a settings surface should not use
   a dialog to change one string. */
.v3-arow[data-editing="true"] {
  flex-direction: column;
  align-items: stretch;
  gap: 10px;
  background: var(--v3-bg);
}

.v3-arow[data-editing="true"] .v3-arow__k {
  font-size: 12px;
  color: var(--v3-ink2);
  font-weight: 600;
}

.v3-afield {
  width: 100%;
  max-width: 340px;
  border: 1px solid var(--v3-hair);
  border-radius: 9px;
  padding: 10px 12px;
  font: inherit;
  font-size: 14px;
  background: var(--v3-card);
  color: var(--v3-ink);
}

.v3-afield:focus-visible {
  outline: none;
  border-color: var(--v3-signature);
  box-shadow: 0 0 0 3px var(--v3-signature-wash);
}

.v3-aedit {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
}

.v3-alink {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--v3-signature-ink);
  background: none;
  border: none;
  padding: 0;
  font-family: inherit;
  cursor: pointer;
  text-decoration: none;
}

.v3-alink:hover { text-decoration: underline; text-underline-offset: 3px; }

.v3-alink:focus-visible {
  outline: 2px solid var(--v3-signature);
  outline-offset: 3px;
  border-radius: 4px;
}

/* THE SAVED CONFIRMATION. One quiet line, stating what happened, in the row
   it happened in. No toast, no animation, no tick that fades before it is
   read. */
.v3-asaved {
  font-size: 11.5px;
  font-weight: 600;
  color: var(--v3-signature-ink);
}

.v3-asaved[data-state="error"] { color: var(--v3-ink); font-weight: 500; }

/* A CONTROL WE HAVE NOT BUILT SAYS SO, and cannot be pressed. A disabled
   control with a stated reason is honest; a live one that does nothing is not.
   Every instance is on a printed allow-list in tests/test_v3_account.py. */
/* THE ACCOUNT PILLS — 35px, and the sixth and last layer of sub-44px control
   this run uncovered. `.v3-apill--danger` is among them, which is the delete
   control on the account document: the one button on any v3 surface whose
   consequence cannot be undone, and it was nine pixels short.

   AFTER THIS ONE, EVERY INTERACTIVE CLASS ON EVERY v3 SURFACE CARRIES THE
   RULE — enumerated rather than hoped for, by reading each class used on a
   button or an anchor in ui/v3/*.html back against its declaration here. Six
   browser passes found six layers one at a time, because each fix removed the
   noise hiding the next; one static enumeration found the last of them
   directly. Both are worth having, and neither replaces the other: the
   enumeration cannot see a rule that loses the cascade, and the browser
   cannot see a class nothing on the page happens to render today. */
.v3-apill {
  font-size: 12.5px;
  font-weight: 600;
  padding: 9px 16px;
  min-height: 44px;
  border-radius: 9px;
  border: 1px solid var(--v3-hair);
  background: var(--v3-card);
  color: var(--v3-ink);
  font-family: inherit;
  cursor: pointer;
}

.v3-apill:hover:not(:disabled) { border-color: var(--v3-ink2); }

.v3-apill:disabled { color: var(--v3-ink2); cursor: default; }

.v3-apill:focus-visible {
  outline: 2px solid var(--v3-signature);
  outline-offset: 2px;
}

.v3-apills {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  padding: 14px 22px;
}

.v3-anote {
  padding: 0 22px 14px;
  font-size: 11.5px;
  color: var(--v3-ink2);
  line-height: 1.5;
  max-width: 560px;
}

.v3-afoot {
  padding: 16px 22px;
  border-top: 1px solid var(--v3-hair);
  font-size: 11.5px;
  color: var(--v3-ink2);
  text-align: center;
}

/* THE DANGER PILL. Claret on the border and the label only — never a fill, and
   never the amber. The one irreversible control on the account surface says so
   without shouting; Law C3 forbids the mode colour as a block fill. */
.v3-apill--danger {
  border-color: var(--v3-mode-controle);
  color: var(--v3-mode-controle-ink);
}

.v3-apill--danger:disabled { color: var(--v3-ink2); border-color: var(--v3-hair); }

/* THE TYPED-CONFIRMATION PANEL. In place, in the flow, never overlaid. */
.v3-delguard {
  margin: 0 22px 16px;
  padding: 16px 18px;
  border: 1px solid var(--v3-hair);
  border-radius: 11px;
  background: var(--v3-bg);
  display: flex;
  flex-direction: column;
  gap: 12px;
  max-width: 520px;
}

/* THE RECORD, IN ITS OWN SCROLL CONTAINER. A student's whole answer history is
   long and wide; the page must not grow a horizontal scrollbar around it, so
   the box scrolls and the page does not. */
.v3-record {
  margin: 0 22px 14px;
  padding: 14px 16px;
  border: 1px solid var(--v3-hair);
  border-radius: 11px;
  background: var(--v3-bg);
}

.v3-record__h {
  margin: 0 0 8px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--v3-ink);
}

.v3-record__body {
  margin: 0 0 10px;
  max-height: 320px;
  overflow: auto;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11.5px;
  line-height: 1.55;
  color: var(--v3-ink2);
  white-space: pre;
}

/* ═══════════════════════════════════════════════════════════════════════════
   UNIT 3 · THE ERROR STATE — one component, three archetypes.
   config/error_states.json is the contract; static/js/cx_error_v3.js renders.

   OFF SATURATED RED, and that is a ruling rather than a taste. The locked
   standard says "three calm in-voice archetypes, off saturated red": almost
   nothing here is an emergency, and a product that alarms at a dropped request
   has nothing left to say when something genuinely goes wrong. The state reads
   in --v3-ink like everything else, and its one quiet mark is the claret hair
   on the left — the same claret the one irreversible screen uses, at 3px, on a
   surface a student was not expecting.
   ═══════════════════════════════════════════════════════════════════════════ */
.v3-errstate {
  max-width: 46ch;
  padding: 16px 18px;
  border: 1px solid var(--v3-hair);
  border-left: 3px solid var(--v3-mode-controle);
  border-radius: 11px;
  background: var(--v3-card);
}

.v3-errstate__t {
  margin: 0 0 6px;
  font-size: 15px;
  font-weight: 600;
  color: var(--v3-ink);
}

/* THE DATA-SAFETY CLAUSE. The most frightening thing about an error is not
   knowing what happened to your work, and it is the one thing a student
   cannot find out for themselves. */
.v3-errstate__safety {
  margin: 0 0 12px;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--v3-ink2);
}

/* ═══════════════════════════════════════════════════════════════════════════
   UNIT 4 · THE EMPTY STATE — designed as content, not as an absence.
   config/empty_states.json is the contract; static/js/cx_empty_v3.js renders.

   NO EDGE, NO SHADOW, NO ACCENT. An empty state is not the focus card: Law K2
   gives a screen exactly one accent edge, and a screen whose only card is the
   one saying "there is nothing here" would spend it on an absence. It reads as
   quiet body copy inside a hairline, which is what it is.

   THE THIRD PARAGRAPH IS THE ONE THAT MATTERS. `__next` carries what would
   fill the state, and on the read-only surfaces it is the ONLY way out —
   Progress carries no button by founder ruling, so the sentence is the
   affordance. It is styled as text and not as a hint: --ink2, full size, no
   italic, because a student who cannot act needs to be able to read it.
   ═══════════════════════════════════════════════════════════════════════════ */
.v3-empty {
  max-width: 46ch;
  padding: 16px 18px;
  border: 1px solid var(--v3-hair);
  border-radius: 11px;
  background: var(--v3-card);
}

.v3-empty__t {
  margin: 0 0 6px;
  font-size: 15px;
  font-weight: 600;
  color: var(--v3-ink);
}

.v3-empty__b {
  margin: 0 0 8px;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--v3-ink2);
}

.v3-empty__next {
  margin: 0;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--v3-ink2);
}

/* The action, when there is one, sits below the sentence rather than beside
   the title: the reading comes first, the offer second. */
.v3-empty [data-empty-action] {
  margin-top: 14px;
}

/* ═══════════════════════════════════════════════════════════════════════════
   UNIT 9 · REDUCED MOTION MEANS INSTANT.

   THE v3 LAYER HAD NO BLOCK AT ALL. codex-components.css carries three; this
   sheet is the v3 layer's own and it never grew one, so a student who had set
   the preference got every v3 transition anyway. Nothing had switched off —
   the rule simply never arrived with the new sheet.

   IT IS BLANKET, AND THAT IS THE RULE RATHER THAN AN ECONOMY. A sheet that
   honours the query for its own keyframes and leaves a transition on a hover
   state has not honoured it, and the student who set the preference is the
   one who finds out. `*` and both pseudo-elements, every duration to ZERO —
   not to 0.01s, which is a number written to satisfy a linter.

   `scroll-behavior: auto` is here for the same reason: a smooth scroll is
   motion, and it is the one that most reliably makes people ill.

   MEASURED, NOT ASSERTED. scripts/drive_v3_a11y.py emulates the query at the
   browser and reads every resolved duration back off every v3 surface.
   ═══════════════════════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    transition-duration: 0s !important;
    transition-delay: 0s !important;
    animation-duration: 0s !important;
    animation-delay: 0s !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   UNIT 6 · THE SEGMENTED CONTROLS, LAST IN THE SHEET ON PURPOSE.

   `.v3-seg button` is declared twice in this file, and the second declaration
   is 1650 lines after the first. Putting the 44px minimum with the other
   target rules near the top meant it lost to the sheet's own later rule on
   nothing but source order — same specificity, later wins — and the browser
   went on resolving 34px while the rule sat there looking applied.

   That is why every one of these numbers is measured on a RENDERED page and
   not read out of this file. A gate that parsed the stylesheet would have
   found `min-height: 44px` and reported a pass.
   ═══════════════════════════════════════════════════════════════════════════ */
.v3-seg button {
  min-width: 44px;
  min-height: 44px;
}

/* ══════════════════════════════════════════════════════════════════════════
   THE APP-SPEC LAYER (RUN 100) — design-system/APP-SPEC.html, in tokens.
   ══════════════════════════════════════════════════════════════════════════

   WHAT THIS IS. The founder locked a tightened "Old Money" direction as a
   single self-contained page. That page is the pixel reference; this block is
   the same design expressed against the --v3-* palette, because a v3 surface
   may not carry a colour literal (tests/test_v3_standard.py gate 1) and
   because the app has to theme, which a one-page mock does not.

   THE PREFIX IS `cxs-` AND THE OLD `v3-` COMPONENTS ARE LEFT STANDING. The
   session, controle, account, method and gdc screens still consume them, and
   they now wear the new palette for free — they read the same tokens. A
   screen is moved onto this layer by rewriting its markup, one commit at a
   time, so a half-migrated product is never a half-broken one.

   THREE PLACES THIS DEPARTS FROM THE SPEC, ALL OF THEM MEASURED, ALL OF THEM
   BECAUSE THE SPEC PUTS A MARK-WEIGHT COLOUR ON TEXT A STUDENT READS:
     · the meta zone takes --v3-ink3, not the spec's --muted   (3.06 -> 4.52:1)
     · .cxs-todo's numeral takes --v3-ink2, keeping the brass RING (3.12:1)
     · the "/7"-style suffix and the crumb separator take --v3-ink3, not
       --faint (2.13:1)
   Every other value here is the spec's.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── shared scales. Non-colour, so they are the spec's verbatim. ────────── */
:root {
  --cxs-sp-1:4px;  --cxs-sp-2:8px;  --cxs-sp-3:12px; --cxs-sp-4:16px;
  --cxs-sp-5:20px; --cxs-sp-6:24px; --cxs-sp-7:32px; --cxs-sp-8:40px;
  --cxs-sp-9:48px; --cxs-sp-10:64px; --cxs-sp-12:96px;
  --cxs-r-xs:6px; --cxs-r-sm:8px; --cxs-r-md:10px; --cxs-r-lg:12px;
  --cxs-r-pill:999px;
  /* Standard v1 §2: "full-width main (content max ~1220-1280,
     generous padding). Never a narrow centred column." 1080px was
     right for a page with a top bar and no sidebar; it is not the
     Standard's measure. (RUN 114 UNIT 1) */
  --cxs-edge:1280px;
}

/* ── type roles ────────────────────────────────────────────────────────── */
.cxs-serif { font-family: var(--v3-serif, Georgia, serif); }
.cxs-tnum  { font-feature-settings:"tnum" 1; font-variant-numeric: tabular-nums; }

.cxs-eyebrow {
  font-size: 11px; font-weight: 600; letter-spacing: .15em;
  text-transform: uppercase; color: var(--v3-ink3); margin: 0;
}
.cxs-micro {
  font-size: 10.5px; font-weight: 600; letter-spacing: .16em;
  text-transform: uppercase; color: var(--v3-ink3);
}
.cxs-h1 {
  font-family: var(--v3-serif, Georgia, serif); font-weight: 500;
  font-size: clamp(28px, 4.2vw, 40px); line-height: 1.1; letter-spacing: -.02em;
  margin: 0; color: var(--v3-ink);
}
.cxs-h1 .cxs-accent { color: var(--v3-signature); font-style: italic; }
.cxs-lead {
  color: var(--v3-ink2); font-size: 15.5px; line-height: 1.55;
  margin-top: var(--cxs-sp-3); max-width: 52ch;
}
.cxs-pagehead { margin-bottom: var(--cxs-sp-8); max-width: 34ch; }
.cxs-pagehead .cxs-eyebrow { margin-bottom: var(--cxs-sp-4); }

/* ── primitives ────────────────────────────────────────────────────────── */
.cxs-card {
  background: var(--v3-card); border: 1px solid var(--v3-hair);
  border-radius: var(--cxs-r-md); box-shadow: var(--v3-shadow-sm);
}
.cxs-hairline { height: 1px; background: var(--v3-hair); border: 0; margin: 0; }

.cxs-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  text-decoration: none;
  font-family: var(--v3-sans, system-ui, sans-serif);
  font-size: 14px; font-weight: 500; letter-spacing: .005em;
  /* 44px minimum target — the spec's 11px padding on a 14px/1.55 line lands
     just under it, and tests/test_v3_a11y.py measures the rendered box. */
  padding: 11px 20px; min-height: 44px;
  border-radius: var(--cxs-r-sm); border: 1px solid transparent;
  transition: background .2s cubic-bezier(.2, 0, .2, 1), transform .1s cubic-bezier(.2, 0, .2, 1),
              box-shadow .2s cubic-bezier(.2, 0, .2, 1), border-color .2s cubic-bezier(.2, 0, .2, 1),
              color .2s cubic-bezier(.2, 0, .2, 1);
}
.cxs-btn--primary {
  background: var(--v3-btn); color: var(--v3-btntext);
  box-shadow: var(--v3-shadow-sm);
}
.cxs-btn--primary:hover { background: var(--v3-accent-hover); box-shadow: var(--v3-shadow); }
.cxs-btn--primary:active { transform: translateY(1px); box-shadow: var(--v3-shadow-sm); }
.cxs-btn--primary svg { width: 15px; height: 15px; transition: transform .2s cubic-bezier(.2, 0, .2, 1); }
.cxs-btn--primary:hover svg { transform: translateX(2px); }
.cxs-btn--ghost {
  background: transparent; border-color: var(--v3-hair2); color: var(--v3-ink);
}
.cxs-btn--ghost:hover { background: var(--v3-chip); border-color: var(--v3-ink3); }
.cxs-btn--ghost:active { transform: translateY(1px); }

.cxs-badge {
  font-size: 10px; font-weight: 600; letter-spacing: .15em; text-transform: uppercase;
  color: var(--v3-signature);
  background: color-mix(in srgb, var(--v3-signature) 12%, var(--v3-card));
  border: 1px solid color-mix(in srgb, var(--v3-signature) 24%, transparent);
  padding: 3px 10px; border-radius: var(--cxs-r-pill);
}

/* the mastery / readiness bar. A TRACK plus a FILL, and the fill's state is
   carried by a class as well as by colour. */
.cxs-bar {
  height: 5px; border-radius: var(--cxs-r-pill);
  background: var(--v3-surface2); overflow: hidden; position: relative; display: block;
}
.cxs-bar > i {
  display: block; height: 100%; border-radius: var(--cxs-r-pill);
  background: var(--v3-signature); transform-origin: left center;
  animation: cxs-barin .22s cubic-bezier(.2, 0, .2, 1) both;
}
@keyframes cxs-barin { from { transform: scaleX(0); } to { transform: scaleX(1); } }
.cxs-bar > i.lo  { background: var(--v3-mode-controle); }
.cxs-bar > i.mid { background: var(--v3-mode-drill); }
.cxs-bar > i.hi  { background: var(--v3-mode-study); }

/* progress dots */
.cxs-dots { display: flex; gap: 5px; }
.cxs-dots i {
  width: 22px; height: 4px; border-radius: var(--cxs-r-pill);
  background: var(--v3-surface2); display: block;
  transition: background .2s cubic-bezier(.2, 0, .2, 1);
}
.cxs-dots i.on  { background: var(--v3-signature); }
.cxs-dots i.cur { background: var(--v3-brass); }

/* ── the shell: one topbar, the only header on a v3 page ────────────────── */
.cxs-topbar {
  position: sticky; top: 0; z-index: 50;
  background: color-mix(in srgb, var(--v3-bg) 86%, transparent);
  backdrop-filter: saturate(1.4) blur(12px);
  -webkit-backdrop-filter: saturate(1.4) blur(12px);
  border-bottom: 1px solid var(--v3-hair);
}
.cxs-topbar__inner {
  max-width: var(--cxs-edge); margin: 0 auto; padding: 0 var(--cxs-sp-7);
  height: 62px; display: flex; align-items: center; gap: var(--cxs-sp-6);
}
.cxs-brand {
  display: flex; align-items: center; gap: 11px;
  text-decoration: none; color: var(--v3-ink); border-radius: var(--cxs-r-xs);
}
.cxs-crest { width: 30px; height: 30px; flex: none; display: block; }
.cxs-crest .o { stroke: var(--v3-brass); }
.cxs-crest .i { stroke: var(--v3-signature); }
.cxs-crest .c { fill: var(--v3-signature); }
.cxs-nav { display: flex; gap: 2px; margin-left: var(--cxs-sp-2); }
.cxs-nav a {
  background: none; border: 0; color: var(--v3-ink2); text-decoration: none;
  font-size: 13.5px; font-weight: 500; letter-spacing: .005em;
  padding: 8px 13px; border-radius: var(--cxs-r-xs); position: relative;
  display: inline-flex; align-items: center; min-height: 44px;
  transition: color .2s cubic-bezier(.2, 0, .2, 1), background .2s cubic-bezier(.2, 0, .2, 1);
}
.cxs-nav a:hover { color: var(--v3-ink); background: color-mix(in srgb, var(--v3-surface2) 55%, transparent); }
.cxs-nav a[aria-current="page"] { color: var(--v3-ink); }
.cxs-nav a[aria-current="page"]::after {
  content: ""; position: absolute; left: 13px; right: 13px; bottom: 9px;
  height: 2px; background: var(--v3-signature); border-radius: 2px 2px 0 0;
}
.cxs-right { margin-left: auto; display: flex; align-items: center; gap: 12px; }
.cxs-tgl {
  width: 44px; height: 44px; border-radius: var(--cxs-r-sm);
  border: 1px solid var(--v3-hair); background: var(--v3-card); color: var(--v3-ink2);
  display: grid; place-items: center;
  transition: border-color .2s cubic-bezier(.2, 0, .2, 1), color .2s cubic-bezier(.2, 0, .2, 1),
              background .2s cubic-bezier(.2, 0, .2, 1), transform .1s cubic-bezier(.2, 0, .2, 1);
}
.cxs-tgl:hover { color: var(--v3-ink); border-color: var(--v3-hair2); background: var(--v3-chip); }
.cxs-tgl:active { transform: translateY(1px); }
.cxs-tgl svg { width: 16px; height: 16px; display: block; }
.cxs-chip {
  width: 44px; height: 44px; border-radius: 50%;
  border: 1px solid var(--v3-brass-soft);
  background: radial-gradient(120% 120% at 30% 22%,
              color-mix(in srgb, var(--v3-brass) 30%, var(--v3-card)) 0%,
              var(--v3-card) 62%);
  color: var(--v3-ink); text-decoration: none;
  display: grid; place-items: center;
  font-family: var(--v3-serif, Georgia, serif);
  font-size: 13px; font-weight: 600; letter-spacing: .02em;
  box-shadow: var(--v3-shadow-sm);
}

.cxs-main {
  max-width: var(--cxs-edge); margin: 0 auto;
  padding: var(--cxs-sp-9) var(--cxs-sp-7) var(--cxs-sp-12);
}

/* ── HOME ──────────────────────────────────────────────────────────────── */
.cxs-home { display: grid; grid-template-columns: 1.7fr 1fr; gap: var(--cxs-sp-6); align-items: stretch; }

.cxs-focus {
  padding: var(--cxs-sp-7) var(--cxs-sp-7) var(--cxs-sp-6);
  position: relative; overflow: hidden;
}
.cxs-focus::before {
  content: ""; position: absolute; inset: 0 auto 0 0; width: 3px;
  background: var(--v3-signature); opacity: .9;
}
.cxs-focus__kicker { display: flex; align-items: center; gap: var(--cxs-sp-3); margin-bottom: var(--cxs-sp-5); flex-wrap: wrap; }
.cxs-focus h2 {
  font-family: var(--v3-serif, Georgia, serif); font-weight: 500;
  font-size: 24px; line-height: 1.16; letter-spacing: -.015em;
  margin: 0 0 var(--cxs-sp-3); color: var(--v3-ink);
}
.cxs-focus__desc { color: var(--v3-ink2); font-size: 14.5px; line-height: 1.55; max-width: 46ch; margin: 0; }
.cxs-focus__meta { display: flex; flex-wrap: wrap; gap: var(--cxs-sp-3) var(--cxs-sp-6); margin: var(--cxs-sp-6) 0; }
.cxs-metaitem { display: flex; flex-direction: column; gap: 3px; }
.cxs-metaitem .k { font-size: 11px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; color: var(--v3-ink3); }
.cxs-metaitem .v { font-size: 14.5px; font-weight: 500; color: var(--v3-ink); }
.cxs-cta { display: flex; align-items: center; gap: var(--cxs-sp-5); flex-wrap: wrap; }
.cxs-reason { font-size: 12.5px; color: var(--v3-ink3); max-width: 38ch; line-height: 1.5; margin: 0; }

.cxs-grade { padding: var(--cxs-sp-6); display: flex; flex-direction: column; }
.cxs-grade .cxs-micro { margin-bottom: var(--cxs-sp-4); }
.cxs-grade__big {
  font-family: var(--v3-serif, Georgia, serif); font-weight: 500;
  font-size: 56px; line-height: .88; letter-spacing: -.025em; color: var(--v3-ink);
  display: flex; align-items: baseline; gap: 8px;
}
.cxs-grade__big .of { font-size: 20px; color: var(--v3-ink3); font-weight: 400; }
.cxs-trend { display: inline-flex; align-items: center; gap: 7px; margin-top: var(--cxs-sp-4); font-size: 12.5px; color: var(--v3-mode-study); font-weight: 500; }
.cxs-trend .pill {
  background: var(--v3-mode-study-chip); color: var(--v3-mode-study-ink);
  border-radius: var(--cxs-r-pill); padding: 3px 9px; font-size: 11px;
  font-weight: 600; letter-spacing: .02em; display: inline-flex; align-items: center; gap: 5px;
}
.cxs-grade__note { margin-top: auto; padding-top: var(--cxs-sp-5); font-size: 12px; color: var(--v3-ink3); line-height: 1.5; }

.cxs-readiness { padding: var(--cxs-sp-6) var(--cxs-sp-7); margin-top: var(--cxs-sp-6); }
.cxs-readiness__head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--cxs-sp-4); margin-bottom: var(--cxs-sp-5); }
.cxs-readiness h3 { font-family: var(--v3-serif, Georgia, serif); font-size: 18px; font-weight: 500; letter-spacing: -.01em; margin: 0; color: var(--v3-ink); }
.cxs-rrow {
  display: grid; grid-template-columns: 130px 1fr 40px; align-items: center;
  gap: var(--cxs-sp-5); padding: 13px 0; border-top: 1px solid var(--v3-hair);
}
.cxs-rrow:first-child { border-top: 0; padding-top: 2px; }
.cxs-rrow:last-child { padding-bottom: 2px; }
.cxs-rrow .name { font-size: 14px; color: var(--v3-ink); font-weight: 400; }
.cxs-rrow .num { font-size: 13.5px; color: var(--v3-ink2); text-align: right; font-weight: 500; }

.cxs-modeshead { margin: var(--cxs-sp-8) 0 var(--cxs-sp-5); display: flex; align-items: baseline; gap: var(--cxs-sp-4); }
.cxs-modeshead h3 { font-family: var(--v3-serif, Georgia, serif); font-size: 19px; font-weight: 500; letter-spacing: -.01em; margin: 0; color: var(--v3-ink); }
.cxs-modes { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--cxs-sp-4); }
/* TWO DOORS GET TWO COLUMNS (RUN 127 Track 1). Today's rail folded from four
   tiles to Practise + Le Contrôle; left at repeat(4, 1fr) the two would each
   take a quarter of the row and sit orphaned against the left edge. The
   four-column rule stays for any surface still drawing four. */
.cxs-modes--two { grid-template-columns: repeat(2, 1fr); }
.cxs-mode {
  padding: var(--cxs-sp-5); text-align: left; background: var(--v3-card);
  border: 1px solid var(--v3-hair); border-radius: var(--cxs-r-md);
  display: flex; flex-direction: column; gap: 10px; min-height: 160px;
  text-decoration: none; color: var(--v3-ink);
  transition: border-color .2s cubic-bezier(.2, 0, .2, 1), transform .22s cubic-bezier(.2, 0, .2, 1),
              box-shadow .22s cubic-bezier(.2, 0, .2, 1);
}
.cxs-mode:hover { border-color: var(--v3-hair2); transform: translateY(-3px); box-shadow: var(--v3-shadow-lift); }
.cxs-mode:active { transform: translateY(-1px); }
.cxs-mode__mk {
  width: 30px; height: 30px; border-radius: var(--cxs-r-xs); display: grid; place-items: center;
  border: 1px solid var(--v3-hair2); color: var(--v3-signature);
  background: var(--v3-chip); margin-bottom: 2px;
  transition: border-color .2s cubic-bezier(.2, 0, .2, 1), background .2s cubic-bezier(.2, 0, .2, 1);
}
.cxs-mode__mk svg { width: 15px; height: 15px; }
.cxs-mode h4 { font-family: var(--v3-serif, Georgia, serif); font-size: 16.5px; font-weight: 500; letter-spacing: -.005em; margin: 0; }
.cxs-mode p { font-size: 12.5px; color: var(--v3-ink3); line-height: 1.5; margin: auto 0 0; }
.cxs-mode--feature .cxs-mode__mk { color: var(--v3-btntext); background: var(--v3-signature); border-color: var(--v3-signature); }

/* ── PRACTICE ──────────────────────────────────────────────────────────── */
.cxs-prac { max-width: 760px; margin: 0 auto; }
.cxs-crumb { display: flex; align-items: center; justify-content: space-between; gap: var(--cxs-sp-4); padding-bottom: var(--cxs-sp-4); flex-wrap: wrap; }
.cxs-crumb .path { font-size: 12.5px; color: var(--v3-ink3); display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.cxs-crumb .path b { color: var(--v3-ink2); font-weight: 500; }
.cxs-crumb .path .sep { color: var(--v3-ink3); }
.cxs-progress { display: flex; align-items: center; gap: var(--cxs-sp-3); }
.cxs-progress .count { font-size: 12px; color: var(--v3-ink3); letter-spacing: .02em; white-space: nowrap; }

.cxs-firstnote {
  display: flex; gap: var(--cxs-sp-3); align-items: flex-start;
  background: var(--v3-mode-drill-chip);
  border: 1px solid color-mix(in srgb, var(--v3-mode-drill) 32%, transparent);
  border-radius: var(--cxs-r-sm); padding: 14px 16px;
  margin: var(--cxs-sp-5) 0 var(--cxs-sp-6);
}
.cxs-firstnote .ic { color: var(--v3-mode-drill-ink); flex: none; margin-top: 1px; }
.cxs-firstnote .ic svg { width: 16px; height: 16px; display: block; }
.cxs-firstnote p { font-size: 13px; color: var(--v3-ink2); line-height: 1.55; margin: 0; }
.cxs-firstnote b { font-weight: 600; color: var(--v3-ink); }
.cxs-firstnote .tag {
  font-size: 10px; font-weight: 700; letter-spacing: .14em; text-transform: uppercase;
  color: var(--v3-mode-drill-ink); display: block; margin-bottom: 4px;
}

.cxs-stem { padding: var(--cxs-sp-6) var(--cxs-sp-7); }
.cxs-stem__qmeta { display: flex; align-items: center; gap: 10px; margin-bottom: var(--cxs-sp-4); }
.cxs-marks {
  font-size: 11px; font-weight: 600; color: var(--v3-ink2); letter-spacing: .02em;
  border: 1px solid var(--v3-hair2); border-radius: var(--cxs-r-pill); padding: 2px 11px;
}
.cxs-stem__text { font-size: 16px; line-height: 1.62; color: var(--v3-ink); margin: 0; }

/* the figure well. overflow-x so a wide diagram scrolls inside its own box
   rather than making the PAGE scroll sideways. */
.cxs-figure {
  margin: var(--cxs-sp-6) 0 var(--cxs-sp-2); padding: var(--cxs-sp-6);
  border: 1px solid var(--v3-hair); border-radius: var(--cxs-r-sm);
  background: var(--v3-chip); display: flex; justify-content: center; overflow-x: auto;
}
.cxs-venn { width: 100%; max-width: 452px; height: auto; display: block; }
.cxs-venn .u-box { fill: none; stroke: var(--v3-hair2); stroke-width: 1; }
.cxs-venn .circle { fill: var(--v3-signature); stroke: var(--v3-signature); stroke-width: 1.25; }
.cxs-venn .c-a, .cxs-venn .c-b { fill-opacity: .09; }
.cxs-venn .c-both { fill: var(--v3-signature); fill-opacity: .20; stroke: none; }
.cxs-venn text { fill: var(--v3-ink); font-family: var(--v3-sans, system-ui, sans-serif); }
.cxs-venn .lbl { font-size: 12px; font-weight: 600; letter-spacing: .11em; text-transform: uppercase; fill: var(--v3-ink2); }
.cxs-venn .val { font-size: 16px; font-weight: 500; font-variant-numeric: tabular-nums; fill: var(--v3-ink); }
.cxs-venn .u-lbl { font-size: 11.5px; letter-spacing: .05em; fill: var(--v3-ink3); font-variant-numeric: tabular-nums; }

.cxs-parts { padding: var(--cxs-sp-2) var(--cxs-sp-7) 0; }
.cxs-part { display: flex; gap: var(--cxs-sp-4); padding: var(--cxs-sp-4) 0; border-top: 1px solid var(--v3-hair); }
.cxs-part:first-child { border-top: 0; }
.cxs-part .lab {
  font-family: var(--v3-serif, Georgia, serif); font-style: italic; font-size: 17px;
  color: var(--v3-signature); flex: none; width: 26px; padding-top: 1px; line-height: 1.4;
}
.cxs-part .body { flex: 1; }
.cxs-part .q { font-size: 14.5px; color: var(--v3-ink); line-height: 1.55; margin: 0; }
.cxs-part .m {
  font-size: 11px; font-weight: 600; color: var(--v3-ink3); border: 1px solid var(--v3-hair);
  border-radius: var(--cxs-r-pill); padding: 1px 9px; margin-left: 9px;
  white-space: nowrap; vertical-align: 1px;
}

.cxs-mcq { margin-top: var(--cxs-sp-4); display: grid; gap: 8px; max-width: 340px; }
.cxs-opt {
  display: flex; align-items: center; gap: 12px; text-align: left; width: 100%;
  background: var(--v3-card); border: 1px solid var(--v3-hair);
  border-radius: var(--cxs-r-sm); padding: 11px 13px; min-height: 44px;
  transition: border-color .16s cubic-bezier(.2, 0, .2, 1), background .16s cubic-bezier(.2, 0, .2, 1),
              transform .1s cubic-bezier(.2, 0, .2, 1);
}
.cxs-opt:hover { border-color: var(--v3-hair2); background: var(--v3-chip); }
.cxs-opt:active { transform: translateY(1px); }
.cxs-opt .key {
  width: 22px; height: 22px; flex: none; border-radius: var(--cxs-r-xs);
  border: 1px solid var(--v3-hair2); display: grid; place-items: center;
  font-size: 11.5px; font-weight: 600; color: var(--v3-ink2);
  font-feature-settings: "tnum" 1;
  transition: background .16s cubic-bezier(.2, 0, .2, 1), border-color .16s cubic-bezier(.2, 0, .2, 1), color .16s cubic-bezier(.2, 0, .2, 1);
}
.cxs-opt .txt { font-size: 14px; color: var(--v3-ink); }
.cxs-opt .txt.muted { color: var(--v3-ink3); }
.cxs-opt[aria-pressed="true"] {
  border-color: var(--v3-signature);
  background: color-mix(in srgb, var(--v3-signature) 8%, var(--v3-card));
}
.cxs-opt[aria-pressed="true"] .key { background: var(--v3-signature); border-color: var(--v3-signature); color: var(--v3-btntext); }

.cxs-actionbar {
  display: flex; align-items: center; justify-content: space-between; gap: var(--cxs-sp-4);
  margin-top: var(--cxs-sp-6); padding-top: var(--cxs-sp-5); border-top: 1px solid var(--v3-hair);
}

/* ── PROGRESS ──────────────────────────────────────────────────────────── */
.cxs-statrow {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 0;
  margin: var(--cxs-sp-2) 0 var(--cxs-sp-7);
  border: 1px solid var(--v3-hair); border-radius: var(--cxs-r-md);
  overflow: hidden; background: var(--v3-card);
}
.cxs-stat { padding: var(--cxs-sp-5) var(--cxs-sp-6); border-left: 1px solid var(--v3-hair); }
.cxs-stat:first-child { border-left: 0; }
.cxs-stat .v {
  font-family: var(--v3-serif, Georgia, serif); font-size: 32px; font-weight: 500;
  line-height: 1; letter-spacing: -.015em; font-variant-numeric: tabular-nums;
  color: var(--v3-ink);
}
.cxs-stat .k { font-size: 11.5px; font-weight: 500; letter-spacing: .02em; color: var(--v3-ink3); margin-top: 9px; }

.cxs-finding { padding: var(--cxs-sp-6) var(--cxs-sp-7); position: relative; overflow: hidden; }
.cxs-finding::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: var(--v3-mode-controle); }
.cxs-finding__label {
  display: inline-block; font-size: 10px; font-weight: 700; letter-spacing: .15em;
  text-transform: uppercase; color: var(--v3-mode-controle-ink);
  background: var(--v3-mode-controle-chip); border-radius: var(--cxs-r-pill);
  padding: 3px 11px; margin-bottom: var(--cxs-sp-4);
}
.cxs-finding h3 {
  font-family: var(--v3-serif, Georgia, serif); font-size: 21px; font-weight: 500;
  line-height: 1.22; letter-spacing: -.015em; margin: 0; color: var(--v3-ink);
}
.cxs-finding__b { color: var(--v3-ink2); font-size: 14.5px; margin: var(--cxs-sp-3) 0 0; max-width: 60ch; line-height: 1.6; }
.cxs-fix {
  margin-top: var(--cxs-sp-5); background: var(--v3-chip); border: 1px solid var(--v3-hair);
  border-radius: var(--cxs-r-sm); padding: 15px 17px; display: flex;
  gap: var(--cxs-sp-3); align-items: flex-start;
}
.cxs-fix .fl {
  font-size: 10px; font-weight: 700; letter-spacing: .15em; text-transform: uppercase;
  color: var(--v3-mode-study-ink); flex: none; padding-top: 2px;
}
.cxs-fix p { font-size: 14px; color: var(--v3-ink); line-height: 1.6; margin: 0; }

.cxs-two { display: grid; grid-template-columns: 1fr 1fr; gap: var(--cxs-sp-6); margin-top: var(--cxs-sp-6); align-items: start; }
.cxs-panel { padding: var(--cxs-sp-6); }
.cxs-panel h3 { font-family: var(--v3-serif, Georgia, serif); font-size: 17px; font-weight: 500; letter-spacing: -.01em; margin: 0 0 4px; color: var(--v3-ink); }
.cxs-panel .sub { font-size: 12px; color: var(--v3-ink3); margin: 0 0 var(--cxs-sp-5); }

.cxs-mrow { display: grid; grid-template-columns: 1fr 44px; gap: 9px var(--cxs-sp-4); align-items: center; padding: 13px 0; border-top: 1px solid var(--v3-hair); }
.cxs-mrow:first-of-type { border-top: 0; padding-top: 2px; }
.cxs-mrow .mname { font-size: 14px; color: var(--v3-ink); display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.cxs-mrow .flag {
  font-size: 9.5px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  color: var(--v3-mode-controle-ink); background: var(--v3-mode-controle-chip);
  border-radius: var(--cxs-r-pill); padding: 2px 8px;
}
.cxs-mrow .mnum { text-align: right; font-size: 13.5px; font-weight: 500; color: var(--v3-ink2); font-variant-numeric: tabular-nums; }
.cxs-mrow .cxs-bar { grid-column: 1 / -1; }

.cxs-todo { counter-reset: cxsstep; display: flex; flex-direction: column; padding: 0; margin: 0; }
.cxs-todo li { list-style: none; display: flex; gap: var(--cxs-sp-4); padding: 15px 0; border-top: 1px solid var(--v3-hair); align-items: flex-start; }
.cxs-todo li:first-child { border-top: 0; padding-top: 2px; }
.cxs-todo .n {
  counter-increment: cxsstep; flex: none; width: 26px; height: 26px; border-radius: 50%;
  border: 1px solid var(--v3-brass-soft);
  /* THE SPEC PUTS BRASS ON THIS NUMERAL. Brass is 3.12:1 on --v3-bg — a mark,
     not text — so the RING stays brass and the glyph takes --v3-ink2. */
  color: var(--v3-ink2); background: var(--v3-chip);
  display: grid; place-items: center;
  font-family: var(--v3-serif, Georgia, serif); font-size: 13px; font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.cxs-todo .n::before { content: counter(cxsstep); }
.cxs-todo .tt { font-size: 14.5px; color: var(--v3-ink); font-weight: 500; }
.cxs-todo .td { font-size: 12.5px; color: var(--v3-ink3); margin-top: 3px; line-height: 1.5; }
/* ── THE NEEDS-WORK ROW IS A DOOR (RUN 127 Track 2) ──────────────────────
   Progress names the subtopic holding a grade back; the row now opens it.
   There is no generic `a` reset in this sheet — only .cxs-nav a — so an
   anchor dropped in here would render as a default underlined link on a
   surface that has none. It takes the row's own ink instead, and the hover
   is the one .v3-row already uses, so a student meets one affordance across
   the client rather than a new one per screen.
   NO COLOUR LITERAL: inherit, plus two v3 tokens that flip with the theme. */
.cxs-todo li > a {
  display: flex; flex-direction: column; flex: 1;
  text-decoration: none; color: inherit;
  padding: 2px 6px; margin: -2px -6px;
  border-radius: var(--cxs-r-xs);
  /* THE SHEET DECLARES ONE EASING AND THIS IS IT. Written as the literal
     because that is how every other transition in this file writes it —
     there is no easing token to reach for. `ease` shipped here for one
     edit and tests/test_interface_discipline.py's MOTION rule caught it:
     "every easing in the v3 sheet is the one declared curve", which is
     Standard v1 §1's "one gentle transition per interaction, one easing"
     made mechanical. */
  transition: background .15s cubic-bezier(.2, 0, .2, 1);
}
.cxs-todo li > a:hover { background: var(--v3-chip); }
.cxs-todo li > a:focus-visible {
  outline: 2px solid var(--v3-ink); outline-offset: 2px;
}

/* ── the maths inline set, from the spec ───────────────────────────────── */
.cxs-mset { font-variant-numeric: tabular-nums; }
.cxs-frac { display: inline-flex; flex-direction: column; text-align: center; vertical-align: -0.55em; margin: 0 .12em; line-height: 1; }
.cxs-frac .num { padding: 0 .35em 1px; }
.cxs-frac .den { padding: 1px .35em 0; border-top: 1px solid currentColor; }
.cxs-op { padding: 0 .14em; }

/* ── focus, motion, responsive ─────────────────────────────────────────── */
.cxs-topbar :focus-visible, .cxs-main :focus-visible {
  outline: none; border-radius: var(--cxs-r-xs);
  box-shadow: 0 0 0 2px var(--v3-bg), 0 0 0 4px var(--v3-signature);
}
@media (prefers-reduced-motion: reduce) {
  .cxs-bar > i { animation: none; }
  .cxs-mode, .cxs-opt, .cxs-btn { transition: none; }
}
@media (max-width: 900px) {
  .cxs-home { grid-template-columns: 1fr; }
  .cxs-modes { grid-template-columns: repeat(2, 1fr); }
  .cxs-two { grid-template-columns: 1fr; }
}
@media (max-width: 640px) {
  .cxs-main { padding: var(--cxs-sp-7) var(--cxs-sp-5) var(--cxs-sp-10); }
  .cxs-topbar__inner { padding: 0 var(--cxs-sp-4); gap: var(--cxs-sp-3); }
  .cxs-nav a { padding: 8px 9px; font-size: 12.5px; }
  .cxs-focus { padding: var(--cxs-sp-6) var(--cxs-sp-5) var(--cxs-sp-5); }
  .cxs-stem { padding: var(--cxs-sp-5); }
  .cxs-parts { padding: 0 var(--cxs-sp-5); }
  .cxs-statrow { grid-template-columns: 1fr 1fr; }
  .cxs-stat:nth-child(2) { border-left: 1px solid var(--v3-hair); }
  .cxs-stat:nth-child(3) { border-left: 0; border-top: 1px solid var(--v3-hair); }
  .cxs-stat:nth-child(4) { border-top: 1px solid var(--v3-hair); }
  .cxs-modes { grid-template-columns: 1fr; }
  .cxs-rrow { grid-template-columns: 110px 1fr 34px; gap: var(--cxs-sp-3); }
}

/* ── THE SHELL, RESTYLED TO THE SPEC (RUN 100) ──────────────────────────────
   design-system/APP-SPEC.html's topbar, applied to the markup cx_nav_v3.js
   ALREADY EMITS. Not one class name changes here, and that is deliberate:
   tests/test_v3_standard.py gate 3 drives the real generator through node and
   counts `class="v3-nav__item"` as an EXACT attribute match, so a second class
   on that anchor would take the gate to zero destinations while the screen
   still looked right. Law N1 is a founder law about the SHAPE of the nav; this
   is its skin, and the skin is the only thing that moves.

   Restyling here rather than in each page is what puts the new shell on every
   v3 route — session, controle, account, method, gdc included — in one edit. */
.v3-nav {
  position: sticky; top: 0; z-index: 50;
  background: color-mix(in srgb, var(--v3-bg) 86%, transparent);
  backdrop-filter: saturate(1.4) blur(12px);
  -webkit-backdrop-filter: saturate(1.4) blur(12px);
  border-bottom: 1px solid var(--v3-hair);
}
.v3-nav__inner {
  max-width: var(--cxs-edge); margin: 0 auto; padding: 0 var(--cxs-sp-7);
  height: 62px; display: flex; align-items: center; gap: var(--cxs-sp-6);
}
/* --v3-display WAS DEFINED NOWHERE IN static/, so this resolved to the Georgia
   fallback and the brand lockup has been rendering in Georgia on every v3
   surface — the session bar, the funnel bar and the sidebar alike. Nothing
   caught it: tests/test_type_law_rendered.py's SERIF pattern matches `georgia`
   as readily as `cinzel`, and `.v3-wordmark` is on its permitted list, so the
   WRONG face was permitted for being a face at all. --v3-smallcap is the
   Standard's own Cinzel token and is defined. (RUN 114 UNIT 1) */
.v3-wordmark {
  font-family: var(--v3-smallcap, Georgia, serif);
  color: var(--v3-ink); text-decoration: none;
  display: inline-flex; align-items: center; gap: 11px;
  border-radius: var(--cxs-r-xs);
}
.v3-nav__items { display: flex; gap: 2px; margin-left: var(--cxs-sp-2); }
.v3-nav__item {
  color: var(--v3-ink2); text-decoration: none;
  font-family: var(--v3-sans, system-ui, sans-serif);
  font-size: 13.5px; font-weight: 500; letter-spacing: .005em;
  padding: 8px 13px; border-radius: var(--cxs-r-xs); position: relative;
  display: inline-flex; align-items: center; min-height: 44px;
  border-bottom: 0;
  transition: color .2s cubic-bezier(.2, 0, .2, 1), background .2s cubic-bezier(.2, 0, .2, 1);
}
.v3-nav__item:hover {
  color: var(--v3-ink);
  background: color-mix(in srgb, var(--v3-surface2) 55%, transparent);
}
.v3-nav__item[aria-current="page"] {
  color: var(--v3-ink); border-bottom: 0; background: none;
}
.v3-nav__item[aria-current="page"]::after {
  content: ""; position: absolute; left: 13px; right: 13px; bottom: 9px;
  height: 2px; background: var(--v3-signature); border-radius: 2px 2px 0 0;
}
.v3-nav__you {
  margin-left: auto; width: 44px; height: 44px; border-radius: 50%;
  display: grid; place-items: center; text-decoration: none;
  border: 1px solid var(--v3-brass-soft);
  background: radial-gradient(120% 120% at 30% 22%,
              color-mix(in srgb, var(--v3-brass) 30%, var(--v3-card)) 0%,
              var(--v3-card) 62%);
  box-shadow: var(--v3-shadow-sm);
}
.v3-nav__you[aria-current="page"] { border-color: var(--v3-signature); }
.v3-nav__exit {
  margin-left: auto; color: var(--v3-ink2); text-decoration: none;
  font-family: var(--v3-sans, system-ui, sans-serif);
  font-size: 13.5px; font-weight: 500;
  background: none; border: 1px solid var(--v3-hair);
  border-radius: var(--cxs-r-sm); padding: 0 16px;
  min-height: 44px; display: inline-flex; align-items: center;
  transition: color .2s cubic-bezier(.2, 0, .2, 1), border-color .2s cubic-bezier(.2, 0, .2, 1),
              background .2s cubic-bezier(.2, 0, .2, 1);
}
.v3-nav__exit:hover { color: var(--v3-ink); border-color: var(--v3-hair2); background: var(--v3-chip); }

/* The grade band, spaced for the spec's rhythm now that it sits between the
   stat row and the finding card rather than under the page title. */
.v3-ready { margin: 0 0 var(--cxs-sp-7); }
.v3-ready .cxs-micro { display: block; margin-bottom: var(--cxs-sp-3); }
#p-pathcard { margin-top: var(--cxs-sp-6); }

/* ── PRACTICE, RESTYLED TO THE SPEC (RUN 100) ───────────────────────────────
   APP-SPEC's question screen, applied to the classes ui/v3/session.html and
   cx_session_v3.js ALREADY EMIT. No markup and no JS changed: the session is
   the most heavily gated surface in the product (test_v3_session,
   test_journey_v3, test_render_qa_fidelity, test_notation all read it), and a
   restyle that does not touch its DOM cannot break any of them.

   THE STEM MOVES TO THE UI FACE. The spec sets the question in Libre Franklin
   at 16px/1.62 — body copy, not a hero word — and the sheet had it in the
   display serif. A question stem is the most-read prose in the product. */
.v3-session {
  max-width: 760px; margin: 0 auto;
  padding: var(--cxs-sp-7) var(--cxs-sp-7) var(--cxs-sp-12);
}
.v3-session__head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--cxs-sp-4); flex-wrap: wrap;
  padding-bottom: var(--cxs-sp-4); border-bottom: 0;
}
.v3-session__title { font-size: 12.5px; color: var(--v3-ink3); font-weight: 400; }
.v3-session__mode { color: var(--v3-ink2); font-weight: 500; }
.v3-session__step {
  font-size: 12px; color: var(--v3-ink3); letter-spacing: .02em; white-space: nowrap;
}

/* the question card — the spec's .stem, with the parts inside it */
#q-card.v3-card { padding: var(--cxs-sp-6) var(--cxs-sp-7); }
.v3-stem {
  font-family: var(--v3-sans, system-ui, sans-serif);
  font-size: 16px; line-height: 1.62; color: var(--v3-ink);
}
#q-eyebrow {
  font-size: 11px; font-weight: 600; color: var(--v3-ink2); letter-spacing: .02em;
  text-transform: uppercase;
  border: 1px solid var(--v3-hair2); border-radius: var(--cxs-r-pill);
  padding: 2px 11px; display: inline-block;
}

/* THE FIGURE WELL. overflow-x so a wide diagram scrolls inside its own box
   instead of making the page scroll sideways — a scroll container is the fix,
   not the defect. */
.v3-figure {
  margin: var(--cxs-sp-6) 0 var(--cxs-sp-2); padding: var(--cxs-sp-6);
  border: 1px solid var(--v3-hair); border-radius: var(--cxs-r-sm);
  background: var(--v3-chip); display: flex; justify-content: center;
  overflow-x: auto;
}

/* the lettered parts */
.v3-part { display: block; padding: var(--cxs-sp-4) 0; border-top: 1px solid var(--v3-hair); }
.v3-part:first-child { border-top: 0; }
.v3-partblock { font-size: 14.5px; color: var(--v3-ink); line-height: 1.55; }

/* THE ANSWER OPTIONS. The spec caps the column at 340px: a fraction in a
   full-width button is a target the size of the page for a value two
   characters wide. */
.v3-options { margin-top: var(--cxs-sp-4); display: grid; gap: 8px; max-width: 340px; }
.v3-option {
  display: flex; align-items: center; gap: 12px; text-align: left; width: 100%;
  background: var(--v3-card); border: 1px solid var(--v3-hair);
  border-radius: var(--cxs-r-sm); padding: 11px 13px; min-height: 44px;
  transition: border-color .16s cubic-bezier(.2, 0, .2, 1),
              background .16s cubic-bezier(.2, 0, .2, 1);
}
.v3-option:hover { border-color: var(--v3-hair2); background: var(--v3-chip); }
.v3-option__key {
  width: 22px; height: 22px; flex: none; border-radius: var(--cxs-r-xs);
  border: 1px solid var(--v3-hair2); display: grid; place-items: center;
  font-size: 11.5px; font-weight: 600; color: var(--v3-ink2);
  font-feature-settings: "tnum" 1;
  transition: background .16s cubic-bezier(.2, 0, .2, 1),
              border-color .16s cubic-bezier(.2, 0, .2, 1),
              color .16s cubic-bezier(.2, 0, .2, 1);
}
.v3-option__value { font-size: 14px; color: var(--v3-ink); }
.v3-option[aria-pressed="true"] {
  border-color: var(--v3-signature);
  background: color-mix(in srgb, var(--v3-signature) 8%, var(--v3-card));
}
.v3-option[aria-pressed="true"] .v3-option__key {
  background: var(--v3-signature); border-color: var(--v3-signature); color: var(--v3-btntext);
}

/* THE VENN, drawn the way the spec draws it: fine strokes, one accent lens on
   the intersection, tabular figures. The engine emits the SVG; these rules
   dress whatever it emits, which is why they select on shape rather than on
   classes the emitter would have to be changed to add. */
.v3-figure svg { max-width: 100%; height: auto; }
.v3-figure svg circle, .v3-figure svg rect, .v3-figure svg ellipse {
  stroke: var(--v3-signature); stroke-width: 1.25;
}
.v3-figure svg text {
  fill: var(--v3-ink); font-family: var(--v3-sans, system-ui, sans-serif);
  font-variant-numeric: tabular-nums;
}

/* The question card is a column flexbox, so the marks chip stretched to the
   full card width — a pill the width of the page is not a chip. */
#q-eyebrow { align-self: flex-start; width: auto; }

/* ONE FRAME, NOT TWO. The diagram emitter wraps its SVG in its own panel; with
   the spec's figure well around it the diagram sat inside two nested boxes.
   The well is the frame, so the inner wrapper goes flat. */
.v3-figure > div, .v3-figure > figure {
  background: none; border: 0; padding: 0; margin: 0; box-shadow: none;
}

/* ── THE BANNER LANDMARK (RUN 101) ──────────────────────────────────────────
   THE v3 SHELL HAD NO <header>. tests/test_one_shell.py exists because RUN 99
   counted banner landmarks on twelve v2 routes and found zero; that was fixed
   for v2, and the v3 shell — which is now the ONLY student app — never had
   one. Nine screens, no banner, measured 0/0/0/0/0/0/0/0/0 the moment the
   gate was repointed at the canonical app.

   THE STICKY MOVES TO THE HEADER, and that is not a detail. A `position:
   sticky` element sticks only within its PARENT's box, so leaving it on the
   <nav> inside a header that is exactly as tall as the nav would have left a
   bar that scrolls away — a silent visual regression behind an accessibility
   fix. The header carries the sticky, the ground and the hairline; the nav
   carries the navigation. */
.v3-shellbar {
  position: sticky; top: 0; z-index: 50;
  background: color-mix(in srgb, var(--v3-bg) 86%, transparent);
  backdrop-filter: saturate(1.4) blur(12px);
  -webkit-backdrop-filter: saturate(1.4) blur(12px);
  border-bottom: 1px solid var(--v3-hair);
}
.v3-nav {
  position: static; background: none; border-bottom: 0;
  backdrop-filter: none; -webkit-backdrop-filter: none;
}

/* ── WORK, COMPOSED TO THE SPEC (RUN 101) ───────────────────────────────────
   APP-SPEC.html draws no "Work" screen — its four modes live on Home, which
   RUN 100 built. What the spec governs here is the TYPE, the CARD and the
   SPACING; the IA does not move, because "Work leads with the suggestion,
   then a LIST" is a founder ruling with a gate (tests/test_v3_phase3.py gate
   1 asserts .v3-rows is a flex COLUMN and that no grid property appears
   anywhere on the page). A grid of mode cards here would have looked like the
   spec and overturned a ruling. */
.v3-rec { margin-bottom: var(--cxs-sp-8); }

.v3-orline {
  display: flex; align-items: center; gap: var(--cxs-sp-4);
  margin: var(--cxs-sp-8) 0 var(--cxs-sp-5);
}
.v3-orline__l { flex: 1; height: 1px; background: var(--v3-hair); }
.v3-orline__x {
  font-size: 10.5px; font-weight: 600; letter-spacing: .16em;
  text-transform: uppercase; color: var(--v3-ink3); white-space: nowrap;
}

/* THE LIST. A hairline-separated column, the spec's readiness rail rhythm:
   one rule between rows, none above the first, and the whole row is the
   target — no per-row button, which the gate also holds. */
.v3-rows { display: flex; flex-direction: column; }
.v3-row {
  display: flex; align-items: flex-start; gap: var(--cxs-sp-4);
  width: 100%; text-align: left; background: none; border: 0;
  border-top: 1px solid var(--v3-hair);
  padding: var(--cxs-sp-5) var(--cxs-sp-3);
  min-height: 44px; color: var(--v3-ink);
  transition: background .16s cubic-bezier(.2, 0, .2, 1);
}
.v3-row:first-child { border-top: 0; }
.v3-row:hover { background: var(--v3-chip); }

/* THE MARK, not a bare dot. The spec gives each mode an abstract mark in a
   30px framed square; this is that frame, and the mode's colour reaches it as
   a RING rather than as text — Law C1 keeps drill's amber off any glyph. */
.v3-row__dot {
  width: 30px; height: 30px; flex: none; border-radius: var(--cxs-r-xs);
  border: 1px solid var(--v3-hair2); background: var(--v3-chip);
  display: grid; place-items: center; margin-top: 2px;
}
.v3-row__dot::after {
  content: ""; width: 8px; height: 8px; border-radius: 50%;
  background: var(--v3-accent, var(--v3-signature));
}
.v3-row__m { flex: 1; display: block; }
.v3-row__nm {
  font-family: var(--v3-serif, Georgia, serif);
  font-size: 16.5px; font-weight: 500; letter-spacing: -.005em;
  color: var(--v3-ink); display: inline;
}
.v3-row__d {
  display: block; font-size: 12.5px; color: var(--v3-ink3);
  line-height: 1.5; margin-top: 3px;
}
.v3-row__chev { color: var(--v3-ink3); flex: none; align-self: center; }

/* The mark inside the framed square. The mode colour arrives as a STROKE,
   never as `color` — Law C1 keeps drill's amber off anything a contrast gate
   reads as text, and gate 2 of test_v3_standard.py enforces that by property. */
.v3-row__dot::after { content: none; }
.v3-row__dot svg { width: 15px; height: 15px; display: block; }
.v3-mode-diagnostic .v3-row__dot svg { stroke: var(--v3-mode-diagnostic); }
.v3-mode-study      .v3-row__dot svg { stroke: var(--v3-mode-study); }
.v3-mode-drill      .v3-row__dot svg { stroke: var(--v3-mode-drill); }
.v3-mode-controle   .v3-row__dot svg { stroke: var(--v3-mode-controle); }

/* This page's lede is a sentence, not the spec's four-word greeting, so the
   34ch measure that suits Home clamps it to four lines here. */
/* The spec's 34ch pagehead measure suits a four-word greeting. This screen's
   lede is a full sentence, and 34ch broke it over four lines. The MODIFIER
   widens the head rather than the h1: max-width on the child cannot beat a
   narrower block on the parent, which is why setting it on #w-lede alone
   changed nothing. */
.cxs-pagehead--wide { max-width: 52ch; }

/* ── THE REMAINING v3 SCREENS, COMPOSED TO THE SPEC (RUN 101 Phase B2) ──────
   /v3/method, /v3/gdc, /v3/you and /v3/account all share three primitives —
   .v3-screen, .v3-kicker, .v3-lede — so composing them is ONE change here
   rather than four markup edits. That matters beyond tidiness: those four
   files are read by test_v3_phase3 (the method reveal, the gdc per-model
   block), test_v3_account and test_error_states, several of them
   POSITIONALLY. A restyle that touches no markup cannot break a gate that
   reads markup.

   The measure, the rhythm and the type are APP-SPEC's: 1080px edge, the
   48/32/96 page padding, the 11px uppercase eyebrow, and the Fraunces
   clamp(28,4.2vw,40) page title. */
.v3-screen {
  max-width: var(--cxs-edge);
  padding: var(--cxs-sp-9) var(--cxs-sp-7) var(--cxs-sp-12);
}
.v3-kicker {
  font-size: 11px; font-weight: 600; letter-spacing: .15em;
  text-transform: uppercase; color: var(--v3-ink3);
  margin: 0 0 var(--cxs-sp-4);
}
.v3-lede {
  font-family: var(--v3-serif, Georgia, serif); font-weight: 500;
  font-size: clamp(28px, 4.2vw, 40px); line-height: 1.1;
  letter-spacing: -.02em; color: var(--v3-ink);
  margin: 0 0 var(--cxs-sp-3); max-width: 26ch;
}
.v3-why {
  color: var(--v3-ink2); font-size: 15.5px; line-height: 1.55;
  margin: 0 0 var(--cxs-sp-8); max-width: 52ch;
}
.v3-screen__title {
  font-family: var(--v3-serif, Georgia, serif); font-weight: 500;
  font-size: clamp(28px, 4.2vw, 40px); line-height: 1.1;
  letter-spacing: -.02em; margin: 0 0 var(--cxs-sp-8);
}

/* ── THE YOU DRAWER WAS CLIPPING ITS OWN VALUES (RUN 101, pre-existing) ─────
   PHOTOGRAPHED, not reasoned about, and present identically in RUN 100's
   committed gallery — so it predates this run rather than being caused by it.

   `.v3-drow` sets `width: 100%` and `padding: 12px 22px` with no border-box,
   so every row was 44px WIDER than the 400px drawer, and the drawer clips
   (`overflow: hidden`). What was cut off was the right-hand column of every
   row: "Not se[t]" for exam session, target grade and calculator, "Da[rk]" on
   the theme control, and "View, export, delet[e]" on the data-rights link.

   A STUDENT COULD NOT READ THEIR OWN SETTINGS, and the one control this
   product has already shipped broken once — the theme toggle — was the one
   with its last option cut in half. No gate saw it: the DOM contains every
   one of those strings, and a checker reading textContent finds them all
   present. Only a screenshot shows the box they are drawn in.

   Fixed on the box that overflows, not by widening the drawer, because the
   400px measure is the design and the padding is what was unaccounted for. */
.v3-drow, .v3-dsec, .v3-drawer__head { box-sizing: border-box; }

/* The launch guarantee under the funnel's ask. Quiet — it is a commitment the
   visitor can read, not a banner competing with the diagnosis above it. */
.v3-ask__note {
  margin-top: var(--cxs-sp-4); font-size: 12.5px; line-height: 1.5;
  color: var(--v3-ink3); max-width: 46ch;
}

/* ══ THE NAV FITS A PHONE (RUN 105 UNIT 4) ════════════════════════════════
   MEASURED: 390px viewport, scrollWidth 448, on /registered and /v3/today —
   every signed-in screen scrolled sideways.

   codex-v3.css already had a phone treatment for this bar (~line 499). A later
   nav redesign in this file re-declares .v3-nav, .v3-nav__inner, .v3-wordmark
   and .v3-nav__you UNCONDITIONALLY and AFTER it, so at equal specificity the
   newer block won and the mobile rules stopped applying. The media query still
   matched — matchMedia('(max-width:720px)') reports true — the declarations
   simply lost. That is why the page looked correct in the source and wrong on
   a phone.

   This block sits after the newer shell so it wins in turn. Everything in it
   is inside the media query: the desktop nav is byte-for-byte unchanged, and
   no colour, font or token is touched.

   The arithmetic it repairs, at 390px:
     310px usable  =  390 − 16 page padding − 64 inner padding
     396px wanted  =  wordmark 44 + tabs 238 + avatar 66 + two 24px gaps
   The wordmark goes (the mark is one tap away on You, and the original phone
   treatment hid it for the same reason), the padding and gaps come down, and
   the tab row is allowed to shrink instead of pushing the avatar off-screen. */
@media (max-width: 720px) {
  .v3-nav__inner {
    padding: 0 var(--cxs-sp-3);
    gap: var(--cxs-sp-2);
  }
  /* The wordmark is the one item on this bar that is not a destination. */
  .v3-wordmark { display: none; }
  /* min-width:0 is load-bearing: a flex item defaults to min-width:auto and
     REFUSES to shrink below its content, which is how a row that "should" fit
     still overflows. */
  .v3-nav__items {
    margin-left: 0;
    flex: 1 1 auto;
    min-width: 0;
    justify-content: space-around;
    gap: 0;
  }
  .v3-nav__item { padding: 8px 6px; }
  /* margin-left:auto pushed You to the corner; with the tab row now taking the
     free space there is no corner to push to, and auto would re-create the
     overflow. */
  .v3-nav__you { margin-left: 0; flex: 0 0 auto; }
}


/* ═══════════════════════════════════════════════════════════════════════════
   THE APP SHELL — LOGGED-IN CLIENT STANDARD v1 §2 (RUN 114 UNIT 1)
   docs/design/codex-logged-in-client-standard-v1.md, built to
   design/le-codex-studio.html.

   A persistent 248px left SIDEBAR, a 64px TOP BAR beside it, and a FULL-WIDTH
   main under both. Rendered by static/js/cx_nav_v3.js into #v3-nav-slot.

   WHY THE TWO BARS ARE FIXED AND THE OFFSET IS ON <body>. The alternative was
   a grid on <body> with the shell's two elements as grid items, and it does
   not work: a sticky top bar inside a 64px grid row has a 64px containing
   block and cannot move, so it scrolls away. Fixed bars plus a body offset
   gives the mock's layout with no wrapper element, which matters because it
   means ELEVEN EXISTING v3 PAGES INHERIT THE SHELL WITHOUT THEIR MARKUP BEING
   TOUCHED — the rebuild is one screen per commit, and a shell that required
   every page to be re-nested would have had to move all of them at once.

   THE FOUR SURFACES WITH NO CHROME OPT OUT IN THEIR OWN <body>, not here:
   `class="v3 v3-bare"` on session, controle, diagnostic and start. A JS-added
   class would shift the page 248px after first paint on every load; a static
   one cannot. It can DRIFT from cx_nav_v3's own SESSION/ANON lists, so
   tests/test_v3_shell_standard.py asserts the two agree, route by route.
   ═══════════════════════════════════════════════════════════════════════════ */

:root {
  --v3-side-w: 248px;
  --v3-top-h: 64px;
  --v3-tab-h: 62px;
}

body.v3 {
  padding-left: var(--v3-side-w);
  padding-top: var(--v3-top-h);
}

/* THE PRE-AUTH AND IN-SESSION SURFACES. Standard v1 §2: "Nav is suppressed
   inside a live session and inside the pre-auth flows -- those are full-screen,
   sidebar + top bar hidden." */
body.v3.v3-bare {
  padding-left: 0;
  padding-top: 0;
}

/* ── THE SIDEBAR ───────────────────────────────────────────────────────── */
/* THERE IS NO GLOBAL box-sizing RESET IN THIS SHEET, and the sidebar found it:
   a `width: 100%` item with 12px of horizontal padding measured 248px inside a
   224px column and its wash ran 12px past the sidebar's own border. Declared
   for the shell's own subtree rather than globally, because a reset applied to
   every v3 page in a shell commit is a re-layout of eleven screens smuggled in
   under a nav change. */
.v3-side,
.v3-side *,
.v3-top,
.v3-top * { box-sizing: border-box; }

.v3-side {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  width: var(--v3-side-w);
  background: var(--v3-nav);
  border-right: 1px solid var(--v3-hair);
  display: flex;
  flex-direction: column;
  z-index: 50;
}

.v3-side__brand {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 10px;
  padding: 22px 22px 18px;
  color: var(--v3-ink);
  text-decoration: none;
  border-radius: var(--cxs-r-xs);
}

/* `.v3-wordmark` is in the same 44px-target block as the nav item, so it too
   arrives centred with a 44px floor. In the sidebar it is a two-line block
   beside the mark, not a tap target of its own — the anchor around it is. */
.v3-side .v3-wordmark {
  justify-content: flex-start;
  min-width: 0;
  min-height: 0;
}

/* currentColor, so the mark takes the lockup's ink rather than pinning a
   literal on a v3 surface (gate 1 of tests/test_v3_standard.py). */
.v3-side__mark { width: 30px; height: 34px; flex: none; color: var(--v3-ink); }

/* THE LOCKUP IS CINZEL AGAIN, AND IT HAD STOPPED BEING CINZEL.
   `.v3-wordmark` asked for `var(--v3-display, ...)` and --v3-display is
   defined nowhere in static/, so the brand has been rendering in the Georgia
   fallback on every v3 surface. --v3-smallcap is the Standard's own Cinzel
   token and is defined; see the note in codex-tokens.css. */
.v3-side .v3-wordmark {
  font-family: var(--v3-smallcap, Georgia, serif);
  font-weight: 600;
  font-size: 13px;
  line-height: 1.15;
  letter-spacing: .12em;
  color: var(--v3-ink);
  display: block;
  margin: 0;
}

.v3-side .v3-wordmark__de {
  font-family: var(--v3-serif, Georgia, serif);
  font-style: italic;
  font-weight: 400;
  font-size: 12px;
  letter-spacing: 0;
}

/* THE SIDEBAR SETS THE LOCKUP ON TWO LINES, and it does it here rather than in
   the markup. The locked mock draws ATELIER over "de MATHS" in the 248px
   column; putting a <br> in the generator split the lockup into two string
   literals, and config/lexicon.json exempts the wordmark from the shouting rule
   BY ITS EXACT STRING — so 'MATHS' alone became a violation and
   tests/test_lexicon.py went red. The break is a setting, not a different
   lockup, so it is a setting. */
.v3-side .v3-wordmark__de::before {
  content: "\A";
  white-space: pre;
}

.v3-side .v3-nav {
  background: transparent;
  border-bottom: 0;
  position: static;
  z-index: auto;
  flex: 1;
  padding: 10px 12px;
}

.v3-side .v3-nav__items {
  display: flex;
  flex-direction: column;
  gap: 2px;
  /* `margin-left: var(--cxs-sp-2)` from the RUN 100 top-bar block shifted the
     whole column 8px right, so a width:100% item overflowed the sidebar by 8px
     and the current item's wash ran off its edge. Visible in the render, in
     nothing else. */
  margin: 0;
  padding: 0;
  min-width: 0;
}

/* EVERY INHERITED RULE IS OVERRIDDEN EXPLICITLY, AND THAT IS DELIBERATE.
   `.v3-nav__item` carries FIVE earlier declarations in this sheet — the 44px
   target block at the top, the RUN 77 top-bar item, the RUN 78 filled pill, the
   RUN 100 APP-SPEC item, and two mobile blocks. Three of them are wrong for a
   sidebar and were VISIBLE in the first render: `justify-content: center` (from
   the target block) centred the label in a 248px column, and the RUN 100
   `[aria-current]::after` painted a 2px forest underline across the wash. The
   fix is to state every property this element needs rather than to rely on
   winning by order, because "the last block wins" is exactly the assumption
   this repo has been burnt by. */
.v3-side .v3-nav__item {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  text-align: left;
  gap: 11px;
  width: 100%;
  min-width: 0;
  min-height: 44px;
  padding: 9px 12px;
  border: 0;
  border-radius: var(--cxs-r-md);
  font-family: var(--v3-sans, system-ui, sans-serif);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0;
  color: var(--v3-ink2);
  text-decoration: none;
  position: relative;
  background: none;
}

/* The RUN 100 underline. A sidebar's current item is a wash and a left bar,
   and a 2px rule across the bottom of it reads as a divider. */
.v3-side .v3-nav__item[aria-current="page"]::after { content: none; }

.v3-side .v3-nav__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--v3-taupe);
  flex: none;
}

.v3-side .v3-nav__item:hover {
  background: var(--v3-chip);
  color: var(--v3-ink);
}

/* THE ACTIVE ITEM CARRIES THREE SIGNALS, NOT ONE. A forest left-bar, a wash,
   and a weight change -- so the current destination is not on colour alone,
   which is the house rule the Standard restates in §1. */
.v3-side .v3-nav__item[aria-current="page"] {
  background: var(--v3-signature-wash);
  color: var(--v3-signature-ink);
  font-weight: 600;
  border-bottom-color: transparent;
}

.v3-side .v3-nav__item[aria-current="page"] .v3-nav__dot {
  background: var(--v3-signature);
}

.v3-side .v3-nav__item[aria-current="page"]::before {
  content: "";
  position: absolute;
  left: -12px;
  top: 8px;
  bottom: 8px;
  width: 3px;
  border-radius: 0 3px 3px 0;
  background: var(--v3-signature);
}

.v3-side__foot {
  border-top: 1px solid var(--v3-hair);
  padding: 10px 12px;
}

/* THE SAME OVERRIDE PROBLEM, AND IT RENDERED AS A CLIPPED GREEN DISC.
   `.v3-nav__you` was a 44x44 circle with a brass ring and a radial gradient —
   correct for a corner avatar on a top bar, and wrong for a full-width row at
   the foot of a sidebar. width, height, border, background and place-items all
   have to be said again here. */
.v3-side .v3-nav__you {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 11px;
  width: 100%;
  height: auto;
  min-width: 0;
  min-height: 44px;
  margin-left: 0;
  padding: 8px 10px;
  border: 0;
  border-radius: var(--cxs-r-md);
  background: none;
  box-shadow: none;
  color: var(--v3-ink);
  text-decoration: none;
}

.v3-side .v3-nav__you:hover { background: var(--v3-chip); }

.v3-side .v3-nav__you[aria-current="page"] {
  background: var(--v3-signature-wash);
  color: var(--v3-signature-ink);
}

.v3-side .v3-nav__avatar {
  width: 34px;
  height: 34px;
  border: 0;
  border-radius: 50%;
  background: var(--v3-signature);
  color: var(--v3-btntext);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--v3-serif, Georgia, serif);
  font-size: 16px;
  line-height: 1;
  flex: none;
}

.v3-side__whom { display: block; line-height: 1.2; min-width: 0; }

/* Filled by mount() from /api/profile, and EMPTY until it answers. An empty
   inline element takes no space, so the foot reads as "You" alone rather than
   as a name-shaped gap. */
.v3-side__who {
  display: block;
  font-size: 13px;
  font-weight: 600;
  color: inherit;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.v3-side__role {
  display: block;
  font-size: 11.5px;
  font-weight: 400;
  color: var(--v3-ink3);
}

.v3-side .v3-nav__you[aria-current="page"] .v3-side__role {
  color: var(--v3-signature-ink);
}

/* ── THE TOP BAR ───────────────────────────────────────────────────────── */
/* THE MOCK DRAWS THIS TRANSLUCENT, WITH A BACKDROP BLUR, AND IT IS OPAQUE
   HERE. Its value is a literal `rgba(255,255,255,.82)`, and every colour on a
   v3 surface must resolve from a token; a new veil token would need a dark
   value, and dark is frozen until light is signed off. Under a 64px bar on a
   near-white page the difference is not visible, and it is one token away when
   dark thaws. Recorded rather than quietly dropped. */
.v3-top {
  position: fixed;
  top: 0;
  left: var(--v3-side-w);
  right: 0;
  height: var(--v3-top-h);
  background: var(--v3-nav);
  border-bottom: 1px solid var(--v3-hair);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--cxs-sp-4);
  padding: 0 var(--cxs-sp-7);
  z-index: 45;
}

.v3-top__ctx { min-width: 0; }

.v3-top__eyebrow {
  display: block;
  font-family: var(--v3-smallcap, Georgia, serif);
  text-transform: uppercase;
  letter-spacing: .18em;
  font-size: 10.5px;
  font-weight: 600;
  color: var(--v3-ink3);
}

.v3-top__title {
  font-family: var(--v3-serif, Georgia, serif);
  font-size: 19px;
  font-weight: 500;
  line-height: 1.25;
  margin: 0;
  color: var(--v3-ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* An unclaimed title takes no space, so a screen that has not set one shows
   the eyebrow alone rather than a reserved empty line. */
.v3-top__title:empty { display: none; }

.v3-top__acts {
  display: flex;
  align-items: center;
  gap: var(--cxs-sp-3);
  flex: none;
}

/* ── THE COURSE SWITCHER ───────────────────────────────────────────────── */
.v3-course { position: relative; }

.v3-course__btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  background: var(--v3-card);
  border: 1px solid var(--v3-hair);
  border-radius: var(--cxs-r-pill);
  padding: 6px 13px;
  font-family: var(--v3-sans, system-ui, sans-serif);
  font-size: 12.5px;
  font-weight: 600;
  color: var(--v3-ink);
  cursor: pointer;
}

.v3-course__btn:hover { border-color: var(--v3-hair2); }

.v3-course__tag {
  font-family: var(--v3-smallcap, Georgia, serif);
  font-size: 8.5px;
  letter-spacing: .12em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--v3-ink3);
}

.v3-course__now:empty { display: none; }
.v3-course__chev { color: var(--v3-ink3); font-size: 10px; }

.v3-course__menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  min-width: 250px;
  background: var(--v3-card);
  border: 1px solid var(--v3-hair);
  border-radius: var(--cxs-r-lg);
  box-shadow: var(--v3-shadow);
  padding: 6px;
  z-index: 60;
}

.v3-course__opt {
  display: block;
  width: 100%;
  min-height: 44px;
  text-align: left;
  border: 0;
  background: transparent;
  padding: 10px 12px;
  border-radius: var(--cxs-r-sm);
  cursor: pointer;
  font-family: var(--v3-sans, system-ui, sans-serif);
  font-size: 13.5px;
  font-weight: 500;
  color: var(--v3-ink);
}

.v3-course__opt:hover { background: var(--v3-chip); }

.v3-course__opt.is-on {
  background: var(--v3-signature-wash);
  color: var(--v3-signature-ink);
  font-weight: 600;
}

.v3-course__desc {
  display: block;
  font-size: 11.5px;
  font-weight: 400;
  color: var(--v3-ink3);
  margin-top: 2px;
}

.v3-course__opt.is-on .v3-course__desc { color: var(--v3-signature-ink); }

/* THE ONE LINE THE SWITCHER CAN SAY (RUN 116). A course change that fails is
   reported inside the menu, where the student just acted -- the 64px bar has
   no room for a sentence and closing the menu on a failure would leave them
   looking at the course they did not choose. --v3-mode-controle-ink is the
   text-legal claret; [hidden] keeps it out of the layout when it says nothing,
   which is every render but the one that needs it. */
.v3-course__err {
  margin: 6px 10px 4px;
  font-size: 12px;
  font-style: italic;
  line-height: 1.45;
  color: var(--v3-mode-controle-ink);
}
.v3-course__err[hidden] { display: none; }

/* ── LE CONTRÔLE · THE PAPER AND ITS RAIL (RUN 116) ─────────────────────── */
/* The mock draws the palette as a right-hand rail beside the question. ONE
   COLUMN UNTIL THERE IS ROOM FOR TWO: a palette squeezed against a stem is
   worse than a palette under it, and the paper is the thing being read. The
   card keeps its place in the flow, so every gate that reads #q-card as it
   lands reads the same element in the same order. */
.v3-exam-cols { display: grid; grid-template-columns: 1fr; gap: 18px; }

@media (min-width: 1040px) {
  .v3-exam-cols { grid-template-columns: minmax(0, 1fr) 216px; align-items: start; }
  /* The rail follows the paper down. `top` clears the sticky bar above it. */
  .v3-pal { position: sticky; top: 88px; }
}

.v3-pal {
  background: var(--v3-card);
  border: 1px solid var(--v3-hair);
  border-radius: var(--v3-radius-card);
  padding: 16px 18px 14px;
}
.v3-pal[hidden] { display: none; }

.v3-pal__h {
  font-family: var(--v3-smallcap);
  font-size: 9px; letter-spacing: .12em; text-transform: uppercase;
  color: var(--v3-ink3); font-weight: 600; margin: 0 0 12px;
}

.v3-pal__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(44px, 1fr));
  gap: 6px;
}

/* 44px SQUARE, because a palette cell is a tap target on the one screen where
   a mis-tap costs a student time they are being measured on. */
.v3-pal__q {
  min-width: 44px; min-height: 44px;
  border: 1px solid var(--v3-hair);
  border-radius: var(--v3-radius-ctl);
  background: var(--v3-card);
  color: var(--v3-ink);
  font-family: var(--v3-sans);
  font-size: 13.5px;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  transition: border-color .15s, background .15s;
}
.v3-pal__q:hover { border-color: var(--v3-taupe); }

/* ANSWERED is the signature wash, the same one the product uses for earned
   everywhere else. It is never the only signal: the cell's aria-label says
   "answered" in words. */
.v3-pal__q.is-done {
  background: var(--v3-signature-wash);
  border-color: var(--v3-signature);
  color: var(--v3-signature-ink);
}

/* FLAGGED is Le Contrôle's own claret, as an EDGE rather than a fill, so a
   cell can be answered and flagged at once and still read as both. */
.v3-pal__q.is-flag { border-color: var(--v3-mode-controle); border-width: 2px; }

.v3-pal__q.is-cur {
  background: var(--v3-btn);
  border-color: var(--v3-btn);
  color: var(--v3-btntext);
  font-weight: 600;
}
.v3-pal__q.is-cur.is-flag { border-color: var(--v3-mode-controle); }

.v3-pal__legend {
  display: flex; flex-wrap: wrap; gap: 12px;
  margin: 12px 0 0;
  font-size: 11.5px; color: var(--v3-ink2);
}
.v3-pal__legend span { display: inline-flex; align-items: center; gap: 6px; }
.v3-pal__d {
  width: 10px; height: 10px; border-radius: 3px; display: inline-block;
  border: 1px solid var(--v3-hair);
}
.v3-pal__d--done { background: var(--v3-signature-wash); border-color: var(--v3-signature); }
.v3-pal__d--flag { border-color: var(--v3-mode-controle); border-width: 2px; }

.v3-pal__count {
  margin: 10px 0 0;
  font-size: 12.5px; color: var(--v3-ink2);
  font-variant-numeric: tabular-nums;
}

/* THE FLAG SITS WITH THE QUESTION, because it is an act on this question and
   not a reading of the paper. */
.v3-flagrow { margin-top: 20px; }
.v3-flagrow[hidden] { display: none; }
.v3-flagrow__err {
  margin: 10px 0 0;
  font-size: 12.5px; font-style: italic;
  color: var(--v3-mode-controle-ink);
}
.v3-flagrow__err[hidden] { display: none; }
.v3-flagrow #q-flag[aria-pressed="true"] {
  border-color: var(--v3-mode-controle);
  color: var(--v3-mode-controle-ink);
}

/* ── THE GLOBAL GENERATE-EXAM ──────────────────────────────────────────── */
/* Claret, because a paper is Le Controle's and the mode colours are identity.
   --v3-mode-controle-ink is the text-legal claret and is what the label sits
   on; the fill is the mode colour itself, which is a fill and not text. */
.v3-top__gen {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-height: 44px;
  background: var(--v3-mode-controle);
  color: var(--v3-btntext);
  border: 0;
  border-radius: var(--cxs-r-pill);
  padding: 8px 16px;
  font-family: var(--v3-sans, system-ui, sans-serif);
  font-size: 12.5px;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
}

.v3-top__gen:hover { background: var(--v3-mode-controle-ink); }

.v3-side a:focus-visible,
.v3-side button:focus-visible,
.v3-top a:focus-visible,
.v3-top button:focus-visible {
  outline: 2px solid var(--v3-signature);
  outline-offset: 2px;
  border-radius: var(--cxs-r-xs);
}

/* ── MOBILE: THE SIDEBAR BECOMES A BOTTOM TAB BAR ──────────────────────── */
/* Standard v1 §1: "Sidebar -> bottom tab bar". The locked mock turns it into a
   horizontally scrolling row at the TOP instead; the Standard and the
   directive's own test both say bottom tab bar, and the Standard wins over the
   mock. It is also what v3 already did at this breakpoint, and what RUN 79
   learned the hard way: a bar at the bottom is where a thumb is.

   THIS BLOCK IS LAST IN THE FILE ON PURPOSE. Three earlier `max-width: 720px`
   blocks style `.v3-nav` as the old top bar; these selectors are more specific
   AND later, so both ways of winning agree. */
@media (max-width: 720px) {
  body.v3 {
    padding-left: 0;
    padding-top: var(--v3-top-h);
    padding-bottom: var(--v3-tab-h);
  }

  body.v3.v3-bare { padding-bottom: 0; }

  .v3-side {
    top: auto;
    left: 0;
    right: 0;
    bottom: 0;
    width: auto;
    height: var(--v3-tab-h);
    flex-direction: row;
    align-items: stretch;
    border-right: 0;
    border-top: 1px solid var(--v3-hair);
  }

  /* The brand is not a destination, and a tab bar is four destinations. */
  .v3-side__brand { display: none; }

  .v3-side .v3-nav {
    flex: 3;
    padding: 0;
    display: flex;
    align-items: stretch;
  }

  .v3-side .v3-nav__items {
    flex: 1;
    flex-direction: row;
    justify-content: space-around;
    gap: 0;
  }

  .v3-side .v3-nav__item {
    flex-direction: column;
    justify-content: center;
    gap: 4px;
    padding: 6px 4px;
    border-radius: 0;
    font-size: 11.5px;
    text-align: center;
  }

  /* The 3px forest bar moves from the left edge to the top edge, because on a
     bottom bar "left" is not where the eye reads a current tab. */
  .v3-side .v3-nav__item[aria-current="page"] {
    background: transparent;
  }

  .v3-side .v3-nav__item[aria-current="page"]::before {
    left: 0;
    right: 0;
    top: 0;
    bottom: auto;
    width: auto;
    height: 3px;
    border-radius: 0 0 3px 3px;
  }

  .v3-side__foot {
    flex: 1;
    border-top: 0;
    border-left: 1px solid var(--v3-hair);
    padding: 0;
    display: flex;
  }

  .v3-side .v3-nav__you {
    flex: 1;
    flex-direction: column;
    justify-content: center;
    gap: 2px;
    border-radius: 0;
    background: transparent;
  }

  .v3-side .v3-nav__avatar { width: 22px; height: 22px; font-size: 12px; }
  .v3-side__who { display: none; }
  .v3-side__role { font-size: 11.5px; }

  .v3-top {
    left: 0;
    padding: 0 var(--cxs-sp-4);
  }

  /* The context still names the screen; the two right-hand controls do not fit
     beside it at 390px, and Generate exam is one tap away on Study Plans. */
  .v3-top__gen { display: none; }
  .v3-course__menu { right: 0; left: auto; }
}


/* ═══════════════════════════════════════════════════════════════════════════
   TODAY — LOGGED-IN CLIENT STANDARD v1 §3.2 (RUN 114 UNIT 2)
   Built to design/le-codex-studio.html's Today view.

   Three blocks lead the screen: the greeting, the active plan's next step, and
   a slim status strip. Everything here is additive to the .cxs-* layer the
   shell already ships; nothing existing is restyled, because the two blocks
   that survive BELOW the fold until UNIT 3 (readiness by topic, the practice
   modes) must keep rendering exactly as they do today.
   ═══════════════════════════════════════════════════════════════════════════ */

/* THE SINGLE FORWARD ACTION. Standard v1 §1: "Buttons: ink solid primary
   (--btn/--btntext), ghost = hairline; `.go` = forest for the single forward
   action." The mock's Continue on Today is that button.

   MEASURED, NOT ASSUMED: --v3-btntext on --v3-signature is 6.39:1, so white on
   forest clears AA. This is NOT the thing RUN 100 refused — that was filling a
   primary with the MODE colour, which on Drill is white on #BE842A at 3.22:1.
   The forest is the signature, not a mode, and it is the same green the active
   nav item and the correctness wash already use. */
.cxs-btn--go {
  background: var(--v3-signature);
  color: var(--v3-btntext);
  box-shadow: var(--v3-shadow-sm);
}
.cxs-btn--go:hover { background: var(--v3-signature-ink); box-shadow: var(--v3-shadow); }
.cxs-btn--go:active { transform: translateY(1px); box-shadow: var(--v3-shadow-sm); }
.cxs-btn--go svg { width: 15px; height: 15px; transition: transform .2s cubic-bezier(.2, 0, .2, 1); }
.cxs-btn--go:hover svg { transform: translateX(2px); }

/* THE LEDE. One derived sentence under the greeting, at a reading measure.
   §1: "Running text ~65ch". */
.cxs-lede {
  max-width: 62ch;
  margin: var(--cxs-sp-4) 0 0;
  font-size: 15.5px;
  line-height: 1.6;
  color: var(--v3-ink2);
}
.cxs-lede:empty { display: none; }

/* THE PLAN EYEBROW on the focus card. Cinzel small-caps, and forest rather
   than the meta ink because it names the plan this card belongs to — Standard
   v1 §3.2's "Your plan · <name>". */
.cxs-planline {
  font-family: var(--v3-smallcap, Georgia, serif);
  text-transform: uppercase;
  letter-spacing: .18em;
  font-size: 10.5px;
  font-weight: 600;
  color: var(--v3-signature-ink);
  margin: 0;
}
.cxs-planline:empty { display: none; }

/* THE STATUS STRIP AT THREE CELLS. §3.2 names exactly three readings —
   coverage, days worked, the held grade — where the mock draws a fourth
   ("82% Best mastery"). The fourth is not built: a mastery percentage on a
   student-facing surface is the reading this product states as a band or a
   number out of 100 and never as a percent, and the strip has no room to say
   which of those it means. */
.cxs-statrow--3 { grid-template-columns: repeat(3, 1fr); }

/* The "/51" after a coverage count: the same treatment .cxs-grade__big gives
   its suffix, so the denominator reads as context and not as a second figure. */
.cxs-stat .v .of {
  font-size: 17px;
  color: var(--v3-ink3);
  font-weight: 400;
  letter-spacing: 0;
}

/* A WITHHELD GRADE IS A WORD, NOT A NUMBER, and it is set in the serif italic
   the mock uses for it so it never reads as a band. */
.cxs-stat .v--held {
  font-style: italic;
  color: var(--v3-ink3);
  font-size: 24px;
  letter-spacing: 0;
}

/* The one caption a cell may carry: how far off a reading is. Sits under the
   key so the cell reads value, name, then the caveat. */
.cxs-stat .c {
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--v3-ink3);
  margin-top: 5px;
}
.cxs-stat .c:empty { display: none; }

/* WHAT SURVIVES BELOW THE FOLD UNTIL UNIT 3. The mock's Today is the three
   blocks above and nothing else, and the readiness rail and the practice modes
   are not in it. They are kept for one more unit because removing them now
   would take away the only way a student can choose a mode: Standard v1 §3.5
   gives readiness to Progress, and the Director's ruling folds Practice into
   Study Plans AT UNIT 3. A quieter separator marks them as the older half of
   the page rather than part of the new composition. */
.cxs-until-unit3 {
  margin-top: var(--cxs-sp-10);
  padding-top: var(--cxs-sp-7);
  border-top: 1px solid var(--v3-hair);
}

@media (max-width: 720px) {
  .cxs-statrow--3 { grid-template-columns: 1fr; }
  .cxs-statrow--3 .cxs-stat { border-left: 0; border-top: 1px solid var(--v3-hair); }
  .cxs-statrow--3 .cxs-stat:first-child { border-top: 0; }
}


/* ═══════════════════════════════════════════════════════════════════════════
   STUDY PLANS — LOGGED-IN CLIENT STANDARD v1 §3.3 (RUN 114 UNIT 3)
   Built to design/le-codex-studio.html's Study Plans view.
   ═══════════════════════════════════════════════════════════════════════════ */

.cxs-plans {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--cxs-sp-4);
  margin-bottom: var(--cxs-sp-8);
}

.cxs-plan {
  background: var(--v3-card);
  border: 1px solid var(--v3-hair);
  border-radius: var(--cxs-r-lg);
  padding: 22px 24px;
  display: flex;
  flex-direction: column;
}

/* THE HOME PLAN IS PINNED AND IT LOOKS IT. The forest border and the shadow
   are the one card on this screen carrying either: §1 gives the shadow to the
   single focus card, and on Study Plans that is the plan a student always has. */
.cxs-plan--home {
  border-color: var(--v3-signature);
  box-shadow: var(--v3-shadow);
}

.cxs-plan__name {
  font-family: var(--v3-serif, Georgia, serif);
  font-size: 20px;
  font-weight: 500;
  letter-spacing: -.01em;
  color: var(--v3-ink);
  margin: 8px 0 4px;
}

.cxs-plan__meta { font-size: 12.5px; color: var(--v3-ink2); }
.cxs-plan__meta:empty { display: none; }

.cxs-plan__bar {
  height: 6px;
  background: var(--v3-chip);
  border-radius: var(--cxs-r-pill);
  overflow: hidden;
  margin: 16px 0 8px;
}
.cxs-plan__bar i {
  display: block;
  height: 100%;
  background: var(--v3-signature);
  border-radius: var(--cxs-r-pill);
}

.cxs-plan__cov { font-size: 11.5px; color: var(--v3-ink3); margin: 0; }
.cxs-plan__cap { margin-top: var(--cxs-sp-3); }

.cxs-plan__acts {
  display: flex;
  gap: var(--cxs-sp-2);
  flex-wrap: wrap;
  margin-top: auto;
  padding-top: var(--cxs-sp-5);
}

.cxs-btn--sm { padding: 8px 14px; min-height: 44px; font-size: 13px; }

/* The dashed card is an invitation, not a plan. It carries no shadow and no
   accent: one accent per screen, and the home plan has it. */
.cxs-plan--new {
  border-style: dashed;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: var(--cxs-sp-3);
  min-height: 190px;
}

/* A SMALL NOTE IS NOT A LABEL. `.cxs-micro` uppercases with .16em tracking,
   which is right for "MASTERY / 100" and wrong for a sentence: the voice canon
   bans ALL-CAPS shouting and reserves caps for the wordmark and display. The
   first render of Study Plans showed "TAP A STATE TO CYCLE IT. TO START, THEN
   STUDYING, THEN MASTERED." across the card, which is a sentence at a shout —
   invisible in the source, because the markup is sentence case and the CSS
   does the shouting. (RUN 114 UNIT 3) */
.cxs-note {
  font-size: 12px;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  line-height: 1.5;
  color: var(--v3-ink3);
  margin: 0;
}

/* ── THE SYLLABUS TREE ──────────────────────────────────────────────────── */
.cxs-tree { padding: var(--cxs-sp-6) var(--cxs-sp-7); margin-bottom: var(--cxs-sp-6); }

.cxs-tree__head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--cxs-sp-4);
  flex-wrap: wrap;
  margin-bottom: var(--cxs-sp-4);
}
.cxs-tree__head h3 {
  font-family: var(--v3-serif, Georgia, serif);
  font-size: 19px; font-weight: 500; letter-spacing: -.01em;
  margin: 0; color: var(--v3-ink);
}
.cxs-tree__note { margin-top: var(--cxs-sp-5); max-width: 66ch; }

.cxs-topic { border-top: 1px solid var(--v3-hair); }
.cxs-topic:first-child { border-top: 0; }

.cxs-topic__row {
  display: flex;
  align-items: center;
  gap: var(--cxs-sp-3);
  width: 100%;
  min-height: 44px;
  padding: 13px 4px;
  border: 0;
  background: none;
  text-align: left;
  cursor: pointer;
  font-family: var(--v3-sans, system-ui, sans-serif);
  color: var(--v3-ink);
}
.cxs-topic__chev {
  color: var(--v3-ink3);
  font-size: 13px;
  display: inline-block;
  transition: transform .15s cubic-bezier(.2, 0, .2, 1);
}
.cxs-topic.is-open .cxs-topic__chev { transform: rotate(90deg); }
.cxs-topic__name { font-size: 14px; font-weight: 600; flex: 1; }
.cxs-topic__count { font-size: 11.5px; color: var(--v3-ink3); }

.cxs-subs { display: none; padding: 0 0 10px 24px; }
.cxs-topic.is-open .cxs-subs { display: block; }

.cxs-sub {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--cxs-sp-4);
  padding: 7px 10px;
  border-radius: var(--cxs-r-sm);
}
.cxs-sub:hover { background: var(--v3-surface2); }
.cxs-sub__name { font-size: 13.5px; color: var(--v3-ink); }

/* THE STATE IS A WORD FIRST. Colour never carries it alone (§1), so the
   button's TEXT is the state and the tint is the second signal. */
.cxs-state {
  font-family: var(--v3-smallcap, Georgia, serif);
  font-size: 9.5px;
  letter-spacing: .08em;
  text-transform: uppercase;
  font-weight: 600;
  border-radius: var(--cxs-r-pill);
  padding: 5px 11px;
  min-height: 44px;
  border: 1px solid var(--v3-hair);
  background: var(--v3-card);
  color: var(--v3-ink3);
  cursor: pointer;
  white-space: nowrap;
  flex: none;
}
.cxs-state--studying {
  color: var(--v3-mode-study-ink);
  border-color: var(--v3-mode-study);
  background: var(--v3-card);
}
.cxs-state--mastered {
  color: var(--v3-signature-ink);
  border-color: var(--v3-signature);
  background: var(--v3-signature-wash);
}

/* ── THE EXAM GENERATOR ─────────────────────────────────────────────────── */
.cxs-gen { padding: var(--cxs-sp-6) var(--cxs-sp-7); }
.cxs-gen h3 {
  font-family: var(--v3-serif, Georgia, serif);
  font-size: 19px; font-weight: 500; letter-spacing: -.01em;
  margin: 0 0 var(--cxs-sp-3); color: var(--v3-ink);
}

.cxs-scopes {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--cxs-sp-3);
  margin-top: var(--cxs-sp-5);
}

.cxs-scope {
  display: block;
  text-align: left;
  border: 1px solid var(--v3-hair);
  border-radius: var(--cxs-r-lg);
  background: var(--v3-card);
  padding: 18px 20px;
  cursor: pointer;
  font-family: var(--v3-sans, system-ui, sans-serif);
  transition: border-color .15s cubic-bezier(.2, 0, .2, 1),
              background .15s cubic-bezier(.2, 0, .2, 1);
}
.cxs-scope:hover { border-color: var(--v3-hair2); }
.cxs-scope.is-on {
  border-color: var(--v3-signature);
  background: var(--v3-signature-wash);
}
.cxs-scope__h {
  display: flex; align-items: center; gap: var(--cxs-sp-2); flex-wrap: wrap;
  font-size: 14.5px; font-weight: 600; color: var(--v3-ink);
}
/* --v3-ink2, NOT ink3, AND THE REASON IS A MEASUREMENT. This tag reads
   "true mock" and "focused paper" — the two words that tell a student WHICH
   paper they are about to sit — and on the selected scope's wash in dark, ink3
   measured 4.09:1, under AA. Found the day Study Plans entered the rendered
   contrast sweep and not before, because nothing had ever looked at this
   screen. */
.cxs-scope__tag {
  font-family: var(--v3-smallcap, Georgia, serif);
  font-size: 8.5px; letter-spacing: .1em; text-transform: uppercase;
  font-weight: 600; color: var(--v3-ink2);
  border: 1px solid var(--v3-hair); border-radius: var(--cxs-r-pill);
  padding: 3px 8px;
}
.cxs-scope__d {
  display: block; font-size: 12.5px; line-height: 1.5;
  color: var(--v3-ink2); margin-top: 7px;
}

.cxs-genrow {
  display: flex; align-items: center; gap: var(--cxs-sp-3);
  flex-wrap: wrap; margin-top: var(--cxs-sp-4);
}
.cxs-select {
  min-height: 44px; padding: 10px 12px;
  border: 1px solid var(--v3-hair); border-radius: var(--cxs-r-sm);
  background: var(--v3-card); color: var(--v3-ink);
  font-family: var(--v3-sans, system-ui, sans-serif); font-size: 14px;
}

.cxs-papers { display: flex; gap: var(--cxs-sp-2); margin-top: var(--cxs-sp-4); flex-wrap: wrap; }
.cxs-paper {
  border: 1px solid var(--v3-hair); border-radius: var(--cxs-r-sm);
  background: var(--v3-card); color: var(--v3-ink2);
  padding: 8px 15px; min-height: 44px; font-size: 13px; cursor: pointer;
  font-family: var(--v3-sans, system-ui, sans-serif);
}
.cxs-paper.is-on {
  background: var(--v3-btn); color: var(--v3-btntext); border-color: var(--v3-btn);
}

.cxs-gen__cta { margin-top: var(--cxs-sp-5); align-items: center; }
.cxs-gen__cta .cxs-note { margin: 0; max-width: 46ch; }
#gen-go[aria-disabled="true"] { opacity: .55; pointer-events: none; }

.cxs-plan :focus-visible,
.cxs-tree :focus-visible,
.cxs-gen :focus-visible {
  outline: 2px solid var(--v3-signature);
  outline-offset: 2px;
  border-radius: var(--cxs-r-xs);
}

@media (max-width: 1100px) {
  .cxs-plans { grid-template-columns: repeat(2, 1fr); }
  .cxs-scopes { grid-template-columns: 1fr; }
}

@media (max-width: 720px) {
  .cxs-plans { grid-template-columns: 1fr; }
  .cxs-tree, .cxs-gen { padding: var(--cxs-sp-5) var(--cxs-sp-5); }
  .cxs-sub { flex-wrap: wrap; }
}


/* ── MAKING A PLAN (RUN 114 UNIT 3) ─────────────────────────────────────── */
.cxs-newplan { padding: var(--cxs-sp-6) var(--cxs-sp-7); margin-bottom: var(--cxs-sp-6); }
.cxs-newplan h3 {
  font-family: var(--v3-serif, Georgia, serif);
  font-size: 19px; font-weight: 500; letter-spacing: -.01em;
  margin: 0 0 var(--cxs-sp-4); color: var(--v3-ink);
}
.cxs-npgrid {
  display: grid; grid-template-columns: 2fr 1fr;
  gap: var(--cxs-sp-4); margin-bottom: var(--cxs-sp-5);
}
.cxs-npfield { display: flex; flex-direction: column; gap: 6px; }
.cxs-input {
  min-height: 44px; padding: 10px 12px;
  border: 1px solid var(--v3-hair); border-radius: var(--cxs-r-sm);
  background: var(--v3-card); color: var(--v3-ink);
  font-family: var(--v3-sans, system-ui, sans-serif); font-size: 14px;
}
.cxs-input:focus-visible { outline: 2px solid var(--v3-signature); outline-offset: 2px; }
.cxs-npcover { margin: 0 0 var(--cxs-sp-3); }
.cxs-nptopics { display: flex; flex-wrap: wrap; gap: var(--cxs-sp-2); }

/* A CHOSEN TOPIC CARRIES aria-pressed AS WELL AS A TINT: colour is never the
   only signal, and a toggle a screen reader cannot read is not a toggle. */
.cxs-pick {
  border: 1px solid var(--v3-hair); border-radius: var(--cxs-r-pill);
  background: var(--v3-card); color: var(--v3-ink2);
  padding: 8px 14px; min-height: 44px; font-size: 13px; cursor: pointer;
  font-family: var(--v3-sans, system-ui, sans-serif);
}
.cxs-pick:hover { border-color: var(--v3-hair2); }
.cxs-pick.is-on {
  border-color: var(--v3-signature);
  background: var(--v3-signature-wash);
  color: var(--v3-signature-ink);
  font-weight: 600;
}
.cxs-np__cta { margin-top: var(--cxs-sp-5); align-items: center; }
.cxs-np__cta .cxs-note { margin: 0; }
.cxs-newplan :focus-visible {
  outline: 2px solid var(--v3-signature); outline-offset: 2px;
  border-radius: var(--cxs-r-xs);
}
@media (max-width: 720px) {
  .cxs-npgrid { grid-template-columns: 1fr; }
  .cxs-newplan { padding: var(--cxs-sp-5); }
}


/* ═══════════════════════════════════════════════════════════════════════════
   THE SESSION — LOGGED-IN CLIENT STANDARD v1 §3.4 (RUN 114 UNIT 4)
   "per sub-question, the paper on the left and its mark scheme in the facing
   column on the right, aligned per part."
   ═══════════════════════════════════════════════════════════════════════════ */

/* ONE GRID, TWO COLUMNS, ONE ROW PER PART. Alignment is the requirement, and a
   grid is the only construction that cannot drift: the row IS the sub-question,
   so a tall set of options and a short mark scheme still start on the same
   line. Two side-by-side lists would agree on the first part and disagree by
   the third. */
.v3-qa {
  display: grid;
  grid-template-columns: 1fr 1fr;
  border: 1px solid var(--v3-hair);
  border-radius: var(--cxs-r-md);
  overflow: hidden;
  background: var(--v3-card);
}

.v3-qa__h {
  font-family: var(--v3-smallcap, Georgia, serif);
  text-transform: uppercase;
  letter-spacing: .14em;
  font-size: 10px;
  font-weight: 600;
  color: var(--v3-ink3);
  background: var(--v3-card);
  border-bottom: 1px solid var(--v3-hair);
  padding: 11px var(--cxs-sp-6);
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--cxs-sp-3);
}
.v3-qa__h--m { border-left: 1px solid var(--v3-hair); }

/* The reveal rule, in words, on the column it governs. Not small-caps: it is a
   sentence, and .v3-qa__h is a label. */
.v3-qa__when {
  font-family: var(--v3-sans, system-ui, sans-serif);
  font-style: normal;
  text-transform: none;
  letter-spacing: 0;
  font-size: 11px;
  font-weight: 400;
  color: var(--v3-ink3);
}

.v3-qa__q,
.v3-qa__m {
  padding: var(--cxs-sp-5) var(--cxs-sp-6);
  border-top: 1px solid var(--v3-hair);
}
.v3-qa > .v3-qa__q:nth-of-type(1) { border-top: 0; }

/* The facing column sits on the recessed surface, which is what makes it read
   as the scheme beside the paper rather than as more paper. */
.v3-qa__m {
  border-left: 1px solid var(--v3-hair);
  background: var(--v3-surface2);
}

.v3-part__marks {
  font-family: var(--v3-smallcap, Georgia, serif);
  font-size: 10px;
  letter-spacing: .1em;
  color: var(--v3-ink3);
  margin-left: var(--cxs-sp-3);
  white-space: nowrap;
}

/* ── THE VEIL ───────────────────────────────────────────────────────────
   §3.4's mode-timed reveal. The cell is present and its BODY is hidden, so
   nothing moves when it opens: the scheme is not rendered late, it is
   unveiled. */
.v3-qa[data-scheme="veiled"] .v3-qa__body { display: none; }
.v3-qa[data-scheme="open"] .v3-qa__veil { display: none; }
/* THE MOCK SETS THIS IN SERIF ITALIC AND IT IS INTER HERE. §1 is explicit:
   "EB Garamond = hero words, titles, and maths notation (italic). Inter = all
   functional/UI text." A line telling a student what will open a column is
   functional text, and tests/test_type_law_rendered.py enforces that rule on
   the rendered DOM. One line either way, and the rule is the founder's own.

   AND IT IS --v3-ink2, NOT ink3. Measured: ink3 on the DARK recessed surface
   is 4.16:1, below AA — dark's --v3-surface2 (#242719) is LIGHTER than its
   card (#1B1E15), so the "recessed" cell is raised there and a meta ink loses
   its margin. ink2 is 7.24:1 on the same ground. */
.v3-qa__veil {
  margin: 0;
  font-family: var(--v3-sans, system-ui, sans-serif);
  font-size: 13.5px;
  color: var(--v3-ink2);
}

/* ── AN AWARDING LINE ───────────────────────────────────────────────── */
.v3-msline {
  display: flex;
  gap: var(--cxs-sp-3);
  align-items: flex-start;
  padding: 6px 8px;
  margin: 0 -8px;
  border-radius: var(--cxs-r-xs);
  font-size: 14px;
  line-height: 1.55;
}

/* THE IB CODE. Dotted underline and a help cursor say "there is more here"
   without an icon; the gloss itself is `title` plus `aria-label`, so it is
   reachable by hover, by keyboard focus and by a screen reader. */
.v3-msline__code {
  font-family: var(--v3-smallcap, Georgia, serif);
  font-size: 9.5px;
  letter-spacing: .05em;
  font-weight: 600;
  background: var(--v3-chip);
  color: var(--v3-ink2);
  border-radius: 5px;
  padding: 3px 7px;
  margin-top: 2px;
  min-width: 38px;
  text-align: center;
  white-space: nowrap;
  flex: none;
}
.v3-msline__code[data-gloss] {
  border-bottom: 1px dotted var(--v3-ink3);
  cursor: help;
}
.v3-msline__code[data-gloss]:focus-visible {
  outline: 2px solid var(--v3-signature);
  outline-offset: 2px;
}

.v3-msline__step { flex: 1; min-width: 0; color: var(--v3-ink); }
/* NOT FLAGGED, AND NOT CLEAN EITHER. This and the total below sit inside the
   veiled body, so no gate had ever measured them: the contrast sweep reads the
   page as it loads, and the scheme is closed then. They take the same ink as
   the veil for the same measured reason, rather than waiting to be found the
   first time a gate happens to open the column. */
.v3-msline__note {
  display: block;
  font-size: 12px;
  color: var(--v3-ink2);
  margin-top: 4px;
}

.v3-qa__total {
  margin: var(--cxs-sp-3) 0 0;
  padding-top: var(--cxs-sp-3);
  border-top: 1px dashed var(--v3-hair);
  font-family: var(--v3-smallcap, Georgia, serif);
  font-size: 10px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--v3-ink2);
}

/* ── THE TWO CORRECTNESS WASHES ─────────────────────────────────────────
   §1: "earned/correct = --signature-wash fill + --signature-ink text;
   missed/incorrect = claret wash + #8C3B4E text. Quiet, never loud."

   THE WASH IS NEVER THE ONLY SIGNAL. Each line also carries data-verdict, and
   the reveal beside it says in words whether the answer was right — colour
   alone would leave a student who cannot separate these two hues with no
   reading at all. */
.v3-msline[data-verdict="earned"] {
  background: var(--v3-signature-wash);
  color: var(--v3-signature-ink);
}
.v3-msline[data-verdict="earned"] .v3-msline__step { color: var(--v3-signature-ink); }
.v3-msline[data-verdict="missed"] {
  background: var(--v3-missed-wash);
  color: var(--v3-mode-controle-ink);
}
.v3-msline[data-verdict="missed"] .v3-msline__step { color: var(--v3-mode-controle-ink); }

/* An "I'm not sure" earns nothing and misses nothing: it did not count, so it
   wears neither wash. Saying it in the absence of colour is the honest
   rendering of a question that was not marked. */
.v3-msline[data-verdict="notcounted"] { background: transparent; }

/* ── THE HONEST THIRD OPTION ────────────────────────────────────────────
   Set apart from the answers above it by a rule, because it is not one of
   them: it is a way of saying you do not know. It carries no accent — nothing
   here should look like the safe choice OR the wrong one. */
.v3-option--idk {
  margin-top: var(--cxs-sp-4);
  border-top: 1px solid var(--v3-hair);
  padding-top: var(--cxs-sp-4);
  border-radius: var(--cxs-r-sm);
}
.v3-option--idk .v3-option__key {
  background: transparent;
  color: var(--v3-ink3);
  border-color: var(--v3-hair);
}
.v3-option__hint {
  display: block;
  font-size: 12px;
  color: var(--v3-ink3);
  margin-top: 3px;
}

@media (max-width: 900px) {
  /* §1's responsive rule: "the question|mark-scheme facing columns stack
     vertically". The scheme keeps its recessed ground and gains a top rule, so
     it still reads as the facing half rather than as more question. */
  .v3-qa { grid-template-columns: 1fr; }
  .v3-qa__h { display: none; }
  .v3-qa__m { border-left: 0; border-top: 1px solid var(--v3-hair); }
  .v3-qa__q { border-top: 1px solid var(--v3-hair); }
}


/* §3.4 OPENS WITH "FULL-WIDTH", AND THE SESSION WAS 760px (RUN 114 UNIT 4).
   That measure was right for a single column of question and reveal; it is not
   right for a paper facing its mark scheme, where 760px gives each column 360
   and breaks a rendered equation across three lines. The Standard's own
   measure is the shell's: content max ~1220-1280.

   THE READING MEASURE IS KEPT WHERE IT IS A READING. The stem and the reveal
   still sit at ~65ch inside the wider frame, because §1's "running text ~65ch"
   is about prose and the facing grid is not prose. */
.v3-session { max-width: var(--cxs-edge); }

/* AND THE QUESTION CARD ITSELF. `.v3-card` carries `max-width: 620px` —
   "the reading column, spec §4" — which is right for every other card in the
   product and is the exact cap Standard v1 §3.1 names as the failure on a
   full-width surface. The card that HOLDS THE FACING GRID is widened, and only
   that one: at 620px each column gets 300px and a rendered equation breaks
   across three lines. Every other v3 card keeps its reading measure. */
#q-card { max-width: none; }
.v3-session .v3-stem,
.v3-session .v3-reveal { max-width: 68ch; }

@media (max-width: 900px) {
  .v3-session { max-width: 760px; }
}

/* ══ THE PLAN HUB (RUN 116 Track B) ═══════════════════════════════════════
   Standard v1: "opening a plan lands on a calm hub — its next step + a
   choose-a-mode row + the syllabus tree to pick any subtopic."

   CALM IS A MEASURABLE THING HERE AND NOT A MOOD: one shadow on the page
   (this card is the focus card while it is open), one forest action, and the
   four ways drawn as equals so none of them is the answer. Colour is the
   only thing that separates the modes, and it is never the only signal — each
   row carries its mode's NAME as a tag. */
/* THE SHADOW IS A TOKEN. `--v3-shadow` is the Standard's own, cast from
   --ink's channels and redefined for dark; a literal rgba() here fails
   tests/test_v3_standard.py's token-only-colour law, and rightly — it is the
   one value on the card that would not have followed the palette. */
.cxs-hub {
  /* .cxs-card carries background, border, radius and shadow but NOT padding —
     each card sets its own — so without this the eyebrow and the Continue
     button sat flush against the card's edges. Standard v1 §1: "padding
     ~28-30". */
  padding: 28px 30px;
  border-left: 3px solid var(--v3-signature);
  box-shadow: var(--v3-shadow);
  /* THE TOP BAR IS FIXED AND 64px TALL, so scrollIntoView() lands the hub's
     first line UNDERNEATH it. scroll-margin is the browser's own answer to
     that and needs no JavaScript offset arithmetic to drift. */
  scroll-margin-top: 84px;
}
.cxs-hub__eyebrow { margin: 0 0 6px; }
.cxs-hub__title {
  font-family: var(--v3-serif, Georgia, serif);
  font-size: 26px;
  line-height: 1.2;
  font-weight: 500;
  color: var(--v3-ink);
  margin: 0 0 6px;
  text-wrap: balance;
}
.cxs-hub__next {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  flex-wrap: wrap;
  padding: 14px 0 4px;
  border-top: 1px solid var(--v3-hair);
  margin-top: 14px;
}
.cxs-hub__stepname {
  font-family: var(--v3-serif, Georgia, serif);
  font-size: 17px;
  color: var(--v3-ink);
  margin: 2px 0 0;
}
.cxs-hub__or { margin: 18px 0 8px; }

/* THE FOUR WAYS. A grid rather than a row, because at 1280px four side by
   side gives each about 280px and the descriptions are two lines; below 900
   they stack, which is the same behaviour /v3/work already has. */
.cxs-hub__modes {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}
.cxs-way {
  display: flex;
  align-items: stretch;
  gap: 12px;
  padding: 14px 16px;
  border: 1px solid var(--v3-hair);
  border-radius: 12px;
  background: var(--v3-card);
  text-decoration: none;
  color: inherit;
  /* THE DECLARED CURVE, NOT `ease`. tests/test_interface_discipline.py holds
     the v3 sheet to ONE easing so the whole client moves the same way; `ease`
     is the browser's default and is a second curve smuggled in. */
  transition: border-color .18s cubic-bezier(.2, 0, .2, 1),
              background .18s cubic-bezier(.2, 0, .2, 1);
}
.cxs-way:hover { border-color: var(--v3-hair2); background: var(--v3-surface2); }
.cxs-way:focus-visible {
  outline: 2px solid var(--v3-signature);
  outline-offset: 2px;
}
/* THE MODE'S COLOUR IS AN EDGE, NEVER A FILL AND NEVER THE TEXT. Law C1: the
   drill amber may not carry ink on cream, so it is a 3px rule and the name
   beside it stays --v3-ink. */
.cxs-way__edge {
  flex: 0 0 3px;
  border-radius: 2px;
  background: var(--v3-signature);
}
.cxs-way.v3-mode-diagnostic .cxs-way__edge { background: var(--v3-mode-diagnostic); }
.cxs-way.v3-mode-study      .cxs-way__edge { background: var(--v3-mode-study); }
.cxs-way.v3-mode-drill      .cxs-way__edge { background: var(--v3-mode-drill); }
.cxs-way.v3-mode-controle   .cxs-way__edge { background: var(--v3-mode-controle); }
.cxs-way__body { min-width: 0; }
.cxs-way__h {
  font-family: var(--v3-serif, Georgia, serif);
  font-size: 16px;
  font-weight: 500;
  color: var(--v3-ink);
  margin: 0 0 3px;
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
}
/* NOT TAUPE. Law K5 forbids the taupe on a text property, and
   tests/test_v3_standard.py caught this before it shipped. --v3-ink2 is what
   .cxs-scope__tag — the same shape of small-cap tag, two screens away —
   already uses. */
.cxs-way__tag {
  font-family: var(--v3-smallcap, Georgia, serif);
  font-size: 9.5px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--v3-ink2);
}
.cxs-way__d {
  font-family: var(--v3-sans, system-ui, sans-serif);
  font-size: 13px;
  line-height: 1.45;
  color: var(--v3-ink2);
  margin: 0;
}
.cxs-hub__tree { margin-top: 14px; }

/* ── TAPPING A SUBTOPIC STARTS IT ────────────────────────────────────────
   The name is a button now. It must not LOOK like a button in a list of
   fifty-one rows — a wall of chrome is the opposite of the tree's job — so it
   is the same text it always was, with the affordance carried by hover,
   focus and the underline that appears on both. */
.cxs-sub__start { display: flex; align-items: center; gap: 10px; min-width: 0; flex-wrap: wrap; }
/* 44px OF TARGET, AND NOT A PIXEL OF EXTRA CHROME. The name reads as the
   same text it always was; what changed is the size of the thing a thumb has
   to hit. It was 17px tall — the text's own line box — and
   tests/test_v3_a11y.py caught it on all fifty-one rows. The height is taken
   as a MIN so a long name that wraps still grows, and the underline is on the
   inner span rather than the button so it hugs the words instead of a
   44px-tall box. */
button.cxs-sub__name {
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  font-family: var(--v3-sans, system-ui, sans-serif);
  font-size: 13.5px;
  color: var(--v3-ink);
  text-align: left;
  cursor: pointer;
  border-bottom: 0;
}
button.cxs-sub__name:hover { color: var(--v3-signature-ink); }
button.cxs-sub__name:focus-visible {
  outline: 2px solid var(--v3-signature);
  outline-offset: 3px;
  border-radius: 2px;
}
.cxs-sub__ways { display: inline-flex; gap: 8px; }
.cxs-sub__ways[hidden] { display: none; }

@media (max-width: 900px) {
  .cxs-hub__modes { grid-template-columns: minmax(0, 1fr); }
}

/* ══ PROGRESS: A WEAK SPOT IS A DOOR (RUN 116 Track C) ════════════════════
   Standard v1's second way into a subtopic is "from Progress: weak spot ->
   subtopic detail -> Practise", so each mastery row is now a control with a
   detail under it.

   THE GRID MOVES TO THE BUTTON, IT IS NOT REDEFINED. `.cxs-mrow` was
   `grid-template-columns: 1fr 44px` with the bar spanning both, and wrapping
   its three children in a button would have made that button the single grid
   child and collapsed the layout. The row becomes a plain block that holds the
   control and its detail; the control inherits the grid the row used to have,
   so `.cxs-mrow .mname`, `.flag`, `.mnum` and `.cxs-bar` keep working
   unchanged — they were always descendant selectors. */
.cxs-mrow { display: block; }
.cxs-mrow__open {
  appearance: none;
  width: 100%;
  background: none;
  border: 0;
  text-align: left;
  cursor: pointer;
  font: inherit;
  color: inherit;
  /* 44px OF TARGET. The plan hub's subtopic control shipped at 17px and
     test_v3_a11y caught it on fifty-one rows; this one is a full row and is
     given the floor explicitly rather than relying on its content to reach it. */
  min-height: 44px;
  display: grid;
  grid-template-columns: 1fr 44px;
  gap: 9px var(--cxs-sp-4);
  align-items: center;
  padding: 13px 0;
  border-radius: 4px;
}
.cxs-mrow__open:hover .mname { color: var(--v3-signature-ink); }
.cxs-mrow__open:focus-visible {
  outline: 2px solid var(--v3-signature);
  outline-offset: 2px;
}

.cxs-mdet {
  padding: 2px 0 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.cxs-mdet[hidden] { display: none; }
.cxs-mdet__say {
  font-family: var(--v3-sans, system-ui, sans-serif);
  font-size: 13px;
  line-height: 1.5;
  color: var(--v3-ink2);
  margin: 0;
  max-width: 62ch;
}
.cxs-mdet__acts { display: flex; gap: 8px; flex-wrap: wrap; }

/* ── THE FILTER AND THE FIND BOX ─────────────────────────────────────────
   Two controls, one question: which of this student's readings am I looking
   at. They sit above the list, hairline-separated from it, and are small
   because they are instruments and not the subject. */
.cxs-mfilter, .cxs-mfind {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 0 10px;
}
.cxs-mfind { margin-bottom: 14px; padding-bottom: 14px;
             border-bottom: 1px solid var(--v3-hair); }
.cxs-mfilter .cxs-micro, .cxs-mfind .cxs-micro { flex: none; }
.cxs-select--sm, .cxs-input--sm {
  font-size: 13px;
  padding: 8px 10px;
  min-height: 44px;
  flex: 1 1 auto;
  min-width: 0;
}
