/* ── Self-hosted IBM Plex Sans (ADR-0072) ────────────────────────────────
 *
 * Three weights — Regular (400), Medium (500), SemiBold (600) — to match
 * DESIGN.md §3 tiers. Latin-1 subset only; `unicode-range` follows IBM's
 * own subsetted CSS so browsers skip the download when no glyph is
 * needed. `font-display: swap` paints the system-stack fallback first
 * and swaps in Plex when loaded. `size-adjust: 100%` is a conservative
 * baseline; genuine CLS tuning (ascent-override / descent-override
 * against measured fallback metrics) lands in a follow-up slice if
 * Lighthouse surfaces visible shift.
 *
 * `src` chains `local(...)` first so a clinician with Plex already
 * installed on the device skips the network fetch.
 */
@font-face {
    font-family: "IBM Plex Sans";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    size-adjust: 100%;
    src: local("IBM Plex Sans"), local("IBMPlexSans"),
        url("/static/fonts/IBMPlexSans-Regular-Latin1.woff2") format("woff2");
    unicode-range: U+0000, U+000D, U+0020-007E, U+00A0-00FF, U+0131, U+0152-0153,
        U+02C6, U+02DA, U+02DC, U+2013-2014, U+2018-201A, U+201C-201E,
        U+2020-2022, U+2026, U+2030, U+2039-203A, U+2044, U+20AC, U+2122,
        U+2212, U+FB01-FB02;
}
@font-face {
    font-family: "IBM Plex Sans";
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    size-adjust: 100%;
    src: local("IBM Plex Sans Medium"), local("IBMPlexSans-Medium"),
        url("/static/fonts/IBMPlexSans-Medium-Latin1.woff2") format("woff2");
    unicode-range: U+0000, U+000D, U+0020-007E, U+00A0-00FF, U+0131, U+0152-0153,
        U+02C6, U+02DA, U+02DC, U+2013-2014, U+2018-201A, U+201C-201E,
        U+2020-2022, U+2026, U+2030, U+2039-203A, U+2044, U+20AC, U+2122,
        U+2212, U+FB01-FB02;
}
@font-face {
    font-family: "IBM Plex Sans";
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    size-adjust: 100%;
    src: local("IBM Plex Sans SemiBold"), local("IBMPlexSans-SemiBold"),
        url("/static/fonts/IBMPlexSans-SemiBold-Latin1.woff2") format("woff2");
    unicode-range: U+0000, U+000D, U+0020-007E, U+00A0-00FF, U+0131, U+0152-0153,
        U+02C6, U+02DA, U+02DC, U+2013-2014, U+2018-201A, U+201C-201E,
        U+2020-2022, U+2026, U+2030, U+2039-203A, U+2044, U+20AC, U+2122,
        U+2212, U+FB01-FB02;
}

:root {
    /* ── Primitive colours (DESIGN.md §2) ── */
    --color-sage: #A8C5A0;
    --color-sage-light: #D4E5D0;
    --color-sage-hover: #96B88D;
    --color-sage-deep: #7BB369;
    --color-mist-blue: #B8D4E3;
    --color-mist-blue-hover: #9BC2D6;
    --color-encourage-sand: #F5E6CC;
    --color-encourage-sand-hover: #D4A94A;
    --color-muted-coral: #E8A0A0;
    --color-warm-cream: #FAF9F6;
    --color-slate-ink: #2C3E50;
    --color-quiet-grey: #6B7C8A;
    --color-quiet-grey-soft: #8A97A3;
    --color-quiet-grey-darker: #66737F;
    --color-divider-ash: #D0D5D0;
    --color-border-strong: #8C918C;
    --color-error-text: #A65E5E;
    --color-surface-white: #FFFFFF;

    /* ── Primitive type (DESIGN.md §3; ADR-0072 adopts IBM Plex Sans) ── */
    --font-family-sans: "IBM Plex Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    --font-size-xs: 0.75rem;
    --font-size-sm: 0.85rem;
    --font-size-base: 1rem;
    --font-size-lg: 1.05rem;
    --font-size-xl: 1.3rem;
    --font-size-2xl: 1.8rem;
    --font-weight-normal: 400;
    --font-weight-medium: 500;
    --font-weight-semibold: 600;
    --line-height-tight: 1.2;
    --line-height-snug: 1.3;
    --line-height-base: 1.4;
    --line-height-relaxed: 1.6;

    /* ── Primitive spacing (DESIGN.md frontmatter, 4px grid) ── */
    --space-xs: 0.25rem;
    --space-sm: 0.5rem;
    --space-md: 0.75rem;
    --space-lg: 1rem;
    --space-xl: 1.5rem;
    --space-2xl: 2rem;

    /* ── Primitive radius (DESIGN.md frontmatter) ── */
    --radius-sm: 4px;
    --radius-md: 8px;
    --radius-lg: 10px;

    /* ── Primitive elevation (DESIGN.md §4) ── */
    --shadow-none: none;
    --shadow-hover: 0 4px 16px rgba(44, 62, 80, 0.08);

    /* ── Primitive motion (DESIGN.md §1; 150–200ms on state changes) ── */
    --duration-fast: 100ms;
    --duration-base: 150ms;
    --duration-slow: 200ms;
    --easing-standard: ease;
    --easing-out: ease-out;

    /* ── Primitive breakpoints (issue #377; em-based so they track the
       browser's default font-size, not the document root). 40em/64em/80em
       give us mobile (<640px), tablet (640–1024px), and desktop-wide
       (>1280px) zones; the three test viewports — 375 / 768 / 1440 — fall
       below sm, between sm and md, and above lg respectively. ── */
    --bp-sm: 40em;
    --bp-md: 64em;
    --bp-lg: 80em;

    /* ── Semantic aliases (role, not value) ── */
    --surface: var(--color-warm-cream);
    --surface-raised: var(--color-surface-white);
    --border: var(--color-divider-ash);
    --text-muted: var(--color-quiet-grey);
    --text-subtle: var(--color-quiet-grey-darker);
    --text-decorative: var(--color-quiet-grey-soft);
    --focus-ring: 0 0 0 3px rgba(168, 197, 160, 0.25);

    /* ── Legacy compatibility (pre-#375 tokens; still resolve) ── */
    --sage: var(--color-sage);
    --sage-light: var(--color-sage-light);
    --bg: var(--color-warm-cream);
    --text: var(--color-slate-ink);
    --blue: var(--color-mist-blue);
    --blue-hover: var(--color-mist-blue-hover);
    --success: var(--color-sage);
    --encourage: var(--color-encourage-sand);
    --error: var(--color-muted-coral);
    --radius: var(--radius-md);
}

* {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

body {
    font-family: var(--font-family-sans);
    background: var(--bg);
    color: var(--text);
    line-height: 1.6;
    min-height: 100vh;
}

.container {
    max-width: 600px;
    margin: 0 auto;
    padding: 2rem 1.5rem;
}

h1 {
    font-size: 1.8rem;
    font-weight: 600;
    line-height: var(--line-height-tight);
    margin-bottom: 0.25rem;
}

.subtitle {
    color: var(--color-quiet-grey);
    margin-bottom: 2rem;
}

/* ── Skip link (slice 8 / #381) ──
 *
 * First focusable affordance in <body>. Positioned off-screen via
 * absolute top:-40px until focused; on :focus / :focus-visible it
 * snaps into the top-left of the viewport so a keyboard user lands
 * on a visible target on first Tab. Background uses the brand
 * dark-sage so the link reads against any underlying header band,
 * and z-index sits above the htmx loading overlay (which is well
 * under 1000) so it can never be visually trapped.
 */

.skip-link {
    /* `position: fixed` is the bulletproof pattern — it pins the chip
     * to the viewport regardless of <body> positioning, so a future
     * `position: relative` on body can't push the off-screen link
     * back into view. */
    position: fixed;
    top: -40px;
    left: 0;
    background: var(--color-slate-ink);
    color: var(--color-surface-white);
    padding: 0.5rem 1rem;
    z-index: 1000;
    text-decoration: none;
    font-weight: 600;
    border-radius: 0 0 4px 0;
    transition: top var(--duration-fast) ease;
}

.skip-link:focus,
.skip-link:focus-visible {
    top: 0;
    outline: 3px solid var(--color-sage);
    outline-offset: -3px;
}

/* ── App header with logout ── */

.app-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
}

/* The slice-8 landmark restructure (#381) makes <header> a sibling of
 * <main> and adds class="container" so the header reads at the same
 * width as <main class="container">. Zero the bottom padding so the
 * gap to <main> is just main's own padding-top, not header.padding-
 * bottom stacked on top. */
.app-header.container {
    padding-bottom: 0;
}

.app-footer {
    color: var(--color-quiet-grey);
    font-size: 0.85rem;
    text-align: center;
}

.app-footer__copy {
    margin: 0;
}

/* Mirrors .app-header.container — zero the top padding so the gap
 * from <main> is just main's own padding-bottom. */
.app-footer.container {
    padding-top: 0;
}

