/* Ship Your AI Agent Product — the course renderer.

   SPEAKS "AIR", THE SITE'S DESIGN SYSTEM (app.css v3), NOT ITS OWN PALETTE.
   This file used to carry the dark Foundry palette so a member walking out of
   /act1 into the course did not feel like they left the site. That reasoning
   was right about continuity and wrong about which side to be continuous WITH:
   index, login and community are all Air — light, typography-led, one accent —
   so the course was the surface that felt like a different product, not the
   one holding the line. The tokens below are re-pointed at Air's values, and
   the game surfaces (/act1, /trial) deliberately stay dark: a story act is a
   world you enter, a course is a document you work in.

   Kept as its own stylesheet for the same reason act1.css is: course.html
   stays independent of the community shell, and neither page can break the
   other's layout by editing a shared rule.

   ONE ACCENT, NOT FOUR. Module cards were typed by colour — cyan read, violet
   practice, amber build — on the argument that a course list is scanned for
   "what kind of work is this". The list already says so in words: renderHub
   writes typeLabel(m) into .cv-mod-meta on every card. So the four hues were
   restating a label, and four accents on one page is the thing that stops a
   layout reading as one object. The type survives as text; colour is spent
   only where it means something you cannot read — done, and where to click. */

/* THE TOKENS LIVE ON :root, NOT ON .cv-root, AND THAT IS LOAD-BEARING.
   posttrain-ta.js mounts the teaching-assistant panel position:fixed on
   <body> — outside .cv-root — so a token scoped to .cv-root does not reach it
   and every var(--cv-*) in posttrain-ta.css silently falls back to its
   literal. That is not theoretical: it is how .pt-ta-where kept a 3.48:1 grey
   after this file had already been fixed to 4.91:1, and the only reason it was
   caught is that the contrast test opens the panel. On :root the panel and the
   page cannot disagree. */
:root {
    /* Air's values, under this file's names, so every rule below keeps
       working and nothing has to hunt for the site's tokens. */
    --cv-ink: #1d1d1f;
    --cv-ink-2: #6e6e73;
    /* NOT Air's #86868b, and do not "restore" it. Air is a marketing system:
       #86868b carries captions under big pictures, where it is decoration. The
       same grey here lands on "Read · 12 min" and "0 of 7 done" at 13px, which
       is INFORMATION, and it measures 3.6:1 on white — under AA, and under it
       on the exact surface a member squints at on a phone outdoors. Apple ship
       that grey and Apple fail this check with it. There are two text greys on
       this page, not three; the third tier is carried by size and weight,
       which is how the hierarchy on an Apple page mostly works anyway.
       scripts/test_course_contrast.py fails the build if this is lightened. */
    --cv-ink-3: #6e6e73;
    --cv-page: #fbfbfd;
    --cv-panel: #ffffff;
    --cv-sunk: #f5f5f7;
    --cv-line: #e8e8ed;
    --cv-line-2: #d2d2d7;
    --cv-accent: #7c3aed;
    --cv-accent-2: #6d28d9;
    --cv-accent-soft: #f3eefe;
    --cv-ok: #16a34a;
    --cv-ok-soft: #e9f7ee;
    --cv-danger: #c0342c;

    /* Depth is a hairline plus almost nothing. A card that needs a heavy
       shadow to separate from the page is a card on the wrong background. */
    --cv-lift: 0 1px 2px rgba(0, 0, 0, .04), 0 8px 24px rgba(0, 0, 0, .04);
    --cv-lift-2: 0 2px 6px rgba(0, 0, 0, .06), 0 18px 44px rgba(0, 0, 0, .10);
    --cv-main: 720px;
    --cv-prose: 710px;
}

/* The page surface itself — layout only, no tokens. */
.cv-root {
    min-height: 100vh;
    background: var(--cv-page);
    color: var(--cv-ink);
    padding: 26px 16px 90px;
}

.cv-wrap { max-width: var(--cv-main); margin: 0 auto; }

/* Wider canvas ONLY for pre-course modules (`body.cv-wide` on start.html).
   Nav and rails stay wide; all lesson content shares the prose column. */
.cv-wide.cv-prestart { --cv-main: 880px; }
.cv-wide.cv-prestart #cv-module .cv-m-head,
.cv-wide.cv-prestart #cv-module #cv-m-body,
.cv-wide.cv-prestart #cv-module #cv-m-body > * {
    max-width: var(--cv-prose);
    margin-left: auto;
    margin-right: auto;
}

/* Focus is VISIBLE, and it is visible on a light background — the dark theme
   could lean on a glow, this cannot. One ring, one colour, everywhere. */
.cv-root :focus-visible {
    outline: 2px solid var(--cv-accent);
    outline-offset: 2px;
    border-radius: 8px;
}

.cv-gate { text-align: center; padding: 70px 10px; }
.cv-gate h2 { font-size: 28px; font-weight: 600; letter-spacing: -0.02em; margin-bottom: 12px; }
.cv-gate p { color: var(--cv-ink-2); line-height: 1.8; margin-bottom: 22px; }

/* ---------- hub ---------- */

.cv-head { margin-bottom: 26px; }

/* The course cover. Runs at its natural height — the file is delivered at the
   ratio it is shown at (scripts/posttrain_art_build.py), so there is no
   object-fit here to re-crop it at a width nobody chose. */
.cv-hero {
    position: relative;
    margin: 0 0 22px;
    border-radius: 18px;
    overflow: hidden;
    border: 1px solid var(--cv-line);
    background: var(--cv-sunk);
    line-height: 0;
}

/* The slot ships on every course page, empty. A course with no cover — and a
   cover whose file 404s, which removes the <img> — must leave no trace, not a
   1px bordered strip above the title. */
.cv-hero:empty { display: none; }

.cv-hero-img { display: block; width: 100%; height: auto; }

/* NO DISSOLVE. The dark theme faded the bottom of the cover into the page so
   the kicker below read as the same surface continuing. On a light page that
   gradient would have to dissolve a dark painting into white, which does not
   dissolve — it smears. A framed picture, hairline and corner radius, is the
   Air treatment: the frame does the joining, not a gradient. */

.cv-course {
    font-size: 12px; font-weight: 600; letter-spacing: 0.08em;
    text-transform: uppercase; color: var(--cv-accent);
}

.cv-head h1 {
    font-size: 40px; font-weight: 600; line-height: 1.08;
    letter-spacing: -0.022em; margin: 10px 0 14px; color: var(--cv-ink);
}

.cv-intro { color: var(--cv-ink-2); font-size: 17px; line-height: 1.65; }
.cv-intro p + p { margin-top: 10px; }

/* ---------- stage map (a course's `stageMap`) ---------- */

