/*
 * TASK-075 — shell + page-frame styles. Consumes the design-token CSS
 * custom properties emitted by SpecStep.Web.Theme.TokenCssEmitter
 * (loaded from /css/tokens.css). This file owns the shell layout
 * (top bar + left rail + main) plus shared page-frame utility
 * classes; per-surface styles colocate with the Razor components.
 */

* { box-sizing: border-box; }

html, body {
    margin: 0;
    padding: 0;
    background: var(--bg);
    color: var(--fg);
    font-family: var(--font-sans);
    font-size: var(--text-14);
    line-height: var(--leading-normal, 1.5);
    -webkit-font-smoothing: antialiased;
}

/* ---- skip-to-content (TASK-081a) ---- */
.skip-to-content {
    position: absolute;
    top: -40px;
    left: var(--space-8);
    z-index: 10000;
    padding: var(--space-8) var(--space-12);
    background: var(--accent);
    color: white;
    border-radius: var(--radius-4);
    font-weight: var(--weight-semibold);
    text-decoration: none;
}
.skip-to-content:focus { top: var(--space-8); }

/* ---- shell ---- */
.sf-shell {
    display: grid;
    grid-template-columns: 240px 1fr;
    /* 4th row (auto) hosts the global AppFooter. The 1fr main row
       still consumes all remaining vertical space, so the workspace
       doesn't visibly shrink unless the user scrolls. min-height
       (not height) so the shell can grow when its content is taller
       than the viewport — without this, long pages like /interview
       at small viewports would clip the footer below the fold with
       no scroll.

       2026-05-16 — topbar row bumped from 56px → 68px to match
       MarketingLayout's header height (.sf-marketing__header has
       18px vertical padding around 32px-tall iconbuttons → 68px
       total). The prior 56px row exactly equaled 16px pad + 24px
       lockup + 16px pad, leaving zero breathing room AND clipping
       the lockup at the row edges. Updated `.sf-topbar` padding +
       `.sf-topbar__lockup` height below in the same block so all
       three changes flow together. */
    /* 2026-05-31 — 68px → 72px to fit the 36px tagline lockup (was
       28px) with the same 18px breathing each side. */
    grid-template-rows: 72px 1fr auto;
    grid-template-areas:
        "topbar topbar"
        "rail   main"
        "footer footer";
    min-height: 100vh;
}

/* PR-T41 (2026-05-08) — topbar harmonized with the marketing
   header (.sf-marketing__header). Same sticky-blur backdrop,
   same editorial padding + gap, same border treatment. The
   account-chrome children (version chip, alerts bell, avatar)
   stay; only the wrapper styling converges with marketing so
   the two shells feel like the same product. */
.sf-topbar {
    grid-area: topbar;
    position: sticky;
    top: 0;
    z-index: 50;
    display: flex;
    align-items: center;
    gap: var(--space-16);
    /* 2026-05-16 — 16px → 18px vertical padding to match
       MarketingLayout's header. Paired with grid-template-rows: 72px
       on .sf-shell (above) so the 36px tagline lockup + 32px
       iconbuttons both fit with breathing room. */
    padding: 18px var(--space-32);
    border-bottom: 1px solid var(--border);
    background: color-mix(in oklab, var(--bg) 88%, transparent);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
}
.sf-topbar__brand { display: flex; align-items: center; gap: var(--space-8); line-height: 0; }
/* Brand-package adoption: BrandMark renders an inline SVG with
   fill="currentColor", so `color: var(--fg)` makes the mark inherit
   the theme-appropriate ink/paper color. width/height keep it
   consistent with the prior 20px-ish gear footprint and align with the
   brand-package's documented 24px minimum for the full mark.
   PR-T37 (2026-05-08) — kept for any caller still rendering BrandMark;
   MainLayout now uses the horizontal lockup SVG instead. */
.sf-topbar__mark { width: 24px; height: 24px; color: var(--fg); flex: none; }
.sf-topbar__product { font-weight: var(--weight-semibold); }
/* PR-T37 (2026-05-08) — horizontal lockup in the topbar. Same SVG
   the marketing layout uses; the cross-shell consistency is the
   point. The lockup carries its own wordmark so no separate text
   span is needed.
   2026-05-31 — now the tagline lockup ("At Every Step") at 36px (was
   28px), paired with the .sf-shell grid-row bump to 72px so it fits
   with 18px breathing. Dark mode no longer inverts a light-ink SVG
   with a filter — MainLayout renders two <img>s (--light / --dark)
   and we toggle them on [data-theme], so dark mode uses the
   designer's paper-ink -reversed asset (its tagline is tuned to
   #9A9C9F for dark surfaces). */
.sf-topbar__lockup { height: 36px; width: auto; display: block; }
.sf-topbar__lockup--dark { display: none; }
:root[data-theme="dark"] .sf-topbar__lockup--light { display: none; }
:root[data-theme="dark"] .sf-topbar__lockup--dark { display: block; }
.sf-topbar__version { color: var(--fg-subtle); font-size: var(--text-12); }
.sf-topbar__chips { display: flex; gap: var(--space-4); }
.sf-chip {
    padding: 2px var(--space-8);
    background: var(--bg-sunken);
    color: var(--fg-muted);
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    font-size: var(--text-12);
}
/* PR-T43 (2026-05-08) — full visual parity with .sf-marketing__nav
   so the in-app shell's nav links read as the same product. Gap
   moved from --space-20 (=20px) to literal 28px, padding dropped
   to match marketing exactly. Links remain hidden below 700px so
   the topbar doesn't crowd on narrow viewports.
   2026-05-14 — v1-launch Batch A C-A3. The original PR-T43 pinned
   "Helvetica Neue" explicitly to match marketing; that pin
   contradicted the platform Segoe UI Variable / Inter stack on
   non-macOS hosts. With C-A3 moving marketing's UI to
   --marketing-font-ui (= --font-sans), this rule now picks up
   --font-sans directly so the topbar matches both: marketing and
   the rest of the app. */
.sf-topbar__nav {
    display: flex;
    gap: 28px;
    margin-left: var(--space-12);
    font-family: var(--font-sans);
    font-size: 13px;
}
.sf-topbar__navlink {
    color: var(--fg-muted);
    text-decoration: none;
    font-weight: 500;
}
.sf-topbar__navlink:hover { color: var(--fg); }
.sf-topbar__navlink:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: 2px;
}
@media (max-width: 700px) {
    .sf-topbar__nav { display: none; }
}

/* 2026-05-18 — marketing-site launch-readiness P2 #9. The shell's
   240px rail (.sf-rail) consumed ~60% of a 390px viewport, pushing
   /feedback, /settings/api-keys, /support/ticket content offscreen
   on mobile per customer feedback `019e38dc-044b`. Below the
   tablet breakpoint we collapse the rail entirely and reflow the
   main content to span the full row. The rail's content stays
   reachable via the topbar nav (signed-in users) and via direct
   URL bookmarks; a future enhancement may promote it to a drawer
   that the user can open from a hamburger. */
@media (max-width: 700px) {
    .sf-shell {
        grid-template-columns: 1fr;
        grid-template-areas:
            "topbar"
            "main"
            "footer";
    }
    .sf-rail { display: none; }
}
.sf-topbar__spacer { flex: 1; }
.sf-topbar__actions { display: flex; align-items: center; gap: var(--space-8); }
.sf-iconbutton {
    width: 32px; height: 32px;
    display: inline-flex; align-items: center; justify-content: center;
    background: transparent; border: 0; color: var(--fg);
    border-radius: var(--radius-6); cursor: pointer;
}
.sf-iconbutton:hover { background: var(--bg-sunken); }
.sf-iconbutton:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.sf-persona {
    display: inline-flex; align-items: center; gap: var(--space-8);
    padding: 4px var(--space-8); background: transparent; border: 0; cursor: pointer; color: var(--fg);
    border-radius: var(--radius-6);
    /* 2026-05-16 — `.sf-persona` is now an <a href="/workspace"> on
       both shells (was <button>); clicking the user's name navigates
       to /workspace, hover still opens the dropdown via
       .sf-persona-menu:hover. text-decoration: none + the font-
       inherit + line-height: 1 keep the visual identical to the
       former <button> shape. */
    text-decoration: none;
    font: inherit;
    line-height: 1;
}
.sf-persona:hover { background: var(--bg-sunken); text-decoration: none; }
.sf-persona:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.sf-persona__monogram {
    width: 24px; height: 24px;
    display: inline-flex; align-items: center; justify-content: center;
    /* Session 21 — was --accent-500 (#21b878), white-on-which is
       2.56 : 1 (axe FAIL). Same fix as .sf-btn--primary in
       Session 20: bump to --accent-700 (#117b50, ~5.3 : 1) so the
       12px white monogram clears WCAG 2.1 AA. */
    background: var(--accent-700); color: white; border-radius: 50%;
    font-size: var(--text-12); font-weight: var(--weight-semibold);
}
/* Image variant of the persona monogram. Matches box dimensions of
   the text monogram so swapping img <-> span via UserAvatar's onerror
   fallback preserves the layout. The provider photo replaces the
   accent-700 background. */
.sf-persona__monogram--img {
    background: transparent;
    object-fit: cover;
    display: inline-block;
}
.sf-persona__name { font-size: var(--text-14); }

/* PR-T41 (2026-05-08) — rail harmonized with the editorial topbar:
   transparent bg so the page bg flows through (was --bg-elevated
   panel chrome), softer border via color-mix so it reads as a
   subtle divide rather than a hard panel edge, more vertical
   padding to match the topbar's editorial breathing. */
.sf-rail {
    grid-area: rail;
    border-right: 1px solid color-mix(in oklab, var(--border) 50%, transparent);
    background: transparent;
    padding: var(--space-20) var(--space-16);
    overflow-y: auto;
}
/* PR-T41 (2026-05-08) — eyebrow tracking + smaller font so the
   group headings (PROJECT DOCUMENTATION / ADMINISTRATION) read
   like the marketing eyebrows above section h2s. */
.sf-rail__heading {
    font-size: var(--text-11, 11px);
    font-weight: var(--weight-semibold);
    text-transform: uppercase;
    letter-spacing: 0.10em;
    color: var(--fg-subtle);
    margin: var(--space-8) var(--space-8) var(--space-12);
}
/* 2026-05-02 Settings reorganization — wraps {heading + items} so a
   second group (Administration) can render below the primary group
   with appropriate visual separation. Single-group rendering (anonymous
   users, normal users) is unaffected by an empty group. */
.sf-rail__group {
    display: block;
}
.sf-rail__group + .sf-rail__group {
    margin-top: var(--space-16);
    padding-top: var(--space-16);
    border-top: 1px solid var(--border);
}
.sf-rail__items { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 2px; }
.sf-rail__item {
    display: flex;
    align-items: center;
    gap: var(--space-8);
    padding: var(--space-8) var(--space-12);
    color: var(--fg); text-decoration: none;
    border-radius: var(--radius-6);
    font-weight: var(--weight-medium);
}
.sf-rail__item:hover { background: var(--bg-sunken); }
.sf-rail__item.active {
    background: color-mix(in srgb, var(--accent) 12%, transparent);
    color: var(--accent-fg);
}
.sf-rail__item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* 2026-05-18 — Rail-link glyphs (custom monochrome icons next to
   each page link). The SVG declares stroke="currentColor", so the
   icon picks up whichever color the rail item's hover/active rules
   set. Idle state is slightly muted so the label stays the primary
   visual element; hover restores --fg; active inherits --accent-fg
   from the .sf-rail__item.active rule above. flex: 0 0 auto keeps
   the icon from shrinking when the label wraps. */
.sf-rail__icon {
    flex: 0 0 auto;
    color: var(--fg-muted);
    transition: color 120ms ease;
}
.sf-rail__item:hover .sf-rail__icon { color: var(--fg); }
.sf-rail__item.active .sf-rail__icon { color: var(--accent-fg); }

.sf-main {
    grid-area: main;
    /* Initiative H follow-up — `overflow-y: auto` was here but the
       grid uses min-height (not height), so main grows with its
       content and the document scrolls. The rule was dead code AND
       it broke <a href="#x"> scroll-into-view: Chromium walks the
       ancestor chain looking for a scroll container, hits <main>
       with overflow-y:auto, scrolls IT (no-op since scrollHeight
       === clientHeight), and stops without scrolling the document.
       Removing it lets native fragment navigation work the same as
       it does in MarketingLayout. */
    padding: var(--space-32);
    /* TASK-236 follow-on (2026-05-18) — `min-width: 0` is the
       canonical fix for a CSS-grid 1fr column being forced wider
       than its allocated track by an oversize child. Without this,
       a wide table or long unbreakable string inside .sf-main grew
       the column past the viewport edge, clipping the right side
       of page chrome (most visibly on /admin/communication after
       its switch to MainLayout). This is universally desired: 1fr
       should mean "take your assigned track" not "expand to fit
       intrinsic min-content". */
    min-width: 0;
}
.sf-main:focus-visible { outline: none; }

/* ---- page frame ---- */
.sf-page__header { margin-bottom: var(--space-24); }
.sf-page__header h1 { font-size: var(--text-24); margin: 0 0 var(--space-4); }
.sf-page__subtitle { color: var(--fg-muted); margin: 0; }

/* TASK-224 (2026-05-17) — admin documentation hub card grid at
   /admin/docs. Mirrors the visual treatment of other admin landing
   pages but with a navigational card-grid pattern for picking which
   documentation surface to open. Each card is a full <a> for
   keyboard accessibility (the entire surface activates on click /
   Enter). */
.sf-admin-docs-hub__grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: var(--space-16);
}
.sf-admin-docs-hub__card {
    display: block;
    padding: var(--space-16);
    border: 1px solid var(--border);
    border-radius: var(--radius-8);
    background: var(--bg-surface);
    text-decoration: none;
    color: inherit;
    transition: border-color 120ms, background 120ms;
}
.sf-admin-docs-hub__card:hover { border-color: var(--accent-strong); background: var(--bg-subtle); }
.sf-admin-docs-hub__card:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: 2px; }
.sf-admin-docs-hub__card-title { font-size: var(--text-16); font-weight: var(--weight-semibold); margin: 0 0 var(--space-8); }
.sf-admin-docs-hub__card-body { color: var(--fg-muted); margin: 0 0 var(--space-12); font-size: var(--text-13); }
.sf-admin-docs-hub__card-cta { color: var(--accent-strong); font-size: var(--text-13); font-weight: var(--weight-semibold); }

/* ---- breadcrumb (per-section, sits above the H1) ---- */
.sf-breadcrumb { margin: 0 0 var(--space-4); }
.sf-breadcrumb__list {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    margin: 0;
    padding: 0;
    list-style: none;
    font-size: var(--text-12);
    color: var(--fg-muted);
}
.sf-breadcrumb__item { display: inline-flex; align-items: center; }
.sf-breadcrumb__link {
    color: var(--fg-muted);
    text-decoration: none;
    border-radius: var(--radius-4);
}
.sf-breadcrumb__link:hover,
.sf-breadcrumb__link:focus-visible { color: var(--fg); text-decoration: underline; }
.sf-breadcrumb__sep { color: var(--fg-subtle); }
.sf-breadcrumb__current { color: var(--fg); font-weight: 500; }
.sf-page__placeholder {
    padding: var(--space-32);
    border: 1px dashed var(--border-strong);
    border-radius: var(--radius-8);
    color: var(--fg-muted);
    text-align: center;
}
.sf-page__title-row { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-16); }
.sf-page__actions { display: flex; gap: var(--space-8); }

/* ---- buttons ---- */
.sf-btn {
    /* Web Controls v1 (2026-05-30) — canonical .btn metrics: 36px tall, 0 14px pad, 4px radius, 500 / 13.5, 8px gap. */
    height: 36px;
    padding: 0 14px;
    border: 1px solid transparent;
    border-radius: var(--radius-4);
    font: inherit; font-weight: var(--weight-medium); font-size: 13.5px; line-height: 1; letter-spacing: -0.005em;
    transition: background var(--motion-duration-fast) ease, color var(--motion-duration-fast) ease, border-color var(--motion-duration-fast) ease;
    cursor: pointer; display: inline-flex; align-items: center; justify-content: center; gap: 8px;
    /* 2026-05-18 — marketing-site launch-readiness P2 #11. Anchor
       elements with .sf-btn (e.g., pricing-card CTAs) were inheriting
       the default underline + accent text color of <a>, making the
       primary button "appear like a default browser/light button in
       some states" per customer feedback `019e38dc-044b`. Reset
       text-decoration on the .sf-btn base so anchor-as-button reads
       identically to <button class="sf-btn"> across states. */
    text-decoration: none;
}
.sf-btn:hover { text-decoration: none; }
/* Session 20 — bump from --accent-500 (#21b878, white-text contrast
   2.56 : 1, FAILS WCAG 2.1 AA) to --accent-700 (#117b50, ~5.3 : 1,
   passes AA for normal-size body text). The button is the most
   visually prominent CTA on the Landing page so it MUST clear the
   normal-text threshold (the large-text 3 : 1 carve-out doesn't
   apply to a 13px button label). Hover bumps to --accent-800 so the
   contrast STAYS passing through the hover state. The lighter
   --accent-500/600 values still drive non-text accent surfaces
   (focus ring, accent borders) where the WCAG color-contrast rule
   doesn't apply. Caught by axe-core on the live Landing page;
   diagnostic in Session-20 commit history. */
/* Web Controls v1 (2026-05-30) — primary actions are INK, not green.
   Per the canonical controls' .btn--primary { background: var(--ink) }.
   var(--fg)/var(--bg) auto-invert by theme: near-black ink button with
   paper text in light; paper button with ink text in dark (the controls'
   ink/paper duality). The bright/dark greens now drive accents only
   (focus rings, selection, success) — see the accent-reanchor follow-up. */
.sf-btn--primary { background: var(--fg); color: var(--bg); border-color: var(--fg); }
.sf-btn--primary:hover {
    /* 2026-06-01 — owner: the prior 85% mix was too subtle on the dark
       buttons. Lighten more (76%) + a small lift shadow so the hover reads
       clearly. Stays high-contrast (--bg text on a 76%-fg ground) in both
       themes; the shadow is a light-theme lift (harmless on dark). */
    background: color-mix(in oklab, var(--fg) 76%, var(--bg));
    border-color: color-mix(in oklab, var(--fg) 76%, var(--bg));
    box-shadow: 0 2px 6px rgba(15, 17, 21, 0.18);
}
/* Interview review S3 (2026-05-02) — primary button disabled state.
   Was inheriting only the fieldset-level opacity:0.55 which left the
   button looking ready-to-fire. Now flips to bg-sunken + fg-subtle so
   the user can see at a glance that there's nothing to send. */
.sf-btn--primary:disabled,
.sf-btn--primary[disabled] {
    background: var(--bg-sunken);
    color: var(--fg-subtle);
    cursor: not-allowed;
}
.sf-btn--primary:disabled:hover,
.sf-btn--primary[disabled]:hover { background: var(--bg-sunken); }
.sf-btn--ghost { background: transparent; color: var(--fg); border-color: var(--border-strong); }
.sf-btn--ghost:hover { background: var(--bg-sunken); }
.sf-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---- workspace ---- */
.sf-workspace { display: flex; flex-direction: column; gap: var(--space-24); }

/* 2026-05-14 — v1-launch cross-batch theme #2 (Batch E C-E7 + Batch
   F C-F4 / Should-improve 24). Changed from fixed `repeat(6, 1fr)`
   to `repeat(auto-fit, minmax(220px, 1fr))` so that:
     - Workspace's 6-tile row still fills at desktop (220 * 6 = 1320px
       fits any modern viewport; below that the tiles auto-flow to
       wrap rather than cram).
     - CostUsage / Analytics / ProfitReport / Generations rows with
       3 or 4 tiles fill the row width instead of leaving 2-3 phantom
       cells (the original 6-col grid silently allocated flex share
       to invisible empty cells, narrowing the actual tiles enough
       that values like "$22.41 7d" wrapped awkwardly).
     - Narrow viewports collapse cleanly without a media query.
   Workspace mobile-responsive collapse (Batch D C-D2) is a separate
   PR; this grid change is necessary AND sufficient for the Settings
   + Admin stat-tile rows. */
.sf-stat-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: var(--space-16); }
/* 2026-05-14 — v1-launch Batch D C-D2. Mobile collapse for the
   stat-tile row. `auto-fit minmax(220px, 1fr)` already wraps to 2
   columns at ≤640px and 1 column at ≤320px, but those breakpoints
   leave a noticeable visual gap on common phone widths
   (375-414px) where 220px tiles flow as 1-up but with the legacy
   16px gap reading awkwardly against the cards' rounded radius.
   Explicit single-column rule at ≤600px + tighter padding so the
   six-tile row reads as a vertical stack instead of an awkward
   grid. */
@media (max-width: 600px) {
    .sf-stat-tiles { grid-template-columns: 1fr; gap: var(--space-12); }
    .sf-stat-tile { padding: var(--space-12); }
    .sf-stat-tile__value { font-size: var(--text-24); }
}
.sf-stat-tile {
    background: var(--bg-elevated); border: 1px solid var(--border);
    border-radius: var(--radius-8); padding: var(--space-16);
    display: flex; flex-direction: column; gap: var(--space-4);
}
.sf-stat-tile__caption { font-size: var(--text-12); color: var(--fg-subtle); font-weight: var(--weight-semibold); letter-spacing: 0.04em; }
.sf-stat-tile__value { font-size: var(--text-32); font-weight: var(--weight-semibold); color: var(--fg); }
.sf-stat-tile__value--small { font-size: 13px; font-weight: var(--weight-medium); line-height: 1.2; }
.sf-stat-tile__sub { font-size: var(--text-12); color: var(--fg-muted); }
/* 2026-05-15 — Workspace stat tiles can be clickable (ISSUES → filtered
   table, USAGE → /settings/plan). The link variant preserves the tile
   shape + colors and adds a subtle hover + focus ring so keyboard users
   see where they are. */
.sf-stat-tile--link { text-decoration: none; color: inherit; cursor: pointer; transition: background-color 120ms ease-out; }
.sf-stat-tile--link:hover { background: var(--bg-sunken); }
.sf-stat-tile--link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* "Over budget" cue for the USAGE tile when the user has hit their
   monthly quota. Reuses the existing danger token so the signal
   matches every other alarm surface in the app. */
.sf-stat-tile__value--alert { color: var(--danger-fg); }
/* ---- filter bar ---- */
.sf-filterbar { display: flex; align-items: center; gap: var(--space-12); }
.sf-search { position: relative; flex: 0 1 360px; }
.sf-search input {
    width: 100%; height: 36px; padding: 0 36px 0 var(--space-12);
    background: var(--bg-elevated); color: var(--fg);
    border: 1px solid var(--border); border-radius: var(--radius-6);
    font: inherit;
}
.sf-search input::placeholder { color: var(--fg-subtle); }
.sf-search__kbd {
    position: absolute; right: var(--space-8); top: 50%; transform: translateY(-50%);
    background: var(--bg-sunken); padding: 1px 6px; border-radius: var(--radius-4);
    font-size: var(--text-12); color: var(--fg-muted);
}
.sf-segmented {
    display: inline-flex; background: var(--bg-elevated); border: 1px solid var(--border);
    border-radius: var(--radius-6); padding: 2px; gap: 2px;
}
.sf-segmented__btn {
    padding: 4px var(--space-12); height: 28px;
    background: transparent; border: 0; border-radius: var(--radius-4);
    color: var(--fg-muted); cursor: pointer; font: inherit; font-size: 13px;
}
.sf-segmented__btn[aria-selected="true"],
.sf-segmented__btn[aria-checked="true"] { background: var(--bg-sunken); color: var(--fg); font-weight: var(--weight-medium); }
.sf-segmented__btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* 2026-06-01 — count badge on a segmented option (the pending-candidate count on
   the Build Lessons view filter). Tokens only. */
.sf-segmented__badge {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 18px; height: 18px; padding: 0 5px; margin-left: var(--space-4);
    background: var(--warning-bg); color: var(--warning-fg);
    border-radius: 999px; font-size: 11px; font-weight: var(--weight-semibold); line-height: 1;
}
.sf-filterbar__spacer { flex: 1; }

/* ---- generation list ---- */
.sf-genlist {
    background: var(--bg-elevated); border: 1px solid var(--border);
    border-radius: var(--radius-8);
    /* 2026-05-14 — v1-launch Batch D C-D2. Was `overflow: hidden`
       which clipped action buttons (Download / Retry / Delete) on
       narrow viewports with no scroll fallback. The 8-column row
       has a real minimum width (~720px) — letting the container
       overflow-scroll lets mobile users access every cell. The
       header + rows below set min-width on themselves so the
       horizontal scrolling kicks in at the breakpoint, not earlier. */
    overflow-x: auto;
}
.sf-genlist__head, .sf-genrow {
    display: grid;
    grid-template-columns: 2fr 1.2fr 1fr 2.4fr 0.8fr 1fr 1fr 0.6fr;
    align-items: center;
    padding: 0 var(--space-16);
    gap: var(--space-12);
    /* 2026-05-14 — v1-launch Batch D C-D2. Pin minimum row width
       so 8-column layout doesn't crush each cell to unreadable.
       Below 720px the parent (.sf-genlist) overflow-x scrolls. */
    min-width: 720px;
}
/* 2026-05-14 — v1-launch Batch D H-D10. Zebra striping on
   generation rows. Subtle (bg-sunken-elevated mix) — alternates
   readability without losing the active-state highlight. */
.sf-genrow:nth-child(even) { background: color-mix(in oklab, var(--bg-elevated) 65%, var(--bg-sunken) 35%); }

/* 2026-05-14 — v1-launch Batch D H-D4. GenerationDetail rail
   Share card — permalink rendered as inline selectable text +
   Copy button. The card class spaces the URL + button row;
   `__url` is a `<code>` element pinned to one line with overflow
   so long URLs scroll horizontally rather than wrap into a
   ragged column. */
.sf-permalink-card__row {
    display: flex;
    align-items: center;
    gap: var(--space-8);
    margin-top: var(--space-8);
}
.sf-permalink-card__url {
    flex: 1 1 auto;
    min-width: 0;
    padding: 4px var(--space-8);
    background: var(--bg-sunken);
    border: 1px solid var(--border);
    border-radius: var(--radius-4);
    font-family: var(--font-mono);
    font-size: var(--text-12);
    color: var(--fg-muted);
    overflow-x: auto;
    white-space: nowrap;
}
/* 2026-05-15 — a11y fix. The element is now keyboard-focusable
   (tabindex="0") so axe-core's scrollable-region-focusable rule
   passes. Visible focus ring matches the rest of the platform's
   focus-ring treatment. */
.sf-permalink-card__url:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}
.sf-permalink-card__row .sf-btn { flex: 0 0 auto; }

/* 2026-05-14 — v1-launch Batch D H-D8. GenerationDetail rail
   cost-share treatment. 4px stacked bar sits between the totals
   dl and the per-agent breakdown table; each segment is a
   per-agent color (--agent-otto / --agent-alan / etc.) flex-sized
   by share-of-total. A matching colored dot prefixes each row in
   the breakdown table so the user can map bar to row at a glance.
   No new color tokens — reuses the existing AgentRoleColors family
   from tokens.css. */
.sf-detail__rail-cost-bar {
    display: flex;
    height: 4px;
    margin: var(--space-8) 0 var(--space-12);
    border-radius: var(--radius-4, 4px);
    overflow: hidden;
    background: var(--bg-sunken);
}
.sf-detail__rail-cost-bar-seg { display: block; height: 100%; }
.sf-detail__rail-cost-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 999px;
    margin-right: var(--space-8);
    vertical-align: middle;
}
.sf-genlist__head {
    padding-top: var(--space-12); padding-bottom: var(--space-12);
    border-bottom: 1px solid var(--border);
    font-size: var(--text-12); color: var(--fg-subtle); font-weight: var(--weight-semibold); letter-spacing: 0.04em;
}
/* 2026-05-09 — sortable column-header buttons. Inherit the
   uppercase/tracking column-header look; click-target gets a subtle
   hover so users learn it's interactive. ▲/▼ glyph is rendered in
   .sf-genlist__sortglyph so the spacing is stable. */
.sf-genlist__sortbtn {
    background: none;
    border: 0;
    color: inherit;
    font: inherit;
    text-transform: inherit;
    letter-spacing: inherit;
    padding: 0;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: var(--space-4, 4px);
}
.sf-genlist__sortbtn:hover { color: var(--fg); }
.sf-genlist__sortbtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }
.sf-genlist__sortglyph {
    font-size: 9px;
    color: var(--accent-fg);
}

/* 2026-05-09 — pagination footer for the generations + packages tables.
   Three slots: rows-per-page selector + range label on the left, pager
   buttons + "Page X of Y" on the right. Subtle visual weight — the
   table itself is the focus. */
.sf-tablefooter {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--space-12, 12px);
    padding: var(--space-12, 12px) var(--space-16, 16px);
    border-top: 1px solid var(--border);
    font-size: var(--text-12, 12px);
    color: var(--fg-subtle);
}
.sf-tablefooter__left {
    display: inline-flex;
    align-items: center;
    gap: var(--space-12, 12px);
}
.sf-tablefooter__label {
    display: inline-flex;
    align-items: center;
    gap: var(--space-8, 8px);
}
.sf-tablefooter__range { color: var(--fg-muted); font-variant-numeric: tabular-nums; }
.sf-tablefooter__pager {
    display: inline-flex;
    align-items: center;
    gap: var(--space-8, 8px);
}
.sf-tablefooter__pagelabel {
    min-width: 7em;
    text-align: center;
    font-variant-numeric: tabular-nums;
    color: var(--fg-muted);
}
.sf-genrow {
    padding-top: var(--space-12); padding-bottom: var(--space-12);
    border-bottom: 1px solid var(--border);
    font-size: 13px;
    transition: background var(--motion-duration-fast) var(--motion-easing-out);
}
.sf-genrow:last-child { border-bottom: 0; }
.sf-genrow:hover { background: var(--bg-sunken); }

/* Per-row source-channel badge — shows whether the generation was
   kicked off from the Web app, REST API, or an MCP client. Subtle by
   design (small + low contrast) — it's metadata, not a primary signal. */
.sf-source-pill {
    display: inline-block;
    margin-left: var(--space-8);
    padding: 1px 6px;
    font-size: 10px;
    font-weight: var(--weight-semibold);
    letter-spacing: 0.06em;
    border-radius: 3px;
    border: 1px solid transparent;
    vertical-align: middle;
    line-height: 1.4;
}
/* Initiative F D2 (2026-05-03) — was hex literals
   (#22c55e / #3b82f6 / #a855f7). Now reads from the
   --source-{web,api,mcp} tokens that ship in tokens.css.
   The 12% bg / 25% border / solid fg color-mix structure is
   preserved so the visual treatment is unchanged; only the
   underlying color values are now tokenized. */
.sf-source-pill--web {
    background: color-mix(in srgb, var(--source-web) 12%, transparent);
    color: var(--source-web);
    border-color: color-mix(in srgb, var(--source-web) 25%, transparent);
}
.sf-source-pill--api {
    background: color-mix(in srgb, var(--source-api) 12%, transparent);
    color: var(--source-api);
    border-color: color-mix(in srgb, var(--source-api) 25%, transparent);
}
.sf-source-pill--mcp {
    background: color-mix(in srgb, var(--source-mcp) 12%, transparent);
    color: var(--source-mcp);
    border-color: color-mix(in srgb, var(--source-mcp) 25%, transparent);
}
.sf-genrow--pulse {
    animation: sf-row-pulse var(--motion-duration-slow) var(--motion-easing-emphatic);
    background: color-mix(in srgb, var(--accent) 8%, transparent);
}
@keyframes sf-row-pulse {
    0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 50%, transparent); }
    60%  { box-shadow: 0 0 0 12px color-mix(in srgb, var(--accent) 0%, transparent); }
    100% { box-shadow: 0 0 0 0 transparent; }
}
.sf-cell--right { text-align: right; }
/* 2026-06-01 — tables Wave 5: shared center-align for bounded grant-checkbox
   matrix columns (Agent Activation, Generation Types), replacing per-table
   inline text-align so the tier-grant matrices align consistently. */
.sf-cell--center { text-align: center; }
.sf-cell--cost { font-variant-numeric: tabular-nums; font-weight: var(--weight-medium); }
.sf-cell--actions { display: flex; gap: 4px; justify-content: flex-end; }

/* Workspace review S5 (2026-05-02) — long project names previously
   broke the genrow grid because the project cell had no min-width
   constraint and the title text didn't truncate. Now the cell can
   shrink to zero (CSS-grid default is min-content / auto for the
   first column otherwise) and the title clamps to one line with
   an ellipsis; the full name is on the title attribute for hover
   reveal. */
.sf-cell--project { min-width: 0; }
.sf-cell--project .sf-cell__title {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Workspace review C4 (2026-05-02) — single-line consolidated
   resume strip. Replaces the prior two full sf-card blocks for
   in-flight + past interviews, reclaiming ~200px of vertical
   space above the generation list. In-flight chips render
   inline; past interviews live behind a disclosure that expands
   in-place. */
.sf-resume-strip {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-12);
    margin: var(--space-16) 0 0;
    padding: var(--space-12) var(--space-16);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-8);
    font-size: var(--text-13);
}
.sf-resume-strip__inflight {
    display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-8);
    flex: 1 1 auto;
    min-width: 0;
}
.sf-resume-strip__label {
    color: var(--fg-subtle);
    font-size: var(--text-12);
    letter-spacing: var(--track-wide);
    font-weight: var(--weight-semibold);
    text-transform: uppercase;
}
.sf-resume-strip__overflow {
    color: var(--fg-muted);
    font-size: var(--text-12);
}
.sf-resume-chip {
    display: inline-flex;
    flex-direction: column;
    gap: 2px;
    padding: var(--space-4) var(--space-12);
    background: var(--bg-sunken);
    border: 1px solid transparent;
    border-radius: var(--radius-6);
    color: var(--fg);
    text-decoration: none;
    max-width: 280px;
    transition: border-color var(--motion-duration-fast) var(--motion-easing-out),
                background var(--motion-duration-fast) var(--motion-easing-out);
}
.sf-resume-chip:hover { border-color: var(--accent); background: var(--bg-elevated); text-decoration: none; }
.sf-resume-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.sf-resume-chip__title {
    font-weight: var(--weight-medium);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sf-resume-chip__sub { color: var(--fg-muted); font-size: var(--text-12); }
.sf-resume-strip__past {
    flex: 0 0 auto;
    color: var(--fg-muted);
}
.sf-resume-strip__past summary {
    cursor: pointer;
    padding: var(--space-4) var(--space-8);
    border-radius: var(--radius-6);
    list-style: none;
    user-select: none;
}
.sf-resume-strip__past summary::-webkit-details-marker { display: none; }
.sf-resume-strip__past summary::before {
    content: "▸ ";
    color: var(--fg-subtle);
    transition: transform var(--motion-duration-fast) var(--motion-easing-out);
    display: inline-block;
}
.sf-resume-strip__past[open] summary::before { content: "▾ "; }
.sf-resume-strip__past summary:hover { background: var(--bg-sunken); color: var(--fg); }
.sf-resume-strip__past-list {
    list-style: none; padding: 0;
    margin: var(--space-12) 0 0;
    display: flex; flex-direction: column; gap: var(--space-8);
    width: 100%;
}
.sf-resume-strip__past-list li { display: flex; flex-direction: column; gap: 2px; padding: var(--space-4) 0; }

/* Workspace review C5 (2026-05-02) — right-side detail sheet
   for the Workspace generation list. The spec
   (docs/design/docs/05-surfaces.md) and the prototype both
   model triage from a sheet rather than a full-page navigation;
   production had drifted to a navigation away from the list,
   pulling the user out of context. The sheet is implemented as
   a modal-via-CSS overlay + fixed-position right pane. /generations/{id}
   stays as the deep-link target for notifications + shared links. */
.sf-detail-sheet-overlay {
    position: fixed;
    inset: 0;
    background: rgba(15, 17, 21, 0.32);
    z-index: 90;
    animation: sf-detail-sheet-fade var(--motion-duration-fast) var(--motion-easing-out) both;
}
.sf-detail-sheet {
    position: fixed;
    /* 2026-05-16 — tracks the .sf-shell grid-row bump from 56px → 68px
       (paired with the larger lockup + matched marketing-header
       padding). Detail sheet still pins immediately below the topbar. */
    top: 68px; /* matches .sf-topbar height */
    right: 0;
    bottom: 0;
    width: min(560px, 100vw);
    background: var(--bg-elevated);
    border-left: 1px solid var(--border);
    box-shadow: var(--shadow-pop);
    z-index: 91;
    display: flex;
    flex-direction: column;
    animation: sf-detail-sheet-slide var(--motion-duration-base) var(--motion-easing-emphatic) both;
}
@keyframes sf-detail-sheet-fade {
    from { opacity: 0; }
    to   { opacity: 1; }
}
@keyframes sf-detail-sheet-slide {
    from { transform: translateX(24px); opacity: 0; }
    to   { transform: translateX(0);    opacity: 1; }
}
.sf-detail-sheet__header {
    display: flex; align-items: flex-start; justify-content: space-between;
    gap: var(--space-12);
    padding: var(--space-20) var(--space-24);
    border-bottom: 1px solid var(--border);
}
.sf-detail-sheet__title-block { display: flex; flex-direction: column; gap: var(--space-8); min-width: 0; flex: 1; }
.sf-detail-sheet__title {
    margin: 0;
    font-size: var(--text-20);
    font-weight: var(--weight-semibold);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sf-detail-sheet__sub { display: flex; align-items: center; gap: var(--space-8); flex-wrap: wrap; }
.sf-detail-sheet__body {
    flex: 1;
    overflow-y: auto;
    padding: var(--space-20) var(--space-24);
    display: flex; flex-direction: column; gap: var(--space-16);
}
.sf-detail-sheet__meta {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-16);
    margin: 0;
}
.sf-detail-sheet__meta > div { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.sf-detail-sheet__meta dt {
    font-size: var(--text-12);
    color: var(--fg-subtle);
    letter-spacing: var(--track-wide);
    font-weight: var(--weight-semibold);
    text-transform: uppercase;
}
.sf-detail-sheet__meta dd { margin: 0; font-size: var(--text-14); color: var(--fg); }
.sf-detail-sheet__cost { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-weight: var(--weight-semibold); }
.sf-detail-sheet__failure {
    background: var(--danger-bg);
    border: 1px solid var(--danger-border);
    color: var(--danger-fg);
    border-radius: var(--radius-8);
    padding: var(--space-12) var(--space-16);
}
.sf-detail-sheet__failure strong { display: block; margin-bottom: var(--space-4); }
.sf-detail-sheet__failure p { margin: 0; font-size: var(--text-13); }
/* Bundle 5 PR 2 — review-budget-exhausted blockers list */
.sf-detail-sheet__failure-blockers-lede { margin-top: var(--space-12) !important; font-weight: var(--weight-semibold); }
.sf-detail-sheet__failure-blockers {
    margin: var(--space-4) 0 0;
    padding-left: var(--space-20);
    font-size: var(--text-13);
    line-height: 1.5;
}
.sf-detail-sheet__failure-blockers li { margin-top: var(--space-4); }
.sf-detail-sheet__failure-blockers code { font-family: var(--font-mono); font-size: var(--text-12); padding: 1px 4px; background: rgba(0,0,0,0.06); border-radius: var(--radius-4); }
.sf-detail-sheet__actions {
    display: flex; gap: var(--space-8); justify-content: flex-end;
    padding: var(--space-16) var(--space-24);
    border-top: 1px solid var(--border);
}

@media (max-width: 768px) {
    .sf-detail-sheet { width: 100vw; top: 0; }
    .sf-detail-sheet-overlay { display: none; }
}
@media (prefers-reduced-motion: reduce) {
    .sf-detail-sheet-overlay,
    .sf-detail-sheet { animation: none; }
}
/* ---- topbar bell + dropdown ---- (Phase 9.5 follow-up)
   Wraps the icon-button + an absolutely-positioned dropdown anchored
   to the bottom-right of the bell. The badge is a small pill in the
   top-right corner of the icon. Both light + dark via existing tokens. */
.sf-bell { position: relative; display: inline-block; }
/* PR-T7 follow-up (2026-05-06) — always-present transparent hover
   bridge directly below the bell button. The cursor crossing from
   the button into the dropdown stays over this strip (still inside
   `.sf-bell` for :hover purposes), so the dropdown doesn't blink
   off mid-reach. The previous bridge was a `.sf-bell-dropdown::before`
   pseudo, which only existed while the dropdown itself was
   `display: block` — there was a one-frame flicker as the cursor
   left the button and the dropdown re-evaluated its visibility.
   Putting the bridge on the wrapper's own pseudo dodges that race
   entirely (the wrapper is always in the layout). */
.sf-bell::after {
    content: "";
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    height: var(--space-8);
    pointer-events: auto;
}
.sf-bell__btn { position: relative; }
.sf-bell__badge {
    position: absolute;
    top: -2px; right: -4px;
    min-width: 18px; height: 18px;
    padding: 0 5px;
    border-radius: 9px;
    background: var(--state-failed, #c33);
    color: #fff;
    font-size: 11px;
    font-weight: var(--weight-semibold);
    display: inline-flex; align-items: center; justify-content: center;
    line-height: 1;
}
.sf-bell-dropdown {
    /* PR-T7 (2026-05-06) — hidden by default; revealed on hover /
       focus / click-pinned via the rules below. The dropdown is
       always rendered in the DOM (the Razor component dropped its
       @if (_open) gate) so CSS can show it instantly without a
       SignalR round-trip. */
    display: none;
    position: absolute;
    top: calc(100% + var(--space-8));
    right: 0;
    min-width: 320px;
    max-width: 380px;
    max-height: 480px;
    overflow-y: auto;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-8);
    box-shadow: 0 8px 32px -8px rgba(0,0,0,0.25);
    z-index: 1000;
}
/* PR-T7 follow-up (2026-05-06) — bridge moved to `.sf-bell::after`
   (above) so it's always in the layout. The previous dropdown::before
   pseudo only rendered while the dropdown was `display: block`,
   leaving a one-frame flicker on cursor transit. */
/* Show on hover, on keyboard focus inside the wrapper, OR when
   click-pinned (sf-bell--open from the C# toggle). */
.sf-bell:hover .sf-bell-dropdown,
.sf-bell:focus-within .sf-bell-dropdown,
.sf-bell--open .sf-bell-dropdown {
    display: block;
}
.sf-bell-dropdown__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--space-12) var(--space-16);
    border-bottom: 1px solid var(--border);
}
.sf-bell-dropdown__mark-read {
    background: transparent;
    border: 0;
    color: var(--accent-fg);
    font-size: var(--text-13, 13px);
    cursor: pointer;
    padding: 0;
}
.sf-bell-dropdown__mark-read:hover { text-decoration: underline; }
.sf-bell-dropdown__empty {
    padding: var(--space-24) var(--space-16);
    color: var(--fg-muted);
    text-align: center;
    font-size: var(--text-14, 14px);
}
.sf-bell-dropdown__list {
    list-style: none;
    margin: 0; padding: 0;
}
.sf-notification-item {
    display: flex;
    gap: var(--space-12);
    padding: var(--space-12) var(--space-16);
    border-bottom: 1px solid var(--border);
    cursor: pointer;
    transition: background 80ms ease;
}
.sf-notification-item:last-child { border-bottom: 0; }
.sf-notification-item:hover { background: var(--bg-sunken); }
.sf-notification-item--unread {
    border-left: 3px solid var(--accent);
    background: color-mix(in srgb, var(--accent) 4%, transparent);
}
.sf-notification-item__icon {
    flex: none;
    width: 28px; height: 28px;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: var(--radius-pill);
    background: var(--bg-sunken);
    color: var(--fg);
    font-size: var(--text-14, 14px);
}
.sf-notification-item__body { flex: 1; min-width: 0; }
.sf-notification-item__headline {
    color: var(--fg);
    font-size: var(--text-14, 14px);
    line-height: 1.35;
    word-wrap: break-word;
}
.sf-notification-item__time {
    color: var(--fg-muted);
    font-size: var(--text-12);
    margin-top: 2px;
}

.sf-cell__title { font-weight: var(--weight-semibold); display: inline-flex; align-items: center; gap: var(--space-4); }

/* Phase 9.5 follow-up — inline name edit on Workspace generation rows.
   sf-iconbutton--xs is a smaller variant for the pencil; sf-input--inline
   trims the standard input padding for the row context; sf-name-edit
   horizontal flex keeps the 3 form elements on one line. */
.sf-iconbutton--xs { width: 22px; height: 22px; font-size: var(--text-12, 12px); }
.sf-iconbutton--sm { width: 28px; height: 28px; font-size: var(--text-14, 14px); }
.sf-input--inline { height: 30px; padding: 0 var(--space-8); font-size: var(--text-14, 14px); }
.sf-btn--sm { height: 28px; padding: 0 10px; font-size: 12.5px; gap: 6px; }
.sf-name-edit { display: flex; gap: var(--space-4); align-items: center; flex-wrap: wrap; }
.sf-name-edit .sf-input--inline { flex: 1; min-width: 140px; }
.sf-cell__sub { color: var(--fg-muted); font-size: var(--text-12); }
.sf-cell__sub--inline { display: block; margin-top: 2px; }

/* ---- progress bar ---- */
.sf-progress {
    position: relative; height: 4px; background: var(--bg-sunken);
    border-radius: var(--radius-pill); overflow: hidden;
}
.sf-progress__bar {
    position: absolute; top: 0; left: 0; height: 100%;
    background: var(--accent);
    transition: width var(--motion-duration-base) var(--motion-easing-out);
}

/* ---- state pill ---- */
.sf-pill {
    /* Web Controls v1 (2026-05-30) — squared chip / UPPERCASE / state-tinted
       border, per-state colors via the sf-pill--* variants (--state-fg /
       --state-bg).
       2026-06-01 — owner reversed the mono "choice A" font: the rough
       --font-mono read as out of place next to the smooth --font-sans WEB
       source chip. The pill now matches the WEB chip's text treatment
       (sans / 10px / semibold) while keeping its squared, state-coloured
       chip shape. */
    display: inline-flex; align-items: center; gap: 6px;
    height: 22px;
    padding: 0 var(--space-8);
    border: 1px solid color-mix(in oklab, var(--state-fg, var(--border-strong)) 30%, transparent);
    border-radius: var(--radius-3);
    font-family: var(--font-sans);
    font-size: 10px;
    font-weight: var(--weight-semibold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    white-space: nowrap;
    color: var(--state-fg, var(--fg-muted));
    background: var(--state-bg, var(--bg-sunken));
}
/* 2026-06-01 — owner asked to drop the status bullet inside chips. Hidden
   sitewide rather than stripped from every call site; the per-state colour
   + label already carry the status, so the dot was redundant decoration. */
.sf-pill__dot { display: none; }
.sf-pill--queued     { --state-fg: var(--state-queued-fg);     --state-bg: var(--state-queued-bg); }
.sf-pill--drafting   { --state-fg: var(--state-drafting-fg);   --state-bg: var(--state-drafting-bg); }
.sf-pill--reviewing  { --state-fg: var(--state-reviewing-fg);  --state-bg: var(--state-reviewing-bg); }
.sf-pill--fresheyes  { --state-fg: var(--state-fresheyes-fg);  --state-bg: var(--state-fresheyes-bg); }
.sf-pill--assembling { --state-fg: var(--state-assembling-fg); --state-bg: var(--state-assembling-bg); }
.sf-pill--refining   { --state-fg: var(--state-refining-fg);   --state-bg: var(--state-refining-bg); }
.sf-pill--delivering { --state-fg: var(--state-delivering-fg); --state-bg: var(--state-delivering-bg); }
.sf-pill--complete   { --state-fg: var(--state-complete-fg);   --state-bg: var(--state-complete-bg); }
.sf-pill--failed     { --state-fg: var(--state-failed-fg);     --state-bg: var(--state-failed-bg); }
.sf-pill--paused     { --state-fg: var(--state-paused-fg);     --state-bg: var(--state-paused-bg); }

/* Interview review token gap (2026-05-02) — semantic pill modifiers
   referenced widely (LogsAlertsTable, ResearcherRunCard, Interview
   profile cards, MyProviderKeysPanel, LogsErrorsTable) but with no
   CSS rules. Without these the pills rendered with the bare
   `.sf-pill` baseline (--bg-sunken bg + --fg color) — the Researcher
   Beta tag and the per-state alert pills lost all semantic color.
   Mapped onto existing --state-* and the new --warning/--danger
   tokens so they resolve correctly per theme. */
/* Marketing review (2026-05-02) — re-pointed sf-pill--info from
   --state-drafting-* (semantic mismatch — "drafting" is a generation
   state, not a UI mood) onto the new --info-bg/--info-fg pair that
   ships in the warning/danger/info triplet. */
.sf-pill--info       { --state-fg: var(--info-fg);              --state-bg: var(--info-bg); }
/* System-pages review (2026-05-02) — re-pointed sf-pill--success from
   --state-complete-* (semantic mismatch — "complete" is a generation
   state, not a UI mood) onto the new --success-bg/--success-fg pair
   that ships in the warning/danger/info/success quartet. */
.sf-pill--success    { --state-fg: var(--success-fg);           --state-bg: var(--success-bg); }
.sf-pill--warn       { --state-fg: var(--warning-fg);          --state-bg: var(--warning-bg); }
.sf-pill--danger     { --state-fg: var(--danger-fg);           --state-bg: var(--danger-bg); }
.sf-pill--neutral    { --state-fg: var(--state-queued-fg);     --state-bg: var(--state-queued-bg); }

/* ---- agent chip (2026-05-30, MCP client-identity PR-5) ----
   Renders the captured MCP agent identity (monogram + product name +
   optional version) on the session-state admin surfaces. The monogram
   accent is keyed by a per-agent slug to existing --source-*/--type-*
   tokens — no new colors. See AgentChip.razor + the design review at
   docs/design/reviews/2026-05-30-session-state-client-identity/. */
.sf-agent-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-8);
    padding: 3px var(--space-8) 3px 3px;
    background: var(--bg-sunken);
    border: 1px solid var(--border);
    border-radius: var(--radius-6);
    font-size: var(--text-13);
    font-weight: var(--weight-medium);
    color: var(--fg);
    line-height: 1;
    white-space: nowrap;
}
.sf-agent-chip__mono {
    flex: none;
    width: 20px;
    height: 20px;
    border-radius: var(--radius-4);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 10px;
    font-weight: var(--weight-bold);
    letter-spacing: -0.01em;
    color: white;
    background: var(--fg-subtle); /* fallback; overridden by --<slug> modifier */
}
.sf-agent-chip__name {
    font-size: var(--text-13);
    font-weight: var(--weight-medium);
    color: var(--fg);
}
.sf-agent-chip__sep {
    color: var(--fg-subtle);
    font-size: var(--text-12);
    user-select: none;
}
.sf-agent-chip__version {
    font-family: var(--font-mono);
    font-size: var(--text-12);
    color: var(--fg-muted);
    font-weight: var(--weight-regular);
}
/* Unknown / unresolved client. */
.sf-agent-chip--unknown .sf-agent-chip__mono { background: var(--border-strong); color: var(--fg-subtle); }
.sf-agent-chip--unknown .sf-agent-chip__name { color: var(--fg-subtle); font-weight: var(--weight-regular); }
/* Compact, borderless list-cell variant. */
.sf-agent-chip--table {
    background: transparent;
    border-color: transparent;
    padding: 0;
    gap: var(--space-4);
    font-size: var(--text-12);
}
.sf-agent-chip--table .sf-agent-chip__mono { width: 16px; height: 16px; font-size: 9px; border-radius: var(--radius-2); }
.sf-agent-chip--table .sf-agent-chip__name { font-size: var(--text-12); }
.sf-agent-chip--table .sf-agent-chip__version { font-size: 11px; }
/* Per-agent monogram accent (cluster hint via existing tokens; not 1:1 identity). */
.sf-agent-chip--claude-code    .sf-agent-chip__mono { background: var(--source-mcp); }
.sf-agent-chip--cursor         .sf-agent-chip__mono { background: var(--type-desktop-app); }
.sf-agent-chip--codex-cli      .sf-agent-chip__mono { background: var(--source-api); }
.sf-agent-chip--github-copilot .sf-agent-chip__mono { background: var(--source-api); }
.sf-agent-chip--windsurf       .sf-agent-chip__mono { background: var(--type-desktop-app); }
.sf-agent-chip--cline          .sf-agent-chip__mono { background: var(--source-mcp); }
.sf-agent-chip--aider          .sf-agent-chip__mono { background: var(--warning); }
.sf-agent-chip--continue       .sf-agent-chip__mono { background: var(--source-mcp); }

/* ---- project type badge ---- */
.sf-typebadge {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 2px var(--space-8);
    border-radius: var(--radius-4);
    font-size: var(--text-12); font-weight: var(--weight-medium);
    color: var(--type-fg); background: color-mix(in srgb, var(--type-fg) 12%, transparent);
}
.sf-typebadge__icon { font-size: 14px; line-height: 1; }
.sf-typebadge--web-app           { --type-fg: var(--type-web-app); }
.sf-typebadge--mobile-app        { --type-fg: var(--type-mobile-app); }
.sf-typebadge--mobile-game       { --type-fg: var(--type-mobile-game); }
.sf-typebadge--desktop-app       { --type-fg: var(--type-desktop-app); }
.sf-typebadge--browser-extension { --type-fg: var(--type-browser-extension); }
.sf-typebadge--ai-agent          { --type-fg: var(--type-ai-agent); }
.sf-typebadge--ai-tool           { --type-fg: var(--type-ai-tool); }

/* ---- empty state ----
   2026-05-15 — H-E14: the canonical .sf-empty card lives below
   (~line 2810 — "Empty-state card (review finding S3)"). The bare
   single-line definition that used to live here was dead — the
   later block defined the same selector and won via cascade. Use
   `.sf-empty--inset` for in-table contexts where the dashed card
   chrome would be visually heavy.  */

/* 2026-05-14 — v1-launch Batch C H-C1 (deferred follow-up).
   Interview first-load orientation block — replaces the bare
   "Starting interview…" empty state with a richer welcome card
   that frames the wait (what's about to happen, how long it takes,
   pause guarantee). Sits inside `.sf-conversation` so the existing
   page grid handles centering / spacing; we only need card chrome. */
.sf-interview-orientation {
    margin: var(--space-32) auto;
    max-width: 640px;
    padding: var(--space-24) var(--space-32);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-12);
    text-align: left;
}
.sf-interview-orientation__title {
    margin: 0 0 var(--space-12);
    font-size: var(--text-20, 20px);
    color: var(--ink);
    font-weight: var(--weight-semibold);
    line-height: 1.3;
}
.sf-interview-orientation__lede {
    margin: 0 0 var(--space-16);
    color: var(--fg-muted);
    font-size: var(--text-14);
    line-height: 1.55;
}
.sf-interview-orientation__bullets {
    margin: 0 0 var(--space-20);
    padding-left: var(--space-20);
    color: var(--fg);
    font-size: var(--text-14);
    line-height: 1.55;
}
.sf-interview-orientation__bullets li + li { margin-top: var(--space-8); }
.sf-interview-orientation__status {
    margin: 0;
    padding-top: var(--space-16);
    border-top: 1px dashed var(--border);
    color: var(--fg-subtle);
    font-size: var(--text-13);
}
/* Interview review S6 (2026-05-02) — error variant of sf-empty
   replaces the inline `style="border-color: var(--state-failed, #c33);"`
   that several pages were carrying. */
.sf-empty--error {
    border: 1px solid var(--danger-border);
    background: var(--danger-bg);
    color: var(--danger-fg);
    border-radius: var(--radius-8);
}

/* sf-card--inline-top — utility margin for stacked cards.
   Replaces inline style="margin-top: var(--space-16);" on many
   sites (Interview no-intake card, Interview linked-generations
   card, Workspace cards before the C2b consolidation). */
.sf-card--inline-top { margin-top: var(--space-16); }

/* System-pages review (2026-05-02) — semantic card variants for
   tonal banners. Replace the prior sf-card--cta chrome with
   tokens that resolve correctly per theme + per state. */
.sf-card--success {
    background: var(--success-bg);
    border-color: var(--success-border);
}
.sf-card--success .sf-card__title { color: var(--success-fg); }
.sf-card--danger {
    background: var(--danger-bg);
    border-color: var(--danger-border);
}
.sf-card--danger .sf-card__title { color: var(--danger-fg); }

/* PR-X1 (2026-05-06) — warning-toned card variant for the
   PausedAwaitingClarification surface on the Generation Details
   page. Reuses the warning tokens (used by paused-banner / stale
   alerts elsewhere) so the styling stays consistent across surfaces
   that mean "user action required". */
.sf-card--warning {
    background: var(--warning-bg);
    border-color: var(--warning-border);
}
.sf-card--warning .sf-card__title { color: var(--warning-fg); }
.sf-card__eyebrow {
    display: inline-block;
    font-size: var(--text-12);
    color: var(--warning-fg);
    letter-spacing: var(--track-wide);
    text-transform: uppercase;
    font-weight: var(--weight-semibold);
    margin-bottom: var(--space-4);
}

/* System-pages review C5 (2026-05-02) — sf-prose--centered
   modifier replaces the inline style="text-align: center;" that
   AccountDisabled and BillingSuccess were each carrying. Also
   adds a small CTA strip primitive so a centered prose page can
   line up its action buttons consistently. */
.sf-prose--centered { text-align: center; }
.sf-prose--centered ul,
.sf-prose--centered ol { text-align: left; display: inline-block; }
.sf-prose__cta {
    display: flex; justify-content: center; gap: var(--space-12);
    flex-wrap: wrap;
    margin-top: var(--space-24);
}

/* System-pages review C6 (2026-05-02) — Ticket form layout.
   Two columns at ≥860px (form on the left, FAQ-deflection +
   automatic-context + alt-contacts panel on the right);
   collapses to single-column with the help panel beneath the
   form on phones. */
.sf-ticket { display: block; }
.sf-ticket__layout {
    display: grid;
    grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
    gap: var(--space-32);
    align-items: start;
}
@media (max-width: 860px) {
    .sf-ticket__layout { grid-template-columns: 1fr; gap: var(--space-16); }
}
.sf-ticket__form-col { min-width: 0; }
.sf-ticket__help-col {
    display: flex; flex-direction: column; gap: var(--space-16);
}
.sf-ticket__help-card {
    /* Help-rail cards are smaller than primary content cards. */
    padding: var(--space-16);
}
.sf-ticket__deflect-list,
.sf-ticket__context-list {
    list-style: none; padding: 0; margin: 0;
    display: flex; flex-direction: column; gap: var(--space-8);
    font-size: var(--text-13);
}
.sf-ticket__deflect-list li a,
.sf-ticket__context-list li a {
    color: var(--accent-fg);
    text-decoration: none;
}
.sf-ticket__deflect-list li a:hover,
.sf-ticket__context-list li a:hover { text-decoration: underline; }
.sf-ticket__context-list li { color: var(--fg-muted); }
.sf-ticket__context-list li strong { color: var(--fg); margin-right: var(--space-4); }

/* Pre-filled-from-account chip beside a form label. Inline
   `sf-pill` reuse with a left-margin so the label text and the
   chip space cleanly. */
.sf-field__prefill {
    margin-left: var(--space-8);
    vertical-align: middle;
    cursor: help;
}

/* ─────────────────────────────────────────────────────────────────
   GenerationDetail — System-pages review C1 + C4 (2026-05-02)
   Full-page rebuild of /generations/{id}. Compact metadata header
   + pipeline progress strip + two-column main/rail layout.
   ───────────────────────────────────────────────────────────── */
.sf-detail { display: block; }
.sf-detail__header {
    display: flex; align-items: flex-start; justify-content: space-between;
    gap: var(--space-16);
    padding: var(--space-12) 0 var(--space-16);
    border-bottom: 1px solid var(--border);
    margin-bottom: var(--space-24);
}
.sf-detail__title-block { display: flex; flex-direction: column; gap: var(--space-8); min-width: 0; flex: 1; }
.sf-detail__title {
    margin: 0;
    font-size: var(--text-24);
    font-weight: var(--weight-semibold);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sf-detail__meta {
    display: flex; align-items: center; flex-wrap: wrap;
    gap: var(--space-12);
    font-size: var(--text-13);
}
.sf-detail__actions { flex: none; display: flex; gap: var(--space-8); }

/* 2026-05-14 — v1-launch Batch D H-D7 (deferred follow-up).
   Unified telemetry strip card. Folds the progress bar + cost
   row + pipeline strip into a single elevated surface with
   dashed hairlines between sections — one telemetry surface
   instead of three stacked slabs. Each inner section keeps its
   prior internal layout; this rule only owns the outer card +
   the between-row dividers. */
.sf-detail__telemetry {
    display: flex;
    flex-direction: column;
    gap: var(--space-12);
    padding: var(--space-16) var(--space-20);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-8);
    margin: 0 0 var(--space-16);
}
.sf-detail__telemetry-row { margin: 0; padding: 0; }
.sf-detail__telemetry-row + .sf-detail__telemetry-row {
    padding-top: var(--space-12);
    border-top: 1px dashed var(--border);
}
/* 2026-05-05 — specification-kind subtitle. Keeps users (and MCP
   callers staring at the details page) clear on what the
   deliverable is: a spec package, not application code. */
.sf-detail__subtitle {
    margin: 0;
    display: flex; align-items: center; flex-wrap: wrap;
    gap: var(--space-8);
    font-size: var(--text-13);
}
.sf-detail__subtitle-hint {
    color: var(--fg-muted);
}
.sf-detail__description {
    margin: 0;
    font-size: var(--text-14);
    color: var(--fg-muted);
    max-width: 80ch;
    line-height: 1.5;
}

/* Pipeline progress strip — horizontal sequence of step dots +
   labels showing where the generation is in its journey. Done
   steps are filled accent, the active step pulses, pending
   steps are outlined and muted. */
.sf-pipeline {
    list-style: none; padding: 0; margin: 0 0 var(--space-24);
    display: flex; align-items: center;
    gap: var(--space-12);
    flex-wrap: wrap;
}
.sf-pipeline__step {
    display: inline-flex; align-items: center; gap: var(--space-8);
    font-size: var(--text-13);
    color: var(--fg-muted);
    position: relative;
}
/* Connector line between steps. */
.sf-pipeline__step + .sf-pipeline__step::before {
    content: "";
    width: var(--space-24);
    height: 1px;
    background: var(--border);
    margin-right: var(--space-4);
}
.sf-pipeline__dot {
    width: 18px; height: 18px;
    border-radius: 50%;
    border: 1.5px solid var(--border-strong);
    background: var(--bg-elevated);
    display: inline-flex; align-items: center; justify-content: center;
    color: white;
}
.sf-pipeline__step--done .sf-pipeline__dot {
    background: var(--accent-700);
    border-color: var(--accent-700);
}
.sf-pipeline__step--done { color: var(--fg); }
.sf-pipeline__step--active .sf-pipeline__dot {
    background: var(--accent);
    border-color: var(--accent);
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 18%, transparent);
    animation: sf-pipeline-pulse var(--motion-duration-slow) ease-in-out infinite alternate;
}
.sf-pipeline__step--active {
    color: var(--accent-fg);
    font-weight: var(--weight-semibold);
}
@keyframes sf-pipeline-pulse {
    from { box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 18%, transparent); }
    to   { box-shadow: 0 0 0 8px color-mix(in srgb, var(--accent)  6%, transparent); }
}
@media (prefers-reduced-motion: reduce) {
    .sf-pipeline__step--active .sf-pipeline__dot { animation: none; }
}
.sf-pipeline__label { white-space: nowrap; }
@media (max-width: 640px) {
    /* On phones, drop the labels — keep just the dots + connector
       so the strip remains scannable in narrow widths. */
    .sf-pipeline__label { display: none; }
}

/* Two-column layout — main content on the left, info rail on the
   right. Collapses to single-column below 900px with the rail
   moving below the main content. */
.sf-detail__layout {
    display: grid;
    grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
    gap: var(--space-24);
    align-items: start;
}
@media (max-width: 900px) {
    .sf-detail__layout { grid-template-columns: 1fr; }
}
.sf-detail__main-col { min-width: 0; display: flex; flex-direction: column; gap: var(--space-16); }
.sf-detail__rail { display: flex; flex-direction: column; gap: var(--space-16); }
.sf-detail__rail-card { padding: var(--space-16); }

/* Rail card description list — key/value pairs with the key
   ALL-CAPS muted on top and the value below. */
.sf-detail__rail-list {
    margin: 0;
    display: flex; flex-direction: column; gap: var(--space-12);
}
.sf-detail__rail-list > div { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.sf-detail__rail-list dt {
    font-size: var(--text-12);
    color: var(--fg-subtle);
    letter-spacing: var(--track-wide, 0.04em);
    font-weight: var(--weight-semibold);
    text-transform: uppercase;
}
.sf-detail__rail-list dd { margin: 0; font-size: var(--text-14); color: var(--fg); }
.sf-detail__rail-id { font-family: var(--font-mono); font-size: var(--text-12); overflow-wrap: anywhere; }

/* Cost rail card — per-agent breakdown table sitting below the
   total / token-count summary list. Compact rows so the card
   doesn't dominate the rail. */
.sf-detail__rail-cost-breakdown {
    width: 100%; margin-top: var(--space-12); border-collapse: collapse;
    font-size: var(--text-12); color: var(--fg);
}
.sf-detail__rail-cost-breakdown th,
.sf-detail__rail-cost-breakdown td {
    padding: 4px 0; border-bottom: 1px solid var(--border);
    text-align: left;
}
.sf-detail__rail-cost-breakdown th {
    color: var(--fg-subtle); font-weight: var(--weight-semibold);
    letter-spacing: 0.04em;
}
.sf-detail__rail-cost-breakdown tr:last-child td { border-bottom: none; }
.sf-detail__rail-cost-note { margin-top: var(--space-8); font-size: var(--text-12); color: var(--fg-muted); }

/* 2026-05-23 — cost-visibility PR #3. LOC totals + per-agent LOC
   columns use the same green/red color tokens as the bubble's
   .sf-diffstat chip so the visual signal carries from the
   conversation feed up to the rail-card rollup. The rail-list dd
   variant gets a slight color block; the table-cell variant is
   plain colored text (less visual weight; the row already has
   structure from the dot + agent name). */
.sf-detail__rail-loc {
    font-variant-numeric: tabular-nums;
    font-weight: var(--weight-medium, 500);
}
.sf-detail__rail-loc--added { color: var(--success-fg); }
.sf-detail__rail-loc--removed { color: var(--danger-fg); }
.sf-detail__rail-list .sf-detail__rail-loc {
    /* dd context — give the LOC chip a small backed treatment to
       match the .sf-diffstat__added / .sf-diffstat__removed pill
       look without re-using the chip markup (the dl rows want a
       single inline span). */
    display: inline-block;
    padding: 2px 6px;
    border-radius: var(--radius-pill, 999px);
}
.sf-detail__rail-list .sf-detail__rail-loc--added {
    background: var(--success-bg);
    color: var(--success-fg);
}
.sf-detail__rail-list .sf-detail__rail-loc--removed {
    background: var(--danger-bg);
    color: var(--danger-fg);
}

/* Per-agent breakdown tfoot — Total row. Sits below the per-agent
   rows; visually distinct via a top-border separator + slightly
   stronger label weight. */
.sf-detail__rail-cost-breakdown tfoot th,
.sf-detail__rail-cost-breakdown tfoot td {
    border-top: 1px solid var(--border);
    border-bottom: none;
    padding-top: var(--space-6, 6px);
    font-weight: var(--weight-semibold, 600);
}
.sf-detail__rail-cost-total th {
    color: var(--fg);
    /* `th[scope="row"]` browser default is bold + left-aligned —
       both desirable here, but reset uppercase + letter-spacing
       inherited from the column-header rule above. */
    text-transform: none;
    letter-spacing: 0;
}

/* Package-contents list (the "What's in the zip" card on
   Complete-state). Reuses sf-prose-style readability rather
   than the dense workspace-table treatment. */
.sf-detail__contents-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex; flex-direction: column; gap: var(--space-8);
    font-size: var(--text-13);
}
.sf-detail__contents-list li code {
    background: var(--bg-sunken);
    padding: 1px var(--space-8);
    border-radius: var(--radius-4);
    margin-right: var(--space-4);
    font-family: var(--font-mono);
}

/* Initiative E session E5 (2026-05-03) — in-flight status visibility.
   Dense metadata grid (Stage / Round / Working agent / Last activity)
   + a recent-activity event list backed by GenerationEvent. */
.sf-detail__inflight-meta {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    gap: var(--space-12);
    margin: 0 0 var(--space-12);
}
.sf-detail__inflight-meta > div { display: flex; flex-direction: column; gap: 2px; }
.sf-detail__inflight-meta dt {
    font-size: var(--text-12);
    color: var(--fg-subtle);
    letter-spacing: var(--track-wide, 0.04em);
    font-weight: var(--weight-semibold);
    text-transform: uppercase;
}
.sf-detail__inflight-meta dd {
    margin: 0;
    font-size: var(--text-14);
    color: var(--fg);
}

.sf-detail__inflight-events {
    margin-top: var(--space-12);
    border-top: 1px solid var(--border);
    padding-top: var(--space-12);
}
.sf-detail__inflight-events > summary {
    cursor: pointer;
    list-style: none;
    user-select: none;
    font-size: var(--text-12);
    color: var(--fg-subtle);
    letter-spacing: var(--track-wide, 0.04em);
    font-weight: var(--weight-semibold);
    text-transform: uppercase;
    padding: var(--space-4) 0;
}
.sf-detail__inflight-events > summary::-webkit-details-marker { display: none; }
.sf-detail__inflight-events > summary::before {
    content: "▸ ";
    color: var(--fg-subtle);
    display: inline-block;
}
.sf-detail__inflight-events[open] > summary::before { content: "▾ "; }
.sf-detail__event-list {
    list-style: none;
    margin: var(--space-8) 0 0;
    padding: 0;
    display: flex; flex-direction: column; gap: var(--space-4);
}
.sf-detail__event-list li {
    display: flex; align-items: baseline; gap: var(--space-12);
    font-size: var(--text-13);
    padding: var(--space-4) 0;
    border-bottom: 1px dashed var(--border);
}
.sf-detail__event-list li:last-child { border-bottom: 0; }
.sf-detail__event-time {
    font-family: var(--font-mono);
    font-size: var(--text-12);
    color: var(--fg-muted);
    flex: none;
}
.sf-detail__event-label { color: var(--fg); }

/* ---- interview surface ---- */
.sf-interview {
    display: grid;
    grid-template-columns: 1fr 320px;
    grid-template-rows: 1fr auto;
    grid-template-areas:
        "convo side"
        "composer side";
    gap: var(--space-24);
    height: 100%;
    /* The convo column scrolls independently; composer sticks to the
       bottom of the convo column. The side panel is full-height. */
}
.sf-conversation { grid-area: convo; max-width: 880px; width: 100%; margin: 0 auto; overflow-y: auto; }
/* Interview review C1 (2026-05-02) — compact metadata strip
   replaces the prior sf-page__header which carried debug copy
   ("Interview 1a2b3c4d · status Active") in production. Sits
   above the first turn at ~36px and doubles as the live status
   line: project-type badge + turn count + start time + status
   pill. */
.sf-conversation__header {
    padding: var(--space-12) 0 var(--space-16);
    border-bottom: 1px solid var(--border);
    margin-bottom: var(--space-16);
}
.sf-conversation__meta {
    display: flex; align-items: center; flex-wrap: wrap;
    gap: var(--space-12);
    font-size: var(--text-13);
}
.sf-conversation__meta-item { display: inline-flex; align-items: center; }
/* PR-T35 (2026-05-07) — small inline action button for the meta
   strip. Currently hosts only "Delete" but generic enough to take
   future actions (Pause / Abandon) if those graduate from the menu.
   Styled like a quiet text link so it doesn't compete with the
   status pill or project-type badge. Hover bumps to --fg-strong
   so the affordance is obvious without being loud. */
.sf-conversation__meta-action {
    margin-left: auto;
    background: none;
    border: none;
    padding: 0;
    font: inherit;
    color: var(--fg-subtle);
    cursor: pointer;
    text-decoration: underline;
    text-underline-offset: 2px;
}
.sf-conversation__meta-action:hover { color: var(--accent-danger, #dc2626); }
.sf-conversation__meta-action:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: 2px;
}
/* sf-visually-hidden — accessible-but-hidden h1 for landmark
   roles on pages where the visual title is intentionally
   suppressed (Interview's compact metadata strip replaces the
   visible h1 but still needs a heading for screen readers). */
.sf-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;
}
.sf-sidepanel    { grid-area: side;
    border-left: 1px solid var(--border);
    padding-left: var(--space-16);
    display: flex; flex-direction: column; gap: var(--space-24);
    overflow-y: auto;
}
.sf-composer     { grid-area: composer;
    position: sticky; bottom: 0; background: var(--bg);
    padding-top: var(--space-12);
    /* Composer is now a vertical stack: optional attachment chip
       row on top, the textarea + actions row below. */
    display: flex; flex-direction: column; gap: var(--space-8);
    max-width: 880px; width: 100%; margin: 0 auto;
}
.sf-composer__row {
    /* 2026-05-14 — v1-launch Batch C H-C9 (deferred follow-up).
       Anchor the action buttons to the BOTTOM of the textarea so a
       tall multi-line draft doesn't visually orphan Send/paperclip
       at the top. Was align-items: stretch (forced the action stack
       to grow with the textarea); now align-items: end + a flat
       horizontal action row at 36px. */
    display: flex; gap: var(--space-8); align-items: end;
}
.sf-composer__input {
    flex: 1; resize: none; min-height: 44px;
    padding: var(--space-8) var(--space-12);
    background: var(--bg-elevated); color: var(--fg);
    border: 1px solid var(--border); border-radius: var(--radius-6);
    font: inherit;
}
.sf-composer__input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* Right-side action row.
   2026-05-14 — v1-launch Batch C H-C9 (deferred follow-up).
   Flatten to a horizontal row: paperclip + Send sit side-by-side
   at matching 36px heights, anchored to the bottom of the textarea
   via the parent row's align-items: end. Replaces the prior column
   stack that climbed up alongside a tall draft. */
.sf-composer__actions {
    display: flex; flex-direction: row; align-items: center; gap: var(--space-8);
}
.sf-composer__send {
    /* Match the paperclip's 36px height so Send + attach read as
       paired affordances. The base sf-btn padding-driven height
       (~40px+) was too tall once we put the two side-by-side. */
    height: 36px;
    padding-block: 0;
}

/* Paperclip — visually a small icon button. The native <InputFile>
   inside is hidden but still triggers the file picker on click. */
.sf-composer__paperclip {
    display: inline-flex; align-items: center; justify-content: center;
    width: 36px; height: 36px;
    background: var(--bg-elevated); color: var(--fg-muted);
    border: 1px solid var(--border); border-radius: var(--radius-6);
    line-height: 1; cursor: pointer;
    transition: color var(--motion-duration-fast) var(--motion-easing-out),
                background var(--motion-duration-fast) var(--motion-easing-out);
}
.sf-composer__paperclip:hover { background: var(--bg-sunken); color: var(--fg); }
.sf-composer__paperclip:focus-within { outline: 2px solid var(--accent); outline-offset: 2px; }
.sf-composer__paperclip-icon { display: block; }
.sf-composer__paperclip-input {
    /* Visually hide but stay clickable through the wrapping label. */
    position: absolute; width: 1px; height: 1px;
    padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0);
    white-space: nowrap; border: 0;
}

/* Attachment chip row above the textarea. Horizontal scroll when
   many files are attached so we don't wrap and push the textarea
   down. */
.sf-composer__attachments {
    display: flex; gap: var(--space-8); overflow-x: auto;
    padding-bottom: var(--space-4);
}
.sf-attachment-chip {
    display: flex; flex-direction: column; align-items: center;
    flex: 0 0 auto; min-width: 64px; max-width: 96px;
    padding: var(--space-4); border-radius: var(--radius-6);
    background: var(--bg-elevated); border: 1px solid var(--border);
}
.sf-attachment-chip__thumb {
    width: 64px; height: 64px; object-fit: cover;
    border-radius: var(--radius-4);
}
.sf-attachment-chip__icon {
    width: 64px; height: 64px;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 32px; line-height: 1;
}
.sf-attachment-chip__name {
    font-size: var(--text-12, 12px);
    margin-top: var(--space-4);
    max-width: 96px;
    overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
    color: var(--fg-muted, var(--fg));
}

.sf-turns { list-style: none; padding: 0; margin: 0 0 var(--space-24); display: flex; flex-direction: column; gap: var(--space-16); }

/* Initiative F session F7 (2026-05-03) — "Captured so far"
   side-panel section. Renders verifiable facts from the
   snapshot (project type, turn count, reference files) as
   labeled bullets. Empty state explains the panel will fill
   as the conversation progresses. */
.sf-captured-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-8);
    font-size: var(--text-13);
}
.sf-captured-list li {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: var(--space-4) 0;
    border-bottom: 1px dashed var(--border);
}
.sf-captured-list li:last-child { border-bottom: 0; }
.sf-captured-list__label {
    font-size: var(--text-12);
    color: var(--fg-subtle);
    letter-spacing: var(--track-wide, 0.04em);
    font-weight: var(--weight-semibold);
    text-transform: uppercase;
}
.sf-captured-list__value { color: var(--fg); }
.sf-sidepanel__empty {
    font-style: italic;
    color: var(--fg-muted);
}

/* Initiative F session F6 (2026-05-03) — "{Agent} joined the
   conversation" inline divider rendered between transcript turns
   when the membership service shows a new agent joined in that
   timestamp window. Subtle treatment: hairline border above + below,
   small accent-fg label centered. The user sees this exactly once
   per agent + can correlate it with the turn that triggered the
   consultation. */
.sf-turn-divider {
    display: flex; align-items: center; justify-content: center;
    gap: var(--space-12);
    padding: var(--space-8) 0;
    margin: var(--space-8) 0;
    color: var(--fg-subtle);
    font-size: var(--text-12);
    text-align: center;
    list-style: none;
    position: relative;
}
.sf-turn-divider::before,
.sf-turn-divider::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--border);
}
.sf-turn-divider__label {
    color: var(--accent-fg);
    font-weight: var(--weight-medium);
    flex: none;
}
.sf-turn-divider__time {
    color: var(--fg-subtle);
    font-variant-numeric: tabular-nums;
    flex: none;
}

/* Initiative F session F5 (2026-05-03) — turn-affordance redesign.
   Agent turns are the canonical card (left-aligned, --bg-elevated,
   full bubble chrome with the avatar circle). User turns are a
   quieter "reply" — right-aligned, NO bubble, --fg text on the
   page background, with a small attribution line above carrying
   real initials + display name + timestamp. The asymmetry mirrors
   the actual conversation shape: the Interviewer is the one asking
   structured questions; the user is just answering. */
.sf-turn { display: flex; gap: var(--space-12); }
.sf-turn--agent { align-items: flex-start; }
.sf-turn--user {
    /* Indent the user reply to the right side of the column. The
       attribution line + body align right, and the column is
       capped at 640px to match the agent bubble's max-width. */
    flex-direction: column;
    align-items: flex-end;
    text-align: right;
    max-width: 640px;
    margin-left: auto;
    gap: var(--space-4);
}

.sf-turn__avatar {
    flex: 0 0 32px; width: 32px; height: 32px;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: 50%;
    font-size: var(--text-12); font-weight: var(--weight-semibold);
}
.sf-turn__avatar--agent {
    background: color-mix(in srgb, var(--accent) 16%, var(--bg-sunken));
    color: var(--accent-fg);
    font-size: var(--text-14);
}
.sf-turn__avatar--user {
    background: var(--accent-700);
    color: white;
    /* Smaller avatar inline with the attribution text rather than
       the 32px standalone block — the user reply doesn't need an
       avatar block of its own. */
    flex: 0 0 24px; width: 24px; height: 24px;
}
/* Image variant of the turn avatar. The provider photo replaces
   the accent-700 fill while preserving the circular crop + box
   dimensions set by .sf-turn__avatar / .sf-turn__avatar--user. */
.sf-turn__avatar--img {
    background: transparent;
    object-fit: cover;
    display: inline-block;
}

/* Initiative G6 (2026-05-03) — shared <img>-based agent avatar
   used by the interview chat bubbles, the AI Team panel, the
   "agent joined" divider, and any future surface that displays
   an agent. Replaces the prior glyph/emoji circles. The headshot
   PNGs ship with a baked-in cream background so we crop to a
   circle with border-radius:50% rather than rendering a colored
   ring around them.
   The optional --agent-{slug} accent (set inline via the page's
   per-agent wrapper) becomes a 1px ring; falls back transparent
   so the same rule works for surfaces that don't pin an accent. */
.sf-agent-avatar {
    display: block;
    border-radius: 50%;
    background: var(--bg-sunken);
    box-shadow: 0 0 0 1px var(--agent-accent, transparent);
    object-fit: cover;
}
.sf-turn-divider__avatar { flex: none; }
.sf-ai-team__avatar { flex: none; width: 24px; height: 24px; }
.sf-ai-team__icon--fallback {
    background: color-mix(in srgb, var(--accent) 16%, var(--bg-sunken));
    color: var(--accent-fg);
    border-radius: 50%;
    font-weight: var(--weight-semibold);
}

.sf-turn__bubble { background: var(--bg-elevated); border: 1px solid var(--border); border-radius: var(--radius-12); padding: var(--space-12) var(--space-16); max-width: 640px; }
.sf-turn__meta { display: flex; gap: var(--space-8); align-items: baseline; margin-bottom: 4px; }
.sf-turn__author { font-weight: var(--weight-semibold); font-size: var(--text-12); }
.sf-turn__time { color: var(--fg-subtle); font-size: var(--text-12); font-variant-numeric: tabular-nums; }
.sf-turn__body { color: var(--fg); line-height: var(--leading-snug); white-space: pre-wrap; }
/* User-reply attribution line: small avatar + name + timestamp
   sitting above the body text with a hairline accent border. */
.sf-turn__attribution {
    display: inline-flex; align-items: center; gap: var(--space-8);
    font-size: var(--text-12);
}
.sf-turn__attribution .sf-turn__author { color: var(--accent-fg); }
.sf-turn__body--reply {
    /* No bubble — text on the page background. Slight padding so
       the right-aligned text breathes; left-padding extra to give
       it some negative space against the attribution line above. */
    padding: var(--space-4) 0 var(--space-4) var(--space-32);
    color: var(--fg);
}
/* Interview review S1 (2026-05-02) — `sf-turn__body--typing` was
   referenced by Interview.razor but never defined. The "Thinking…"
   indicator was rendering as static text. Now: muted color + 3-dot
   pulsing animation that collapses to static dots under
   prefers-reduced-motion. */
.sf-turn__body--typing {
    color: var(--fg-muted);
    font-style: italic;
    display: inline-flex;
    align-items: center;
    gap: var(--space-8);
}
.sf-turn__body--typing::after {
    content: "";
    display: inline-block;
    width: 6px; height: 6px;
    border-radius: 50%;
    background: currentColor;
    box-shadow: 10px 0 0 0 currentColor, 20px 0 0 0 currentColor;
    animation: sf-typing-pulse var(--motion-duration-slow) ease-in-out infinite;
}
@keyframes sf-typing-pulse {
    0%, 60%, 100% { opacity: 0.25; }
    30%           { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
    .sf-turn__body--typing::after { animation: none; opacity: 0.6; }
}

.sf-completion-card {
    background: var(--bg-elevated); border: 1px solid var(--border-strong);
    border-radius: var(--radius-12); padding: var(--space-24);
    box-shadow: var(--shadow-2);
    display: flex; flex-direction: column; gap: var(--space-16);
}
.sf-completion-card__title { margin: 0; color: var(--accent-fg); font-size: var(--text-20); }
.sf-completion-card__lede  { margin: 0; color: var(--fg-muted); }
.sf-completion-card__actions { display: flex; gap: var(--space-8); justify-content: flex-end; }

/* Initiative I1 (2026-05-06) — "What we detected" preview panel on
   the completion card. Sits above the review-profile picker and
   surfaces the project attributes Otto detected as small chips, with
   tooltips naming the specialist agent each chip drives. The
   "Preview" pill anchors expectations: the routing ships in PR 3+
   so users don't expect immediate behavior change. */
.sf-detected-attrs { display: flex; flex-direction: column; gap: var(--space-8); }
.sf-detected-attrs__heading {
    margin: 0;
    font-size: var(--text-12);
    color: var(--fg-subtle);
    font-weight: var(--weight-semibold);
    letter-spacing: 0.04em;
    text-transform: uppercase;
}
.sf-detected-attrs__chips {
    list-style: none; margin: 0; padding: 0;
    display: flex; flex-wrap: wrap; gap: var(--space-6);
}
/* 2026-05-14 — v1-launch Batch C H-C2 (deferred follow-up).
   Specialist-agent badge inside the detected-attrs chips. Visible
   text so keyboard users + AT readers see the name (previously only
   in the `title` tooltip). Slightly de-emphasized via --fg-muted-on-info
   so the primary chip label stays primary. */
.sf-detected-attrs__agent {
    margin-left: var(--space-4);
    font-weight: var(--weight-regular);
    opacity: 0.78;
}
.sf-detected-attrs__note {
    margin: 0;
    font-size: var(--text-12);
    color: var(--fg-muted);
    display: flex; align-items: center; gap: var(--space-6);
}

.sf-profiles { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-12); border: 0; padding: 0; margin: 0; }
.sf-profiles__legend { font-size: var(--text-12); color: var(--fg-subtle); font-weight: var(--weight-semibold); letter-spacing: 0.04em; padding: 0; margin-bottom: var(--space-8); }
.sf-profile {
    display: block;
    position: relative; /* anchors .sf-profile__overlay (review S6 — was inline style on the label) */
    padding: var(--space-12);
    border: 1px solid var(--border);
    border-radius: var(--radius-8);
    cursor: pointer;
}
.sf-profile input { position: absolute; opacity: 0; pointer-events: none; }
.sf-profile--disabled { opacity: 0.55; cursor: not-allowed; }
.sf-profile__overlay {
    position: absolute;
    bottom: var(--space-8);
    right: var(--space-8);
    font-size: 0.85em;
}
.sf-profile--selected { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 6%, transparent); }
/* Initiative F D5 (2026-05-03) — Researcher gets full-width row
   below the 3-column reviewer grid. Was breaking out as a centered
   orphan row that read like a layout bug. The full-width treatment
   matches the Researcher's structural difference (it spawns 3
   sub-runs vs the others' single run). Internal layout shifts to
   horizontal: title | sub | cost on one line so the wider footprint
   reads as a "compare-with" row, not a giant single card. */
.sf-profile--researcher {
    grid-column: 1 / -1;
    display: grid;
    grid-template-columns: 1fr 2fr auto;
    column-gap: var(--space-16);
    align-items: center;
    background: color-mix(in srgb, var(--accent) 4%, transparent);
}
.sf-profile--researcher .sf-profile__sub { margin-top: 0; }
.sf-profile--researcher .sf-profile__cost {
    margin-top: 0;
    text-align: right;
    white-space: nowrap;
}
.sf-profile--researcher.sf-profile--selected {
    background: color-mix(in srgb, var(--accent) 10%, transparent);
}
.sf-profile__title {
    /* Interview review S6 (2026-05-02) — display: flex + gap so the
       inline `style="margin-left: var(--space-4);"` on the Beta pill
       can be removed. */
    display: flex; align-items: center; gap: var(--space-8);
    font-weight: var(--weight-semibold);
}
.sf-profile__sub { font-size: var(--text-12); color: var(--fg-muted); margin-top: 2px; }
/* Interview review C6 (2026-05-02) — cost is the load-bearing
   decision input on this card; promote it from --text-12/--fg-subtle
   to --text-14/--fg + monospace so dollar amounts align across cards.
   The Researcher-card row breakage was fixed in Initiative F D5
   (2026-05-03) via the .sf-profile--researcher full-width modifier
   above. */
.sf-profile__cost {
    font-size: var(--text-14);
    font-family: var(--font-mono);
    color: var(--fg);
    font-weight: var(--weight-semibold);
    margin-top: var(--space-8);
    font-variant-numeric: tabular-nums;
    display: flex;
    align-items: center;
    gap: var(--space-6);
    flex-wrap: wrap;
}

/* 2026-05-13 — per-intake cost/duration range emitted by
   IGenerationEstimator. Slight letter-spacing on the en-dash so
   "$1.10–$1.45" reads as one fluid range rather than four glyphs
   competing with the mono-num cost figures. */
.sf-profile__cost-range {
    letter-spacing: 0.01em;
}

/* 2026-05-13 — cold-start fallback indicator. Renders when any
   active agent in the intake's tier × profile × project-attrs
   slice has fewer than IAgentCostHistoryService.MinSampleSize
   completed generations in the rolling 30-day window; the
   estimate is the hardcoded profile seed rather than history-
   grounded. Small grey badge keeps it out of the user's way
   while still telegraphing "this number will tighten as we
   accumulate runs." */
.sf-profile__projected-pill {
    display: inline-block;
    padding: 1px var(--space-6);
    border-radius: 999px;
    background: var(--bg-muted);
    color: var(--fg-muted);
    font-family: var(--font-sans);
    font-size: 10px;
    font-weight: var(--weight-medium);
    letter-spacing: 0.02em;
    text-transform: lowercase;
    line-height: 1.4;
    border: 1px solid var(--border);
}

/* 2026-05-13 — collapsible side-by-side profile comparison panel.
   Sits inside the .sf-profiles grid between the 3 main cards
   (row 1) and Researcher (row 3). grid-column: 1 / -1 spans all
   3 columns so the grid auto-flow keeps Researcher on its own
   row below.

   Native <details> handles open/close + a11y; the chevron
   ::before + transform animation gives a consistent disclosure
   cue across browsers (default markers vary stylistically). */
.sf-profile-compare {
    grid-column: 1 / -1;
    margin: var(--space-4) 0 var(--space-8);
    border-top: 1px dashed var(--border);
    padding-top: var(--space-12);
}
.sf-profile-compare > summary {
    cursor: pointer;
    font-size: var(--text-13);
    color: var(--fg-muted);
    font-weight: var(--weight-medium);
    list-style: none;
    display: inline-flex;
    align-items: center;
    gap: var(--space-4);
    user-select: none;
}
.sf-profile-compare > summary::-webkit-details-marker { display: none; }
.sf-profile-compare > summary::before {
    content: "▸";
    transition: transform 0.15s;
    display: inline-block;
}
.sf-profile-compare[open] > summary::before {
    transform: rotate(90deg);
}
.sf-profile-compare > summary:hover { color: var(--fg); }
.sf-profile-compare__body {
    margin-top: var(--space-12);
    overflow-x: auto;
}
.sf-profile-compare__body table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--text-13);
}
.sf-profile-compare__body th,
.sf-profile-compare__body td {
    padding: var(--space-8) var(--space-12);
    border-bottom: 1px solid var(--border);
    text-align: left;
    vertical-align: top;
}
.sf-profile-compare__body thead th {
    font-weight: var(--weight-semibold);
    color: var(--fg);
}
.sf-profile-compare__body tbody th {
    color: var(--fg-muted);
    font-weight: var(--weight-medium);
    width: 22%;
}
.sf-profile-compare__body tbody tr:last-child th,
.sf-profile-compare__body tbody tr:last-child td {
    border-bottom: 0;
}

/* 2026-05-14 — v1-launch Batch C H-C11 (deferred follow-up).
   Profile-compare table was overflowing at narrow viewports (≤480px)
   because the inherited `width: 100%` collapsed columns instead of
   triggering the parent's overflow-x. Wrap in `.sf-profile-compare__scroll`
   + give the table a `min-width` so columns keep useful sizing while
   the wrapper scrolls horizontally. Sticky first column keeps row
   labels visible while the data columns scroll. */
.sf-profile-compare__scroll {
    overflow-x: auto;
    /* Match the iOS rubber-band scroll feel without locking the
       outer page; harmless on non-iOS. */
    -webkit-overflow-scrolling: touch;
}
.sf-profile-compare__scroll table {
    min-width: 540px;
}
@media (max-width: 640px) {
    .sf-profile-compare__scroll table tbody th {
        position: sticky;
        left: 0;
        background: var(--bg);
        z-index: 1;
    }
}

.sf-sidepanel__section { display: flex; flex-direction: column; gap: var(--space-8); }
.sf-sidepanel__heading { font-size: var(--text-12); color: var(--fg-subtle); font-weight: var(--weight-semibold); letter-spacing: 0.04em; margin: 0; }
.sf-sidepanel__caption { color: var(--fg-muted); font-size: var(--text-12); margin: 0; }

/* Interview review C5 (2026-05-02) — collapsible side-panel
   section. AI Team is auto-collapsed after a few turns since
   it's orienting context, not mid-interview signal. <details>
   styling matches the resume strip's past-interviews disclosure
   for consistency. */
.sf-sidepanel__collapsible { display: block; }
.sf-sidepanel__collapsible > summary {
    cursor: pointer;
    list-style: none;
    user-select: none;
    font-size: var(--text-12);
    color: var(--fg-subtle);
    font-weight: var(--weight-semibold);
    letter-spacing: var(--track-wide, 0.04em);
    text-transform: uppercase;
    padding: var(--space-4) 0;
}
.sf-sidepanel__collapsible > summary::-webkit-details-marker { display: none; }
.sf-sidepanel__collapsible > summary::before {
    content: "▸ ";
    color: var(--fg-subtle);
    display: inline-block;
}
.sf-sidepanel__collapsible[open] > summary::before { content: "▾ "; }
.sf-sidepanel__collapsible > summary:hover { color: var(--fg); }
.sf-sidepanel__collapsible[open] > *:not(summary) { margin-top: var(--space-12); }

/* ---- Meet Your AI Team panel ---- (Phase 9.5 follow-up)
   Compact roster of agents that will work on a generation kicked off
   from the current interview. Active members get an accent left
   border + 'active' pill; conditional members (Designer) only render
   when the project type warrants it. */
.sf-ai-team__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-8); }
.sf-ai-team__member {
    display: flex; gap: var(--space-8);
    padding: var(--space-8);
    border-radius: var(--radius-6);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
}
.sf-ai-team__member--active {
    border-left: 3px solid var(--accent);
    padding-left: 6px;
}
.sf-ai-team__icon {
    flex: none;
    width: 24px; height: 24px;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: var(--text-14, 14px);
}
.sf-ai-team__body { flex: 1; min-width: 0; }
.sf-ai-team__name {
    font-size: var(--text-13, 13px);
    font-weight: var(--weight-semibold);
    color: var(--fg);
    display: flex; align-items: center; gap: var(--space-4);
}
.sf-ai-team__badge {
    font-size: 10px;
    font-weight: var(--weight-medium);
    padding: 1px 6px;
    border-radius: var(--radius-pill);
    background: var(--accent);
    color: #fff;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.sf-ai-team__role { color: var(--fg-muted); font-size: var(--text-12); margin-top: 2px; line-height: 1.35; }
.sf-ai-team__note { font-style: italic; }
/* Preview badge — the agent is gated to a small audience, distinguish from production agents. */
.sf-ai-team__badge--preview { background: var(--bg-sunken); color: var(--fg-muted); }
.sf-doclist { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 4px; }
.sf-doclist__item { display: flex; justify-content: space-between; padding: 4px var(--space-8); border-radius: var(--radius-4); background: var(--bg-sunken); font-size: var(--text-13, 13px); }
.sf-doclist__redacted { color: var(--fg-subtle); font-size: var(--text-12); }
.sf-summary { padding-left: var(--space-16); margin: 0; color: var(--fg); }
.sf-summary li { margin-bottom: 4px; }

/* ---------------------------------------------------------------------
   TASK-095 + TASK-104 — Source-control + cost-dashboard helpers.
   --------------------------------------------------------------------- */

.sf-card {
    /* Web Controls v1 (2026-05-30) — canonical .card: 6px radius (r-card —
       the controls' radius hierarchy is chip 3 / control 4 / card 6) +
       more generous padding. */
    border: 1px solid var(--border);
    border-radius: var(--radius-6);
    padding: var(--space-20);
    background: var(--bg-elevated);
    margin-bottom: var(--space-16);
    display: flex; flex-direction: column; gap: var(--space-8);
}
.sf-card__title { font-weight: var(--weight-semibold); font-size: var(--text-16); }
.sf-card__sub   { color: var(--fg-muted); margin: 0; font-size: var(--text-14, 14px); }
.sf-card__actions { display: flex; gap: var(--space-8); align-items: center; }

/* Support hub tiles. Three across at desktop, stacks below 720px.
   Anchor variants get hover lift + accent border; the disabled
   variant (chat 'coming soon') keeps the same shape but mutes the
   colors and removes interactivity. */
.sf-support-tiles {
    display: grid;
    /* 2026-05-15 — 2x2 grid (was repeat(3,1fr)) to accommodate the
       fourth Contact us tile. Mobile breakpoint below still stacks
       to single column at ≤720px. */
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-16);
    margin: var(--space-24) 0 var(--space-32);
}
@media (max-width: 720px) {
    .sf-support-tiles { grid-template-columns: 1fr; }
}
.sf-support-tile {
    display: flex;
    flex-direction: column;
    gap: var(--space-8);
    padding: var(--space-20, 20px);
    border: 1px solid var(--border);
    border-radius: var(--radius-8);
    background: var(--bg-elevated);
    color: inherit;
    text-decoration: none;
    transition: border-color 120ms ease, transform 120ms ease, box-shadow 120ms ease;
}
.sf-support-tile:hover {
    border-color: var(--accent);
    transform: translateY(-2px);
    box-shadow: 0 4px 16px -8px var(--accent);
}
.sf-support-tile:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}
.sf-support-tile__icon {
    width: 36px; height: 36px;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: var(--radius-pill);
    background: var(--bg-sunken);
    color: var(--accent-fg);
    font-weight: var(--weight-semibold);
    font-size: var(--text-18, 18px);
}
.sf-support-tile__title {
    font-weight: var(--weight-semibold);
    font-size: var(--text-16);
}
.sf-support-tile__sub {
    color: var(--fg-muted);
    margin: 0;
    font-size: var(--text-14, 14px);
    flex: 1;
}
.sf-support-tile__cta {
    /* Session 21 — was --accent-500 (#21b878), accent-on-white is
       2.56 : 1 (axe FAIL on /support's "Read the FAQ →" /
       "Open a ticket →"). Use --accent-fg (#117b50 in light theme,
       #6ad8a3 in dark) which is the project's "accessible accent
       foreground" token — purpose-built for accent-colored text
       that has to clear WCAG AA. ~5.3 : 1 light, ~7.6 : 1 dark. */
    color: var(--accent-fg);
    font-weight: var(--weight-medium);
    font-size: var(--text-14, 14px);
    margin-top: var(--space-8);
}
.sf-support-tile__cta--muted { color: var(--fg-muted); }
.sf-support-tile--disabled {
    cursor: not-allowed;
    opacity: 0.75;
}
.sf-support-tile--disabled:hover {
    border-color: var(--border);
    transform: none;
    box-shadow: none;
}

.sf-fieldset {
    border: 1px solid var(--border);
    border-radius: var(--radius-8);
    padding: var(--space-16);
    margin-top: var(--space-16);
}
.sf-fieldset legend { font-weight: var(--weight-semibold); padding: 0 var(--space-8); }
.sf-fieldset[disabled] { opacity: 0.6; }
.sf-fieldset[disabled] .sf-input,
.sf-fieldset[disabled] .sf-btn { pointer-events: none; }

.sf-label { display: block; margin-top: var(--space-12); font-size: var(--text-13, 13px); font-weight: var(--weight-semibold); color: var(--fg); }
.sf-help  { font-size: var(--text-12); color: var(--fg-muted); margin: 4px 0 0; }
/* 2026-05-14 — v1-launch cross-batch theme #2. The .sf-help--error
   modifier was referenced in NotificationsPanel + SourceControlPanel
   but never defined; production rendered the error message in body
   color. Theme-aware via --danger-fg (defined for both light + dark
   per tokens.css). */
.sf-help--error { color: var(--danger-fg); }

.sf-checkbox-row { display: flex; align-items: center; gap: var(--space-8); margin-top: var(--space-12); cursor: pointer; }
.sf-checkbox-row input { margin: 0; }

/* 2026-05-14 — v1-launch cross-batch theme #2 — missing CSS primitives
   sweep. Each of the classes below was referenced in production markup
   but had no rule defined; production was rendering the markup as
   unstyled <div>/<span>/<input>, falling back to browser defaults that
   don't honour the SpecStep design tokens (and don't theme-react to
   dark mode). All values resolve through existing tokens.

   Reference: docs/design/reviews/v1-launch/E-settings/TRIAGE.md (C-E4
   sf-panel chrome, C-E6 token drift, H-E16 sf-radiogroup, H-E17
   sf-banner), F-admin-billing/TRIAGE.md (C-F5 sf-button family,
   C-F6 sf-stat-tile__label, C-F8 17 non-existent CSS-vars). The
   non-existent-CSS-var sweep (C-E6 + C-F8) lands in a sister PR
   (theme #2b) because it touches Razor consumers; this PR is CSS-
   only so no discipline-gate obligation. */

/* sf-panel — chrome wrapper used by 13 of 14 Settings panels +
   sister admin panels. */
.sf-panel { display: flex; flex-direction: column; }
.sf-panel + .sf-panel { margin-top: var(--space-32); }
.sf-panel__title {
    margin: 0;
    font-size: var(--text-20);
    font-weight: var(--weight-semibold);
    line-height: var(--leading-tight, 1.25);
    color: var(--fg);
}
.sf-panel__lede {
    margin: var(--space-4) 0 var(--space-24);
    color: var(--fg-muted);
    font-size: var(--text-14);
    max-width: 72ch;
}
.sf-panel__header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-16);
    margin-bottom: var(--space-4);
}

/* sf-banner — inline alert primitive distinct from sf-card. Thinner
   padding, no border-radius variation, left-edge accent stripe in
   the semantic color. Used by ConnectedMcpClientsPanel + (soon)
   ApiKeysPanel / WebhooksPanel / PackageComparatorPanel /
   EngineeringReleaseNotes / OAuth/Consent / LegalToSAccept etc.
   Four semantic variants. */
.sf-banner {
    display: flex;
    align-items: flex-start;
    gap: var(--space-12);
    padding: var(--space-12) var(--space-16);
    border-radius: var(--radius-6);
    border: 1px solid var(--border);
    border-left-width: 3px;
    background: var(--bg-elevated);
    color: var(--fg);
}
.sf-banner__title { font-weight: var(--weight-semibold); margin: 0; }
.sf-banner__body { margin: var(--space-4) 0 0; color: var(--fg-muted); font-size: var(--text-14); }
.sf-banner--info    { border-left-color: var(--info-fg);    background: var(--info-bg, var(--bg-elevated)); }
.sf-banner--success { border-left-color: var(--success-fg); background: var(--success-bg, var(--bg-elevated)); }
.sf-banner--warn    { border-left-color: var(--warning-fg); background: var(--warning-bg, var(--bg-elevated)); }
.sf-banner--danger,
.sf-banner--error   { border-left-color: var(--danger-fg);  background: var(--danger-bg, var(--bg-elevated)); }

/* sf-stack — vertical flex with gap. Replaces ~30 inline
   style="display: flex; flex-direction: column; gap: var(--space-N)"
   sites across Settings + Admin panels. Three size variants. */
.sf-stack { display: flex; flex-direction: column; gap: var(--space-8); }
.sf-stack--sm { gap: var(--space-4); }
.sf-stack--md { gap: var(--space-12); }
.sf-stack--lg { gap: var(--space-16); }
.sf-stack--row { flex-direction: row; align-items: center; }

/* sf-radiogroup + sf-radio — card-style radio set used by
   RetentionPanel + LlmProviders. Earlier markup referenced
   sf-radiogroup / sf-radio / __title / __sub but no rule was
   defined; production rendered browser-default radios (unreadable
   on macOS dark mode). */
.sf-radiogroup {
    display: flex;
    flex-direction: column;
    gap: var(--space-8);
    border: 0;
    padding: 0;
    margin: 0;
}
.sf-radiogroup__legend {
    font-size: var(--text-14);
    font-weight: var(--weight-semibold);
    color: var(--fg);
    margin-bottom: var(--space-8);
    padding: 0;
}
.sf-radio {
    display: flex;
    align-items: flex-start;
    gap: var(--space-12);
    padding: var(--space-12) var(--space-16);
    border: 1px solid var(--border);
    border-radius: var(--radius-8);
    background: var(--bg-elevated);
    cursor: pointer;
    transition: border-color 120ms ease, background-color 120ms ease;
}
.sf-radio:hover { border-color: var(--accent); }
.sf-radio:has(input:checked) {
    border-color: var(--accent);
    background: color-mix(in srgb, var(--accent) 8%, var(--bg-elevated));
}
.sf-radio:has(input:focus-visible) {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}
.sf-radio input[type="radio"] {
    margin: 2px 0 0;
    flex-shrink: 0;
    accent-color: var(--accent-700, var(--accent));
}
.sf-radio__title {
    display: block;
    font-weight: var(--weight-semibold);
    color: var(--fg);
    margin: 0;
}
.sf-radio__sub {
    display: block;
    color: var(--fg-muted);
    font-size: var(--text-13, 13px);
    margin: var(--space-4) 0 0;
}

/* sf-checkbox — standalone checkbox primitive distinct from sf-
   checkbox-row (which is the inline-flex label-wrapping variant).
   Used by BillingPanel + WebhooksPanel. */
.sf-checkbox {
    display: inline-flex;
    align-items: center;
    gap: var(--space-8);
    cursor: pointer;
    color: var(--fg);
}
.sf-checkbox input[type="checkbox"] {
    margin: 0;
    accent-color: var(--accent-700, var(--accent));
}
.sf-checkbox:has(input[disabled]) { opacity: 0.6; cursor: not-allowed; }

.sf-subsection { margin-top: var(--space-24); }
.sf-subsection__title { font-size: var(--text-16); font-weight: var(--weight-semibold); margin: 0 0 4px; }
.sf-subsection__sub   { color: var(--fg-muted); font-size: var(--text-13, 13px); margin: 0 0 var(--space-12); }

/* PR-T36 (2026-05-07) — Undo toast for soft-delete operations.
   Bottom-left corner, slides in from the side, auto-dismisses after
   10s. Single instance lives in MainLayout; activates when a delete
   navigates with ?undo=...&undoId=... query params. */
.sf-undo-toast {
    position: fixed;
    left: var(--space-24, 24px);
    bottom: var(--space-24, 24px);
    z-index: 1100;
    display: inline-flex;
    align-items: center;
    gap: var(--space-12, 12px);
    padding: 12px 16px;
    background: var(--bg-elevated, #1f2937);
    color: var(--fg-on-dark, #f9fafb);
    border-radius: var(--radius-8, 8px);
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18);
    font-size: var(--text-13, 13px);
    animation: sf-undo-toast-in 0.2s ease-out;
}
.sf-undo-toast__msg { flex: 1; }
.sf-undo-toast__undo {
    background: none;
    border: 1px solid currentColor;
    color: inherit;
    font: inherit;
    font-weight: var(--weight-semibold, 600);
    padding: 4px 12px;
    border-radius: var(--radius-4, 4px);
    cursor: pointer;
}
.sf-undo-toast__undo:hover { background: rgba(255, 255, 255, 0.08); }
.sf-undo-toast__undo:disabled { opacity: 0.5; cursor: wait; }
.sf-undo-toast__dismiss {
    background: none;
    border: none;
    color: inherit;
    font-size: 18px;
    line-height: 1;
    padding: 4px 8px;
    cursor: pointer;
    opacity: 0.6;
}
.sf-undo-toast__dismiss:hover { opacity: 1; }
@keyframes sf-undo-toast-in {
    from { transform: translateY(8px); opacity: 0; }
    to { transform: translateY(0); opacity: 1; }
}

/* TASK-159 (2026-05-08) — Live notification toast.
   Top-right corner, slides in from the side, auto-dismisses after 8s.
   Single instance lives in MainLayout; activates when SignalR pushes
   a NotificationCreated event. Distinct corner from sf-undo-toast
   (bottom-left) so the two surfaces never overlap if a soft-delete +
   a notification-arrival fire in the same window. */
.sf-live-toast {
    position: fixed;
    right: var(--space-24, 24px);
    top: var(--space-24, 24px);
    z-index: 1100;
    display: inline-flex;
    align-items: center;
    gap: var(--space-12, 12px);
    padding: 12px 16px;
    background: var(--bg-elevated, #1f2937);
    color: var(--fg-on-dark, #f9fafb);
    border-radius: var(--radius-8, 8px);
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18);
    font-size: var(--text-13, 13px);
    max-width: 420px;
    animation: sf-live-toast-in 0.2s ease-out;
}
.sf-live-toast__icon {
    font-size: 18px;
    line-height: 1;
    opacity: 0.85;
    flex-shrink: 0;
}
.sf-live-toast__body { flex: 1; min-width: 0; }
.sf-live-toast__headline {
    font-weight: var(--weight-semibold, 600);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.sf-live-toast__open {
    background: none;
    border: 1px solid currentColor;
    color: inherit;
    font: inherit;
    font-weight: var(--weight-semibold, 600);
    padding: 4px 12px;
    border-radius: var(--radius-4, 4px);
    cursor: pointer;
}
.sf-live-toast__open:hover { background: rgba(255, 255, 255, 0.08); }
.sf-live-toast__dismiss {
    background: none;
    border: none;
    color: inherit;
    font-size: 18px;
    line-height: 1;
    padding: 4px 8px;
    cursor: pointer;
    opacity: 0.6;
}
.sf-live-toast__dismiss:hover { opacity: 1; }
/* 2026-05-09 — kind-tinted backgrounds. The neutral elevated bg is
   the default; the modifiers below shift the background to a low-
   saturation green (success) or red (failed) so the user can tell
   the kind at a glance without reading the icon. Token-based, so
   light + dark themes pick up the right hues automatically. */
.sf-live-toast--success {
    background: var(--success-bg, color-mix(in srgb, #16a34a 18%, var(--bg-elevated, #1f2937)));
    border-left: 3px solid var(--success-fg, #16a34a);
}
.sf-live-toast--failed {
    background: var(--danger-bg, color-mix(in srgb, #c0392b 18%, var(--bg-elevated, #1f2937)));
    border-left: 3px solid var(--danger-fg, #c0392b);
}
@keyframes sf-live-toast-in {
    from { transform: translateX(8px); opacity: 0; }
    to { transform: translateX(0); opacity: 1; }
}

/* H-E12 (2026-05-14) — global toast surface. Bottom-right corner so
   the three concurrent toast surfaces (undo bottom-left, live-
   notification top-right, generic bottom-right) never overlap.
   sf-toast-host stacks multiple toasts vertically with the newest
   on the bottom so an in-flight stack reads top-down by age. */
.sf-toast-host {
    position: fixed;
    right: var(--space-24, 24px);
    bottom: var(--space-24, 24px);
    z-index: 1100;
    display: flex;
    flex-direction: column;
    gap: var(--space-8, 8px);
    max-width: 420px;
    pointer-events: none;
}
.sf-toast {
    pointer-events: auto;
    display: inline-flex;
    align-items: center;
    gap: var(--space-12, 12px);
    padding: 12px 16px;
    background: var(--bg-elevated, #1f2937);
    color: var(--fg-on-dark, #f9fafb);
    border-radius: var(--radius-8, 8px);
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18);
    font-size: var(--text-13, 13px);
    border-left: 3px solid var(--fg-on-dark, #f9fafb);
    animation: sf-toast-in 0.2s ease-out;
}
.sf-toast__text { flex: 1; min-width: 0; }
.sf-toast__dismiss {
    background: none;
    border: none;
    color: inherit;
    font-size: 18px;
    line-height: 1;
    padding: 4px 8px;
    cursor: pointer;
    opacity: 0.6;
}
.sf-toast__dismiss:hover { opacity: 1; }
.sf-toast--info {
    background: var(--info-bg, color-mix(in srgb, #2563eb 14%, var(--bg-elevated, #1f2937)));
    border-left-color: var(--info-fg, #2563eb);
}
.sf-toast--success {
    background: var(--success-bg, color-mix(in srgb, #16a34a 18%, var(--bg-elevated, #1f2937)));
    border-left-color: var(--success-fg, #16a34a);
}
.sf-toast--warning {
    background: var(--warning-bg, color-mix(in srgb, #d97706 18%, var(--bg-elevated, #1f2937)));
    border-left-color: var(--warning-fg, #d97706);
}
.sf-toast--error {
    background: var(--danger-bg, color-mix(in srgb, #c0392b 18%, var(--bg-elevated, #1f2937)));
    border-left-color: var(--danger-fg, #c0392b);
}
@keyframes sf-toast-in {
    from { transform: translateY(8px); opacity: 0; }
    to { transform: translateY(0); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
    .sf-toast { animation: none; }
}

/* H-E1 (2026-05-14) — SaveBar primitive. Sticky bottom-of-form bar
   that surfaces while a form has unsaved changes; dismisses on
   save success or discard. Distinct from sf-toast-host (ephemeral
   post-action notification) — SaveBar lives DURING the edit,
   toast lives AFTER.

   position: sticky scopes the pin to the form's scroll container,
   so multi-form panels (e.g. ProfilePanel with separate profile +
   SMS forms) can each render their own SaveBar without competing
   for a single fixed-position slot. */
.sf-save-bar {
    position: sticky;
    bottom: 0;
    z-index: 50;
    display: flex;
    align-items: center;
    gap: var(--space-16, 16px);
    margin-top: var(--space-16, 16px);
    padding: 12px 16px;
    background: var(--bg-elevated, #1f2937);
    color: var(--fg-on-dark, #f9fafb);
    border-radius: var(--radius-8, 8px);
    box-shadow: 0 -6px 16px rgba(0, 0, 0, 0.12);
    border-left: 3px solid var(--info-fg, #2563eb);
    animation: sf-save-bar-in 0.18s ease-out;
}
.sf-save-bar__message {
    flex: 1;
    font-size: var(--text-13, 13px);
    font-weight: var(--weight-semibold, 600);
}
.sf-save-bar__actions {
    display: inline-flex;
    align-items: center;
    gap: var(--space-8, 8px);
    flex-shrink: 0;
}
.sf-save-bar__discard,
.sf-save-bar__save {
    /* Inherit the existing .sf-btn shape; SaveBar variants only
       constrain layout — no per-button color overrides. */
}
@keyframes sf-save-bar-in {
    from { transform: translateY(6px); opacity: 0; }
    to { transform: translateY(0); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
    .sf-save-bar { animation: none; }
}

/* PR-T36 (2026-05-07) — Recycle Bin panel chrome (admin + user share
   the same component). Three-tab strip + table; quiet styling so the
   focus is the row content, not the chrome.
   2026-05-14 — v1-launch Batch E C-E5. The `__head/__title/__subtitle`
   trio was retired in favor of the shared `sf-panel__header/title/lede`
   chrome that every other /settings/* panel uses. The internal sub-
   tabs (`sf-recycle__tabs`, `sf-recycle__tab`) stay — they're a
   legitimate primitive the shared sf-panel doesn't model. */
.sf-recycle__tabs {
    display: flex;
    gap: var(--space-8, 8px);
    border-bottom: 1px solid var(--border, #e5e7eb);
    margin-bottom: var(--space-16, 16px);
}
.sf-recycle__tab {
    background: none;
    border: none;
    padding: 10px 14px;
    font: inherit;
    color: var(--fg-subtle);
    cursor: pointer;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
}
.sf-recycle__tab:hover { color: var(--fg, inherit); }
.sf-recycle__tab--active {
    color: var(--fg, inherit);
    border-bottom-color: var(--fg);
    font-weight: var(--weight-semibold, 600);
}
.sf-recycle__table tbody td { vertical-align: middle; }
/* H-E4 (2026-05-14) — Delete-forever affordance carries the
   danger semantic via subdued text + hover treatment, not via a
   --danger button variant (that would compete visually with the
   adjacent Restore for the same primary action slot). The
   Confirm step uses the existing sf-btn--primary so the
   destructive commit is a deliberate single click. */
.sf-recycle__delete-forever {
    color: var(--danger-fg, #c0392b);
}
.sf-recycle__delete-forever:hover:not(:disabled) {
    color: var(--danger-fg, #c0392b);
    background: color-mix(in srgb, var(--danger-fg, #c0392b) 8%, transparent);
}

.sf-table { width: 100%; border-collapse: collapse; font-size: var(--text-13, 13px); }
.sf-table th, .sf-table td {
    padding: var(--space-8); border-bottom: 1px solid var(--border); text-align: left;
}
.sf-table th { font-size: var(--text-12); color: var(--fg-subtle); font-weight: var(--weight-semibold); letter-spacing: 0.04em; }
/* 2026-06-01 — tables Wave 5 coherence. Column headers (thead th) read
   mono-uppercase across EVERY sf-table, matching both the framed admin
   grids (.sf-admin-table-frame .sf-table th, ~8834) and the canonical
   controls.css `.tbl thead th` spec — so the SfDataTable grids and the
   bare detail/matrix/analytics tables share one header voice. Scoped to
   `thead th` so row headers (tbody th[scope=row] — agent names, tier
   labels, file paths) keep their natural casing. */
.sf-table thead th { text-transform: uppercase; letter-spacing: 0.06em; }
.sf-table__num { text-align: right; font-variant-numeric: tabular-nums; }

/* TASK-222 (2026-05-16) — clickable row affordance. The four admin
   queue pages (Feedback, BugReports, AdminInterviewsList,
   AdminGenerationsList) emit this modifier on rows that wire an
   @onclick row-navigation handler. Without these rules the rows
   render identically to non-clickable rows and the user has no
   signal the row is interactive — which is exactly the bug the
   user surfaced on 2026-05-16. */
.sf-table__row--clickable { cursor: pointer; }
.sf-table__row--clickable:hover { background: var(--bg-subtle); }
.sf-table__row--clickable:focus-within { outline: 2px solid var(--accent-strong); outline-offset: -2px; }

/* ─────────────────────────────────────────────────────────────────
   Settings-style pages — /settings, /admin, /billing
   2026-05-02 — lifted from the design review at
   docs/design/reviews/2026-05-02-settings-redesign/ (review
   finding C1: the sf-settings__* classes were referenced by
   Settings.razor but had no matching rules; the page rendered with
   browser defaults. The new SettingsTabs shared component is the
   only place these classes are emitted.)
   ───────────────────────────────────────────────────────────── */
.sf-settings { display: block; }
/* 2026-05-03 — sub-nav moved from the LEFT column to the RIGHT
   column. The user reported that the prior left-side placement
   created a "double menu" feeling against the primary rail (also
   on the left: Workspace, Interview, Settings, Admin, Billing).
   Right-side placement separates "what app section am I in" (left
   primary rail) from "what view within this section" (right
   sub-nav). Markup order in SettingsTabs.razor matches: panel
   first, nav second — so screen readers and keyboard navigation
   land on the content first and reach the sub-nav after. */
.sf-settings__layout {
    display: grid;
    grid-template-columns: 1fr 220px;
    gap: var(--space-32);
    align-items: start;
}
.sf-settings__tabs {
    display: flex; flex-direction: column; gap: var(--space-16);
    position: sticky; top: var(--space-24);
}
.sf-settings__group { display: flex; flex-direction: column; gap: var(--space-2); }
.sf-settings__group-heading {
    font-size: var(--text-12); color: var(--fg-subtle);
    letter-spacing: var(--track-wide, 0.04em);
    font-weight: var(--weight-semibold);
    text-transform: uppercase;
    padding: 0 var(--space-12);
    margin: 0 0 var(--space-4);
}
.sf-settings__tab {
    display: flex; align-items: center; gap: var(--space-8);
    padding: var(--space-8) var(--space-12);
    background: transparent; border: 0;
    color: var(--fg); text-align: left; cursor: pointer;
    border-radius: var(--radius-6);
    font-family: inherit; font-size: var(--text-14);
    position: relative;
    transition: background var(--motion-duration-fast) var(--motion-easing-out),
                color var(--motion-duration-fast) var(--motion-easing-out);
}
.sf-settings__tab:hover { background: var(--bg-sunken); }
.sf-settings__tab[aria-selected="true"] {
    color: var(--accent-fg);
    font-weight: var(--weight-semibold);
    background: var(--bg-sunken);
}
.sf-settings__tab[aria-selected="true"]::before {
    content: "";
    position: absolute; left: 0; top: 6px; bottom: 6px;
    width: 3px; border-radius: 2px; background: var(--accent);
}
.sf-settings__tab:focus-visible {
    outline: 2px solid var(--accent); outline-offset: 2px;
}
.sf-settings__tab--danger { color: var(--danger); }
.sf-settings__tab--danger[aria-selected="true"] { color: var(--danger); }
.sf-settings__tab--danger[aria-selected="true"]::before { background: var(--danger); }

/* 2026-05-04 — routable-section refactor (PR #21+). The new
   AdminLayout / SettingsLayout / BillingLayout render each tab as
   a NavLink anchor instead of a button, so the active state
   arrives as a CSS class (sf-settings__tab--active) rather than
   the aria-selected attribute the original button-based
   SettingsTabs uses. Mirror the visual treatment so both forms
   render identically — anchors also need text-decoration: none
   (the existing button-based rule didn't need it). */
a.sf-settings__tab { text-decoration: none; }
.sf-settings__tab--active {
    color: var(--accent-fg);
    font-weight: var(--weight-semibold);
    background: var(--bg-sunken);
}
.sf-settings__tab--active::before {
    content: "";
    position: absolute; left: 0; top: 6px; bottom: 6px;
    width: 3px; border-radius: 2px; background: var(--accent);
}
.sf-settings__tab--danger.sf-settings__tab--active { color: var(--danger); }
.sf-settings__tab--danger.sf-settings__tab--active::before { background: var(--danger); }
.sf-settings__tab--placeholder {
    color: var(--fg-subtle);
    cursor: not-allowed;
    font-style: italic;
}
.sf-settings__tab--placeholder:hover { background: transparent; }
.sf-settings__tab-aside {
    margin-left: auto;
    font-size: var(--text-12); color: var(--fg-subtle);
    font-style: normal;
}
.sf-settings__panel {
    max-width: 920px;
    display: flex; flex-direction: column; gap: var(--space-16);
    min-width: 0; /* allow flex children with overflow to shrink */
}

/* Empty-state card (review finding S3) — used by /admin and
   /billing when the user has no permission for any tab on the page,
   and reusable anywhere a "nothing here yet" card is needed. */
.sf-empty {
    /* Web Controls v1 (2026-05-30) — canonical .empty: card radius (6, not 12)
       + recessed paper bg (sunken, not elevated), dashed border kept. */
    border: 1px dashed var(--border-strong);
    border-radius: var(--radius-6);
    padding: var(--space-48) var(--space-32);
    text-align: center; color: var(--fg-muted);
    background: var(--bg-sunken);
    display: flex; flex-direction: column; gap: var(--space-12); align-items: center;
}
.sf-empty__title { color: var(--fg); font-size: var(--text-20); font-weight: var(--weight-semibold); margin: 0; }
.sf-empty__body  { max-width: 56ch; margin: 0; font-size: var(--text-14); color: var(--fg-muted); }
.sf-empty__cta   { display: flex; gap: var(--space-8); margin-top: var(--space-12); }

/* H-E14 (2026-05-15) — in-table variant. Strips the dashed
   border-card chrome so the empty state renders inline inside a
   .sf-table cell or compact panel without competing with the
   table's own gridlines. Pairs with .sf-empty: same selector
   semantics + same role="status" / role="alert" usage; just less
   visual weight. */
.sf-empty--inset {
    border: 0;
    background: transparent;
    padding: var(--space-32) var(--space-16);
    border-radius: 0;
}

/* Mobile fallback — vertical tabs collapse below 768px so the
   sticky 220px column doesn't crowd the panel on a phone. */
@media (max-width: 768px) {
    .sf-settings__layout { grid-template-columns: 1fr; gap: var(--space-16); }
    .sf-settings__tabs { position: static; }
    .sf-settings__group-heading { padding: 0; }
}

/* Phase 7 — "Coming soon" banner shown above the three-pane shell while
   the UI is being wired to the backend. Remove this rule + the banner
   element in MainLayout.razor when Phase 7 ships. */
.sf-coming-soon-banner {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1000;
  padding: 0.5rem 1rem;
  background: var(--accent-default, #ff6a00);
  color: #ffffff;
  font: 600 0.875rem/1.2 system-ui, -apple-system, sans-serif;
  text-align: center;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.1);
}

/* Push the shell down by the banner height so nothing's hidden under it. */
.sf-coming-soon-banner + .sf-shell {
  padding-top: 2.25rem;
}

/* Phase 7 Session 1 — empty-state CTA used when the user has no
   generations yet. Shows a friendly hero panel with a primary button. */
.sf-empty--cta {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 1rem;
  padding: 3rem 2rem;
  /* Session 23 — was `var(--color-border-subtle, #e5e7eb)` /
     `var(--color-surface-1, #ffffff)` / `var(--color-text-secondary,
     #6b7280)`. Those token names don't exist in this project (the
     design system is `--bg-elevated` / `--border` / `--fg-muted`),
     so the fallback hex values fired and produced a hardcoded
     light-theme card with #6b7280 muted text — `<strong>Welcome to
     SpecStep.</strong>` rendered as #a3a8b2 (inheriting --fg-muted)
     on hardcoded #ffffff in dark theme = 2.38 : 1, fails AA. Swap
     to the project's actual tokens so the card adapts to whichever
     theme is active. */
  border: 1px solid var(--border);
  border-radius: 0.75rem;
  background: var(--bg-elevated);
}
.sf-empty--cta strong { font-size: 1.25rem; font-weight: 600; }
.sf-empty--cta p { max-width: 32rem; color: var(--fg-muted); }

/* Workspace review C2 (2026-05-02) — first-run onboarding empty
   state. Three numbered steps that teach the actual flow + show
   real cost / time estimates so the user knows what to expect
   before they click. Replaces the prior "Welcome to SpecStep ✨"
   greeting (anti-slop checklist). */
.sf-workspace-onboard {
    max-width: 640px;
    margin: 0 auto;
    text-align: left;
}
.sf-workspace-onboard .sf-empty__title {
    text-align: center;
}
.sf-workspace-onboard__steps {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-16);
    width: 100%;
}
.sf-workspace-onboard__steps li {
    display: grid;
    grid-template-columns: 32px 1fr;
    gap: var(--space-12);
    align-items: start;
}
.sf-workspace-onboard__num {
    width: 28px; height: 28px;
    border-radius: var(--radius-pill);
    background: var(--bg-sunken);
    color: var(--accent-fg);
    display: inline-flex;
    align-items: center; justify-content: center;
    font-size: var(--text-13);
    font-weight: var(--weight-semibold);
    font-variant-numeric: tabular-nums;
}
.sf-workspace-onboard__steps li strong { display: block; font-size: var(--text-14); font-weight: var(--weight-semibold); }
.sf-workspace-onboard__steps li .sf-cell__sub { display: block; margin-top: 2px; }
.sf-workspace-onboard__cta {
    margin-top: var(--space-24);
    display: flex; gap: var(--space-8); justify-content: center;
}

/* ---------------------------------------------------------------------
   Phase 8 Session A — Marketing layout (Landing / Privacy / Terms /
   Pricing when anonymous). Slim header + hero/sections + footer.
   No left rail, no workspace shell.
   --------------------------------------------------------------------- */

/* ---------------------------------------------------------------------
   Marketing surface — PR-L1 (2026-05-05) + dark-mode + cohesion
   sweep PR-T37 (2026-05-08).

   Was: hand-rolled paper/ink palette, light-only, with explicit
   re-pin of in-app tokens to the cream constants. That made the
   surface visually disjoint from the app AND broke dark-mode
   parity (toggle did nothing visible on marketing pages — only
   FluentUI primitives flipped).

   Now: the marketing-side palette aliases the app's semantic
   tokens (--bg / --fg / --bg-elevated etc. from tokens.css). The
   sectioned .razor.css files keep referencing var(--paper) /
   var(--card) / var(--ink) — they don't change — but those vars
   resolve to the app's tokens which already flip at
   [data-theme="dark"]. Net result: marketing renders cool-neutral
   in light mode (matches app) and cool-dark in dark mode (matches
   app). The editorial Georgia headlines + cyan section accent
   stay; only the surface palette converges with the app.
   --------------------------------------------------------------------- */
.sf-marketing {
    /* Marketing palette aliases — section .razor.css files
       reference these names; resolution flows to the app tokens
       which honor [data-theme="dark"] in tokens.css. */
    --paper:    var(--bg);
    --paper-2:  var(--bg-sunken);
    --card:     var(--bg-elevated);
    --ink:      var(--fg);
    --ink-2:    var(--fg);
    --muted:    var(--fg-subtle);
    --muted-2:  var(--fg-muted);
    --rule:     var(--border);
    --otto-accent: var(--agent-otto, #7BD0E5);

    /* 2026-05-14 — v1-launch Batch A C-A3. Marketing typography
       tokens. The 8 Marketing/Sections/*.razor.css files previously
       hardcoded "Helvetica Neue", "Inter" (sans) and "JetBrains
       Mono", "SF Mono", Menlo (mono) — a parallel font system that
       (a) used Helvetica everywhere, contradicting the platform's
       Segoe UI Variable (light) / Inter (dark) stack defined in
       tokens.css, (b) used JetBrains Mono for code chips while the
       same code chips inside /workspace use Cascadia Code, and
       (c) couldn't flip light/dark fonts at all because the values
       were literal strings, not tokens.

       These three tokens collapse the parallel system back into
       the platform contract:
         • --marketing-font-display — editorial Georgia, the
           intentional marketing-only voice; stays a literal stack
           because tokens.css doesn't define a serif family.
         • --marketing-font-ui — UI / nav / eyebrow / captions /
           pills; aliases the app's --font-sans so light-mode
           renders Segoe UI Variable and dark-mode renders Inter
           (per tokens.css :root vs [data-theme="dark"]).
         • --marketing-font-mono — inline code chips; aliases the
           app's --font-mono (Cascadia Code) so a code chip on /
           matches the same chip on /workspace. */
    --marketing-font-display: Georgia, "Times New Roman", serif;
    --marketing-font-ui: var(--font-sans);
    --marketing-font-mono: var(--font-mono);
    /* TASK-161 (2026-05-08) — re-introduce the darker on-light
       companion. PR-T37 collapsed this back to the base cyan with
       the claim "agent-otto cyan reads at >= 4.5:1 against both
       --bg values"; that claim was wrong. --bg is #fafafa in
       light mode, --bg-sunken is #f3f4f6, --bg-elevated is #ffffff.
       Base #7BD0E5 against any of these reads ~1.5:1 (axe-core
       FAIL on PitchSection .num, MeetTheTeam .num, HowItWorks
       header). #1A6B7E reads >= 6:1 on every light-mode surface
       so the 3:1 large-text gate (and even the 4.5:1 normal-text
       gate) clears. Dark-mode override below keeps the bright
       cyan since contrast on the dark surfaces is fine. */
    --otto-accent-on-light: #1A6B7E;

    min-height: 100vh;
    display: flex;
    flex-direction: column;
    background: var(--paper);
    color: var(--ink);
    font-family: var(--marketing-font-display);
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;

    /* PR-T37 (2026-05-08) — the in-app token re-pin block was
       removed. With --paper/--ink now aliasing --bg/--fg, the
       previous re-pin (--bg: var(--paper)) would create a
       circular reference. The app's tokens flow through naturally
       and respect [data-theme="dark"] from tokens.css. */
}
/* Heading typography — Georgia, near-zero weight + tight tracking.
   The bundle uses clamp() for fluid sizing across viewports. */
.sf-marketing h1, .sf-marketing h2, .sf-marketing h3, .sf-marketing h4 {
    font-family: var(--marketing-font-display);
    font-weight: 400;
    letter-spacing: -0.02em;
    margin: 0;
}
/* PR-T33 marketing fix lives below as the historical comment.
   PR-T42 (2026-05-08) — the same Blazor FocusOnNavigate behavior
   was producing the same outline ring on signed-in app pages
   (Workspace h1, Settings h1, etc.). User reported: "on the
   workspace page when you load it, it auto selects and puts a
   border around 'Workspace' at the top." The marketing-scoped
   rule didn't catch the app shells. Promoted to a global rule
   that targets every h1/h2/h3 regardless of namespace — the rule
   is safe because structural headings aren't interactive (nothing
   happens when you tab to them), so suppressing the outline has
   no functional cost. Screen readers still announce the focused
   heading; the visual indicator is the only thing removed. */
h1:focus, h2:focus, h3:focus,
h1:focus-visible, h2:focus-visible, h3:focus-visible { outline: none; }
.sf-marketing h1 { font-size: clamp(40px, 6vw, 72px); line-height: 1.05; }
.sf-marketing h2 { font-size: clamp(32px, 4vw, 48px); line-height: 1.08; }
.sf-marketing h3 { font-size: clamp(20px, 2.2vw, 26px); line-height: 1.2; }
.sf-marketing p  { margin: 0; text-wrap: pretty; }

/* Sans-serif utility (.sans) for nav, eyebrow, captions, buttons.
   Mono utility for inline code in feature cards. Both route through
   the marketing font tokens (which alias the platform's sans/mono
   stacks) per v1-launch Batch A C-A3. */
.sf-marketing .sans { font-family: var(--marketing-font-ui); }
.sf-marketing .mono { font-family: var(--marketing-font-mono); }

/* Eyebrow — small uppercase tag above section headings. */
.sf-marketing .eyebrow {
    font-family: var(--marketing-font-ui);
    font-size: 11px;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--muted);
    font-weight: 600;
}

/* Section container — caps width + horizontal padding. */
.sf-marketing .container { max-width: 1200px; margin: 0 auto; padding: 0 32px; }

/* Buttons — PR-T37 (2026-05-08) cohesion sweep: same shape +
   accent palette as the app's sf-btn primitives, but slightly
   more generous padding to suit the editorial section layouts.
   Was: pill-radius 999px, ink-on-paper. Now: --radius-6 + accent
   green so the marketing CTA visually continues into the app. */
.sf-marketing .btn {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    padding: 12px 20px;
    border-radius: var(--radius-6, 6px);
    font-family: var(--marketing-font-ui);
    font-size: 14px;
    font-weight: 600;
    letter-spacing: 0.02em;
    border: 1px solid transparent;
    cursor: pointer;
    transition: transform .2s, background .2s, border-color .2s, color .2s;
    text-decoration: none;
}
.sf-marketing .btn-primary {
    /* TASK-161 (2026-05-08) — was --accent-500 (#21b878), white-on-
       which is 2.56:1 (axe-core FAIL — required 4.5:1). Bumped to
       --accent-700 (#117b50, ~5.3:1) per the established project
       convention also used by .sf-persona__monogram (Session 21)
       and .sf-btn--primary (Session 20). Hover stays one step
       lighter (--accent-600) for the visual lift. */
    background: var(--accent-700, #117b50);
    color: #fff;
    border-color: var(--accent-700, #117b50);
}
.sf-marketing .btn-primary:hover {
    transform: translateY(-1px);
    background: var(--accent-600, #169a63);
    border-color: var(--accent-600, #169a63);
}
.sf-marketing .btn-ghost {
    background: transparent;
    color: var(--ink);
    border-color: var(--rule);
}
.sf-marketing .btn-ghost:hover {
    border-color: var(--ink);
    color: var(--ink);
}

/* Header — sticky lockup nav, paper-tone w/ blur. */
.sf-marketing__header {
    position: sticky; top: 0; z-index: 50;
    display: flex;
    align-items: center;
    gap: var(--space-16);
    padding: 18px 32px;
    background: color-mix(in oklab, var(--paper) 88%, transparent);
    backdrop-filter: blur(8px);
    border-bottom: 1px solid var(--rule);
    /* 2026-05-16 — explicit UI-font on the header. .sf-marketing
       sets font-family to --marketing-font-display (Georgia) for
       the editorial body sections, but the chrome (header + footer)
       should use the same sans-serif as the rest of the platform
       (auth-shell topbar uses --font-sans via global body). Without
       this rule, the persona name + dropdown text inherited Georgia
       on landing while rendering Segoe UI / Inter on auth pages —
       a visible inconsistency the user flagged. */
    font-family: var(--marketing-font-ui);
}
.sf-marketing__brand {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    text-decoration: none;
    color: var(--ink);
    line-height: 0;
}
.sf-marketing__brand-lockup { height: 36px; width: auto; display: block; }
/* PR-T37 (2026-05-08) — dark-mode lockup. 2026-05-31: the old
   invert() filter on a light-ink SVG is replaced by the designer's
   paper-ink -reversed asset. MarketingLayout renders two <img>s
   (--light / --dark); we toggle them on [data-theme], same shape as
   .sf-topbar__lockup for cross-shell consistency. */
.sf-marketing__brand-lockup--dark { display: none; }
:root[data-theme="dark"] .sf-marketing__brand-lockup--light { display: none; }
:root[data-theme="dark"] .sf-marketing__brand-lockup--dark { display: block; }

/* TASK-161 (2026-05-08) — restore the bright cyan as the on-light
   companion in dark mode. The light-mode default sets
   --otto-accent-on-light to #1A6B7E for legibility on light
   surfaces (#fafafa / #f3f4f6 / #ffffff). On dark surfaces
   (#0f1115 / #0a0c10 / #161a21) the base bright cyan #7BD0E5
   already clears the contrast gate, so re-alias the on-light
   variant back to the base in dark mode. */
:root[data-theme="dark"] .sf-marketing { --otto-accent-on-light: var(--otto-accent); }
/* Legacy classes retained for back-compat with non-redesigned pages
   that may render before PR-L2 lands. Hidden when the lockup variant
   above is present in the same brand link. */
.sf-marketing__mark { width: 24px; height: 24px; color: var(--ink); flex: none; }
.sf-marketing__product { font-size: var(--text-16); }
.sf-marketing__brand:has(.sf-marketing__brand-lockup) .sf-marketing__mark,
.sf-marketing__brand:has(.sf-marketing__brand-lockup) .sf-marketing__product { display: none; }

.sf-marketing__nav {
    display: flex;
    gap: 28px;
    margin-left: auto;
    font-family: var(--marketing-font-ui);
    font-size: 13px;
}
.sf-marketing__navlink {
    color: var(--muted-2);
    text-decoration: none;
    font-weight: 500;
}
.sf-marketing__navlink:hover { color: var(--ink); }
/* CTA variant — pill-shaped ghost button on the right edge of the
   nav row. Replaces the prior border-rectangle treatment so the
   conversion endpoint matches the bundle's hero CTAs. */
.sf-marketing__navlink--cta {
    padding: 8px 16px;
    border: 1px solid var(--rule);
    border-radius: 999px;
    color: var(--ink);
    transition: border-color var(--motion-duration-fast) var(--motion-easing-out),
                background var(--motion-duration-fast) var(--motion-easing-out);
}
.sf-marketing__navlink--cta:hover {
    border-color: var(--ink);
    background: var(--card);
}
@media (max-width: 780px) {
    .sf-marketing__nav { display: none; }
}

/* 2026-05-16 — preview chip in the marketing header. Mirrors
   .sf-topbar__chips (cross-shell honesty signal: SpecStep is in
   preview, anon visitors on / deserve to see that as much as
   signed-in users do). The chip flows immediately after the brand
   lockup so it reads as a continuation of the product identity,
   then `margin-left: auto` on .sf-marketing__nav (above) pushes the
   nav to the right edge as today. */
.sf-marketing__chips { display: flex; gap: var(--space-4); align-items: center; }
/* Hide the preview chip on the narrowest viewports — the chip is a
   transparency signal, not load-bearing, and at ≤480px every horizontal
   pixel matters. Stays visible everywhere else (including the 780px
   mobile-drawer breakpoint where the nav itself collapses). */
@media (max-width: 480px) {
    .sf-marketing__chips { display: none; }
}

/* 2026-05-16 — persona menu in the marketing header. The base
   .sf-persona-menu styles (defined globally below) handle position,
   dropdown, hover-open behavior. The marketing surface only needs a
   small layout hook so the menu sits between the theme toggle and
   the (now-absent) sign-in pill cleanly; the global menu styles
   already provide the inline-flex / gap layout. */
.sf-marketing__persona { display: inline-flex; align-items: center; }

/* ============================================================
   2026-05-29 (marketing search Session 2) — top-menu-bar site
   search box (SiteSearch.razor). Rendered in BOTH shells
   (.sf-marketing__nav + .sf-topbar__nav) + the mobile drawer, so
   it uses GLOBAL tokens only (the --ink/--rule/--card aliases are
   scoped to .sf-marketing and wouldn't resolve in the app shell).
   Panel styles live here (global), NOT in isolated .razor.css, so
   the server-sanitized <mark> inside the snippet MarkupString gets
   styled — CSS isolation doesn't reach MarkupString-injected nodes.
   ============================================================ */
/* flex: none defeats the filter-bar `.sf-search { flex: 0 1 360px }`
   rule (line ~478): it's the same class name on a different component,
   and without this override the topbar search claims a 360px flex-basis
   inside .sf-marketing__nav / .sf-topbar__nav, over-subscribing the row
   so the nav links shrink and wrap to two lines (2026-05-29 fix). The
   search sizes to its (intentionally compact) field below — that, not a
   `white-space: nowrap` on the links, is what keeps the nav on one line
   at desktop widths while still letting the links wrap gracefully on
   genuinely narrow screens. */
.sf-search { position: relative; display: inline-flex; align-items: center; flex: none; }
/* The FIELD is the visual control (pill, border, focus ring); the icon +
   input are flex siblings inside it. This keeps the magnifier from
   overlapping the text WITHOUT relying on input padding-left, which a
   higher-specificity global input rule was clobbering. */
.sf-search__field {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    width: clamp(150px, 11vw, 180px);
    height: 34px;
    padding: 0 12px;
    background: var(--bg-sunken);
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    transition: border-color var(--motion-duration-fast) var(--motion-easing-out),
                box-shadow var(--motion-duration-fast) var(--motion-easing-out);
}
.sf-search__field:focus-within {
    border-color: var(--accent);
    box-shadow: 0 0 0 2px color-mix(in oklab, var(--accent) 35%, transparent);
}
.sf-search__icon { flex: none; color: var(--fg-muted); }
.sf-search__input {
    flex: 1 1 auto;
    min-width: 0;
    height: 100%;
    padding: 0;
    border: none;
    background: transparent;
    font-family: var(--font-sans);
    font-size: 13px;
    color: var(--fg);
    outline: none;
}
.sf-search__input::placeholder { color: var(--fg-muted); }

.sf-search__panel {
    position: absolute;
    top: calc(100% + var(--space-8));
    left: 0;
    z-index: 200;
    width: 360px;
    max-width: 80vw;
    max-height: 60vh;
    overflow-y: auto;
    padding: var(--space-8);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-6);
    box-shadow: var(--shadow-pop);
    text-align: left;
}
.sf-search__state { padding: var(--space-12); font-size: 13px; color: var(--fg-muted); }
.sf-search__list { list-style: none; margin: 0; padding: 0; }
.sf-search__group {
    padding: var(--space-8) var(--space-8) var(--space-4);
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--fg-muted);
}
.sf-search__result, .sf-search__see-all {
    display: block;
    padding: var(--space-8) var(--space-12);
    border-radius: var(--radius-6);
    cursor: pointer;
    text-decoration: none;
}
.sf-search__result--active { background: var(--bg-sunken); }
.sf-search__result-title { display: block; font-size: 14px; font-weight: 600; color: var(--fg); }
.sf-search__result-snippet {
    display: block;
    margin-top: 2px;
    font-size: 12px;
    line-height: 1.4;
    color: var(--fg-muted);
}
.sf-search__result-snippet mark {
    background: color-mix(in oklab, var(--accent) 22%, transparent);
    color: inherit;
    border-radius: 2px;
    padding: 0 1px;
}
.sf-search__see-all {
    margin-top: var(--space-4);
    border-top: 1px solid var(--border);
    border-radius: 0;
    font-size: 13px;
    font-weight: 500;
    color: var(--accent-700);
}

/* The topbar search hides with the rest of the nav at the mobile
   breakpoint; the mobile drawer carries its own .sf-search--mobile. */
@media (max-width: 780px) {
    .sf-marketing__nav .sf-search,
    .sf-topbar__nav .sf-search { display: none; }
}

/* Mobile-drawer variant: full-width input, inline (non-overlay) panel. */
.sf-search--mobile { display: flex; width: 100%; margin-bottom: var(--space-12); }
.sf-search--mobile .sf-search__field { width: 100%; height: 40px; }
.sf-search--mobile .sf-search__input { font-size: 16px; }
.sf-search--mobile .sf-search__panel {
    position: static;
    width: 100%;
    max-width: none;
    max-height: none;
    box-shadow: none;
    border: none;
    padding: var(--space-4) 0 0;
}

/* /search full results page (Search.razor). The page wraps in
   .sf-prose-page (editorial Georgia h1/h2); these rules retune the
   results so the result LINKS lead, not giant serif category headings.
   Two-class selectors (.sf-search-page .x) clear the .sf-prose-page h2
   specificity. */
.sf-search-page__form { display: flex; gap: var(--space-8); align-items: center; margin: var(--space-16) 0 var(--space-24); }
.sf-search-page__input {
    flex: 0 1 420px;
    height: 40px;
    padding: 0 14px;
    font-family: var(--font-sans);
    font-size: 14px;
    color: var(--fg);
    background: var(--bg-sunken);
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    outline: none;
}
.sf-search-page__input:focus-visible {
    border-color: var(--accent);
    box-shadow: 0 0 0 2px color-mix(in oklab, var(--accent) 35%, transparent);
}
.sf-search-page__count { color: var(--fg-muted); margin-bottom: var(--space-16); }
.sf-search-page__hint, .sf-search-page__empty { color: var(--fg-muted); }
.sf-search-page .sf-search-page__group { margin-bottom: var(--space-24); }
.sf-search-page .sf-search-page__group-title {
    font-family: var(--font-sans);
    font-size: 12px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--fg-muted);
    margin: 0 0 var(--space-8);
}
.sf-search-page__list { list-style: none; margin: 0; padding: 0; }
.sf-search-page__result { padding: var(--space-12) 0; border-bottom: 1px solid var(--border); }
.sf-search-page__result-link { font-size: 16px; font-weight: 600; color: var(--accent-700); text-decoration: none; }
.sf-search-page__result-link:hover { text-decoration: underline; }
.sf-search-page__result-route { margin-left: var(--space-8); font-size: 12px; color: var(--fg-muted); }
.sf-search-page__result-snippet { display: block; margin-top: var(--space-4); font-size: 13px; line-height: 1.5; color: var(--fg-muted); }
.sf-search-page__result-snippet mark {
    background: color-mix(in oklab, var(--accent) 22%, transparent);
    color: inherit;
    border-radius: 2px;
    padding: 0 1px;
}

/* 2026-05-13 — mobile-nav scaffolding. Closes the BACKLOG-adjacent
   gap surfaced by a Codex review: at ≤780px the .sf-marketing__nav
   above hides entirely with NO replacement UI, so mobile visitors
   see only the logo + theme toggle. The hamburger button below
   appears at the same breakpoint and opens the .sf-marketing__mobile-nav
   dialog (defined after this block). */
.sf-marketing__hamburger { display: none; }
@media (max-width: 780px) {
    .sf-marketing__hamburger {
        display: inline-flex;
        flex-direction: column;
        justify-content: space-between;
        width: 32px;
        height: 22px;
        padding: 4px 0;
        background: transparent;
        border: none;
        cursor: pointer;
        color: inherit;
    }
    .sf-marketing__hamburger span {
        display: block;
        height: 2px;
        background: currentColor;
        border-radius: 2px;
    }
}

/* Mobile-nav dialog. Full-screen on mobile, hidden on desktop (the
   inline nav handles it). <dialog> handles focus + Escape natively;
   ::backdrop styles the dim overlay. */
.sf-marketing__mobile-nav {
    border: none;
    padding: 0;
    background: var(--bg);
    color: var(--fg);
    width: 100vw;
    max-width: 100vw;
    height: 100vh;
    max-height: 100vh;
    /* Reset the default <dialog> margin/inset so it covers the
       viewport rather than centering as a card. */
    margin: 0;
    inset: 0;
}
.sf-marketing__mobile-nav::backdrop {
    background: color-mix(in oklab, var(--fg) 65%, transparent);
    backdrop-filter: blur(2px);
}
.sf-marketing__mobile-nav nav {
    display: flex;
    flex-direction: column;
    gap: 0;
    padding: 72px 24px 32px;
}
.sf-marketing__mobile-nav nav a {
    font-family: Georgia, "Times New Roman", serif;
    font-size: 22px;
    color: var(--fg);
    text-decoration: none;
    padding: 18px 0;
    border-bottom: 1px solid var(--border);
}
.sf-marketing__mobile-nav nav a:last-child {
    border-bottom: none;
}
.sf-marketing__mobile-nav nav a.cta {
    margin-top: 28px;
    background: var(--fg);
    color: var(--bg);
    padding: 18px 20px;
    border-radius: 12px;
    text-align: center;
    border: none;
    font-family: var(--marketing-font-ui);
    font-size: 16px;
    font-weight: 600;
}
.sf-marketing__mobile-nav-close {
    position: absolute;
    top: 16px;
    right: 16px;
    background: transparent;
    border: none;
    font-size: 32px;
    line-height: 1;
    color: var(--fg);
    cursor: pointer;
    padding: 8px 14px;
}

.sf-marketing__main {
    flex: 1;
    width: 100%;
    margin: 0 auto;
    /* No max-width on the main element — sections handle their own
       container width. Marketing pages with prose body content are
       wrapped in .sf-prose / .sf-marketing__main--prose for the
       typography-friendly 70ch cap. */
}
.sf-marketing__main--prose {
    max-width: 1080px;
    padding: var(--space-48) var(--space-24);
}
.sf-marketing__main:focus-visible { outline: none; }

/* ---- global app footer ----
   Used by both MarketingLayout (replaces inline footer) and
   MainLayout (placed in the .sf-shell grid's new footer row, see
   .sf-shell rule above). The class root is .sf-app-footer (not
   .sf-marketing__footer) because the footer is no longer marketing-
   only — keeping the class name honest about scope. */
.sf-app-footer {
    border-top: 1px solid var(--border);
    background: var(--bg-elevated);
    padding: var(--space-16) var(--space-24);
}
/* When inside the .sf-shell grid, fill the footer area; the grid
   takes care of pinning to the bottom row. */
.sf-shell .sf-app-footer { grid-area: footer; margin-top: 0; }
/* When inside a flex column (MarketingLayout), hug the bottom even
   if the body content is short. */
.sf-marketing .sf-app-footer {
    margin-top: auto;
    /* Override the in-app token-driven background/border so the
       marketing footer reads with the new paper-tone palette. */
    background: var(--paper-2);
    border-top: 1px solid var(--rule);
    padding: 32px;
}
.sf-app-footer__row {
    max-width: 1200px;
    margin: 0 auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-16);
    flex-wrap: wrap;
}
.sf-app-footer__copyright { color: var(--fg-muted); font-size: var(--text-13, 13px); }
/* 2026-05-15 — the copyright now wraps the operating-entity name in
   an anchor (No Compromise AI → nocompromise.ai). Style the link to
   match the surrounding muted text instead of the default blue, with
   the same hover treatment as the footer nav links. */
.sf-app-footer__copyright a { color: inherit; text-decoration: none; }
.sf-app-footer__copyright a:hover { color: var(--fg); text-decoration: underline; }
.sf-app-footer__nav { display: flex; gap: var(--space-16); flex-wrap: wrap; }
.sf-app-footer__nav a { color: var(--fg-muted); text-decoration: none; font-size: var(--text-13, 13px); }
.sf-app-footer__nav a:hover { color: var(--fg); }
/* Marketing footer: nav links use the muted paper-tone color.
   PR-T2 follow-up (2026-05-06) — --muted (#7A6E55) on --paper-2
   (#F5EFD8) renders at 4.34:1 for 13px text, just under WCAG AA's
   4.5:1 bar; axe-core has been failing this on every marketing
   route. Mix --muted ~70/30 with --ink to land at ~5:1 while keeping
   the muted-paper character. Same baseline that PR #131 used for the
   .beat .who agent accents. */
.sf-marketing .sf-app-footer__copyright,
.sf-marketing .sf-app-footer__copyright a,
.sf-marketing .sf-app-footer__nav a {
    color: color-mix(in oklab, var(--muted) 70%, var(--ink) 30%);
    font-family: var(--marketing-font-ui);
    font-size: 13px;
}
.sf-marketing .sf-app-footer__copyright a:hover,
.sf-marketing .sf-app-footer__nav a:hover { color: var(--ink); }

/* ---- hero ---- */
/* Marketing review C1 (2026-05-02) — was a centered single-column
   hero with no proof. Now a 60/40 two-column with a workspace-
   screenshot demo on the right. Collapses to single-column below
   900px so phone visitors get the text first. */
.sf-hero {
    padding: var(--space-48) 0 var(--space-32);
    display: grid;
    grid-template-columns: 1.2fr 1fr;
    gap: var(--space-48);
    align-items: center;
}
@media (max-width: 900px) {
    .sf-hero {
        grid-template-columns: 1fr;
        text-align: center;
    }
    .sf-hero__cta { justify-content: center; }
    .sf-hero__demo { display: none; } /* hide demo on phones — the text is the priority */
}
.sf-hero__text { display: flex; flex-direction: column; gap: var(--space-16); }
.sf-hero__headline {
    font-size: clamp(28px, 4.5vw, 48px);
    font-weight: var(--weight-semibold);
    line-height: var(--leading-tight, 1.15);
    letter-spacing: var(--track-tight, -0.01em);
    margin: 0;
    max-width: 720px;
}
.sf-hero__subhead {
    font-size: var(--text-16);
    line-height: var(--leading-snug, 1.4);
    color: var(--fg-muted);
    max-width: 640px;
    margin: 0;
}
.sf-hero__cta {
    display: flex;
    gap: var(--space-12);
    flex-wrap: wrap;
}
.sf-hero__proof {
    color: var(--fg-subtle);
    font-size: var(--text-13);
    margin: var(--space-8) 0 0;
}

/* Workspace-screenshot mock — fake browser chrome around a small
   table of demo generation rows. Hand-built HTML (not an image
   asset) so it survives token changes and renders correctly in
   both themes without a separate dark/light asset pair. */
.sf-hero__demo {
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-12);
    box-shadow: var(--shadow-3);
    overflow: hidden;
}
.sf-demo-chrome { display: flex; flex-direction: column; }
.sf-demo-chrome__bar {
    display: flex; align-items: center; gap: var(--space-8);
    padding: var(--space-12) var(--space-16);
    background: var(--bg-sunken);
    border-bottom: 1px solid var(--border);
}
.sf-demo-chrome__dot {
    width: 10px; height: 10px;
    border-radius: 50%;
    background: var(--border-strong);
}
.sf-demo-chrome__url {
    margin-left: var(--space-12);
    font-family: var(--font-mono);
    font-size: var(--text-12);
    color: var(--fg-muted);
}
.sf-demo-chrome__body {
    padding: var(--space-12) var(--space-16);
    display: flex; flex-direction: column; gap: var(--space-8);
}
.sf-demo-row {
    display: grid;
    grid-template-columns: 1.4fr 1fr 1fr 0.6fr;
    align-items: center;
    gap: var(--space-12);
    font-size: var(--text-13);
    padding: var(--space-8) 0;
    border-bottom: 1px solid var(--border);
}
.sf-demo-row:last-child { border-bottom: 0; }
.sf-demo-row--head {
    font-size: var(--text-12);
    color: var(--fg-subtle);
    font-weight: var(--weight-semibold);
    letter-spacing: var(--track-wide, 0.04em);
    padding-bottom: var(--space-4);
}
.sf-demo-row__name { font-weight: var(--weight-medium); }
.sf-demo-row__num { text-align: right; font-variant-numeric: tabular-nums; font-family: var(--font-mono); }

/* Anatomy-of-a-package section. Two-column: directory tree on
   the left + a representative ADR sample on the right. Both are
   <pre> blocks; mono font + tinted bg-sunken background read as
   "this is real output," not "this is a marketing illustration." */
.sf-anatomy {
    margin: var(--space-48) 0;
}
.sf-anatomy__lede {
    text-align: center;
    color: var(--fg-muted);
    font-size: var(--text-16);
    margin: 0 auto var(--space-24);
    max-width: 56ch;
}
.sf-anatomy__grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-24);
}
@media (max-width: 720px) {
    .sf-anatomy__grid { grid-template-columns: 1fr; }
}
.sf-anatomy__tree,
.sf-anatomy__sample {
    background: var(--bg-sunken);
    border: 1px solid var(--border);
    border-radius: var(--radius-8);
    padding: var(--space-16);
    margin: 0;
    font-family: var(--font-mono);
    font-size: var(--text-13);
    line-height: var(--leading-snug);
    color: var(--fg);
    overflow-x: auto;
    white-space: pre;
}
.sf-anatomy__token-key { color: var(--accent-fg); font-weight: var(--weight-semibold); }
.sf-anatomy__token-bullet { color: var(--accent-fg); }

.sf-btn--large {
    height: 44px;
    padding: 0 18px;
    font-size: 15px;
    gap: 10px;
}

/* ---- features ---- */
.sf-section-heading {
    font-size: var(--text-24);
    text-align: center;
    margin: var(--space-48) 0 var(--space-24);
}
.sf-features__grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: var(--space-24);
    margin-bottom: var(--space-48);
}
.sf-feature {
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-12);
    padding: var(--space-24);
    display: flex;
    flex-direction: column;
    gap: var(--space-8);
}
.sf-feature__icon {
    font-size: 32px;
    line-height: 1;
}
.sf-feature__title {
    font-size: var(--text-16);
    font-weight: var(--weight-semibold);
    margin: 0;
}
.sf-feature__body {
    color: var(--fg-muted);
    line-height: var(--leading-snug, 1.4);
    margin: 0;
}
.sf-feature code {
    font-family: var(--font-mono);
    font-size: 0.9em;
    background: var(--bg-sunken);
    padding: 1px 6px;
    border-radius: var(--radius-4);
}

/* ---- sign-in section ---- */
.sf-signin {
    padding: var(--space-32) 0 var(--space-48);
    text-align: center;
}
.sf-signin__lede {
    color: var(--fg-muted);
    margin: 0 auto var(--space-24);
    max-width: 540px;
}
.sf-signin__buttons {
    display: flex;
    flex-direction: column;
    gap: var(--space-12);
    max-width: 360px;
    margin: 0 auto var(--space-16);
}
.sf-signin-btn {
    display: inline-flex;
    align-items: center;
    gap: var(--space-12);
    padding: var(--space-12) var(--space-16);
    background: var(--bg-elevated);
    color: var(--fg);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-8);
    font: inherit;
    font-weight: var(--weight-medium);
    text-decoration: none;
    cursor: pointer;
    transition: background var(--motion-duration-fast) var(--motion-easing-out);
    width: 100%;
    justify-content: center;
}
.sf-signin-btn:hover:not([disabled]) { background: var(--bg-sunken); }
.sf-signin-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Session 20 — disabled state without `opacity` on the host button.
   `opacity: 0.55` blends BOTH the label text (#1b1b1f → ~#8a8a8b on
   white) AND the vendor icon glyphs (white → ~#a4a6aa on the vendor
   color), dropping the effective contrast below WCAG AA on the
   "(configuring…)" buttons that show whenever an OAuth provider
   isn't configured (the live anonymous Landing page in CI). The
   muted-text + sunken-background treatment below keeps both the
   label and the icon visually flagged as disabled while preserving
   the color-contrast ratios axe-core checks. The :disabled icon
   override below intentionally swaps the white glyph to the muted
   foreground over the same sunken background so the icon-on-bg
   ratio is computed against tokens that already pass AA. */
.sf-signin-btn[disabled] {
    color: var(--fg-muted);
    background: var(--bg-sunken);
    border-color: var(--border);
    cursor: not-allowed;
}
/* Marketing review C3 (2026-05-02) — was a 24px circle filled
   with the vendor's brand color carrying a single letter (G / ⬢ /
   ⊞). Per Google / GitHub / Microsoft brand-guidance docs, sign-in
   affordances must use the official mark, not a guessed glyph in
   a colored chip. The icon now wraps an inline SVG (the actual
   brand mark); the colored-circle background is gone — the brand
   marks themselves carry the color. The GitHub mark uses
   currentColor so it inherits the button's --fg in both themes. */
.sf-signin-btn__icon {
    width: 20px; height: 20px;
    display: inline-flex; align-items: center; justify-content: center;
    flex: none;
}
.sf-signin-btn__icon svg { display: block; }
.sf-signin-btn--github .sf-signin-btn__icon { color: var(--fg); }

/* 2026-05-03 — signed-in callout button. Reuses the .sf-signin-btn
   chrome but tints accent-700 to read as the primary action (you're
   already signed in; "Open workspace" is the obvious next step). */
.sf-signin-btn--workspace {
    background: var(--accent-700);
    color: white;
    border-color: var(--accent-700);
}
.sf-signin-btn--workspace:hover:not([disabled]) {
    background: var(--accent-800, var(--accent-700));
    color: white;
}
.sf-signin-btn[disabled] .sf-signin-btn__icon { opacity: 0.55; }
.sf-signin__terms {
    /* Session 20 — was `var(--fg-subtle)` (#8b8d94 on light bg ≈
       3.3 : 1) which fails WCAG 2.1 AA for normal-size text. The
       muted token (#5b5d63 ≈ 6.6 : 1) passes AA at 12px and stays
       perceptibly de-emphasized vs the lede above. */
    color: var(--fg-muted);
    font-size: var(--text-12);
    margin: 0 auto;
    max-width: 540px;
}
.sf-signin__terms a { color: var(--fg-muted); text-decoration: underline; }

/* ---- prose (Privacy / Terms / written marketing pages) ---- */
.sf-prose {
    max-width: 720px;
    margin: 0 auto;
    line-height: var(--leading-normal, 1.5);
}
.sf-prose h1 { font-size: var(--text-32); margin: 0 0 var(--space-8); }
.sf-prose h2 { font-size: var(--text-20); margin: var(--space-32) 0 var(--space-12); }
/* 2026-05-14 — v1-launch Batch F H-F7 (deferred follow-up).
   `sf-prose__h1` lets a semantic <h2> render at h1-visual size
   when the surrounding layout already supplies the page-level
   <h1>. Used on EngineeringReleaseNotes where AdminLayout already
   emits "Administration" as the section h1 — the page title is
   conceptually h1 ("SpecStep engineering release notes") but
   must be h2 to avoid duplicate top-level headings. */
.sf-prose h2.sf-prose__h1 { font-size: var(--text-32); margin: 0 0 var(--space-8); }
.sf-prose h3 { font-size: var(--text-16); margin: var(--space-20) 0 var(--space-8); color: var(--fg); }
.sf-prose p { margin: 0 0 var(--space-12); }

/* PR-L3 (2026-05-05) — marketing-context overrides for prose pages
   under MarketingLayout. Adopts paper/ink tokens, Georgia headings,
   and breathing-room padding so prose pages render against the new
   marketing chrome without looking like leaked in-app content.
   Out-of-marketing renders (e.g. when an authenticated user views
   /pricing through MainLayout) keep the original token-driven look. */
.sf-marketing .sf-prose {
    max-width: 760px;
    padding: var(--space-48) var(--space-24) var(--space-64);
    color: var(--ink);
}
.sf-marketing .sf-prose h1 { font-size: clamp(36px, 4.5vw, 56px); margin: 0 0 16px; line-height: 1.05; }
.sf-marketing .sf-prose h2 { font-size: clamp(24px, 3vw, 32px); margin: 40px 0 14px; line-height: 1.15; color: var(--ink); }
.sf-marketing .sf-prose h3 { font-size: clamp(18px, 2.2vw, 22px); margin: 28px 0 10px; color: var(--ink); }
.sf-marketing .sf-prose p,
.sf-marketing .sf-prose li {
    color: var(--ink-2);
    font-size: 17px;
    line-height: 1.6;
}
.sf-marketing .sf-prose a { color: var(--ink); text-decoration: underline; text-decoration-color: var(--otto-accent); text-decoration-thickness: 2px; text-underline-offset: 3px; }
.sf-marketing .sf-prose a:hover { text-decoration-color: var(--ink); }
.sf-marketing .sf-prose strong { color: var(--ink); }
.sf-marketing .sf-prose code,
.sf-marketing .sf-prose pre {
    font-family: var(--marketing-font-mono);
}
.sf-marketing .sf-prose pre {
    background: var(--card);
    border: 1px solid var(--rule);
    border-radius: 12px;
    padding: 16px 20px;
    overflow-x: auto;
}
.sf-marketing .sf-prose__meta {
    border-top: 1px solid var(--rule);
    border-bottom: 1px solid var(--rule);
}
.sf-marketing .sf-prose__updated { color: var(--muted); font-family: var(--marketing-font-ui); }

/* Pricing — same scoping pattern. The .sf-pricing wrapper inside
   .sf-marketing adopts marketing tokens for the cards + padding
   so the page reads as part of the new identity. */
.sf-marketing .sf-pricing {
    max-width: 1200px;
    padding: var(--space-48) var(--space-24) var(--space-64);
}
.sf-marketing .sf-pricing__header h1 { color: var(--ink); }
.sf-marketing .sf-pricing__subhead { color: var(--muted-2); font-size: 18px; }
.sf-marketing .sf-pricing-card {
    background: var(--card);
    border: 1px solid var(--rule);
    border-radius: 18px;
    color: var(--ink-2);
}
.sf-marketing .sf-pricing-card--featured {
    border-color: var(--otto-accent);
    box-shadow: 0 16px 40px rgba(27, 31, 42, .08);
}
.sf-marketing .sf-pricing-card__name { color: var(--ink); }
/* PR-T2 follow-up (2026-05-06) — the sub-elements below hardcode
   var(--fg) / var(--fg-muted) which come from the in-app theme. In
   dark mode that flips them to a near-white (#E7E9EE) while the
   marketing card stays on the cream --card surface (#FFFCEF) — 1.18:1
   contrast, axe-core blocking. Marketing-scope overrides re-pin to
   the cream-palette ink/muted tokens so the cards read at >=4.5:1
   regardless of the user's system dark-mode preference. */
.sf-marketing .sf-pricing-card__price-unit { color: var(--ink); }
.sf-marketing .sf-pricing-card__features { color: var(--ink-2); }
.sf-marketing .sf-pricing-card__price-meta {
    color: color-mix(in oklab, var(--muted) 70%, var(--ink) 30%);
}
/* Pricing CTAs now inherit the canonical ink .sf-btn--primary (Web
   Controls v1, 2026-05-30) — the marketing-only ink override + its #000
   hover were removed as redundant once the base primary became ink. */
.sf-prose__updated { color: var(--fg-subtle); font-size: var(--text-13, 13px); margin: 0 0 var(--space-24); }

/* 2026-05-14 — v1-launch Batch A H-A9. ReleaseNotes ships 3 prose
   classes that previously rendered under default <p>/<nav> styling
   because no CSS rule existed for them: ~80 .sf-prose__release-meta
   instances, the .sf-prose__toc landmark, and the .sf-prose__admin-link
   callout above the TOC. The rules below give each a defined visual
   role. .sf-marketing overrides still apply where present so the
   anonymous-on-marketing-chrome render also picks them up cleanly.
   2026-05-14 (test-drift cleanup) — bump release-meta selector to
   `.sf-prose p.sf-prose__release-meta` so its specificity (0,2,1)
   matches `.sf-marketing .sf-prose p` and source-order wins (this
   rule appears later in the file). Without the bump, the marketing
   prose override (font-size: 17px) silently won on every public
   ReleaseNotes render — the release-meta line displayed at body
   size instead of the intended 13px metadata size. */
.sf-prose p.sf-prose__release-meta {
    color: var(--fg-subtle);
    font-size: var(--text-13);
    margin: calc(var(--space-8) * -1) 0 var(--space-16);
    letter-spacing: 0.02em;
}
.sf-prose__admin-link {
    margin: 0 0 var(--space-24);
    padding: var(--space-12) var(--space-16);
    background: var(--bg-sunken);
    border: 1px solid var(--border);
    border-left: 3px solid var(--accent-fg);
    border-radius: var(--radius-6);
    color: var(--fg-muted);
    font-size: var(--text-13);
}
.sf-prose__admin-link strong { color: var(--fg); margin-right: var(--space-4); }
.sf-prose__toc {
    margin: 0 0 var(--space-32);
    padding: var(--space-16) var(--space-20);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-8);
}
.sf-prose__toc h2 {
    margin-top: 0;
    font-size: var(--text-18, 18px);
}
.sf-prose__toc h3 {
    margin-top: var(--space-16);
    font-size: var(--text-14);
    color: var(--fg-muted);
    text-transform: uppercase;
    letter-spacing: 0.08em;
}
.sf-prose__toc ul {
    margin: 0 0 var(--space-8);
    padding-left: var(--space-20);
}
.sf-prose__toc li {
    margin-bottom: var(--space-4);
    font-size: var(--text-14);
}

/* 2026-05-14 — v1-launch Batch A H-A4. Floating back-to-top pill
   for long-form prose pages (ReleaseNotes is 1400+ lines). Anchors
   to #contents, scrolls smoothly via CSS scroll-behavior on the
   article root. Hidden by default until JS toggles the .is-visible
   class once scroll position exceeds 600px. position: fixed pins
   it to the viewport corner; aria-label carries the accessible
   name since the visible glyph is the symbol "↑". On narrow
   viewports we hide rather than overlap content. */
.sf-prose__back-to-top {
    position: fixed;
    bottom: var(--space-24, 24px);
    right: var(--space-24, 24px);
    display: none;
    align-items: center;
    justify-content: center;
    gap: var(--space-4);
    height: 36px;
    padding: 0 var(--space-16);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: 999px;
    color: var(--fg);
    text-decoration: none;
    font-size: var(--text-13);
    font-weight: var(--weight-medium);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
    transition: opacity 0.2s, transform 0.2s;
    opacity: 0;
    transform: translateY(8px);
    z-index: 50;
}
.sf-prose__back-to-top.is-visible {
    display: inline-flex;
    opacity: 1;
    transform: translateY(0);
}
.sf-prose__back-to-top:hover { border-color: var(--accent-fg); color: var(--accent-fg); }
.sf-prose__back-to-top:focus-visible {
    outline: 2px solid var(--accent-fg);
    outline-offset: 2px;
}
@media (max-width: 640px) {
    .sf-prose__back-to-top { display: none !important; }
}
@media (prefers-reduced-motion: reduce) {
    .sf-prose__back-to-top { transition: none !important; }
}
/* Marketing review C10 (2026-05-02) — header strip on prose
   pages. Wraps the .sf-prose__updated child so future metadata
   (author, byline, canonical-URL chip) can land here without
   rewiring the page templates. */
.sf-prose__meta {
    display: flex; align-items: center; gap: var(--space-12);
    flex-wrap: wrap;
    margin: 0 0 var(--space-24);
    padding: var(--space-12) 0;
    border-top: 1px solid var(--border);
    border-bottom: 1px solid var(--border);
}
.sf-prose__meta .sf-prose__updated { margin: 0; }
/* 2026-05-14 — v1-launch Batch A H-A5. Inline jump-nav for long
   marketing prose pages (About has 7 h2 sections). Pill-shaped
   anchors that wrap on narrow viewports. Each pill is a real <a>
   so keyboard tab order picks them up and focus-visible carries
   a real outline. */
.sf-prose__jumpnav {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-8);
    margin: 0 0 var(--space-32);
}
.sf-prose__jumpnav a {
    display: inline-flex;
    align-items: center;
    height: 28px;
    padding: 0 var(--space-12);
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--bg-elevated);
    color: var(--fg-muted);
    text-decoration: none;
    font-size: var(--text-13);
    font-weight: var(--weight-medium);
    transition: border-color 0.15s, color 0.15s;
}
.sf-prose__jumpnav a:hover {
    border-color: var(--accent-fg);
    color: var(--accent-fg);
}
.sf-prose__jumpnav a:focus-visible {
    outline: 2px solid var(--accent-fg);
    outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
    .sf-prose__jumpnav a { transition: none !important; }
}
.sf-prose ul { padding-left: var(--space-24); margin: 0 0 var(--space-12); }
.sf-prose li { margin-bottom: 6px; color: var(--fg-muted); }
.sf-prose a { color: var(--accent-fg); }
.sf-prose strong { color: var(--fg); }

/* Codex round-7 (2026-05-13) — base prose styles for `code`, `pre`,
   `table`, `blockquote`. Round 7's design review caught that the
   `.sf-marketing .sf-prose` override at line 2890 covers these for
   anonymous visitors, but signed-in users hit `MainLayout → .sf-prose`
   with no marketing wrapper and every table / code block / blockquote
   rendered under browser defaults. The base block now covers them so
   the rendered markdown looks right regardless of which shell wraps
   the prose. The `.sf-marketing` overrides still apply when present. */
.sf-prose code {
    font-family: var(--font-mono);
    font-size: var(--text-13);
    background: var(--bg-sunken);
    border-radius: var(--radius-4);
    padding: 1px 4px;
}
.sf-prose pre {
    font-family: var(--font-mono);
    font-size: var(--text-13);
    background: var(--bg-sunken);
    border: 1px solid var(--border);
    border-radius: var(--radius-6);
    padding: var(--space-16);
    overflow-x: auto;
    line-height: 1.55;
    margin: 0 0 var(--space-16);
}
.sf-prose pre code {
    background: transparent;
    padding: 0;
    border-radius: 0;
    font-size: inherit;
}
.sf-prose table {
    width: 100%;
    border-collapse: collapse;
    margin: var(--space-16) 0;
    font-size: var(--text-14);
}
.sf-prose th,
.sf-prose td {
    padding: 8px 12px;
    border-bottom: 1px solid var(--border);
    text-align: left;
    vertical-align: top;
    color: var(--fg);
}
.sf-prose th {
    background: var(--bg-elevated);
    font-weight: 600;
}
.sf-prose blockquote {
    border-left: 3px solid var(--accent);
    background: var(--bg-sunken);
    padding: var(--space-12) var(--space-16);
    margin: var(--space-16) 0;
    color: var(--fg);
    border-radius: 0 var(--radius-6) var(--radius-6) 0;
}
.sf-prose blockquote p { margin: 0; }
.sf-prose blockquote p + p { margin-top: var(--space-8); }
.sf-prose hr {
    border: 0;
    border-top: 1px solid var(--border);
    margin: var(--space-24) 0;
}

/* 2026-05-14 — v1-launch Batch C PR-C2. OAuth consent screen.
   Class family scopes the markup-specific bits without spilling
   into other `.sf-card` users. .sf-oauth-consent itself is just
   a vertical-rhythm anchor; the heavy lifting is the buttons row
   below the scopes list. */
.sf-oauth-consent__scopes {
    margin: var(--space-16) 0;
    padding-left: var(--space-20);
}
.sf-oauth-consent__scopes li {
    margin-bottom: var(--space-8);
    color: var(--fg);
}
.sf-oauth-consent__actions-help {
    margin: var(--space-16) 0;
}
.sf-oauth-consent__form { margin: 0; }
.sf-oauth-consent__actions {
    display: flex;
    gap: var(--space-12);
    margin-top: var(--space-16);
    flex-wrap: wrap;
}
.sf-oauth-consent__actions .sf-btn { min-width: 96px; }

/* TASK-178 (2026-05-08) — legal-document callouts.
   Two variants:
     .sf-prose__legal-callout         — soft tinted block, used for the
                                        AI-Output (§5.5) + Preview-Edition
                                        (§9.5) sections.
     .sf-prose__legal-callout--strong — high-contrast bordered block,
                                        used for the §11 warranty disclaimer
                                        and §12 liability cap to satisfy
                                        the UCC "conspicuous" requirement.
     .sf-prose__legal-allcaps         — uppercase + tracked-out treatment
                                        wrapped on the warranty + liability
                                        text per the UCC safe harbor.
*/
.sf-prose__legal-callout {
    margin: var(--space-12) 0 var(--space-16);
    padding: var(--space-16);
    border-left: 3px solid var(--accent);
    background: var(--bg-sunken);
    border-radius: var(--radius-6);
}
.sf-prose__legal-callout > p:last-child { margin-bottom: 0; }

.sf-prose__legal-callout--strong {
    border-left: 4px solid var(--state-failed-fg, var(--accent));
    border-top: 1px solid var(--border-strong);
    border-right: 1px solid var(--border-strong);
    border-bottom: 1px solid var(--border-strong);
    background: var(--bg-elevated);
}

.sf-prose__legal-allcaps {
    text-transform: uppercase;
    letter-spacing: 0.04em;
    font-size: var(--text-13);
    /* 2026-05-14 — v1-launch Batch A H-A8. line-height nudged to
       1.45 so the uppercase tracked-out lines breathe; the prior
       --leading-normal was tight enough that the H/E/L of adjacent
       lines visually touched on narrow viewports. */
    line-height: 1.45;
}

/* 2026-05-14 — v1-launch Batch A H-A8. Legal-page H3 + body type
   on Tos / Privacy were within 1px of each other on the
   <640px clamp range. Pin H3 to a flat var(--text-20) +
   semibold so the section subheadings (§5.5, §9.5, etc.) read as
   a real step up from body. Scoped to `.sf-prose` with a legal-
   surface marker class so About / ReleaseNotes h3 sizes don't
   shift. The marker class is added to the <article> on
   Tos.razor + Privacy.razor in the same commit. */
.sf-prose--legal h3 {
    font-size: var(--text-20);
    font-weight: var(--weight-semibold);
}

/* Phase 8 Session E — Pricing page styles. */
.sf-pricing {
    max-width: 1080px;
    margin: 0 auto;
    padding: var(--space-32) var(--space-24);
}
.sf-pricing__header { text-align: center; margin-bottom: var(--space-32); }
.sf-pricing__header h1 { font-size: clamp(28px, 4vw, 40px); margin: 0 0 var(--space-8); }
.sf-pricing__subhead { color: var(--fg-muted); margin: 0 auto; max-width: 540px; }
.sf-pricing__grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: var(--space-24);
    margin-bottom: var(--space-24);
}
.sf-pricing-card {
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-12);
    padding: var(--space-24);
    display: flex;
    flex-direction: column;
    gap: var(--space-16);
}
.sf-pricing-card--featured {
    border-color: var(--accent);
    box-shadow: var(--shadow-2);
    /* Marketing review (2026-05-02) — featured tier sits a touch
       higher to make the visual hierarchy unambiguous before the
       user has read the badge. */
    transform: translateY(-4px);
}
/* Marketing review S6 (2026-05-02) — sf-pricing-card--current
   used to add a 2px --accent-fg border that visually collided
   with --featured's --accent-500 border. Now the "your plan"
   communication moves to a sf-pill--info badge in the header
   (see sf-pricing-card__badge below). The --current modifier
   is intentionally NOT added on the article element anymore;
   the pill alone carries the meaning. */

.sf-pricing-card__header {
    display: flex; flex-direction: column; gap: var(--space-4);
    /* Make room for the absolute-positioned badge. */
    position: relative;
}
.sf-pricing-card__badge {
    align-self: flex-start;
    margin-bottom: var(--space-4);
}

.sf-pricing-card__name {
    margin: 0;
    font-size: var(--text-20);
    font-weight: var(--weight-semibold);
}
/* Marketing review C7 (2026-05-02) — tightened price unit. Was
   "/ month" rendered at default text size in --fg-muted, which
   read as a footnote next to a 32px price. Now /mo sits inline
   at --text-14 / --fg with zero gap, and the meta + per-cost
   lines below carry the additional information. */
.sf-pricing-card__price {
    display: flex; align-items: baseline; gap: 0; margin: 0;
}
.sf-pricing-card__price-value { font-size: var(--text-32); font-weight: var(--weight-semibold); font-variant-numeric: tabular-nums; }
.sf-pricing-card__price-unit { color: var(--fg); font-size: var(--text-14); font-weight: var(--weight-medium); }
.sf-pricing-card__price-meta { color: var(--fg-muted); font-size: var(--text-13); margin: 0; }
.sf-pricing-card__features {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 6px;
    color: var(--fg);
    font-size: var(--text-14);
}
/* Marketing review C5 (2026-05-02) — was a Unicode '✓ ' character
   prepended via ::before content, which renders inconsistently by
   font (squat in Segoe UI Variable, hairline in Inter) and at 14px
   the --accent-fg can drift below WCAG AA for thin glyphs.
   Switched to a 14×14 inline SVG check rendered via a CSS
   background-image data-URI so the markup stays clean. */
.sf-pricing-card__features li {
    padding-left: 24px;
    position: relative;
}
.sf-pricing-card__features li::before {
    content: "";
    position: absolute;
    left: 0; top: 4px;
    width: 16px; height: 16px;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23117b50' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-size: contain;
}
:root[data-theme="dark"] .sf-pricing-card__features li::before {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%236ad8a3' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");
}
.sf-pricing-card__cta { margin-top: auto; display: flex; flex-direction: column; }
.sf-pricing-card__cta .sf-btn { width: 100%; height: 40px; }
/* 2026-05-14 — v1-launch Batch A C-A1. Replaces the prior
   `.sf-pricing-card__cta-with-overlay` + disabled-button + overlay-
   pill triple. One affordance, one accessible name. Same vertical
   space as a Free/Pro CTA (40px tall) so the three cards still align
   across the row. Visual treatment matches the prior pill so the
   roadmap-card silhouette is unchanged: muted tint, 1px border, pill
   radius, uppercase tracking. role="status" on the <p> in markup
   tells AT users this is a status announcement rather than an
   interactive control. */
/* 2026-05-14 — v1-launch Batch E C-E2. Generic "Coming soon"
   status pill, sibling of `.sf-pricing-card__cta-coming-soon`.
   Used in non-pricing contexts where an action button is
   unbuilt and showing a disabled control would mislead. role
   ="status" on the markup tells AT users this is a state
   announcement rather than an interactive control. */
.sf-cta-coming-soon {
    margin: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: 40px;
    padding: 0 var(--space-16);
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--bg-elevated);
    color: var(--fg-muted);
    font-size: var(--text-12);
    font-weight: var(--weight-medium);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.sf-pricing-card__cta-coming-soon {
    margin: 0;
    width: 100%;
    height: 40px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--bg-elevated);
    color: var(--fg-muted);
    font-size: var(--text-12);
    font-weight: var(--weight-medium);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
/* Marketing review C4 (2026-05-02) — comparison table beneath the
   pricing cards. Lets users diff Pro→Team in one horizontal scan
   instead of mentally comparing two cards in parallel. Reuses the
   sf-table chrome with extra spacing tuned for the marketing
   surface (rather than the dense workspace tables). */
.sf-pricing-compare {
    margin: var(--space-32) 0;
}
.sf-pricing-compare__table {
    width: 100%;
    border-collapse: collapse;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-8);
    overflow: hidden;
    /* 2026-05-13 — table-layout: fixed forces all three tier columns
       to equal width once the feature column claims its 32%. Without
       this, "Most popular" under Pro and the wider checkmark cells in
       Team grow those columns and shrink Free — which then offsets
       OverlayDataRow "Coming soon" pills (rendered in a colspan=3
       cell, center-aligned) from the visual Pro center. With fixed
       layout the merged-cell center == Pro column center. */
    table-layout: fixed;
}
.sf-pricing-compare__table th,
.sf-pricing-compare__table td {
    padding: var(--space-12) var(--space-16);
    border-bottom: 1px solid var(--border);
    text-align: left;
    font-size: var(--text-14);
}
/* 2026-05-13 — center-align all tier-column content (headers + cells)
   so checkmarks, em-dashes, plain text values, and the Coming-soon
   overlay pills all line up under the same x position as their column
   header. Without this, the row-header `text-align: left` cascaded
   into the tier cells too, leaving checks at the LEFT of each column
   while the overlay pill (centered in a colspan=3 cell) sat at the
   geometric center — visually off-center even though it was
   mathematically centered on Pro. The feature column stays left-
   aligned via `.sf-pricing-compare__feature-col` + `tbody th[scope="row"]`
   below. */
.sf-pricing-compare__table thead th:not(.sf-pricing-compare__feature-col),
.sf-pricing-compare__table tbody td {
    text-align: center;
}
.sf-pricing-compare__table thead th {
    background: var(--bg-sunken);
    color: var(--fg);
    font-weight: var(--weight-semibold);
    font-size: var(--text-14);
    text-transform: none;
    letter-spacing: normal;
}
.sf-pricing-compare__table tbody th {
    color: var(--fg-muted);
    font-weight: var(--weight-medium);
}
.sf-pricing-compare__table tbody tr:last-child th,
.sf-pricing-compare__table tbody tr:last-child td { border-bottom: 0; }
.sf-pricing-compare__feature-col { width: 32%; }

/* 2026-05-13 — stylish checkmark in the comparison grid for cells
   that previously rendered the literal word "Included". Same Lucide
   polyline shape as the tier-card features check
   (.sf-pricing-card__features li::before above) so the visual
   vocabulary is consistent between the two surfaces. Stroke uses
   currentColor; the class sets the green tint and inverts for dark
   mode using the same #117b50 / #6ad8a3 pair the features check
   does. */
.sf-pricing-compare__check {
    display: inline-flex;
    align-items: center;
    color: #117b50;
}
.sf-pricing-compare__check svg { display: block; }
:root[data-theme="dark"] .sf-pricing-compare__check { color: #6ad8a3; }

/* 2026-05-13 — comparison-grid section headings. Each category
   (Review profiles, External connectors, Agents included) renders
   as a single-row band that introduces the indented sub-rows below
   it. The band reuses --bg-sunken (same as thead) but with smaller
   uppercase tracking so it reads as a sub-header rather than a
   second column header. */
.sf-pricing-compare__section-row th {
    background: var(--bg-sunken);
    color: var(--fg);
    font-weight: var(--weight-semibold);
    font-size: var(--text-12);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding-top: var(--space-16);
}
/* Muted sub-line on a section heading — used for the External
   connectors Free-tier "connect + preview only" caveat that
   doesn't fit cleanly as a checkmark. */
.sf-pricing-compare__section-note {
    display: block;
    margin-top: 2px;
    font-weight: var(--weight-regular);
    color: var(--fg-muted);
    font-size: var(--text-12);
    text-transform: none;
    letter-spacing: 0;
}
/* Sub-row indent — visually groups rows under their heading. Only
   applied to rows tagged sf-pricing-compare__sub-row, so top-level
   rows (Generations, API access, etc.) stay flush. */
.sf-pricing-compare__sub-row > th[scope="row"] {
    padding-left: var(--space-24);
}
/* "Not included" marker — muted em-dash. Contrasts with the green
   check (.sf-pricing-compare__check) so a glance down a column
   tells the user which features are granted vs. omitted. */
.sf-pricing-compare__absent {
    color: var(--fg-muted);
    opacity: 0.55;
}

/* 2026-05-13 — colspan-3 cell for rows whose three tier columns
   merge into a single status pill (e.g. "Coming soon" providers).
   Center-aligned so the pill sits visually across the three tier
   columns instead of clinging to the left edge. Selector includes
   the table scope to outrank the generic `.sf-pricing-compare__table
   td` rule that sets text-align: left (specificity 0,1,1). */
.sf-pricing-compare__table td.sf-pricing-compare__overlay-cell {
    text-align: center;
}
/* The status pill itself — muted background, subtle border,
   uppercase tracking so it reads as a roadmap marker rather than
   a competing call-to-action. Inherits theme tokens so dark mode
   flips automatically. */
.sf-pricing-compare__overlay-pill {
    display: inline-block;
    padding: 4px 12px;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--bg-sunken);
    color: var(--fg-muted);
    font-size: var(--text-12);
    font-weight: var(--weight-medium);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.sf-pricing__footnote { text-align: center; color: var(--fg-muted); font-size: var(--text-13, 13px); }
/* Session 23 — `<a href="mailto:…">Contact us</a>` inside the
   pricing footnote inherited the browser default link color
   (#0000ee), which only clears 2.01 : 1 on the dark theme's #0f1115
   bg (axe FAIL). Use the project's accessible-accent token so the
   link is themed AND clears WCAG AA in both themes (#117b50 light /
   #6ad8a3 dark). Underline keeps it identifiably interactive even
   though the color is greener than the default link blue. */
.sf-pricing__footnote a { color: var(--accent-fg); text-decoration: underline; }

/* Phase 8 hotfix — make the topbar brand a link without underline. */
a.sf-topbar__brand {
    text-decoration: none;
    color: inherit;
}
a.sf-topbar__brand:hover .sf-topbar__product { color: var(--accent-fg); }

/* Initiative H — version chip is now an <a> linking to /release-notes
   (the timeline of past releases). Kill default link underline so it
   still reads as a chip; lift to --fg on hover so it's discoverable as
   interactive without being loud; reuse the same focus-visible ring
   the topbar navlinks use so keyboard users see a consistent affordance
   when tabbing through the header. */
a.sf-topbar__version {
    text-decoration: none;
    color: var(--fg-subtle);
    cursor: pointer;
}
a.sf-topbar__version:hover { color: var(--fg); text-decoration: underline; text-underline-offset: 2px; }
a.sf-topbar__version:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: 2px;
}

/* Phase 8 hotfix v1.4.8 — persona dropdown menu, hover/focus driven.
   Click-to-toggle was unreliable because the toggle handler often raced
   the SSR → interactive handoff and stayed in the wrong state until the
   user reloaded. Pure CSS :hover + :focus-within means the menu is
   reachable even if the Blazor circuit hasn't reconnected yet, and it
   stays accessible (keyboard tab-into the trigger keeps it open via
   :focus-within on the wrapper). */
.sf-persona-menu { position: relative; }
.sf-persona__caret {
    margin-left: 4px;
    color: var(--fg-subtle);
    font-size: 0.75em;
    transition: transform var(--motion-duration-fast) var(--motion-easing-out);
}
.sf-persona-menu:hover .sf-persona__caret,
.sf-persona-menu:focus-within .sf-persona__caret { transform: rotate(180deg); }

/* Invisible 8px hover-bridge between the trigger and the dropdown so the
   cursor can travel without losing :hover (which would close the menu). */
.sf-persona-menu::after {
    content: "";
    position: absolute;
    top: 100%;
    right: 0;
    width: 240px;
    height: 8px;
    pointer-events: none;
}
.sf-persona-menu:hover::after,
.sf-persona-menu:focus-within::after { pointer-events: auto; }

.sf-persona__dropdown {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    min-width: 240px;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-8);
    box-shadow: var(--shadow-2);
    z-index: 100;
    overflow: hidden;
    display: flex;
    flex-direction: column;

    /* Hidden by default; revealed on hover/focus. visibility:hidden
       (vs display:none) keeps it animatable and in the a11y tree. */
    visibility: hidden;
    opacity: 0;
    transform: translateY(-4px);
    pointer-events: none;
    transition:
        opacity var(--motion-duration-fast) var(--motion-easing-out),
        transform var(--motion-duration-fast) var(--motion-easing-out),
        visibility 0s linear var(--motion-duration-fast);
}
.sf-persona-menu:hover .sf-persona__dropdown,
.sf-persona-menu:focus-within .sf-persona__dropdown {
    visibility: visible;
    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;
    transition:
        opacity var(--motion-duration-fast) var(--motion-easing-out),
        transform var(--motion-duration-fast) var(--motion-easing-out);
}
.sf-persona__identity {
    padding: var(--space-12) var(--space-16);
    border-bottom: 1px solid var(--border);
}
.sf-persona__name-full {
    font-weight: var(--weight-semibold);
    font-size: var(--text-14);
    color: var(--fg);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.sf-persona__email {
    font-size: var(--text-12);
    color: var(--fg-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.sf-persona__menuitem {
    display: block;
    padding: var(--space-8) var(--space-16);
    background: transparent;
    border: 0;
    text-align: left;
    color: var(--fg);
    text-decoration: none;
    font: inherit;
    cursor: pointer;
    width: 100%;
}
.sf-persona__menuitem:hover { background: var(--bg-sunken); }
.sf-persona__menuitem--danger { color: var(--danger, #b42318); border-top: 1px solid var(--border); }
.sf-persona__signout-form { margin: 0; }

/* Phase 8 Session F — tier chip in persona menu. Always visible
   when signed in so the user knows what plan they're on without
   navigating to Settings → Billing. */
.sf-tier-chip {
    display: inline-block;
    padding: 1px var(--space-6);
    margin-left: var(--space-6);
    border-radius: var(--radius-pill);
    font-size: var(--text-12);
    line-height: var(--text-16);
    font-weight: var(--weight-semibold);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.sf-tier-chip--free { background: var(--bg-sunken); color: var(--fg-muted); }
.sf-tier-chip--pro  { background: var(--accent-100, #dbeafe); color: var(--accent-700, #1d4ed8); }
.sf-tier-chip--team { background: var(--success-100, #dcfce7); color: var(--success-700, #15803d); }

/* Phase 8 Session F — "Plan: ..." line inside the dropdown identity
   block, with a "Manage plan" link to Settings → Billing. */
.sf-persona__plan {
    margin-top: var(--space-6);
    font-size: var(--text-12);
    color: var(--fg-muted);
    display: flex;
    align-items: center;
    gap: var(--space-8);
}
.sf-persona__plan-link {
    margin-left: auto;
    color: var(--accent-fg, var(--fg));
    text-decoration: none;
    font-weight: var(--weight-medium);
}
.sf-persona__plan-link:hover { text-decoration: underline; }

/* Phase 8 Session F — quota awareness banners on Workspace.
   Warning at >=80% usage, exceeded at 100%.
   Workspace review C1 (2026-05-02): the previous rules referenced
   --warning-50/300/900 + --danger-50/300/900 — none of which existed
   in tokens.css — so the var() lookups fell through to the hex
   fallbacks (yellow-on-near-white, red-on-near-white) and the banner
   broke in dark mode. Now uses the dedicated --warning-{bg,border,fg}
   and --danger-{bg,border,fg} pairs that resolve correctly per theme. */
.sf-quota-banner {
    margin: var(--space-12) 0 0;
    padding: var(--space-12) var(--space-16);
    background: var(--warning-bg);
    border: 1px solid var(--warning-border);
    color: var(--warning-fg);
    border-radius: var(--radius-8);
    font-size: var(--text-14);
}
.sf-quota-banner a {
    margin-left: var(--space-8);
    color: inherit;
    font-weight: var(--weight-semibold);
}
.sf-quota-banner--exceeded {
    background: var(--danger-bg);
    border-color: var(--danger-border);
    color: var(--danger-fg);
}

/* Phase 9 — admin + profile primitives. */
.sf-form { display: flex; flex-direction: column; gap: var(--space-12); margin: 0; }
.sf-form__actions { display: flex; gap: var(--space-8); align-items: center; flex-wrap: wrap; }
.sf-field { display: flex; flex-direction: column; gap: var(--space-4); }
.sf-field label { font-size: var(--text-13); font-weight: var(--weight-semibold); color: var(--fg); }

/* H-E15 (2026-05-14) — Backfill the missing CSS for the prior
   `sf-form__*` class shapes still in use in WebhooksPanel +
   StatusManagementPanel. The canonical primitives are
   `sf-form__actions` + `sf-field` (above); the aliases below carry
   compatible visuals so those panels render correctly without a
   markup refactor. Full migration to canonical classes is a
   separate refactor PR (touches markup + risks layout drift). */
.sf-form__label { font-size: var(--text-13); font-weight: var(--weight-semibold); color: var(--fg); display: block; margin-bottom: var(--space-4); }
.sf-form__hint { font-weight: var(--weight-regular); color: var(--fg-muted); }
.sf-form__row { display: flex; gap: var(--space-12); align-items: stretch; }
.sf-form__row > * { min-width: 0; }
.sf-form__fieldset { border: 0; padding: 0; margin: 0; }
/* Web Controls v1 (2026-05-30) — canonical .input: 36px tall, radius-4,
   stronger border, ink border + soft ring on focus (per controls.css). */
.sf-input {
    height: 36px;
    padding: 0 var(--space-12);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-4);
    background: var(--bg);
    color: var(--fg);
    font: inherit;
    transition: border-color var(--motion-duration-fast) ease, box-shadow var(--motion-duration-fast) ease, background var(--motion-duration-fast) ease;
}
.sf-input::placeholder { color: var(--fg-subtle); }
.sf-input:hover { border-color: var(--fg-muted); }
.sf-input:focus {
    outline: none;
    border-color: var(--fg);
    box-shadow: 0 0 0 3px color-mix(in oklab, var(--fg) 8%, transparent);
}
.sf-input:disabled,
.sf-input[disabled] {
    background: var(--bg-sunken);
    color: var(--fg-muted);
    cursor: not-allowed;
}
textarea.sf-input {
    height: auto;
    min-height: 96px;
    padding: var(--space-8) var(--space-12);
    line-height: var(--leading-normal);
    resize: vertical;
}
.sf-input.sf-mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: 0.02em; }
.sf-readonly {
    padding: var(--space-8) var(--space-12);
    background: var(--bg-sunken);
    border-radius: var(--radius-6);
    color: var(--fg-muted);
}
.sf-divider { border: 0; border-top: 1px solid var(--border); margin: var(--space-24) 0; }
.sf-link {
    background: none;
    border: 0;
    padding: 0;
    color: var(--accent-fg, var(--accent-700, #1d4ed8));
    text-decoration: underline;
    cursor: pointer;
    font: inherit;
    text-align: left;
}
.sf-link:hover { text-decoration: none; }
.sf-btn--danger {
    background: var(--danger);
    color: var(--bg);
    border: 1px solid var(--danger);
}
.sf-btn--danger:hover { background: var(--danger-700, #8b1a13); }
.sf-btn--danger:disabled { background: var(--bg-sunken); color: var(--fg-subtle); cursor: not-allowed; }

/* Modal overlay used by Users + Roles drawers. */
.sf-modal {
    position: fixed; inset: 0;
    background: rgba(0, 0, 0, 0.45);
    display: flex; align-items: center; justify-content: center;
    z-index: 1000;
    padding: var(--space-16);
}
.sf-modal__content {
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-8);
    padding: var(--space-24);
    max-width: 640px;
    width: 100%;
    max-height: 90vh;
    overflow-y: auto;
    box-shadow: var(--shadow-2);
}

/* ---- API key reveal modal ---- (Phase 9.5 follow-up)
   The previous "no styles defined" state made the modal render as an
   unstyled vertical stack of paragraphs + buttons, which the user
   reported as "long text + buttons in weird places". Define the
   backdrop + modal box + the keyreveal-specific structure. */
.sf-modal-backdrop {
    position: fixed; inset: 0;
    background: rgba(0, 0, 0, 0.55);
    display: flex; align-items: center; justify-content: center;
    z-index: 1100;
    padding: var(--space-16);
}
/* When .sf-modal appears alongside .sf-modal-backdrop (the new
   pattern used by ApiKeyRevealModal), the backdrop is the overlay
   and .sf-modal is the centered card — distinct from the legacy
   .sf-modal-as-overlay above used by Users/Roles drawers. */
.sf-modal-backdrop > .sf-modal {
    position: static;
    inset: auto;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-8);
    padding: 0;
    max-width: 560px;
    width: 100%;
    max-height: 90vh;
    overflow-y: auto;
    box-shadow: 0 24px 64px -8px rgba(0, 0, 0, 0.45);
    display: flex;
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
}
.sf-modal__header { padding: var(--space-20, 20px) var(--space-24); border-bottom: 1px solid var(--border); }
/* Interview review C3 (2026-05-02) — legal-disclaimer modal carries
   warning weight via a 4px top border in --warning-border instead
   of the dropped ⚖ glyph in the heading. The modal otherwise looks
   identical to other modals — the colored top edge does the
   visual signaling. */
.sf-modal-backdrop > .sf-modal--legal-ack {
    border-top: 4px solid var(--warning-border);
}
.sf-modal__title { font-size: var(--text-18, 18px); margin: 0; color: var(--fg); }
.sf-modal__lede { color: var(--fg-muted); margin: var(--space-4) 0 0; font-size: var(--text-14, 14px); }
.sf-modal__footer {
    padding: var(--space-16) var(--space-24);
    border-top: 1px solid var(--border);
    display: flex; gap: var(--space-8); justify-content: flex-end; align-items: center;
}
/* 2026-05-14 — v1-launch Batch C H-C12 (deferred follow-up).
   The delete-confirm modal markup has referenced `sf-modal__error`
   since PR-T35 but the rule was never defined — error text rendered
   in default body color. Establish the canonical pill-less inline-
   error treatment now that all modals route through the same slot
   shape (danger-fg for emphasis without a banner backdrop, since
   the modal is already a focused surface). */
.sf-modal__error {
    margin: var(--space-8) 0 0;
    color: var(--danger-fg);
    font-size: var(--text-14, 14px);
}

/* ============================================================
   Explain-this modal (2026-05-27) — gold-standard dress-up of
   ExplainPackageModal. Three-zone layout (pinned header band /
   scrollable body / pinned footer toolbar) matching the Ops Center
   frame's token language. The modal previously had ZERO dedicated
   CSS and rendered on the bare .sf-modal shell. All values resolve
   from the token set; the four space/text tokens that don't exist
   yet carry inline px fallbacks (same convention as .sf-modal above).
   Design spec: docs/design/reviews/2026-05-27-explain-modal/.
   ============================================================ */

/* Pin the header + footer and let only the body scroll (the base
   .sf-modal-backdrop > .sf-modal scrolls the whole card). */
.sf-modal-backdrop > .sf-explain-modal { overflow: hidden; }

.sf-explain-modal__header {
    padding: var(--space-20) var(--space-24) var(--space-16);
    border-bottom: 1px solid var(--border);
    background: var(--bg-elevated);
    flex-shrink: 0;
}
.sf-explain-modal__title {
    font-size: var(--text-20);
    font-weight: var(--weight-semibold);
    color: var(--fg);
    margin: 0 0 var(--space-4);
    line-height: var(--leading-snug);
    letter-spacing: var(--track-tight);
}
.sf-explain-modal__lede {
    font-size: var(--text-14);
    color: var(--fg-muted);
    margin: 0;
    line-height: var(--leading-normal);
}

.sf-explain-modal__meta-row {
    display: flex;
    align-items: center;
    gap: var(--space-12);
    margin-top: var(--space-8);
    flex-wrap: wrap;
}
.sf-explain-modal__back-btn {
    display: inline-flex;
    align-items: center;
    gap: var(--space-4);
    height: 26px;
    padding: 0 var(--space-10, 10px);
    background: transparent;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-pill);
    color: var(--fg);
    font-family: var(--font-sans);
    font-size: var(--text-13);
    font-weight: var(--weight-medium);
    cursor: pointer;
    transition: background var(--motion-duration-fast) var(--motion-easing-out);
}
.sf-explain-modal__back-btn:hover:not([disabled]) { background: var(--bg-sunken); }
.sf-explain-modal__back-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.sf-explain-modal__back-btn[disabled] { opacity: 0.5; cursor: not-allowed; }
.sf-explain-modal__back-btn svg { flex-shrink: 0; }

.sf-explain-modal__cached-pill {
    display: inline-flex;
    align-items: center;
    gap: var(--space-4);
    height: 22px;
    padding: 0 var(--space-8);
    background: var(--success-bg);
    color: var(--success-fg);
    border-radius: var(--radius-pill);
    font-size: var(--text-12);
    font-weight: var(--weight-medium);
    line-height: 1;
}
.sf-explain-modal__cached-pill::before {
    content: '';
    width: 6px; height: 6px;
    border-radius: 50%;
    background: currentColor;
    flex-shrink: 0;
}
.sf-explain-modal__model-meta {
    font-size: var(--text-12);
    color: var(--fg-muted);
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
}

/* Download controls in the header (icon-only; tooltips + aria carry labels). */
.sf-explain-modal__dl-header {
    display: flex;
    align-items: center;
    gap: var(--space-6, 6px);
    margin-top: var(--space-12);
}
.sf-explain-modal__dl-label {
    font-size: var(--text-12);
    font-weight: var(--weight-semibold);
    color: var(--fg-muted);
    text-transform: uppercase;
    letter-spacing: var(--track-wide);
    margin-right: var(--space-4);
    white-space: nowrap;
}
.sf-explain-modal__dl-label::after {
    content: '';
    display: inline-block;
    width: 3px; height: 3px;
    border-radius: 50%;
    background: var(--fg-subtle);
    margin-left: var(--space-8);
    vertical-align: middle;
}
.sf-explain-modal__dl-btn {
    width: 28px; height: 28px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: 0;
    border-radius: var(--radius-6);
    color: var(--fg-muted);
    cursor: pointer;
    text-decoration: none;
    transition: background var(--motion-duration-fast) var(--motion-easing-out),
                color var(--motion-duration-fast) var(--motion-easing-out);
}
.sf-explain-modal__dl-btn:hover { background: var(--bg-sunken); color: var(--fg); }
.sf-explain-modal__dl-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Scrollable body zone. */
.sf-explain-modal__scroll {
    flex: 1 1 auto;
    overflow-y: auto;
    min-height: 200px;
    scrollbar-width: thin;
    scrollbar-color: var(--border-strong) transparent;
}

/* Audience picker grid. */
.sf-explain-modal__options {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-8);
    padding: var(--space-16) var(--space-24);
}
.sf-explain-modal__option {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    padding: var(--space-12) var(--space-14, 14px);
    background: var(--bg-sunken);
    border: 1.5px solid var(--border);
    border-radius: var(--radius-8);
    cursor: pointer;
    text-align: left;
    font-family: var(--font-sans);
    color: var(--fg);
    transition: border-color var(--motion-duration-fast) var(--motion-easing-out),
                background var(--motion-duration-fast) var(--motion-easing-out),
                box-shadow var(--motion-duration-fast) var(--motion-easing-out);
}
.sf-explain-modal__option:hover {
    border-color: var(--border-strong);
    background: var(--bg-elevated);
}
.sf-explain-modal__option--selected {
    border-color: var(--accent);
    background: color-mix(in srgb, var(--accent) 7%, var(--bg-elevated));
    box-shadow: 0 0 0 1px var(--accent) inset;
}
.sf-explain-modal__option:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.sf-explain-modal__option-icon {
    width: 16px; height: 16px;
    color: var(--fg-muted);
    flex-shrink: 0;
    margin-bottom: var(--space-2);
}
.sf-explain-modal__option--selected .sf-explain-modal__option-icon { color: var(--accent-fg); }
.sf-explain-modal__option-title {
    font-size: var(--text-14);
    font-weight: var(--weight-semibold);
    color: var(--fg);
    line-height: var(--leading-snug);
}
.sf-explain-modal__option-desc {
    font-size: var(--text-12);
    color: var(--fg-muted);
    line-height: var(--leading-normal);
}

/* Loading state — brand loader + caption. */
.sf-explain-modal__loading {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: var(--space-48) var(--space-24);
    gap: var(--space-16);
    min-height: 200px;
}
.sf-explain-modal__loading-caption {
    font-size: var(--text-14);
    color: var(--fg-muted);
    text-align: center;
    margin: 0;
    max-width: 280px;
    line-height: var(--leading-normal);
}

/* Result body — sf-prose typography, but cancel its centering so the
   modal card is the width constraint. */
.sf-explain-modal__body { padding: var(--space-20) var(--space-24); }
.sf-explain-modal__body.sf-prose { max-width: none; margin: 0; }

/* Footer toolbar. */
.sf-explain-modal__footer {
    display: flex;
    align-items: center;
    padding: var(--space-12) var(--space-16);
    border-top: 1px solid var(--border);
    background: var(--bg-sunken);
    gap: var(--space-8);
    flex-shrink: 0;
}
.sf-explain-modal__dl-toolbar {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    margin-right: auto;
}
.sf-explain-modal__toolbar-btn {
    width: 32px; height: 32px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--radius-6);
    color: var(--fg-muted);
    cursor: pointer;
    text-decoration: none;
    transition: background var(--motion-duration-fast) var(--motion-easing-out),
                color var(--motion-duration-fast) var(--motion-easing-out),
                border-color var(--motion-duration-fast) var(--motion-easing-out);
}
.sf-explain-modal__toolbar-btn:hover { background: var(--bg-elevated); border-color: var(--border); color: var(--fg); }
.sf-explain-modal__toolbar-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.sf-explain-modal__copy-btn {
    height: 32px;
    padding: 0 var(--space-12);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-6);
    background: transparent;
    color: var(--fg);
    font-family: var(--font-sans);
    font-size: var(--text-13);
    font-weight: var(--weight-medium);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: var(--space-6, 6px);
    transition: background var(--motion-duration-fast) var(--motion-easing-out);
    white-space: nowrap;
}
.sf-explain-modal__copy-btn:hover { background: var(--bg-elevated); }
.sf-explain-modal__copy-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.sf-explain-modal__toolbar-divider {
    width: 1px; height: 20px;
    background: var(--border);
    flex-shrink: 0;
    margin: 0 var(--space-4);
}
.sf-explain-modal__footer-actions {
    display: flex;
    align-items: center;
    gap: var(--space-8);
    flex-shrink: 0;
}

.sf-keyreveal__body { padding: var(--space-20, 20px) var(--space-24); display: flex; flex-direction: column; gap: var(--space-12); }
.sf-keyreveal__instruction { color: var(--fg); margin: 0; font-size: var(--text-14, 14px); line-height: 1.5; }
.sf-keyreveal__label { font-size: var(--text-12); font-weight: var(--weight-semibold); color: var(--fg-muted); text-transform: uppercase; letter-spacing: 0.04em; }
.sf-keyreveal__rowgroup { display: flex; gap: var(--space-8); align-items: stretch; }
.sf-keyreveal__rawbox {
    flex: 1;
    padding: var(--space-12);
    background: var(--bg-sunken);
    border: 1px solid var(--border);
    border-radius: var(--radius-6);
    font-size: var(--text-13, 13px);
    word-break: break-all;
    line-height: 1.4;
    user-select: all;
}
.sf-keyreveal__copy { white-space: nowrap; }
.sf-keyreveal__nag {
    padding: var(--space-12);
    background: color-mix(in srgb, var(--state-failed, #c33) 8%, transparent);
    border: 1px solid var(--state-failed, #c33);
    border-radius: var(--radius-6);
    color: var(--state-failed, #c33);
    font-size: var(--text-13, 13px);
}

.sf-create-key { display: flex; gap: var(--space-8); margin-top: var(--space-12); }
.sf-create-key .sf-input { flex: 1; }

/* Phase 9.5 follow-up — Interview Attached Files panel. Drag-drop
   uploader + per-file progress + grouped list (archives + children). */
.sf-attached-files { display: flex; flex-direction: column; gap: var(--space-12); margin-top: var(--space-16); }
.sf-attached-files__header { margin-bottom: var(--space-4); }
.sf-attached-files__title { margin: 0 0 var(--space-4); font-size: var(--text-14); font-weight: var(--weight-semibold); }
.sf-attached-files__lede { margin: 0; color: var(--fg-subtle); font-size: var(--text-12); }
.sf-attached-files__dropzone {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-8);
    padding: var(--space-16);
    background: var(--bg-sunken);
    border: 1px dashed var(--border);
    border-radius: var(--radius-8);
    cursor: pointer;
    text-align: center;
    transition: background var(--motion-duration-fast) var(--motion-easing-out),
                border-color var(--motion-duration-fast) var(--motion-easing-out);
}
.sf-attached-files__dropzone:hover,
.sf-attached-files__dropzone--drag {
    background: color-mix(in srgb, var(--accent) 6%, var(--bg-sunken));
    border-color: var(--accent);
}
.sf-attached-files__input {
    /* Hide the native input but keep it focusable + clickable via the
       wrapping label. */
    width: 1px; height: 1px; opacity: 0; position: absolute;
}
/* System-pages review (2026-05-02) — was --fg-default which doesn't
   exist; falls through to inherited color. Use --fg explicitly. */
.sf-attached-files__dropzone-cta { color: var(--fg); font-size: var(--text-13); }
.sf-attached-files__pending,
.sf-attached-files__list,
.sf-attached-files__children {
    list-style: none;
    margin: 0;
    padding: 0;
}
.sf-attached-files__pending-row,
.sf-attached-files__item-row {
    display: flex;
    align-items: center;
    gap: var(--space-8);
    padding: var(--space-8) 0;
    border-bottom: 1px solid var(--border);
}
.sf-attached-files__item-row:last-child { border-bottom: 0; }
.sf-attached-files__icon { font-size: 18px; }
.sf-attached-files__item-meta { flex: 1; min-width: 0; }
.sf-attached-files__children {
    margin-left: var(--space-24);
    padding-left: var(--space-12);
    border-left: 2px solid var(--border);
}
.sf-attached-files__child {
    display: flex;
    align-items: center;
    gap: var(--space-8);
    padding: var(--space-4) 0;
}

/* 2026-05-14 — v1-launch Batch C C-C4 (deferred follow-up).
   Shared system-page shell. Five surfaces functionally identical
   ("you can't get past this — here's why + here's the next step")
   but visually three different products in the prior state:
   Error.razor + AccessDenied.razor used `.sf-error-page`;
   AccountDisabled.razor + LegalToSAccept.razor used `.sf-prose`;
   OAuth/Consent.razor used `.sf-card sf-card--inline-top
   sf-oauth-consent`. Promoted the `.sf-error-page` rule set to
   `.sf-system-page` as the canonical shell — all 5 pages now
   render in a centered 640px-max card with --bg-elevated
   background, --radius-12 corners, 32px padding, and an h1 →
   lede → content → action row → footer-help vertical rhythm.
   `.sf-error-page` keeps the same rule body via the `,` selector
   so any third-party Razor surface still referencing the old
   class doesn't regress. */
.sf-system-page,
.sf-error-page {
    max-width: 640px;
    margin: var(--space-32) auto;
    padding: var(--space-32);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-12);
    text-align: center;
}
.sf-system-page h1,
.sf-error-page h1 { margin: 0 0 var(--space-12); }
.sf-system-page__lede,
.sf-error-page__lede { color: var(--fg-muted); margin-bottom: var(--space-24); }
.sf-system-page__trace,
.sf-error-page__trace {
    background: var(--bg-sunken);
    border: 1px solid var(--border);
    border-radius: var(--radius-8);
    padding: var(--space-12);
    margin: var(--space-16) 0 var(--space-24);
    text-align: left;
}
.sf-system-page__trace-label,
.sf-error-page__trace-label {
    font-size: 12px;
    color: var(--fg-subtle);
    margin-bottom: var(--space-4);
}
.sf-system-page__trace-id,
.sf-error-page__trace-id {
    display: block;
    overflow-x: auto;
    word-break: break-all;
    font-size: 12px;
}
.sf-system-page__actions,
.sf-error-page__actions {
    display: flex;
    gap: var(--space-12);
    justify-content: center;
    margin-bottom: var(--space-16);
}
.sf-system-page__footer,
.sf-error-page__footer { margin-top: var(--space-16); }
/* Optional left-aligned body content (used by AccountDisabled +
   LegalToSAccept that have body copy that doesn't read well
   centered). */
.sf-system-page__body {
    text-align: left;
    color: var(--fg);
}
.sf-system-page__body p { margin: var(--space-8) 0; line-height: 1.55; }

/* Phase 9.5 follow-up — Analytics dashboard chart layout.
   Two-column responsive grid (collapses to single column under
   ~720px). Each tile pairs a fixed-height canvas with a small
   summary table beneath, so the data is still legible if Chart.js
   fails to load. */
.sf-chart-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
    gap: var(--space-16);
    margin-top: var(--space-12);
}
.sf-chart-tile {
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-8);
    padding: var(--space-12);
}
.sf-chart-tile__caption {
    font-size: 12px;
    color: var(--fg-subtle);
    font-weight: var(--weight-semibold);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    margin-bottom: var(--space-8);
}
.sf-chart-tile__canvas-wrap {
    position: relative;
    height: 200px;
}
.sf-chart-tile__canvas-wrap--doughnut {
    height: 220px;
}
.sf-chart-tile__table {
    margin-top: var(--space-12);
}
.sf-chart-tile__table tbody tr td {
    padding: var(--space-4) var(--space-8);
}

/* Phase 9.5 follow-up — Profile panel role list.
   One role per row. Pre-fix the chips ran inline with no separator,
   producing run-on text like "SYSTEM ADMINISTRATORUNLIMITED ACCESS"
   when a user held two roles. Vertical list scales cleanly when a
   user holds many roles (Account Manager, Accounting, BI, Developer,
   etc., post-Phase-9.5). */
.sf-role-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
}
.sf-role-list > li { display: block; }

/* Phase 9.5 follow-up — Role Permissions matrix.
   2026-05-18 — TASK-243 serious facelift. The matrix is now a fully-
   fledged gold-standard surface: framed wrap, two-tier sticky
   header (category + per-permission), sticky role-name column,
   custom filled-dot cell affordance, info-icon popover for column
   descriptions, sysadmin row rendered as a sunken accent block.
   Permission labels rotate vertical via `writing-mode: vertical-rl`
   so the matrix fits a 1440 viewport without horizontal scroll.

   --matrix-cat-h is the height the category-row claims in sticky
   layout — the second sticky row offsets by it. Keep in sync with
   the .sf-permission-matrix__category line-height + padding. */
.sf-permission-matrix-wrap {
    --matrix-cat-h: 32px;
    --matrix-role-w: 200px;
    overflow-x: auto;
    border: 1px solid var(--border);
    border-radius: var(--radius-8);
    background: var(--bg-elevated);
    box-shadow: var(--shadow-1);
    margin-bottom: var(--space-16);
}

/* Matrix table — fixed cell sizing + collapsed borders so the
   sticky overlays sit clean. */
.sf-permission-matrix {
    border-collapse: separate;
    border-spacing: 0;
    width: 100%;
    table-layout: fixed;
}

/* Two-tier header. Row 1 (category colgroup) is the outer sticky
   layer (z 3); row 2 (per-permission columns) sticks below it at
   `top: var(--matrix-cat-h)` (z 3 too — same plane). The top-left
   corner gets z 4 so it stays above both row + column-sticky
   neighbors when scrolled. */
.sf-permission-matrix thead th {
    position: sticky;
    background: var(--bg-elevated);
    z-index: 3;
}
.sf-permission-matrix__category {
    top: 0;
    text-align: center;
    font-size: var(--text-12);
    font-weight: var(--weight-semibold);
    color: var(--fg-subtle);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding: var(--space-6) var(--space-8);
    border-bottom: 1px solid var(--border);
    border-left: 1px solid var(--border);
    height: var(--matrix-cat-h);
    line-height: 1.2;
}
.sf-permission-matrix__category:first-of-type { border-left: none; }

.sf-permission-matrix__corner {
    top: 0;
    left: 0;
    z-index: 4 !important;
    width: var(--matrix-role-w);
    min-width: var(--matrix-role-w);
    text-align: left;
    padding: var(--space-8) var(--space-12);
    border-right: 1px solid var(--border);
    border-bottom: 1px solid var(--border);
    font-weight: var(--weight-semibold);
    box-shadow: 1px 0 0 var(--border);
}

/* Per-permission column header. The label rotates bottom-to-top via
   writing-mode + 180deg rotate so longer DisplayNames don't blow
   out matrix width. Column is 44px wide × 132px tall — touch-target
   compliant + room for ~14 chars of label at --text-12. */
.sf-perm-col {
    top: var(--matrix-cat-h);
    width: 44px;
    min-width: 44px;
    max-width: 44px;
    height: 132px;
    vertical-align: bottom;
    text-align: center;
    padding: var(--space-4) 0 var(--space-6);
    border-left: 1px solid var(--border);
    border-bottom: 1px solid var(--border);
    position: relative;
}
.sf-perm-col__label {
    display: inline-block;
    writing-mode: vertical-rl;
    transform: rotate(180deg);
    white-space: nowrap;
    max-height: 108px;
    overflow: hidden;
    text-overflow: ellipsis;
    font-size: var(--text-12);
    font-weight: var(--weight-medium);
    color: var(--fg);
    line-height: 1.2;
}

/* Info-icon "?" button at the bottom of each column header. Hidden
   by default; fades in on `<th>` hover, on keyboard focus-within,
   or when its popover is open (aria-expanded="true"). Click toggles
   the popover; the CSS :hover route covers mouse users who don't
   want to click. */
.sf-perm-col__info {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    margin-top: var(--space-4);
    padding: 0;
    border-radius: 50%;
    border: 1px solid var(--border-strong);
    background: var(--bg-elevated);
    color: var(--fg-subtle);
    font-size: 10px;
    font-weight: var(--weight-semibold);
    line-height: 1;
    cursor: pointer;
    opacity: 0;
    transition: opacity 120ms ease-out, background-color 120ms, color 120ms;
}
.sf-perm-col:hover .sf-perm-col__info,
.sf-perm-col:focus-within .sf-perm-col__info,
.sf-perm-col__info[aria-expanded="true"] {
    opacity: 1;
}
.sf-perm-col__info:hover,
.sf-perm-col__info:focus-visible {
    background: var(--accent);
    color: white;
    border-color: var(--accent);
    outline: 2px solid color-mix(in srgb, var(--accent) 30%, transparent);
    outline-offset: 1px;
}

/* Popover. Hidden by default; shown when the <th>'s :hover state is
   active OR when JS state added the --open class. Anchored above
   the column header with a 220px max-width. Avoids the native
   `title=` delay + makes the description keyboard + touch
   reachable. */
.sf-perm-popover {
    position: absolute;
    bottom: calc(100% - var(--space-4));
    left: 50%;
    transform: translateX(-50%);
    width: 240px;
    background: var(--bg-elevated);
    color: var(--fg);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-6);
    box-shadow: var(--shadow-2);
    padding: var(--space-8) var(--space-12);
    z-index: 10;
    font-size: var(--text-12);
    font-weight: var(--weight-regular);
    line-height: 1.4;
    text-align: left;
    text-transform: none;
    letter-spacing: 0;
    opacity: 0;
    pointer-events: none;
    transition: opacity 120ms ease-out;
}
.sf-perm-popover strong {
    display: block;
    font-size: var(--text-13);
    font-weight: var(--weight-semibold);
    margin-bottom: var(--space-4);
}
.sf-perm-popover p {
    margin: 0 0 var(--space-6);
    color: var(--fg);
}
.sf-perm-popover code {
    display: inline-block;
    font-size: 11px;
    color: var(--fg-subtle);
    background: var(--bg-sunken);
    padding: 1px var(--space-4);
    border-radius: var(--radius-4);
}
.sf-perm-col:hover .sf-perm-popover,
.sf-perm-popover--open {
    opacity: 1;
    pointer-events: auto;
}

/* Sticky role-name column. Sits at left: 0 with z-index 2 so it
   stacks below the header (z 3/4) but above body cells (default 0).
   Right-side border shadow marks the divider when the table scrolls
   horizontally. */
.sf-perm-role {
    position: sticky;
    left: 0;
    z-index: 2;
    background: var(--bg-elevated);
    width: var(--matrix-role-w);
    min-width: var(--matrix-role-w);
    padding: var(--space-12) var(--space-12);
    border-bottom: 1px solid var(--border);
    border-right: 1px solid var(--border);
    box-shadow: 1px 0 0 var(--border);
    vertical-align: middle;
}
.sf-perm-role strong {
    display: block;
    font-size: var(--text-13);
    font-weight: var(--weight-semibold);
    color: var(--fg);
}
.sf-perm-role .sf-cell__sub {
    margin-top: var(--space-2);
    font-size: var(--text-12);
    color: var(--fg-subtle);
    line-height: 1.3;
}

/* Custom filled-dot cell affordance. The native checkbox is hidden
   visually but kept in the layout (opacity 0 + absolute) so keyboard
   + screen readers still hit it. The label is the click target;
   the sibling .sf-perm-grant__dot is the visible affordance. */
.sf-perm-cell {
    width: 44px;
    min-width: 44px;
    max-width: 44px;
    height: 44px;
    padding: 0;
    text-align: center;
    border-left: 1px solid var(--border);
    border-bottom: 1px solid var(--border);
    background: var(--bg-elevated);
    transition: background-color 120ms ease-out;
}
.sf-permission-matrix tbody tr:hover .sf-perm-cell {
    background: var(--bg-hover);
}
.sf-permission-matrix tbody tr:hover .sf-perm-role {
    background: var(--bg-hover);
}
.sf-perm-grant {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    cursor: pointer;
}
.sf-perm-grant__input {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
    margin: 0;
}
.sf-perm-grant__dot {
    display: inline-block;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    border: 1.5px solid var(--border-strong);
    background: transparent;
    transition: background-color 120ms ease-out, border-color 120ms, transform 120ms;
}
.sf-perm-grant__input:checked + .sf-perm-grant__dot {
    background: var(--accent);
    border-color: var(--accent);
}
.sf-perm-grant:hover .sf-perm-grant__dot {
    border-color: var(--accent);
}
.sf-perm-grant__input:focus-visible + .sf-perm-grant__dot {
    outline: 2px solid color-mix(in srgb, var(--accent) 40%, transparent);
    outline-offset: 2px;
}
.sf-perm-grant--pending .sf-perm-grant__dot {
    animation: sf-perm-pulse 0.9s ease-in-out infinite;
}
.sf-perm-grant__input:disabled {
    cursor: progress;
}
@keyframes sf-perm-pulse {
    0%, 100% { transform: scale(1); opacity: 1; }
    50% { transform: scale(0.85); opacity: 0.55; }
}

/* System Administrator row — rendered as a single sunken accent
   block instead of a colspan strip inside the grid. Reads as a
   category-distinct concept: "this role gets everything." */
.sf-perm-row--sysadmin .sf-perm-role {
    background: var(--bg-sunken);
    border-left: 3px solid var(--accent);
    padding-left: calc(var(--space-12) - 3px);
}
.sf-perm-sysadmin-cell {
    background: var(--bg-sunken);
    padding: var(--space-12) var(--space-16);
    border-bottom: 1px solid var(--border);
    color: var(--fg);
    text-align: left;
    vertical-align: middle;
}
.sf-perm-sysadmin-cell strong {
    font-size: var(--text-13);
    font-weight: var(--weight-semibold);
}
.sf-perm-sysadmin-cell .sf-cell__sub {
    margin-top: var(--space-2);
    font-size: var(--text-12);
    color: var(--fg-subtle);
    font-style: italic;
}

/* 2026-05-18 — TASK-243. Audit drawer (replaces the prior above-
   matrix mini-feed). Collapsed by default; <summary> shows the
   chevron + count badge. Mirrors the gold-standard "drawer below
   the table" pattern used elsewhere in the admin surface. */
.sf-audit-drawer {
    margin-top: var(--space-12);
    border: 1px solid var(--border);
    border-radius: var(--radius-8);
    background: var(--bg-elevated);
    overflow: hidden;
}
.sf-audit-drawer > summary {
    display: flex;
    align-items: center;
    gap: var(--space-8);
    padding: var(--space-10) var(--space-16);
    cursor: pointer;
    font-size: var(--text-13);
    font-weight: var(--weight-semibold);
    color: var(--fg);
    list-style: none;
    user-select: none;
}
.sf-audit-drawer > summary::-webkit-details-marker { display: none; }
.sf-audit-drawer__chevron {
    display: inline-block;
    transition: transform 150ms ease-out;
    color: var(--fg-subtle);
    font-size: 16px;
    line-height: 1;
}
.sf-audit-drawer[open] .sf-audit-drawer__chevron {
    transform: rotate(90deg);
}
.sf-audit-drawer__count {
    margin-left: auto;
    font-size: var(--text-12);
    font-weight: var(--weight-regular);
    color: var(--fg-subtle);
    padding: 2px var(--space-8);
    background: var(--bg-sunken);
    border-radius: var(--radius-pill);
}
.sf-audit-drawer__feed {
    list-style: none;
    margin: 0;
    padding: var(--space-8) var(--space-16) var(--space-12);
    display: flex;
    flex-direction: column;
    gap: var(--space-6);
    border-top: 1px solid var(--border);
}
.sf-audit-drawer__entry {
    display: flex;
    align-items: baseline;
    gap: var(--space-8);
    font-size: var(--text-13);
    line-height: 1.4;
}
.sf-audit-drawer__text {
    flex: 1;
    color: var(--fg);
}
.sf-audit-drawer__when {
    color: var(--fg-subtle);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* Mobile — keep the matrix but shrink the role column + reposition
   the popover below the header (above would clip on small
   viewports). Per the TASK-243 plan, the card-per-role mobile
   layout is deferred to a separate session. */
@media (max-width: 768px) {
    .sf-permission-matrix-wrap { --matrix-role-w: 140px; }
    .sf-perm-col__label { font-size: var(--text-11); max-height: 96px; }
    .sf-perm-popover {
        bottom: auto;
        top: calc(100% + var(--space-4));
    }
}

/* Phase 9.5 follow-up — user picker for the AI Agents grant table.
   Search input + native <select> dropdown + Add button laid out in a
   single horizontal row. The select stays narrow (no flex grow) so
   the search input dominates. */
.sf-user-picker {
    display: grid;
    grid-template-columns: 1fr 1.5fr auto;
    gap: var(--space-8);
    margin-bottom: var(--space-12);
}
.sf-user-picker > .sf-input { width: 100%; }

/* Keyvalue list (Profile "Account" section). */
.sf-keyvalue {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: var(--space-8) var(--space-16);
    margin: var(--space-12) 0 0;
}
.sf-keyvalue dt { color: var(--fg-muted); font-size: var(--text-13); }
.sf-keyvalue dd { margin: 0; color: var(--fg); }

/* Toggle list (Notifications + role-assignment drawer). */
.sf-toggles { list-style: none; padding: 0; margin: var(--space-12) 0 0; display: flex; flex-direction: column; gap: var(--space-8); }
.sf-toggle-row {
    display: flex; align-items: center; gap: var(--space-12);
    padding: var(--space-8) var(--space-12);
    background: var(--bg-sunken);
    border-radius: var(--radius-6);
}
.sf-toggle-row > div { flex: 1; }
.sf-toggle-row__label { font-weight: var(--weight-semibold); }
.sf-toggle-row__sub { color: var(--fg-muted); font-size: var(--text-12); }
.sf-switch input { width: 18px; height: 18px; }

/* Inline card variant (SMS verify code prompt). */
.sf-card--inline { margin-top: var(--space-12); }

/* Phase 9.5 follow-up — variant rendered by the page-level
   ErrorBoundary in MainLayout when a panel/component crashes. Soft
   warning colors so the user knows something went wrong without it
   feeling like a hard system failure. */
.sf-card--error {
    border-color: color-mix(in srgb, #ef4444 40%, var(--border));
    background: color-mix(in srgb, #ef4444 6%, transparent);
}
.sf-card--error h2 { margin-top: 0; }

/* Disabled-user row in the admin grid. */
.sf-row--muted td { opacity: 0.6; }

/* Subsection title within a panel. */
.sf-panel__subtitle {
    margin-top: var(--space-16);
    margin-bottom: var(--space-8);
    font-size: var(--text-16);
    font-weight: var(--weight-semibold);
}

/* ───────────────────────────────────────────────────────────────
   SpecStep loader package v1 — designer-supplied animated brand
   loader. Sourced from /Users/jerry/Downloads/SpecStep (1).zip
   (loader.css, designer-authored). Drop-in: keep the .ss-* class
   names exactly so a future designer refresh can replace this
   block verbatim. The animation is pure CSS keyframes; no JS,
   no SMIL. Respects prefers-reduced-motion.
   ─────────────────────────────────────────────────────────────── */

@keyframes ss-draw {
    0%   { stroke-dashoffset: 100; }
    55%  { stroke-dashoffset: 0; }
    85%  { stroke-dashoffset: 0; }
    100% { stroke-dashoffset: 100; }
}

@keyframes ss-pop {
    0%, 18%   { opacity: 0; transform: scale(0.5); }
    22%, 85%  { opacity: 1; transform: scale(1); }
    100%      { opacity: 0; transform: scale(0.5); }
}

.ss-loader               { display: inline-block; line-height: 0; }
.ss-loader .ss-path      {
    stroke-dasharray: 100;
    stroke-dashoffset: 100;
    animation: ss-draw 2.6s cubic-bezier(0.7, 0, 0.3, 1) infinite;
}
.ss-loader .ss-node      {
    opacity: 0;
    transform-box: fill-box;
    transform-origin: center;
    animation: ss-pop 2.6s cubic-bezier(0.7, 0, 0.3, 1) infinite;
}
.ss-loader .ss-n1        { animation-delay: 0.36s; }
.ss-loader .ss-n2        { animation-delay: 0.72s; }
.ss-loader .ss-n3        { animation-delay: 1.08s; }

@media (prefers-reduced-motion: reduce) {
    .ss-loader .ss-path,
    .ss-loader .ss-node { animation: none; opacity: 1; stroke-dashoffset: 0; }
}

/* Project additions: caption wrapper + full-screen overlays. The
   designer's .ss-loader selector stays reserved for the SVG itself
   per their package; .sf-loader-wrap holds the optional caption. */
.sf-loader-wrap {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-8);
}
.sf-loader-wrap__caption {
    font-size: var(--text-12, 12px);
    color: var(--fg-muted, var(--fg));
}
.sf-loader-wrap--lg .sf-loader-wrap__caption {
    font-size: var(--text-14, 14px);
}

/* Initial-load splash: shown via inline CSS at <body> mount time
   and removed by the inline boot script in App.razor once Blazor
   has attached. z-index high enough to cover the SSR shell. */
#sf-initial-splash {
    position: fixed;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--bg);
    z-index: 9999;
}
#sf-initial-splash.sf-initial-splash--hidden {
    display: none;
}

/* Reconnect overlay: Blazor toggles classes on this exact id when
   the SignalR circuit drops, reconnects, fails, or is rejected.
   We keep the framework's class-name contract and just supply the
   markup + styling. The default state is hidden; the show class
   reveals it. */
#components-reconnect-modal {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(15, 18, 38, 0.72);
    z-index: 10000;
    align-items: center;
    justify-content: center;
    flex-direction: column;
    gap: var(--space-16);
    padding: var(--space-24);
    color: #FAFAF7;
    text-align: center;
}
#components-reconnect-modal.components-reconnect-show {
    display: flex;
}
/* TASK-240 (2026-05-18) — `failed` / `rejected` states are now
   auto-reloaded by the MutationObserver in App.razor's boot script.
   Keep the modal hidden during those states so the user never sees
   the "please refresh" text in the brief window before the reload
   fires. The `show` caption (the retry-in-progress cue) is the only
   visible state now. */
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-rejected {
    display: none;
}
#components-reconnect-modal .sf-reconnect__caption-show { display: block; }

/* Initiative G4 (2026-05-03) — AI Agents row layout: 32px avatar
   plus a two-line name (character name primary, role label as the
   small subtitle). Used in Settings → AI Agents on /admin. */
.sf-agent-row { display: flex; align-items: center; gap: var(--space-12); }
.sf-agent-row__avatar {
    width: 32px; height: 32px;
    border-radius: var(--radius-6);
    background: var(--bg-sunken);
    flex: 0 0 32px;
}
.sf-agent-row__name { display: flex; flex-direction: column; line-height: var(--leading-snug); }

/* Initiative G4 (2026-05-03) — per-agent System Administrator
   detail page at /admin/agents/{slug}. Hero with the agent's
   intro animation + accent stripe; identity card with the
   immutable per-agent metadata; public-presentation editor
   bound to AgentAudience.PublicSummary + ShowOnHomePage; design
   asset gallery; collapsed read-only "current rules" pre block.
   The --agent-accent inline custom property comes from the
   page's HeroAccentStyle() so each agent's stripe pulls its
   own signature color without per-agent CSS rules. */
.sf-agent-detail { display: flex; flex-direction: column; gap: var(--space-32); max-width: 920px; }
.sf-agent-detail__hero {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: var(--space-32);
    align-items: center;
    padding: var(--space-32);
    border-radius: var(--radius-12);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-left: 4px solid var(--agent-accent, var(--accent));
}
.sf-agent-detail__hero-art { display: flex; align-items: center; justify-content: center; }
.sf-agent-detail__hero-text { display: flex; flex-direction: column; gap: var(--space-8); }
.sf-agent-detail__hero-text h1 { margin: 0; font-size: var(--text-32); }
.sf-agent-detail__role-label { margin: 0; color: var(--fg-muted); font-size: var(--text-16); }
.sf-agent-detail__tagline { margin: 0; color: var(--fg); font-size: var(--text-16); }

.sf-agent-detail__identity {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: var(--space-16) var(--space-32);
    margin: var(--space-12) 0;
}
.sf-agent-detail__identity > div { display: flex; flex-direction: column; gap: var(--space-2); }
.sf-agent-detail__identity dt {
    font-size: var(--text-12); color: var(--fg-subtle);
    text-transform: uppercase; letter-spacing: var(--track-wide, 0.04em);
    font-weight: var(--weight-semibold);
}
.sf-agent-detail__identity dd { margin: 0; color: var(--fg); font-size: var(--text-14); }

.sf-agent-detail__gallery {
    display: flex; flex-wrap: wrap; gap: var(--space-16);
    margin: var(--space-12) 0;
}
.sf-agent-detail__tile {
    display: flex; flex-direction: column; gap: var(--space-4);
    align-items: center; margin: 0;
    padding: var(--space-8);
    background: var(--bg-sunken);
    border-radius: var(--radius-6);
}
.sf-agent-detail__tile img { display: block; }
.sf-agent-detail__tile figcaption { font-size: var(--text-12); color: var(--fg-subtle); }

.sf-agent-detail__downloads {
    list-style: none; padding: 0; margin: var(--space-8) 0;
    display: flex; flex-wrap: wrap; gap: var(--space-12);
}
.sf-agent-detail__downloads a { color: var(--accent-fg); }

.sf-agent-detail__motion {
    display: flex; flex-wrap: wrap; gap: var(--space-24);
    margin: var(--space-12) 0;
}
.sf-agent-detail__motion figure { display: flex; flex-direction: column; gap: var(--space-4); align-items: center; margin: 0; }
.sf-agent-detail__motion figcaption { font-size: var(--text-12); color: var(--fg-subtle); }

.sf-agent-detail__rules summary {
    cursor: pointer; padding: var(--space-8) 0;
    font-weight: var(--weight-semibold); color: var(--accent-fg);
}
.sf-agent-detail__rules pre {
    background: var(--bg-sunken);
    padding: var(--space-16);
    border-radius: var(--radius-6);
    overflow-x: auto;
    font-family: var(--font-mono);
    font-size: var(--text-13);
    line-height: var(--leading-snug);
    white-space: pre-wrap;
    color: var(--fg);
    border: 1px solid var(--border);
}

.sf-form-actions { display: flex; gap: var(--space-12); margin-top: var(--space-12); }

.sf-panel__sub {
    margin: var(--space-16) 0 var(--space-8);
    font-size: var(--text-14);
    font-weight: var(--weight-semibold);
    color: var(--fg-muted);
    text-transform: uppercase;
    letter-spacing: var(--track-wide, 0.04em);
}

@media (max-width: 768px) {
    .sf-agent-detail__hero { grid-template-columns: 1fr; text-align: center; }
    .sf-agent-detail__hero-art { justify-content: center; }
}

/* ─────────────────────────────────────────────────────────────────
   Initiative H (2026-05-03) — marketing-site overhaul.
   Adds four sections to the landing page in this order:
     hero → BrandLineCallout → OurProcessSection → MeetTheTeamSection
     → anatomy → AiCoderPositioningSection → sign-in
   Per the design subagent's review, sections alternate between
   --bg / --bg-sunken / --bg-elevated for visual rhythm without
   introducing color. All token references — no hex literals.
   ───────────────────────────────────────────────────────────────── */

/* Brand-line slab — between hero and process, full-bleed.
   No section heading; the line stands alone. Verbatim copy is
   locked; do not paraphrase. */
.sf-brand-line {
    background: var(--bg-sunken);
    border-top: 1px solid var(--border);
    border-bottom: 1px solid var(--border);
    padding: var(--space-32) var(--space-24);
    text-align: center;
    margin: var(--space-32) calc(-1 * var(--space-24));
}
.sf-brand-line__text {
    margin: 0;
    font-size: var(--text-24);
    font-weight: var(--weight-semibold);
    color: var(--fg);
    letter-spacing: var(--track-tight, -0.01em);
    line-height: var(--leading-snug);
}

/* Our process — hub-and-spokes diagram with Otto in the center
   and 6 satellite specialists around him. Dashed connectors
   communicate "live, dynamic" rather than scripted pipeline.
   Below the diagram, a 3-step caption row pins the phases. */
.sf-process {
    padding: var(--space-48) var(--space-24);
    text-align: center;
}
.sf-process__heading {
    margin: 0 0 var(--space-12);
    font-size: var(--text-32);
    font-weight: var(--weight-semibold);
    color: var(--fg);
    letter-spacing: var(--track-tight, -0.01em);
}
.sf-process__subhead {
    max-width: 56ch;
    margin: 0 auto var(--space-32);
    font-size: var(--text-16);
    color: var(--fg-muted);
    line-height: var(--leading-normal);
}

/* The diagram itself: a CSS grid that places the hub in the center
   and the 6 satellites in fixed N/NE/SE/S/SW/NW slots around it.
   Connection lines are SVG-free — drawn with a layered radial-gradient
   "ring" pseudo-element that suggests the hub's reach without needing
   per-line geometry. */
.sf-process__diagram {
    position: relative;
    margin: 0 auto;
    width: min(640px, 100%);
    aspect-ratio: 1 / 1;
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    grid-template-rows: 1fr 1fr 1fr;
    place-items: center;
    gap: var(--space-12);
}
.sf-process__diagram::before {
    /* Dashed ring connecting the hub to all 6 satellites — a single
       circular border serves as the "all consultations are live"
       visual cue without overlapping any specific tile. */
    content: "";
    position: absolute;
    inset: 12%;
    border: 1.5px dashed var(--border-strong);
    border-radius: 50%;
    pointer-events: none;
}
.sf-process__hub {
    grid-column: 2;
    grid-row: 2;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-4);
    padding: var(--space-12);
    background: var(--bg-elevated);
    border: 3px solid var(--agent-accent, var(--accent));
    border-radius: var(--radius-12);
    box-shadow: var(--shadow-2);
    z-index: 1;
}
.sf-process__hub-avatar { width: 80px; height: 80px; }
.sf-process__hub-label {
    font-size: var(--text-20);
    font-weight: var(--weight-semibold);
    color: var(--fg);
}
.sf-process__hub-role {
    font-size: var(--text-12);
    color: var(--fg-muted);
}
.sf-process__satellite {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-4);
    padding: var(--space-8);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-top: 3px solid var(--agent-accent, var(--border));
    border-radius: var(--radius-8);
    transition: transform var(--motion-duration-fast) var(--motion-easing-out),
                box-shadow var(--motion-duration-fast) var(--motion-easing-out),
                border-top-width var(--motion-duration-fast) var(--motion-easing-out);
    min-width: 96px;
}
.sf-process__satellite:hover {
    transform: translateY(-2px);
    box-shadow: var(--shadow-2);
    border-top-width: 4px;
}
.sf-process__satellite-avatar { width: 40px; height: 40px; }
.sf-process__satellite-label {
    font-size: var(--text-12);
    font-weight: var(--weight-semibold);
    color: var(--fg);
}
/* Slot positions on a 3×3 grid: N=top-center, NE=top-right,
   SE=bottom-right, S=bottom-center, SW=bottom-left, NW=top-left. */
.sf-process__satellite--n  { grid-column: 2; grid-row: 1; }
.sf-process__satellite--ne { grid-column: 3; grid-row: 1; }
.sf-process__satellite--se { grid-column: 3; grid-row: 3; }
.sf-process__satellite--s  { grid-column: 2; grid-row: 3; }
.sf-process__satellite--sw { grid-column: 1; grid-row: 3; }
.sf-process__satellite--nw { grid-column: 1; grid-row: 1; }

.sf-process__phases {
    list-style: none;
    margin: var(--space-32) auto 0;
    padding: 0;
    max-width: 840px;
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-24);
    text-align: left;
}
.sf-process__phase {
    display: flex;
    gap: var(--space-12);
    align-items: flex-start;
}
.sf-process__phase-num {
    flex: 0 0 32px;
    width: 32px;
    height: 32px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: var(--accent-700);
    color: white;
    font-size: var(--text-14);
    font-weight: var(--weight-semibold);
}
.sf-process__phase-body {
    font-size: var(--text-14);
    color: var(--fg-muted);
    line-height: var(--leading-normal);
}
.sf-process__phase-body strong {
    display: block;
    color: var(--fg);
    margin-bottom: 2px;
}

/* Meet the Team — 4-col grid of agent tiles. Reads from
   IAgentAudienceUpdateService filtered to ShowOnHomePage = true.
   Each tile is --bg-elevated with a 3px --agent-{slug} top border
   plus 1px --border on the other sides. SVG headshots sit on the
   transparent card; no circular crop (the full-body silhouette
   has irregular features the antenna/arms — clipping breaks it). */
.sf-meet-team {
    padding: var(--space-48) var(--space-24);
    text-align: center;
}
.sf-meet-team__heading {
    margin: 0 0 var(--space-12);
    font-size: var(--text-32);
    font-weight: var(--weight-semibold);
    color: var(--fg);
    letter-spacing: var(--track-tight, -0.01em);
}
.sf-meet-team__subhead {
    max-width: 56ch;
    margin: 0 auto var(--space-32);
    font-size: var(--text-16);
    color: var(--fg-muted);
    line-height: var(--leading-normal);
}
.sf-meet-team__loaderror,
.sf-meet-team .sf-cell__sub { text-align: center; }
.sf-meet-team__grid {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: var(--space-24);
    align-items: stretch;
}
.sf-meet-team__tile {
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-top: 3px solid var(--agent-accent, var(--border));
    border-radius: var(--radius-12);
    transition: transform var(--motion-duration-fast) var(--motion-easing-out),
                box-shadow var(--motion-duration-fast) var(--motion-easing-out),
                border-top-width var(--motion-duration-fast) var(--motion-easing-out);
    overflow: hidden;
}
.sf-meet-team__tile:hover {
    transform: translateY(-2px);
    box-shadow: var(--shadow-2);
    border-top-width: 4px;
}
@* 2026-05-03 — tile is now an <a> wrapping the content; the link
   needs the original tile's flex layout + its own focus ring so the
   click affordance + keyboard a11y both work. *@
.sf-meet-team__tile-link {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding: var(--space-24) var(--space-16);
    color: inherit;
    text-decoration: none;
    height: 100%;
}
.sf-meet-team__tile-link:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: var(--radius-8);
}
.sf-meet-team__tile-headshot {
    margin-bottom: var(--space-16);
    height: 96px;
    display: flex;
    align-items: center;
    justify-content: center;
}
.sf-meet-team__tile-headshot img {
    width: 96px;
    height: 96px;
    display: block;
}
.sf-meet-team__tile-name {
    margin: 0;
    font-size: var(--text-20);
    font-weight: var(--weight-semibold);
    color: var(--fg);
}
.sf-meet-team__tile-role {
    margin: var(--space-4) 0 var(--space-12);
    font-size: var(--text-14);
    color: var(--fg-muted);
}
.sf-meet-team__tile-summary {
    margin: 0;
    font-size: var(--text-14);
    color: var(--fg);
    line-height: var(--leading-normal);
}

/* AI-coder positioning strip — narrow --bg-elevated section above
   sign-in. Names the AI coding agents the packages are designed
   for. Text labels (no fake-looking SVG vendor marks, per the
   designer's H1 guidance). */
.sf-ai-coder {
    padding: var(--space-48) var(--space-24);
    background: var(--bg-elevated);
    border-top: 1px solid var(--border);
    border-bottom: 1px solid var(--border);
    text-align: center;
    margin: var(--space-32) calc(-1 * var(--space-24)) 0;
}
.sf-ai-coder__heading {
    margin: 0 0 var(--space-12);
    font-size: var(--text-24);
    font-weight: var(--weight-semibold);
    color: var(--fg);
    letter-spacing: var(--track-tight, -0.01em);
}
.sf-ai-coder__body {
    max-width: 56ch;
    margin: 0 auto var(--space-24);
    font-size: var(--text-16);
    color: var(--fg-muted);
    line-height: var(--leading-normal);
}
.sf-ai-coder__targets {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--space-24);
}
.sf-ai-coder__target {
    font-size: var(--text-14);
    font-weight: var(--weight-semibold);
    color: var(--fg);
    padding: var(--space-8) var(--space-16);
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    background: var(--bg);
}
/* Public per-agent page — sanitized version of the sysadmin
   /admin/agents/{slug} detail page, served at /agents/{slug}.
   Marketing-layout chrome with the same hero pattern + a slim
   summary block + back-to-team CTA. Added 2026-05-03. */
.sf-agent-public {
    display: flex;
    flex-direction: column;
    gap: var(--space-32);
    max-width: 920px;
    margin: 0 auto;
}
.sf-agent-public__hero {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: var(--space-32);
    align-items: center;
    padding: var(--space-32);
    border-radius: var(--radius-12);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-left: 4px solid var(--agent-accent, var(--accent));
}
.sf-agent-public__hero-art { display: flex; align-items: center; justify-content: center; }
.sf-agent-public__hero-text { display: flex; flex-direction: column; gap: var(--space-8); }
.sf-agent-public__hero-text h1 { margin: 0; font-size: var(--text-32); }
.sf-agent-public__role-label { margin: 0; color: var(--fg-muted); font-size: var(--text-16); }
.sf-agent-public__tagline { margin: 0; color: var(--fg); font-size: var(--text-16); }
.sf-agent-public__summary {
    padding: 0 var(--space-32);
    max-width: 56ch;
    margin: 0 auto;
    text-align: center;
}
.sf-agent-public__summary p {
    margin: var(--space-12) 0 0;
    font-size: var(--text-16);
    color: var(--fg);
    line-height: var(--leading-normal);
}
.sf-agent-public__cta {
    display: flex;
    gap: var(--space-12);
    justify-content: center;
    flex-wrap: wrap;
    padding: var(--space-16) var(--space-24);
}
@media (max-width: 768px) {
    .sf-agent-public__hero { grid-template-columns: 1fr; text-align: center; }
    .sf-agent-public__hero-art { justify-content: center; }
}

/* "Top-secret mission" empty-state — shown on /agents/{slug} when
   the agent's AgentAudience.ShowOnHomePage flag is OFF. Fun-but-
   professional: agent's accent color tints the SVG background and
   the left border, so the page still reads as THIS agent's page
   even while it's off-roster. Mirrors .sf-agent-public's two-column
   shape so the layout transition between visible / hidden states is
   structural, not jarring. Added 2026-05-23. */
.sf-agent-secret {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: var(--space-32);
    align-items: center;
    max-width: 920px;
    margin: 0 auto;
    padding: var(--space-32);
    border-radius: var(--radius-12);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-left: 4px solid var(--agent-accent, var(--accent));
}
.sf-agent-secret__art {
    display: flex;
    align-items: center;
    justify-content: center;
}
.sf-agent-secret__art svg {
    width: 240px;
    height: 240px;
    display: block;
}
.sf-agent-secret__body {
    display: flex;
    flex-direction: column;
    gap: var(--space-12);
}
.sf-agent-secret__eyebrow {
    margin: 0;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: var(--text-12);
    text-transform: uppercase;
    letter-spacing: 0.16em;
    color: var(--agent-accent, var(--fg-muted));
}
.sf-agent-secret__title {
    margin: 0;
    font-size: var(--text-28);
    line-height: var(--leading-tight);
}
.sf-agent-secret__lede {
    margin: 0;
    font-size: var(--text-16);
    color: var(--fg);
    line-height: var(--leading-normal);
}
.sf-agent-secret__cta {
    display: flex;
    gap: var(--space-12);
    flex-wrap: wrap;
    margin-top: var(--space-12);
}
@media (max-width: 768px) {
    .sf-agent-secret {
        grid-template-columns: 1fr;
        text-align: center;
    }
    .sf-agent-secret__art {
        justify-content: center;
    }
    .sf-agent-secret__cta {
        justify-content: center;
    }
}

/* "Three surfaces" sentence under the vendor pill row. Restored
   2026-05-03; was lost when the "Why SpecStep" 3-card section was
   dissolved in Initiative H. */
.sf-ai-coder__surfaces {
    max-width: 56ch;
    margin: var(--space-24) auto 0;
    font-size: var(--text-14);
    color: var(--fg-muted);
    line-height: var(--leading-normal);
}
.sf-ai-coder__surfaces strong {
    color: var(--fg);
    font-weight: var(--weight-semibold);
}

/* Mobile fallbacks — Initiative H sections collapse cleanly
   below 1080px (4-col → 2-col on team grid) and 768px
   (single-column for everything; diagram condenses). */
@media (max-width: 1080px) {
    .sf-meet-team__grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 768px) {
    .sf-brand-line__text { font-size: var(--text-20); }
    .sf-process__heading,
    .sf-meet-team__heading { font-size: var(--text-24); }
    .sf-process__diagram {
        /* On phones the 3×3 ring is too cramped — collapse the
           satellites into a flex-wrap below the hub. */
        aspect-ratio: auto;
        display: flex;
        flex-direction: column;
        align-items: center;
        gap: var(--space-16);
    }
    .sf-process__diagram::before { display: none; }
    .sf-process__hub,
    .sf-process__satellite {
        grid-column: auto !important;
        grid-row: auto !important;
    }
    .sf-process__phases { grid-template-columns: 1fr; }
}
@media (max-width: 540px) {
    .sf-meet-team__grid { grid-template-columns: 1fr; }
}

/* ====================================================================
   Phase B (API documentation, 2026-05-03; revised same day to drop
   the left sidebar) — section shell for the /api-docs/* pages.
   MainLayout already has a left rail (Workspace / Interview /
   Settings / Admin / Billing); putting a sidebar here would be a
   second left column for signed-in users. Sub-navigation is a
   horizontal pill strip at the top of the content area instead.
   The prose itself reuses .sf-prose so the typography matches About,
   FAQ, Privacy.
   ==================================================================== */
.sf-apidocs {
    display: flex;
    flex-direction: column;
    gap: var(--space-24);
    max-width: 1100px;
    margin: 0 auto;
    padding: var(--space-32) var(--space-24);
}
/* UI/UX D-08 (2026-05-04) — was flex-wrap: wrap; with 8 links the
   ApiDocs sub-nav rendered as a 3-row pill stack on narrow viewports.
   Switch to a horizontally-scrollable single row so the rest of the
   page chrome stays predictable. */
.sf-apidocs__subnav {
    display: flex;
    flex-wrap: nowrap;
    gap: var(--space-8);
    padding-bottom: var(--space-12);
    border-bottom: 1px solid var(--border);
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}
.sf-apidocs__navlink {
    display: inline-flex;
    align-items: center;
    padding: 6px var(--space-12);
    border-radius: var(--radius-6);
    color: var(--fg-muted);
    text-decoration: none;
    font-size: var(--text-14);
    font-weight: var(--weight-medium);
    flex-shrink: 0;
    transition: background var(--motion-duration-fast) var(--motion-easing-out),
                color var(--motion-duration-fast) var(--motion-easing-out);
}
.sf-apidocs__navlink:hover {
    background: var(--bg-elevated);
    color: var(--fg);
}
.sf-apidocs__navlink.active {
    background: var(--accent-100, var(--bg-elevated));
    color: var(--accent-700);
    font-weight: var(--weight-semibold);
}
.sf-apidocs__content {
    /* 2026-05-13 — was max-width: 72ch (≈ 560px at the body font),
       which left the content + TOC bunched on the LEFT with ~220px
       of empty space on the right and the TOC stranded mid-page.
       The fix: let the content column flex-grow so the TOC pins to
       the row's right edge. The inner article (.sf-prose under .sf-apidocs)
       gets its own override below. */
    min-width: 0;
    flex: 1 1 auto;
}

/* Codex round-7 (2026-05-13) — right-rail table of contents. The
   layout block is a flex row that pairs the content column with a
   sticky right rail. The rail hides below 1024px; the content
   column expands to fill the row. */
.sf-apidocs__layout {
    display: flex;
    gap: var(--space-32);
    align-items: flex-start;
}
.sf-apidocs__toc-rail {
    width: 240px;
    flex: 0 0 240px;
    position: sticky;
    top: var(--space-24);
    max-height: calc(100vh - var(--space-48));
    overflow-y: auto;
    padding-left: var(--space-16);
    /* 2026-05-13 — hide the visible scrollbar but keep the wheel /
       trackpad scroll behavior. The rail's max-height + overflow:auto
       were producing a permanent thin scrollbar even on the FAQ where
       the TOC overflowed by only 54px — visually noisy and the
       scroll-spy already moves the active highlight as the user
       scrolls the page, so a tiny inner scrollbar adds no value. */
    scrollbar-width: none;
}
.sf-apidocs__toc-rail::-webkit-scrollbar {
    display: none;
}
.sf-apidocs__toc {
    font-size: var(--text-13);
    line-height: 1.5;
}
.sf-apidocs__toc-title {
    margin: 0 0 var(--space-8);
    color: var(--fg-subtle);
    font-size: var(--text-12);
    font-weight: var(--weight-semibold);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.sf-apidocs__toc-list {
    list-style: none;
    padding: 0;
    margin: 0;
    border-left: 1px solid var(--border);
}
.sf-apidocs__toc-link {
    display: block;
    padding: 4px var(--space-12);
    color: var(--fg-muted);
    text-decoration: none;
    border-left: 2px solid transparent;
    margin-left: -1px;
    line-height: 1.4;
    transition: color var(--motion-duration-fast) var(--motion-easing-out),
                border-color var(--motion-duration-fast) var(--motion-easing-out);
}
.sf-apidocs__toc-link:hover {
    color: var(--fg);
}
.sf-apidocs__toc-link--h3 {
    padding-left: var(--space-24);
    font-size: var(--text-12);
}
.sf-apidocs__toc-link--active {
    /* 2026-05-18 — was var(--accent-700) (#117b50 dark green); failed
       WCAG color-contrast in dark mode where the rail sits on near-
       black --bg (#0f1115). var(--accent-fg) is the theme-aware token
       that flips to #6ad8a3 under [data-theme="dark"] — same trap +
       fix already documented at app.css:6871-6872. */
    color: var(--accent-fg);
    border-left-color: var(--accent);
    font-weight: var(--weight-medium);
}

@media (max-width: 1024px) {
    .sf-apidocs__toc-rail { display: none; }
}

@media (max-width: 768px) {
    .sf-apidocs {
        padding: var(--space-16);
    }
}

/* 2026-05-18 — marketing-site launch-readiness P1 #8. Customer
   feedback `019e38dc-044b` measured ~268px of horizontal overflow
   on /api-docs/mcp at 390px viewport. Root cause: long inline code
   tokens (e.g., `/v1/feedback/templates/{id}/{version}`) and table
   cells with non-breakable content pushed their containers past
   the document width. The fixes below:

     (a) `.sf-apidocs .sf-prose code` gets word-break / overflow-wrap
         so long path-like inline tokens wrap inside their cells +
         paragraphs instead of forcing layout to grow.
     (b) `.sf-apidocs .sf-prose table` gets wrapped in an internal
         horizontal-scroll surface — the table's outer container
         clips to the row width and the table itself scrolls
         independently. CSS-only via `display: block; overflow-x:
         auto`, no markup change needed.
     (c) `.sf-apidocs__content` declares `min-width: 0` (already)
         and `max-width: 100%` so the flex-child can't grow past
         its row, even when descendants have intrinsic widths.

   Mobile-only — the desktop layout has room for long inline tokens
   without breaking, and forcing word-break there would mar the
   typography. */
@media (max-width: 768px) {
    .sf-apidocs__content {
        max-width: 100%;
    }
    .sf-apidocs .sf-prose code {
        word-break: break-word;
        overflow-wrap: anywhere;
    }
    .sf-apidocs .sf-prose table {
        display: block;
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        max-width: 100%;
    }
}

/* 2026-05-14 — v1-launch Batch B H-B3. Mobile TOC twin. Hidden on
   desktop (the sticky right rail is visible there); shown < 1024px
   as a collapsible <details> element inside the content column.
   The native triangle indicator carries the open/closed state. */
.sf-apidocs__toc-mobile {
    display: none;
    margin: 0 0 var(--space-24);
    padding: var(--space-12) var(--space-16);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-6);
}
.sf-apidocs__toc-mobile-summary {
    cursor: pointer;
    color: var(--fg);
    font-weight: var(--weight-semibold);
    font-size: var(--text-14);
    list-style: revert;
}
.sf-apidocs__toc-mobile[open] .sf-apidocs__toc-mobile-summary {
    margin-bottom: var(--space-8);
}
.sf-apidocs__toc-mobile .sf-apidocs__toc {
    margin-top: var(--space-4);
}
.sf-apidocs__toc-mobile .sf-apidocs__toc-list {
    border-left: 1px solid var(--border);
}
@media (max-width: 1024px) {
    .sf-apidocs__toc-mobile { display: block; }
}

/* 2026-05-14 — v1-launch Batch B H-B6. Anonymous visitors route
   through MarketingLayout, which scopes `.sf-marketing .sf-prose
   code/pre` to the legacy mono stack ("JetBrains Mono", "SF Mono",
   Menlo). The signed-in shell renders the same docs in Cascadia
   Code per --font-mono. Re-pinning code+pre inside `.sf-apidocs
   .sf-prose` to the platform mono token closes the cross-shell
   discrepancy: same docs page, same Cascadia render, regardless
   of who's viewing. Order matters — this rule sits AFTER the
   .sf-marketing override block at app.css line ~3299 so the
   cascade resolves to var(--font-mono) on /api-docs/* surfaces. */
.sf-apidocs .sf-prose code,
.sf-apidocs .sf-prose pre {
    font-family: var(--font-mono);
}

/* 2026-05-14 — v1-launch Batch B PR-B3. Enhanced code-block shell
   wrapped around every fenced code block by
   ApiDocsCodeBlockEnhancer. Shell = head (language label + copy
   button) + the original `<pre><code>` body underneath. The pre
   stays the scrollable surface; the head is sticky-to-top so it
   doesn't scroll out when the body is long. */
.sf-code {
    margin: 0 0 var(--space-16);
    background: var(--bg-sunken);
    border: 1px solid var(--border);
    border-radius: var(--radius-6);
    overflow: hidden;
}
.sf-code__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--space-4) var(--space-12);
    background: var(--bg-elevated);
    border-bottom: 1px solid var(--border);
    font-family: var(--font-sans);
    font-size: var(--text-12);
}
.sf-code__lang {
    color: var(--fg-subtle);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-weight: var(--weight-semibold);
}
.sf-code__copy {
    appearance: none;
    border: 1px solid transparent;
    background: transparent;
    color: var(--fg-muted);
    font-family: var(--font-sans);
    font-size: var(--text-12);
    font-weight: var(--weight-medium);
    padding: 2px 8px;
    border-radius: 999px;
    cursor: pointer;
    transition: color 0.15s, border-color 0.15s, background 0.15s;
}
.sf-code__copy:hover {
    color: var(--fg);
    border-color: var(--border);
}
.sf-code__copy:focus-visible {
    outline: 2px solid var(--accent-fg);
    outline-offset: 2px;
}
.sf-code__copy[data-sf-copy-state="copied"] {
    color: var(--success-fg);
    border-color: var(--success-border, var(--success-fg));
    background: var(--success-bg, transparent);
}
.sf-code > pre {
    margin: 0;
    border: none;
    border-radius: 0;
    background: var(--bg-sunken);
}
/* 2026-05-14 — v1-launch Batch B H-B7. JSON colorizer token
   classes. Five classes keep the palette legible while using
   existing platform tokens so dark mode + light mode both work
   without further per-rule overrides. */
.sf-code__key      { color: var(--accent-fg); }
.sf-code__string   { color: var(--success-fg); }
.sf-code__number   { color: var(--warning-fg); }
.sf-code__keyword  { color: var(--info-fg); font-weight: var(--weight-medium); }
.sf-code__comment  { color: var(--fg-subtle); font-style: italic; }
@media (prefers-reduced-motion: reduce) {
    .sf-code__copy { transition: none !important; }
}

/* 2026-05-14 — v1-launch Batch B H-B15 (full). MarkdownContent.razor
   hoists the page H1 and the leading "> Last updated:" blockquote
   out of the body into a dedicated header element. The eyebrow
   ("API docs") frames the page name; the body prose opens with the
   lede paragraph below. Replaces the partial fix that styled the
   blockquote in place — the blockquote is no longer rendered in
   the body at all. */
.sf-apidocs__header {
    margin: 0 0 var(--space-32);
    padding-bottom: var(--space-16);
    border-bottom: 1px solid var(--border);
}
.sf-apidocs__header-eyebrow {
    margin: 0;
    font-family: var(--font-mono);
    font-size: var(--text-12);
    font-weight: var(--weight-medium);
    color: var(--fg-subtle);
    letter-spacing: 0.08em;
    text-transform: uppercase;
}
.sf-apidocs__header-title {
    margin: var(--space-8) 0 0;
    font-size: clamp(28px, 4vw, 36px);
    line-height: 1.2;
    color: var(--ink);
}
.sf-apidocs__header-meta {
    margin: var(--space-8) 0 0;
    font-family: var(--font-mono);
    font-size: var(--text-12);
    color: var(--fg-subtle);
}

/* 2026-05-14 — v1-launch Batch B H-B8 (deferred follow-up). When the
   ApiDocsTablePillEnhancer wraps a body cell in a status pill we
   tag it with `.sf-apidocs__pill` so we can tweak the in-table
   layout without changing the global pill rules. The base
   `.sf-pill` uses `display: inline-flex` which forces an unwanted
   baseline shift inside a `<td>`; switch to inline-block + bump
   line-height so multi-line cells (which include the pill on the
   same line as wrapping body text) sit on the same baseline. */
.sf-apidocs .sf-prose table .sf-apidocs__pill {
    display: inline-block;
    line-height: 1.4;
}
/* Cells in pill-treated columns get padding tightened — the
   default `.sf-prose th/td` padding looks heavy when the cell
   carries a single small chip. */
.sf-apidocs .sf-prose table td:has(> .sf-apidocs__pill) {
    white-space: nowrap;
}

/* 2026-05-13 — FAQ-specific outer wrapper for the right-rail TOC
   layout. The FAQ reuses .sf-apidocs__layout / .sf-apidocs__content /
   ApiDocsToc but is rendered inside the marketing chrome's full-bleed
   <main>, NOT inside the API docs' .sf-apidocs container. Without
   this wrapper the flex row stretches full-viewport and the content +
   TOC bunch up at the left with vast empty space on the right.

   The wrapper applies the same 1100px cap + centering as .sf-apidocs.
   With the .sf-prose override below the FAQ article fills the content
   column instead of being double-capped (the .sf-apidocs__content
   72ch cap was lifted globally above, so only .sf-prose remains).
   Result inside .sf-faq-page:
       [ article (~780px) ][ 32px gap ][ TOC 240px ]
   centered in a 1100px container, TOC pinned to the right edge. */
.sf-faq-page {
    max-width: 1100px;
    margin: 0 auto;
    padding: var(--space-32) var(--space-24);
}
.sf-faq-page .sf-prose { max-width: none; margin: 0; }

/* 2026-05-13 — same fix on /api-docs/*. The article inside
   .sf-apidocs__content uses .sf-prose which caps at 720px; with the
   72ch cap lifted from .sf-apidocs__content, removing the .sf-prose
   cap inside .sf-apidocs lets the article fill the content column. */
.sf-apidocs .sf-prose { max-width: none; margin: 0; }

@media (max-width: 768px) {
    .sf-faq-page { padding: var(--space-16); }
}

/* 2026-05-15 — Reusable wide-prose page wrapper for /about, /support,
   /release-notes. Generalizes the .sf-faq-page shape (1100px outer +
   right-rail TOC slot) so any marketing page with substantial content
   can fill the screen rather than rendering as a 720px column.
   Legal pages (/terms, /privacy, /tos) intentionally keep the narrow
   .sf-prose default — reading-line-length is the typographic optimum
   for legal text.

   Inside .sf-prose-page, the global .sf-prose 720px cap is dropped so
   the article column fills the layout's content slot. To opt a child
   article BACK into the narrow reading column (e.g. Support's body
   text under the wide tile grid), wrap it in .sf-prose-page__column. */
.sf-prose-page {
    max-width: 1100px;
    margin: 0 auto;
    padding: var(--space-32) var(--space-24);
}
.sf-prose-page .sf-prose { max-width: none; margin: 0; }
.sf-prose-page__column { max-width: 720px; margin: 0 auto; }

/* Hero header at the top of a .sf-prose-page. 2026-05-15 — flattened
   to match FAQ's natural prose-h1 flow (FAQ uses <article class="sf-prose"
   ><h1> ...</h1> directly with no surrounding band). The prior shape
   (48px top padding + border-bottom + 32px bottom margin) read as
   "too big" with too much whitespace above the title. Now the header
   is just a structural wrapper; the h1 + meta line render flush with
   the article column below. */
.sf-prose-page__hero {
    margin-bottom: var(--space-16);
}
.sf-prose-page__hero h1 {
    font-size: clamp(36px, 4.5vw, 56px);
    margin: 0 0 var(--space-8);
    line-height: 1.05;
    color: var(--ink);
}
.sf-prose-page__hero__meta {
    color: var(--fg-subtle);
    font-size: var(--text-13, 13px);
    line-height: 1.5;
    margin: 0;
}
.sf-prose-page__hero__meta a {
    color: var(--ink);
    text-decoration: underline;
    text-decoration-color: var(--otto-accent);
    text-decoration-thickness: 2px;
    text-underline-offset: 3px;
}
@media (max-width: 768px) {
    .sf-prose-page { padding: var(--space-16); }
}

/* ====================================================================
   Phase B (status page initiative, 2026-05-04) — public /status,
   /status/uptime, /status/history. Horizontal sub-nav at the top of
   the content area; NO left sidebar (MainLayout already has a rail).
   Status colors reuse the existing --success / --warning / --danger
   tokens with -bg / -border / -fg variants.
   ==================================================================== */
.sf-status {
    max-width: 1100px;
    margin: 0 auto;
    padding: var(--space-32) var(--space-24);
    display: flex;
    flex-direction: column;
    gap: var(--space-24);
}
.sf-status__header { display: flex; flex-direction: column; gap: var(--space-4); }
.sf-status__heading { font-size: var(--text-32); font-weight: var(--weight-semibold); margin: 0; }
.sf-status__lede { color: var(--fg-muted); font-size: var(--text-16); margin: 0; }
/* UI/UX D-08 (2026-05-04) — sub-nav scrolls horizontally on narrow
   viewports rather than wrapping to multiple rows. */
.sf-status__subnav {
    display: flex; flex-wrap: nowrap; gap: var(--space-8);
    padding-bottom: var(--space-12);
    border-bottom: 1px solid var(--border);
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}
.sf-status__navlink {
    display: inline-flex; align-items: center;
    padding: 6px var(--space-12);
    border-radius: var(--radius-6);
    color: var(--fg-muted);
    text-decoration: none;
    font-size: var(--text-14);
    font-weight: var(--weight-medium);
    flex-shrink: 0;
    transition: background var(--motion-duration-fast) var(--motion-easing-out),
                color var(--motion-duration-fast) var(--motion-easing-out);
}
.sf-status__navlink:hover { background: var(--bg-elevated); color: var(--fg); }
.sf-status__navlink.active {
    background: var(--accent-100, var(--bg-elevated));
    color: var(--accent-700);
    font-weight: var(--weight-semibold);
}
.sf-status__content { display: flex; flex-direction: column; gap: var(--space-24); }

/* overall banner */
.sf-status__banner {
    display: flex; align-items: center; gap: var(--space-12);
    padding: var(--space-16) var(--space-20);
    border: 1px solid var(--border);
    border-radius: var(--radius-8);
    font-weight: var(--weight-medium);
}
.sf-status__banner-dot { width: 12px; height: 12px; border-radius: 50%; flex: none; background: currentColor; }
.sf-status__banner-text { flex: 1; }
/* UI/UX D-02 (2026-05-04) — was --weight-normal which doesn't exist in tokens.css. */
.sf-status__banner-meta { color: var(--fg-muted); font-size: var(--text-13); font-weight: var(--weight-regular); }
.sf-status__banner--operational { background: var(--success-bg); border-color: var(--success-border); color: var(--success-fg); }
.sf-status__banner--degraded    { background: var(--warning-bg); border-color: var(--warning-border); color: var(--warning-fg); }
.sf-status__banner--outage      { background: var(--danger-bg);  border-color: var(--danger-border);  color: var(--danger-fg); }
/* UI/UX D-09 (2026-05-04) — neutral info banner for transactional
   confirmations (subscribed / unsubscribed). Reserves the green
   operational variant for actual system-health signals. */
.sf-status__banner--info        { background: var(--info-bg);    border-color: var(--info-border);    color: var(--info-fg); }
.sf-status__banner--unknown     { background: var(--bg-elevated); color: var(--fg-muted); }

/* UI/UX D-01 (2026-05-04) — --text-18 isn't in tokens.css; bump
   to --text-20 so section headings actually look like headings. */
.sf-status__section-heading { font-size: var(--text-20); font-weight: var(--weight-semibold); margin: 0 0 var(--space-12) 0; }

/* service list */
.sf-status__services {
    display: flex; flex-direction: column;
    border: 1px solid var(--border);
    border-radius: var(--radius-8);
    overflow: hidden;
    background: var(--bg-elevated);
}
.sf-status__services .sf-status__section-heading {
    padding: var(--space-12) var(--space-16);
    margin: 0;
    border-bottom: 1px solid var(--border);
    background: var(--bg);
}
.sf-status__service-list { list-style: none; margin: 0; padding: 0; }
.sf-status__service {
    display: flex; align-items: center; justify-content: space-between;
    padding: var(--space-12) var(--space-16);
    border-bottom: 1px solid var(--border);
}
.sf-status__service:last-child { border-bottom: 0; }
.sf-status__service-name { font-weight: var(--weight-medium); }
.sf-status__service-pill {
    display: inline-flex; align-items: center; gap: var(--space-8);
    padding: 4px var(--space-12);
    border-radius: 999px;
    font-size: var(--text-13);
    font-weight: var(--weight-medium);
    border: 1px solid transparent;
}
.sf-status__service-dot { width: 8px; height: 8px; border-radius: 50%; background: currentColor; }
.sf-status__service-pill--operational { background: var(--success-bg); color: var(--success-fg); border-color: var(--success-border); }
.sf-status__service-pill--degraded    { background: var(--warning-bg); color: var(--warning-fg); border-color: var(--warning-border); }
.sf-status__service-pill--outage      { background: var(--danger-bg);  color: var(--danger-fg);  border-color: var(--danger-border); }
.sf-status__service-pill--unknown     { background: var(--bg); color: var(--fg-muted); border-color: var(--border); }

/* active incidents */
.sf-status__active { display: flex; flex-direction: column; gap: var(--space-12); }
.sf-status__incident {
    border: 1px solid var(--border);
    border-left-width: 4px;
    border-radius: var(--radius-8);
    padding: var(--space-16) var(--space-20);
    background: var(--bg-elevated);
}
.sf-status__incident--minor    { border-left-color: var(--warning-border); }
.sf-status__incident--major    { border-left-color: var(--warning-fg); }
.sf-status__incident--critical { border-left-color: var(--danger-fg); }
.sf-status__incident-header { display: flex; align-items: center; gap: var(--space-12); flex-wrap: wrap; }
/* UI/UX D-11 (2026-05-04) — anchor metadata spans so a long title
   doesn't squeeze the severity badge to zero width. */
.sf-status__incident-severity { font-size: var(--text-12); text-transform: uppercase; letter-spacing: 0.05em; color: var(--fg-muted); font-weight: var(--weight-semibold); flex-shrink: 0; }
.sf-status__incident-title { flex: 1; margin: 0; font-size: var(--text-16); font-weight: var(--weight-semibold); min-width: 0; }
.sf-status__incident-service { color: var(--fg-muted); font-size: var(--text-13); flex-shrink: 0; }
.sf-status__incident-summary { margin: var(--space-8) 0 0 0; color: var(--fg); }
.sf-status__incident-update {
    margin-top: var(--space-12);
    padding-top: var(--space-12);
    border-top: 1px dashed var(--border);
    display: flex; flex-wrap: wrap; gap: var(--space-8);
    color: var(--fg-muted);
    font-size: var(--text-13);
}
.sf-status__update-status { text-transform: capitalize; font-weight: var(--weight-semibold); color: var(--fg); }
.sf-status__update-when { font-variant-numeric: tabular-nums; }

/* subscribe form
   2026-05-14 — v1-launch Batch C H-C8 (deferred follow-up).
   `.sf-status__subscribe--strip` modifier collapses the previously
   full-width card-with-stacked-form into a single-row strip:
   heading + email + button on one row. Padding tightens; we drop
   the lede paragraph entirely (heading folds the value-prop in).
   At ≤640px the row stacks (heading on top, form below) so narrow
   viewports stay usable. */
.sf-status__subscribe {
    border: 1px solid var(--border);
    border-radius: var(--radius-8);
    padding: var(--space-16) var(--space-20);
    background: var(--bg-elevated);
}
.sf-status__subscribe--strip {
    padding: var(--space-12) var(--space-16);
}
.sf-status__subscribe-strip-row {
    display: flex;
    align-items: center;
    gap: var(--space-16);
    flex-wrap: wrap;
}
.sf-status__subscribe-strip-heading {
    margin: 0;
    flex: 1 1 240px;
    color: var(--fg);
    font-size: var(--text-14);
    font-weight: var(--weight-medium);
    line-height: 1.4;
}
.sf-status__subscribe-lede { margin: 0 0 var(--space-12) 0; color: var(--fg-muted); font-size: var(--text-14); }
.sf-status__subscribe-form { display: flex; gap: var(--space-8); align-items: stretch; flex: 1 1 320px; }
.sf-status__subscribe-label { flex: 1; display: flex; }
.sf-status__subscribe-input {
    flex: 1;
    padding: 8px var(--space-12);
    border: 1px solid var(--border);
    border-radius: var(--radius-6);
    background: var(--bg);
    color: var(--fg);
    font-size: var(--text-14);
}
.sf-status__subscribe-input:focus { outline: 2px solid var(--accent-600, var(--accent-700)); outline-offset: 1px; }
.sf-status__subscribe-button {
    padding: 8px var(--space-16);
    border: 1px solid var(--accent-700);
    border-radius: var(--radius-6);
    background: var(--accent-700);
    color: white;
    font-weight: var(--weight-semibold);
    cursor: pointer;
}
.sf-status__subscribe-button:hover:not(:disabled) { background: var(--accent-600, var(--accent-700)); }
.sf-status__subscribe-button:disabled { opacity: 0.6; cursor: progress; }
.sf-status__subscribe-feedback { margin: var(--space-8) 0 0 0; color: var(--fg-muted); font-size: var(--text-13); }
.sf-status__subscribe-feedback--error { color: var(--danger-fg); }

/* uptime grid */
.sf-uptime__grid { display: flex; flex-direction: column; gap: var(--space-12); }
.sf-uptime__row {
    display: grid;
    grid-template-columns: minmax(180px, 1fr) 3fr;
    gap: var(--space-16);
    align-items: center;
    padding: var(--space-12);
    border: 1px solid var(--border);
    border-radius: var(--radius-8);
    background: var(--bg-elevated);
}
.sf-uptime__service { display: flex; flex-direction: column; gap: var(--space-4); }
.sf-uptime__service-name { font-weight: var(--weight-medium); }
.sf-uptime__percent { font-size: var(--text-13); color: var(--fg-muted); font-variant-numeric: tabular-nums; }
.sf-uptime__days {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(8px, 1fr));
    gap: 2px;
}
/* UI/UX D-03 (2026-05-04) — square cells (status.claude.com style)
   instead of the previous portrait-aspect "barcode" rendering. */
.sf-uptime__day {
    display: block;
    aspect-ratio: 1;
    min-height: 16px;
    max-height: 32px;
    border-radius: 2px;
    background: var(--success-fg);
    border: 0;
    cursor: pointer;
}
.sf-uptime__day:focus-visible {
    outline: 2px solid var(--accent-700);
    outline-offset: 2px;
}
.sf-uptime__day--operational { background: var(--success-fg); }
.sf-uptime__day--degraded    { background: var(--warning-fg); }
.sf-uptime__day--outage      { background: var(--danger-fg); }
.sf-uptime__day--unknown     { background: var(--border); }

/* history list */
.sf-status__history {
    list-style: none; margin: 0; padding: 0;
    display: flex; flex-direction: column; gap: var(--space-16);
}
.sf-status__history-item {
    border: 1px solid var(--border);
    border-left-width: 4px;
    border-radius: var(--radius-8);
    padding: var(--space-16) var(--space-20);
    background: var(--bg-elevated);
}
.sf-status__history-item--minor    { border-left-color: var(--warning-border); }
.sf-status__history-item--major    { border-left-color: var(--warning-fg); }
.sf-status__history-item--critical { border-left-color: var(--danger-fg); }
.sf-status__history-header { display: flex; align-items: center; gap: var(--space-12); flex-wrap: wrap; }
.sf-status__history-severity { font-size: var(--text-12); text-transform: uppercase; letter-spacing: 0.05em; color: var(--fg-muted); font-weight: var(--weight-semibold); }
.sf-status__history-title { flex: 1; margin: 0; font-size: var(--text-16); font-weight: var(--weight-semibold); }
.sf-status__history-meta { margin-top: var(--space-8); display: flex; flex-wrap: wrap; gap: var(--space-12); color: var(--fg-muted); font-size: var(--text-13); }
.sf-status__history-summary { margin: var(--space-8) 0 0 0; }
.sf-status__history-updates {
    list-style: none; margin: var(--space-12) 0 0 0;
    padding: var(--space-12) 0 0 0;
    border-top: 1px dashed var(--border);
    display: flex; flex-direction: column; gap: var(--space-8);
}
.sf-status__history-update { display: flex; flex-wrap: wrap; gap: var(--space-8); font-size: var(--text-13); }

/* empty / loading / back link */
.sf-status__loading,
.sf-status__empty { color: var(--fg-muted); padding: var(--space-24); text-align: center; }
/* UI/UX D-12 (2026-05-04) — use --accent-fg for theme-safe contrast
   in dark mode (where --accent-700 sits on a near-black background). */
.sf-status__back-link { color: var(--accent-fg, var(--accent-700)); font-weight: var(--weight-medium); }

@media (max-width: 768px) {
    .sf-status { padding: var(--space-16); }
    .sf-uptime__row { grid-template-columns: 1fr; gap: var(--space-8); }
}

/* PR #23 (2026-05-04) — /billing routable sub-pages.
   2026-05-18 — TASK-236 follow-on: removed alongside BillingLayout.razor.
   The Finance dashboard at /billing/dashboard uses AdminTabStrip; the
   sole remaining /billing/* friendly URL (/billing/user-billing) now
   renders under MainLayout with no per-section subnav. */


/* Bundle 6 PR 3 (2026-05-04) — admin smoke test card on /admin/generations */
.sf-smoke-card { display: flex; flex-direction: column; gap: var(--space-16); }
.sf-smoke-card__header { display: flex; flex-direction: column; gap: var(--space-4); }
.sf-smoke-card__alert {
    background: rgba(239, 68, 68, 0.08);
    border: 1px solid rgba(239, 68, 68, 0.4);
    color: rgb(185, 28, 28);
    padding: var(--space-12);
    border-radius: 6px;
    font-size: var(--text-14);
}
.sf-smoke-card__actions { display: flex; align-items: center; gap: var(--space-12); }
.sf-smoke-card__jobid { font-size: var(--text-12); color: var(--fg-muted); }
.sf-smoke-card__live {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-16);
    align-items: center;
    padding: var(--space-12);
    background: var(--bg-subtle);
    border-radius: 6px;
}
.sf-smoke-card__metric { font-size: var(--text-13); color: var(--fg-muted); }
.sf-smoke-card__metric strong { color: var(--fg); font-weight: var(--weight-semibold); }
.sf-smoke-card__reason { width: 100%; font-size: var(--text-13); color: var(--fg-subtle); margin-top: var(--space-4); }
.sf-smoke-card__history-heading { margin: var(--space-12) 0 var(--space-8); font-size: var(--text-14); font-weight: var(--weight-semibold); }
.sf-smoke-card__history { width: 100%; }
.sf-smoke-card__status { font-weight: var(--weight-semibold); display: inline-flex; gap: 6px; align-items: center; }
.sf-smoke-card__status--pass { color: rgb(34, 134, 58); }
.sf-smoke-card__status--fail { color: rgb(185, 28, 28); }
.sf-smoke-card__status--warn { color: rgb(180, 83, 9); }

/* ---------------------------------------------------------------- *
 * PR-M2 (2026-05-06) — Generation Details in-flight surface.       *
 *                                                                  *
 * Selectors here back the new live conversation feed + cost row +  *
 * progress bar + active-agent indicator added to GenerationDetail. *
 * The feed bubble + counter components have their own .razor.css   *
 * (CSS-isolated); only the page-level chrome lives here.           *
 * ---------------------------------------------------------------- */

/* Cost row — "Cost  $1.23  est. $0.90–$1.40 (median $1.10, n=23)".
   Variation A (compact single-line) from the design-reviewer mock. */
.sf-cost-row {
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-8);
    padding: var(--space-12) var(--space-16);
    margin-bottom: var(--space-16);
}
.sf-cost-row__label {
    font-size: var(--text-12);
    color: var(--fg-subtle);
    letter-spacing: var(--track-wide);
    font-weight: var(--weight-semibold);
    text-transform: uppercase;
    margin-bottom: var(--space-8);
}
.sf-cost-row__body {
    display: flex; align-items: baseline; flex-wrap: wrap;
    gap: var(--space-4) var(--space-16);
}
.sf-cost-row__running {
    font-size: var(--text-20);
    font-weight: var(--weight-semibold);
    font-variant-numeric: tabular-nums;
    color: var(--fg);
}
.sf-cost-row__range {
    font-size: var(--text-13);
    color: var(--fg-muted);
}

/* Progress-block wrapper — sits between the metadata header and
   the pipeline strip. Owns vertical rhythm only. */
.sf-gd-progress-block {
    margin-bottom: var(--space-16);
}

/* Active-agent indicator — present-tense "Working now: Otto" with
   a pulsing live dot. Distinct from the past-tense conversation
   feed entries below. */
.sf-gd-active-agent {
    display: flex; align-items: center; gap: var(--space-12);
    padding: var(--space-8) var(--space-12);
    background: color-mix(in srgb, var(--accent) 6%, transparent);
    border: 1px solid color-mix(in srgb, var(--accent) 20%, transparent);
    border-radius: var(--radius-8);
    font-size: var(--text-13);
    margin-top: var(--space-12);
    margin-bottom: var(--space-12);
}
.sf-gd-active-agent__label { color: var(--fg-muted); }
.sf-gd-active-agent__name {
    color: var(--accent-fg);
    font-weight: var(--weight-semibold);
}
.sf-gd-live-dot {
    width: 7px; height: 7px; border-radius: 50%;
    background: var(--accent);
    display: inline-block;
    animation: sf-live-pulse 1.4s ease-in-out infinite;
    flex: none;
}
@keyframes sf-live-pulse {
    0%, 100% { opacity: 1; transform: scale(1); }
    50%      { opacity: 0.45; transform: scale(0.7); }
}
@media (prefers-reduced-motion: reduce) {
    .sf-gd-live-dot { animation: none; }
}

/* Conversation-feed section heading — matches the design-mock's
   uppercase eyebrow pattern with an optional pill count badge. */
.sf-gd-feed-heading {
    margin: var(--space-16) 0 var(--space-12);
    font-size: var(--text-13);
    color: var(--fg-subtle);
    letter-spacing: var(--track-wide);
    font-weight: var(--weight-semibold);
    text-transform: uppercase;
    display: flex; align-items: center; gap: var(--space-8);
}
.sf-gd-feed-heading__count {
    font-size: var(--text-12);
    color: var(--fg-subtle);
    background: var(--bg-sunken);
    border-radius: var(--radius-pill);
    padding: 1px var(--space-8);
}

/* TASK-185 hotfix (2026-05-09) — promoted from
   AgentConversationFeed.razor.css to global. Blazor scoped CSS
   attributes ([b-xxx]) only apply to elements declared in Razor
   markup syntax — not to elements built via RenderTreeBuilder. The
   bubble's inner DOM (body, stats row, role label, narration) is
   built imperatively in RenderBubble at AgentConversationFeed.razor:351,
   so scoped selectors targeting these classes never matched. The
   stats row was rendering as a default block div with no flex / gap,
   collapsing the duration + cost spans into a single run of text
   (`17.2s$0.0112` instead of `17.2s   $0.0112`).
   Classes are already namespaced (sf-gd-feed__*) — no global-scope
   collision risk. */
.sf-gd-feed__item .sf-gd-feed__bubble {
    border-left: 3px solid var(--bubble-accent, var(--border));
    border-radius: 0 var(--radius-12, 12px) var(--radius-12, 12px) 0;
}
/* 2026-05-25 — uniform full-width feed bubbles. The agent
   conversation feed (the "Agent conversation" / "Agent activity log"
   section on GenerationDetail) reads as a consistent activity log,
   so every bubble fills its content column rather than shrinking to
   content like ragged speech bubbles. Interview.razor's chat keeps
   the global .sf-turn__bubble 640px shrink-to-content cap — this
   override is scoped to the feed (.sf-gd-feed__item / .sf-gd-feed__bubble
   inside .sf-now-playing) so that chat is untouched, and excludes the
   compact workspace row (already a single line). min-width:0 lets the
   bubble shrink below its content's intrinsic width so long
   unbreakable tokens wrap instead of overflowing the column. */
.sf-now-playing:not(.sf-now-playing--compact) .sf-gd-feed__item {
    width: 100%;
}
.sf-now-playing:not(.sf-now-playing--compact) .sf-gd-feed__bubble {
    flex: 1 1 auto;
    max-width: 100%;
    min-width: 0;
}
.sf-gd-feed__role {
    font-size: var(--text-12, 12px);
    color: var(--fg-subtle);
    text-transform: uppercase;
    letter-spacing: var(--track-wide, 0.06em);
}
.sf-gd-feed__body {
    margin-bottom: var(--space-4, 4px);
}
.sf-gd-feed__narration {
    color: var(--fg-muted);
    font-size: var(--text-13, 13px);
    line-height: var(--leading-snug, 1.4);
    margin-top: var(--space-4, 4px);
}
.sf-gd-feed__stats {
    display: flex;
    gap: var(--space-12, 12px);
    margin-top: var(--space-8, 8px);
    font-size: var(--text-12, 12px);
    color: var(--fg-subtle);
    font-variant-numeric: tabular-nums;
}
.sf-gd-feed__stat--failed {
    color: var(--danger-fg, #c0392b);
    font-weight: var(--weight-semibold, 600);
}
.sf-gd-feed__stat--retried {
    color: var(--fg-muted);
}
.sf-gd-feed__item--failed { opacity: 0.55; }
/* Compact body density override (mirrors the scoped file). */
.sf-gd-feed--compact .sf-gd-feed__body {
    margin-bottom: 0;
    color: var(--fg-muted);
    font-size: var(--text-12, 12px);
}
/* TASK-176 (2026-05-08) — diff-stat chip (+lines / -lines).
   Promoted from scoped to global per the same TASK-185 hotfix
   reasoning above: RenderDiffStat at AgentConversationFeed.razor:443
   uses RenderTreeBuilder, so the scoped attribute never reached the
   chip and the pill styling silently no-op'd. */
.sf-diffstat {
    display: inline-flex;
    align-items: center;
    gap: var(--space-4, 4px);
    font-family: var(--font-mono);
    font-size: var(--text-12, 12px);
    font-weight: var(--weight-medium, 500);
    font-variant-numeric: tabular-nums;
    line-height: 1;
}
.sf-diffstat__added,
.sf-diffstat__removed {
    display: inline-flex;
    align-items: center;
    padding: 2px 6px;
    border-radius: var(--radius-pill, 999px);
}
.sf-diffstat__added {
    background: var(--success-bg);
    color: var(--success-fg);
}
.sf-diffstat__removed {
    background: var(--danger-bg);
    color: var(--danger-fg);
}
/* Diff-stat right-align contexts. */
.sf-gd-feed__stats .sf-diffstat {
    margin-left: auto;
}
.sf-gd-feed--compact .sf-turn__meta .sf-diffstat {
    margin-left: auto;
}
/* Compact-mode diff-stat density tweak. */
.sf-gd-feed--compact .sf-diffstat__added,
.sf-gd-feed--compact .sf-diffstat__removed {
    padding: 1px 5px;
}

/* 2026-05-23 — cost-visibility PR #2. INTERNAL cost block — shows
   the raw LLM spend AND the billed amount (cost × pricing multiplier)
   on conversation bubbles for admin / accounting personas. Dashed
   outline + INTERNAL badge are load-bearing UX: anyone reading over
   an operator's shoulder must be able to tell this isn't customer-
   facing data at a glance. The component renders nothing for regular
   users (LOC chip stays as the only signal). Gated upstream by
   Permissions.CostViewInternal. Promoted to global for the same
   RenderTreeBuilder reasoning as .sf-diffstat above. */
.sf-cost-internal {
    display: inline-flex;
    align-items: center;
    gap: var(--space-6, 6px);
    padding: 2px 6px;
    border: 1px dashed var(--warning-fg, var(--accent-fg));
    border-radius: var(--radius-pill, 999px);
    background: var(--warning-bg, var(--bg-2, transparent));
    font-family: var(--font-mono);
    font-size: var(--text-12, 12px);
    font-variant-numeric: tabular-nums;
    line-height: 1;
}
.sf-cost-internal__badge {
    font-size: var(--text-10, 10px);
    font-weight: var(--weight-semibold, 600);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--warning-fg, var(--accent-fg));
    padding: 1px 4px;
    border-radius: var(--radius-2, 2px);
    background: color-mix(in srgb, var(--warning-fg, var(--accent-fg)) 12%, transparent);
}
.sf-cost-internal__rows {
    display: inline-flex;
    flex-direction: column;
    gap: 1px;
    line-height: 1.15;
}
.sf-cost-internal__row {
    display: inline-flex;
    align-items: baseline;
    gap: var(--space-4, 4px);
}
.sf-cost-internal__label {
    font-size: var(--text-10, 10px);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-2, var(--text));
    opacity: 0.75;
    min-width: 4.25em;
}
.sf-cost-internal__value {
    font-weight: var(--weight-medium, 500);
    color: var(--text);
}
/* Right-align contexts mirror .sf-diffstat. */
.sf-gd-feed__stats .sf-cost-internal {
    margin-left: auto;
}
.sf-gd-feed--compact .sf-turn__meta .sf-cost-internal {
    margin-left: var(--space-6, 6px);
}
/* Compact-mode density tweak — the meta line is tight. */
.sf-gd-feed--compact .sf-cost-internal {
    padding: 1px 5px;
}
.sf-gd-feed--compact .sf-cost-internal__label {
    min-width: 3.5em;
}

/* 2026-05-23 follow-up to PR #1130 — INTERNAL treatment for the
   rail-card Cost cells + the Total dl row. Mirrors the bubble's
   .sf-cost-internal block (dashed outline + warning-bg fill +
   INTERNAL badge) so the not-customer-facing nature is unambiguous
   on every cost surface, not just the per-call bubble. */

/* dl variant — wraps a single <div> row in the totals list (the
   "Total $X" row visible only to admin/accounting). The .sf-detail__rail-list
   div children are already flex by the parent's existing rules;
   preserve that by keeping `display: flex` on this class. */
.sf-detail__rail-list .sf-cost-internal-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--space-6, 6px);
    border: 1px dashed var(--warning-fg, var(--accent-fg));
    border-radius: var(--radius-2, 4px);
    background: var(--warning-bg, var(--bg-2, transparent));
    padding: var(--space-4, 4px) var(--space-6, 6px);
}
.sf-detail__rail-list .sf-cost-internal-row dt {
    display: inline-flex;
    align-items: center;
    gap: var(--space-4, 4px);
}

/* Table-column variant — applied to th + every td (body + tfoot)
   in the gated Cost column. Adjacent cells' dashed left/right
   borders collapse via the table's border-collapse default, so
   the column reads as a single outlined region with a single
   warning-bg fill. Header gets dashed-top; tfoot gets dashed-
   bottom; together that closes the outline around the column. */
.sf-detail__rail-cost-breakdown .sf-cost-internal-col {
    background: var(--warning-bg, var(--bg-2, transparent));
    border-left: 1px dashed var(--warning-fg, var(--accent-fg));
    border-right: 1px dashed var(--warning-fg, var(--accent-fg));
}
.sf-detail__rail-cost-breakdown thead .sf-cost-internal-col {
    border-top: 1px dashed var(--warning-fg, var(--accent-fg));
}
.sf-detail__rail-cost-breakdown tfoot .sf-cost-internal-col {
    border-bottom: 1px dashed var(--warning-fg, var(--accent-fg));
}
/* Header content — INTERNAL badge sits inline with the column
   title, right-aligned to match the cell's existing .sf-cell--right
   alignment. */
.sf-cost-internal-col__header {
    display: inline-flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--space-4, 4px);
}

/* PR-M3 (2026-05-06) — compact conversation feed slot in the
   workspace generation rows. Spans all 8 grid columns + sits below
   the main row content as a 9th grid item on a second auto-row.
   Only rendered for IN-FLIGHT rows so completed/failed rows stay
   visually identical to today. */
.sf-genrow__feed {
    grid-column: 1 / -1;
    margin-top: var(--space-8);
    padding-top: var(--space-8);
    border-top: 1px dashed var(--border);
}
.sf-genrow:hover .sf-genrow__feed {
    /* Keep the feed visually connected to its row on hover; the
       row itself shifts to --bg-sunken via the existing :hover
       rule. */
}

/* PR-X1 (2026-05-06) — pending-clarification list on the new
   PausedAwaitingClarification card. Each item: agent + section
   meta line, the verbatim question, and the optional Why
   explanation. Renders as a numbered list so users can match
   answers to questions when answering via the API. */
.sf-detail__clarification-list {
    list-style: decimal inside;
    padding: 0;
    margin: var(--space-12) 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-12);
}
.sf-detail__clarification-item {
    background: var(--bg-elevated);
    border: 1px solid var(--rule, var(--border));
    border-radius: var(--radius-8);
    padding: var(--space-12) var(--space-16);
}
.sf-detail__clarification-meta {
    display: flex;
    gap: var(--space-8);
    align-items: baseline;
    flex-wrap: wrap;
    font-size: var(--text-12);
    color: var(--fg-subtle);
    margin-bottom: var(--space-4);
}
.sf-detail__clarification-meta strong { color: var(--fg); }
.sf-detail__clarification-meta code {
    font-family: var(--font-mono);
    font-size: var(--text-11);
    background: var(--bg-sunken);
    padding: 0 var(--space-4);
    border-radius: var(--radius-4);
}
.sf-detail__clarification-question {
    margin: 0;
    color: var(--fg);
    font-weight: var(--weight-medium);
}
.sf-detail__clarification-why {
    margin: var(--space-4) 0 0;
    color: var(--fg-muted);
    font-size: var(--text-13);
}
.sf-detail__clarification-empty {
    margin-top: var(--space-12);
}
.sf-detail__clarification-note {
    margin-top: var(--space-12);
    color: var(--fg-subtle);
    font-size: var(--text-12);
}
.sf-detail__clarification-note code {
    font-family: var(--font-mono);
    font-size: var(--text-11);
    background: var(--bg-sunken);
    padding: 0 var(--space-4);
    border-radius: var(--radius-4);
}

/* PR-X1 (2026-05-06) — warning-toned clarification link inside
   the workspace row's progress cell. Replaces the prior display-
   only text. The link is the primary affordance for unblocking
   PausedAwaitingClarification rows.

   2026-05-23 (bug #3 — clarification UX) — restyled as a button so
   the user reads it as the call-to-action it is, not as a subtitle
   in the progress cell. Pre-2026-05-23 user feedback: "there should
   be a button to answer right on that grid on the work space" —
   the underlined link looked like reference text, not an action. */
.sf-cell__clarification-link {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 4px 10px;
    border-radius: var(--radius-4, 6px);
    background: var(--warning-bg, rgb(254 243 199 / 0.4));
    color: var(--warning-fg);
    font-weight: var(--weight-semibold);
    font-size: 0.875rem;
    text-decoration: none;
    border: 1px solid var(--warning-fg, rgb(180 83 9));
    transition: background-color 120ms ease, transform 80ms ease;
}
.sf-cell__clarification-link:hover {
    background: var(--warning-bg-hover, rgb(254 243 199 / 0.7));
    color: var(--warning-fg);
    text-decoration: none;
    transform: translateY(-1px);
}
.sf-cell__clarification-link:focus-visible {
    outline: 2px solid var(--warning-fg);
    outline-offset: 2px;
}

/* 2026-05-23 (bug #3 — clarification UX) — banner above the
   composer on the /interview page when the interview is in
   AwaitingClarification state. Makes the mode explicit so a user
   landing here (via the Answer button on workspace or
   generation-detail) understands what they're answering and where
   the question is. Pre-2026-05-23 the composer DIDN'T render in
   this status at all — the user could read but not reply. */
.sf-clarification-banner {
    margin: 12px 0;
    padding: 12px 16px;
    border-radius: var(--radius-4, 6px);
    background: var(--warning-bg, rgb(254 243 199 / 0.4));
    border-left: 3px solid var(--warning-fg);
}
.sf-clarification-banner__eyebrow {
    display: block;
    font-size: 0.75rem;
    font-weight: var(--weight-semibold, 600);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--warning-fg);
    margin-bottom: 4px;
}
.sf-clarification-banner__body {
    margin: 0;
    color: var(--fg);
    font-size: 0.9rem;
    line-height: 1.4;
}

/* ================================================================
   PR-T29 (2026-05-07) — Lyra mockup card + lightbox.

   Renders inline in the interview chat when Lyra (DesignerCritic)
   attaches a visual mockup via her save_mockup tool. The card sits
   inside the agent turn's bubble; clicking it opens a wider variant
   of the standard .sf-modal pattern that mounts the mockup HTML in
   a sandboxed iframe (sandbox="allow-same-origin", scripts blocked).

   Visual chrome lifted from docs/design/reviews/2026-05-07-lyra-mockup-card/
   ================================================================ */

.sf-mockup-card {
    margin-top: var(--space-12);
    border: 1px solid var(--border);
    border-top: 3px solid var(--agent-lyra);
    border-radius: var(--radius-8);
    overflow: hidden;
    background: var(--bg-sunken);
}

.sf-mockup-card__preview {
    /* Fixed-height clip frame for the pre-scaled iframe inside. The
       iframe is rendered at its natural ~1280×800 viewport and
       scaled down via CSS transform; this wrapper does the clip. */
    height: 160px;
    overflow: hidden;
    position: relative;
    cursor: pointer;
    background: var(--bg-sunken);
    border: 0;
    width: 100%;
    padding: 0;
}
.sf-mockup-card__preview:hover {
    background: color-mix(in srgb, var(--agent-lyra) 5%, var(--bg-sunken));
}
.sf-mockup-card__preview:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

.sf-mockup-card__preview-iframe {
    /* Pre-scaled iframe: render at 1280×800 then transform to 0.2x
       (= 256×160 logical) so the wrapper's 160px height shows the
       full vertical slice. pointer-events:none so the click lands
       on the wrapper button, not the iframe content. */
    width: 1280px;
    height: 800px;
    border: 0;
    transform: scale(0.2);
    transform-origin: top left;
    pointer-events: none;
    display: block;
}

.sf-mockup-card__preview-overlay {
    position: absolute; inset: 0;
    display: flex; align-items: center; justify-content: center;
    opacity: 0;
    transition: opacity var(--motion-duration-fast) var(--motion-easing-out);
    background: rgba(0,0,0,0.04);
    pointer-events: none;
}
.sf-mockup-card__preview:hover .sf-mockup-card__preview-overlay { opacity: 1; }
[data-theme="dark"] .sf-mockup-card__preview:hover .sf-mockup-card__preview-overlay {
    background: rgba(0,0,0,0.18);
}
.sf-mockup-card__expand-badge {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 5px var(--space-12);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-pill, 999px);
    font-size: var(--text-12); font-weight: var(--weight-medium);
    color: var(--fg);
    box-shadow: var(--shadow-2);
}
@media (prefers-reduced-motion: reduce) {
    .sf-mockup-card__preview-overlay { transition: none; }
}

.sf-mockup-card__footer {
    display: flex; align-items: center;
    gap: var(--space-8);
    padding: var(--space-8) var(--space-12);
    border-top: 1px solid var(--border);
    background: var(--bg-elevated);
}

.sf-mockup-card__byline {
    display: inline-flex; align-items: center; gap: 6px;
    font-size: var(--text-12); color: var(--fg-subtle);
    flex: none;
}
.sf-mockup-card__byline-dot {
    width: 8px; height: 8px; border-radius: 50%;
    background: var(--agent-lyra); flex: none;
}
.sf-mockup-card__byline-name {
    font-weight: var(--weight-medium);
    color: color-mix(in srgb, var(--agent-lyra) 75%, var(--fg-subtle));
}
[data-theme="dark"] .sf-mockup-card__byline-name { color: var(--agent-lyra); }

.sf-mockup-card__caption {
    flex: 1;
    font-size: var(--text-12);
    color: var(--fg-muted);
    overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
}

.sf-mockup-card__open {
    display: inline-flex; align-items: center; gap: var(--space-4);
    padding: 4px var(--space-8);
    background: transparent; border: 0;
    color: var(--accent-fg);
    font: inherit; font-size: var(--text-12); font-weight: var(--weight-medium);
    border-radius: var(--radius-4);
    cursor: pointer;
    white-space: nowrap;
    flex: none;
}
.sf-mockup-card__open:hover {
    background: color-mix(in srgb, var(--accent) 8%, transparent);
    text-decoration: underline;
}
.sf-mockup-card__open:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* Lightbox modifier — extends the existing .sf-modal-backdrop +
   .sf-modal pattern with a wider envelope (mockups need room) and
   the same Lyra accent border the card carries. */
.sf-modal-backdrop > .sf-modal--mockup-lb {
    position: static; inset: auto;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-top: 3px solid var(--agent-lyra);
    border-radius: var(--radius-8);
    padding: 0;
    max-width: min(1200px, 88vw);
    width: 100%;
    max-height: min(90vh, 900px);
    overflow: hidden;
    display: flex; flex-direction: column;
    box-shadow: 0 24px 64px -8px rgba(0, 0, 0, 0.45);
}
@media (max-width: 640px) {
    .sf-modal-backdrop > .sf-modal--mockup-lb {
        max-width: 100%;
        max-height: 100vh;
        border-radius: 0;
    }
}

.sf-modal-lb__header {
    display: flex; align-items: center; gap: var(--space-12);
    padding: var(--space-12) var(--space-16);
    border-bottom: 1px solid var(--border);
    flex: none;
}
.sf-modal-lb__title-group {
    flex: 1; min-width: 0;
    display: flex; flex-direction: column; gap: 2px;
}
.sf-modal-lb__title {
    font-size: var(--text-16);
    font-weight: var(--weight-semibold);
    color: var(--fg); margin: 0;
    overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
}
.sf-modal-lb__meta {
    display: flex; align-items: center; gap: var(--space-8);
    font-size: var(--text-12); color: var(--fg-muted);
}
.sf-modal-lb__meta-dot {
    width: 7px; height: 7px; border-radius: 50%;
    background: var(--agent-lyra); flex: none;
}
.sf-modal-lb__meta-byline {
    font-weight: var(--weight-medium);
    color: color-mix(in srgb, var(--agent-lyra) 75%, var(--fg-subtle));
}
[data-theme="dark"] .sf-modal-lb__meta-byline { color: var(--agent-lyra); }

.sf-modal-lb__close {
    width: 32px; height: 32px;
    display: inline-flex; align-items: center; justify-content: center;
    background: transparent; border: 0; color: var(--fg-muted);
    border-radius: var(--radius-6); cursor: pointer; flex: none;
}
.sf-modal-lb__close:hover { background: var(--bg-sunken); color: var(--fg); }
.sf-modal-lb__close:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.sf-modal-lb__body {
    flex: 1;
    overflow: hidden;
    position: relative;
    background: #ffffff;
    min-height: 0;
}
[data-theme="dark"] .sf-modal-lb__body {
    /* The dark app shell wraps a sandboxed iframe with light-mode
       mockup HTML; surface a divider so the chrome/content boundary
       reads. */
    border-top: 1px solid var(--border);
}
.sf-modal-lb__iframe {
    width: 100%; height: 100%;
    border: none;
    display: block;
}

.sf-modal-lb__footer {
    display: flex; align-items: center; gap: var(--space-8);
    padding: 10px var(--space-16);
    border-top: 1px solid var(--border);
    flex: none;
}
.sf-modal-lb__footer-spacer { flex: 1; }

/* ────────────────────────────────────────────────────────────
   TASK-174 (2026-05-08) — Workspace package search.

   Compact search box above the packages table on the Workspace
   page. Powers the GET /v1/packages/search cross-package full-text
   search endpoint. Mock + critique at
   docs/design/reviews/2026-05-08-workspace-search/.

   No new design tokens — every value pulls from tokens.css.
   ──────────────────────────────────────────────────────────── */
/* TASK-181 (2026-05-08) — "N addenda" chip rendered in the version
   cell of the workspace package row when at least one PackageAddendum
   is attached. Tiny pill, neutral surface, sits inline next to the
   <code>v1.0.0</code> tag. No new tokens. */
.sf-pkg-row__addenda-chip {
    display: inline-flex;
    align-items: center;
    margin-left: var(--space-8);
    padding: 1px var(--space-8);
    background: var(--info-bg, var(--bg-sunken));
    color: var(--info-fg, var(--fg-muted));
    border: 1px solid var(--info-border, var(--border));
    border-radius: 999px;
    font-size: var(--text-11);
    font-weight: var(--weight-semibold);
    line-height: 1.4;
    vertical-align: middle;
}

.sf-pkg-search {
    display: flex;
    align-items: center;
    gap: var(--space-8);
    max-width: 480px;
}
.sf-pkg-search__field {
    position: relative;
    flex: 1;
}
.sf-pkg-search__icon {
    position: absolute;
    left: var(--space-8);
    top: 50%; transform: translateY(-50%);
    color: var(--fg-subtle);
    pointer-events: none;
    display: flex; align-items: center;
    width: 16px; height: 16px;
}
.sf-pkg-search__input {
    width: 100%;
    height: 36px;
    padding: 0 32px 0 32px;
    background: var(--bg-elevated);
    color: var(--fg);
    border: 1px solid var(--border);
    border-radius: var(--radius-6);
    font: inherit;
    font-size: var(--text-14);
}
.sf-pkg-search__input::placeholder { color: var(--fg-subtle); }
.sf-pkg-search__input:focus {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
    border-color: var(--accent);
}
.sf-pkg-search__input--active {
    border-color: var(--accent);
}
.sf-pkg-search__clear {
    position: absolute;
    right: var(--space-4);
    top: 50%; transform: translateY(-50%);
    width: 24px; height: 24px;
    display: inline-flex; align-items: center; justify-content: center;
    background: transparent;
    border: 0;
    border-radius: var(--radius-4);
    color: var(--fg-muted);
    cursor: pointer;
    font-size: var(--text-12);
    padding: 0;
}
.sf-pkg-search__clear:hover {
    background: var(--bg-sunken);
    color: var(--fg);
}
.sf-pkg-search__clear:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}
.sf-pkg-search__hint {
    font-size: var(--text-12);
    color: var(--fg-subtle);
    margin-top: var(--space-4);
}
.sf-pkg-search__hint code {
    font-family: var(--font-mono);
    font-size: var(--text-12);
    background: var(--bg-sunken);
    padding: 1px 4px;
    border-radius: var(--radius-4);
}
.sf-pkg-search__spinner {
    position: absolute;
    left: var(--space-8);
    top: 50%; transform: translateY(-50%);
    width: 16px; height: 16px;
    border: 2px solid var(--border-strong);
    border-top-color: var(--accent);
    border-radius: 50%;
    animation: sf-pkg-search-spin 600ms linear infinite;
}
@keyframes sf-pkg-search-spin {
    to { transform: translateY(-50%) rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
    .sf-pkg-search__spinner { animation: none; border-color: var(--accent); }
}
.sf-pkg-search__results-meta {
    display: flex;
    align-items: center;
    gap: var(--space-8);
    font-size: var(--text-12);
    color: var(--fg-muted);
    padding: var(--space-4) 0;
    margin: 0;
}
.sf-pkg-search__results-count {
    color: var(--fg);
    font-weight: var(--weight-semibold);
}
.sf-pkg-search__results-query {
    color: var(--fg-muted);
}
.sf-pkg-search__results-query code {
    font-family: var(--font-mono);
    font-size: var(--text-12);
    background: var(--bg-sunken);
    padding: 1px 5px;
    border-radius: var(--radius-4);
    color: var(--fg);
}
.sf-pkg-search__snippet-row > td {
    border-bottom: 0;
    padding: 0;
    background: var(--bg-sunken);
}
.sf-pkg-search__snippet-inner {
    padding: var(--space-8) var(--space-16) var(--space-12);
    display: flex; flex-direction: column; gap: var(--space-8);
    border-bottom: 1px solid var(--border);
}
.sf-pkg-search__snippet-label {
    font-size: var(--text-12);
    color: var(--fg-subtle);
    font-weight: var(--weight-semibold);
    letter-spacing: 0.04em;
    text-transform: uppercase;
}
.sf-pkg-search__hit {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    padding: var(--space-8) var(--space-12);
    border: 1px solid var(--border);
    border-radius: var(--radius-6);
    background: var(--bg-elevated);
}
.sf-pkg-search__hit-path {
    font-family: var(--font-mono);
    font-size: var(--text-12);
    color: var(--fg-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.sf-pkg-search__hit-snippet {
    font-size: var(--text-13);
    color: var(--fg);
    line-height: var(--leading-snug);
}
.sf-pkg-search__hit-snippet mark {
    background: var(--accent-50);
    color: var(--accent-fg);
    border-radius: 2px;
    padding: 0 2px;
    font-style: normal;
}
[data-theme="dark"] .sf-pkg-search__hit-snippet mark {
    background: color-mix(in srgb, var(--accent) 20%, transparent);
    color: var(--accent-fg);
}
.sf-pkg-search__expand {
    width: 28px; height: 28px;
    display: inline-flex; align-items: center; justify-content: center;
    background: transparent; border: 0; border-radius: var(--radius-4);
    color: var(--fg-muted); cursor: pointer;
    transition: transform var(--motion-duration-fast) var(--motion-easing-out),
                color var(--motion-duration-fast) var(--motion-easing-out);
}
.sf-pkg-search__expand:hover { color: var(--fg); background: var(--bg-sunken); }
.sf-pkg-search__expand:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.sf-pkg-search__expand--open {
    transform: rotate(180deg);
    color: var(--accent-fg);
}
.sf-pkg-search__empty {
    padding: var(--space-32) var(--space-16);
    text-align: center;
    color: var(--fg-muted);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-8);
}
.sf-pkg-search__empty-title {
    font-size: var(--text-16);
    font-weight: var(--weight-semibold);
    color: var(--fg);
    margin: 0 0 var(--space-8);
}
.sf-pkg-search__empty-body {
    font-size: var(--text-14);
    color: var(--fg-muted);
    max-width: 44ch;
    margin: 0 auto;
    line-height: var(--leading-normal);
}
.sf-pkg-search__empty-body code {
    font-family: var(--font-mono);
    font-size: var(--text-13);
    background: var(--bg-sunken);
    padding: 1px 5px;
    border-radius: var(--radius-4);
    color: var(--fg);
}
.sf-pkg-search__empty-clear {
    margin-top: var(--space-16);
}
.sf-pkg-search__matched-row {
    border-left: 3px solid var(--accent);
}
.sf-pkg-search__matched-row td:first-child {
    padding-left: calc(var(--space-8) - 1px);
}
.sf-pkg-search__loading-overlay {
    opacity: 0.6;
    transition: opacity 180ms;
}

/* ── External Connectors modal — PR 1d (2026-05-13) ─────────────
   Provider tile picker + folder picker. `sf-modal__body` adds the
   inner padding the standard `sf-modal` shape doesn't include
   directly (WebhookSecretRevealModal uses its own `sf-keyreveal__body`
   override; AddExternalConnectorModal uses this generic body). */
.sf-modal__body { padding: var(--space-20, 20px) var(--space-24); }

/* Provider tile row — three flex tiles. */
.sf-tile-row {
    display: flex;
    gap: var(--space-12);
    flex-wrap: wrap;
}
.sf-tile {
    flex: 1 1 140px;
    min-height: 120px;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius-8);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-8);
    padding: var(--space-16);
    cursor: pointer;
    transition: border-color 120ms, background 120ms;
    color: var(--fg);
    font-family: inherit;
}
.sf-tile:hover:not(:disabled) {
    border-color: var(--accent);
    background: var(--bg-sunken);
}
.sf-tile:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}
/* 2026-05-15 — design-reviewer feedback: pure opacity 0.5 on disabled
   provider tiles reads as "this tile failed to load" rather than "this
   provider is on the roadmap." Replaced with a dashed border + muted
   logo + Soon pill chip so the disabled state reads as a deliberate
   future-promise. The tile body stays at full opacity (the shape is
   present) — only the logo + label are muted. */
.sf-tile--disabled,
.sf-tile:disabled {
    cursor: not-allowed;
    border-style: dashed;
    border-color: var(--border-strong, var(--border));
    background: transparent;
    position: relative;
}
.sf-tile--disabled .sf-tile__glyph,
.sf-tile:disabled .sf-tile__glyph {
    opacity: 0.45;
    filter: grayscale(60%);
}
.sf-tile--disabled .sf-tile__label,
.sf-tile:disabled .sf-tile__label {
    color: var(--fg-muted);
}
.sf-tile--disabled:hover,
.sf-tile:disabled:hover {
    border-color: var(--border-strong, var(--border));
    background: transparent;
}
/* 2026-05-15 — sf-tile__glyph used to be sized for an emoji glyph
   (font-size: 28px). Now it holds a 32px <img>, so we set explicit
   width/height + flex centering on the container instead. */
.sf-tile__glyph {
    line-height: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
}
.sf-tile__label { font-size: var(--text-14, 14px); font-weight: var(--weight-semibold); }
.sf-tile__hint { font-size: var(--text-12, 12px); color: var(--fg-muted); }

/* Folder picker row — full-width clickable list item. */
.sf-folder-row {
    width: 100%;
    display: flex;
    align-items: center;
    gap: var(--space-8);
    padding: var(--space-8) var(--space-12);
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--radius-6);
    cursor: pointer;
    text-align: left;
    color: var(--fg);
    font-family: inherit;
}
.sf-folder-row:hover:not(:disabled) {
    border-color: var(--border);
    background: var(--bg-sunken);
}
.sf-folder-row:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}
.sf-folder-row:disabled { opacity: 0.5; cursor: not-allowed; }

/* ── External-Connector "via {Provider}" badge — PR 1d follow-up
   (2026-05-13). Small inline pill rendered next to the filename in
   AttachedFilesPanel for rows whose ReferenceDocument.Source is
   ExternalConnector. Sized to read as metadata, not as the primary
   label. */
.sf-source-badge {
    display: inline-block;
    margin-left: var(--space-8);
    padding: 1px 6px;
    font-size: var(--text-11, 11px);
    font-weight: var(--weight-medium, 500);
    color: var(--fg-muted);
    background: var(--bg-sunken);
    border: 1px solid var(--border);
    border-radius: var(--radius-4);
    line-height: 1.4;
    vertical-align: middle;
    white-space: nowrap;
}

/* ─────────────────────────────────────────────────────────────────
   2026-05-17 — polished admin-table treatment. Applied to
   /admin/feedback + /admin/bug-reports first; subsequent admin
   pages migrate by wrapping their existing markup in the same
   shell classes. Three primitives:

     1. .sf-admin-toolbar    — filter/search bar with consistent
                                form-control styling
     2. .sf-admin-table-frame — card-framed wrapper around an
                                sf-table (rounded corners, soft
                                shadow, sticky header, hover rows)
     3. .sf-admin-pagination — bottom-of-card pagination strip

   The pages keep their existing .sf-table markup; the wrapper
   classes layer the polish on top so the migration is markup-
   only at the consumer.
   ───────────────────────────────────────────────────────────── */

.sf-admin-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: var(--space-12);
    padding: var(--space-16);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-12);
    margin-top: var(--space-16);
    margin-bottom: var(--space-16);
    box-shadow: 0 1px 2px rgba(15, 17, 21, 0.04);
}

.sf-admin-toolbar > label {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    font-size: var(--text-12);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-weight: var(--weight-semibold);
    color: var(--fg-subtle);
}

.sf-admin-toolbar > label > select,
.sf-admin-toolbar > label > input[type="search"],
.sf-admin-toolbar > label > input[type="text"] {
    padding: var(--space-8) var(--space-12);
    border: 1px solid var(--border);
    border-radius: var(--radius-6);
    background: var(--bg);
    color: var(--fg);
    font-size: var(--text-14);
    font-weight: var(--weight-regular);
    text-transform: none;
    letter-spacing: normal;
    min-width: 140px;
    transition: border-color 120ms ease, box-shadow 120ms ease;
}

.sf-admin-toolbar > label > select:hover,
.sf-admin-toolbar > label > input:hover {
    border-color: var(--border-strong);
}

.sf-admin-toolbar > label > select:focus,
.sf-admin-toolbar > label > input:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px rgba(33, 184, 120, 0.18);
}

.sf-admin-toolbar__search {
    flex: 1 1 220px;
    min-width: 220px;
}

.sf-admin-toolbar__actions {
    display: flex;
    gap: var(--space-8);
    margin-left: auto;
    align-self: flex-end;
}

.sf-admin-table-frame {
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-12);
    overflow: hidden;
    box-shadow: 0 1px 2px rgba(15, 17, 21, 0.04), 0 6px 16px rgba(15, 17, 21, 0.06);
    margin-top: var(--space-16);
}

.sf-admin-table-frame .sf-table {
    margin: 0;
    font-size: var(--text-14);
}

.sf-admin-table-frame .sf-table thead {
    background: var(--bg-sunken);
}

.sf-admin-table-frame .sf-table th {
    padding: var(--space-12) var(--space-16);
    font-size: var(--text-12);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--fg-subtle);
    border-bottom: 1px solid var(--border);
    background: transparent;
    position: sticky;
    top: 0;
    z-index: 1;
}

.sf-admin-table-frame .sf-table th .sf-genlist__sortbtn {
    color: inherit;
    font: inherit;
    text-transform: inherit;
    letter-spacing: inherit;
}

.sf-admin-table-frame .sf-table td {
    padding: var(--space-12) var(--space-16);
    border-bottom: 1px solid var(--border);
    vertical-align: middle;
}

.sf-admin-table-frame .sf-table tbody tr:last-child td {
    border-bottom: none;
}

.sf-admin-table-frame .sf-table__row--clickable {
    transition: background 120ms ease;
}

.sf-admin-table-frame .sf-table__row--clickable:hover {
    background: var(--bg-sunken);
}

.sf-admin-table-frame .sf-table__row--clickable:focus-within {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

.sf-admin-table-frame .sf-empty {
    padding: var(--space-48) var(--space-24);
    text-align: center;
    color: var(--fg-subtle);
    background: transparent;
}

.sf-admin-pagination {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-16);
    padding: var(--space-12) var(--space-16);
    background: var(--bg-sunken);
    border-top: 1px solid var(--border);
    font-size: var(--text-13, 13px);
    color: var(--fg-subtle);
}

.sf-admin-pagination > label {
    display: flex;
    align-items: center;
    gap: var(--space-8);
    font-weight: var(--weight-medium);
    color: var(--fg);
}

.sf-admin-pagination > label > select {
    padding: var(--space-4) var(--space-8);
    border: 1px solid var(--border);
    border-radius: var(--radius-6);
    background: var(--bg);
    color: var(--fg);
    font-size: var(--text-13, 13px);
}

.sf-admin-pagination__range {
    color: var(--fg);
    font-variant-numeric: tabular-nums;
}

.sf-admin-pagination__buttons {
    margin-left: auto;
    display: flex;
    gap: var(--space-8);
}

.sf-admin-pagination__buttons .sf-btn {
    min-width: 88px;
}

/* Empty-state card variant used when the filtered query returns
   zero rows. Lives as a sibling of the table-frame, not inside it,
   so the card-shadow + border match the polished table treatment. */
.sf-admin-empty {
    margin-top: var(--space-16);
    padding: var(--space-48) var(--space-24);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-12);
    box-shadow: 0 1px 2px rgba(15, 17, 21, 0.04);
    text-align: center;
    color: var(--fg-subtle);
}

/* ─────────────────────────────────────────────────────────────────
   2026-05-25 — session-state + Projects design pass. Per the
   design-reviewer report at
   docs/design/reviews/2026-05-25-session-state-and-projects/.
   Two new shared primitives the Projects pages need:

     1. .sf-page__breadcrumb — the "← back to list" affordance the
        detail pages already referenced with no CSS backing.
     2. .sf-project-default-card — the accent-bordered hero card that
        replaces the former top-bar project switcher. Surfaces the
        active project + lets the user switch it inline. No existing
        "hero info + inline action" primitive matched, so this is net
        new; it composes entirely from existing tokens.
   ───────────────────────────────────────────────────────────── */

.sf-page__breadcrumb {
    margin: 0 0 var(--space-8);
    font-size: var(--text-13);
    color: var(--fg-muted);
}
.sf-page__breadcrumb a {
    color: var(--fg-muted);
    text-decoration: none;
}
.sf-page__breadcrumb a:hover {
    color: var(--fg);
    text-decoration: underline;
}

.sf-project-default-card {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--space-16);
    padding: var(--space-20) var(--space-24);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-left: 4px solid var(--accent);
    border-radius: var(--radius-8);
    margin-bottom: var(--space-24);
    box-shadow: var(--shadow-1);
}
.sf-project-default-card__body {
    flex: 1 1 auto;
    min-width: 0;
}
.sf-project-default-card__eyebrow {
    font-size: var(--text-12);
    font-weight: var(--weight-semibold);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--accent-fg);
    margin: 0 0 var(--space-4);
}
.sf-project-default-card__name-row {
    display: flex;
    align-items: center;
    gap: var(--space-8);
    margin: 0 0 var(--space-8);
    min-width: 0;
}
.sf-project-default-card__name {
    font-size: var(--text-20);
    font-weight: var(--weight-semibold);
    margin: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.sf-project-default-card__meta {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-16);
    font-size: var(--text-13);
    color: var(--fg-muted);
}
.sf-project-default-card__meta-item {
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.sf-project-default-card__meta-label {
    font-size: var(--text-12);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-weight: var(--weight-semibold);
    color: var(--fg-subtle);
}
.sf-project-default-card__meta-value {
    color: var(--fg);
}
.sf-project-default-card__actions {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    gap: var(--space-8);
    align-items: stretch;
}
.sf-project-default-card__switcher {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    font-size: var(--text-12);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-weight: var(--weight-semibold);
    color: var(--fg-subtle);
}
.sf-project-default-card__switcher > select {
    padding: var(--space-8) var(--space-12);
    border: 1px solid var(--border);
    border-radius: var(--radius-6);
    background: var(--bg);
    color: var(--fg);
    font-size: var(--text-14);
    font-weight: var(--weight-regular);
    text-transform: none;
    letter-spacing: normal;
    min-width: 220px;
}
.sf-project-default-card__switcher > select:focus {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}
.sf-project-default-card__error {
    flex-basis: 100%;
    margin: var(--space-8) 0 0;
    color: var(--danger-fg);
    font-size: var(--text-13);
}

/* The page-header treatment historically only sized an <h1>; the Ops
   Center reference + the redesigned Projects/SessionState pages use an
   <h2> for the page title. Size it to match so the heading is
   consistent regardless of level. */
.sf-page__header h2 {
    font-size: var(--text-24);
    font-weight: var(--weight-semibold);
    margin: 0 0 var(--space-4);
}

/* Projects pages flow container — vertical rhythm between the child
   drill-down sections + the trailing create card. */
.sf-projects-panel__section {
    margin-top: var(--space-32);
}
.sf-projects-panel__section > .sf-card__title {
    margin: 0 0 var(--space-12);
}
.sf-projects-panel__create {
    margin-top: var(--space-32);
}
.sf-project-detail__description {
    margin: var(--space-8) 0 0;
    max-width: 72ch;
    color: var(--fg-muted);
    line-height: var(--leading-normal);
}

/* 2026-05-25 (Part C1) — the inline edit-details form + its error card
   read best as a constrained column, not full-bleed across the panel. */
.sf-project-detail__edit {
    max-width: 640px;
}

/* 2026-05-25 (Part C2) — column-header filter: stacks the sort button +
   a small per-column filter <select> inside a table <th>, so an operator
   filters Status / Priority right from the heading. */
.sf-th-filter {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-4);
}
.sf-th-filter__select {
    font-size: var(--text-12);
    font-weight: var(--weight-regular, 400);
    padding: 2px 6px;
    border: 1px solid var(--border);
    border-radius: var(--radius-4);
    background: var(--bg);
    color: var(--fg);
    max-width: 9rem;
}
.sf-th-filter__select:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}

/* 2026-05-25 (Part D1) — project metrics dashboard. The count tiles reuse
   the Ops Center .sf-admin-metric-tile classes; these rules cover the
   smaller last-activity value + the inline decision-velocity bar chart. */
.sf-project-metrics__value--sm {
    font-size: var(--text-14);
    white-space: nowrap;
}
/* 2026-05-26 (project dashboard frontend) — the Direction B analytics
   dashboard that replaces the lone decision-velocity sparkline. All series
   colours pull from existing tokens (no new hex), so the charts adapt to the
   dark theme for free. Layout mirrors docs/design/reviews/
   2026-05-26-project-dashboard/mocks/index-alt.html. */
.sf-dash { margin-top: var(--space-24); display: flex; flex-direction: column; }
.sf-dash-divider { border: none; border-top: 1px solid var(--border); margin: var(--space-32) 0; }

.sf-dash-section { margin: 0; }
.sf-dash-section__header {
    display: flex; align-items: baseline; gap: var(--space-16);
    padding-left: var(--space-12);
    border-left: 3px solid var(--accent);
    margin-bottom: var(--space-20);
}
.sf-dash-section__title {
    font-size: var(--text-16); font-weight: var(--weight-semibold);
    color: var(--fg); margin: 0;
}
.sf-dash-section__sub { font-size: var(--text-13); color: var(--fg-subtle); }

/* KPI strip */
.sf-dash-kpis {
    display: grid; grid-template-columns: repeat(4, 1fr);
    gap: var(--space-12); margin-bottom: var(--space-20);
}
/* 2026-06-01 (triage-flow dashboards) — 5-KPI strip variant (the flow
   dashboards lead with five headline scalars vs the project dashboard's four). */
.sf-dash-kpis--5up { grid-template-columns: repeat(5, 1fr); }
.sf-dash-kpi {
    background: var(--bg-elevated); border: 1px solid var(--border);
    border-radius: var(--radius-8); padding: var(--space-16); box-shadow: var(--shadow-1);
}
.sf-dash-kpi__label {
    font-size: var(--text-12); font-weight: var(--weight-semibold);
    color: var(--fg-subtle); letter-spacing: 0.04em; text-transform: uppercase;
    margin-bottom: var(--space-8);
}
.sf-dash-kpi__value {
    font-size: var(--text-32); font-weight: var(--weight-semibold);
    color: var(--fg); line-height: 1.2; font-variant-numeric: tabular-nums;
}
.sf-dash-kpi__delta { font-size: var(--text-12); color: var(--fg-subtle); margin-top: var(--space-4); }
.sf-dash-kpi__delta--good { color: var(--success-fg); font-weight: var(--weight-medium); }
.sf-dash-kpi__delta--bad { color: var(--danger); font-weight: var(--weight-medium); }
.sf-dash-kpi__secondary {
    margin-top: var(--space-12); padding-top: var(--space-12);
    border-top: 1px solid var(--border); display: flex; align-items: baseline; gap: var(--space-8);
}
.sf-dash-kpi__secondary-value {
    font-size: var(--text-20); font-weight: var(--weight-semibold);
    color: var(--accent-fg, var(--accent)); font-variant-numeric: tabular-nums;
}
.sf-dash-kpi__secondary-label { font-size: var(--text-12); color: var(--fg-subtle); }

/* Chart cards */
.sf-dash-grid { display: grid; gap: var(--space-16); }
.sf-dash-grid--full { grid-template-columns: 1fr; }
.sf-dash-grid--2 { grid-template-columns: 1fr 1fr; }
.sf-dash-grid--2-1 { grid-template-columns: 2fr 1fr; }
.sf-dash-grid--3 { grid-template-columns: 2fr 1fr 1fr; }
.sf-dash-card {
    background: var(--bg-elevated); border: 1px solid var(--border);
    border-radius: var(--radius-12); padding: var(--space-20) var(--space-24);
    box-shadow: var(--shadow-1); display: flex; flex-direction: column;
    min-width: 0;
}
.sf-dash-card__title {
    font-size: var(--text-13); font-weight: var(--weight-semibold);
    color: var(--fg-subtle); letter-spacing: 0.04em; text-transform: uppercase;
}
.sf-dash-card__sub { font-size: var(--text-13); color: var(--fg-subtle); margin-bottom: var(--space-12); }
.sf-dash-card__body { flex: 1; position: relative; }
.sf-dash-empty {
    display: flex; align-items: center; justify-content: center; height: 100%;
    color: var(--fg-subtle); font-size: var(--text-13); text-align: center;
}

/* Inline flow-efficiency stat (not a gauge — honest about the approximation) */
.sf-dash-stat {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    height: 100%; padding: var(--space-24) var(--space-16); gap: var(--space-12);
}
.sf-dash-stat__value {
    font-size: var(--text-40, 40px); font-weight: var(--weight-semibold);
    color: var(--fg); font-variant-numeric: tabular-nums; line-height: 1;
}
.sf-dash-stat__label { font-size: var(--text-14); color: var(--fg-muted); text-align: center; }
.sf-dash-stat__caveat {
    font-size: var(--text-12); color: var(--fg-subtle); text-align: center;
    font-style: italic; max-width: 200px; line-height: 1.35; margin: 0;
}
.sf-dash-effbar { width: 100%; height: 8px; background: var(--bg-sunken); border-radius: var(--radius-pill); overflow: hidden; }
.sf-dash-effbar__fill { height: 100%; background: var(--accent); border-radius: inherit; }

/* Legend */
.sf-dash-legend { display: flex; flex-wrap: wrap; gap: var(--space-12); margin-top: var(--space-12); }
.sf-dash-legend__item { display: flex; align-items: center; gap: 6px; font-size: var(--text-12); color: var(--fg-muted); }
.sf-dash-legend__swatch { width: 10px; height: 10px; border-radius: var(--radius-2); flex-shrink: 0; }

/* SVG chart primitives */
.sf-dash-gridline { stroke: var(--border); stroke-width: 1; }
.sf-dash-axis { font-size: 9px; fill: var(--fg-subtle); font-family: var(--font-sans, system-ui); }
.sf-dash-median { stroke: var(--accent); stroke-width: 1; stroke-dasharray: 4 3; opacity: 0.6; }

/* Series colours (legend swatches) */
.sf-dash-bg--sessions { background: var(--accent); }
.sf-dash-bg--decisions { background: var(--info); }
.sf-dash-bg--resolved { background: var(--fg-subtle); }
.sf-dash-bg--scope { background: var(--fg-subtle); }
.sf-dash-bg--scatter { background: var(--accent-400); }
.sf-dash-bg--lead { background: var(--warning); }

/* Series colours (SVG fills) */
.sf-dash-fill--sessions { fill: var(--accent); }
.sf-dash-fill--decisions { fill: var(--info); }
.sf-dash-fill--resolved { fill: var(--fg-subtle); }
.sf-dash-fill--lead { fill: var(--warning); }
.sf-dash-fill--scatter { fill: var(--accent-400); }

/* CFD stacked-area band fills + edges */
.sf-dash-area--completed { fill: color-mix(in srgb, var(--accent) 22%, transparent); }
.sf-dash-area--active { fill: color-mix(in srgb, var(--info) 15%, transparent); }
.sf-dash-area--open { fill: color-mix(in srgb, var(--fg-subtle) 12%, transparent); }
.sf-dash-stroke--completed { stroke: var(--accent); stroke-width: 2; }
.sf-dash-stroke--active { stroke: var(--info); stroke-width: 1.5; }
.sf-dash-stroke--open { stroke: var(--fg-subtle); stroke-width: 1.5; stroke-dasharray: 5 3; }
.sf-dash-stroke--scope { stroke: var(--fg-subtle); stroke-width: 1.5; stroke-dasharray: 5 3; }
.sf-dash-stroke--lead { stroke: var(--warning); stroke-width: 2.5; }

@media (max-width: 1024px) {
    .sf-dash-grid--2 { grid-template-columns: 1fr; }
    .sf-dash-grid--2-1 { grid-template-columns: 1fr; }
    .sf-dash-grid--3 { grid-template-columns: 1fr 1fr; }
    .sf-dash-kpis { grid-template-columns: 1fr 1fr; }
    .sf-dash-kpis--5up { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 768px) {
    .sf-dash-grid--3 { grid-template-columns: 1fr; }
    .sf-dash-kpis { grid-template-columns: 1fr 1fr; }
    .sf-dash-kpis--5up { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 480px) {
    .sf-dash-kpis { grid-template-columns: 1fr; }
    .sf-dash-kpis--5up { grid-template-columns: 1fr; }
}

/* 2026-05-27 (admin summary rows) — compact 3-card snapshot strip shown under
   the SessionStateSubnav tabs on the admin list pages. Reuses the dashboard
   token palette + sf-dash-* chart primitives; just adds compact card chrome +
   the donut segment colours. */
.sf-srow {
    display: grid; grid-template-columns: repeat(3, 1fr);
    gap: var(--space-12); margin: var(--space-16) 0 var(--space-24);
}
.sf-srow__card {
    background: var(--bg-elevated); border: 1px solid var(--border);
    border-radius: var(--radius-8); padding: var(--space-12) var(--space-16);
    box-shadow: var(--shadow-1); display: flex; flex-direction: column; min-width: 0;
}
.sf-srow__title {
    font-size: var(--text-12); font-weight: var(--weight-semibold);
    color: var(--fg-subtle); letter-spacing: 0.04em; text-transform: uppercase;
    margin-bottom: var(--space-8);
}
.sf-srow__body { height: 96px; }
.sf-srow__legend { display: flex; flex-wrap: wrap; gap: var(--space-8); margin-top: var(--space-4); }
.sf-srow__lg { display: flex; align-items: center; gap: 5px; font-size: var(--text-12); color: var(--fg-muted); }
.sf-srow__sw { width: 9px; height: 9px; border-radius: var(--radius-2); flex-shrink: 0; }

.sf-srow__donut-wrap { display: flex; align-items: center; gap: var(--space-12); }
.sf-srow__donut { width: 104px; height: 104px; flex-shrink: 0; }
.sf-srow__donut-track { stroke: var(--bg-sunken); }
.sf-srow__donut-value { font-size: 26px; font-weight: var(--weight-semibold); fill: var(--fg); font-variant-numeric: tabular-nums; }
.sf-srow__donut-sub { font-size: 11px; fill: var(--fg-subtle); }
.sf-srow__keys { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 3px; }
.sf-srow__keys li { display: flex; align-items: center; gap: 6px; font-size: var(--text-12); color: var(--fg-muted); }

.sf-srow__stat { display: flex; flex-direction: column; justify-content: center; flex: 1; gap: var(--space-4); }
.sf-srow__stat-value { font-size: var(--text-32); font-weight: var(--weight-semibold); color: var(--fg); line-height: 1.1; font-variant-numeric: tabular-nums; }
.sf-srow__stat-label { font-size: var(--text-12); color: var(--fg-subtle); }

/* Donut segment + legend colours: Resolved=green(done), InProgress=blue(active),
   Open=amber(waiting), Dismissed=grey. */
.sf-srow-seg--open { stroke: var(--warning); }
.sf-srow-seg--inprogress { stroke: var(--info); }
.sf-srow-seg--resolved { stroke: var(--accent); }
.sf-srow-seg--dismissed { stroke: var(--fg-subtle); }
.sf-srow-bg--open { background: var(--warning); }
.sf-srow-bg--inprogress { background: var(--info); }
.sf-srow-bg--resolved { background: var(--accent); }
.sf-srow-bg--dismissed { background: var(--fg-subtle); }

/* 2026-06-01 (Lessons & Rules web surfaces) — Build Lessons status donut:
   Enforced=green(goal), Documented=blue(written), Observed=amber(seen),
   Archived=grey(retired). Token aliases onto the shared sf-srow donut — same
   micro-pattern as the backlog donut above; no new colours. */
.sf-srow-seg--enforced { stroke: var(--success); }
.sf-srow-seg--documented { stroke: var(--info); }
.sf-srow-seg--observed { stroke: var(--warning); }
.sf-srow-seg--archived { stroke: var(--fg-subtle); }
.sf-srow-bg--enforced { background: var(--success); }
.sf-srow-bg--documented { background: var(--info); }
.sf-srow-bg--observed { background: var(--warning); }
.sf-srow-bg--archived { background: var(--fg-subtle); }
/* Build Rules status donut: Active=green, Archived=grey (reuses --archived above). */
.sf-srow-seg--active { stroke: var(--success); }
.sf-srow-bg--active { background: var(--success); }

@media (max-width: 900px) {
    .sf-srow { grid-template-columns: 1fr; }
}

/* ── Per-project breakdown (2026-05-30) — when the signed-in user owns >1 project,
   a 4th "… by project" card joins the three summary cards, so the row goes 4-up. */
.sf-srow--4up { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 1200px) { .sf-srow--4up { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 700px) { .sf-srow--4up { grid-template-columns: 1fr; } }

/* The compact bar list rendered as that 4th card's content — name · bar · count,
   top-N capped with a "+N more" footer. No card chrome (the host sf-srow__card
   supplies the border + title). Tokens only. */
.sf-projbars { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 7px; }
.sf-projbars__row {
    display: grid; grid-template-columns: minmax(56px, 92px) 1fr auto;
    align-items: center; gap: var(--space-8);
}
.sf-projbars__name {
    font-size: var(--text-12); color: var(--fg-muted);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
.sf-projbars__track {
    height: 8px; border-radius: var(--radius-pill, 999px);
    background: var(--bg-sunken); overflow: hidden; min-width: 0;
}
.sf-projbars__fill {
    display: block; height: 100%; border-radius: inherit;
    background: var(--accent); transition: width 200ms ease;
}
.sf-projbars__count {
    font-size: var(--text-12); font-weight: var(--weight-semibold);
    color: var(--fg); font-variant-numeric: tabular-nums; text-align: right; min-width: 2ch;
}
.sf-projbars__more { font-size: 11px; color: var(--fg-subtle); margin-top: 2px; }

/* 2026-06-01 (triage-flow dashboards) — sf-dash-agebar: horizontal item-age bars
   for the "Aging work in progress" dashboard card. A scrollable, link-labeled
   variant of sf-projbars suited to larger item counts; the --long fill (danger)
   flags items past the 85th-percentile historical lead time. A leading status dot
   distinguishes In-progress (info) from Open (muted). Tokens only — no new colours. */
.sf-dash-agebar { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 5px; max-height: 360px; overflow-y: auto; }
.sf-dash-agebar__row { display: grid; grid-template-columns: 12px minmax(120px, 260px) 1fr auto; align-items: center; gap: var(--space-8); }
/* Status dot distinguishes Open (solid, muted) from In-progress (HOLLOW ring,
   info) — the hollow vs solid SHAPE is a non-colour signal so the open/in-progress
   distinction survives greyscale / colour-blindness; the SR status word rides on
   the row link's aria-label. */
.sf-dash-agebar__dot { width: 7px; height: 7px; border-radius: 50%; background: var(--fg-subtle); justify-self: center; flex-shrink: 0; }
.sf-dash-agebar__dot--inprogress { background: transparent; box-shadow: inset 0 0 0 2px var(--info); }
.sf-dash-agebar__label { font-size: var(--text-12); color: var(--fg-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; text-decoration: none; }
a.sf-dash-agebar__label:hover { color: var(--fg); text-decoration: underline; }
.sf-dash-agebar__track { height: 6px; border-radius: var(--radius-pill, 999px); background: var(--bg-sunken); overflow: hidden; min-width: 0; }
.sf-dash-agebar__fill { display: block; height: 100%; border-radius: inherit; background: var(--warning); transition: width 200ms ease; }
.sf-dash-agebar__fill--long { background: var(--danger); }
.sf-dash-agebar__age { font-size: var(--text-12); font-weight: var(--weight-semibold); color: var(--fg); font-variant-numeric: tabular-nums; text-align: right; min-width: 3ch; }
.sf-dash-agebar__more { display: inline-block; font-size: var(--text-11, 11px); color: var(--fg-subtle); margin-top: var(--space-8); text-decoration: none; }
a.sf-dash-agebar__more:hover { color: var(--fg); text-decoration: underline; }

/* Build progress (doc-vs-built) — Part D3b. Overall + per-phase completion
   bars + a per-task checklist with operator-override toggles. */
.sf-build-progress { display: flex; flex-direction: column; gap: var(--space-20); }
.sf-build-progress__overall { display: flex; flex-direction: column; gap: var(--space-8); }
.sf-build-progress__overall-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-12); }
.sf-build-progress__pct { font-size: var(--text-24); font-weight: var(--weight-bold); color: var(--fg); }
.sf-build-progress__count { font-size: var(--text-13); color: var(--fg-muted); }
.sf-build-progress__bar {
    height: 10px;
    border-radius: var(--radius-pill, 999px);
    background: var(--surface-sunken, var(--surface-2, rgba(127,127,127,0.18)));
    overflow: hidden;
}
.sf-build-progress__bar-fill {
    height: 100%;
    background: var(--success-500, var(--accent));
    border-radius: inherit;
    transition: width 240ms ease;
}
.sf-build-progress__phase { display: flex; flex-direction: column; gap: var(--space-8); }
.sf-build-progress__phase-head {
    display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-12);
    border-bottom: 1px solid var(--border-subtle, var(--border, rgba(127,127,127,0.2)));
    padding-bottom: var(--space-4);
}
.sf-build-progress__phase-label { font-weight: var(--weight-semibold); color: var(--fg); }
.sf-build-progress__phase-count { font-size: var(--text-13); color: var(--fg-muted); font-variant-numeric: tabular-nums; }
.sf-build-progress__tasks { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-4); }
.sf-build-progress__task {
    display: flex; align-items: center; gap: var(--space-8);
    padding: var(--space-4) var(--space-8);
    border-radius: var(--radius-8, 8px);
}
.sf-build-progress__task:hover { background: var(--surface-sunken, rgba(127,127,127,0.08)); }
.sf-build-progress__task-state { width: 1.25em; text-align: center; color: var(--fg-muted); }
.sf-build-progress__task--built .sf-build-progress__task-state { color: var(--success-500, var(--accent)); }
.sf-build-progress__task-id { font-size: var(--text-12); color: var(--fg-muted); white-space: nowrap; }
.sf-build-progress__task-desc { flex: 1 1 auto; min-width: 0; color: var(--fg); }
.sf-build-progress__task--built .sf-build-progress__task-desc { color: var(--fg-muted); }
.sf-build-progress__task-actions { display: flex; gap: var(--space-4); flex: 0 0 auto; }
.sf-build-progress__task-actions .sf-btn { font-size: var(--text-12); padding: 2px var(--space-8); }

/* Connect a GitHub repository (connect-repo slice 2, 2026-05-26). The
   explainer card shown when no repo is bound + the connected/change
   variant + the multi-repo picker list rendered after the Setup-URL
   callback bounces back with more than one accessible repo. */
.sf-connect-repo { max-width: 640px; display: flex; flex-direction: column; gap: var(--space-12); }
.sf-connect-repo__lead { margin: 0; color: var(--fg); }
.sf-connect-repo__benefits {
    margin: 0; padding-left: var(--space-20);
    display: flex; flex-direction: column; gap: var(--space-4);
    color: var(--fg); font-size: var(--text-14);
}
.sf-connect-repo__note { margin: 0; font-size: var(--text-13); color: var(--fg-muted); }
.sf-connect-repo--connected .sf-connect-repo__lead { word-break: break-all; }

.sf-connect-repo-list {
    list-style: none; margin: var(--space-12) 0 0; padding: 0;
    display: flex; flex-direction: column; gap: var(--space-4);
}
.sf-connect-repo-list__item {
    display: flex; align-items: center; gap: var(--space-12);
    padding: var(--space-8) var(--space-12);
    border: 1px solid var(--border-subtle, var(--border, rgba(127,127,127,0.2)));
    border-radius: var(--radius-8, 8px);
}
.sf-connect-repo-list__item:hover { background: var(--surface-sunken, rgba(127,127,127,0.06)); }
.sf-connect-repo-list__info { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: var(--space-4); }
.sf-connect-repo-list__name {
    display: inline-flex; align-items: center; gap: var(--space-8);
    font-weight: var(--weight-semibold); color: var(--fg); word-break: break-all;
}
.sf-connect-repo-list__desc {
    font-size: var(--text-13); color: var(--fg-muted);
    overflow: hidden; text-overflow: ellipsis; display: -webkit-box;
    -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
.sf-connect-repo-list__item .sf-btn { flex: 0 0 auto; }

/* Migrate-existing-docs (doc-migration PR-6) — single-slot conflict resolver.
   Each fieldset is one contested canonical slot; the radio options are the
   competing source files. Compact + bordered, matching the connect-repo-list. */
.sf-migrate-conflict {
    margin: var(--space-12) 0 0; padding: var(--space-8) var(--space-12);
    border: 1px solid var(--border-subtle, var(--border, rgba(127,127,127,0.2)));
    border-radius: var(--radius-8, 8px);
}
.sf-migrate-conflict legend { font-size: var(--text-13); color: var(--fg-muted); padding: 0 var(--space-4); }
.sf-migrate-conflict__option {
    display: flex; align-items: center; gap: var(--space-8);
    padding: var(--space-4) 0; cursor: pointer; word-break: break-all;
}
.sf-migrate-conflict__option input { flex: 0 0 auto; }

/* Per-row re-route <select> in the proposed-mapping table's "Maps to" column.
   Sized to the column rather than full-card width so the table stays scannable;
   the long option labels (which carry the full target path) drive the menu
   width when opened. */
.sf-migrate-target {
    max-width: 22rem; width: 100%;
    font-size: var(--text-13);
    padding: var(--space-4) var(--space-8);
}

/* Inline checkbox row inside the filter toolbar (e.g. "Show archived").
   The toolbar's `> label` rule targets direct-child labels only, so the
   nested checkbox + label here read as a normal-weight inline control. */
.sf-admin-toolbar__checkbox-row {
    display: flex;
    align-items: center;
    gap: var(--space-8);
}

/* Global: error text inside a card. Referenced app-wide (project,
   settings, support surfaces) but never had a CSS backing — rendered
   in the default body color, indistinguishable from normal copy. */
.sf-card__error {
    margin: var(--space-8) 0 0;
    color: var(--danger-fg);
    font-size: var(--text-13);
}

/* ─────────────────────────────────────────────────────────────────
   2026-05-25 — session-state pages design pass (slice 2). Horizontal
   sibling-nav strip shared by the four session-state admin pages
   (Build sessions / Decision log / Backlog / Imports). A sub-nav, NOT
   a second left rail (per the "no double left nav" convention).
   ───────────────────────────────────────────────────────────── */
.sf-admin-subnav {
    display: flex;
    flex-wrap: wrap;
    gap: 2px;
    margin-bottom: var(--space-24);
    border-bottom: 1px solid var(--border);
}
.sf-admin-subnav__link {
    display: inline-flex;
    align-items: center;
    padding: var(--space-8) var(--space-12);
    font-size: var(--text-13);
    font-weight: var(--weight-medium);
    color: var(--fg-muted);
    text-decoration: none;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
    border-radius: var(--radius-4) var(--radius-4) 0 0;
    transition: color 120ms ease, border-color 120ms ease;
}
.sf-admin-subnav__link:hover {
    color: var(--fg);
}
.sf-admin-subnav__link--active {
    color: var(--fg);
    border-bottom-color: var(--fg);
    font-weight: var(--weight-semibold);
}
.sf-admin-subnav__link:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* 2026-05-25 — the imports/upload form reads best as a constrained
   column, not full-bleed: a viewport-wide <select> for a short enum
   value (DecisionLog / Backlog / BuildSession) looks unfinished. Cap
   the form card to a comfortable form width. */
.sf-admin-session-state-imports-upload-panel .sf-card {
    max-width: 640px;
}

/* Pagination strip when total count is unknown (count-free offset
   paging): the range label reads "Showing X–Y" with no total + Next
   disables when the page returned fewer rows than the page size. */
.sf-admin-pagination__range--countless {
    color: var(--fg-subtle);
}

/* Page header with a trailing primary action (e.g. the Imports page's
   "Import markdown" button sits opposite the title block). */
.sf-page__header--with-actions {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-16);
    flex-wrap: wrap;
}

/* ─────────────────────────────────────────────────────────────────
   2026-05-25 — session-state pages design pass (slice 2b). The four
   detail pages referenced sf-admin-detail / sf-page__subtitle-strong /
   sf-admin-detail__markdown with NO CSS backing — they rendered as a
   browser-default <dl> + unstyled <h2> + an unbounded raw <pre>. These
   rules turn them into a readable definition-grid card, real section
   headings, and a scroll-bounded mono block.
   ───────────────────────────────────────────────────────────── */

/* Definition-grid card: label column + value column, auto-flowing pairs. */
.sf-admin-detail {
    display: grid;
    grid-template-columns: max-content minmax(0, 1fr);
    gap: var(--space-10) var(--space-24);
    align-items: baseline;
    margin: 0 0 var(--space-24);
    padding: var(--space-20) var(--space-24);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-8);
    box-shadow: var(--shadow-1);
}
.sf-admin-detail dt {
    font-size: var(--text-12);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-weight: var(--weight-semibold);
    color: var(--fg-subtle);
    white-space: nowrap;
}
.sf-admin-detail dd {
    margin: 0;
    min-width: 0;
    color: var(--fg);
    word-break: break-word;
}
@media (max-width: 640px) {
    .sf-admin-detail {
        grid-template-columns: 1fr;
        gap: var(--space-4) 0;
    }
    .sf-admin-detail dd {
        margin-bottom: var(--space-8);
    }
}

/* Section heading between detail blocks (markdown bodies, child tables). */
.sf-page__subtitle-strong {
    font-size: var(--text-16);
    font-weight: var(--weight-semibold);
    color: var(--fg);
    margin: var(--space-24) 0 var(--space-12);
}

/* Readable, scroll-bounded markdown / pre block. */
.sf-admin-detail__markdown {
    margin: 0 0 var(--space-16);
    font-family: var(--font-mono);
    font-size: var(--text-13);
    line-height: 1.6;
    color: var(--fg);
    background: var(--bg-sunken);
    border: 1px solid var(--border);
    border-radius: var(--radius-6);
    padding: var(--space-16);
    white-space: pre-wrap;
    word-break: break-word;
    max-height: 480px;
    overflow: auto;
}

/* Detail-page header eyebrow (the "kind" label above the entry title)
   + a pill cluster under the title + a readable files-touched list. */
.sf-page__eyebrow {
    font-size: var(--text-12);
    font-weight: var(--weight-semibold);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--fg-subtle);
    margin: 0 0 var(--space-4);
}
.sf-page__header-pills {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-8);
    margin-top: var(--space-8);
}
.sf-detail__file-list {
    margin: 0 0 var(--space-16);
    padding-left: var(--space-20);
    color: var(--fg);
    font-size: var(--text-13);
    line-height: 1.7;
}

/* ─────────────────────────────────────────────────────────────────
   2026-06-01 (Lessons & Rules web surfaces, plan sorted-falcon) — the
   read-only Build Lessons / Build Rules surfaces. The only genuinely new
   layout component is .sf-obs-timeline (the observation timeline); the
   .sf-segmented view-filter (above) gained an aria-checked + badge. Every
   other rule below composes existing sf-* primitives + tokens (no new colours).
   ───────────────────────────────────────────────────────────── */

/* Tag chip cluster — a flex-wrap row of .sf-chip tags (lesson applicable-to,
   candidate proposed tags, rule intent tags). --clip keeps a table cell on one
   line, hiding overflow behind a trailing "+N" chip. */
.sf-chip-cluster { display: flex; flex-wrap: wrap; gap: var(--space-4); align-items: center; }
.sf-chip-cluster--clip { flex-wrap: nowrap; overflow: hidden; }
.sf-chip--more { color: var(--fg-subtle); background: transparent; border-color: transparent; }

/* List-row title above the slug in the Title/slug cell. */
.sf-lessons__title { font-weight: var(--weight-medium); margin-bottom: 2px; }

/* Single-line cell that truncates with an ellipsis (candidate proposed pattern). */
.sf-cell--truncate { max-width: 380px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Section-heading trailing count ("5 observations"). */
.sf-page__subtitle-count {
    font-size: var(--text-13); font-weight: var(--weight-regular);
    color: var(--fg-muted); margin-left: var(--space-8);
}

/* Confidence bar (candidate detector confidence [0..1]) — composes the shared
   .sf-dash-effbar track + a numeric label; no new colour. */
.sf-conf-bar { display: flex; align-items: center; gap: var(--space-8); min-width: 120px; }
.sf-conf-bar__track { flex: 1; height: 6px; }
.sf-conf-bar__label { font-size: var(--text-12); color: var(--fg-muted); font-variant-numeric: tabular-nums; min-width: 3ch; text-align: right; }

/* Rail metadata cards are a heading + a standalone sf-admin-detail dl (the dl IS
   the card). Drop the section heading's default top margin inside the rail so the
   first card sits flush under the breadcrumb/subnav. */
.sf-detail__rail .sf-page__subtitle-strong { margin-top: 0; }

/* Empty markdown placeholder (pattern / prevention not written yet). */
.sf-admin-detail__markdown--empty { color: var(--fg-subtle); font-style: italic; font-family: var(--font-sans); white-space: normal; }

/* Observation timeline — append-only, most-recent-first. Each entry: a dot on a
   vertical connector + a meta row (time · source labels) + a mono body block.
   NEW layout component; existing tokens only. */
.sf-obs-timeline { display: flex; flex-direction: column; gap: 0; }
.sf-obs-entry {
    display: grid; grid-template-columns: 20px minmax(0, 1fr); gap: 0 var(--space-12);
    padding-bottom: var(--space-20); position: relative;
}
.sf-obs-entry:not(:last-child)::after {
    content: ""; position: absolute; left: 9px; top: 20px; bottom: 0;
    width: 2px; background: var(--border);
}
.sf-obs-entry__dot {
    width: 10px; height: 10px; border-radius: 50%;
    background: var(--bg-elevated); border: 2px solid var(--accent);
    margin-top: 5px; position: relative; z-index: 1; grid-column: 1; grid-row: 1;
}
.sf-obs-entry__content { grid-column: 2; display: flex; flex-direction: column; gap: var(--space-8); min-width: 0; }
.sf-obs-entry__meta { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-8); font-size: var(--text-12); color: var(--fg-muted); }
.sf-obs-entry__source {
    padding: 1px var(--space-8); border-radius: 999px;
    background: var(--bg-sunken); border: 1px solid var(--border);
    font-size: 11px; font-weight: var(--weight-semibold); color: var(--fg-muted);
}
.sf-obs-entry__body {
    margin: 0; font-size: var(--text-13); color: var(--fg);
    background: var(--bg-sunken); border: 1px solid var(--border);
    border-radius: var(--radius-6); padding: var(--space-12) var(--space-16);
    font-family: var(--font-mono); white-space: pre-wrap; word-break: break-word;
    max-height: 200px; overflow: auto; line-height: 1.6;
}
.sf-obs-entry__links { display: flex; flex-wrap: wrap; gap: var(--space-8); font-size: var(--text-12); }

/* ── Build Rules surface (Phase 3b) — all compose existing tokens. ── */

/* Status + enrichment rendered as a paired inline pill group (the two signals are
   orthogonal: an Active rule can have Failed enrichment). */
.sf-pill-pair { display: inline-flex; flex-wrap: nowrap; gap: var(--space-4); align-items: center; }

/* Numeric scope-priority badge. */
.sf-priority {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 36px; padding: 1px 6px; border: 1px solid var(--border);
    border-radius: var(--radius-4); font-size: 11px; font-weight: var(--weight-semibold);
    color: var(--fg-muted); background: var(--bg-sunken); font-variant-numeric: tabular-nums; white-space: nowrap;
}

/* Retrieval cell on the rules list — count over the last-retrieved date, right-aligned.
   --zero dims an Active rule that's never matched (the diagnostic signal). */
.sf-ret-cell { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; }
.sf-ret-cell__count { font-variant-numeric: tabular-nums; font-weight: var(--weight-semibold); }
.sf-ret-cell__date { font-size: 11px; color: var(--fg-subtle); white-space: nowrap; }
.sf-ret-cell--zero .sf-ret-cell__count { color: var(--fg-subtle); font-weight: var(--weight-regular); }

/* Explanatory lead-in above the trigger surface / similar sections. */
.sf-page__subtitle-help { font-size: var(--text-13); color: var(--fg-muted); margin: 0 0 var(--space-16); line-height: 1.6; }

/* Mono trigger chips (file globs / code patterns are not natural language). */
.sf-chip--mono { font-family: var(--font-mono); font-size: 11px; }

/* Empty trigger dimension placeholder (the axis exists but is unconfigured). */
.sf-trigger-empty { color: var(--fg-subtle); font-style: italic; }

/* Per-row returned / suppressed flag in the retrieval-events table. */
.sf-retflag { color: var(--fg-subtle); }
.sf-retflag--on { color: var(--success-fg); font-weight: var(--weight-semibold); }

/* Zero-retrieval diagnostic banner (inside sf-card--warning). */
.sf-zero-ret__title { margin: 0; font-weight: var(--weight-semibold); color: var(--warning-fg); }
.sf-zero-ret__body { margin: 0; font-size: var(--text-13); color: var(--fg-muted); line-height: 1.6; }

/* Failed-enrichment framing on the rail dl — makes a failed enrichment impossible to
   miss without an extra component. */
.sf-admin-detail--failed { border-color: var(--danger-border); background: var(--danger-bg); }

/* 2026-05-25 (slice 2c) — operator-actions card on the backlog detail
   page (triage / assign / acknowledge-staleness). sf-card supplies the
   frame; these rules lay out the control rows. */
.sf-ss-actions {
    display: flex;
    flex-direction: column;
    gap: var(--space-12);
    margin-bottom: var(--space-24);
}
.sf-ss-actions__row {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: var(--space-12);
}
.sf-ss-actions__field {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    font-size: var(--text-12);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-weight: var(--weight-semibold);
    color: var(--fg-subtle);
}
.sf-ss-actions__field--grow {
    flex: 1 1 220px;
}
.sf-ss-actions__field > select,
.sf-ss-actions__field > input,
.sf-ss-actions__field > textarea {
    padding: var(--space-8) var(--space-12);
    border: 1px solid var(--border);
    border-radius: var(--radius-6);
    background: var(--bg);
    color: var(--fg);
    font-size: var(--text-14);
    font-weight: var(--weight-regular);
    text-transform: none;
    letter-spacing: normal;
}
.sf-ss-actions__field > textarea {
    resize: vertical;
    min-height: 72px;
    font-family: inherit;
    line-height: var(--leading-normal);
}

/* ─────────────────────────────────────────────────────────────────
   2026-06-01 (Lessons & Rules web surfaces, plan sorted-falcon — Phase 4a) —
   lesson operator-action affordances on the Build Lesson detail page. Compose
   the existing sf-ss-actions frame + tokens; the inline promote confirmation
   and the append-observation form are the only new shapes (no new colours).
   ───────────────────────────────────────────────────────────── */

/* Uppercase field label for the inline forms. */
.sf-field-label {
    display: block;
    font-size: var(--text-12); text-transform: uppercase; letter-spacing: 0.06em;
    font-weight: var(--weight-semibold); color: var(--fg-subtle); margin-bottom: var(--space-4);
}
/* Muted action hint under the operator-action row. */
.sf-ss-actions__hint { font-size: var(--text-12); color: var(--fg-subtle); margin: 0; line-height: var(--leading-normal); }
/* Inline Document form inside the actions card. */
.sf-ss-actions__form { display: flex; flex-direction: column; gap: var(--space-8); }
/* Single-line text input inside the inline action forms (candidate triage — slug /
   title / superseding-id). Matches the sf-ss-actions__field input styling. */
.sf-ss-actions__input {
    padding: var(--space-8) var(--space-12); border: 1px solid var(--border); border-radius: var(--radius-6);
    background: var(--bg); color: var(--fg); font-size: var(--text-14); width: 100%;
}
.sf-ss-actions__input:focus-visible { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent); }

/* Enforce-toned button — distinct from --primary to signal the weight of the
   human gate. Uses the success semantic tokens (no new colour). */
.sf-btn--enforce { background: var(--success-bg); color: var(--success-fg); border-color: var(--success-border); }
.sf-btn--enforce:hover { background: var(--success-border); color: #fff; }
.sf-btn--enforce[aria-disabled="true"] { opacity: 0.5; }

/* Inline promote-to-Enforced confirmation (role="group", NOT a modal/alertdialog).
   The warning frame signals consequence without using the danger semantic. */
.sf-confirm-card {
    border: 1px solid var(--warning-border); border-radius: var(--radius-8);
    background: var(--warning-bg); padding: var(--space-16);
    display: flex; flex-direction: column; gap: var(--space-12);
}
.sf-confirm-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.sf-confirm-card__title { margin: 0; font-size: var(--text-14); font-weight: var(--weight-semibold); color: var(--warning-fg); }
.sf-confirm-card__body { margin: 0; font-size: var(--text-13); color: var(--fg-muted); line-height: var(--leading-normal); }
.sf-confirm-card__body strong { color: var(--fg); }
.sf-confirm-card__hint { margin: 0; font-size: var(--text-12); color: var(--fg-subtle); }
.sf-confirm-card__actions { display: flex; flex-wrap: wrap; gap: var(--space-8); }

/* Append-observation form (main column, under the timeline). */
.sf-obs-append-card {
    margin-top: var(--space-16); padding: var(--space-16);
    background: var(--bg-elevated); border: 1px solid var(--border); border-radius: var(--radius-8);
}
.sf-obs-append__title { margin: 0 0 var(--space-12); font-size: var(--text-14); font-weight: var(--weight-semibold); color: var(--fg); }
.sf-obs-append { display: flex; flex-direction: column; gap: var(--space-8); }
.sf-obs-append__textarea {
    width: 100%; min-height: 80px; resize: vertical;
    padding: var(--space-12); border: 1px solid var(--border); border-radius: var(--radius-6);
    background: var(--bg); color: var(--fg); font-family: var(--font-mono); font-size: var(--text-13); line-height: 1.6;
}
.sf-obs-append__textarea:focus-visible { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent); }
.sf-obs-append__row { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--space-12); }
.sf-obs-append__source { display: flex; flex-direction: column; gap: var(--space-4); flex: 1 1 240px; text-transform: none; letter-spacing: normal; margin-bottom: 0; }
.sf-obs-append__source > input {
    padding: var(--space-8) var(--space-12); border: 1px solid var(--border); border-radius: var(--radius-6);
    background: var(--bg); color: var(--fg); font-size: var(--text-14);
}

/* ─────────────────────────────────────────────────────────────────
   TASK-236 follow-on (2026-05-18) — admin Communication thread
   detail surface. Per the design-reviewer report at
   docs/design/reviews/2026-05-18-communication-thread-detail/critique.md,
   the original ad-hoc class names (sf-message__*, sf-button*, sf-textarea
   etc.) had no CSS backing — page rendered with browser defaults.
   This block adds the tokens proposed in the mock. Reusable primitives
   (sf-textarea, sf-page__hint) live at the global level above; the
   thread-specific blocks (sf-thread__*, sf-message__*, sf-draft-card__*,
   sf-outcome-card, sf-action-toast) live inline here.
   ───────────────────────────────────────────────────────────── */

/* Global primitive: muted helper text used across admin surfaces. */
.sf-page__hint {
    font-size: var(--text-13);
    color: var(--fg-subtle);
    line-height: var(--leading-normal);
    margin: 0;
}

/* Global primitive: shared textarea control. */
.sf-textarea {
    display: block;
    width: 100%;
    padding: var(--space-8) var(--space-12);
    border: 1px solid var(--border);
    border-radius: var(--radius-6);
    background: var(--bg-elevated);
    color: var(--fg);
    font: inherit;
    font-size: var(--text-14);
    line-height: var(--leading-normal);
    resize: vertical;
    transition: border-color var(--motion-duration-fast),
                box-shadow var(--motion-duration-fast);
}
.sf-textarea:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px rgba(33, 184, 120, 0.18);
}
.sf-textarea:disabled {
    background: var(--bg-sunken);
    color: var(--fg-subtle);
    cursor: not-allowed;
}

/* Thread meta bar — sits between the page header + the message list. */
.sf-thread__meta-bar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-8);
    padding: var(--space-12) var(--space-16);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-8);
    margin-bottom: var(--space-20);
    box-shadow: var(--shadow-1, 0 1px 2px rgba(15, 17, 21, 0.04));
}
.sf-thread__meta-pills {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-6, 6px);
    flex: 1;
}
.sf-thread__meta-label {
    font-size: var(--text-12);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-weight: var(--weight-semibold);
    color: var(--fg-subtle);
    margin-right: var(--space-4);
}
.sf-thread__message-count {
    margin-left: auto;
    font-size: var(--text-12);
    color: var(--fg-subtle);
    font-variant-numeric: tabular-nums;
}

/* Message-history list container. Page itself scrolls. */
.sf-thread__history {
    display: flex;
    flex-direction: column;
    gap: var(--space-12);
    margin-bottom: var(--space-24);
}

/* Per-message bubble. Inbound = info-blue left accent; outbound = success-green. */
.sf-message {
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-8);
    overflow: hidden;
    box-shadow: var(--shadow-1, 0 1px 2px rgba(15, 17, 21, 0.04));
}
.sf-message--inbound  { border-left: 3px solid var(--info-border); }
.sf-message--outbound { border-left: 3px solid var(--success-border); }

.sf-message__header {
    display: flex;
    align-items: center;
    gap: var(--space-12);
    padding: var(--space-10, 10px) var(--space-16);
    background: var(--bg-sunken);
    border-bottom: 1px solid var(--border);
}
.sf-message__direction-badge {
    font-size: var(--text-11, 11px);
    font-weight: var(--weight-semibold);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--fg-subtle);
    flex-shrink: 0;
}
.sf-message--inbound  .sf-message__direction-badge { color: var(--info-fg); }
.sf-message--outbound .sf-message__direction-badge { color: var(--success-fg); }

.sf-message__sender {
    display: flex;
    flex-direction: column;
    gap: 2px;
    flex: 1;
    min-width: 0;
}
.sf-message__sender-addr {
    font-size: var(--text-13);
    font-weight: var(--weight-semibold);
    color: var(--fg);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.sf-message__timestamp {
    font-size: var(--text-12);
    color: var(--fg-subtle);
    white-space: nowrap;
    flex-shrink: 0;
    font-variant-numeric: tabular-nums;
}
.sf-message__body {
    padding: var(--space-16);
}
.sf-message__body--plain {
    white-space: pre-wrap;
    word-break: break-word;
    font-family: inherit;
    font-size: var(--text-14);
    line-height: var(--leading-normal);
    color: var(--fg);
    margin: 0;
}
.sf-message__body--html {
    width: 100%;
    min-height: 200px;
    max-height: 800px;
    border: none;
    border-radius: 0;
    display: block;
    background: var(--bg-elevated);
}
.sf-message__toggle-row {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    padding: var(--space-8) var(--space-16);
    border-top: 1px solid var(--border);
    background: var(--bg-sunken);
}

/* Draft approval card — the human-in-the-loop surface. Warning-tint
   so it announces itself as the action item. */
.sf-draft-card {
    border: 1px solid var(--warning-border);
    border-left: 4px solid var(--warning-border);
    border-radius: var(--radius-8);
    background: var(--warning-bg);
    overflow: hidden;
    box-shadow: var(--shadow-2);
    margin-bottom: var(--space-16);
}
.sf-draft-card__header {
    display: flex;
    align-items: center;
    gap: var(--space-12);
    padding: var(--space-12) var(--space-16);
    background: color-mix(in srgb, var(--warning-border) 12%, var(--bg-elevated));
    border-bottom: 1px solid var(--warning-border);
}
.sf-draft-card__title {
    font-size: var(--text-13);
    font-weight: var(--weight-semibold);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--warning-fg);
}
.sf-draft-card__meta {
    margin-left: auto;
    font-size: var(--text-12);
    color: var(--warning-fg);
    opacity: 0.8;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 50%;
}
.sf-draft-card__content {
    padding: var(--space-16);
}
.sf-draft-card__subject {
    font-size: var(--text-13);
    color: var(--fg-muted);
    margin-bottom: var(--space-12);
}
.sf-draft-card__subject strong { color: var(--fg); font-weight: var(--weight-medium); }
.sf-draft-card__body {
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-6);
    padding: var(--space-12) var(--space-16);
    font-family: inherit;
    font-size: var(--text-14);
    line-height: var(--leading-normal);
    white-space: pre-wrap;
    word-break: break-word;
    color: var(--fg);
    max-height: 320px;
    overflow-y: auto;
    margin-bottom: var(--space-16);
}
.sf-draft-card__actions {
    display: flex;
    align-items: center;
    gap: var(--space-8);
    flex-wrap: wrap;
}
.sf-draft-card__actions-spacer { flex: 1; }
.sf-approval-btn {
    height: 36px;
    min-width: 148px;
    font-size: var(--text-14);
    font-weight: var(--weight-semibold);
}
.sf-draft-card__reject {
    margin-top: var(--space-16);
    padding: var(--space-16);
    background: var(--danger-bg);
    border: 1px solid var(--danger-border);
    border-radius: var(--radius-6);
}
.sf-draft-card__reject label {
    display: block;
    font-size: var(--text-12);
    font-weight: var(--weight-semibold);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--danger-fg);
    margin-bottom: var(--space-8);
}
.sf-draft-card__reject .sf-textarea { margin-bottom: var(--space-12); }
.sf-draft-card__view-only {
    display: flex;
    align-items: flex-start;
    gap: var(--space-8);
    padding: var(--space-12) var(--space-16);
    background: var(--info-bg);
    border-top: 1px solid var(--info-border);
    border-radius: 0 0 var(--radius-8) var(--radius-8);
    font-size: var(--text-13);
    color: var(--info-fg);
    margin: 0;
}

/* Outcome card — Sent / Rejected terminal states. */
.sf-outcome-card {
    border: 1px solid var(--border);
    border-radius: var(--radius-8);
    padding: var(--space-12) var(--space-16);
    background: var(--bg-elevated);
    display: flex;
    align-items: center;
    gap: var(--space-12);
    margin-bottom: var(--space-16);
    box-shadow: var(--shadow-1, 0 1px 2px rgba(15, 17, 21, 0.04));
}
.sf-outcome-card--sent {
    border-color: var(--success-border);
    background: var(--success-bg);
}
.sf-outcome-card--rejected {
    border-color: var(--border-strong);
    background: var(--bg-elevated);
}
.sf-outcome-card__label {
    font-size: var(--text-13);
    font-weight: var(--weight-semibold);
    color: var(--fg);
}
.sf-outcome-card--sent .sf-outcome-card__label { color: var(--success-fg); }
.sf-outcome-card__detail {
    font-size: var(--text-13);
    color: var(--fg-subtle);
}

/* TASK-236 follow-on (2026-05-18) — bulk-select / delete row above
   the table. Holds the "Select all" toggle + (when ≥1 selected) the
   "N selected" hint + the danger-flavored Delete button. */
.sf-admin-communication__bulk-bar {
    display: flex;
    align-items: center;
    gap: var(--space-12);
    padding: var(--space-8) 0;
    flex-wrap: wrap;
}

/* Narrow checkbox column. */
.sf-admin-communication .sf-table th.sf-admin-communication__check-col,
.sf-admin-communication .sf-table td.sf-admin-communication__check-col {
    width: 32px;
    padding-left: var(--space-12);
    padding-right: var(--space-8);
    text-align: center;
}

/* Inbox tab strip on the Communication queue — right-aligned per the
   operator request, sits below the toolbar + above the email table.
   Wraps the shared sf-admin-tabstrip; no override on the tabstrip
   styling itself, only the container alignment + a small vertical
   rhythm. */
.sf-admin-communication__tabs {
    display: flex;
    justify-content: flex-end;
    margin: var(--space-12) 0 var(--space-8);
}

/* TASK-236 follow-on (2026-05-18) — page-level width discipline for
   the Communication queue under MainLayout (no right rail).
   `min-width: 0` is the standard fix for CSS-grid 1fr columns that
   would otherwise be forced wider than the available track by an
   oversize child (here: the table with long bounce-sender From/To
   addresses + a many-column header row). Without this, the table's
   intrinsic min-content width forced `.sf-main` to grow past the
   viewport edge, clipping the stat cards + filter toolbar + tab strip
   on the right side. The horizontal-scroll on the table-frame is the
   defensive fallback: if a tenant later carries even wider content,
   it scrolls inside the frame instead of breaking the page chrome. */
.sf-admin-communication {
    min-width: 0;
    max-width: 100%;
}
/* TASK-236 follow-on (2026-05-18) — DON'T set overflow-x:auto on
   the frame here. With overflow-x:auto, the frame becomes a scroll
   container; the table can be wider than the frame's visible width,
   but the pagination footer doesn't stretch to match the scroll-area
   width — visually the footer "doesn't span the table" (operator
   report). The cell-truncation rules below already keep the table
   within the available width; the .sf-main min-width:0 in the page-
   layout block above is the canonical fix for grid 1fr overflow.
   Leaving max-width:100% on the frame as a belt-and-suspenders cap. */
.sf-admin-communication .sf-admin-table-frame {
    max-width: 100%;
}

/* TASK-236 follow-on (2026-05-18) — make the big "open count" number
   on each per-inbox stat card render in danger-red. Per the operator's
   request: the unread counts should pop visually so an at-a-glance scan
   of the cards immediately shows where attention is needed. Scoped to
   .sf-admin-communication so other admin pages using MetricTile (Ops
   Center) keep the neutral foreground color. */
.sf-admin-communication .sf-admin-metric-tile__value {
    color: var(--danger-fg);
}

/* TASK-236 follow-on (2026-05-18) — compact row treatment for the
   /admin/communication table. Long From/To addresses (bounce sender
   addresses like MicrosoftExchange<32-hex>@<tenant>) + freely-wrapping
   AI-generated summaries were ballooning each row to ~250px. Without
   constraints the table looked comical. Fixes:
     1. Cell content vertically centered (default is baseline = top)
     2. From + To cells truncate with ellipsis at a sensible max-width
     3. Subject cell width capped; the AI summary is clamped to ONE
        line of ellipsized text (operator can see the full summary
        on the thread-detail page).
   All scoped under .sf-admin-communication so other admin tables
   aren't touched. */
.sf-admin-communication .sf-table td {
    vertical-align: middle;
    padding-top: var(--space-6, 6px);
    padding-bottom: var(--space-6, 6px);
}
.sf-admin-communication__cell-truncate {
    display: block;
    max-width: 22ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.sf-admin-communication__row-subject {
    display: block;
    max-width: 42ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.sf-admin-communication__row-summary {
    display: block;
    max-width: 42ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    margin-top: 2px;
    font-size: var(--text-12);
    color: var(--fg-subtle);
}

/* Inline action feedback toast. */
.sf-action-toast {
    padding: var(--space-12) var(--space-16);
    border-radius: var(--radius-6);
    font-size: var(--text-13);
    display: flex;
    align-items: center;
    gap: var(--space-8);
    margin-bottom: var(--space-16);
}
.sf-action-toast--success {
    background: var(--success-bg);
    border: 1px solid var(--success-border);
    color: var(--success-fg);
}
.sf-action-toast--error {
    background: var(--danger-bg);
    border: 1px solid var(--danger-border);
    color: var(--danger-fg);
}

/* ─────────────────────────────────────────────────────────────────
   2026-05-17 — TASK-228. Admin in-page tab strip.

   Used by pages that host multiple work surfaces under one URL
   (e.g. /admin/dashboard hosting Bug Reports / Feedback / Logs).
   Visually mirrors the sticky vertical .sf-settings__tabs in
   AdminLayout but lays out horizontally above the panel content.
   Each tab is a button (NOT a NavLink) — clicking fires a callback;
   the parent page updates a ?tab= querystring for deep linking.
   ───────────────────────────────────────────────────────────── */
.sf-admin-tabstrip {
    display: flex;
    flex-wrap: wrap;
    align-items: stretch;
    gap: var(--space-4);
    margin: var(--space-8) 0 var(--space-16);
    padding: 0;
    border-bottom: 1px solid var(--border);
}

.sf-admin-tabstrip__tab {
    display: inline-flex;
    align-items: center;
    gap: var(--space-8);
    padding: var(--space-10, 10px) var(--space-16);
    margin-bottom: -1px;
    background: transparent;
    border: 0;
    border-bottom: 2px solid transparent;
    border-radius: 0;
    color: var(--fg-muted);
    font-family: inherit;
    font-size: var(--text-14);
    font-weight: var(--weight-medium);
    cursor: pointer;
    transition: color 120ms ease, border-color 120ms ease, background 120ms ease;
}

.sf-admin-tabstrip__tab:hover {
    color: var(--fg);
    background: var(--bg-sunken);
}

.sf-admin-tabstrip__tab:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: var(--radius-4);
}

.sf-admin-tabstrip__tab--active,
.sf-admin-tabstrip__tab[aria-selected="true"] {
    color: var(--fg);
    border-bottom-color: var(--fg);
    font-weight: var(--weight-semibold);
}

.sf-admin-tabstrip__tab[disabled] {
    color: var(--fg-subtle);
    cursor: not-allowed;
    opacity: 0.65;
}

.sf-admin-tabstrip__badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 22px;
    height: 20px;
    padding: 0 var(--space-6, 6px);
    border-radius: 999px;
    background: var(--bg-sunken);
    color: var(--fg-subtle);
    font-size: var(--text-12);
    font-weight: var(--weight-semibold);
    font-variant-numeric: tabular-nums;
}

.sf-admin-tabstrip__tab--active .sf-admin-tabstrip__badge,
.sf-admin-tabstrip__tab[aria-selected="true"] .sf-admin-tabstrip__badge {
    background: var(--accent);
    color: #fff;
}

/* ─────────────────────────────────────────────────────────────────
   2026-05-17 — TASK-228. Ops Center metric tile.

   Three tiles render in a row above the tab strip on /admin/dashboard:
   Open Bug Reports / Open Feedback / Active Alerts (24h). Each tile
   has a headline value, a small 7-day sparkline rendered as inline
   SVG, and a delta chip comparing today vs. the avg of prior days.
   ───────────────────────────────────────────────────────────── */
.sf-admin-metric-tile-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: var(--space-16);
    margin-top: var(--space-16);
    margin-bottom: var(--space-16);
}

.sf-admin-metric-tile {
    display: grid;
    grid-template-columns: 1fr auto;
    grid-template-rows: auto 1fr auto;
    grid-template-areas:
        "caption caption"
        "value spark"
        "delta spark";
    gap: var(--space-6, 6px);
    padding: var(--space-16);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-12);
    box-shadow: 0 1px 2px rgba(15, 17, 21, 0.04), 0 6px 16px rgba(15, 17, 21, 0.04);
    min-height: 112px;
}

.sf-admin-metric-tile__caption {
    grid-area: caption;
    font-size: var(--text-12);
    font-weight: var(--weight-semibold);
    color: var(--fg-subtle);
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.sf-admin-metric-tile__value {
    grid-area: value;
    font-size: var(--text-32);
    font-weight: var(--weight-semibold);
    color: var(--fg);
    line-height: 1.1;
    font-variant-numeric: tabular-nums;
}

.sf-admin-metric-tile__delta {
    grid-area: delta;
    display: inline-flex;
    align-items: center;
    gap: var(--space-4);
    align-self: end;
    font-size: var(--text-12);
    font-weight: var(--weight-medium);
    color: var(--fg-subtle);
}

.sf-admin-metric-tile__delta--up { color: var(--danger-fg, var(--danger)); }
.sf-admin-metric-tile__delta--down { color: var(--success-fg, var(--success, #1f8a4a)); }
.sf-admin-metric-tile__delta--flat { color: var(--fg-subtle); }

.sf-admin-metric-tile__spark {
    grid-area: spark;
    display: flex;
    align-items: end;
    justify-content: end;
    align-self: stretch;
    min-width: 120px;
}

.sf-admin-metric-tile__spark svg {
    width: 100%;
    max-width: 140px;
    height: 44px;
    display: block;
}

/* When a tile carries an OnClick handler MetricTile renders as a
   <button>; reset the default button chrome so the card framing
   stays identical to the static <article> variant, then layer a
   subtle hover lift + focus ring so the click affordance reads
   the moment the cursor enters the tile. */
.sf-admin-metric-tile--clickable {
    cursor: pointer;
    text-align: left;
    font: inherit;
    color: inherit;
    width: 100%;
    transition: transform 120ms ease, box-shadow 120ms ease, border-color 120ms ease;
}

.sf-admin-metric-tile--clickable:hover {
    border-color: var(--accent);
    transform: translateY(-1px);
    box-shadow: 0 1px 2px rgba(15, 17, 21, 0.06), 0 8px 22px rgba(15, 17, 21, 0.08);
}

.sf-admin-metric-tile--clickable:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.sf-admin-metric-tile--clickable:active {
    transform: translateY(0);
}

@media (max-width: 720px) {
    .sf-admin-metric-tile-row { grid-template-columns: 1fr; }
    .sf-admin-metric-tile__value { font-size: var(--text-24); }
}

/* ─────────────────────────────────────────────────────────────────
   2026-05-17 — UserBillingRowEditorModal layout fix.

   The modal was rendering as a fullscreen pile of floating cards
   with the close-X stuck in the page sidebar because the outer
   wrapper used `.sf-modal-overlay` (undefined) instead of
   `.sf-modal-backdrop` (defined at line ~4756). The class-name
   swap restores the Pattern B cascade so the modal becomes a
   centered max-width 640px card with backdrop + max-height 90vh +
   internal scroll.

   These rules tighten the inside-the-card treatment so each
   section reads as part of one coherent modal instead of nested
   .sf-card frames.

   `.sf-form-row` was also undefined despite being referenced by
   this + other modals; defined here as the canonical "label +
   input + action button" inline row.
   ───────────────────────────────────────────────────────────── */

/* Bigger max-width for the user-billing editor specifically — the
   sections (tier / multiplier / extra usage with note) need a bit
   more breathing room than the default 560px modal allows. */
.sf-modal-backdrop > .sf-admin-user-billing-editor {
    max-width: 640px;
}

.sf-user-billing-editor-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-12);
}

.sf-user-billing-editor-close {
    flex: 0 0 auto;
    color: var(--fg-subtle);
}

.sf-user-billing-editor-close:hover {
    color: var(--fg);
    background: var(--bg-sunken);
}

/* Section inside the modal body — no individual border or
   background (the modal frame is already the card). Just spacing
   + a divider rule between sections so they read as a stacked
   form rather than separate floating cards. */
.sf-user-billing-editor-section {
    padding: var(--space-16) 0;
    border-bottom: 1px solid var(--border);
    display: flex;
    flex-direction: column;
    gap: var(--space-12);
}

.sf-user-billing-editor-section:first-child {
    padding-top: 0;
}

.sf-user-billing-editor-section:last-child {
    border-bottom: none;
    padding-bottom: 0;
}

.sf-user-billing-editor-section__title {
    margin: 0;
    font-size: var(--text-14);
    font-weight: var(--weight-semibold);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--fg-subtle);
}

.sf-user-billing-editor-section__sub {
    margin: 0;
    font-size: var(--text-13, 13px);
    color: var(--fg-muted);
    line-height: 1.5;
}

/* Identity section — compact 2-column dl for Name / Email / User id. */
.sf-user-billing-editor-identity {
    display: grid;
    grid-template-columns: 96px 1fr;
    gap: var(--space-8) var(--space-16);
    margin: 0;
    font-size: var(--text-14);
}

.sf-user-billing-editor-identity dt {
    color: var(--fg-subtle);
    font-weight: var(--weight-medium);
}

.sf-user-billing-editor-identity dd {
    margin: 0;
    color: var(--fg);
    overflow-wrap: anywhere;
}

.sf-user-billing-editor-identity__id {
    font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
    font-size: var(--text-12);
    color: var(--fg-subtle);
}

/* The identity section is informational only — no border-bottom
   divider before the next section since visually it acts as a
   header strip, not an action card. */
.sf-user-billing-editor-section--identity {
    background: var(--bg-sunken);
    margin: calc(var(--space-20) * -1) calc(var(--space-24) * -1) 0;
    padding: var(--space-16) var(--space-24);
    border-bottom: 1px solid var(--border);
    border-radius: 0;
}

/* sf-form-row — canonical inline form row used by every modal in
   the app. The class was referenced by UserBillingRowEditorModal
   + other places but never defined; rows rendered as a vertical
   pile of label/input/button with no alignment. */
.sf-form-row {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: var(--space-12);
}

.sf-form-row > label {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    font-size: var(--text-13, 13px);
    color: var(--fg-subtle);
    font-weight: var(--weight-medium);
}

.sf-form-row > label > select,
.sf-form-row > label > input[type="number"],
.sf-form-row > label > input[type="text"],
.sf-form-row > label > input[type="search"] {
    padding: var(--space-8) var(--space-12);
    border: 1px solid var(--border);
    border-radius: var(--radius-6);
    background: var(--bg);
    color: var(--fg);
    font-size: var(--text-14);
    font-weight: var(--weight-regular);
    min-width: 140px;
    transition: border-color 120ms ease, box-shadow 120ms ease;
}

.sf-form-row > label > select:hover,
.sf-form-row > label > input:hover {
    border-color: var(--border-strong);
}

.sf-form-row > label > select:focus,
.sf-form-row > label > input:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px rgba(33, 184, 120, 0.18);
}

.sf-form-row > .sf-btn {
    flex: 0 0 auto;
}

/* Standalone textarea inside the editor (Extra usage reason note).
   The textarea isn't wrapped in a .sf-form-row because it stretches
   full-width; style it directly so it doesn't render as an unstyled
   browser default. */
.sf-admin-user-billing-editor textarea {
    width: 100%;
    padding: var(--space-8) var(--space-12);
    border: 1px solid var(--border);
    border-radius: var(--radius-6);
    background: var(--bg);
    color: var(--fg);
    font: inherit;
    font-size: var(--text-14);
    resize: vertical;
    transition: border-color 120ms ease, box-shadow 120ms ease;
}

.sf-admin-user-billing-editor textarea:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px rgba(33, 184, 120, 0.18);
}

/* ---------------------------------------------------------------------
   TASK-235 (2026-05-17) — Heroes panel + /admin/users/{id} detail page.

   Two reusable layout primitives:
     - .sf-admin-heroes-section       — vertical stack inside a tab,
       with breathing room between scoreboard + recent feed sections.
     - .sf-admin-user-detail__grid    — responsive 2-column grid for
       Identity / Status / Roles / Billing cards on the user detail
       page.

   Plus a few utility classes for the user-detail page (chip cluster,
   action error, submissions section spacing). All other surfaces are
   styled by the existing .sf-card / .sf-pill / .sf-admin-table-frame
   rules above.
   --------------------------------------------------------------------- */

.sf-admin-heroes-panel > .sf-admin-heroes-section + .sf-admin-heroes-section {
    margin-top: var(--space-32);
}

.sf-admin-user-detail__grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: var(--space-16);
    margin-top: var(--space-16);
}

.sf-admin-user-detail__submissions {
    margin-top: var(--space-32);
}

/* 2026-05-30 — full-width Billing & usage section below the
   Identity/Status/Roles grid. The header puts the section title +
   "Manage billing" button on one row; the stat tiles + ledger + tier
   history tables stack below with breathing room. */
.sf-admin-user-detail__billing {
    margin-top: var(--space-32);
}

.sf-admin-user-detail__billing > .sf-card__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-16);
    flex-wrap: wrap;
}

.sf-admin-user-detail__billing .sf-stat-tiles {
    margin-top: var(--space-16);
}

.sf-admin-user-detail__chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-8);
    margin: var(--space-8) 0;
}

.sf-admin-user-detail__error {
    margin-top: var(--space-8);
    color: var(--state-error, var(--state-failed, #b91c1c));
    font-size: var(--text-13);
}

/* ---------------------------------------------------------------------
   TASK-237 PR3 (2026-05-17) — Analytics tab on the Ops Center dashboard.

   Layout primitives:
     - .sf-admin-analytics-panel    — vertical stack of KPI row + 4 sections.
     - .sf-admin-analytics-section  — section group (Acquisition / Behavior
                                       / Conversion / Retention) with
                                       breathing room between siblings.
     - .sf-analytics-grid           — responsive 2-up grid that hosts the
                                       cards inside Acquisition + Conversion.

   Chart styling for the inline SVG components:
     - .sf-analytics-bar-chart      — stacked daily bar chart canvas.
     - .sf-analytics-bar--new       — solid accent (new visitors).
     - .sf-analytics-bar--returning — muted fill (returning visitors).
     - .sf-analytics-bar-legend     — small swatch+text legend below.

   .sf-analytics-funnel / __row / __label / __track / __bar / __count
                                    — horizontal conversion funnel.

   Plus a small footer line that reiterates the privacy commitments.
   --------------------------------------------------------------------- */

.sf-admin-analytics-panel > .sf-admin-metric-tile-row,
.sf-admin-analytics-panel > .sf-admin-analytics-section + .sf-admin-analytics-section {
    margin-top: var(--space-32);
}

.sf-analytics-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
    gap: var(--space-16);
    margin-top: var(--space-12);
}

.sf-analytics-card__title {
    margin: 0 0 var(--space-8) 0;
    font-size: var(--text-13);
    font-weight: 600;
    color: var(--text-muted, #6b7280);
    letter-spacing: 0.02em;
    text-transform: uppercase;
}

.sf-analytics-bar-chart {
    display: block;
    width: 100%;
    height: auto;
    overflow: visible;
}

.sf-analytics-bar {
    transition: opacity 0.12s ease;
}

.sf-analytics-bar:hover {
    opacity: 0.85;
}

.sf-analytics-bar--new {
    fill: var(--brand-accent, #2563eb);
}

.sf-analytics-bar--returning {
    fill: var(--brand-accent-muted, #93c5fd);
}

.sf-analytics-bar-axis {
    display: flex;
    justify-content: space-between;
    margin-top: var(--space-4);
    font-size: var(--text-12);
    color: var(--text-muted, #6b7280);
}

.sf-analytics-bar-legend {
    display: flex;
    align-items: center;
    gap: var(--space-8);
    margin-top: var(--space-8);
    font-size: var(--text-12);
    color: var(--text-muted, #6b7280);
}

.sf-analytics-bar-legend__swatch {
    display: inline-block;
    width: 12px;
    height: 12px;
    margin-right: 4px;
    margin-left: var(--space-8);
    border-radius: 2px;
}

.sf-analytics-bar-legend__swatch.sf-analytics-bar--new {
    background-color: var(--brand-accent, #2563eb);
}

.sf-analytics-bar-legend__swatch.sf-analytics-bar--returning {
    background-color: var(--brand-accent-muted, #93c5fd);
}

.sf-analytics-funnel {
    display: flex;
    flex-direction: column;
    gap: var(--space-8);
}

.sf-analytics-funnel__row {
    display: grid;
    grid-template-columns: 160px 1fr 120px;
    align-items: center;
    gap: var(--space-8);
}

.sf-analytics-funnel__label {
    font-size: var(--text-13);
    color: var(--text-muted, #6b7280);
}

.sf-analytics-funnel__track {
    background-color: var(--surface-muted, #f3f4f6);
    border-radius: 4px;
    height: 18px;
    overflow: hidden;
}

.sf-analytics-funnel__bar {
    background-color: var(--brand-accent, #2563eb);
    height: 100%;
    border-radius: 4px;
    min-width: 2px;
}

.sf-analytics-funnel__count {
    text-align: right;
    font-size: var(--text-13);
}

.sf-analytics-funnel__pct {
    color: var(--text-muted, #6b7280);
    margin-left: var(--space-4);
}

.sf-admin-analytics-footer {
    margin-top: var(--space-32);
    padding-top: var(--space-16);
    border-top: 1px solid var(--border-default, #e5e7eb);
    font-size: var(--text-12);
    color: var(--text-muted, #6b7280);
}

/* ============================================================
   THE TABLE — sf-datatable (Phase 2, 2026-05-31)
   The canonical data-table look for SfDataTable<TRow>. Builds on
   the admin-table-frame system above (sticky mono-uppercase thead,
   card frame, sf-admin-pagination) and the controls/controls.css
   "THE TABLE" spec. Tokens only. Status = badge (never bare text);
   numeric columns right-align + tabular-nums via .sf-table__num.
   ============================================================ */
.sf-datatable {
    display: flex;
    flex-direction: column;
}

/* Toolbar — reuses .sf-admin-toolbar; these tune the datatable slots. */
.sf-datatable__toolbar {
    align-items: center;
    gap: var(--space-12);
}
.sf-datatable__title {
    font-size: var(--text-16);
    font-weight: var(--weight-semibold);
    color: var(--fg);
    margin-right: auto;
}
.sf-datatable__search { min-width: 220px; }
.sf-datatable__actions {
    display: flex;
    align-items: center;
    gap: var(--space-12);
    flex-wrap: wrap;
}

/* Active-filter chips. .sf-chip is the base pill; these add the
   label + remove-affordance the base chip doesn't define. */
.sf-datatable__chips {
    display: flex;
    align-items: center;
    gap: var(--space-8);
    flex-wrap: wrap;
}
.sf-datatable__chip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-4);
}
.sf-chip__label { white-space: nowrap; }
.sf-chip__remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    padding: 0;
    border: 0;
    border-radius: var(--radius-pill);
    background: transparent;
    color: var(--fg-muted);
    font-size: var(--text-14);
    line-height: 1;
    cursor: pointer;
}
.sf-chip__remove:hover { background: var(--bg-sunken); color: var(--fg); }
.sf-chip__remove:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.sf-datatable__clear-all {
    background: none;
    border: 0;
    color: var(--accent-fg);
    font-size: var(--text-12);
    font-weight: var(--weight-medium);
    cursor: pointer;
    padding: 0 var(--space-4);
}
.sf-datatable__clear-all:hover { text-decoration: underline; }
.sf-datatable__clear-all:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }

/* The table frame reuses .sf-admin-table-frame verbatim; the
   datatable variant only needs to drop the default top margin
   (the toolbar already provides separation). */
.sf-datatable__frame { margin-top: var(--space-12); }
.sf-datatable__toolbar + .sf-datatable__frame { margin-top: var(--space-8); }

/* Header cell — keep the sort button + filter funnel on one row. */
.sf-datatable__th {
    display: inline-flex;
    align-items: center;
    gap: var(--space-4);
}

/* Compact density — tighten cell padding across header + body. */
.sf-datatable--compact .sf-admin-table-frame .sf-table th,
.sf-datatable--compact .sf-admin-table-frame .sf-table td {
    padding: var(--space-4) var(--space-12);
}

/* Loading skeleton rows — shimmer bars, never a spinner (per the
   controls handoff: the brand mark / skeleton is the loader). */
.sf-datatable__skel-row td { vertical-align: middle; }
.sf-datatable__skel {
    display: block;
    height: 12px;
    border-radius: var(--radius-4);
    background: linear-gradient(
        90deg,
        var(--bg-sunken) 0%,
        color-mix(in oklab, var(--bg-sunken) 60%, var(--border)) 50%,
        var(--bg-sunken) 100%);
    background-size: 200% 100%;
    animation: sf-datatable-shimmer 1.2s ease-in-out infinite;
}
@keyframes sf-datatable-shimmer {
    0% { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}
@media (prefers-reduced-motion: reduce) {
    .sf-datatable__skel { animation: none; }
}

/* Empty state — centered brand mark + message inside the table body. */
.sf-datatable__empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-12);
    padding: var(--space-48) var(--space-24);
    color: var(--fg-subtle);
}
.sf-datatable__empty-text { margin: 0; font-size: var(--text-13, 13px); }

/* Pager — reuses .sf-admin-pagination; nothing extra needed but the
   hook is here for datatable-specific overrides. */
.sf-datatable__pager { /* inherits .sf-admin-pagination */ }

/* ------------------------------------------------------------
   Per-column filter popover (sf-colfilter). Distinct namespace
   from the legacy inline .sf-th-filter <select> so both coexist
   during the migration. Popover open/close is Blazor-driven
   (focusout), matching the SiteSearch pattern.
   ------------------------------------------------------------ */
.sf-colfilter { position: relative; display: inline-flex; }
.sf-colfilter__btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    padding: 0;
    border: 0;
    border-radius: var(--radius-4);
    background: transparent;
    color: var(--fg-subtle);
    cursor: pointer;
}
.sf-colfilter__btn:hover { background: var(--bg-sunken); color: var(--fg); }
.sf-colfilter__btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.sf-colfilter__btn--active { color: var(--accent-fg); }
.sf-colfilter__pop {
    position: absolute;
    top: calc(100% + var(--space-4));
    left: 0;
    z-index: var(--z-popover, 200);
    min-width: 180px;
    max-height: 280px;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: var(--space-4);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-6);
    box-shadow: var(--shadow-pop);
}
.sf-colfilter__input {
    font-size: var(--text-13, 13px);
    padding: var(--space-4) var(--space-8);
    border: 1px solid var(--border);
    border-radius: var(--radius-4);
    background: var(--bg);
    color: var(--fg);
}
.sf-colfilter__input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.sf-colfilter__opt {
    text-align: left;
    background: none;
    border: 0;
    border-radius: var(--radius-4);
    padding: var(--space-4) var(--space-8);
    font-size: var(--text-13, 13px);
    color: var(--fg);
    cursor: pointer;
    text-transform: none;
    letter-spacing: normal;
}
.sf-colfilter__opt:hover { background: var(--bg-sunken); }
.sf-colfilter__opt.is-active { background: var(--bg-sunken); color: var(--accent-fg); font-weight: var(--weight-medium); }
.sf-colfilter__opt:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