/* Brand logo — partials/brand_logo.html. The <h1.app-brand> preserves
 * a single document heading for layout-using pages that have no
 * other H1; reset its visual weight so the mark + wordmark are the
 * only things the user sees. The mark (.brand-logo__mark) is the
 * dog-eared notebook page; the recessed wordmark
 * (.brand-logo__wordmark) sits beside it. ``aria-label="reps"`` on
 * the anchor carries the accessible name. */
.app-brand {
    margin: 0;
    font-size: 0;
    line-height: 1;
}

.brand-logo {
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
    color: var(--color-slate-ink);
    text-decoration: none;
    border-radius: 2px;
    transition: opacity var(--duration-fast) ease;
}

.brand-logo:hover {
    opacity: 0.78;
}

.brand-logo:focus-visible {
    outline: 3px solid var(--color-sage);
    outline-offset: 4px;
}

.brand-logo__mark {
    height: 1.85rem;
    width: auto;
    display: block;
    color: currentColor;
}

.brand-logo__wordmark {
    font-family: var(--font-family-sans);
    font-size: 1.45rem;
    font-weight: 600;
    color: currentColor;
    letter-spacing: -0.02em;
    line-height: 1;
}

.logout-form {
    flex-shrink: 0;
}

.btn-logout {
    background: none;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius);
    padding: 0.4rem 0.85rem;
    font-family: inherit;
    font-size: 0.85rem;
    line-height: var(--line-height-base);
    color: var(--color-quiet-grey);
    cursor: pointer;
    transition: background var(--duration-base) var(--easing-standard), color var(--duration-base) var(--easing-standard);
}

.btn-logout:hover {
    background: #f0f0f0;
    color: var(--text);
}

.btn-logout:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
}

h2 {
    font-size: 1.3rem;
    font-weight: 500;
    line-height: var(--line-height-snug);
    margin-bottom: 1rem;
}

h3 {
    font-size: var(--font-size-lg);
    font-weight: var(--font-weight-semibold);
    line-height: var(--line-height-base);
}

/* ── Hub page ── */

.hub {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.hub-greeting {
    color: var(--color-quiet-grey);
    font-size: 1.05rem;
    margin-bottom: 0.5rem;
}

.hub-card {
    display: flex;
    align-items: center;
    gap: 1rem;
    background: white;
    border-radius: var(--radius);
    padding: 1.25rem 1.5rem;
    text-decoration: none;
    color: var(--text);
    border-left: 4px solid var(--sage);
    transition:
        transform var(--duration-base) var(--easing-out),
        box-shadow var(--duration-base) var(--easing-out),
        border-left-color var(--duration-base) var(--easing-standard);
    animation: hubSlideIn 0.35s ease both;
}

.hub-card:nth-child(2) { animation-delay: 0s; }
.hub-card:nth-child(3) { animation-delay: 0.07s; }
.hub-card:nth-child(4) { animation-delay: 0.14s; }

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

.hub-card:hover {
    transform: translateY(-2px);
    box-shadow: var(--shadow-hover);
}

/* Pressed-state inversion: when the card is actively pressed, drop the
 * hover lift back to rest so the surface reads as "pressed down to
 * paper" rather than floating. Subtle, duration-fast, paired with the
 * :hover rule above. */
.hub-card:active {
    transform: translateY(0);
    box-shadow: var(--shadow-none);
    transition:
        transform var(--duration-fast) var(--easing-standard),
        box-shadow var(--duration-fast) var(--easing-standard);
}

.hub-card--practice { border-left-color: var(--sage); }
.hub-card--practice:hover { border-left-color: var(--color-sage-deep); }

.hub-card--org { border-left-color: var(--blue); }
.hub-card--org:hover { border-left-color: var(--blue-hover); }

.hub-card--admin { border-left-color: var(--encourage); }
.hub-card--admin:hover { border-left-color: var(--color-encourage-sand-hover); }

.hub-card__icon {
    font-size: 1.6rem;
    line-height: 1;
    opacity: 0.5;
    flex-shrink: 0;
    width: 2rem;
    text-align: center;
}

.hub-card__body {
    flex: 1;
    min-width: 0;
}

.hub-card__title {
    font-size: 1.05rem;
    font-weight: 600;
    margin-bottom: 0.2rem;
}

.hub-card__desc {
    font-size: 0.85rem;
    color: var(--color-quiet-grey);
    line-height: 1.4;
}

.hub-card__arrow {
    font-size: 1.2rem;
    color: var(--text-subtle);
    flex-shrink: 0;
    transition: color var(--duration-base) var(--easing-standard), transform var(--duration-base) var(--easing-out);
}

.hub-card:hover .hub-card__arrow {
    color: var(--text);
    transform: translateX(3px);
}

/* ── Header actions ── */

.header-actions {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    flex-shrink: 0;
}

.header-admin-link {
    font-size: 0.85rem;
    line-height: var(--line-height-base);
    color: var(--color-quiet-grey);
    text-decoration: none;
    padding: 0.4rem 0.85rem;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius);
    transition: background var(--duration-base) var(--easing-standard), color var(--duration-base) var(--easing-standard);
}

.header-admin-link:hover {
    background: #f0f0f0;
    color: var(--text);
}

/* ── Profile page ──
 * Slice 7 (#399) refines the personal record into a v2 surface:
 * h2 carries the page heading via the base `h2` rule (#403), the
 * email reads as a recessive identity subtitle, and the per-period
 * counts sit under a `<h3>` "Practice usage" sub-heading so the
 * layout has structure without competing with the headline.
 */
/* /profile redesigned as a four-section account hub (#413).
 * Linear / Vercel / Notion school: each section is a labelled
 * landmark separated by a hairline rule routed through the
 * decorative `--color-divider-ash` primitive (the load-bearing
 * `--color-border-strong` would over-emphasise — these dividers
 * are scan-only, per #403's split-token approach). The first
 * section drops its top border so the page heading and the
 * Account label sit close. */
.profile-section {
    margin-top: var(--space-xl);
    padding-top: var(--space-xl);
    border-top: 1px solid var(--color-divider-ash);
}

.profile-section:first-of-type {
    margin-top: var(--space-xl);
    padding-top: 0;
    border-top: none;
}

.profile-section h3 {
    margin: 0 0 var(--space-md);
    color: var(--text-muted);
}

/* Card surface for clinician-served pages (#534). Visual peer of
 * admin's `.performance-summary-card` but kept in style.css because
 * admin.css is not loaded under layout.html (see
 * docs/agent-notes/layout-shell-contract.md) — the two surfaces have
 * to live in their own stylesheets. Used by Performance summary and
 * Practice usage on /profile so the two blocks share one chrome
 * primitive and cannot drift apart. Sage left-border + white surface
 * mirror the catalog-card / admin-card register; the heading inside
 * keeps the muted h3 rhythm of `.profile-section h3` so the page
 * reads as one register even where the chrome differs. */
.profile-card {
    background: white;
    border-radius: var(--radius);
    padding: var(--space-xl);
    border-left: 4px solid var(--color-sage);
    margin-bottom: var(--space-2xl);
}

.profile-card h3 {
    margin: 0 0 var(--space-md);
    color: var(--text-muted);
}

/* Term/value rows inside a profile section — used for identity
 * fields like Email. Stacks label-above-value at the 600px
 * clinician width; the dl carries no margin so the section
 * heading's bottom margin sets the rhythm. */
.profile-rows {
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
}

.profile-row {
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
}

.profile-row__label {
    margin: 0;
    color: var(--text-muted);
    font-size: var(--font-size-sm);
}

.profile-row__value {
    margin: 0;
    color: var(--text);
    font-weight: var(--font-weight-medium);
}

/* Activity stat lines (today / this month) — quiet by design;
 * the numbers carry the meaning, the surrounding copy is a
 * scan-only frame. */
.profile-stat {
    margin: 0;
    color: var(--text-muted);
}

.profile-stat + .profile-stat {
    margin-top: var(--space-sm);
}

/* /history link styling (introduced in #399, retained post-#533).
 * Post-#533 the standalone "Practice history" section is gone and
 * the only consumer is the link inside the Performance summary
 * card (#425). The inline-flex arrow + sage hover reads as the
 * navigation affordance; the surrounding `.profile-section` shell
 * provides spacing and the divider. */
.profile-history__link {
    display: inline-flex;
    align-items: baseline;
    gap: var(--space-xs);
    color: var(--text);
    font-weight: var(--font-weight-medium);
    text-decoration: none;
    transition:
        color var(--duration-base) var(--easing-standard);
}

.profile-history__arrow {
    color: var(--color-sage);
    transition:
        transform var(--duration-base) var(--easing-standard),
        color var(--duration-base) var(--easing-standard);
}

.profile-history__link:hover {
    color: var(--color-sage-hover);
}

.profile-history__link:hover .profile-history__arrow {
    color: var(--color-sage-hover);
    transform: translateX(2px);
}

.profile-history__link:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
    border-radius: var(--radius-sm);
}

/* Performance summary — ready state (#426). The card sits inside the
 * existing `.profile-section` chrome; what we add here is the
 * two-column bullet pair plus the freshness meta line. The clinician
 * view is its own scope — admin-side `.performance-summary-*` lives in
 * admin.css and never reaches layout.html. */