#cv-stages { margin-top: 22px; }
.cv-stage-path {
    list-style: none; margin: 0; padding: 0;
    display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px;
}
.cv-stage {
    position: relative; display: flex; flex-direction: column; gap: 8px;
    background: var(--cv-panel); border: 1px solid var(--cv-line);
    border-radius: 16px; padding: 14px 13px; box-shadow: var(--cv-lift);
    font-size: 13px; line-height: 1.4; color: var(--cv-ink-2);
}
.cv-stage:not(:last-child)::after {
    content: '›'; position: absolute; right: -12px; top: 18px;
    font-size: 18px; line-height: 1; color: var(--cv-line-2);
}
.cv-stage-top { display: flex; align-items: center; justify-content: space-between; }
.cv-stage-n {
    width: 26px; height: 26px; border-radius: 50%;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--cv-accent-soft); color: var(--cv-accent); font-weight: 700; font-size: 13px;
}
.cv-stage.done { border-color: var(--cv-ok); }
.cv-stage.done .cv-stage-n { background: var(--cv-ok-soft); color: var(--cv-ok); }
.cv-stage-range { font-size: 12px; color: var(--cv-ink-3); font-variant-numeric: tabular-nums; }
.cv-stage-title { font-size: 15px; font-weight: 600; color: var(--cv-ink); line-height: 1.25; }
.cv-stage-chip {
    display: flex; gap: 7px; align-items: baseline;
    background: var(--cv-sunk); border-radius: 9px; padding: 6px 8px;
}
.cv-stage-chip i { width: 13px; text-align: center; color: var(--cv-ink-3); font-size: 12px; }
.cv-stage-chip.cost i { color: var(--cv-ok); }
.cv-stage-chip.cost.paid { background: #fff7e6; }
.cv-stage-chip.cost.paid i { color: #b45309; }
.cv-stage-done { display: flex; gap: 7px; align-items: baseline; margin-top: auto; padding-top: 2px; }
.cv-stage-done i { width: 13px; text-align: center; color: var(--cv-accent); font-size: 12px; }

.cv-stage-notes { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-top: 14px; }
.cv-stage-note {
    display: flex; gap: 11px; align-items: flex-start;
    border: 1px dashed var(--cv-line-2); border-radius: 14px; padding: 12px 14px;
    font-size: 13px; line-height: 1.45; color: var(--cv-ink-2);
}
.cv-stage-note > i { color: var(--cv-accent); font-size: 16px; margin-top: 2px; width: 18px; text-align: center; }
.cv-stage-note-h { font-weight: 600; color: var(--cv-ink); margin-bottom: 2px; }

@media (max-width: 700px) {
    .cv-stage-path { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .cv-stage:nth-child(2n)::after { content: none; }
    .cv-stage-notes { grid-template-columns: minmax(0, 1fr); }
}

.cv-bar { margin: 0 0 26px; }
.cv-bar-track { height: 6px; border-radius: 99px; background: #e8e8ed; overflow: hidden; }
.cv-bar-fill {
    height: 100%; width: 0;
    background: var(--cv-accent);
    border-radius: 99px;
    /* motion-audit: allow layout-prop — width set from JS in js/course.js:323 */
    transition: width 0.5s cubic-bezier(.2, .8, .2, 1); /* motion-audit: allow slow-ui — a progress fill is explanatory motion, not UI feedback */
}
.cv-bar-lbl { margin-top: 9px; font-size: 13px; color: var(--cv-ink-3); }

.cv-list { display: grid; gap: 10px; }

.cv-mod {
    display: flex; align-items: center; gap: 14px; width: 100%; text-align: left;
    background: var(--cv-panel); border: 1px solid var(--cv-line);
    border-radius: 16px; padding: 16px 18px; cursor: pointer;
    font-family: inherit; color: inherit;
    box-shadow: var(--cv-lift);
    transition: border-color 0.18s ease, transform 0.18s ease, box-shadow 0.18s ease;
}
.cv-mod:hover {
    border-color: var(--cv-line-2);
    box-shadow: var(--cv-lift-2);
}

.cv-mod-n {
    flex: 0 0 32px; height: 32px; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    background: var(--cv-sunk); color: var(--cv-ink-2);
    font-weight: 600; font-size: 14px;
    font-variant-numeric: tabular-nums;
}
/* Done is the one state worth a colour: it is the only thing on the card that
   the words do not already say. (.practice / .build carry no colour now — see
   the header note; the type is written out in .cv-mod-meta.) */
.cv-mod.done .cv-mod-n { background: var(--cv-ok-soft); color: var(--cv-ok); }

.cv-mod-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1; }
.cv-mod-name { font-weight: 600; font-size: 16px; letter-spacing: -0.01em; color: var(--cv-ink); }
.cv-mod-meta { font-size: 13px; color: var(--cv-ink-3); }
.cv-mod-go { font-size: 13px; color: var(--cv-ink-3); white-space: nowrap; }

.cv-mod.locked { opacity: 0.55; cursor: default; box-shadow: none; }
.cv-mod.locked:hover {
    border-color: var(--cv-line);
    background: var(--cv-panel); box-shadow: none;
}

/* The lift is the only part of these hovers that MOVES, so it is the only part
   gated to a real pointer — on touch it sticks after the tap. The colour and
   shadow above stay ungated. This block sits ABOVE the reduced-motion block,
   which carries no specificity of its own and wins on source order alone. */
@media (hover: hover) and (pointer: fine) {
    .cv-mod:hover { transform: translateY(-1px); }
    /* A locked card is inert: it keeps the colour reset above, and never lifts. */
    .cv-mod.locked:hover { transform: none; }
}

@keyframes cv-nudge {
    0%, 100% { transform: translateX(0); }
    20% { transform: translateX(-5px); }
    45% { transform: translateX(5px); }
    70% { transform: translateX(-3px); }
}
.cv-mod.nudge {
    animation: cv-nudge 0.55s ease;
    border-color: var(--cv-accent);
    box-shadow: 0 0 0 3px var(--cv-accent-soft);
}

/* ---------- the Track 2 door ----------
   Quiet until Track 1 is finished, then it is the brightest thing on the page.
   It never lies about being open: there is no entitlement check behind it yet. */
.cv-next {
    margin-top: 26px; padding: 20px;
    border: 1px dashed var(--cv-line-2); border-radius: 16px;
    background: var(--cv-panel);
}
.cv-next-h { font-weight: 600; font-size: 16px; color: var(--cv-ink); margin-bottom: 6px; }
.cv-next p { font-size: 14.5px; color: var(--cv-ink-2); line-height: 1.7; }
.cv-next-note { margin-top: 8px; font-size: 13px; color: var(--cv-ink-3); }
.cv-next.ready {
    border-style: solid; border-color: var(--cv-accent);
    background: var(--cv-accent-soft);
}
.cv-next.ready .cv-next-h { color: var(--cv-accent-2); }
/* The card can end on TWO doors — self-paced, or the live cohort — so the
   buttons need a row of their own and space between them. Until 2026-09-21
   there was only ever one and it sat flush under the note. Margins rather
   than a flex wrapper: the buttons are appended straight into .cv-next, and
   wrapping them would change the DOM every other course already renders. */
.cv-next .cv-btn { margin-top: 14px; margin-right: 10px; }
/* The price sits between the note and the buttons and is the one line on this
   card a buyer scans for, so it is inked and weighted rather than muted like
   the note above it. */
.cv-next-price { margin-top: 10px; font-weight: 600; color: var(--cv-ink); }

.cv-foot { margin-top: 30px; text-align: center; }

/* ---------- one module ---------- */

/* THE MODULE BAR — back, sideways, out.
   Sticky under the site nav (.nav is position:sticky, top:0, height --nav-h,
   z-index 1000), so this sits directly beneath it and stays below it in the
   stack. It has to be sticky rather than a header that scrolls away: a read
   module with figures is several screens long, and the case this exists for is
   a student who wants out of one, not a student at the top of one.

   The negative margins cancel .cv-root's padding so the bar bleeds to the edges
   of the column instead of floating as a pill with page visible either side of
   it. Nothing scrolls outside that column — .cv-wrap caps at 720px — so the
   blurred strip covers everything that passes under it. */
.cv-mnav {
    position: sticky;
    top: var(--nav-h, 56px);
    z-index: 40;
    display: flex;
    align-items: center;
    gap: 8px;
    margin: -26px -16px 20px;
    padding: 10px 16px;
    background: rgba(251, 251, 253, 0.82);
    -webkit-backdrop-filter: saturate(180%) blur(20px);
    backdrop-filter: saturate(180%) blur(20px);
    border-bottom: 1px solid var(--cv-line);
}

.cv-mnav-btn {
    background: none;
    border: 1px solid var(--cv-line-2);
    color: var(--cv-ink-2);
    border-radius: 999px;
    padding: 7px 14px;
    font-family: inherit;
    font-size: 13px;
    font-weight: 500;
    line-height: 1.2;
    cursor: pointer;
    white-space: nowrap;
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    max-width: 100%;
    transition: border-color 0.15s ease, color 0.15s ease;
}
.cv-mnav-btn:hover { border-color: var(--cv-ink-3); color: var(--cv-ink); }

/* The middle takes the slack, so back and out stay pinned to the two edges
   whatever the module is called. Deliberately NOT position:relative — see the
   menu below. */
.cv-mnav-pick { flex: 1; min-width: 0; display: flex; justify-content: center; }
.cv-mnav-pick.open .cv-mnav-at { border-color: var(--cv-accent); color: var(--cv-ink); }

.cv-mnav-pos { color: var(--cv-accent); font-variant-numeric: tabular-nums; flex: none; }
.cv-mnav-lbl { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.cv-mnav-car { flex: none; font-size: 11px; opacity: 0.7; }

/* ANCHORED TO THE BAR, NOT TO THE BUTTON THAT OPENS IT. The bar is the
   positioned ancestor (position:sticky), which is why .cv-mnav-pick above must
   stay static — hang the menu off the pick instead and its left edge is decided
   by where the flex row happens to put the pick, which is decided by how wide
   the two labels either side of it are, which changes with the LANGUAGE. In zh
   that left 2px of clearance at 390px; one longer module title and it hangs off
   the screen in one language only. Centred on the bar it cannot: the width is a
   percentage of the bar, so the margins are equal and fixed at every width. */
.cv-mnav-menu {
    position: absolute;
    top: calc(100% + 9px);
    left: 50%;
    transform: translateX(-50%);
    width: min(380px, calc(100% - 24px));
    max-height: min(58vh, 420px);
    overflow-y: auto;
    background: var(--cv-panel);
    border: 1px solid var(--cv-line);
    border-radius: 16px;
    padding: 6px;
    box-shadow: var(--shadow-pop, 0 12px 48px rgba(0, 0, 0, 0.12));
    z-index: 41;
}

.cv-mnav-row {
    display: flex; align-items: center; gap: 10px;
    width: 100%; text-align: left;
    background: none; border: 0; border-radius: 10px;
    padding: 9px 10px;
    color: var(--cv-ink-2);
    font-family: inherit; font-size: 14px; cursor: pointer;
}
.cv-mnav-row:hover { background: var(--cv-sunk); color: var(--cv-ink); }
.cv-mnav-row.at { background: var(--cv-accent-soft); color: var(--cv-ink); }

/* A locked row is dimmed but never disabled — tapping it walks you to the
   module that IS open, which is the answer, and a dead button is not. */
.cv-mnav-row.locked { opacity: 0.5; }

.cv-mnav-n {
    flex: 0 0 24px; height: 24px; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    background: var(--cv-sunk); font-size: 12px; font-weight: 600;
    font-variant-numeric: tabular-nums;
}
.cv-mnav-row.done .cv-mnav-n { background: var(--cv-ok-soft); color: var(--cv-ok); }
.cv-mnav-row.at .cv-mnav-n { background: #e6dcfb; color: var(--cv-accent-2); }
.cv-mnav-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.cv-m-head { margin-bottom: 22px; }
.cv-m-kicker {
    font-size: 12px; font-weight: 600; letter-spacing: 0.08em;
    text-transform: uppercase; color: var(--cv-accent);
}
.cv-m-head h1 {
    font-size: 30px; font-weight: 600; line-height: 1.14;
    letter-spacing: -0.02em; margin-top: 9px; color: var(--cv-ink);
}

.cv-lead { color: var(--cv-ink-2); font-size: 17px; line-height: 1.7; margin-bottom: 16px; }

.cv-read, .cv-brief { color: var(--cv-ink-2); font-size: 17px; line-height: 1.7; }
.cv-brief p + p { margin-top: 12px; }

/* ---------- module orientation ----------
   A quiet landmark at the top of a pre-course module: type and time in the
   accent eyebrow, then the one thing the reader walks away able to do. One
   hairline on the left, no box — enough to read as "start here" without
   turning the page into a stack of cards. */
.cv-orient {
    margin: 0 0 18px; padding: 1px 0 1px 13px;
    border-left: 2px solid var(--cv-accent);
}
.cv-orient-tag {
    font-size: 12px; font-weight: 600; letter-spacing: 0.08em;
    text-transform: uppercase; color: var(--cv-accent);
}
.cv-orient-out {
    margin-top: 5px; font-size: 15.5px; line-height: 1.56; color: var(--cv-ink);
}
.cv-orient-lab { font-weight: 600; }
.cv-orient-next {
    margin-top: 4px;
    font-size: 14px;
    line-height: 1.5;
    color: var(--cv-ink-2);
}

/* ---------- module experience ----------
   One interactive block per pre-course module: role picker, request journey,
   restaurant mapper. Aligned with the surrounding prose column in the
   widened pre-course canvas. */
.cv-xp {
    margin: 0 0 20px;
    padding: 14px;
    border: 1px solid var(--cv-line);
    border-radius: 14px;
    background: var(--cv-panel);
}
.cv-xp-title {
    font-size: 18px;
    line-height: 1.3;
    letter-spacing: -0.01em;
    color: var(--cv-ink);
}
.cv-xp-lead {
    margin-top: 6px;
    font-size: 14px;
    line-height: 1.55;
    color: var(--cv-ink-2);
}
.cv-xp-tabs {
    margin-top: 10px;
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}
.cv-xp-tabs-map .cv-xp-tab { min-height: 42px; }
.cv-xp-tab, .cv-xp-step {
    border: 1px solid var(--cv-line-2);
    background: #fff;
    color: var(--cv-ink-2);
    border-radius: 10px;
    padding: 8px 11px;
    font: inherit;
    font-size: 13.5px;
    line-height: 1.35;
    cursor: pointer;
    text-align: left;
}
.cv-xp-step { white-space: nowrap; }
.cv-xp-tab:hover, .cv-xp-step:hover { border-color: var(--cv-ink-3); color: var(--cv-ink); }
.cv-xp-tab.on, .cv-xp-step.on {
    border-color: var(--cv-accent);
    background: var(--cv-accent-soft);
    color: var(--cv-ink);
}
.cv-xp-panel {
    margin-top: 10px;
    border-top: 1px solid var(--cv-line);
    padding-top: 10px;
}
.cv-xp-panel-h {
    font-size: 14px;
    font-weight: 600;
    color: var(--cv-ink);
}
.cv-xp-panel-p {
    margin-top: 4px;
    font-size: 14.5px;
    line-height: 1.6;
    color: var(--cv-ink);
}
.cv-xp-panel-stop {
    margin-top: 7px;
    font-size: 13.5px;
    line-height: 1.55;
    color: var(--cv-ink-2);
    border-left: 2px solid var(--cv-line-2);
    padding-left: 9px;
}
.cv-xp-steps {
    margin-top: 10px;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(148px, 1fr));
    gap: 7px;
}

/* ---------- the build checklist ----------
   The evening's gates as a tick list. A light panel, not seven boxes: one
   surround, hairline rows, and a live count that moves as they clear. The
   checkbox keeps its native affordance — it is the one control here a keyboard
   and a screen reader already know. */
.cv-checklist {
    margin: 22px 0 18px; padding: 6px 16px 14px;
    border: 1px solid var(--cv-line); border-radius: 16px;
    background: var(--cv-panel);
}
.cv-checklist-h {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: 8px 14px; flex-wrap: wrap;
    padding: 10px 0 4px; border-bottom: 1px solid var(--cv-line);
}
.cv-checklist-title {
    font-size: 12px; font-weight: 600; letter-spacing: 0.08em;
    text-transform: uppercase; color: var(--cv-accent);
}
.cv-checklist-count {
    font-size: 13px; color: var(--cv-ink-3);
    font-variant-numeric: tabular-nums;
}
.cv-checklist-list { list-style: none; margin: 0; padding: 0; }
.cv-checklist-item { border-top: 1px solid var(--cv-line); }
.cv-checklist-item:first-child { border-top: none; }
.cv-checklist-label {
    display: flex; align-items: flex-start; gap: 11px;
    padding: 11px 2px; cursor: pointer;
}
.cv-checklist-box {
    flex: none; width: 18px; height: 18px; margin: 1px 0 0;
    accent-color: var(--cv-accent); cursor: pointer;
}
.cv-checklist-box:focus-visible {
    outline: 2px solid var(--cv-accent); outline-offset: 2px;
}
.cv-checklist-txt {
    font-size: 15px; line-height: 1.55; color: var(--cv-ink-2);
    overflow-wrap: anywhere;
}
.cv-checklist-item.checked .cv-checklist-txt {
    color: var(--cv-ink-3); text-decoration: line-through;
    text-decoration-color: var(--cv-line-2);
}
.cv-checklist-jump {
    margin: 0 0 10px 31px;
    border: 1px solid var(--cv-line-2);
    background: #fff;
    color: var(--cv-ink-2);
    border-radius: 999px;
    padding: 4px 9px;
    font: inherit;
    font-size: 12.5px;
    cursor: pointer;
}
.cv-checklist-jump:hover { border-color: var(--cv-ink-3); color: var(--cv-ink); }

/* ---------- build step navigator ----------
   The tools brief is long by design. Navigator + semantic sections keep the
   exact authored words while removing the single-wall paragraph shape. */
.cv-stepnav {
    margin: 4px 0 14px;
    padding: 0 1px;
}
.cv-stepnav-row {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}
.cv-stepnav-btn {
    border: 1px solid var(--cv-line-2);
    background: #fff;
    color: var(--cv-ink-2);
    border-radius: 999px;
    padding: 6px 10px;
    font: inherit;
    font-size: 12.5px;
    line-height: 1.3;
    cursor: pointer;
}
.cv-stepnav-btn[aria-pressed="true"] {
    border-color: var(--cv-accent);
    background: var(--cv-accent-soft);
    color: var(--cv-ink);
}
.cv-stepnav-btn:hover { border-color: var(--cv-ink-3); color: var(--cv-ink); }
.cv-brief-step {
    margin: 14px 0 0;
    padding: 12px 13px 4px;
    border-top: 1px solid var(--cv-line);
}
.cv-brief-step-h {
    font-size: 15px;
    line-height: 1.4;
    font-weight: 600;
    color: var(--cv-ink);
    margin-bottom: 6px;
}

/* ---------- the settle between modules ----------
   The body fades up on each open so previous/next reads as one journey, not a
   hard cut. Small and once; the reduced-motion rule below turns it off. */
.cv-m-body.cv-enter { animation: cvEnter 0.28s ease both; }
@keyframes cvEnter {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: none; }
}

/* The skeleton: six headings to fill, not prose to imitate. */
.cv-skel { display: grid; gap: 8px; margin: 22px 0 18px; }
.cv-skel-row {
    display: flex; gap: 12px; align-items: flex-start;
    background: var(--cv-panel); border: 1px solid var(--cv-line);
    border-radius: 14px; padding: 13px 15px;
}
.cv-skel-n {
    flex: 0 0 22px; height: 22px; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    background: var(--cv-sunk); color: var(--cv-ink-3); font-size: 12px; font-weight: 600;
}
.cv-skel-b { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.cv-skel-b b { font-size: 15px; font-weight: 600; color: var(--cv-ink); }
.cv-skel-b small { font-size: 13.5px; color: var(--cv-ink-3); line-height: 1.65; }

.cv-ta {
    width: 100%; background: var(--cv-panel); color: var(--cv-ink);
    border: 1px solid var(--cv-line-2); border-radius: 14px;
    padding: 14px; font-family: inherit; font-size: 16px; line-height: 1.7;
    resize: vertical;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.cv-ta:focus {
    outline: none; border-color: var(--cv-accent);
    box-shadow: 0 0 0 4px var(--cv-accent-soft);
}
.cv-ta::placeholder { color: #8e8e94; }

/* ---------- the review ----------
   Same place on the page in both module types, because it is the same act:
   you wrote something and somebody read it back to you. */
.cv-run, .cv-check { margin: 16px 0; }

.cv-out {
    margin: 16px 0; padding: 18px; border-radius: 16px;
    background: var(--cv-accent-soft);
    border: 1px solid #e5daf9;
}
.cv-out.waiting { min-height: 70px; opacity: 0.7; }
/* Three pulsing dots, not a blank box. This was the ONE waiting state in the
   app that had none — act1.css, act1-talk.css and posttrain-ta.css all animate
   theirs, and this rule was copied from act1's without the ::after that carries
   the signal. The wait it hides is not short: the same pitch measured 4.8s,
   49.9s and a 90s server timeout on three consecutive runs, so what a member
   stares at for most of a minute has to say "working", or they conclude it is
   broken and leave. */
.cv-out.waiting::after {
    content: '· · ·'; display: block; letter-spacing: 4px;
    color: var(--cv-accent-2); font-size: 18px;
    animation: cvPulse 1.1s ease-in-out infinite;
}
@keyframes cvPulse { 0%, 100% { opacity: .35; } 50% { opacity: 1; } }
/* Past the point where dots alone stop reassuring. Set from JS so it can never
   claim a slow run on a fast one. */
.cv-out-slow {
    font-size: 13.5px; line-height: 1.6; color: var(--cv-ink-2);
}
/* Decoration degrades to INFORMATION, never to nothing. */
@media (prefers-reduced-motion: reduce) {
    .cv-out.waiting::after { animation: none; opacity: .6; }
}
.cv-out-h {
    font-size: 12px; font-weight: 600; letter-spacing: 0.08em;
    text-transform: uppercase; color: var(--cv-accent-2); margin-bottom: 10px;
}
.cv-out-text { font-size: 16px; line-height: 1.7; color: var(--cv-ink); }

.cv-md-h { font-weight: 600; color: var(--cv-ink); margin: 12px 0 4px; }
/* HIERARCHY IN A LONG READ. The rule above is nearly flat — a heading sat 12px
   off the paragraph before it and barely outweighed the body, which is the
   "long paragraphs, no landmarks" the pre-course reads were called out for.
   Scoped to .cv-read so a TA answer (cv-md inside .cv-out-text) and a beat
   pane keep their compact spacing. Every pre-course heading is one level
   (###), so a single stronger, better-spaced step is the whole fix; the first
   heading in the read drops its top gap so a section cannot open on a hole. */
.cv-read .cv-md-h {
    font-size: 20px; line-height: 1.3; letter-spacing: -0.012em;
    margin: 30px 0 8px;
}
.cv-read .cv-md-h:first-child { margin-top: 0; }
.cv-md-li { margin: 3px 0 3px 4px; }
.cv-md-p { margin: 6px 0; }
.cv-md-gap { height: 8px; }
.cv-md-p code, .cv-md-li code {
    background: rgba(0, 0, 0, 0.05); border-radius: 5px; padding: 1px 5px; font-size: 14px;
}

/* THE SAME PILL, IN THE AUTHORED FIELDS. The intro, the leads, a build's brief
   and its skeleton render `code` and **bold** through LessonKit.inlineInto
   since 2026-08-31 — before that their markers came out as punctuation. A
   <code> with no rule here would be the other half of the same defect: the
   asterisks would be gone and the reader would still not see a path as a path.
   `em` scales with its container rather than pinning 14px, because the
   skeleton hint sets 13.5px and the lead sets 17px. */
.cv-intro code, .cv-lead code, .cv-brief code, .cv-done-q code, .cv-skel-b code {
    background: rgba(0, 0, 0, 0.05); border-radius: 5px; padding: 1px 5px; font-size: 0.88em;
}

/* A fenced block — Track 3 opens on one (the pip install line the whole week
   starts with) and the tutor writes them too. Same reason it wraps rather than
   scrolls as Act 1's: a command whose end is off the right edge of a phone is a
   command that does not get run. */
.cv-md-pre {
    background: rgba(0, 0, 0, 0.045); border: 1px solid var(--cv-line-2);
    border-radius: 10px; padding: 11px 13px; margin: 10px 0;
}
.cv-md-pre code {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 13.5px;
    line-height: 1.65; white-space: pre-wrap; word-break: break-word;
    background: none; padding: 0; color: var(--cv-ink);
}

.cv-gloss {
    margin: 16px 0 20px; padding: 14px;
    border: 1px solid var(--cv-line); border-radius: 16px;
    background: #fdfdff;
}
.cv-gloss-head {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: 8px 14px; flex-wrap: wrap;
}
.cv-gloss-title {
    font-size: 19px; line-height: 1.3; letter-spacing: -0.01em;
    color: var(--cv-ink); font-weight: 650;
}
.cv-gloss-status { color: var(--cv-ink-2); font-size: 13.5px; }
.cv-gloss-lead {
    margin: 8px 0 10px; color: var(--cv-ink-2);
    font-size: 14px; line-height: 1.65;
}
.cv-gloss-tools {
    display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 6px;
}
.cv-gloss-tool {
    border: 1px solid var(--cv-line-2); background: #fff; color: var(--cv-ink-2);
    border-radius: 999px; padding: 6px 11px; font: inherit; font-size: 13px;
    cursor: pointer;
}
.cv-gloss-tool:hover { border-color: var(--cv-ink-3); color: var(--cv-ink); }
.cv-gloss-tool:focus-visible {
    outline: 2px solid var(--cv-accent); outline-offset: 2px;
}
.cv-gloss-cat { margin-top: 10px; }
.cv-gloss-cat-h {
    margin: 6px 0 8px; font-size: 14px; letter-spacing: 0.01em;
    color: var(--cv-ink-2);
}
.cv-gloss-grid {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 8px;
}
.cv-gloss-card {
    border: 1px solid var(--cv-line-2); border-radius: 12px; background: #fff;
}
.cv-gloss-btn {
    width: 100%; border: none; background: none; color: var(--cv-ink);
    text-align: left; padding: 11px 12px; border-radius: 12px;
    font: inherit; cursor: pointer; display: flex; flex-direction: column; gap: 3px;
}
.cv-gloss-term {
    font-size: 15px; font-weight: 600; line-height: 1.35;
    overflow-wrap: anywhere;
}
.cv-gloss-hint {
    font-size: 12px; line-height: 1.35; color: var(--cv-ink-3);
}
.cv-gloss-btn:focus-visible {
    outline: 2px solid var(--cv-accent); outline-offset: -2px;
}
.cv-gloss-panel {
    border-top: 1px solid var(--cv-line-2); padding: 10px 12px 11px;
}
.cv-gloss-panel p {
    margin: 0; font-size: 14.5px; line-height: 1.6;
    color: var(--cv-ink); overflow-wrap: anywhere;
}
.cv-gloss-example {
    margin-top: 8px !important; color: var(--cv-ink-2) !important;
    background: #f9f5ff; border-radius: 8px; padding: 8px 9px;
}

.cv-after { margin-top: 22px; }

.cv-tools { display: flex; flex-wrap: wrap; gap: 8px; margin: 12px 0; }
.cv-tools-btn {
    background: none; border: 1px solid var(--cv-line-2); color: var(--cv-ink-2);
    border-radius: 999px; padding: 8px 16px; font-family: inherit;
    font-size: 13px; cursor: pointer;
    transition: border-color 0.15s ease, color 0.15s ease;
}
.cv-tools-btn:hover { border-color: var(--cv-ink-3); color: var(--cv-ink); }

.cv-privacy {
    margin: 16px 0; font-size: 13.5px; line-height: 1.7; color: var(--cv-ink-3);
    border-left: 2px solid var(--cv-line-2); padding-left: 13px;
}

.cv-err { color: var(--cv-danger); font-size: 14px; line-height: 1.7; margin: 8px 0; }
.cv-note { color: var(--cv-ink-3); font-size: 14px; line-height: 1.7; }

.cv-done-h {
    font-size: 24px; font-weight: 600; letter-spacing: -0.02em;
    color: var(--cv-ink); margin-bottom: 10px;
}
.cv-done-q { color: var(--cv-ink-2); font-size: 16px; line-height: 1.75; margin-bottom: 20px; }

/* ---------- buttons ----------
   The Air pill, same shape and weight as .btn in app.css, so a course button
   and a site button are the same object. */

.cv-nav { display: flex; gap: 10px; justify-content: space-between; margin-top: 28px; }

.cv-btn {
    border: none; cursor: pointer; font-family: inherit; font-weight: 500;
    background: var(--cv-accent); color: #fff;
    border-radius: 980px; padding: 12px 26px; font-size: 16px;
    text-decoration: none; display: inline-flex; align-items: center; justify-content: center;
    transition: background 0.18s ease, transform 0.18s ease;
}
.cv-btn:hover { background: var(--cv-accent-2); }
/* Gated ABOVE `:disabled` on purpose: the two selectors weigh the same, so the
   disabled reset below has to stay last or a dead button would lift on hover. */
@media (hover: hover) and (pointer: fine) {
    .cv-btn:hover { transform: translateY(-1px); }
}
.cv-btn:disabled { opacity: 0.45; cursor: default; transform: none; background: var(--cv-accent); }

.cv-btn.ghost {
    background: none; color: var(--cv-ink);
    border: 1px solid var(--cv-line-2); font-weight: 500;
}
.cv-btn.ghost:hover { border-color: var(--cv-ink); background: none; }

@media (max-width: 560px) {
    .cv-head h1 { font-size: 30px; }
    .cv-m-head h1 { font-size: 25px; }
    .cv-intro, .cv-lead, .cv-read, .cv-brief { font-size: 16px; }
    .cv-nav { flex-direction: column-reverse; }
    .cv-nav .cv-btn { width: 100%; }

    /* Three controls stay in ONE row on a phone — a bar that wraps to two
       lines eats a third of the screen on every module. The module NAME is
       what gives, because the <h1> repeating it is 40px below; the position
       count stays, because that is the part the title does not say. */
    .cv-mnav { gap: 6px; padding: 8px 12px; margin: -26px -16px 16px; }
    .cv-mnav-btn { padding: 7px 11px; font-size: 12.5px; gap: 6px; }
    .cv-mnav-lbl { display: none; }
    .cv-gloss { margin-left: -1px; margin-right: -1px; padding: 12px; }
    .cv-gloss-grid { grid-template-columns: minmax(0, 1fr); }
    /* The reading step is smaller on a phone; the outcome tracks it. */
    .cv-orient-out { font-size: 15px; }
    .cv-read .cv-md-h { font-size: 18px; margin-top: 26px; }
    .cv-checklist { margin-left: -1px; margin-right: -1px; padding: 4px 13px 12px; }
    .cv-xp { margin-left: -1px; margin-right: -1px; padding: 12px; }
    .cv-xp-steps { grid-template-columns: minmax(0, 1fr); }
    .cv-checklist-jump { margin-left: 29px; }
    .cv-stepnav { margin: 2px 0 12px; }
    .cv-stepnav-row { display: grid; grid-template-columns: minmax(0, 1fr); }
    .cv-stepnav-btn { border-radius: 10px; text-align: left; }
    .cv-brief-step { padding: 10px 0 2px; }
}

@media (prefers-reduced-motion: reduce) {
    .cv-bar-fill { transition: none; }
    /* Both lifts now sit behind a pointer gate, so the reset that cancels them
       is scoped the same way — identical specificity, and this block is still
       the later one, so it keeps winning. */
    @media (hover: hover) and (pointer: fine) {
        .cv-mod:hover { transform: none; }
        .cv-btn:hover { transform: none; }
    }
    .cv-gloss-tool, .cv-gloss-btn { transition: none; }
    /* The nudge still has to POINT — it just holds still while it does. */
    .cv-mod.nudge { animation: none; box-shadow: 0 0 0 3px var(--cv-accent-soft); }
    /* The settle degrades to no motion — the content is already there. */
    .cv-m-body.cv-enter { animation: none; }
}

/* ---------- the beat: three panes that fold ----------
   Panes 1 and 2 fold and the choice is remembered across every beat in the
   course, not per beat. A student who does not need the worked example should
   have to say so once, not sixteen times. Pane 3 has no header affordance and
   never folds — it is the gate, and a gate you can put away is not a gate. */
.cv-pane {
    border: 1px solid var(--cv-line); border-radius: 16px;
    background: var(--cv-panel); margin-bottom: 12px; overflow: hidden;
    box-shadow: var(--cv-lift);
}
.cv-pane-h {
    display: flex; align-items: center; gap: 10px;
    padding: 14px 18px; cursor: pointer; user-select: none;
}
.cv-pane-h.cv-pane-fixed { cursor: default; }
/* The ring is INSET here. The header runs flush to the edges of a card
   with overflow:hidden, so the +2px offset the global rule uses would be
   clipped away on all four sides and the pane would focus invisibly. */
.cv-pane-h:focus-visible { outline: 2px solid var(--cv-accent); outline-offset: -2px; border-radius: 0; }
.cv-pane-n {
    width: 22px; height: 22px; border-radius: 50%; flex: none;
    display: grid; place-items: center;
    background: var(--cv-sunk); color: var(--cv-ink-2);
    font-size: 12px; font-weight: 600;
}
/* The gate is the one pane you cannot fold, so it is the one pane that carries
   the accent — the page's single "this is the part that counts". */
.cv-pane-gate { border-color: #e5daf9; }
.cv-pane-gate .cv-pane-n { background: var(--cv-accent); color: #fff; }
.cv-pane-t { font-weight: 600; font-size: 16px; letter-spacing: -0.01em; color: var(--cv-ink); }
.cv-pane-s { margin-left: auto; font-size: 13px; color: var(--cv-ink-3); text-align: right; }
/* Optional-field badge. Same spec as .cv-rail-s (the "soon" pill) so the two
   read as one component; inline here because it marks a sentence, not a row. */
.cv-opt {
    display: inline-block; vertical-align: 1px;
    font-size: 11px; color: var(--cv-ink-3, #86868b);
    border: 1px solid var(--cv-line, #e8e8ed);
    border-radius: 999px; padding: 1px 7px;
    white-space: nowrap;
}
.cv-pane-b { padding: 0 18px 18px; }
.cv-pane.folded .cv-pane-b { display: none; }
/* The fold indicator is on the header itself rather than a separate glyph, so
   there is nothing to mis-click next to the hit area. */
.cv-pane:not(.cv-pane-gate) .cv-pane-h::after {
    content: '▾'; color: var(--cv-ink-3); font-size: 12px; margin-left: 8px; flex: none;
}
.cv-pane.folded .cv-pane-h::after { content: '▸'; }

.cv-fl {
    display: block; font-weight: 600; font-size: 13.5px;
    color: var(--cv-ink); margin: 16px 0 6px;
}
.cv-fl span { font-weight: 400; color: var(--cv-ink-3); }
.cv-in {
    width: 100%; background: var(--cv-panel); color: var(--cv-ink);
    border: 1px solid var(--cv-line-2); border-radius: 12px;
    padding: 11px 13px; font-family: inherit; font-size: 15.5px;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.cv-in:focus {
    outline: none; border-color: var(--cv-accent);
    box-shadow: 0 0 0 4px var(--cv-accent-soft);
}
.cv-in::placeholder { color: #8e8e94; }

.cv-assembled {
    margin-top: 18px; padding: 14px 16px;
    border-left: 3px solid var(--cv-accent); border-radius: 0 12px 12px 0;
    background: var(--cv-accent-soft);
    color: var(--cv-ink); font-size: 16px; line-height: 1.7;
}
.cv-assembled.empty { color: var(--cv-ink-3); border-left-color: var(--cv-line-2); background: var(--cv-sunk); }

.cv-carry { margin: 12px 0 0; }
/* Two rewrite buttons side by side, wrapping rather than overflowing on a
   phone -- .cv-btn goes width:100% in the mobile query below. */
.cv-tools { margin: 12px 0 0; display: flex; gap: 8px; flex-wrap: wrap; }
.cv-sm { padding: 8px 16px; font-size: 14px; }


/* ---------- illustrations ----------
   SVG infographics, each 1000 units wide and as tall as its own content needs
   — so no object-fit and no fixed ratio here. They run at natural height, and
   a figure that grows a row grows the box rather than getting cropped at a
   viewport nobody chose. */
.cv-md-fig,
.pt-ta-md-fig {
    margin: 26px 0;
    border-radius: 12px;
    overflow: hidden;
    border: 1px solid var(--cv-line, #e8e8ed);
    background: #fff;
    line-height: 0;
}

.cv-md-fig img,
.pt-ta-md-fig img {
    display: block;
    width: 100%;
    height: auto;
}

/* THE TEXT IN THESE SCALES WITH THE IMAGE, WHICH IS THE WHOLE MOBILE PROBLEM.
   The canvas is 1000 units wide and the labels are sized for the ~688px
   reading column. On a 390px phone the same figure scales to about a third:
   body labels land near 11px and the kickers near 8px — present, unreadable,
   and worse than the painted metaphor they replaced, because a diagram you
   cannot read is just noise.

   So below the width where that bites, the figure keeps a legible floor (560
   units → ~17px body labels) and moves sideways instead of shrinking — the
   same trade a wide table makes. Nothing changes at reading-column width.

   AND IT HAS TO SAY SO. A picture that is simply wider than its box reads as
   cut off, not as scrollable — there is no scrollbar on a phone and no reason
   to guess that an illustration slides. So the right edge gets a fade: the
   universal "there is more this way", costing no words and therefore needing
   no translation. It sits on the OUTER box, which does not move; painted on
   the scroller it would slide away exactly when it is still needed. */
@media (max-width: 560px) {
    .cv-md-fig,
    .pt-ta-md-fig { position: relative; }

    .cv-md-figscroll,
    .pt-ta-md-figscroll {
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
    }

    .cv-md-fig img,
    .pt-ta-md-fig img { min-width: 560px; }

    .cv-md-fig::after,
    .pt-ta-md-fig::after {
        content: '';
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        width: 54px;
        pointer-events: none;   /* a swipe that starts on the fade still scrolls */
        background: linear-gradient(to right, rgba(255, 255, 255, 0), rgba(255, 255, 255, .94));
    }
}

.pt-ta-md-fig {
    margin: 12px 0;
    border-radius: 10px;
}


/* =====================================================================
   THE COURSE RAIL — the module menu in the left gutter (js/course-rail.js)
   Nathan 2026-08-30: "left side add course menu to show all modules."

   IT LIVES IN THE GUTTER, NOT IN THE FLOW, and that is the whole reason this
   is CSS-only and cannot break anything. .cv-wrap stays a 720px centred
   column exactly as it was; the rail is fixed into the empty space beside it.
   So there is no grid to get wrong, no reflow of the reading column, and on
   any viewport too narrow to have a gutter the rail simply is not rendered.

   THE BREAKPOINT IS ARITHMETIC, NOT TASTE. The pre-course column is 880px, so
   the gutter is (100vw - 880) / 2. At 1400px that is 260px, which holds a
   220px rail with 40px of air. Below
   it a rail would sit on top of the words it indexes, which is worse than no
   rail — the module bar at the top of every module has always been the way to
   move, and it stays.
   ===================================================================== */

.cv-rail { display: none; }

@media (min-width: 1400px) {
    .cv-wide .cv-rail {
        display: flex;
        flex-direction: column;
        gap: 4px;
        position: fixed;
        top: 96px;
        left: calc((100vw - var(--cv-main)) / 2 - 260px);
        width: 220px;
        max-height: calc(100vh - 140px);
        overflow-y: auto;
        z-index: 40;      /* far below .fb-btn's 8000 and the TA's 8100 */
        padding-right: 6px;
    }
}

.cv-rail-h {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--cv-ink-3, #86868b);
    padding: 0 10px 8px;
}

.cv-rail-list { list-style: none; margin: 0; padding: 0; }

.cv-rail-row {
    display: flex;
    align-items: baseline;
    gap: 10px;
    width: 100%;
    padding: 8px 10px;
    border: 0;
    border-radius: 8px;
    background: none;
    text-align: left;
    cursor: pointer;
    font: 400 14px/1.35 -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, sans-serif;
    color: var(--cv-ink-2, #6e6e73);
}

.cv-rail-row:hover { background: var(--cv-panel, #f5f5f7); }

.cv-rail-n {
    flex: 0 0 auto;
    min-width: 14px;
    font-size: 12px;
    color: var(--cv-ink-3, #86868b);
    font-variant-numeric: tabular-nums;
}

.cv-rail-t { flex: 1 1 auto; }

/* WHERE YOU ARE STANDING IS THE ONE THING THIS EXISTS TO SHOW, so it is the
   only row that gets ink and weight. Everything else is a list. */
.cv-rail-row.at {
    background: var(--cv-panel, #f5f5f7);
    color: var(--cv-ink, #1d1d1f);
    font-weight: 600;
}

.cv-rail-row.done .cv-rail-n { color: var(--cv-ok, #15803d); }

/* Locked and pending are DIFFERENT and must not look the same: locked opens
   when you finish the one before it, pending is not written yet and never
   opens. A single grey for both tells a student to keep working at a door
   that has nothing behind it. */
.cv-rail-row.locked { color: var(--cv-ink-3, #86868b); }
.cv-rail-row.pending { color: var(--cv-ink-3, #86868b); cursor: default; }
.cv-rail-row.pending:hover { background: none; }

.cv-rail-s {
    flex: 0 0 auto;
    font-size: 11px;
    color: var(--cv-ink-3, #86868b);
    border: 1px solid var(--cv-line, #e8e8ed);
    border-radius: 999px;
    padding: 1px 7px;
}

.cv-rail-all {
    margin-top: 10px;
    padding: 8px 10px;
    border: 0;
    border-radius: 8px;
    background: none;
    text-align: left;
    cursor: pointer;
    font: 400 13px/1.35 -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, sans-serif;
    color: var(--cv-ink-3, #86868b);
}

.cv-rail-all:hover { background: var(--cv-panel, #f5f5f7); color: var(--cv-ink-2, #6e6e73); }

/* =====================================================================
   THE TA, DOCKED — Nathan: "right side to add AI tutor chat box."

   The panel already exists and already works; this does not build a second
   one. It changes where it sits when there is room for it to sit there, and
   nothing else — same DOM, same transcript, same code path. Below the
   breakpoint it is the launcher-and-drawer it has always been.

   GATED ON .cv-wide, WHICH ONLY course.html SETS. This stylesheet's TA rules
   are loaded by /posttrain too, and docking its panel open is a change to a
   live surface nobody asked for.

   The arithmetic again: the panel is 340px docked, and needs (100vw-880)/2
   >= 372. That is 1624px.
   ===================================================================== */

@media (min-width: 1624px) {
    .cv-wide .pt-ta-panel {
        opacity: 1;
        transform: none;
        pointer-events: auto;
        right: calc((100vw - var(--cv-main)) / 2 - 372px);
        bottom: auto;
        top: 96px;
        width: 340px;
        height: min(660px, calc(100vh - 140px));
    }

    /* A launcher for a panel that is already open is a button that does
       nothing, and the close ✕ would leave no way back. Both go. */
    .cv-wide .pt-ta-launch { display: none; }
    .cv-wide .pt-ta-x { display: none; }
}

/* Cohort learning flow: opt-in only. The four pre-course modules and posttrain
   keep their existing layout. Buttons are selectors, not ARIA tabs; disclosures
   use native details/summary. No tick or navigation is learning progress. */
.cv-guided-flow .cv-orient-flow {
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 12px; margin-top: 12px; padding-top: 12px;
    border-top: 1px solid var(--cv-line);
    font-size: 13.5px; line-height: 1.55;
}
.cv-guided-flow .cv-orient-flow dt { font-weight: 650; color: var(--cv-ink); }
.cv-guided-flow .cv-orient-flow dd { margin: 3px 0 0; color: var(--cv-ink-2); }
.cv-guided-flow .cv-orient-note, .cv-checklist-note {
    margin: 12px 0 0; font-size: 13px; line-height: 1.55; color: var(--cv-ink-2);
}
.cv-guided-flow .cv-xp-step, .cv-guided-flow .cv-xp-tab,
.cv-guided-flow .cv-stepnav-btn, .cv-guided-flow .cv-checklist-jump {
    min-height: 44px; max-width: 100%; white-space: normal; overflow-wrap: anywhere;
}
.cv-guided-flow .cv-xp-steps { grid-template-columns: repeat(auto-fit, minmax(min(135px, 100%), 1fr)); }
.cv-guided-flow button.cv-pane-h {
    width: 100%; border: 0; background: transparent; font: inherit;
    color: inherit; text-align: left;
}
.cv-guided-flow .cv-pane-t, .cv-guided-flow .cv-pane-s { min-width: 0; overflow-wrap: anywhere; }
.cv-guided-flow .cv-content-sections { margin: 12px 0 18px; min-width: 0; }
.cv-guided-flow .cv-sectionnav { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 10px; }
.cv-guided-flow .cv-content-section {
    margin: 8px 0; border: 1px solid var(--cv-line); border-radius: 12px;
    padding: 0 14px; background: var(--cv-panel); scroll-margin-top: 100px;
}
.cv-guided-flow .cv-content-summary {
    padding: 12px 0; min-height: 44px; cursor: pointer; font-weight: 600;
    color: var(--cv-ink); font-size: 15px; line-height: 1.5;
}
.cv-guided-flow .cv-content-section[open] > .cv-content-summary { border-bottom: 1px solid var(--cv-line); }
.cv-guided-flow .cv-content-section > div { padding-bottom: 10px; }
.cv-guided-flow .cv-proof-section {
    margin: 12px 0; padding: 14px; border: 1px solid var(--cv-line);
    border-radius: 12px; scroll-margin-top: 100px;
}
.cv-guided-flow .cv-proof-section h3 { font-size: 16px; color: var(--cv-ink); margin-bottom: 8px; }
.cv-guided-flow .cv-content-summary:focus-visible,
.cv-guided-flow .cv-proof-section:focus-visible,
.cv-guided-flow .cv-xp button:focus-visible,
.cv-guided-flow .cv-sectionnav button:focus-visible {
    outline: 2px solid var(--cv-accent); outline-offset: 2px;
}
.cv-guided-flow .cv-orient, .cv-guided-flow .cv-xp,
.cv-guided-flow .cv-content-section, .cv-guided-flow .cv-proof-section {
    min-width: 0; overflow-wrap: anywhere;
}
@media (max-width: 560px) {
    .cv-guided-flow .cv-orient-flow { grid-template-columns: minmax(0, 1fr); gap: 8px; }
    .cv-guided-flow .cv-sectionnav { display: grid; grid-template-columns: minmax(0, 1fr); }
    .cv-guided-flow .cv-content-section, .cv-guided-flow .cv-proof-section { padding-left: 10px; padding-right: 10px; }
    .cv-guided-flow .cv-pane-s { max-width: 38%; }
}
@media (prefers-reduced-motion: reduce) {
    .cv-guided-flow .cv-content-section, .cv-guided-flow .cv-proof-section { scroll-behavior: auto; }
}