.profile-summary__lists {
    display: flex;
    flex-direction: column;
    gap: var(--space-lg);
    margin: 0 0 var(--space-md);
}

.profile-summary__list-heading {
    margin: 0 0 var(--space-sm);
    color: var(--text-muted);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.profile-summary__bullets {
    margin: 0;
    padding-left: var(--space-md);
    color: var(--text);
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
}

.profile-summary__meta {
    margin: 0 0 var(--space-md);
    color: var(--text-muted);
    font-size: var(--font-size-sm);
}

/* ── Course catalog ── */

.catalog {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.catalog-greeting {
    color: var(--color-quiet-grey);
    font-size: 1.05rem;
    margin-bottom: 0.5rem;
}

.catalog-card {
    display: flex;
    align-items: center;
    gap: 1rem;
    background: white;
    border-radius: var(--radius);
    padding: 1.25rem 1.5rem;
    text-decoration: none;
    color: var(--text);
    border-left: 4px solid var(--sage);
    animation: hubSlideIn 0.35s ease both;
}

.catalog-card:nth-child(2) { animation-delay: 0s; }
.catalog-card:nth-child(3) { animation-delay: 0.07s; }

.catalog-card--active {
    transition:
        transform var(--duration-base) var(--easing-out),
        box-shadow var(--duration-base) var(--easing-out),
        border-left-color var(--duration-base) var(--easing-standard);
    cursor: pointer;
}

.catalog-card--active:hover {
    transform: translateY(-2px);
    box-shadow: var(--shadow-hover);
    border-left-color: var(--color-sage-deep);
}

.catalog-card--active:active {
    transform: translateY(0);
    box-shadow: var(--shadow-none);
    transition:
        transform var(--duration-fast) var(--easing-standard),
        box-shadow var(--duration-fast) var(--easing-standard);
}

.catalog-card--locked {
    opacity: 0.55;
    border-left-color: var(--color-divider-ash);
    cursor: default;
}

.catalog-card--locked .catalog-card__title {
    font-weight: 400;
}

/* SVG icon sizing — inherits font-size from parent span */
.icon {
    width: 1em;
    height: 1em;
    display: inline-block;
    vertical-align: middle;
}

.catalog-card__icon {
    font-size: 1.6rem;
    line-height: 1;
    opacity: 0.5;
    flex-shrink: 0;
    width: 2rem;
    text-align: center;
}

.catalog-card__body {
    flex: 1;
    min-width: 0;
}

.catalog-card__title {
    font-size: 1.05rem;
    font-weight: 600;
    margin-bottom: 0.2rem;
}

.catalog-card__desc {
    font-size: 0.85rem;
    color: var(--color-quiet-grey);
    line-height: 1.4;
}

.catalog-card__lock-msg {
    font-size: 0.8rem;
    color: #999;
    font-style: italic;
    margin-top: 0.35rem;
}

.catalog-card__arrow {
    font-size: 1.2rem;
    color: var(--text-subtle);
    flex-shrink: 0;
    transition: color var(--duration-base) var(--easing-standard), transform var(--duration-base) var(--easing-out);
}

.catalog-card--active:hover .catalog-card__arrow {
    color: var(--text);
    transform: translateX(3px);
}

/* ── Course detail page ── */

.course-detail__back {
    display: inline-block;
    font-size: 0.9rem;
    color: var(--color-quiet-grey);
    text-decoration: none;
    margin-bottom: 1rem;
    transition: color var(--duration-base) var(--easing-standard);
}

.course-detail__back:hover {
    color: var(--text);
}

.course-detail__title {
    margin-bottom: 0.35rem;
}

.course-detail__desc {
    color: var(--color-quiet-grey);
    font-size: 0.95rem;
    margin-bottom: 1.5rem;
}

/* ── Course progress (linear-bar primitive, DESIGN.md §5) ── */

.course-progress {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    margin-bottom: 1.5rem;
}

.course-progress__track {
    width: 100%;
    height: 0.5rem;
    background: var(--color-sage-light);
    border-radius: var(--radius-md);
    overflow: hidden;
}

.course-progress__fill {
    width: var(--progress, 0%);
    height: 100%;
    background: var(--color-sage);
    border-radius: inherit;
}

.course-progress__count {
    align-self: flex-end;
    font-size: var(--font-size-xs);
    color: var(--color-quiet-grey);
    font-variant-numeric: tabular-nums;
}

/* ── Quiz progress (one-question-per-page indicator, #451) ── */

.quiz-progress {
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
    margin-bottom: var(--space-lg);
}

.quiz-progress__label {
    margin: 0;
    font-size: var(--font-size-xs);
    color: var(--color-quiet-grey-darker);
    font-variant-numeric: tabular-nums;
}

.quiz-progress__bar {
    width: 100%;
    height: 0.5rem;
    background: var(--color-sage-light);
    border-radius: var(--radius-md);
    overflow: hidden;
}

.quiz-progress__fill {
    /* Width set inline (style="width: N%") proportional to position/total. */
    height: 100%;
    background: var(--color-sage);
    border-radius: inherit;
    transform-origin: left center;
    animation: quiz-progress-grow var(--duration-base) var(--easing-out);
}

@keyframes quiz-progress-grow {
    from { transform: scaleX(0); }
}

/* Slice 5 (#453) swap-in animation: subtle cross-fade with a tiny
 * vertical lift on every htmx swap of #quiz-shell. Applied to the
 * .quiz-form and .quiz-review children (not .quiz-progress, which
 * already animates via quiz-progress-grow above — stacking both
 * would feel heavy).
 *
 * No per-rule reduced-motion wrap: the universal reduced-motion
 * media block near the bottom of this file rewrites animation
 * durations to 0.01ms for every element, collapsing this animation
 * to instant under reduce. */
#quiz-shell .quiz-form,
#quiz-shell .quiz-review {
    animation: quiz-swap-in var(--duration-base) var(--easing-out);
}

@keyframes quiz-swap-in {
    from {
        opacity: 0;
        transform: translateY(4px);
    }
}

/* ── Quiz choice pills (slice 2 of #449's quiz redesign; #462) ──
 *
 * Paints `.quiz-choice` as a full-width sage-palette pill with
 * hover, focus-visible, and selected states, plus a small bordered
 * letter badge box at the leading edge. The fieldset (`.quiz-question`)
 * lays them out as a flex column with vertical gaps so pills stack
 * cleanly. Each pill is a flex row: badge → label-text. The native
 * radio is visually hidden but stays keyboard-reachable; the parent
 * pill (via `:has()`) carries every visible affordance — hover tint,
 * focus halo, sustained selected state.
 *
 * Hover-on-checked third tone (issue body open question): the
 * sage-light hover and sage-light selected fills would collide on a
 * checked-and-hovered pill. Routed through the existing
 * `--color-sage-deep` primitive so no new hex literal is needed and
 * the #375 token guard stays clean. */

.quiz-question {
    /* Reset the fieldset chrome — the pill block carries all visual
     * grouping; the <h3> heading + aria-labelledby do the semantics. */
    border: none;
    padding: 0;
    margin: 0 0 var(--space-xl);
    /* Issue #881: margin-top clears the 20px gap threshold under margin
     * collapse with the h3 heading's browser-default margin-block-end
     * (~16.8px).  --space-xl (24px) → gap = max(16.8, 24) = 24px.
     * --space-md/--space-lg would NOT increase the gap (sneaky-pass). */
    margin-top: var(--space-xl);
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
}

.quiz-choice {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    width: 100%;
    padding: var(--space-md) var(--space-lg);
    background: var(--color-surface-white);
    border: 1px solid var(--color-divider-ash);
    border-radius: var(--radius-md);
    cursor: pointer;
    /* Establish a positioning context for the absolutely-positioned
     * radio below. Without this, the radio falls back to the nearest
     * positioned ancestor (or the initial containing block) — fine
     * today because the radio is invisible and inert, but a future
     * transform/filter on an ancestor could relocate it unpredictably.
     * Cheap defense-in-depth aligning with the standard sr-only
     * pattern. */
    position: relative;
    transition:
        background var(--duration-base) var(--easing-standard),
        border-color var(--duration-base) var(--easing-standard),
        box-shadow var(--duration-base) var(--easing-standard);
}

/* Visually hide the native radio while keeping it keyboard-
 * reachable. `display: none` and `visibility: hidden` strip the
 * input from the focus sequence; the off-canvas pattern preserves
 * Tab order and the pill's `:has(input:focus-visible)` rule below
 * carries the focus affordance instead of the native outline. */
.quiz-choice input[type="radio"] {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
    pointer-events: none;
}

.quiz-choice:hover {
    background: var(--color-sage-light);
    cursor: pointer;
}

.quiz-choice:has(input:focus-visible) {
    border-color: var(--color-sage);
    box-shadow: var(--focus-ring);
}

.quiz-choice:has(input:checked) {
    background: var(--color-sage-light);
    border-color: var(--color-sage);
}

.quiz-choice:has(input:checked):hover {
    background: var(--color-sage-deep);
    border-color: var(--color-sage-hover);
}

.quiz-choice__letter {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-sm);
    background: var(--color-surface-white);
    color: var(--color-slate-ink);
    font-variant-numeric: tabular-nums;
    font-weight: var(--font-weight-medium);
    font-size: var(--font-size-sm);
}

/* ── Quiz review (final review-before-submit step, #452) ── */

.quiz-review__heading {
    margin: 0 0 var(--space-sm);
    font-size: var(--font-size-xl);
}

.quiz-review__intro {
    margin: 0 0 var(--space-lg);
    color: var(--color-quiet-grey-darker);
    font-size: var(--font-size-sm);
}

.quiz-review__list {
    list-style: none;
    margin: 0 0 var(--space-xl);
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
}

.quiz-review__row {
    display: flex;
    gap: var(--space-md);
    align-items: flex-start;
    padding: var(--space-md);
    background: var(--color-surface-white);
    border-radius: var(--radius-md);
    border-left: 4px solid var(--color-sage-light);
}

.quiz-review__row--missing {
    border-left-color: var(--color-muted-coral);
    background: rgba(232, 160, 160, 0.06);
}

.quiz-review__row-body {
    flex: 1;
    min-width: 0;
}

.quiz-review__prompt {
    margin: 0 0 var(--space-xs);
    font-size: var(--font-size-base);
}

.quiz-review__position {
    color: var(--color-quiet-grey-darker);
    font-variant-numeric: tabular-nums;
    margin-right: var(--space-xs);
}

.quiz-review__answer {
    margin: 0;
    color: var(--color-quiet-grey-darker);
    font-size: var(--font-size-sm);
}

.quiz-review__missing {
    color: var(--color-error-text);
    font-style: normal;
    font-weight: 600;
}

.quiz-review__edit {
    flex: 0 0 auto;
}

.quiz-review__submit-form {
    display: flex;
    justify-content: flex-end;
}

.quiz-review__submit[disabled] {
    opacity: 0.5;
    cursor: not-allowed;
}

/* ── Module cards ── */

.module-list {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.module-card {
    display: flex;
    align-items: center;
    gap: 1rem;
    background: white;
    border-radius: var(--radius);
    padding: 1rem 1.25rem;
    text-decoration: none;
    color: var(--text);
    border-left: 4px solid var(--sage);
    animation: hubSlideIn 0.35s ease both;
}

.module-card:nth-child(1) { animation-delay: 0s; }
.module-card:nth-child(2) { animation-delay: 0.04s; }
.module-card:nth-child(3) { animation-delay: 0.08s; }
.module-card:nth-child(4) { animation-delay: 0.12s; }
.module-card:nth-child(5) { animation-delay: 0.16s; }
.module-card:nth-child(6) { animation-delay: 0.20s; }
.module-card:nth-child(7) { animation-delay: 0.24s; }

.module-card--active {
    transition:
        transform var(--duration-base) var(--easing-out),
        box-shadow var(--duration-base) var(--easing-out),
        border-left-color var(--duration-base) var(--easing-standard);
    cursor: pointer;
}

.module-card--active:hover {
    transform: translateY(-2px);
    box-shadow: var(--shadow-hover);
    border-left-color: var(--color-sage-deep);
}

.module-card--active:active {
    transform: translateY(0);
    box-shadow: var(--shadow-none);
    transition:
        transform var(--duration-fast) var(--easing-standard),
        box-shadow var(--duration-fast) var(--easing-standard);
}

.module-card--locked {
    opacity: 0.55;
    border-left-color: var(--color-divider-ash);
    cursor: default;
}

.module-card--locked .module-card__title {
    font-weight: 400;
}

.module-card__icon {
    font-size: 1.4rem;
    line-height: 1;
    opacity: 0.5;
    flex-shrink: 0;
    width: 1.75rem;
    text-align: center;
}

.module-card__body {
    flex: 1;
    min-width: 0;
}

.module-card__title {
    margin-bottom: 0.15rem;
}

.module-card__desc {
    font-size: 0.82rem;
    color: var(--color-quiet-grey);
    line-height: 1.4;
}

.module-card__arrow {
    font-size: 1.2rem;
    color: var(--text-subtle);
    flex-shrink: 0;
    transition: color var(--duration-base) var(--easing-standard), transform var(--duration-base) var(--easing-out);
}

.module-card--active:hover .module-card__arrow {
    color: var(--text);
    transform: translateX(3px);
}

.module-card__fidelity {
    font-size: 0.72rem;
    color: var(--text-decorative);
    font-weight: 500;
    letter-spacing: 0.02em;
    margin-top: 0.35rem;
    margin-bottom: 0;
}

/* Slice 4 (#629): didactic-gated lock reason copy on locked tiles.
   Matches .module-card__fidelity weight/spacing so the lock line and
   the fidelity counter sit cleanly above each other when both are
   visible (e.g. a fresh user staring at a locked Top Problem). */
.module-card__lock-reason {
    font-size: 0.72rem;
    color: var(--text-decorative);
    font-weight: 500;
    letter-spacing: 0.02em;
    margin-top: 0.35rem;
    margin-bottom: 0;
}

.module-card__fidelity-achieved {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    margin-top: 0.4rem;
    margin-bottom: 0;
    padding: 0.18rem 0.55rem;
    background: var(--color-sage-light);
    border: 1px solid var(--color-sage);
    border-radius: 999px;
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--color-sage-deep);
    line-height: 1.3;
}

.module-card__fidelity-check {
    font-size: 0.85rem;
    line-height: 1;
    color: var(--color-sage-deep);
}

/* ── General animations ── */

.step {
    animation: fadeIn 0.3s ease;
}

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

.btn {
    display: inline-block;
    padding: 0.75rem 1.5rem;
    border: none;
    border-radius: var(--radius);
    font-size: 1rem;
    cursor: pointer;
    transition:
        background var(--duration-base) var(--easing-standard),
        color var(--duration-base) var(--easing-standard),
        transform var(--duration-fast) var(--easing-standard),
        box-shadow var(--duration-base) var(--easing-standard);
    min-height: 44px;
    font-family: inherit;
}

.btn:active {
    transform: scale(0.98);
}

.btn:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
}

.btn-primary {
    background: var(--sage);
    color: var(--text);
    width: 100%;
    font-weight: 500;
    text-align: center;
    text-decoration: none;
}

.btn-primary:hover {
    background: var(--color-sage-hover);
}

.btn-primary:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

.btn-primary:disabled:hover {
    background: var(--sage);
}

.feedback-actions {
    display: flex;
    gap: 0.75rem;
    margin-top: 1rem;
    flex-wrap: wrap;
}

.feedback-actions .btn-primary {
    width: auto;
    flex: 1 1 10rem;
}

.btn-next {
    flex: 1 1 10rem;
    background: #e8ece8;
    color: var(--text);
    font-weight: 500;
    text-align: center;
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.btn-next:hover {
    background: #d9dfd9;
}

.btn-next--locked {
    opacity: 0.55;
    cursor: default;
    border-left: 3px solid var(--color-divider-ash);
}

.btn-next--locked:hover {
    background: #e8ece8;
}

.landing-card {
    background: white;
    border-radius: var(--radius);
    padding: 1.5rem;
}

.landing-card p {
    margin-bottom: 1.25rem;
}

/* ── Form elements ── */

input[type="text"],
input[type="email"],
input[type="password"],
textarea {
    width: 100%;
    padding: 0.65rem 0.85rem;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius);
    font-family: inherit;
    font-size: 1rem;
    color: var(--text);
    background: var(--bg);
    transition: border-color var(--duration-slow) var(--easing-standard), box-shadow var(--duration-slow) var(--easing-standard);
    box-sizing: border-box;
}

textarea {
    resize: vertical;
}

input[type="text"]:focus,
input[type="email"]:focus,
input[type="password"]:focus,
textarea:focus {
    outline: none;
    border-color: var(--sage);
    box-shadow: var(--focus-ring);
}

label {
    display: block;
    font-weight: 500;
    color: var(--text);
    margin-bottom: 0.35rem;
    margin-top: 0.85rem;
}

label:first-child {
    margin-top: 0;
}

.landing-card form .btn-primary {
    margin-top: 1.25rem;
}

/* ── Auth page elements ── */

.auth-link {
    margin-top: 1rem;
    color: var(--color-quiet-grey);
    font-size: 0.9rem;
}

.auth-link a {
    color: var(--sage);
    text-decoration: none;
    font-weight: 500;
}

.auth-link a:hover {
    text-decoration: underline;
}

.landing-card form {
    margin-top: 0.5rem;
}

.practice-list {
    list-style: none;
    margin-bottom: 1.5rem;
}

.practice-list li {
    padding: 0.35rem 0;
    padding-left: 1.25rem;
    position: relative;
}

.practice-list li::before {
    content: "–";
    position: absolute;
    left: 0;
    color: var(--sage);
    font-weight: 600;
}

/* ── Focus selection ── */

.focus-form {
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
}

.checkbox-label {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.6rem 0.75rem;
    border-radius: var(--radius);
    cursor: pointer;
    transition:
        background var(--duration-base) var(--easing-standard),
        box-shadow var(--duration-base) var(--easing-standard);
}

.checkbox-label:hover {
    background: var(--sage-light);
}

.checkbox-label:focus-within {
    background: var(--sage-light);
    box-shadow: var(--focus-ring);
}

.checkbox-label input[type="checkbox"] {
    width: 18px;
    height: 18px;
    accent-color: var(--color-sage-hover);
    cursor: pointer;
}

/* #682 Slice 4: visually distinguish focus combos whose resolved
   patient has no synthetic-plan YAML so clinicians can avoid landing
   in the empty-state interstitial. Non-gating — the checkbox stays
   selectable. Opacity scoped to the label text (NOT the checkbox
   itself) so missing coverage doesn't read as "disabled". The
   coverage note is real markup in the template so screen readers
   announce it; the value string "missing" / "covered" is pinned in
   utility_routes._categories_with_coverage (status field). */
.checkbox-label[data-synthetic-coverage="missing"] .checkbox-label__text {
    opacity: 0.65;
}

.checkbox-label__coverage-note {
    margin-left: auto;
    font-size: 0.8em;
    color: var(--text-muted);
    font-style: italic;
}

.focus-fieldset {
    border: none;
    padding: 0;
    margin: 0;
}

.focus-fieldset__heading {
    margin-bottom: var(--space-sm);
}

/* ── End-form fieldset reset (#915) ──
 * Browser-default fieldset borders bisect <legend> text, creating
 * "overlapping lines" on inner-obstacle, 3-steps, support-people,
 * and personalized-note end forms. Reset border + padding, add
 * inter-fieldset spacing. Does NOT affect .quiz-question (line 883),
 * .focus-fieldset (line 1441), or evals.css fieldset classes — all
 * have their own class-gated rules.
 * Templates: inner_obstacle_end.html, three_steps_end.html,
 * support_people_end.html, personalized_note_end.html */
.inner-obstacle__obstacle,
.inner-obstacle__action,
.inner-obstacle__plan,
.three-steps__step,
.support-people__person,
.personalized-note__note {
    border: none;
    padding: 0;
    margin: 0 0 var(--space-lg);
}

.inner-obstacle__obstacle > legend,
.inner-obstacle__action > legend,
.inner-obstacle__plan > legend,
.three-steps__step > legend,
.support-people__person > legend,
.personalized-note__note > legend {
    padding: 0;
    font-weight: var(--font-weight-semibold);
    margin-bottom: var(--space-sm);
}

.optional-label {
    font-weight: 400;
    color: #888;
    font-size: 0.85em;
}

.radio-label {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.6rem 0.75rem;
    border-radius: var(--radius);
    cursor: pointer;
    transition:
        background var(--duration-base) var(--easing-standard),
        box-shadow var(--duration-base) var(--easing-standard);
}

.radio-label:hover {
    background: var(--sage-light);
}

.radio-label:focus-within {
    background: var(--sage-light);
    box-shadow: var(--focus-ring);
}

.radio-label input[type="radio"] {
    width: 18px;
    height: 18px;
    accent-color: var(--color-sage-hover);
    cursor: pointer;
}

.focus-form .btn-primary {
    margin-top: 0.75rem;
}

.error,
.error-message {
    color: #c0392b;
    background: #fde8e8;
    padding: 0.75rem 1rem;
    border-radius: var(--radius);
    margin-bottom: 1rem;
    font-size: 0.9rem;
}

.mic-troubleshooting {
    margin-top: 1.25rem;
    font-size: 0.85rem;
    color: var(--color-quiet-grey);
}

.mic-permission {
    margin-bottom: var(--space-xl);
    padding: var(--space-lg) var(--space-xl);
    background: var(--surface-raised);
    border-radius: var(--radius);
    color: var(--text);
    transition: background var(--duration-base) var(--easing-standard);
}

/* AC4: distinct state treatments using only existing palette tokens.
 * `prompt` reads as "next action needed" via sage-light wash.
 * `denied` reads as "blocked, action needed" via warm encourage-sand
 * — the same amber tone already on usage warnings, no new accent. */
.mic-permission[data-state="prompt"] {
    background: var(--sage-light);
}

.mic-permission[data-state="denied"] {
    background: var(--color-encourage-sand);
}

.mic-permission__copy {
    margin: 0 0 0.75rem;
    font-size: 0.9rem;
    line-height: 1.5;
}

/* ── Mic blocked page ── */

.mic-blocked h2 {
    margin-bottom: 0.75rem;
}

.mic-blocked__recovery-title {
    margin-top: 1rem;
    margin-bottom: 0.5rem;
}

.mic-blocked__actions {
    display: flex;
    align-items: center;
    gap: 1rem;
    margin-top: 1.25rem;
    flex-wrap: wrap;
}

.mic-blocked__back {
    color: var(--color-quiet-grey);
    text-decoration: none;
    font-size: 0.9rem;
}

.mic-blocked__back:hover {
    text-decoration: underline;
}

.mic-blocked__status {
    margin-top: 0.75rem;
    font-size: 0.9rem;
    color: #c0392b;
    min-height: 1.2em;
}

.mic-blocked__status:empty {
    margin-top: 0;
}

.mic-troubleshooting > summary {
    cursor: pointer;
    display: inline-block;
    padding: 0.25rem 0;
}

.mic-troubleshooting > summary:hover {
    color: #3d4852;
}

.mic-troubleshooting__body {
    margin-top: 0.5rem;
    padding: 0.75rem 1rem;
    background: #f5f7f9;
    border-radius: var(--radius);
    color: #3d4852;
}

.mic-troubleshooting__body ol {
    margin: 0.5rem 0 0;
    padding-left: 1.25rem;
}

.mic-troubleshooting__body li {
    margin-bottom: 0.25rem;
}

.htmx-indicator {
    display: none;
}

.htmx-request .htmx-indicator,
.htmx-request.htmx-indicator {
    display: block;
}

/* AC3 + DESIGN.md §5: labelled-status with a subtle pulsing dot.
 * The `::before` dot is the visual cue; the existing text remains the
 * label ("Starting session…", "Ending section…"). Drops the bare-text
 * opacity pulse the v1 rule used in favour of a single, restrained dot
 * — §5 forbids bare spinners and prefers a labelled status. */
.pulse {
    text-align: center;
    color: var(--text-muted);
    padding: var(--space-xl);
}

.pulse::before {
    content: "";
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: currentColor;
    margin-right: var(--space-sm);
    vertical-align: 1px;
    animation: pulseDot 1.5s var(--easing-standard) infinite;
}

@keyframes pulseDot {
    0%, 100% { opacity: 0.4; transform: scale(0.9); }
    50% { opacity: 1; transform: scale(1.1); }
}

/* ── Chat bubbles ── */

#conversation {
    max-height: 50vh;
    overflow-y: auto;
    padding: 0.5rem 0;
    margin-bottom: 1rem;
    /* P5 follow-up: Firefox-side affordance. Webkit equivalents below. */
    scrollbar-width: thin;
    scrollbar-color: var(--color-sage) transparent;
}

/* P5 follow-up: macOS hides the platform scrollbar at rest, so a
 * clinician scrolling up to read history can miss new bubbles below
 * the fold. A thin, sage-thumbed bar makes overflow honest without
 * the chat-app gamification of a "↓ new messages" badge. Track stays
 * transparent (Flat-At-Rest Rule); thumb deepens on hover so the
 * grab affordance reads. */
#conversation::-webkit-scrollbar {
    width: 6px;
}

#conversation::-webkit-scrollbar-track {
    background: transparent;
}

#conversation::-webkit-scrollbar-thumb {
    background: var(--color-sage);
    border-radius: 3px;
}

#conversation::-webkit-scrollbar-thumb:hover {
    background: var(--color-sage-hover);
}

.message {
    max-width: 80%;
    padding: 0.75rem 1rem;
    border-radius: var(--radius);
    margin-bottom: 0.75rem;
    font-size: var(--font-size-base);
    line-height: var(--line-height-relaxed);
    animation: fadeIn 0.3s ease;
}

.message-label {
    display: block;
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-medium);
    color: var(--color-quiet-grey);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin-bottom: 0.25rem;
}

.message.patient {
    background: var(--sage-light);
    border-left: 3px solid var(--sage);
    margin-right: auto;
}

.message.clinician {
    background: var(--blue);
    border-right: 3px solid var(--blue-hover);
    margin-left: auto;
}

/* AC5: empty `<p>` inside a patient bubble (pre-TTS reveal) reads as
 * intentional, not blank. Once audio.js writes the patient text the
 * `:empty` matcher releases and the placeholder disappears. */
.message.patient > p:empty::before {
    content: "Patient is preparing to speak.";
    color: var(--text-decorative);
    font-style: italic;
}

/* ── Session chrome (AC2: header strip + mic container + status) ── */

.session-chrome__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-md);
    padding: var(--space-sm) 0;
    margin-bottom: var(--space-lg);
    border-bottom: 1px solid var(--border);
}

.session-chrome__label {
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    color: var(--text-muted);
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.session-chrome__mic {
    display: flex;
    gap: var(--space-sm);
    margin-top: var(--space-lg);
}

.btn-speak {
    flex: 1;
    background: var(--sage);
    color: var(--text);
    font-weight: 600;
    font-size: 1.1rem;
    min-height: 56px;
    border-radius: 28px;
    transition:
        background var(--duration-base) var(--easing-standard),
        color var(--duration-base) var(--easing-standard),
        transform var(--duration-fast) var(--easing-standard),
        box-shadow var(--duration-base) var(--easing-standard);
}

.btn-speak:hover {
    background: var(--color-sage-hover);
}

/* AC2: five lifecycle phases pick up restrained, distinct treatments
 * via the `data-mic-state` hook. Tokens-only — the recording red
 * routes through `--color-muted-coral` so no off-palette literal
 * survives, and the `recordPulse` keyframe is the sole motion
 * primitive (slice 3 ceiling honoured). */
.btn-speak[data-mic-state="requesting"] {
    background: var(--sage-light);
    color: var(--color-quiet-grey-darker);
    cursor: progress;
}

.btn-speak[data-mic-state="listening"] {
    background: var(--color-muted-coral);
    color: var(--text);
    animation: recordPulse 1.5s var(--easing-out) infinite;
}

.btn-speak[data-mic-state="processing"] {
    background: var(--sage-light);
    color: var(--color-quiet-grey-darker);
    cursor: wait;
}

.btn-speak[data-mic-state="acknowledged"] {
    background: var(--sage-light);
    color: var(--color-quiet-grey-darker);
}

.btn-speak:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

@keyframes recordPulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(232, 160, 160, 0.4); }
    50% { box-shadow: 0 0 0 12px rgba(232, 160, 160, 0); }
}

/* End-Section ghost-tier in the mic container — transparent over cream
 * with a 1px load-bearing border, parallels the §5 Ghost variant on
 * .btn-logout / .header-admin-link. Secondary to .btn-speak (no flex:1,
 * no sage background). #799: moved from header strip to .session-chrome__mic
 * so Speak (primary) and End Section (secondary) share the same visual
 * grouping. */
.btn-end {
    background: none;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius);
    padding: 0.4rem 0.85rem;
    font-size: 0.85rem;
    line-height: var(--line-height-base);
    color: var(--color-quiet-grey);
    min-height: auto;
    white-space: nowrap;
}

.btn-end:hover {
    background: #f0f0f0;
    color: var(--text);
}

/* #799: hidden state collapses without flex gap so the mic container
 * does not reserve space for an invisible button. */
.btn-end[hidden] {
    display: none;
}

/* AC2: status indicator adopts the labelled-status-with-dot pattern
 * (DESIGN.md §5 Progress indicators). The dot sits inline with "Your
 * turn" / "Listening…" / "Patient is speaking…" and pulses via the
 * shared keyframe used by `.pulse::before`. */
.status-text {
    text-align: center;
    color: var(--text-muted);
    font-size: 0.9rem;
    padding: var(--space-sm);
    min-height: 2rem;
}

/* Static dot — distinguishes the status surface from prose without
 * pulsing during steady-state ("Your turn"). The `--active` modifier
 * (Slice 5 / #397) flips the dot into the labelled-status pulse while
 * the patient considers — visible motion that reads as "still working"
 * during the /respond round-trip. */
.status-text::before {
    content: "";
    display: inline-block;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentColor;
    margin-right: var(--space-sm);
    vertical-align: 1px;
    opacity: 0.6;
}

/* AC4: while the patient is considering, the status dot pulses via the
 * existing `pulseDot` keyframe (slice 4 vocabulary, slice 3 ceiling).
 * No new keyframe — the global `prefers-reduced-motion: reduce` block
 * neutralises this animation alongside every other. */
.status-text--active::before {
    animation: pulseDot 1.5s var(--easing-standard) infinite;
    opacity: 1;
}

/* Slice 3 (#541): "X responses left" counter pill at <=3 remaining.
 * Sand tone — a calm wind-down signal, not an alarm; sits between the
 * conversation column and the status line. The `[hidden]` selector
 * defends `display: block` against the native `hidden` attribute so the
 * pill collapses fully when toggled by audio.js (which it is on every
 * non-warning turn). */
.responses-left-pill {
    display: block;
    width: max-content;
    margin: 0 auto var(--space-sm);
    padding: 0.25rem 0.75rem;
    background: var(--color-encourage-sand);
    border: 1px solid var(--color-encourage-sand-hover);
    border-radius: 999px;
    font-size: 0.8rem;
    font-weight: 500;
    color: var(--color-quiet-grey-darker);
    line-height: 1.3;
}

.responses-left-pill[hidden] {
    display: none;
}

/* ── Feedback ── */

.feedback-card {
    border-radius: var(--radius);
    padding: 1.25rem;
    margin-bottom: 1.25rem;
    background: white;
    border-left: 4px solid var(--sage);
}

.feedback-card.rating-exemplary {
    border-left-color: var(--color-sage-deep);
}

.feedback-card.rating-proficient {
    border-left-color: var(--sage);
}

.feedback-card.rating-developing {
    border-left-color: var(--color-encourage-sand-hover);
}

.rating-badge {
    display: inline-block;
    padding: 0.25rem 0.75rem;
    border-radius: 12px;
    font-size: 0.85rem;
    font-weight: 600;
    margin-bottom: 0.75rem;
}

.rating-exemplary .rating-badge {
    background: var(--color-sage-light);
    color: #3d6b32;
}

.rating-proficient .rating-badge {
    background: var(--sage-light);
    color: #4a6944;
}

.rating-developing .rating-badge {
    background: var(--encourage);
    color: #8a6d2f;
}

.feedback-summary {
    font-style: italic;
    line-height: 1.5;
}

.feedback-section {
    background: white;
    border-radius: var(--radius);
    padding: 1.25rem;
    margin-bottom: 1rem;
}

.feedback-section h3 {
    margin-bottom: 0.75rem;
}

.feedback-text {
    color: #4a5568;
    margin-top: 0.5rem;
}

/* ── Growth-oriented feedback sections (Top Problem) ── */

.growth-section {
    background: white;
    border-radius: var(--radius);
    padding: 1.25rem;
    margin-bottom: 1rem;
    border-left: 4px solid var(--sage-light);
}

.growth-section--positive {
    border-left-color: var(--sage);
}

.growth-section--critique {
    border-left-color: var(--encourage);
}

.growth-section--synthesis {
    border-left-color: var(--blue);
}

.growth-section h3 {
    font-size: var(--font-size-lg);
    font-weight: var(--font-weight-semibold);
    margin-bottom: 0.5rem;
    color: var(--text);
}

.growth-section-body {
    color: #4a5568;
    line-height: 1.55;
    max-width: 68ch;
}

/* ── Checklist ── */

.checklist {
    list-style: none;
    margin-bottom: 0.5rem;
}

.checklist-item {
    padding: 0.3rem 0;
    padding-left: 1.5rem;
    position: relative;
}

.checklist-item::before {
    position: absolute;
    left: 0;
    font-weight: 600;
}

.checklist-item.checked::before {
    content: "\2713";
    color: #5a9e4b;
}

.checklist-item.unchecked::before {
    content: "\2717";
    color: #c0392b;
}

/* Per-dim fallback marker (#501 AC6 + ADR-0087): when a dimension
   call fails the slot lands False + sources[is_X]="fallback"; the row
   is muted and the X glyph is replaced with a question mark so a
   transient transport error reads differently from a real 0-score. */
.checklist-item--fallback {
    color: #888;
    font-style: italic;
}

.checklist-item--fallback.unchecked::before {
    content: "?";
    color: #888;
}

.checklist-fallback-note {
    margin-left: 0.4rem;
    font-size: 0.85em;
    color: #888;
}

/* ── Quote boxes ── */

.quote-box {
    padding: 0.75rem 1rem;
    border-radius: var(--radius);
    margin-bottom: 0.5rem;
    font-style: italic;
    border-left: 3px solid;
}

.quote-box.strength {
    background: var(--sage-light);
    border-left-color: var(--sage);
}

.quote-box.growth {
    background: var(--encourage);
    border-left-color: var(--color-encourage-sand-hover);
}

/* ── Feedback tracker (async job progress) ── */

.feedback-tracker {
    position: relative;
    background: white;
    border-radius: var(--radius);
    padding: 1.5rem;
    margin-bottom: 1rem;
    border-left: 4px solid var(--sage);
}

/* AC6 / Audit #401 Gap 5: a calm sage heartbeat in the corner reads
 * as "still working" between the 1s polls. Reuses the slice 4
 * `pulseDot` keyframe (slice 3 ceiling honoured) and inherits the
 * universal `prefers-reduced-motion: reduce` gate from the block at
 * the bottom of this file — no per-rule wrap needed. */
.feedback-tracker::after {
    content: "";
    position: absolute;
    top: var(--space-md);
    right: var(--space-md);
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--sage);
    animation: pulseDot 1.5s var(--easing-standard) infinite;
}

/* Terminal states own the spotlight — celebrate / overflow animations
 * already carry the visible work, so the heartbeat steps aside. */
.feedback-tracker--complete::after,
.feedback-tracker--overflow::after {
    display: none;
}

.feedback-tracker__title {
    font-size: 1.1rem;
    font-weight: 600;
    color: var(--text);
    margin-bottom: 1rem;
}

.feedback-progress {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    margin-bottom: 1rem;
    view-transition-name: feedback-progress;
}

.feedback-progress__track {
    width: 100%;
    height: 0.5rem;
    background: var(--sage-light);
    border-radius: 999px;
    overflow: hidden;
}

.feedback-progress__fill {
    width: var(--progress, 0%);
    height: 100%;
    background: var(--sage);
    border-radius: inherit;
    transition: width 600ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

.feedback-progress__count {
    align-self: flex-end;
    font-size: 0.8rem;
    color: var(--color-quiet-grey-soft);
    font-variant-numeric: tabular-nums;
}

.tracker-stations {
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin: 0;
    padding: 0;
}

.station {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.5rem 0.75rem;
    border-radius: calc(var(--radius) - 2px);
    transition: background-color 200ms ease, color 200ms ease;
}

.station-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.25rem;
    height: 1.25rem;
    border-radius: 50%;
    font-size: 0.9rem;
    line-height: 1;
    flex-shrink: 0;
    transition: background-color 200ms ease, color 200ms ease;
}

.station-label {
    color: inherit;
}

.station--done {
    color: var(--text);
}

.station--done .station-icon {
    background: var(--sage);
    color: white;
    font-weight: 600;
}

.station--active {
    color: var(--text);
    background: var(--sage-light);
}

.station--active .station-icon {
    background: var(--sage);
    color: white;
    animation: tracker-pulse 1.4s ease-in-out infinite;
}

.station--pending {
    color: var(--color-quiet-grey-soft);
}

.station--pending .station-icon {
    background: transparent;
    color: #bac3cb;
    border: 1px solid #d6dde2;
}

@keyframes tracker-pulse {
    0%, 100% {
        transform: scale(1);
        opacity: 1;
    }
    50% {
        transform: scale(1.15);
        opacity: 0.75;
    }
}

.feedback-tracker--complete {
    border-left-color: var(--success, var(--sage));
}

.feedback-tracker--overflow {
    border-left-color: var(--sage-light);
}

.feedback-tracker--overflow .station--active .station-icon {
    animation: none;
    opacity: 0.85;
}

.tracker-overflow-message {
    margin: 0.75rem 0 0;
    font-size: 0.9rem;
    color: var(--color-quiet-grey-soft);
    font-style: italic;
}

/* ── Self-quiz partials (issue #846) ──
 *
 * The self-quiz response partial (partials/self_quiz_response.html)
 * renders strength / growth / module-goal sections plus a Start
 * Practice Session CTA; the question partial
 * (partials/self_quiz_question.html) renders the free-text prompt
 * + textarea. Both inherit the global ``* { margin: 0; padding: 0 }``
 * reset (style.css:147-151) so every element starts flush — these
 * rules re-establish vertical rhythm using design-system spacing
 * tokens, mirroring the .feedback-card / .growth-section card
 * pattern (raised surface + left-border accent + padding) so each
 * section reads as a distinct block rather than a wall of prose.
 *
 * Per ADR-0099, self-quiz CSS lives in style.css (feature CSS owner),
 * NOT didactic.css (didactic-scoped only).
 */

.self-quiz-response {
    display: flex;
    flex-direction: column;
    gap: var(--space-lg);
}

.self-quiz-response__strength {
    padding: var(--space-md) var(--space-lg);
    background: var(--surface-raised);
    border-radius: var(--radius);
    border-left: 4px solid var(--color-sage);
}

.self-quiz-response__title {
    margin-bottom: var(--space-sm);
}

.self-quiz-response__acknowledgment {
    margin-top: var(--space-xs);
    line-height: var(--line-height-relaxed);
}

.self-quiz-response__growth {
    padding: var(--space-md) var(--space-lg);
    background: var(--surface-raised);
    border-radius: var(--radius);
    border-left: 4px solid var(--color-encourage-sand-hover);
}

.self-quiz-response__growth-title {
    margin-bottom: var(--space-sm);
}

.self-quiz-response__tip {
    margin-top: var(--space-xs);
    line-height: var(--line-height-relaxed);
}

.self-quiz-response__module-goal {
    padding: var(--space-md) var(--space-lg);
    background: var(--surface-raised);
    border-radius: var(--radius);
    border-left: 4px solid var(--color-mist-blue);
}

.self-quiz-response__module-goal-title {
    margin-bottom: var(--space-sm);
}

.self-quiz-response__goal {
    margin-top: var(--space-xs);
    line-height: var(--line-height-relaxed);
}

.self-quiz-response__bullets {
    margin: var(--space-sm) 0 0;
    padding-left: var(--space-lg);
}

.self-quiz-response__bullet {
    margin-bottom: var(--space-xs);
}

/* Extra margin-top on top of the parent's --space-lg flex gap so the
 * CTA sits with deliberate breathing room — asymmetric to the
 * section-to-section rhythm so the button reads as "the action"
 * rather than the next stacked card child. Mirrors the
 * .action-plan-review__continue pattern. */
.self-quiz-response__start {
    margin-top: var(--space-md);
}

/* ── Self-quiz question partial ──
 *
 * The question surface is a single prompt + textarea form, not a
 * multi-section card stack, so it uses a lighter flex-column rhythm
 * (gap --space-md) without card chrome. The textarea inherits the
 * global ``textarea`` rule (border, padding, focus ring); this block
 * only adds the spacing between the reassurance line, the question
 * heading, the form, and the error message. */

.self-quiz-question {
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
}

.self-quiz-question__reassurance {
    margin-bottom: var(--space-sm);
    color: var(--text-muted);
    line-height: var(--line-height-relaxed);
}

.self-quiz-question__text {
    margin-bottom: var(--space-sm);
}

.self-quiz-question__form {
    margin-top: var(--space-sm);
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
}

.self-quiz-question__textarea {
    margin-bottom: var(--space-sm);
}

.self-quiz-question__error {
    margin-top: var(--space-sm);
    margin-bottom: var(--space-sm);
    color: var(--color-error-text);
}

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

/* ── Responsive enhancements (issue #377) ──
 * Mobile-first: every rule above this block is the small-screen
 * default. Width-based @media queries below add desktop polish
 * where the empty 1440 viewport invites it (loosen container
 * padding, breathe between sections) and tighten the small-screen
 * surface where the desktop sizing was too generous (44px tap
 * targets on header affordances + the /profile inline rungs).
 *
 * The breakpoint values literal-duplicate `--bp-sm` (40em),
 * `--bp-md` (64em), and `--bp-lg` (80em) declared in `:root`.
 * Media-query conditions cannot consume `var()` until CSS Custom
 * Media Queries (Level 5) ship; until then the tokens act as
 * documentation and the literals here as the consumers. Keep them
 * in sync — the magic-value test in `tests/test_token_scale.py`
 * lints the names but cannot lint the values.
 *
 * The three rodney verification viewports (375 / 768 / 1440)
 * exercise below `--bp-sm`, between `--bp-sm` and `--bp-md`, and
 * above `--bp-lg` respectively, so each block below has at least
 * one captured frame proving it took effect.
 */

/* ── Below --bp-sm (mobile, e.g. 375px) ── */
@media (max-width: 39.99em) {
    /* 44px minimum tap target on header chrome and the /profile
     * inline rungs — Apple HIG / WCAG 2.5.5. The desktop sizes
     * (34px / 26px) were tuned for mouse precision; on a touch
     * surface they shrink below the comfortable hit zone. */
    .header-admin-link,
    .btn-logout {
        min-height: 44px;
        display: inline-flex;
        align-items: center;
    }
    .profile-history__link {
        min-height: 44px;
        display: inline-flex;
        align-items: center;
    }

    /* Tighter container padding on mobile so the constrained
     * viewport carries more usable content width (375px ⇒ 343px
     * inner instead of 327px). */
    .container {
        padding-left: var(--space-lg);
        padding-right: var(--space-lg);
    }

    /* #803: cheat sheet panel takes less vertical room on mobile so
     * the sheet doesn't dominate the 375px viewport. Padding tightens
     * from --space-xl to --space-lg for the same reason. */
    .cheat-sheet__panel {
        max-height: 60vh;
        padding: var(--space-lg);
    }
}

/* ── At/above --bp-sm (tablet/desktop, e.g. 768px) ── */
@media (min-width: 40em) {
    /* #914: Constrain cheat sheet panel to main content column width
     * on tablet/desktop. Complement to mobile @media (max-width: 39.99em).
     * Matches .container max-width: 600px (line 162). */
    .cheat-sheet__panel {
        max-width: 600px;
        margin: 0 auto;
    }
}

/* ── At/above --bp-md (desktop, e.g. 1440px) ── */
@media (min-width: 64em) {
    /* Slightly looser vertical padding on the container so the
     * single-column shell breathes against the larger desktop
     * canvas; horizontal padding stays unchanged so the inner
     * column width remains consistent. */
    .container {
        padding-top: var(--space-2xl);
        padding-bottom: var(--space-2xl);
    }
}

.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.back-link {
    display: inline-block;
    color: var(--color-quiet-grey);
    text-decoration: none;
    font-size: 0.85rem;
    margin-bottom: 0.5rem;
}

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

/* #578 Slice 1 follow-up — Slice 7 (#485) shipped the three-CTA
   choice partial without a container rule, so the buttons stacked
   flush against each other and against the heading (visible in the
   #578 rodney captures). Apply flex column with a small inter-item
   gap so the CTAs read as distinct choices rather than one tall
   green slab. The buttons keep their full-width .btn-primary
   styling; the parent owns inter-item gap, so a future fourth CTA
   is one HTML line, no CSS edit. The recap heading + choice heading
   get zero block margin so the flex gap is the single source of
   vertical rhythm inside the container. */
.practice-choice {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.practice-choice__heading {
    margin: 0;
    font-size: 1rem;
    font-weight: 600;
    color: var(--text);
}

/* Slice 8 (#486) — Continue-with-same-patient recap surface on the
   Miracle Question start page. The dl pairs render as label/value
   stacks; the section sits above the choice CTAs to ground the
   user in *which* prior session they're about to resume before
   they click. Margins zeroed once #578's flex container took over
   inter-item spacing; the recap reads as one block of the
   choice-partial stack rather than a floating island. */
.practice-choice__recap {
    margin: 0;
    padding: 0.75rem 1rem;
    background: var(--surface-raised);
    border: 1px solid var(--border);
    border-radius: 6px;
}

.practice-choice__recap-heading {
    margin: 0 0 0.5rem 0;
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--text);
}

.practice-choice__recap-list {
    margin: 0;
    display: grid;
    gap: 0.4rem;
}

.practice-choice__recap-list dt {
    margin: 0;
    font-size: 0.8rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
}

.practice-choice__recap-list dd {
    margin: 0 0 0.25rem 0;
    font-size: 0.95rem;
    line-height: 1.4;
    color: var(--text);
}

/* #646 Slice 1 — Review Action Plan card chrome.
   Slice 10 (#488) shipped the filled review surface with BEM classes
   but no CSS rules; the page rendered as unstyled markup. This block
   gives the surface real card chrome: raised white surface on the
   warm-cream page background, design-system 8px radius (--radius),
   generous --space-xl padding, and the resting --shadow-hover lift
   so the card reads as the primary subject of the page rather than a
   thin-bordered box. Larger and more visually weighted than the
   inline .practice-choice__recap recap above; the two surfaces share
   tokens (--surface-raised, --border, label/value rhythm) but the
   review page is a destination, not an inline summary, so it earns
   stronger chrome. */
.action-plan-review {
    display: flex;
    flex-direction: column;
    gap: var(--space-lg);
    padding: var(--space-xl);
    background: var(--surface-raised);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-hover);
}

/* Heading zeroes the global h2's margin-bottom (style.css:323-328) so
   the parent gap is the only vertical rhythm. Pulled up in size and
   tightened in leading so it reads as the card's primary heading
   rather than a slightly-bolder body line. */
.action-plan-review__heading {
    margin: 0;
    font-size: 1.25rem;
    font-weight: 600;
    line-height: 1.3;
    color: var(--text);
}

.action-plan-review__section {
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
}

/* Section heading = label (mirrors .practice-choice__recap-list dt
   typography: smaller, uppercase, weight 600, muted color) so the
   h3 stops competing on h-scale and reads as the field name above
   its value. Body keeps default size/leading and uses --text so the
   value reads as primary copy. Margins zeroed so the parent
   .action-plan-review gap owns vertical rhythm; the section's own
   inner --space-xs gap separates the label from the value. */
.action-plan-review__section-heading {
    margin: 0;
    font-size: 0.8rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
}

.action-plan-review__section-body {
    margin: 0;
    font-size: 1rem;
    line-height: 1.55;
    color: var(--text);
}

/* Extra margin-top on top of the parent's --space-lg flex gap so the
   CTA sits with deliberate breathing room — asymmetric to the
   section-to-section rhythm so the button reads as "the action"
   rather than the next list item. Keeps the inherited btn/btn-primary
   contract; no btn-shape overrides leak from this block. */
.action-plan-review__continue {
    margin-top: var(--space-md);
}

/* #647 Slice 2 — empty-state variant. The `--empty` partial inherits the
   filled card chrome via the outer .action-plan-review class; this block
   only adds the two-tier body/hint typography (fact then recovery) and
   the CTA breathing room. No chrome overrides — the surface, border,
   radius, padding, and shadow flow from the Slice 1 rule above. */
.action-plan-review__empty-body {
    margin: 0;
    font-size: 1rem;
    line-height: 1.55;
    color: var(--text);
}

/* Recovery tier — sits beneath __empty-body as secondary copy. Muted
   color + smaller font-size so the user reads fact-then-recovery as
   two visual tiers, parallel to how __section-heading reads as label
   beneath __section-body's value in the filled variant. */
.action-plan-review__empty-hint {
    margin: 0;
    font-size: 0.9rem;
    line-height: 1.5;
    color: var(--text-muted);
}

/* Extra margin-top on top of the parent's --space-lg flex gap so the
   recovery CTA sits with deliberate breathing room beneath the
   two-tier copy — mirrors __continue's asymmetric spacing so the
   button reads as "the action" rather than the next stacked card
   child. Inherits .btn-primary; no btn-shape overrides leak here. */
.action-plan-review__start-fresh {
    margin-top: var(--space-md);
}

/* ── Cheat sheet bottom sheet (issue #803) ──
 * Bottom-anchored modal panel triggered from .session-chrome__header.
 * At rest: display:none + hidden attr (belt + suspenders) so the sheet
 * never shifts #conversation or .session-chrome__mic. When open:
 * position:fixed overlay with backdrop + panel; the global
 * prefers-reduced-motion block (above) covers the slide transition
 * via its universal selector + !important — no new reduced-motion
 * rule needed here. */

.btn-cheat-sheet {
    background: transparent;
    color: var(--text-muted);
    border: 1px solid var(--border);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    padding: var(--space-xs) var(--space-md);
}

.btn-cheat-sheet:hover {
    background: var(--sage-light);
    color: var(--text);
}

#cheat-sheet {
    display: none;
}

#cheat-sheet[aria-hidden="false"] {
    display: block;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 100;
}

.cheat-sheet__backdrop {
    position: fixed;
    inset: 0;
    background: rgba(44, 62, 80, 0.35);
    z-index: 99;
}

.cheat-sheet__panel {
    position: relative;
    background: var(--surface-raised);
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    padding: var(--space-xl);
    max-height: 70vh;
    overflow-y: auto;
    z-index: 101;
    transform: translateY(0);
    transition: transform var(--duration-base) var(--easing-out);
}

#cheat-sheet[aria-hidden="true"] .cheat-sheet__panel {
    transform: translateY(100%);
}

.cheat-sheet__title {
    margin: 0 0 var(--space-lg);
    font-size: var(--font-size-md);
    font-weight: var(--font-weight-semibold);
}

.cheat-sheet__close {
    position: absolute;
    top: var(--space-sm);
    right: var(--space-sm);
    background: transparent;
    border: none;
    font-size: 1.5rem;
    line-height: 1;
    color: var(--text-muted);
    cursor: pointer;
    padding: var(--space-xs) var(--space-sm);
    min-height: 44px;
    min-width: 44px;
}

.cheat-sheet__close:hover {
    color: var(--text);
}

.cheat-sheet__close:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
}

.cheat-sheet__section {
    margin-bottom: var(--space-lg);
}

.cheat-sheet__section:last-child {
    margin-bottom: 0;
}

.cheat-sheet__label {
    display: block;
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    color: var(--text-muted);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    margin-bottom: var(--space-xs);
}

.cheat-sheet__section p {
    margin: 0;
    line-height: var(--line-height-relaxed);
}

/* ── Quit-session confirmation modal (issue #919) ──
 * Centered-card overlay triggered when a clinician attempts to leave
 * an active practice session. At rest: display:none + hidden attr
 * (belt + suspenders) so the modal never flashes on page load. When
 * open: position:fixed flexbox overlay with backdrop + card.
 *
 * Flexbox centering (align-items + justify-content) is used instead
 * of transform: translate(-50%, -50%) to avoid prefers-reduced-motion
 * suppression and keep positioning robust.
 *
 * z-index layering: backdrop (105) < card (106) < modal container
 * (110). The container is the flex parent; backdrop and card are
 * children positioned independently.
 *
 * NOT in this file: JS show/hide logic, focus trap, ESC handler —
 * those land in AC-2/AC-3. */

#quit-session-modal {
    display: none;
}

#quit-session-modal[aria-hidden="false"] {
    display: flex;
    position: fixed;
    inset: 0;
    align-items: center;
    justify-content: center;
    z-index: 110;
}

.quit-session-modal__backdrop {
    position: fixed;
    inset: 0;
    background: rgba(44, 62, 80, 0.35);
    z-index: 105;
}

.quit-session-modal__card {
    position: relative;
    z-index: 106;
    background: var(--surface-raised);
    border-radius: var(--radius-lg);
    padding: var(--space-xl);
    max-width: 400px;
    width: 90%;
}

.quit-session-modal__card h2 {
    margin: 0 0 var(--space-lg);
    font-size: var(--font-size-md);
    font-weight: var(--font-weight-semibold);
}

.quit-session-modal__actions {
    display: flex;
    gap: var(--space-md);
    justify-content: flex-end;
}
