/* Design System - Data Analyst Portal v2 */

:root {
    /* Colors — `--primary` is the canonical app-wide brand colour
       (blue), used across nav, buttons, links, and active states on
       login, dashboard, catalog, marketplace, admin, etc. The /home
       redesign opted into a green/navy palette via local
       `.home-mock` / `.advanced-mock` overrides; those scopes set
       their own `--hp-primary` so the green only applies where the
       redesign explicitly opts in. Everything else inherits blue. */
    --primary: #0073D1;
    --primary-light: rgba(0, 115, 209, 0.1);
    --primary-dark: #005BA3;
    --brand-accent: #4F9DEB;
    --hero-bg: #0f1b3a;
    --hero-ink: #f3f6ff;
    --text-primary: #0e1525;
    --text-secondary: #4a5168;
    --background: #f6f7fa;
    --surface: #ffffff;
    --surface-dim: #f0f2f6;
    --border: #e4e7ee;
    --border-light: #f0f2f6;
    --border-dark: #c4c4c4;
    --success: #10B77F;
    --warning: #F59F0A;
    --error: #EA580C;
    --info-bg: #eef3ff;
    --info-ink: #1c3994;
    --info-line: #4f7cf2;
    --warn-bg: #fff8e6;
    --warn-ink: #6e4d00;
    --warn-line: #f5c84b;

    /* Typography — system stack matching the CEO mock so weight +
       letterforms render identically on macOS (San Francisco) and
       Windows (Segoe UI). Inter remains a graceful fallback in the
       cascade. */
    --font-primary: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Inter", system-ui, sans-serif;
    --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;

    /* Font sizes */
    --text-xs: 10px;
    --text-sm: 12px;
    --text-base: 14px;
    --text-md: 16px;
    --text-lg: 18px;
    --text-xl: 24px;
    --text-2xl: 30px;

    /* Font weights */
    --font-normal: 400;
    --font-medium: 500;
    --font-semibold: 600;
    --font-bold: 700;
    --font-extrabold: 800;

    /* Extra text colors — referenced by inline page styles that should
       eventually migrate (home hero used --hp-text-muted, etc.) */
    --text-muted: #9CA3AF;
    --text-disabled: #D1D5DB;

    /* Spacing — 4-multiple scale. Gaps filled to cover dashboard /home
       inline-style values (28px / 40px / 48px / 64px). */
    --space-1: 4px;
    --space-2: 8px;
    --space-3: 12px;
    --space-4: 16px;
    --space-5: 20px;
    --space-6: 24px;
    --space-7: 28px;
    --space-8: 32px;
    --space-9: 40px;
    --space-10: 48px;
    --space-12: 64px;

    /* App header — single source of truth for the sticky header height so
       page-level sticky sub-toolbars can offset off it (var, not magic
       numbers). Kept in sync with `.app-header` padding + content height. */
    --app-header-height: 57px;

    /* Border radius */
    --radius-sm: 4px;
    --radius-md: 6px;
    --radius-lg: 8px;
    --radius-xl: 12px;
    --radius-2xl: 16px;
    --radius-full: 9999px;

    /* Shadows. --shadow-card is the canonical card elevation;
       --shadow-elevated lifts hero blocks. Shadow tints aligned with
       the new green/navy palette so heavy elevations don't carry a
       leftover blue cast. */
    --shadow-sm: rgba(15, 27, 58, 0.05) 0px 1px 2px 0px, rgba(15, 27, 58, 0.06) 0px 1px 3px 0px;
    --shadow-md: rgba(15, 27, 58, 0.08) 0px 4px 16px 0px;
    --shadow-card: 0 1px 3px 0 rgba(15, 27, 58, 0.06), 0 1px 2px -1px rgba(15, 27, 58, 0.05);
    --shadow-elevated: 0 8px 24px rgba(15, 27, 58, 0.14);

    /* Focus + interaction — alpha brand-green below uses color-mix on
       var(--ds-primary) so it follows the active theme (light/blue/dark);
       these were hardcoded brand-green rgba() literals (#497 §5). */
    --focus-ring: 0 0 0 3px color-mix(in srgb, var(--ds-primary) 25%, transparent);
    --transition-fast: 120ms ease;
    --transition-base: 200ms ease;
    --transition-slow: 320ms ease;

    /* Layout — outer chrome widths. Pages should reference these instead
       of hardcoding 1280 / 1200 / 800 to keep /home and /dashboard in
       step. */
    --width-narrow: 800px;
    --width-app: 1280px;
    --width-wide: 1400px;

    /* Legacy token aliases — keep absorbed style.css rules rendering
       until Task 16 cleanup. Each legacy name maps onto its modern
       equivalent so every absorbed selector resolves to a real value
       instead of falling back to `unset`. */
    --bg: var(--background);
    --card-bg: var(--surface);
    --text: var(--text-primary);
    --text-light: var(--text-secondary);
    --secondary: var(--text-secondary);
    --radius: var(--radius-lg);

    /* Intermediate font sizes used by the legacy absorbed section.
       These fill gaps in the 10/12/14/16/18/24/30px scale. Added in
       the design-token migration pass (issue #400) so rem literals
       in legacy selectors resolve through a named token and follow
       operator overrides. */
    --text-13: 13px;   /* 0.8125rem — help text, small labels, code snippets */
    --text-15: 15px;   /* 0.9375rem — slightly-larger body copy */
    --text-22: 22px;   /* 1.375rem  — logo / brand lockup heading */
    --text-28: 28px;   /* 1.75rem   — login card h2 display size */

    /* Card accent surface tints — applied as background on .card-error,
       .card-highlight, .card-ai, .card-success. Operator overrides to
       the corresponding --ds-accent-* set automatically flow through. */
    --card-error-bg:     #fff8f8;
    --card-highlight-bg: #f8faff;
    --card-ai-bg:        #f8fdf8;
    --card-success-bg:   #f8fdf8;

    /* Badge role colors — semantic fills for .badge-analyst / .badge-privileged /
       .badge-admin. These live outside the --ds-accent-* family because they map
       to UX roles rather than alert severity, but operator overrides apply. */
    --badge-success-bg:  #e8f5e9;
    --badge-success-ink: #2e7d32;
    --badge-warn-bg:     #fff3e0;
    --badge-warn-ink:    #ef6c00;
    --badge-info-bg:     #e3f2fd;
    --badge-info-ink:    #1565c0;

    /* Flash message fills — success / error / info / warning. Kept separate
       from --ds-accent-* so an operator can tune the flash palette independently
       of card accents. Names follow the existing .flash-{variant} class family. */
    --flash-success-bg:     #e8f5e9;
    --flash-success-ink:    #2e7d32;
    --flash-success-border: #c8e6c9;
    --flash-error-bg:       #ffebee;
    --flash-error-ink:      #c62828;
    --flash-error-border:   #ffcdd2;
    --flash-info-bg:        #e3f2fd;
    --flash-info-ink:       #1565c0;
    --flash-info-border:    #bbdefb;
    --flash-warn-bg:        #fff3e0;
    --flash-warn-ink:       #ef6c00;
    --flash-warn-border:    #ffe0b2;
    /* v2 flash ink shades (slightly darker than .flash-* for the v2 family). */
    --flash-success-v2-ink: #0d9668;
    --flash-error-v2-ink:   #c2410c;

    /* Code-block dark surface — the pre-highlight dark code panel used in the
       legacy Quick Start / SSH setup cards. Distinct from the page surface
       tokens (which are light) so dark-on-light and light-on-dark don't
       accidentally share the same token. */
    --code-dark-bg:     #1a1a2e;
    --code-dark-ink:    #e4e4e7;
    --code-dark-border: #2d2d44;
    /* Pure white for .code-block inside .code-block-wrapper (dark translucent
       surface) — distinct from --code-dark-ink so contrast there is maximal. */
    --code-wrapper-ink: #fff;

    /* Form placeholder — the medium-grey used in text inputs and textareas. */
    --placeholder-color: #9aa0a6;

    /* Username box — sky-blue highlight panel on the dashboard setup page.
       The copy-button family reuses the same blue so the panel reads
       as one coherent block. */
    --username-box-bg:   #f0f9ff;
    --username-box-line: #0ea5e9;
    --username-ink:      #0369a1;
    --btn-copy-bg:       #0ea5e9;
    --btn-copy-hover:    #0284c7;
    --btn-copy-success:  #22c55e;

    /* Info box — subtle surface used for hint / info panels in forms. */
    --info-box-bg:     #f8fafc;
    --info-box-border: #e2e8f0;

    /* CC setup card gradient — purple-to-indigo used by the Claude Code
       setup card. Exposed as tokens so an operator can swap the gradient
       without editing the rule directly. */
    --cc-gradient-from: #667eea;
    --cc-gradient-to:   #764ba2;

    /* Support info panel — same surface as --info-box-bg; named separately
       so the .support-info component can be themed independently. */
    --support-info-bg: var(--info-box-bg);

    /* Username preview text — slate-grey used in .username-preview label. */
    --username-preview-color: #64748b;
}

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

body {
    font-family: var(--font-primary);
    background-color: var(--background);
    color: var(--text-primary);
    font-size: var(--text-base);
    line-height: 1.5;
    min-height: 100vh;
}

/* =====================================================
   Absorbed from the deleted app/web/static/style.css.
   Kept verbatim here so legacy classes used by login/auth
   pages, error pages, and password-setup pages keep
   rendering. Individual rules retire in the design-pass
   migration tasks; final cleanup in Task 16 removes the
   ones no template references after that sweep.
   ===================================================== */

.container {
    /* Canonical page-shell width + gutters. Pages inherit this
       directly — no per-page max-width / padding override needed.
       The 16px / 48px asymmetric vertical padding gives a tight gap
       between the sticky nav and the first hero (matches /home) and
       generous breathing room above the footer. Side gutters are
       32px so dense tables / grids align to the same vertical rules
       as the canonical `.page-header--hero`.
       Pages that genuinely need narrower line-length (setup wizards,
       single-form flows) opt in via .container--narrow; wide tables
       opt in via .container--wide. */
    max-width: var(--width-app);
    margin: 0 auto;
    padding: 16px 32px 48px;
}
/* Strip default `<main>` padding/margin when it sits inside our
   canonical `.container`. The container already owns the gutters,
   so the `<main>` element is a pure flow container. */
.container > main {
    margin: 0;
    padding: 0;
}

/* Width modifiers — opt-in alternatives to the default page shell. */
.container--narrow { max-width: var(--width-narrow); }   /* 800px — long-form reading, setup flows */
.container--wide   { max-width: var(--width-wide); }     /* 1400px — admin index lists, marketplace grids */
.container--full   { max-width: none; padding-left: var(--space-4); padding-right: var(--space-4); }

/* ── A content page as a white SHEET ──────────────────────────────────────
   `.idx` below (index pages) and `.container--full` in admin_page.css (every
   admin page) both paint their shell `--ds-surface`, and both say why in their
   own comments: a page left on the app background reads as a different product
   from the one it was reached from. Content pages on base_page.html /
   base_ds.html had no way to say the same thing, which is how the semantic
   layer and the metrics list ended up as the grey stop in a white app — you
   reach them from the Library's Definitions block, and the canvas changes
   colour on the way (#1898).

   Opt in per page: `{% block body_attrs %}class="page-sheet"{% endblock %}`.

   FULL BLEED, on the body rather than on `.container`: the container is padded
   and max-width'd, so painting that would leave grey gutters beside a white
   column — an oversized card, not a page. This is the same choice `.idx`
   documents ("no grey gutters beside the centred header either").

   `body.detail-page` is in the same rule rather than opting in template by
   template: the nine detail pages (data package, table, recipe, plugin,
   marketplace item, data app, collection, file, memory domain) already carry
   that class for their footer treatment, so it is the marker for this family
   and one selector keeps all nine from drifting apart. Cards-on-grey was their
   look, and cards on a sheet is the look every index page they are reached from
   already has — the cards keep their own borders either way.

   `html body.…` carries the extra element on purpose: the rule these have to
   beat is `[data-theme="paper"] body` in paper-skin.css (the sheet that paints
   the grey), which ties on specificity, and sheet order is not a thing a page
   should have to reason about. */
html body.page-sheet,
html body.detail-page { background: var(--ds-surface); }

/* Index / resource-list page layout (#896 prototype) — used via
   base_index.html by My Stack, Catalog, and similar list pages. The
   header zone (title + description + actions + search + tabs) and the
   full-width band below it that carries the card grid are one unbroken
   surface, running to the foot of the page. Shared here so pages carry
   no per-page background/border styling. Structure/spacing/backgrounds
   only. */
.idx {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    /* The header zone reads as part of the page surface, not as a grey
       band above it: paint the whole shell --ds-surface so the app
       background never shows behind the title/description/actions row
       (full-width, so no grey gutters beside the centred header either). */
    background: var(--ds-surface);
}
.idx-head {
    width: 100%;
    max-width: var(--width-wide);
    margin: 0 auto;
    padding: 32px 32px 0;
}
/* The tab strip is the last row of the header; drop its bottom margin
   so the content band sits flush beneath it. */
.idx-head > :last-child { margin-bottom: 0; }
/* No divider above the band: header and body share one surface, so a
   hairline between them drew a line across the middle of a single page.
   Spacing alone separates the two zones. */
.idx-band {
    flex: 1 0 auto;
    background: var(--ds-surface);
}
.idx-band-inner {
    width: 100%;
    max-width: var(--width-wide);
    margin: 0 auto;
    padding: 28px 32px 48px;
}
/* The global `footer` rule adds a 48px top margin for breathing room on
   content pages; inside the index shell that margin opened a grey gap
   between the white band and the footer (the band's flex-grow stops at
   the margin). Zero it so the white band runs flush to the footer. The
   hairline `border-top` divider is dropped too — the white band already
   provides the visual break above the credit. */
.idx > footer {
    margin-top: 0;
    border-top: none;
    background: var(--ds-surface);
}

/* Catalog detail pages (package / table / recipe) drop the footer's
   hairline divider so the credit sits flush below the page body. Rail-
   scoped so the default topnav chrome renders byte-identically. */
html[data-ui-layout="rail"] body.detail-page footer {
    border-top: none;
}

/* ── Connect-anywhere banner component (macros/_connect_banner.html) ─────
   Reusable "same knowledge, everywhere" card. Two variants share one
   light-green, borderless surface and one CTA:
     .cbn--inline  compact row — copy + tool chips + CTA (e.g. /ask).
     .cbn--orbit   illustration-led — the Agnes orb as a hub with orbiting
                   tool satellites, then copy + CTA (e.g. /stack).
   Colours ride the --ds-* tokens; no raw hex, no per-page banner CSS. */
.cbn {
    background: var(--ds-agnes-soft);
    border: none;
    border-radius: 16px;
    text-align: left;
}
/* --ds-agnes-soft is a LIGHT-ONLY tint (it has no dark-theme override), so
   under `data-theme="dark"` the banner kept its near-white #ecfdf5 fill while
   --ds-text-primary flipped to near-white too — the title landed at ~1:1
   contrast and disappeared. Tint the current surface with the Agnes green
   instead, the same idiom `.ag-pv-bubble` (agents.html) already uses for this
   exact reason, so the fill follows the theme and the text tokens stay legible
   on it. Dark-scoped: the light + paper themes keep the flat -soft fill.
   `.cbn--bar` opts out — it paints the Knowledge Layer hero's gradient (see
   the shared fill rule below), which already follows --ds-surface. */
:root[data-theme="dark"] .cbn:not(.cbn--bar) {
    background: color-mix(in srgb, var(--ds-agnes) 18%, var(--ds-surface));
}
.cbn-title { color: var(--ds-text-primary); font-weight: 750; letter-spacing: -0.02em; }
.cbn-copy { color: var(--ds-text-secondary); }
.cbn-cta {
    display: inline-flex; align-items: center; gap: 8px; flex-shrink: 0;
    background: var(--ds-primary); color: var(--ds-text-inverse);
    border: none; border-radius: var(--ds-radius-pill);
    padding: 11px 22px; font-size: 14px; font-weight: 650; letter-spacing: -0.01em;
    text-decoration: none; cursor: pointer;
    box-shadow: 0 4px 12px color-mix(in srgb, var(--ds-primary) 25%, transparent);
    transition: background 0.15s, transform 0.1s;
}
/* The close button on a dismissible card. Quiet at rest — this is a way out
   of an invitation, not a second action competing with the CTA beside it —
   and it only exists where the call site passed a `dismiss_key`. Positioned
   in the flow rather than absolutely, so it cannot land on top of the copy
   when the bar wraps on a narrow column. */
.cbn-dismiss {
    flex: 0 0 auto;
    align-self: flex-start;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    margin-left: 4px;
    padding: 0;
    border: none;
    border-radius: var(--ds-radius-pill, 999px);
    background: none;
    color: var(--ds-text-muted);
    cursor: pointer;
}
.cbn-dismiss:hover {
    background: color-mix(in srgb, var(--ds-text-primary) 8%, transparent);
    color: var(--ds-text-primary);
}
.cbn-dismiss:focus-visible {
    outline: var(--ds-focus-outline);
    outline-offset: var(--ds-focus-outline-offset);
}
.cbn-cta:hover { background: var(--ds-primary-dark); }
.cbn-cta:active { transform: translateY(1px); }

/* Bar variant — one row: white "plugin" tile (Agnes orb) · copy · CTA right. */
.cbn--bar { display: flex; align-items: center; gap: 18px; padding: 16px 20px; }
.cbn-bar-icon {
    flex-shrink: 0; width: 52px; height: 52px; border-radius: 14px;
    background: var(--ds-surface); border: 1px solid var(--ds-border-light);
    box-shadow: 0 2px 8px color-mix(in srgb, var(--ds-text-primary) 7%, transparent);
    display: grid; place-items: center;
}
.cbn-bar-orb { width: 30px; height: 30px; display: block; }
.cbn-bar-text { flex: 1 1 auto; min-width: 0; }
.cbn--bar .cbn-title { font-size: 17px; line-height: 1.18; margin: 0 0 3px; }
.cbn--bar .cbn-copy { margin: 0; font-size: 13px; line-height: 1.45; }
.cbn--bar .cbn-cta { flex-shrink: 0; }
@media (max-width: 620px) {
    .cbn--bar { flex-wrap: wrap; }
    .cbn--bar .cbn-bar-text { flex-basis: 100%; order: 2; }
    .cbn--bar .cbn-cta { order: 3; }
}

/* Shared tool chip */
.cbn-tool {
    display: grid; place-items: center;
    background: var(--ds-surface); border: 1px solid var(--ds-agnes-line);
    box-shadow: 0 1px 3px color-mix(in srgb, var(--ds-text-primary) 6%, transparent);
}
.cbn-tool img { object-fit: contain; display: block; }

/* Inline variant */
.cbn--inline { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; padding: 15px 18px; }
.cbn--inline .cbn-lead { display: flex; flex-direction: column; gap: 3px; flex: 1; min-width: 220px; }
.cbn--inline .cbn-title { font-size: 14.5px; }
.cbn--inline .cbn-copy { margin: 0; font-size: 12.5px; line-height: 1.45; }
.cbn--inline .cbn-tools { display: flex; align-items: center; gap: 12px; }
.cbn--inline .cbn-tool { width: 38px; height: 38px; border-radius: 11px; }
.cbn--inline .cbn-tool img { width: 22px; height: 22px; }
.cbn--inline .cbn-tool svg { width: 20px; height: 20px; color: var(--ds-text-secondary); }

/* Orbit variant */
.cbn--orbit { display: flex; align-items: center; gap: 26px; flex-wrap: wrap; padding: 18px 26px; }
.cbn--orbit .cbn-body { flex: 1 1 300px; min-width: 240px; }
.cbn--orbit .cbn-title { font-size: 20px; line-height: 1.14; margin: 0 0 7px; }
.cbn--orbit .cbn-copy { margin: 0 0 15px; font-size: 13.5px; line-height: 1.5; max-width: 420px; }
.cbn-orbit { position: relative; width: 200px; height: 168px; flex-shrink: 0; }
.cbn-orbit .ring { position: absolute; border-radius: 50%; border: 1.5px dashed var(--ds-agnes-line); }
.cbn-orbit .r1 { width: 156px; height: 156px; left: 22px; top: 6px; opacity: 0.8; }
.cbn-orbit .r2 {
    width: 106px; height: 106px; left: 47px; top: 31px; border-style: solid; border-color: transparent;
    background: radial-gradient(circle, color-mix(in srgb, var(--ds-agnes) 10%, transparent), transparent 70%);
}
.cbn-orbit .hub {
    position: absolute; left: 68px; top: 52px; width: 64px; height: 64px; border-radius: 17px;
    background: var(--ds-surface); border: 1px solid var(--ds-agnes-line); overflow: hidden;
    display: grid; place-items: center;
    box-shadow: 0 10px 22px color-mix(in srgb, var(--ds-agnes) 22%, transparent);
}
.cbn-orbit .hub img { width: 46px; height: 46px; object-fit: contain; display: block; }
.cbn-orbit .sat {
    position: absolute; width: 36px; height: 36px; border-radius: 11px;
    background: var(--ds-surface); border: 1px solid var(--ds-agnes-line);
    display: grid; place-items: center;
    box-shadow: 0 5px 13px color-mix(in srgb, var(--ds-text-primary) 12%, transparent);
}
.cbn-orbit .sat img { width: 19px; height: 19px; object-fit: contain; display: block; }
.cbn-orbit .s1 { left: 108px; top: 1px; }
.cbn-orbit .s2 { left: 2px; top: 66px; }
.cbn-orbit .s3 { left: 52px; top: 130px; }
.cbn-orbit .dot { position: absolute; width: 4px; height: 4px; border-radius: 50%; background: color-mix(in srgb, var(--ds-agnes) 45%, transparent); }
.cbn-orbit .d1 { left: 154px; top: 32px; }
.cbn-orbit .d2 { left: 28px; top: 126px; }
.cbn-orbit .d3 { left: 108px; top: 150px; }
@media (max-width: 720px) {
    .cbn-orbit { display: none; }
    .cbn--orbit { padding: 18px 20px; }
}

/* ── Product notice (macros/_page_notice.html) ───────────────────────────
   The quiet "still being finished" caveat that closes a page head. Tuned to
   be secondary on purpose: the treatment it replaced filled the panel with
   --ds-warn-bg, then stacked a 34px tinted icon tile, a warn-ink 15px/650
   heading and a bordered pill on top of it — four amber signals for copy
   nobody has to act on, which read as a system error rather than a note.
   Here the amber survives as ONE carrier (a 15px glyph) over a
   half-strength wash, with neutral type on top. All three themes are covered
   by the tokens: --ds-warn-* is redefined per theme (dark maps to the
   translucent accent tints), so the mixes below follow the theme instead of
   pinning a light-only pastel. */
.pnote {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    margin: 0 0 10px;
    padding: 10px 13px;
    background: color-mix(in srgb, var(--ds-warn-bg) 45%, var(--ds-surface));
    border: 1px solid color-mix(in srgb, var(--ds-warn-line) 50%, transparent);
    border-radius: 10px;
}
/* Optical alignment: the glyph centres on the title's x-height rather than
   its line box, so it reads as sitting ON the first line. */
.pnote-icon {
    flex-shrink: 0;
    display: grid;
    place-items: center;
    margin-top: 2px;
    /* Full-strength ink: this glyph is the ONE amber carrier, so it should be
       the most saturated thing in the panel. Measured 4.8:1 against the wash
       in the weakest theme (paper) — comfortably over the 3:1 floor for a
       graphic, and it does not need to carry meaning on its own (aria-hidden;
       the title says the same thing in words). */
    color: var(--ds-warn-ink);
}
.pnote-body { min-width: 0; }
.pnote-title {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0;
    font-size: 13px;
    font-weight: 600;
    line-height: 1.4;
    color: var(--ds-text-primary);
}
.pnote-text {
    margin: 3px 0 0;
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--ds-text-secondary);
    max-width: 78ch;
}
/* Flat marker, not a chip — see the macro's note on why. At 9.5px this is
   "small text" for contrast purposes however bold it is, so the amber is
   pulled toward the text colour rather than thinned with alpha: warn-ink
   alone measures only 4.8:1 on the paper theme's wash, and alpha took it to
   4.0 — under the 4.5 floor. This mix keeps the amber cast and measures
   5.9 / 8.8 / 11.5 across paper / blue / dark. */
.pnote-badge {
    flex-shrink: 0;
    font-size: 9.5px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: color-mix(in srgb, var(--ds-warn-ink) 85%, var(--ds-text-primary));
}

/* ── Knowledge-surface fill (.cbn--bar) ──────────────────────────────────
   The near-white layered gradient + soft lift that says "this is Agnes
   itself". It was shared by two banners — the Knowledge Layer hero (`.klb`)
   and the compact connect bar — until the hero was retired; the connect bar
   is the only consumer now, but the fill is kept as its own rule because
   `.cld-door--lead` below imitates it deliberately and the two should stay
   readable side by side.

   Almost-white base with a whisper of mint on the left, a very light purple on
   the right, and subtle radial blues — an expensive-SaaS gradient, no hard
   edges. Kept faint so it reads as near-white. Every layer rides --ds-surface
   or a color-mix into it, so the fill follows every palette theme (which is
   why `.cbn--bar` needs no dark-theme override of its own). */
.cbn--bar {
    background:
        radial-gradient(60% 120% at 84% 12%, color-mix(in srgb, var(--ds-kind-agent) 7%, transparent), transparent 60%),
        radial-gradient(58% 120% at 6% 92%, color-mix(in srgb, var(--ds-primary) 9%, transparent), transparent 60%),
        radial-gradient(90% 140% at 50% -20%, color-mix(in srgb, var(--ds-assistant) 6%, transparent), transparent 62%),
        linear-gradient(108deg,
            color-mix(in srgb, var(--ds-agnes) 5%, var(--ds-surface)) 0%,
            var(--ds-surface) 48%,
            color-mix(in srgb, var(--ds-kind-agent) 5%, var(--ds-surface)) 100%);
    /* Depth through light, not a heavy card: a whisper of a shadow + a soft
       coloured lift. */
    box-shadow:
        0 1px 2px color-mix(in srgb, var(--ds-text-primary) 4%, transparent),
        0 22px 52px -42px color-mix(in srgb, var(--ds-primary) 34%, transparent);
}

/* Dashboard */
.dashboard h2 {
    font-size: var(--text-xl);
    font-weight: 600;
    margin-bottom: 24px;
    color: var(--text);
}

/* Header */
header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 20px 0;
    margin-bottom: 24px;
    border-bottom: 1px solid var(--border);
    flex-wrap: wrap;
    gap: 16px;
}

a.logo { text-decoration: none; color: inherit; display: block; }
a.logo:hover h1 { color: var(--primary); }

.logo h1 {
    font-size: var(--text-22);
    font-weight: 600;
    color: var(--text);
    margin: 0;
    letter-spacing: -0.02em;
}

.logo .subtitle {
    font-size: var(--text-13);
    color: var(--text-light);
    margin-top: 2px;
}

.user-info {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: var(--text-base);
    color: var(--text-light);
}

.avatar {
    width: 32px;
    height: 32px;
    border-radius: 50%;
}

/* Buttons */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 10px 20px;
    font-size: var(--text-base);
    font-weight: 500;
    text-decoration: none;
    border-radius: var(--radius);
    border: none;
    cursor: pointer;
    transition: background-color 0.2s, box-shadow 0.2s;
}

.btn-primary {
    /* Primary action buttons across the app share /home's green CTA
       fill (`--ds-primary`) so users see one consistent "do it"
       affordance everywhere. The brand-blue `--primary` stays as
       the link / accent colour elsewhere; only the filled button
       background flips to green. */
    background-color: var(--ds-primary);
    color: white;
    font-weight: 500;
    transition: all 0.15s ease;
}

.btn-primary:hover {
    background-color: var(--ds-primary-dark);
    box-shadow: 0 2px 8px color-mix(in srgb, var(--ds-primary) 30%, transparent);
}

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

.btn-secondary {
    background-color: transparent;
    color: var(--secondary);
    border: 1px solid var(--border);
}

.btn-secondary:hover {
    background-color: var(--bg);
}

.btn-sm {
    padding: 6px 12px;
    font-size: var(--text-sm);
}

.btn-google {
    background-color: white;
    color: var(--text);
    border: 1px solid var(--border);
    padding: 14px 28px;
    font-size: var(--text-md);
    font-weight: 500;
    width: 100%;
    max-width: 280px;
    transition: all 0.2s ease;
}

.btn-google:hover {
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
    border-color: var(--border-dark);
    background-color: var(--background);
}

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

.google-icon {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
}

/* Cards */
.card {
    background: var(--card-bg);
    border-radius: var(--radius);
    padding: 24px;
    margin-bottom: 20px;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
    border: 1px solid var(--border);
}

.card h3 {
    font-size: var(--text-md);
    font-weight: 600;
    margin-bottom: 16px;
    color: var(--text);
    letter-spacing: -0.01em;
}

.card p {
    color: var(--text-light);
    margin-bottom: 12px;
}

.card-error {
    border-left: 4px solid var(--error);
    background-color: var(--card-error-bg);
}

.card-highlight {
    border-left: 4px solid var(--primary);
    background-color: var(--card-highlight-bg);
}

.card-ai {
    border-left: 4px solid var(--success);
    background-color: var(--card-ai-bg);
}

/* Manual setup collapsible */
.manual-setup {
    margin-top: 16px;
}

.manual-setup summary {
    cursor: pointer;
    font-size: var(--text-base);
    color: var(--text-light);
    padding: 8px 0;
}

.manual-setup summary:hover {
    color: var(--text);
}

.manual-setup .card {
    margin-top: 12px;
    margin-bottom: 0;
}

.card-error h3 {
    color: var(--error);
}

.error-message {
    color: var(--error);
    font-weight: 500;
}

/* Info grid */
.info-grid {
    display: grid;
    gap: 12px;
}

.info-item {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.info-item .label {
    font-weight: 500;
    min-width: 140px;
}

.info-item .value {
    color: var(--text-light);
}

/* Badges */
.badge {
    display: inline-block;
    padding: 2px 8px;
    font-size: var(--text-sm);
    font-weight: 500;
    border-radius: 4px;
    background-color: var(--bg);
    color: var(--text-light);
    margin-right: 4px;
}

.badge-analyst {
    background-color: var(--badge-success-bg);
    color: var(--badge-success-ink);
}

.badge-privileged {
    background-color: var(--badge-warn-bg);
    color: var(--badge-warn-ink);
}

.badge-admin {
    background-color: var(--badge-info-bg);
    color: var(--badge-info-ink);
}

/* Code blocks */
code {
    font-family: var(--font-mono);
    font-size: var(--text-sm);
    background: var(--border-light);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: 1px var(--space-1);
    color: var(--text-primary);
}

/* Label qualifiers — optional, default, hint text next to form labels */
.label-qualifier,
.optional {
    font-size: var(--text-sm);
    font-weight: var(--font-normal);
    color: var(--text-secondary);
    margin-left: var(--space-1);
}

/* Radio option cards — visual selected state */
.sync-option-card:has(input:checked) {
    border-color: var(--primary) !important;
    background: var(--primary-light) !important;
}

.code-block {
    background-color: var(--code-dark-bg);
    color: var(--code-dark-ink);
    padding: 14px 16px;
    border-radius: var(--radius);
    overflow-x: auto;
    margin: 12px 0;
    border: 1px solid var(--code-dark-border);
}

.code-block code,
.code-block pre {
    background: transparent;
    border: none;
    border-radius: 0;
    padding: 0;
    font-size: var(--text-13);
    color: inherit;
    line-height: 1.6;
}

.code-block pre {
    margin: 0;
    white-space: pre-wrap;
    word-break: break-all;
}

.code-block.code-compact {
    padding: 10px 14px;
    margin: 6px 0;
}

/* Command rows for Quick Start */
.command-row {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 8px;
}

.command-row .command-label {
    font-weight: 500;
    font-size: var(--text-base);
    min-width: 70px;
    color: var(--text-light);
}

.command-row .code-block {
    flex: 1;
    margin: 0;
}

/* SSH config details/summary */
.ssh-config-details {
    margin-top: 16px;
    padding-top: 12px;
    border-top: 1px solid var(--border);
}

.ssh-config-details summary {
    cursor: pointer;
    font-size: var(--text-base);
    font-weight: 500;
    color: var(--primary);
    padding: 4px 0;
    user-select: none;
}

.ssh-config-details summary:hover {
    color: var(--primary-dark);
}

.ssh-config-details[open] summary {
    margin-bottom: 8px;
}

.ssh-config-details .help-text {
    margin-top: 4px;
    margin-bottom: 4px;
}

/* Forms */
.form-group {
    margin-bottom: 20px;
}

.form-group label {
    display: block;
    font-weight: 500;
    margin-bottom: 8px;
}

.form-group textarea {
    width: 100%;
    padding: 12px 14px;
    font-family: "SF Mono", Monaco, "Cascadia Code", "Roboto Mono", monospace;
    font-size: var(--text-13);
    line-height: 1.5;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    resize: vertical;
    min-height: 100px;
    background-color: var(--card-bg);
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.form-group textarea:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: var(--focus-ring);
}

.form-group textarea::placeholder {
    color: var(--placeholder-color);
}

.help-text {
    font-size: var(--text-13);
    color: var(--text-light);
    margin-top: 8px;
    line-height: 1.5;
}

.help-text strong {
    color: var(--text);
    font-weight: 500;
}

/* Flash messages */
.flash-messages {
    margin-bottom: 24px;
}

.flash {
    padding: 14px 18px;
    border-radius: var(--radius);
    margin-bottom: 12px;
    font-size: var(--text-base);
    font-weight: 500;
}

.flash-success {
    background-color: var(--flash-success-bg);
    color: var(--flash-success-ink);
    border: 1px solid var(--flash-success-border);
}

.flash-error {
    background-color: var(--flash-error-bg);
    color: var(--flash-error-ink);
    border: 1px solid var(--flash-error-border);
}

.flash-info {
    background-color: var(--flash-info-bg);
    color: var(--flash-info-ink);
    border: 1px solid var(--flash-info-border);
}

.flash-warning {
    background-color: var(--flash-warn-bg);
    color: var(--flash-warn-ink);
    border: 1px solid var(--flash-warn-border);
}

/* Login page */
.login-container {
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 60vh;
    padding: 20px;
}

.login-card {
    background: var(--card-bg);
    border-radius: 12px;
    padding: 48px 40px;
    text-align: center;
    box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.04);
    max-width: 640px;
    width: 100%;
    border: 1px solid var(--border);
}

/* Operator brand lockup above the Sign In heading. The card has a light
   `--card-bg`, so a colored wordmark SVG (the typical operator logo) reads
   well here. Constrain height and let width scale so any aspect ratio fits;
   `svg { display: block }` drops the inline-baseline gap under the lockup. */
.login-card-logo {
    margin-bottom: 24px;
    display: flex;
    justify-content: center;
}

.login-card-logo svg,
.login-card-logo img {
    display: block;
    height: 36px;
    width: auto;
    max-width: 100%;
}

.login-card h2 {
    margin-bottom: 12px;
    font-size: var(--text-28);
    font-weight: 600;
    color: var(--text);
}

.login-description {
    color: var(--text-light);
    margin-bottom: 32px;
    font-size: var(--text-md);
    line-height: 1.5;
}

.login-steps {
    margin-top: 24px;
    padding-top: 20px;
    border-top: 1px solid var(--border);
    text-align: left;
}

.login-steps .step {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: var(--text-base);
    color: var(--text-light);
    padding: 6px 0;
}

.login-steps .step-num,
.steps-horizontal .step-num {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    background-color: var(--primary);
    color: white;
    border-radius: 50%;
    font-size: var(--text-sm);
    font-weight: 600;
    flex-shrink: 0;
}

/* Inline steps for compact layout */
.steps-inline {
    display: flex;
    gap: 20px;
    flex-wrap: wrap;
    margin-bottom: 16px;
    padding-bottom: 12px;
    border-bottom: 1px solid var(--border);
}

.steps-inline .step-item {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: var(--text-13);
    color: var(--text-light);
}

/* Form row for inline layout */
.form-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
}

.form-group-inline {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 0;
}

.form-group-inline label {
    margin-bottom: 0;
}

/* AI hint inline */
.ai-hint {
    margin-top: 16px;
    padding-top: 12px;
    border-top: 1px solid var(--border);
    font-size: var(--text-13);
    color: var(--text-light);
}

.ai-hint code {
    display: inline;
    font-size: var(--text-sm);
    color: var(--primary);
}

/* Manual setup compact */
.manual-content {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    padding: 12px 0;
}

.manual-content code {
    font-size: var(--text-sm);
}

.manual-content .help-text {
    margin: 0;
    font-size: var(--text-sm);
}

.login-note {
    font-size: var(--text-13);
    color: var(--text-light);
    margin-top: 12px;
}

/* Error page — the live rules are the "Error pages (4xx / 5xx)" block far
   below. A superseded copy used to sit here, and because `.error-card h2`
   outranks `.error-title` on specificity its `font-size: 3rem` was what
   actually rendered: every error page shouted its headline at 48px while the
   design intended 24px. Do not reintroduce element-scoped rules here. */

/* Footer */
footer {
    margin-top: 48px;
    padding-top: 20px;
    padding-bottom: 8px;
    border-top: 1px solid var(--border);
    text-align: center;
    color: var(--text-light);
    font-size: var(--text-13);
}

/* Shared footer partial (`_footer.html`): product name, running build, then
   the operator credit when configured. Only the INSIDE is styled here — the
   outer `<footer>` keeps the rule above plus every chrome-specific override
   that keys on the element or on `footer_class` (`.idx > footer`,
   `body.chat-page-body .container > footer`, the rail detail-page rule, and
   `.cloud-chat-empty-foot`), so no existing footer treatment moves.
   Inline spans, not a flex row — see the comment in the partial: inline flow
   inherits each chrome's own centering and needs no width, which a flex row
   would have to be given separately in each of the six layout contexts. */
.site-footer__line {
    margin: 0;
}
.site-footer__brand {
    font-weight: 600;
    color: var(--ds-text-secondary);
}
.site-footer__build,
.site-footer__deploy {
    color: var(--ds-text-muted);
}
/* Separators ride the segments they precede, so each one disappears with its
   own segment — no dangling bullet when /api/version is unreachable or when
   the instance configures no credit. */
.site-footer__build::before,
.site-footer__deploy::before {
    content: '·';
    margin: 0 6px;
}
.site-footer__build[hidden] {
    display: none;  /* explicit: `hidden` alone loses to any inline reset */
}

/* Username box and copy buttons */
.username-box {
    background: var(--username-box-bg);
    border: 2px solid var(--username-box-line);
    border-radius: 8px;
    padding: 16px;
    margin: 16px 0;
}

.username-display {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-top: 8px;
}

.username-display code {
    font-size: 20px;
    font-weight: bold;
    color: var(--username-ink);
    background: white;
    padding: 8px 16px;
    border-radius: 6px;
    flex-grow: 1;
}

.btn-copy {
    background: var(--btn-copy-bg);
    color: white;
    border: none;
    padding: 8px 16px;
    border-radius: 6px;
    cursor: pointer;
    font-size: var(--text-base);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    transition: all 0.2s ease;
}

.btn-copy:hover {
    background: var(--btn-copy-hover);
}

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

.btn-copy.copied {
    background: var(--btn-copy-success);
}

.btn-copy-inline {
    background: var(--btn-copy-bg);
    color: white;
    border: none;
    padding: 4px 10px;
    border-radius: 4px;
    cursor: pointer;
    font-size: var(--text-sm);
    margin-left: 8px;
    transition: background 0.15s ease;
}

.btn-copy-inline:hover {
    background: var(--btn-copy-hover);
}

.btn-copy-block {
    background: var(--btn-copy-bg);
    color: white;
    border: none;
    padding: 10px 24px;
    border-radius: 6px;
    cursor: pointer;
    font-size: var(--text-base);
    font-weight: 600;
    width: 100%;
    transition: all 0.2s ease;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

.btn-copy-block:hover {
    background: var(--btn-copy-hover);
    box-shadow: 0 2px 8px rgba(14, 165, 233, 0.3);
}

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

.btn-copy-block.copied {
    background: var(--btn-copy-success);
}

.username-preview {
    font-size: 18px;
    color: var(--username-preview-color);
}

.info-box {
    background: var(--info-box-bg);
    border: 1px solid var(--info-box-border);
    border-radius: 6px;
    padding: 12px;
    margin-bottom: 16px;
}

.card-success {
    border-left: 4px solid var(--success);
    background-color: var(--card-success-bg);
}

.help-details {
    margin-top: 12px;
    font-size: var(--text-base);
}

.help-details summary {
    cursor: pointer;
    color: var(--primary);
    padding: 4px 0;
    font-weight: 500;
}

.help-details summary:hover {
    color: var(--primary-dark);
}

.help-details ul {
    margin-top: 8px;
    margin-left: 20px;
    color: var(--text-light);
}

.help-details li {
    margin-bottom: 4px;
}

/* Claude Code Setup Card on Dashboard */
.cc-setup-card {
    background: linear-gradient(135deg, var(--cc-gradient-from) 0%, var(--cc-gradient-to) 100%);
    color: white;
    border: none;
    margin-bottom: 24px;
}

.cc-setup-card h3 {
    color: white;
    margin-bottom: 12px;
}

.cc-setup-card .cc-instruction {
    color: rgba(255, 255, 255, 0.95);
    margin-bottom: 16px;
    font-size: var(--text-15);
    line-height: 1.6;
}

.cc-setup-card .cc-helper-text {
    color: rgba(255, 255, 255, 0.8);
    font-size: var(--text-13);
    margin-top: 12px;
    margin-bottom: 0;
}

.cc-setup-card .btn-copy-block {
    background: white;
    color: var(--cc-gradient-from);
    font-weight: 600;
    padding: 12px 24px;
    border-radius: 6px;
    border: none;
    cursor: pointer;
    font-size: var(--text-base);
    width: 100%;
    transition: all 0.2s ease;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

.cc-setup-card .btn-copy-block:hover {
    background: var(--border-light);
    transform: translateY(-1px);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

.cc-setup-card .btn-copy-block:active {
    transform: scale(0.98);
}

.cc-setup-card .btn-copy-block.copied {
    background: var(--btn-copy-success);
    color: white;
}

/* CLI hint in setup card */
.cc-setup-card .cli-hint {
    margin-top: 16px;
    padding-top: 12px;
    border-top: 1px solid rgba(255, 255, 255, 0.2);
}

.cc-setup-card .cli-hint summary {
    cursor: pointer;
    color: rgba(255, 255, 255, 0.85);
    font-size: var(--text-13);
    font-weight: 500;
    padding: 4px 0;
}

.cc-setup-card .cli-hint summary:hover {
    color: white;
}

.cc-setup-card .cli-hint .cli-content {
    margin-top: 10px;
    background: rgba(255, 255, 255, 0.1);
    padding: 10px 12px;
    border-radius: 6px;
}

.cc-setup-card .cli-hint .cli-content code {
    display: block;
    background: rgba(0, 0, 0, 0.2);
    color: white;
    padding: 8px 10px;
    border-radius: 4px;
    font-size: var(--text-sm);
    margin-bottom: 6px;
}

.cc-setup-card .cli-hint .cli-desc {
    color: rgba(255, 255, 255, 0.7);
    font-size: var(--text-sm);
}

/* Support info banner */
.support-info {
    text-align: center;
    color: var(--text-light);
    font-size: var(--text-13);
    padding: 12px 16px;
    background: var(--support-info-bg);
    border-radius: 6px;
    margin-bottom: 20px;
    border: 1px solid var(--border);
}

.support-info strong {
    color: var(--text);
}

/* Auth tabs */
.auth-tabs {
    display: flex;
    gap: 0;
    margin-bottom: 24px;
    border-bottom: 2px solid var(--border);
}

.auth-tab {
    flex: 1;
    padding: 12px 16px;
    background: transparent;
    border: none;
    border-bottom: 2px solid transparent;
    margin-bottom: -2px;
    font-size: var(--text-15);
    font-weight: 500;
    color: var(--text-light);
    cursor: pointer;
    transition: all 0.2s ease;
}

.auth-tab:hover {
    color: var(--text);
}

.auth-tab.active {
    color: var(--primary);
    border-bottom-color: var(--primary);
}

.auth-tab-content {
    display: none;
}

.auth-tab-content.active {
    display: block;
}

.tab-description {
    color: var(--text-light);
    font-size: var(--text-base);
    margin-bottom: 20px;
    text-align: center;
}

.signup-note {
    color: var(--text-light);
    font-size: var(--text-13);
    text-align: center;
    margin-top: 16px;
}

/* Login form styles */
.login-form {
    text-align: left;
    margin: 24px 0;
}

.login-form .form-group {
    margin-bottom: 16px;
}

.login-form .form-group label {
    display: block;
    font-weight: 500;
    font-size: var(--text-base);
    margin-bottom: 6px;
    color: var(--text);
}

.login-form .form-group input {
    width: 100%;
    padding: 12px 14px;
    font-size: var(--text-15);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background-color: var(--card-bg);
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.login-form .form-group input:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: var(--focus-ring);
}

.login-form .form-group input::placeholder {
    color: var(--placeholder-color);
}

.login-form .form-hint {
    display: block;
    font-size: var(--text-sm);
    color: var(--text-light);
    margin-top: 4px;
}

.btn-block {
    width: 100%;
}

.btn-link {
    background: transparent;
    color: var(--primary);
    border: none;
    padding: 8px 4px;
    font-size: var(--text-base);
    cursor: pointer;
    text-decoration: none;
}

.btn-link:hover {
    color: var(--primary-dark);
    text-decoration: underline;
}

/* Login page links */
.login-links {
    display: flex;
    justify-content: center;
    gap: 16px;
    margin: 16px 0;
    flex-wrap: wrap;
}

.login-links form {
    margin: 0;
}

/* Request access form */
.request-access-form,
.reset-form {
    display: inline;
}

/* Divider */
.divider {
    display: flex;
    align-items: center;
    margin: 24px 0;
    color: var(--text-light);
    font-size: var(--text-base);
}

.divider::before,
.divider::after {
    content: "";
    flex: 1;
    height: 1px;
    background-color: var(--border);
}

.divider span {
    padding: 0 16px;
}

/* Account email display */
.account-email {
    background-color: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 12px 16px;
    margin-bottom: 20px;
    text-align: center;
    font-size: var(--text-15);
}

/* Responsive */
@media (max-width: 600px) {
    .container {
        padding: 15px;
    }

    header {
        flex-direction: column;
        gap: 15px;
        text-align: center;
    }

    .info-item {
        flex-direction: column;
        gap: 4px;
    }

    .info-item .label {
        min-width: auto;
    }

    .username-display {
        flex-direction: column;
        align-items: stretch;
    }

    .username-display code {
        text-align: center;
    }

    .login-links {
        flex-direction: column;
        gap: 8px;
    }
}


/* Container */
.container-v2 {
    max-width: 1400px;
    margin: 0 auto;
    padding: var(--space-6) var(--space-5);
}

/* Header */
.header-v2 {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: var(--space-4) 0;
    margin-bottom: var(--space-6);
    border-bottom: 1px solid var(--border);
}

.logo-v2 h1 {
    font-size: var(--text-lg);
    font-weight: var(--font-semibold);
    color: var(--text-primary);
    margin: 0;
}

.logo-v2 .subtitle {
    font-size: var(--text-sm);
    color: var(--text-secondary);
    margin-top: 2px;
}

.nav-v2 {
    display: flex;
    align-items: center;
    gap: var(--space-3);
}

.user-info-v2 {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--text-sm);
    color: var(--text-secondary);
}

.avatar-v2 {
    width: 32px;
    height: 32px;
    border-radius: var(--radius-full);
}

/* Buttons */
.btn-v2 {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    padding: 0 var(--space-3);
    height: 36px;
    font-size: var(--text-base);
    font-weight: var(--font-medium);
    text-decoration: none;
    border-radius: var(--radius-md);
    border: none;
    cursor: pointer;
    transition: all 0.15s ease;
}

.btn-primary-v2 {
    background-color: var(--primary);
    color: white;
}

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

.btn-secondary-v2 {
    background-color: var(--background);
    color: var(--text-primary);
    border: 1px solid var(--border);
}

.btn-secondary-v2:hover {
    background-color: var(--border);
}

.btn-ghost-v2 {
    background-color: transparent;
    color: var(--text-secondary);
}

.btn-ghost-v2:hover {
    background-color: rgba(243, 244, 246, 0.5);
}

/* Cards */
.card-v2 {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    padding: var(--space-5);
    margin-bottom: var(--space-5);
    box-shadow: var(--shadow-sm);
}

.card-v2 h3 {
    font-size: var(--text-md);
    font-weight: var(--font-semibold);
    color: var(--text-primary);
    margin-bottom: var(--space-4);
}

.card-v2 p {
    color: var(--text-secondary);
    margin-bottom: var(--space-3);
}

/* Welcome section */
.welcome-v2 {
    margin-bottom: var(--space-6);
}

.welcome-v2 h2 {
    font-size: var(--text-xl);
    font-weight: var(--font-extrabold);
    color: var(--text-primary);
    margin-bottom: var(--space-2);
}

/* AI Setup Card - Primary CTA */
.ai-setup-card {
    background: linear-gradient(135deg, var(--primary) 0%, #0056A3 100%);
    border: none;
    color: white;
    padding: var(--space-6);
    margin-bottom: var(--space-5);
}

.ai-setup-card h3 {
    color: white;
    font-size: var(--text-lg);
    margin-bottom: var(--space-3);
}

.ai-setup-card .setup-description {
    color: rgba(255, 255, 255, 0.9);
    font-size: var(--text-base);
    line-height: 1.6;
    margin-bottom: var(--space-4);
}

.ai-setup-card .btn-copy-v2 {
    background: white;
    color: var(--primary);
    font-weight: var(--font-semibold);
    padding: var(--space-3) var(--space-6);
    height: 44px;
    width: 100%;
    border-radius: var(--radius-md);
    border: none;
    cursor: pointer;
    font-size: var(--text-base);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    transition: all 0.2s ease;
}

.ai-setup-card .btn-copy-v2:hover {
    background: var(--border-light);
    transform: translateY(-1px);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

.ai-setup-card .btn-copy-v2.copied {
    background: var(--success);
    color: white;
}

.ai-setup-card .helper-text {
    color: rgba(255, 255, 255, 0.9);
    font-size: var(--text-sm);
    margin-top: var(--space-3);
    margin-bottom: 0;
    line-height: 1.5;
}

/* CLI Hint */
.cli-hint-v2 {
    margin-top: var(--space-4);
    padding-top: var(--space-3);
    border-top: 1px solid rgba(255, 255, 255, 0.2);
}

.cli-hint-v2 summary {
    cursor: pointer;
    color: rgba(255, 255, 255, 0.8);
    font-size: var(--text-sm);
    font-weight: var(--font-medium);
    padding: var(--space-1) 0;
    list-style: none;
}

.cli-hint-v2 summary::-webkit-details-marker {
    display: none;
}

.cli-hint-v2 summary::before {
    content: '+ ';
}

.cli-hint-v2[open] summary::before {
    content: '- ';
}

.cli-hint-v2 summary:hover {
    color: white;
}

.cli-hint-v2 .cli-content {
    margin-top: var(--space-3);
    background: rgba(0, 0, 0, 0.15);
    padding: var(--space-3);
    border-radius: var(--radius-md);
}

.cli-hint-v2 code {
    display: block;
    background: rgba(0, 0, 0, 0.2);
    color: white;
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-sm);
    font-family: var(--font-mono);
    font-size: var(--text-sm);
    margin-bottom: var(--space-2);
}

.cli-hint-v2 .cli-desc {
    color: rgba(255, 255, 255, 0.7);
    font-size: var(--text-xs);
}

/* Support banner */
.support-banner {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    padding: var(--space-3) var(--space-4);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    margin-bottom: var(--space-5);
    font-size: var(--text-sm);
    color: var(--text-secondary);
}

.support-banner strong {
    color: var(--text-primary);
}

.support-banner .slack-badge {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    background: var(--primary-light);
    color: var(--primary);
    padding: 2px var(--space-2);
    border-radius: var(--radius-full);
    font-weight: var(--font-medium);
}

/* Status cards */
.status-card {
    border-left: 3px solid var(--success);
}

.status-card.error {
    border-left-color: var(--error);
}

.status-card.warning {
    border-left-color: var(--warning);
}

/* Username box */
.username-box-v2 {
    background: var(--primary-light);
    border: 1px solid var(--primary);
    border-radius: var(--radius-lg);
    padding: var(--space-4);
    margin: var(--space-4) 0;
}

.username-box-v2 label {
    font-size: var(--text-sm);
    font-weight: var(--font-medium);
    color: var(--text-secondary);
    display: block;
    margin-bottom: var(--space-2);
}

.username-display-v2 {
    display: flex;
    align-items: center;
    gap: var(--space-3);
}

.username-display-v2 code {
    font-family: var(--font-mono);
    font-size: var(--text-xl);
    font-weight: var(--font-bold);
    color: var(--primary);
    background: white;
    padding: var(--space-2) var(--space-4);
    border-radius: var(--radius-md);
    flex-grow: 1;
}

.username-display-v2 .btn-copy-sm {
    background: var(--primary);
    color: white;
    border: none;
    padding: var(--space-2) var(--space-4);
    border-radius: var(--radius-md);
    cursor: pointer;
    font-size: var(--text-sm);
    font-weight: var(--font-semibold);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    transition: all 0.15s ease;
}

.username-display-v2 .btn-copy-sm:hover {
    background: var(--primary-dark);
}

.username-display-v2 .btn-copy-sm.copied {
    background: var(--success);
}

/* Info grid */
.info-grid-v2 {
    display: grid;
    gap: var(--space-3);
}

.info-item-v2 {
    display: flex;
    align-items: center;
    gap: var(--space-3);
}

.info-item-v2 .label {
    font-weight: var(--font-medium);
    color: var(--text-secondary);
    min-width: 120px;
    font-size: var(--text-sm);
}

.info-item-v2 .value {
    color: var(--text-primary);
}

.info-item-v2 code {
    font-family: var(--font-mono);
    font-size: var(--text-sm);
    background: var(--background);
    padding: 2px var(--space-2);
    border-radius: var(--radius-sm);
}

/* Badges */
.badge-v2 {
    display: inline-flex;
    align-items: center;
    padding: 2px var(--space-2);
    font-size: var(--text-xs);
    font-weight: var(--font-medium);
    border-radius: var(--radius-full);
    background: var(--background);
    color: var(--text-secondary);
    margin-right: var(--space-1);
}

.badge-analyst-v2 {
    background: rgba(16, 183, 127, 0.1);
    color: var(--success);
}

.badge-privileged-v2 {
    background: rgba(245, 159, 10, 0.1);
    color: var(--warning);
}

.badge-admin-v2 {
    background: color-mix(in srgb, var(--ds-primary) 10%, transparent);
    color: var(--primary);
}

/* Form */
.form-v2 {
    margin-top: var(--space-4);
}

.form-group-v2 {
    margin-bottom: var(--space-4);
}

.form-group-v2 label {
    display: block;
    font-weight: var(--font-medium);
    color: var(--text-primary);
    margin-bottom: var(--space-2);
    font-size: var(--text-sm);
}

.form-group-v2 textarea {
    width: 100%;
    padding: var(--space-3);
    font-family: var(--font-mono);
    font-size: var(--text-sm);
    line-height: 1.5;
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    resize: vertical;
    min-height: 80px;
    background: var(--surface);
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.form-group-v2 textarea:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: 0 0 0 2px var(--primary-light);
}

.form-group-v2 textarea::placeholder {
    color: var(--text-secondary);
}

.form-row-v2 {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
}

.form-info-v2 {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--text-sm);
    color: var(--text-secondary);
}

.form-info-v2 code {
    font-family: var(--font-mono);
    font-weight: var(--font-semibold);
    color: var(--text-primary);
}

/* Help text */
.help-text-v2 {
    font-size: var(--text-sm);
    color: var(--text-secondary);
    margin-top: var(--space-2);
}

.help-text-v2 strong {
    color: var(--text-primary);
}

/* Info box */
.info-box-v2 {
    background: var(--background);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    padding: var(--space-3);
    margin-bottom: var(--space-4);
    display: flex;
    align-items: center;
    gap: var(--space-2);
}

.info-box-v2 .username-preview {
    font-family: var(--font-mono);
    font-size: var(--text-lg);
    font-weight: var(--font-semibold);
    color: var(--primary);
}

/* Alert */
.alert-v2 {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    padding: var(--space-4);
    border-radius: var(--radius-lg);
    margin-bottom: var(--space-4);
}

.alert-success-v2 {
    background: rgba(16, 183, 127, 0.1);
    border-left: 3px solid var(--success);
}

.alert-error-v2 {
    background: rgba(234, 88, 12, 0.1);
    border-left: 3px solid var(--error);
}

.alert-warning-v2 {
    background: rgba(245, 159, 10, 0.1);
    border-left: 3px solid var(--warning);
}

.alert-v2 .alert-icon {
    font-size: var(--text-lg);
}

.alert-v2 h4 {
    font-size: var(--text-base);
    font-weight: var(--font-semibold);
    color: var(--text-primary);
    margin-bottom: var(--space-1);
}

.alert-v2 p {
    font-size: var(--text-sm);
    color: var(--text-secondary);
    margin: 0;
}

/* Flash messages */
.flash-messages-v2 {
    margin-bottom: var(--space-5);
}

.flash-v2 {
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-lg);
    margin-bottom: var(--space-3);
    font-size: var(--text-sm);
    font-weight: var(--font-medium);
}

.flash-success-v2 {
    background: rgba(16, 183, 127, 0.1);
    color: var(--flash-success-v2-ink);
    border: 1px solid rgba(16, 183, 127, 0.3);
}

.flash-error-v2 {
    background: rgba(234, 88, 12, 0.1);
    color: var(--flash-error-v2-ink);
    border: 1px solid rgba(234, 88, 12, 0.3);
}

.flash-info-v2 {
    background: var(--primary-light);
    color: var(--primary);
    border: 1px solid color-mix(in srgb, var(--ds-primary) 30%, transparent);
}

/* Footer */
.footer-v2 {
    margin-top: var(--space-8);
    padding-top: var(--space-5);
    border-top: 1px solid var(--border);
    text-align: center;
    color: var(--text-secondary);
    font-size: var(--text-sm);
}

/* Next step card */
.next-step-card {
    background: var(--ds-accent-info-bg);
    border: 1px solid var(--primary);
    border-left: 3px solid var(--primary);
}

.next-step-card h3 {
    color: var(--primary);
}

/* Data Link Card */
.data-link-card {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--space-4) var(--space-5);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    text-decoration: none;
    transition: all 0.15s ease;
    margin-bottom: var(--space-5);
}

.data-link-card:hover {
    border-color: var(--primary);
    box-shadow: 0 0 0 1px var(--primary);
}

.data-link-content {
    display: flex;
    align-items: center;
    gap: var(--space-4);
}

.data-link-icon {
    width: 44px;
    height: 44px;
    background: var(--primary-light);
    border-radius: var(--radius-md);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

.data-link-text h3 {
    font-size: var(--text-md);
    font-weight: var(--font-semibold);
    color: var(--text-primary);
    margin-bottom: 2px;
}

.data-link-text p {
    font-size: var(--text-sm);
    color: var(--text-secondary);
    margin: 0;
}

.data-link-stats {
    display: flex;
    gap: var(--space-4);
    padding: var(--space-3) var(--space-4);
    background: var(--background);
    border-radius: var(--radius-md);
}

.data-stat {
    text-align: center;
    padding: 0 var(--space-2);
}

.data-stat .value {
    font-size: var(--text-md);
    font-weight: var(--font-semibold);
    color: var(--text-primary);
}

.data-stat .label {
    font-size: 10px;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.3px;
}

.data-stat-divider {
    width: 1px;
    background: var(--border);
}

.data-link-arrow {
    width: 32px;
    height: 32px;
    background: var(--primary-light);
    border-radius: var(--radius-full);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--primary);
    transition: all 0.15s ease;
    flex-shrink: 0;
}

.data-link-card:hover .data-link-arrow {
    background: var(--primary);
    color: white;
    transform: translateX(2px);
}

.data-categories {
    display: flex;
    gap: var(--space-2);
    margin-top: var(--space-2);
}

.category-tag {
    font-size: var(--text-xs);
    padding: 2px 8px;
    border-radius: var(--radius-full);
    font-weight: var(--font-medium);
}

.category-tag.finance { background: var(--ds-accent-success-bg); color: var(--ds-accent-success-ink); }
.category-tag.hr { background: rgba(139, 92, 246, 0.1); color: #7c3aed; }
.category-tag.sales { background: color-mix(in srgb, var(--ds-primary) 10%, transparent); color: var(--primary); }
.category-tag.telemetry { background: rgba(245, 159, 10, 0.1); color: #b45309; }
.category-tag.support { background: rgba(234, 88, 12, 0.1); color: #EA580C; }
.category-tag.revenue { background: color-mix(in srgb, var(--ds-primary) 10%, transparent); color: var(--primary); }
.category-tag.customers { background: rgba(139, 92, 246, 0.1); color: #7c3aed; }
.category-tag.marketing { background: rgba(245, 159, 10, 0.1); color: #b45309; }

.data-highlights {
    display: flex;
    gap: var(--space-4);
    margin-top: var(--space-3);
    font-size: var(--text-sm);
    color: var(--text-secondary);
}

.data-highlight {
    display: flex;
    align-items: center;
    gap: 4px;
}

.data-highlight strong {
    color: var(--text-primary);
    font-weight: var(--font-semibold);
}

/* Slack badge as link */
a.slack-badge {
    text-decoration: none;
    transition: all 0.15s ease;
}

a.slack-badge:hover {
    background: var(--primary);
    color: white;
}

/* Dashboard 2-column grid */
.dashboard-grid {
    display: grid;
    grid-template-columns: 3fr 2fr;
    gap: var(--space-5);
    align-items: start;
}

.col-left {
    display: flex;
    flex-direction: column;
    gap: var(--space-5);
}

.col-right {
    display: flex;
    flex-direction: column;
    gap: var(--space-5);
}

.full-width {
    grid-column: 1 / -1;
}

/* Remove bottom margin for cards inside grid (grid gap handles spacing) */
.dashboard-grid .card-v2 {
    margin-bottom: 0;
}

.dashboard-grid .data-link-card {
    margin-bottom: 0;
}

.dashboard-grid .support-banner {
    margin-bottom: 0;
}

.support-banner .heart {
    color: #e74c3c;
}

/* Responsive */
@media (max-width: 1024px) {
    .dashboard-grid {
        grid-template-columns: 1fr;
    }
}

@media (max-width: 640px) {
    .container-v2 {
        padding: var(--space-4);
    }

    .header-v2 {
        flex-direction: column;
        gap: var(--space-3);
        text-align: center;
    }

    .username-display-v2 {
        flex-direction: column;
    }

    .username-display-v2 code {
        width: 100%;
        text-align: center;
    }

    .form-row-v2 {
        flex-direction: column;
        align-items: stretch;
    }

    .info-item-v2 {
        flex-direction: column;
        align-items: flex-start;
        gap: var(--space-1);
    }

    .info-item-v2 .label {
        min-width: auto;
    }

    .dashboard-grid {
        grid-template-columns: 1fr;
    }
}

/* Setup steps styling */
.setup-steps {
    margin: 24px 0;
    display: flex;
    flex-direction: column;
    gap: 20px;
}

.step-item {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.step-header {
    display: flex;
    align-items: center;
    gap: 12px;
}

.step-number {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    background: white;
    color: var(--primary);
    border-radius: 50%;
    font-weight: 700;
    font-size: 16px;
    flex-shrink: 0;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.step-header strong {
    color: white;
    font-size: 16px;
    font-weight: 600;
}

.step-body {
    margin-left: 44px;
}

.step-body p {
    margin: 0 0 8px 0;
    color: rgba(255, 255, 255, 0.9);
    font-size: 14px;
    line-height: 1.5;
}

/* Code block styling */
.code-block-wrapper {
    position: relative;
    display: flex;
    align-items: center;
    background: rgba(0, 0, 0, 0.2);
    border: 1px solid rgba(255, 255, 255, 0.2);
    border-radius: 6px;
    padding: 12px 48px 12px 12px;
    margin-top: 8px;
}

.code-block {
    flex: 1;
    font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
    font-size: var(--text-13);
    /* Pure white (not --code-dark-ink's #e4e4e7): this .code-block sits inside
       .code-block-wrapper's dark translucent surface and needs max contrast. */
    color: var(--code-wrapper-ink);
    background: transparent;
    border: none;
    white-space: pre;
    overflow-x: auto;
}

.btn-copy-code {
    position: absolute;
    right: 8px;
    top: 50%;
    transform: translateY(-50%);
    background: rgba(255, 255, 255, 0.15);
    border: 1px solid rgba(255, 255, 255, 0.3);
    border-radius: 4px;
    padding: 6px 8px;
    cursor: pointer;
    transition: all 0.2s ease;
    display: flex;
    align-items: center;
    justify-content: center;
    color: white;
}

.btn-copy-code:hover {
    background: rgba(255, 255, 255, 0.25);
    border-color: rgba(255, 255, 255, 0.4);
}

.btn-copy-code.copied {
    background: rgba(76, 175, 80, 0.3);
    border-color: rgba(76, 175, 80, 0.5);
}

.btn-copy-code svg {
    width: 16px;
    height: 16px;
}

/* Compact setup banner (existing users) */
.setup-compact {
    background: linear-gradient(135deg, var(--primary) 0%, #0056A3 100%);
    border: none;
    color: white;
    padding: var(--space-4) var(--space-5);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
}

.setup-compact h3 {
    color: white;
    margin-bottom: 0;
    font-size: var(--text-base);
}

.setup-compact p {
    color: rgba(255, 255, 255, 0.85);
    margin: 4px 0 0 0;
    font-size: var(--text-sm);
}

.setup-compact .btn-copy-v2 {
    background: white;
    color: var(--primary);
    font-weight: var(--font-semibold);
    padding: var(--space-2) var(--space-5);
    height: 40px;
    border-radius: var(--radius-md);
    border: none;
    cursor: pointer;
    font-size: var(--text-sm);
    white-space: nowrap;
    transition: all 0.15s ease;
    flex-shrink: 0;
}

.setup-compact .btn-copy-v2:hover {
    background: var(--border-light);
    transform: translateY(-1px);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

.setup-compact .btn-copy-v2.copied {
    background: var(--success);
    color: white;
}

/* Unified Notifications card */
.notifications-card {
    padding: 0;
}

.notifications-card h3 {
    padding: var(--space-5) var(--space-5) 0;
    margin-bottom: var(--space-3);
}

.notif-channels {
    display: flex;
    flex-direction: column;
}

.notif-channel {
    padding: var(--space-4) var(--space-5);
    border-top: 1px solid var(--border);
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
}

.notif-channel-icon {
    width: 36px;
    height: 36px;
    border-radius: var(--radius-md);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

.notif-channel-icon.telegram {
    background: var(--ds-accent-info-bg);
}

/* No --ds-* token represents a neutral violet without borrowing
   --ds-kind-agent (entity-kind semantic) or --ds-readonly (sharing-state
   semantic) — see the raw-hex allowlist note in
   tests/test_design_system_contract.py (#1625). Left as raw hex. */
.notif-channel-icon.desktop {
    background: #f3e8ff;
}

.notif-channel-body {
    flex: 1;
    min-width: 0;
}

.notif-channel-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
    margin-bottom: var(--space-1);
}

.notif-channel-name {
    font-weight: var(--font-semibold);
    font-size: var(--text-base);
    color: var(--text-primary);
}

.notif-status-badge {
    font-size: var(--text-xs);
    font-weight: var(--font-medium);
    padding: 2px 8px;
    border-radius: var(--radius-full);
}

.notif-status-badge.linked {
    background: rgba(16, 183, 127, 0.1);
    color: var(--success);
}

.notif-status-badge.not-linked {
    background: var(--background);
    color: var(--text-secondary);
}

.notif-channel-desc {
    font-size: var(--text-sm);
    color: var(--text-secondary);
    margin-bottom: var(--space-3);
}

.notif-channel-action {
    display: flex;
    align-items: center;
    gap: var(--space-2);
}

.notif-channel-action input[type="text"] {
    width: 120px;
    padding: 6px 10px;
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    font-family: var(--font-mono);
    font-size: var(--text-sm);
    text-align: center;
}

.btn-sm-v2 {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-1);
    padding: 0 var(--space-3);
    height: 32px;
    font-size: var(--text-sm);
    font-weight: var(--font-medium);
    text-decoration: none;
    border-radius: var(--radius-md);
    border: none;
    cursor: pointer;
    transition: all 0.15s ease;
}

.btn-sm-primary {
    background-color: var(--primary);
    color: white;
}

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

.btn-sm-secondary {
    background-color: var(--background);
    color: var(--text-primary);
    border: 1px solid var(--border);
}

.btn-sm-secondary:hover {
    background-color: var(--border);
}

.download-link {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: var(--text-sm);
    font-weight: var(--font-medium);
    color: var(--primary);
    text-decoration: none;
}

.download-link:hover {
    text-decoration: underline;
}

.notif-install-steps {
    margin: 4px 0 0;
    padding-left: 20px;
    font-size: var(--text-sm);
    color: var(--text-secondary);
    line-height: 1.7;
}

.notif-install-steps li {
    padding-left: 4px;
}

.notif-install-steps .download-link {
    font-weight: var(--font-normal);
}

/* Beta warning styles */
.beta-warning {
    background: rgba(245, 159, 10, 0.1);
    border: 1px solid rgba(245, 159, 10, 0.3);
    border-radius: var(--radius-md);
    padding: var(--space-2) var(--space-3);
    margin-bottom: var(--space-3);
    font-size: var(--text-sm);
    color: #b45309;
}

.beta-warning strong {
    color: #92400e;
}

.beta-tag {
    display: inline-flex;
    align-items: center;
    background: rgba(245, 159, 10, 0.15);
    color: #b45309;
    font-size: var(--text-xs);
    font-weight: var(--font-medium);
    padding: 1px 6px;
    border-radius: var(--radius-full);
    margin-left: var(--space-1);
}

@media (max-width: 640px) {
    .setup-compact {
        flex-direction: column;
        align-items: stretch;
        text-align: center;
    }

    .setup-compact .btn-copy-v2 {
        width: 100%;
    }
}

/* Data Settings Card */
.settings-card {
    border-left: 3px solid var(--primary);
}

.settings-card h3 {
    margin-bottom: var(--space-3);
}

.dataset-toggles {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

.dataset-toggle {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    padding: var(--space-3);
    background: var(--background);
    border-radius: var(--radius-md);
    gap: var(--space-3);
}

.dataset-toggle.disabled {
    opacity: 0.5;
    pointer-events: none;
}

.dataset-info {
    flex: 1;
    min-width: 0;
}

.dataset-label {
    font-weight: var(--font-medium);
    font-size: var(--text-base);
    color: var(--text-primary);
    display: flex;
    align-items: center;
    gap: var(--space-2);
}

.dataset-size {
    font-size: var(--text-xs);
    color: var(--text-secondary);
    font-weight: var(--font-normal);
}

.dataset-desc {
    font-size: var(--text-sm);
    color: var(--text-secondary);
    margin-top: 2px;
}

/* Toggle Switch */
.toggle-switch {
    position: relative;
    width: 44px;
    height: 24px;
    flex-shrink: 0;
}

.toggle-switch input {
    opacity: 0;
    width: 0;
    height: 0;
}

.toggle-slider {
    position: absolute;
    cursor: pointer;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: var(--border);
    transition: 0.2s ease;
    border-radius: 24px;
}

.toggle-slider:before {
    position: absolute;
    content: "";
    height: 18px;
    width: 18px;
    left: 3px;
    bottom: 3px;
    background-color: white;
    transition: 0.2s ease;
    border-radius: 50%;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}

.toggle-switch input:checked + .toggle-slider {
    background-color: var(--primary);
}

.toggle-switch input:checked + .toggle-slider:before {
    transform: translateX(20px);
}

.toggle-switch input:disabled + .toggle-slider {
    cursor: not-allowed;
    opacity: 0.6;
}

.settings-hint {
    font-size: var(--text-sm);
    color: var(--text-secondary);
    margin-top: var(--space-3);
    margin-bottom: 0;
    padding-top: var(--space-3);
    border-top: 1px solid var(--border);
}

.settings-saving {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--text-sm);
    color: var(--text-secondary);
}

.settings-error {
    font-size: var(--text-sm);
    color: var(--error);
    margin-top: var(--space-2);
}

/* ========== KPI Card (Your Data Widget) ========== */
.kpi-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    overflow: hidden;
}

.kpi-header {
    background: linear-gradient(135deg, #1e3a5f 0%, #0f172a 100%);
    color: white;
    padding: var(--space-5);
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.kpi-header h3 {
    font-size: var(--text-md);
    font-weight: var(--font-semibold);
    color: white;
    margin: 0;
}

.kpi-header-stats {
    display: flex;
    gap: var(--space-5);
}

.kpi-header-stat {
    text-align: center;
}

.kpi-header-stat .value {
    font-size: var(--text-xl);
    font-weight: var(--font-semibold);
}

.kpi-header-stat .label {
    font-size: var(--text-xs);
    opacity: 0.7;
    text-transform: uppercase;
}

.kpi-header-stat-secondary {
    opacity: 0.7;
}

.kpi-body {
    padding: var(--space-5);
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-4);
}

.kpi-section {
    padding: var(--space-4);
    border-radius: var(--radius-md);
    border: 1px solid var(--border);
}

.kpi-section-disabled {
    opacity: 0.5;
}

.kpi-section-header {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin-bottom: var(--space-3);
}

.kpi-icon {
    width: 24px;
    height: 24px;
    border-radius: var(--radius-sm);
    display: flex;
    align-items: center;
    justify-content: center;
}

.kpi-icon.batch {
    background: var(--primary-light);
}

.kpi-icon.live {
    background: rgba(16, 183, 127, 0.1);
}

.kpi-icon.disabled {
    background: var(--background);
}

.kpi-section-title {
    font-size: var(--text-sm);
    font-weight: var(--font-medium);
}

.kpi-badge {
    margin-left: auto;
    font-size: 9px;
    font-weight: var(--font-semibold);
    padding: 2px 6px;
    border-radius: var(--radius-full);
    text-transform: uppercase;
    display: flex;
    align-items: center;
    gap: 4px;
}

.kpi-badge.live {
    background: rgba(16, 183, 127, 0.1);
    color: var(--success);
}

.kpi-section-time {
    font-size: var(--text-xs);
    color: var(--text-secondary);
    margin-bottom: var(--space-2);
}

.kpi-section-time.live {
    color: var(--success);
}

.kpi-section-datasets {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.kpi-footer {
    padding: var(--space-3) var(--space-5);
    background: var(--background);
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.kpi-highlights {
    display: flex;
    gap: var(--space-4);
    font-size: var(--text-sm);
    color: var(--text-secondary);
}

.kpi-highlights strong {
    color: var(--text-primary);
}

.kpi-link {
    font-size: var(--text-sm);
    font-weight: var(--font-medium);
    color: var(--primary);
    text-decoration: none;
    display: flex;
    align-items: center;
    gap: 4px;
    transition: all 0.15s ease;
}

.kpi-link:hover {
    text-decoration: underline;
}

/* Live dot animation */
.live-dot {
    width: 6px;
    height: 6px;
    background: var(--success);
    border-radius: 50%;
    animation: pulse 2s infinite;
    display: inline-block;
}

@keyframes pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.4; }
}

/* Disabled category tag */
.category-tag.disabled {
    background: var(--background);
    color: var(--text-secondary);
}

/* Responsive KPI card */
@media (max-width: 640px) {
    .kpi-header {
        flex-direction: column;
        gap: var(--space-3);
        text-align: center;
    }

    .kpi-body {
        grid-template-columns: 1fr;
    }

    .kpi-footer {
        flex-direction: column;
        gap: var(--space-3);
        text-align: center;
    }

    .kpi-highlights {
        flex-wrap: wrap;
        justify-content: center;
    }
}

/* ========== Login Page Split Layout ========== */
.login-page {
    min-height: 100vh;
    background: var(--background);
}

.login-split {
    display: grid;
    grid-template-columns: 1fr 1fr;
    min-height: 100vh;
}

/* Left side: Features */
.login-features {
    background: var(--primary);
    color: white;
    padding: var(--space-6) var(--space-8);
    display: flex;
    align-items: center;
    justify-content: center;
}

/* Navy theme — when the instance opts in via
   `AGNES_INSTANCE_THEME=navy` / `instance.theme=navy`, the
   `<html data-theme="navy">` attribute is set by base_login.html and
   we flip the login-page hero panel from the legacy brand-blue
   `--primary` to the deep-navy gradient used by the design-system
   hero so /login matches the rest of the navy-themed app. Only
   `.login-features` carries a brand colour on the pre-login screens;
   the rest of the login chrome is theme-neutral. */
:root[data-theme="navy"] .login-features {
    background: linear-gradient(135deg, #0f1b3a 0%, #0a1430 100%);
}

.features-content {
    max-width: 480px;
}

.features-title {
    font-size: 28px;
    font-weight: var(--font-semibold);
    line-height: 1.3;
    margin-bottom: var(--space-3);
}

.features-subtitle {
    font-size: var(--text-md);
    line-height: 1.6;
    opacity: 0.9;
    margin-bottom: var(--space-4);
}

/* Feature cards */
.feature-cards {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

.feature-card {
    display: flex;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    background: rgba(255, 255, 255, 0.1);
    border-radius: var(--radius-lg);
    border: 1px solid rgba(255, 255, 255, 0.15);
}

.feature-icon {
    width: 40px;
    height: 40px;
    border-radius: var(--radius-md);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    background: rgba(255, 255, 255, 0.15);
    color: white;
}

.feature-text h3 {
    font-size: var(--text-base);
    font-weight: var(--font-semibold);
    margin-bottom: 2px;
    color: white;
}

/* BETA chip on a feature-card heading — translucent-white chip readable
   on the brand-coloured features panel. Marks every capability that
   isn't the mature data-package core. */
.feature-text h3 .feature-beta {
    display: inline-block;
    margin-left: var(--space-2);
    padding: 1px 6px;
    font-size: 10px;
    font-weight: var(--font-semibold);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    vertical-align: middle;
    border-radius: var(--radius-sm);
    background: rgba(255, 255, 255, 0.2);
    color: white;
    border: 1px solid rgba(255, 255, 255, 0.35);
}

.feature-text p {
    font-size: var(--text-sm);
    line-height: 1.5;
    color: rgba(255, 255, 255, 0.8);
    margin: 0;
}

/* "Made by Keboola" attribution lockup at the foot of the brand panel.
   Logo paths use currentColor so the wordmark renders white on the
   coloured features panel. */
.login-made-by {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin-top: var(--space-5);
    color: rgba(255, 255, 255, 0.8);
    font-size: var(--text-sm);
}

.login-made-by .made-by-logo {
    height: 26px;
    width: auto;
    color: white;
}

/* Right side: Login card */
.login-card-wrapper {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-6);
    background: var(--background);
}

.login-page .login-card {
    background: var(--surface);
    border-radius: var(--radius-xl);
    padding: 48px 40px;
    text-align: center;
    box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.04);
    max-width: 420px;
    width: 100%;
    border: 1px solid var(--border);
}

.login-page .login-card h2 {
    margin-bottom: var(--space-2);
    font-size: var(--text-xl);
    font-weight: var(--font-semibold);
    color: var(--text-primary);
}

.login-page .login-description {
    color: var(--text-secondary);
    margin-bottom: var(--space-6);
    font-size: var(--text-base);
    line-height: 1.5;
}

.login-page .login-note {
    font-size: var(--text-sm);
    color: var(--text-secondary);
    margin-top: var(--space-3);
}

.login-page .login-note strong {
    color: var(--text-primary);
}

.login-page .divider {
    display: flex;
    align-items: center;
    margin: var(--space-5) 0;
    color: var(--text-secondary);
    font-size: var(--text-sm);
}

.login-page .divider::before,
.login-page .divider::after {
    content: "";
    flex: 1;
    height: 1px;
    background-color: var(--border);
}

.login-page .divider span {
    padding: 0 var(--space-4);
}

/* Google button styling */
.login-page .btn-google {
    background-color: white;
    color: var(--text-primary);
    border: 1px solid var(--border);
    padding: 14px 28px;
    font-size: var(--text-base);
    font-weight: var(--font-medium);
    width: 100%;
    max-width: 280px;
    transition: all 0.2s ease;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    border-radius: var(--radius-md);
    text-decoration: none;
    cursor: pointer;
}

.login-page .btn-google:hover {
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
    border-color: var(--border-dark);
    background-color: var(--background);
}

.login-page .btn-google:active {
    transform: scale(0.98);
}

/* Email magic link button styling */
.login-page .btn-email {
    background-color: #4361ee;
    color: white;
    border: 1px solid #3a56d4;
    padding: 14px 28px;
    font-size: var(--text-base);
    font-weight: var(--font-medium);
    border-radius: var(--radius-md);
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 12px;
    transition: all 0.2s ease;
    cursor: pointer;
}

.login-page .btn-email:hover {
    background-color: #3a56d4;
    box-shadow: 0 2px 8px rgba(67, 97, 238, 0.3);
}

.login-page .btn-email:active {
    transform: scale(0.98);
}

.login-page .btn-secondary {
    background-color: transparent;
    color: var(--text-secondary);
    border: 1px solid var(--border);
    padding: 14px 28px;
    font-size: var(--text-base);
    font-weight: var(--font-medium);
    border-radius: var(--radius-md);
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: all 0.2s ease;
    cursor: pointer;
}

.login-page .btn-secondary:hover {
    background-color: var(--background);
    border-color: var(--text-secondary);
}

/* Login-card width constraints — provider buttons + inline error banner
   sit centred at 280px max-width so they line up with the card's content
   column. Previously these widths were carried inline on each element
   (`style="max-width: 280px"`); pulled into the stylesheet so markup can
   compose `.btn .btn-primary/secondary .btn-block .login-provider-btn`
   via `ds.button(klass=...)`. */
.login-page .login-provider-btn {
    max-width: 280px;
}
.login-page .login-error {
    max-width: 280px;
    margin: 0 auto var(--space-4);
    text-align: left;
}

/* Responsive: Tablet */
@media (max-width: 1024px) {
    .login-split {
        grid-template-columns: 1fr;
    }

    .login-features {
        padding: var(--space-6);
        min-height: auto;
    }

    .features-content {
        max-width: 600px;
    }

    .features-title {
        font-size: 28px;
    }

    .features-subtitle {
        margin-bottom: var(--space-6);
    }

    .feature-cards {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: var(--space-3);
    }

    .feature-card {
        padding: var(--space-3);
    }

    .feature-icon {
        width: 36px;
        height: 36px;
    }

    .login-card-wrapper {
        padding: var(--space-6);
    }
}

/* Responsive: Mobile */
@media (max-width: 640px) {
    .login-features {
        padding: var(--space-5);
    }

    .features-content {
        max-width: 100%;
    }

    .features-title {
        font-size: 24px;
    }

    .features-subtitle {
        font-size: var(--text-base);
        margin-bottom: var(--space-5);
    }

    .feature-cards {
        grid-template-columns: 1fr;
        gap: var(--space-3);
    }

    .feature-card {
        padding: var(--space-3);
        gap: var(--space-3);
    }

    .feature-text h3 {
        font-size: var(--text-base);
    }

    .feature-text p {
        font-size: var(--text-xs);
    }

    .login-card-wrapper {
        padding: var(--space-4);
    }

    .login-page .login-card {
        padding: var(--space-6);
    }
}

/* ========== Corporate Memory Widget (Design System - Warning Color) ========== */
.memory-widget {
    border-left: 3px solid var(--warning);
}

.memory-widget .card-header-custom {
    background: rgba(245, 159, 10, 0.08);
    padding: var(--space-4) var(--space-5);
    margin: calc(-1 * var(--space-5));
    margin-bottom: var(--space-4);
    border-bottom: 1px solid var(--border);
    display: flex;
    align-items: center;
    gap: var(--space-2);
}

.memory-icon {
    width: 28px;
    height: 28px;
    background: var(--warning);
    border-radius: var(--radius-md);
    display: flex;
    align-items: center;
    justify-content: center;
    color: white;
}

.memory-widget h3 {
    margin-bottom: 0;
    display: flex;
    align-items: center;
    gap: var(--space-2);
}

.memory-stats-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-4);
    margin-bottom: var(--space-4);
}

.memory-stat {
    text-align: center;
    padding: var(--space-4);
    background: var(--background);
    border-radius: var(--radius-lg);
}

.memory-stat .value {
    font-size: var(--text-2xl);
    font-weight: var(--font-bold);
    color: var(--text-primary);
    line-height: 1;
}

.memory-stat .label {
    font-size: var(--text-xs);
    color: var(--text-secondary);
    margin-top: var(--space-1);
}

.memory-stat.highlight {
    background: var(--warning);
}

.memory-stat.highlight .value,
.memory-stat.highlight .label {
    color: white;
}

.memory-description {
    font-size: var(--text-sm);
    color: var(--text-secondary);
    line-height: 1.6;
    padding: var(--space-3) var(--space-4);
    background: var(--background);
    border-radius: var(--radius-md);
    border-left: 3px solid rgba(245, 159, 10, 0.3);
    margin-bottom: var(--space-4);
}

.memory-description strong {
    color: var(--text-primary);
}

.memory-description code {
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    background: rgba(0, 0, 0, 0.05);
    padding: 1px 4px;
    border-radius: var(--radius-sm);
}

.memory-footer {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding-top: var(--space-3);
    border-top: 1px solid var(--border);
    margin-top: var(--space-4);
}

.sync-status {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: var(--text-sm);
    color: var(--success);
}

.memory-link {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: var(--space-2) var(--space-4);
    background: var(--warning);
    color: white;
    text-decoration: none;
    font-size: var(--text-sm);
    font-weight: var(--font-medium);
    border-radius: var(--radius-md);
    transition: all 0.15s ease;
}

.memory-link:hover {
    background: #d97706;
    transform: translateY(-1px);
}

/* ─── Shared modern header (used by base.html + future pages) ─── */
/* Mirrors the inline header styles in dashboard.html so all pages share chrome. */

/* Adaptive header. Narrow/medium screens: a compact two-row layout —
     Row 1 (.app-header-bar) — product name (left) + search / help / user (right).
     Row 2 (.app-header-nav) — primary navigation, left-aligned under the name.
   Wide screens (≥1200px, see media query below): everything collapses onto a
   single compact row so the header isn't unnecessarily tall.
   A subtle 1px bottom border separates the header from page content. */
/* Single horizontal band: [logo] [primary nav ····] ⟨spacer⟩ [search]
   [Admin*] [help] [user]. The nav grows to fill the middle and hands overflow
   to the "More" menu (priority-plus in app.js) so the row never wraps on
   desktop/laptop widths. `flex-wrap` is the graceful fallback for very narrow
   (mobile) viewports where even a minimal nav + the actions cluster can't
   share a line with the logo — the actions cluster wraps below rather than
   clipping. */
.app-header {
    background: var(--surface, #fff);
    border-bottom: 1px solid var(--border, #e5e7eb);
    /* Override the legacy generic `header { margin-bottom: 24px }` rule from
       style.css — the .app-header sits flush against the page container. */
    margin-bottom: 0;
    padding: var(--space-2) var(--space-8);   /* 8px 32px — compact single row */
    display: flex;
    flex-direction: row;
    /* nowrap so a tight width squeezes the nav (which then sheds items into
       "More" via priority-plus) instead of wrapping the actions cluster to a
       second row. Mobile (<640px) flips this to `wrap` for a deliberate
       two-row layout. */
    flex-wrap: nowrap;
    align-items: center;
    column-gap: var(--space-6);
    row-gap: var(--space-2);
    /* Neutralize the legacy generic `header { text-align: center }`
       (@media max-width:600px in this file) so the header never center-aligns. */
    text-align: left;
    min-height: var(--app-header-height);
    position: sticky;
    top: 0;
    z-index: 100;
}

/* Logo pinned left — never shrinks. */
.app-header-logo { flex: 0 0 auto; }

/* `.app-header-left` — kept for the `ds.primary_nav` macro (_components.html),
   scaffolding for a topnav refactor that never landed before the topnav
   chrome itself was retired (Wave 0, 2026-08); the macro has no current
   caller. Left in place rather than deleted since it's inert CSS with no
   render cost. */
.app-header-left { display: inline-flex; align-items: center; gap: var(--space-3); flex: 0 0 auto; }

/* Primary nav — the flexible middle. Grows to fill, shrinks toward zero
   (handing items to "More") so the actions cluster keeps its space. */
.app-header-nav {
    display: flex;
    align-items: center;
    flex: 1 1 auto;
    gap: var(--space-1);
    /* Never let the nav grow past the header (otherwise a too-wide nav
       overflows the page instead of being measured as overflow). Combined
       with `.is-priority`'s `overflow:hidden`, this makes clientWidth track
       the available width so the priority-plus logic can detect overflow. */
    min-width: 0;
    max-width: 100%;
    flex-wrap: wrap;   /* no-JS fallback: links wrap rather than clip */
}
.app-header-nav.is-priority {
    flex-wrap: nowrap;
    overflow: hidden;
}

/* Right cluster — search + Admin + help + user. Pinned right (margin-left
   auto so it hugs the far edge even when the nav shrinks), never shrinks its
   own gaps; the search inside it does the shrinking. */
.app-header-actions {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex: 0 0 auto;
    margin-left: auto;
    justify-content: flex-end;
    min-width: 0;
}

.app-header-logo {
    display: inline-flex;
    align-items: center;
    text-decoration: none;
    color: inherit;
    font-weight: 600;
    font-size: 16px;
}
.app-header-logo svg {
    display: block;
    max-height: 40px;
    width: auto;
}
a.app-header-logo:focus-visible {
    outline: 2px solid var(--primary, #6366f1);
    outline-offset: 2px;
    border-radius: 4px;
}

.app-header-subtitle {
    font-size: 11px;
    font-weight: 500;
    color: var(--text-secondary, #6b7280);
    letter-spacing: 0.4px;
    text-transform: uppercase;
    margin-top: 2px;
}

.app-header-right {
    display: flex;
    align-items: center;
    gap: 16px;
    /* `.app-header` now uses `justify-content: flex-start` (was
       space-between), so the legacy `primary_nav` macro that still uses
       this class keeps its right alignment via an explicit auto margin. */
    margin-left: auto;
}

.app-header-email {
    font-size: 13px;
    color: var(--text-secondary, #6b7280);
    font-weight: 500;
}

.app-avatar {
    width: 36px;
    height: 36px;
    border-radius: 50%;
    background: var(--primary-light, #eef2ff);
    color: var(--primary, #6366f1);
    font-size: 13px;
    font-weight: 600;
    display: flex;
    align-items: center;
    justify-content: center;
    letter-spacing: 0.3px;
}

.app-avatar-img {
    width: 36px;
    height: 36px;
    border-radius: 50%;
    border: 2px solid var(--border, #e5e7eb);
}

/* Top-nav entries. Both <a class="app-nav-link"> and
   <button class="app-nav-link app-nav-menu-trigger"> share this rule
   so the Admin dropdown trigger renders identical to its siblings —
   same font, color, padding, hover, active state. The button-element
   resets (background/border/font-family) flow from here, so
   .app-nav-menu-trigger no longer needs to strip <button> chrome
   separately. */
.app-nav-link {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 14px;
    font-weight: 500;
    font-family: inherit;
    color: var(--text-secondary);
    text-decoration: none;
    padding: 8px 14px;
    border-radius: 8px;
    border: 0;
    background: transparent;
    cursor: pointer;
    line-height: 1.2;
    /* Keep each label on one line so a tight nav row overflows horizontally
       (→ items move into the "More" menu) instead of wrapping its text. */
    white-space: nowrap;
    transition: all 0.15s ease;
}
.app-nav-link:hover {
    color: var(--primary-dark);
    background: var(--primary-light);
}
.app-nav-link.is-active,
.app-nav-link[aria-expanded="true"] {
    color: var(--primary-dark);
    background: var(--primary-light);
}
.app-nav-link:focus { outline: none; }
.app-nav-link:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
}

.app-btn-logout {
    font-size: 13px;
    font-weight: 500;
    color: var(--text-secondary, #6b7280);
    background: none;
    border: 1px solid var(--border, #e5e7eb);
    border-radius: 8px;
    padding: 6px 14px;
    cursor: pointer;
    transition: all 0.15s ease;
    text-decoration: none;
    display: inline-block;
}
.app-btn-logout:hover {
    color: var(--text-primary, #111827);
    border-color: #d1d5db;
    background: var(--border-light, #f3f4f6);
}

/* ── User menu (dropdown) ── */
.app-user-menu { position: relative; display: inline-flex; align-items: center; }
.app-user-menu-trigger {
    display: inline-flex; align-items: center; gap: 6px;
    background: none; border: 1px solid transparent;
    border-radius: 999px; padding: 4px 10px 4px 4px;
    cursor: pointer; transition: all 0.15s ease;
}
.app-user-menu-trigger:hover { background: var(--border-light, #f3f4f6); border-color: var(--border, #e5e7eb); }
.app-user-menu-trigger[aria-expanded="true"] { background: var(--border-light, #f3f4f6); border-color: var(--border, #e5e7eb); }
.app-user-menu-chevron { color: var(--text-secondary, #6b7280); transition: transform 0.15s ease; }
.app-user-menu-trigger[aria-expanded="true"] .app-user-menu-chevron { transform: rotate(180deg); }
.app-user-menu-panel {
    position: absolute; top: calc(100% + 8px); right: 0;
    min-width: 240px;
    /* The Admin sections now live in here, so cap the height and scroll
       rather than run off the bottom of the viewport. */
    max-height: calc(100vh - 88px);
    overflow-y: auto;
    background: var(--surface, #fff);
    border: 1px solid var(--border, #e5e7eb);
    border-radius: 10px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
    padding: 6px;
    z-index: 50;
}
.app-user-menu-panel[hidden] { display: none; }
/* Admin block inside the user dropdown — the collapsible <details> groups
   reuse the .app-nav-menu-* look; this just labels the block and nudges the
   nested section headers to line up with the flat items above them. */
.app-user-menu-admin-label { padding-left: 12px; }
.app-user-menu-admin .app-nav-menu-group > summary.app-nav-menu-section { padding-left: 12px; }
.app-user-menu-admin .app-nav-menu-item { padding-left: 20px; }
.app-user-menu-header {
    padding: 10px 12px 8px;
    border-bottom: 1px solid var(--border-light, #f3f4f6);
    margin-bottom: 4px;
}
.app-user-menu-email { font-size: 13px; font-weight: 500; color: var(--text-primary, #111827); word-break: break-all; }
.app-user-menu-role { font-size: 11px; color: var(--text-secondary, #6b7280); margin-top: 2px; text-transform: uppercase; letter-spacing: 0.3px; }
.app-user-menu-item {
    display: block; padding: 8px 12px;
    font-size: 13px; color: var(--text-primary, #111827);
    text-decoration: none; border-radius: 6px;
}
.app-user-menu-item:hover { background: var(--border-light, #f3f4f6); }
.app-user-menu-item.is-active { background: var(--primary-light); color: var(--primary); font-weight: 500; }
/* Onboarding launcher — the "?" help icon in the header that re-opens the
   guided tour. Borderless icon button that sits next to the user menu. */
.app-onboarding-launch {
    display: inline-flex; align-items: center; justify-content: center;
    width: 34px; height: 34px; padding: 0;
    border: 0; border-radius: 8px; background: transparent;
    color: var(--text-secondary, #6b7280); cursor: pointer;
}
.app-onboarding-launch:hover { background: var(--border-light, #f3f4f6); color: var(--text-primary, #111827); }
.app-onboarding-launch:focus-visible { outline: var(--ds-focus-outline); outline-offset: var(--ds-focus-outline-offset); }

/* ── Theme toggle — Light / Dark / System segmented control in the user menu.
   One row: the "Theme" label reads like a menu item, the three choices sit
   opposite it as an icon-only pill (markup: _theme_toggle.html) — the stacked
   label-over-full-width-buttons layout cost two rows for the same three
   choices. Built only from tokens that flip under [data-theme="dark"]
   (--surface, --surface-dim, --border, --text-*, --shadow-sm), so it themes
   itself in both modes with no dark-specific rules.
   Wiring: app.js → window.__agnesTheme. ── */
.app-user-menu-sep { height: 1px; background: var(--border-light, #f3f4f6); margin: 6px 4px; }
.app-theme-toggle {
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    padding: 4px 12px 6px;
}
.app-theme-toggle-label {
    font-size: 13px; line-height: 1.4;
    color: var(--text-primary, #111827);
}
.app-theme-toggle-options {
    display: inline-flex; gap: 2px; padding: 2px;
    background: var(--surface-dim, #f0f2f6);
    border: 1px solid var(--border, #e5e7eb);
    border-radius: 999px;
}
.app-theme-toggle-btn {
    display: inline-flex; align-items: center; justify-content: center;
    width: 26px; height: 26px; padding: 0;
    border: 1px solid transparent; border-radius: 999px;
    color: var(--text-secondary, #6b7280);
    background: transparent; cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
/* Sized here (not on the inline SVGs) so the icons can't inherit a stray
   page-level `svg { width: 100% }` and blow the pill apart. */
.app-theme-toggle-btn svg { width: 15px; height: 15px; display: block; }
/* Idle hover = a ghost of the active chip (ink + ring, no fill). A fill would
   need a token between --surface-dim and --surface, and the two are a hair
   apart in light mode — the ring reads in both themes. */
.app-theme-toggle-btn:hover { color: var(--text-primary, #111827); }
.app-theme-toggle-btn:not(.is-active):hover { border-color: var(--border, #e5e7eb); }
.app-theme-toggle-btn.is-active {
    background: var(--surface, #fff);
    border-color: var(--border, #e5e7eb);
    color: var(--text-primary, #111827);
    box-shadow: var(--shadow-sm, 0 1px 2px rgba(0, 0, 0, 0.08));
}
.app-theme-toggle-btn:focus-visible { outline: 2px solid var(--ds-primary, #2ea877); outline-offset: 2px; }

/* ── Admin nav dropdown — same vocabulary as user menu, scoped under nav links ── */
.app-nav-menu { position: relative; display: inline-flex; }
/* The "More" overflow container is toggled via the `hidden` attribute by
   app.js; the class `display:inline-flex` above would otherwise defeat the
   native `[hidden]`, so restore it explicitly. */
.app-nav-more[hidden] { display: none; }
/* .app-nav-menu-trigger styling flows entirely from .app-nav-link above.
   The trigger class is kept in markup as a hook for app.js dropdown
   wiring and for targeting the chevron rotation below. */
.app-nav-menu-chevron { color: currentColor; transition: transform 0.15s ease; }
.app-nav-menu-trigger[aria-expanded="true"] .app-nav-menu-chevron { transform: rotate(180deg); }
.app-nav-menu-panel {
    position: absolute; top: calc(100% + 8px); right: 0;
    min-width: 200px;
    max-height: calc(100vh - 80px);
    overflow-y: auto;
    background: var(--surface, #fff);
    border: 1px solid var(--border, #e5e7eb);
    border-radius: 10px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
    padding: 6px;
    z-index: 50;
}
.app-nav-menu-panel[hidden] { display: none; }
.app-nav-menu-item {
    display: block; padding: 8px 12px;
    font-size: 13px; color: var(--text-primary, #111827);
    text-decoration: none; border-radius: 6px;
}
.app-nav-menu-item:hover { background: var(--border-light, #f3f4f6); }
.app-nav-menu-item.is-active { background: var(--primary-light); color: var(--primary-dark); font-weight: 500; }

/* ── Admin mega-menu — first-class, admin-only. The trigger reuses
   `.app-nav-link`; this styles its wide multi-column panel so all admin
   domains are visible at once (vs. the old one-at-a-time accordion buried in
   the user menu). Panel is right-anchored under the trigger and capped to the
   viewport so it never runs off-screen. ── */
.app-admin-menu { position: relative; display: inline-flex; }
.app-admin-menu-trigger .app-nav-menu-chevron { margin-left: 2px; }
.app-admin-menu-panel {
    /* Override the narrow single-column `.app-nav-menu-panel` width. */
    width: min(680px, 94vw);
    min-width: 0;
    max-height: calc(100vh - var(--app-header-height) - 24px);
}
.app-admin-menu-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-4);
    padding: 4px 8px 8px;
    margin-bottom: 4px;
    border-bottom: 1px solid var(--border-light, #f3f4f6);
}
.app-admin-menu-title {
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: var(--text-secondary, #6b7280);
}
.app-admin-menu-hub {
    font-size: 12px;
    font-weight: 500;
    color: var(--primary-dark);
    text-decoration: none;
    padding: 2px 8px;
    border-radius: 6px;
}
.app-admin-menu-hub:hover { background: var(--primary-light); }
.app-admin-menu-hub.is-active { background: var(--primary-light); }
.app-admin-menu-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 2px var(--space-4);
}
.app-admin-menu-col { min-width: 0; }
/* Column section header — small-caps label, no background band (the columns
   already separate the groups visually). First label in a column has no top
   margin; subsequent ones (a column holding two groups) get breathing room. */
.app-admin-menu-section {
    padding: 6px 12px 2px;
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: var(--text-muted, #9ca3af);
}
.app-admin-menu-col > .app-admin-menu-section:first-child { padding-top: 0; }
.app-admin-menu-panel .app-nav-menu-item { padding: 6px 12px; }
@media (max-width: 720px) {
    .app-admin-menu-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Primary-nav links relocated into the "More" overflow menu by app.js: render
   the pill links as full-width block rows so they read as menu items. Their
   hover / active / focus states come from `.app-nav-link` above, unchanged. */
.app-nav-more-panel .app-nav-link {
    display: flex;
    width: 100%;
    justify-content: flex-start;
    padding: 8px 12px;
    border-radius: 6px;
}
/* One-line explainer under a nav item (#755 "Sources" group — Data sources
   vs. MCP sources were visually indistinguishable, which is exactly the
   confusion the issue reported). Muted, wraps under the link label. */
.app-nav-menu-item-desc {
    display: block;
    font-size: 11px;
    font-weight: 400;
    color: var(--text-secondary, #6b7280);
    margin-top: 1px;
    white-space: normal;
}
.admin-menu-separator { margin: 4px 8px; border: 0; border-top: 1px solid var(--border, #e5e7eb); }
/* Section header inside the admin dropdown — non-clickable, small-caps,
   sitting in a light gray band so each named group (Activity Center /
   Users & Access / Data / Agent Experience / Server) reads as a
   visually contained cluster. The negative horizontal margins extend
   the band edge-to-edge inside the panel (panel has 6px padding;
   matching -6px here). Rounded top corners on the first header line
   up with the panel's own border-radius so the first band tucks
   into the top of the panel cleanly. */
.app-nav-menu-section {
    background: var(--border-light, #f3f4f6);
    margin: 8px -6px 4px;
    padding: 6px 12px;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: var(--text-secondary, #6b7280);
    font-weight: 700;
}
.app-nav-menu-section:first-child {
    margin-top: -6px;
    border-radius: 10px 10px 0 0;
}
.app-nav-menu-section + .app-nav-menu-item { margin-top: 4px; }

/* <details data-section=...> group wrapper — visually identical to the
   pre-collapse render, but each section can fold up. <summary> reuses
   the .app-nav-menu-section style and gets a chevron via :before that
   rotates 90deg when [open] (no JS for the visual). */
.app-nav-menu-group { margin: 0; }
.app-nav-menu-group > summary.app-nav-menu-section {
    cursor: pointer;
    user-select: none;
    list-style: none;       /* hide native disclosure triangle (most browsers) */
    display: flex;
    align-items: center;
    gap: 6px;
}
.app-nav-menu-group > summary.app-nav-menu-section::-webkit-details-marker {
    display: none;          /* WebKit fallback */
}
.app-nav-menu-group > summary.app-nav-menu-section::before {
    content: "›";
    font-size: 12px;
    line-height: 1;
    color: var(--text-secondary, #6b7280);
    transition: transform 0.12s ease;
}
.app-nav-menu-group[open] > summary.app-nav-menu-section::before {
    transform: rotate(90deg);
}
/* First-section rounded corners stayed on the original first-child rule,
   but the wrapper <details> is now the first child — re-apply on the
   nested <summary>. */
.app-nav-menu-group:first-of-type > summary.app-nav-menu-section {
    margin-top: -6px;
    border-radius: 10px 10px 0 0;
}

/* Mobile — a single row can't hold logo + nav + the full actions cluster, so
   deliberately wrap into two rows: row 1 is logo + actions (search / Admin /
   help / user), row 2 is the primary nav full-width beneath. This is the only
   width at which the header is intentionally multi-row. */
@media (max-width: 640px) {
    .app-header {
        flex-wrap: wrap;
        padding-left: var(--space-4);
        padding-right: var(--space-4);
        column-gap: var(--space-4);
    }
    .app-header-logo { order: 1; }
    .app-header-actions { order: 2; flex: 0 0 auto; margin-left: auto; gap: var(--space-1); }
    .app-header-nav { order: 3; flex-basis: 100%; }
    /* initPriorityNav() adds .is-priority regardless of viewport (it only
       reacts to overflow, not screen width), which would otherwise force
       flex-wrap: nowrap here and fight the two-row mobile layout above —
       the nav needs to wrap on its own row, not overflow into "More". */
    .app-header-nav.is-priority { flex-wrap: wrap; overflow: visible; }
    .app-nav-link { padding: 6px 10px; }
}

/* ── Standard page shell ─────────────────────────────────────────────
   Shared layout container for pages that extend base.html. Matches the
   dashboard's `.main` dimensions so /dashboard, /marketplace, and the
   marketplace detail pages all align. Add `page-shell` to a page's root
   wrapper to opt in. */
/* `.page-shell` opt-in is now redundant — the canonical `.container`
   already provides the 1280px max-width + `16px 32px 48px` padding +
   stripped `<main>` defaults that this rule used to add. Kept as a
   no-op marker so existing templates that opt in continue to render
   the same as everything else. */


/* ==============================================================
   News content vocabulary (shared)
   --------------------------------------------------------------
   Used by: /news (full content), /home (perex slot via .home-news-body),
            /admin/news preview pane.

   Author classes documented in docs/operator/news-content-guide.md:
     .news-hero, .callout, .callout-{info,warn,success,danger},
     .video-embed, .news-section, .news-grid-2, .news-grid-3, .news-cta.

   Selectors are scope-free where the class itself is the marker
   (.callout, .news-hero, .video-embed, .news-grid-*, .news-cta) so
   authors can drop them anywhere — /home perex, /news body,
   /admin/news preview, future admin pages.

   The table / pre / code overrides ARE scoped to .news-content because
   `<table>` / `<pre>` / inline `<code>` exist on plenty of other pages
   that should not inherit this styling.
   ============================================================== */

/* Callouts — boxed inline notice with colored left border. */
.callout {
    border-left: 4px solid var(--primary);
    background: var(--primary-light);
    color: #111827;
    padding: 12px 16px;
    border-radius: 6px;
    margin: 12px 0;
}
.callout-info    { border-left-color: var(--primary); background: var(--primary-light); }
/* warn/success/danger backgrounds ride the --ds-accent-* family (dark-theme
   safe) instead of raw hex; ink flips alongside so the pair stays readable
   under dark (#1625) — the border-left accent stays the pre-existing literal,
   it is decorative and not a text/background contrast pair. */
.callout-warn    { border-left-color: #C2410C; background: var(--ds-accent-warn-bg); color: var(--ds-accent-warn-ink); }
.callout-success { border-left-color: #047857; background: var(--ds-accent-success-bg); color: var(--ds-accent-success-ink); }
.callout-danger  { border-left-color: #B91C1C; background: var(--ds-accent-danger-bg); color: var(--ds-accent-danger-ink); }
.callout > :first-child { margin-top: 0; }
.callout > :last-child  { margin-bottom: 0; }

/* 16:9 wrapper for YouTube/Vimeo/Loom iframes. */
.video-embed {
    position: relative;
    padding-bottom: 56.25%;
    height: 0;
    overflow: hidden;
    border-radius: 8px;
    margin: 14px 0;
    background: black;
}
.video-embed iframe,
.video-embed video,
.video-embed a {
    position: absolute;
    top: 0; left: 0;
    width: 100%; height: 100%;
    border: 0;
}
.video-embed a {
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    text-decoration: underline;
}

/* Section divider — subtle top margin to separate topics. */
.news-section { margin-top: 28px; }
.news-section:first-child { margin-top: 0; }

/* 2- / 3-column responsive grids. */
.news-grid-2,
.news-grid-3 {
    display: grid;
    gap: 16px;
    margin: 12px 0;
}
.news-grid-2 { grid-template-columns: repeat(2, 1fr); }
.news-grid-3 { grid-template-columns: repeat(3, 1fr); }
@media (max-width: 720px) {
    .news-grid-2,
    .news-grid-3 { grid-template-columns: 1fr; }
}

/* Anchor styled as primary button. */
a.news-cta {
    display: inline-block;
    background: var(--primary);
    color: white !important;       /* override link colors on /home */
    text-decoration: none !important;
    padding: 9px 18px;
    border-radius: 6px;
    font-size: 14px;
    font-weight: 500;
    margin: 8px 0;
    transition: background 120ms ease;
}
a.news-cta:hover { background: #0056A3; }

/* --- .news-content scope: tables, code, pre, blockquote, headings ---
   Anything inside the long-form body needs these refinements; they
   would over-style chrome elsewhere on the site so we scope them. */

/* Every color below is a --ds-* token (#1625): .news-content renders inside
   .news-card, whose background is var(--surface) and DOES flip under dark
   theme, so a literal ink here (e.g. the previous `color: #111827`) went
   invisible the moment the card flipped — measured at 1.16:1 on the dark
   surface, the exact bug class this file is being swept for. */
.news-content { font-size: 15px; line-height: 1.6; color: var(--ds-text-primary); }
.news-content h1 { font-size: 24px; margin: 24px 0 12px; line-height: 1.25; }
.news-content h2 { font-size: 20px; margin: 22px 0 10px; line-height: 1.3; }
.news-content h3 { font-size: 17px; margin: 18px 0 8px; }
.news-content p  { margin: 0 0 12px; }
.news-content ul,
.news-content ol { margin: 0 0 12px 24px; }
.news-content li { margin: 4px 0; }
.news-content blockquote {
    border-left: 4px solid var(--ds-border);
    padding: 6px 16px;
    margin: 14px 0;
    color: var(--ds-text-secondary);
    font-style: italic;
    background: var(--ds-surface-dim);
    border-radius: 0 6px 6px 0;
}

/* Code: inline = light gray pill; block = dark navy with amber text.
   The :not(pre code) split keeps inline code from overriding the
   pre>code dark scheme — earlier versions had pre>code inheriting
   the light background from the inline rule, producing the
   "yellow-on-silver" unreadable combo. */
.news-content code {
    background: var(--ds-surface-dim);
    color: var(--ds-text-primary);
    padding: 1px 6px;
    border-radius: 4px;
    font-family: var(--font-mono);
    font-size: 13px;
}
/* .news-content pre stays a deliberate always-dark code panel (like the
   /home terminal mock) — navy bg + amber ink, legible under every theme
   without flipping, so it is not part of the #1625 sweep. */
.news-content pre {
    background: #0F172A;
    color: #FBBF24;
    padding: 14px 18px;
    border-radius: 8px;
    overflow-x: auto;
    margin: 14px 0;
    line-height: 1.55;
}
.news-content pre code {
    background: transparent;
    color: inherit;
    padding: 0;
    font-size: 13px;
    border-radius: 0;
}

/* Tables: border-collapse, header band, zebra rows on hover, compact
   padding. Authors get a presentable default without writing custom
   classes; .news-grid-2 / .news-grid-3 covers the layout-grid use case. */
.news-content table {
    width: 100%;
    border-collapse: collapse;
    margin: 14px 0;
    font-size: 14px;
    background: var(--ds-surface);
    border: 1px solid var(--ds-border);
    border-radius: 8px;
    overflow: hidden;
}
.news-content thead { background: var(--ds-surface-dim); }
.news-content th {
    text-align: left;
    padding: 10px 14px;
    font-weight: 600;
    color: var(--ds-text-secondary);
    border-bottom: 1px solid var(--ds-border);
}
.news-content td {
    padding: 10px 14px;
    border-bottom: 1px solid var(--ds-border-light);
    vertical-align: top;
}
.news-content tbody tr:last-child td { border-bottom: none; }
.news-content tbody tr:hover { background: var(--ds-surface-dim); }
.news-content table code {
    /* Same tint as the inline-code pill above — the original's slightly
       lighter #EEF2F7 vs #F3F4F6 was a marginal, unintentional-looking
       difference not worth a second token. */
    background: var(--ds-surface-dim);
}

/* Reusable hero block — blue gradient, eyebrow + title + lead.
   Defined AFTER the .news-content overrides so its `color: white` on
   nested headings wins source-order ties (both selectors share
   specificity 0,1,1 since they're "one class + one type"). Authors
   drop `<section class="news-hero">` anywhere — /home perex, /news
   body, /admin/news preview. */
.news-hero {
    background: linear-gradient(135deg, var(--primary) 0%, #0056A3 100%);
    color: white;
    border-radius: 16px;
    padding: 28px 32px;
    margin: 18px 0;
    box-shadow: 0 8px 24px rgba(31, 138, 94, 0.18);
}
.news-hero .eyebrow {
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.8px;
    opacity: 0.85;
    margin-bottom: 10px;
    color: white;
}
.news-hero h1,
.news-hero h2,
.news-hero h3 {
    color: white;
    margin: 0 0 8px;
    font-weight: 600;
    line-height: 1.25;
}
.news-hero h1 { font-size: 26px; letter-spacing: -0.4px; }
.news-hero h2 { font-size: 20px; }
.news-hero h3 { font-size: 17px; }
.news-hero .lead,
.news-hero p {
    font-size: 15px;
    opacity: 0.94;
    line-height: 1.55;
    margin: 0;
    color: white;
}
.news-hero a {
    color: #FBBF24;
    text-decoration: underline;
}
.news-hero code {
    background: rgba(15, 23, 42, 0.55);
    color: #FBBF24;
    border: 1px solid rgba(255, 255, 255, 0.18);
    padding: 1px 6px;
    border-radius: 4px;
    font-size: 12.5px;
}

/* /home perex slot: the same callout / video-embed / hero classes
   work inside .home-news-body without selector duplication, since the
   rules above don't depend on a parent. The home-specific chrome
   (margin, header strip) lives in home_not_onboarded.html. */


/* ==============================================================
   Marketplace format guide page (/marketplace/format-guide).
   Renders curator-facing markdown via markdown-it-py; the rules
   below scope the document chrome to .format-guide so we don't
   override <h1>/<pre>/<table> styling on other pages. Dark <pre>
   matches the JSON snippets the guide includes.
   ============================================================== */
.container:has(.format-guide) { max-width: none; padding: 24px 16px; }
.format-guide {
    max-width: 880px;
    margin: 0 auto;
    padding: 0;
    line-height: 1.55;
}
.format-guide h1 {
    font-size: 28px; font-weight: 700;
    margin: 0 0 8px;
    color: var(--text-primary, #111827);
}
.format-guide .lead {
    font-size: 15px; color: var(--text-secondary, #6b7280);
    margin-bottom: 32px; padding-bottom: 16px;
    border-bottom: 1px solid var(--border, #e5e7eb);
}
.format-guide h2 {
    font-size: 20px; font-weight: 600;
    margin: 32px 0 12px;
    color: var(--text-primary, #111827);
}
.format-guide h3 {
    font-size: 16px; font-weight: 600;
    margin: 24px 0 8px;
    color: var(--text-primary, #111827);
}
.format-guide p {
    margin: 0 0 12px;
    color: var(--text-primary, #111827);
}
.format-guide a { color: var(--primary, #6366f1); text-decoration: none; }
.format-guide a:hover { text-decoration: underline; }
.format-guide code {
    background: var(--surface-alt, #f3f4f6);
    padding: 2px 6px; border-radius: 4px;
    font-family: var(--font-mono, ui-monospace, monospace);
    font-size: 13px;
}
.format-guide pre {
    background: #0f172a; color: #e2e8f0;
    padding: 16px; border-radius: 8px; overflow-x: auto;
    font-size: 13px;
    font-family: var(--font-mono, ui-monospace, monospace);
    line-height: 1.5;
}
.format-guide pre code { background: none; color: inherit; padding: 0; }
.format-guide table {
    width: 100%; border-collapse: collapse;
    margin: 12px 0 20px;
    font-size: 13.5px;
}
.format-guide th, .format-guide td {
    text-align: left; padding: 8px 12px;
    border-bottom: 1px solid var(--border-light, #f3f4f6);
}
.format-guide thead th {
    background: var(--surface-alt, #f9fafb);
    font-weight: 600; font-size: 12px;
    text-transform: uppercase; letter-spacing: 0.4px;
    color: var(--text-secondary, #6b7280);
}
.format-guide ul, .format-guide ol {
    margin: 0 0 12px; padding-left: 24px;
}
.format-guide li { margin-bottom: 6px; }
.format-guide blockquote {
    border-left: 3px solid var(--primary, #6366f1);
    margin: 12px 0; padding: 4px 12px;
    background: var(--surface-alt, #f9fafb);
    border-radius: 0 8px 8px 0;
}
.format-guide .back-link {
    display: inline-flex; align-items: center; gap: 4px;
    color: var(--text-secondary, #6b7280);
    font-size: 13px;
    margin-bottom: 16px;
}
.format-guide .back-link:hover { color: var(--primary, #6366f1); }

/* =====================================================
   Canonical button family (Task 4)
   .btn + .btn-primary | .btn-secondary | .btn-ghost | .btn-danger
                       + .btn-sm | .btn-lg

   Base .btn, .btn-primary, .btn-secondary, .btn-sm come from the
   absorbed style.css block above (no change needed — they already use
   the new tokens via the legacy-alias layer). This section appends
   selector-list aliases for legacy class names (   .btn-secondary-v2, .btn-ghost-v2, .modal-btn) and defines the new
   variants (.btn-ghost, .btn-danger, .btn-lg) that the legacy stylesheet
   didn't ship.

   Aliases removed in Task 16 once templates migrate to canonical names.
   Cascade-order note: this section is the LAST in the file, so it wins
   over both the absorbed legacy rules AND any earlier v2-suffixed rules
   (e.g. .btn-primary-v2 hover color in the absorbed block). Adding
   per-page <style> overrides will still beat this on equal-specificity
   selectors due to source order — Tasks 8–14 strip those.
   ===================================================== */

.btn-primary {
    background-color: var(--ds-primary);
    color: #fff;
    border: 1px solid var(--ds-primary);
    transition: all 0.15s ease;
}
.btn-primary:hover,
.btn-primary-v2:hover,
.modal-btn.primary:hover {
    background-color: var(--ds-primary-dark);
    border-color: var(--ds-primary-dark);
    box-shadow: 0 2px 8px color-mix(in srgb, var(--ds-primary) 30%, transparent);
}

.btn-secondary {
    background-color: var(--surface);
    color: var(--text-primary);
    border: 1px solid var(--border);
}
.btn-secondary:hover,
.btn-secondary-v2:hover,
.modal-btn:not(.primary):not(.danger):hover {
    background-color: var(--border-light);
}

.btn-ghost {
    background-color: transparent;
    color: var(--text-secondary);
    border: 1px solid transparent;
}
.btn-ghost:hover,
.btn-ghost-v2:hover {
    background-color: var(--border-light);
    color: var(--text-primary);
}

/* When the ds.button macro composes variant='ghost' with klass='btn-link'
 * (the 3 auth pages — login_email, password_reset, password_setup —
 * use this combo for "Forgot Password?" / "Back to Login" affordances
 * that should look like links, not buttons), the cascade default would
 * let .btn-ghost (defined later in this file) override .btn-link's
 * blue with text-secondary grey. Boost specificity (0,0,3,0 beats the
 * single-class rules above) to keep the link color in that case.
 * Devin review on PR #375 flagged this contrast regression. */
.btn.btn-ghost.btn-link {
    color: var(--primary);
}
.btn.btn-ghost.btn-link:hover {
    color: var(--primary-dark);
    text-decoration: underline;
    background-color: transparent;
}

.btn-danger {
    background-color: var(--surface);
    color: var(--error);
    border: 1px solid var(--error);
}
.btn-danger:hover,
.modal-btn.danger:hover {
    background-color: var(--error);
    color: #fff;
}

/* `.btn-warning` removed — the single use in admin_marketplaces.html
   (system-confirm modal Confirm button) was destructive in spirit (fans
   out a forced grant to every principal) and now renders via .btn-danger.
   The 4 canonical variants (primary / secondary / ghost / danger) cover
   every honest CTA without inventing a 5th amber-fill axis. */

/* `.btn-required` — amber-disabled affordance for the "already in stack,
   mandated by your org" state on catalog_package_detail + memory_domain_detail.
   Always paired with the `disabled` attribute, so the base `.btn:disabled`
   rule below applies its opacity wash on top of the amber accent. Uses the
   canonical --ds-accent-warn-* vocabulary so a future warn-palette tweak
   propagates here. */
.btn-required {
    background: var(--ds-accent-warn-bg);
    color: var(--ds-accent-warn-ink);
    border: 1px solid var(--ds-accent-warn-line);
    cursor: not-allowed;
}

.btn-lg { padding: 12px 20px; font-size: var(--text-md); }

/* `.info-panel-accent` — emitted by `ds.info_panel_accent(accent='…')`.
   Canonical replacement for `.mp-curator-block`-style aside panels. The
   `--{accent}` modifier supplies the surface tokens. Four canonical
   accents reuse the `--ds-accent-{info,warn,success,danger}-*` family;
   bespoke pages can layer additional modifiers locally. */
.info-panel-accent {
    border: 1px solid var(--ds-border);
    border-left-width: 4px;
    border-radius: 8px;
    padding: 14px 18px;
    background: var(--ds-surface);
    color: var(--ds-text-primary);
}
.info-panel-accent__title {
    font-size: var(--text-md);
    font-weight: 600;
    margin: 0 0 6px;
    color: inherit;
}
.info-panel-accent__body { font-size: var(--text-sm); line-height: 1.55; }
.info-panel-accent--info {
    background: var(--ds-accent-info-bg);
    color: var(--ds-accent-info-ink);
    border-left-color: var(--ds-accent-info-line);
}
.info-panel-accent--warn {
    background: var(--ds-accent-warn-bg);
    color: var(--ds-accent-warn-ink);
    border-left-color: var(--ds-accent-warn-line);
}
.info-panel-accent--success {
    background: var(--ds-accent-success-bg);
    color: var(--ds-accent-success-ink);
    border-left-color: var(--ds-accent-success-line);
}
.info-panel-accent--danger {
    background: var(--ds-accent-danger-bg);
    color: var(--ds-accent-danger-ink);
    border-left-color: var(--ds-accent-danger-line);
}

/* `:disabled` is enough for a <button> — the browser refuses the activation
   itself. `[aria-disabled]` is ADVISORY: it tells a screen reader the control
   is off and stops nothing, so an <a class="btn"> painted at half opacity
   still navigates on click. `pointer-events: none` is what makes the two
   spellings mean the same thing, which is the whole point of them sharing a
   rule. Pair it with `tabindex="-1"` at the call site (as `ds.button` does)
   so the keyboard cannot reach what the pointer cannot. */
.btn:disabled,
.btn[aria-disabled="true"] { opacity: 0.5; cursor: not-allowed; }
.btn[aria-disabled="true"] { pointer-events: none; }

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

/* =====================================================
   Canonical form controls (Task 5)
   .search-input | .filter-bar | .filter-pill | .form-input

   Selector-list aliases for legacy per-page classes (   .marketplaces-search, .kb-search, .filters-card, .pill) so existing
   markup keeps rendering until migration tasks swap them out.
   ===================================================== */

.search-input,
.filter-bar input[type="search"],
.filter-bar input[type="text"],
.filter-bar select {
    height: 36px;
    padding: 0 var(--space-3);
    font-family: var(--font-primary);
    font-size: var(--text-sm);
    color: var(--text-primary);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.search-input::placeholder,
.users-search::placeholder,
.marketplaces-search::placeholder,
.kb-search::placeholder,
.filter-bar input::placeholder { color: var(--text-muted); }

.search-input:focus,
.users-search:focus,
.marketplaces-search:focus,
.kb-search:focus,
.filter-bar input:focus,
.filter-bar select:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: var(--focus-ring);
}

.filter-bar {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
    padding: var(--space-3) var(--space-4);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    margin-bottom: var(--space-4);
}

.filter-bar > .search-input,
.filters-card > .search-input { flex: 1 1 240px; min-width: 200px; }

.filter-pill {
    display: inline-flex;
    align-items: center;
    height: 28px;
    padding: 0 var(--space-3);
    font-family: var(--font-primary);
    font-size: var(--text-sm);
    font-weight: var(--font-medium);
    color: var(--text-secondary);
    background: transparent;
    border: 1px solid var(--border);
    border-radius: var(--radius-full);
    cursor: pointer;
    transition: background var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast);
    text-decoration: none;
}

.filter-pill:hover {
    background: var(--border-light);
    color: var(--text-primary);
}

.filter-pill.is-active,
.filter-pill[aria-pressed="true"] {
    background: var(--primary-light);
    color: var(--primary);
    border-color: var(--primary);
}

/* Form input — sibling of .search-input for forms (multi-line capable). */
.form-input,
input.form-input,
select.form-input,
textarea.form-input {
    width: 100%;
    min-height: 36px;
    padding: var(--space-2) var(--space-3);
    font-family: var(--font-primary);
    font-size: var(--text-sm);
    color: var(--text-primary);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

textarea.form-input {
    min-height: 96px;
    resize: vertical;
    font-family: var(--font-mono);
    line-height: 1.5;
}

textarea.form-textarea {
    width: 100%;
    min-height: 80px;
    padding: var(--space-2) var(--space-3);
    font-family: var(--font-primary);
    font-size: var(--text-sm);
    color: var(--text-primary);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    resize: vertical;
    line-height: normal;
    transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

textarea.form-textarea:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: var(--focus-ring);
}

textarea.form-textarea::placeholder { color: var(--text-muted); }

.form-input:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: var(--focus-ring);
}

.form-input::placeholder { color: var(--text-muted); }

/* chip-input widget — set font on the container so the inner input's
   `font: inherit` inline style picks up the right value */
.chip-input {
    font-family: var(--font-primary);
    font-size: var(--text-sm);
    color: var(--text-primary);
}

/* =====================================================
   Page-header primitive (Task 6)
   .page-header + __main / __title / __subtitle / __actions / __eyebrow
   + variants: .page-header--hero | .page-header--compact
   + .tab-strip (paired tab row for marketplace tabs etc.)
   ===================================================== */

.page-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-4);
    margin: var(--space-6) 0 var(--space-5);
    flex-wrap: wrap;
}

.page-header__main { min-width: 0; flex: 1 1 auto; }

.page-header__title {
    margin: 0;
    font-size: 22px;
    font-weight: var(--font-semibold);
    color: var(--text-primary);
    line-height: 1.2;
}

.page-header__subtitle {
    margin: var(--space-2) 0 0;
    font-size: var(--text-sm);
    color: var(--text-secondary);
    line-height: 1.4;
}

.page-header__eyebrow {
    margin: 0 0 var(--space-2);
    font-size: var(--text-xs);
    font-weight: var(--font-semibold);
    text-transform: uppercase;
    letter-spacing: 0.8px;
    color: var(--text-secondary);
}

.page-header__actions {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
}

/* Plain variant — the INDEX-PAGE head: a name, a sentence, and nothing
   drawn around them.

   This is the treatment /library, /agents and /chats already run by hand
   (`.lib-head`, `.ag-head`, `.ch-head` — a 22–24px title over a muted
   lede capped at 68ch), lifted onto the shared primitive so a page can
   opt in with one variable (`page_hero_plain`) instead of a fourth
   private copy. The lede's own type scale is shared too — see
   `.page-lede` below, which those three heads now ride as well.

   Use it where the page is a WORKSPACE the reader stays in — a list they
   filter, a table they act on, a two-pane editor. A gradient (or, under
   paper, a bordered white card) around the title is chrome the reader
   scrolls past on every visit, and on a page whose body is already made of
   cards it puts a card above the cards. The hero variant below stays right
   where a page is a DESTINATION that has to introduce itself: /home, an
   install walkthrough, a marketing-adjacent surface.

   Only three declarations differ from the base primitive — the type scale,
   the measure, and the link colour (there is no global `a` rule, so an
   unstyled link in the lede renders as UA blue). Everything else is
   inherited, which is what keeps this from becoming a second header
   component: the theme's own `h1` rule still owns the weight and the
   tracking, exactly as it does for the three hand-built heads above. */
.page-header--plain {
    display: block;
    margin: 8px 0 0;
    /* Padding, not margin: the head's clearance must not collapse-adjoin a
       following notice or tab strip, both of which carry their own top
       edge. */
    padding: 0 0 22px;
}

/* ── The page title ───────────────────────────────────────────────────
   The other half of the head, on the same terms as `.page-lede` below:
   one size, named once, for the `<h1>` a page puts above its lede.

   22px — the /library size, the smaller of the two that were in the
   tree. It is chosen as a PAIR with the 13px lede rather than on its
   own: /library sized both together and the result is the head the rest
   of the app is being matched to. 24px was the majority (the plain
   header and every hand-built head but one) and is a one-line change
   here if that ever reverses.

   Only `margin` and `font-size` — weight, tracking and colour stay with
   the theme's own `h1` rule, the same division the plain variant above
   keeps. Layout a particular head needs (a flex row for a badge beside
   the name, `overflow-wrap` for a name that can be one long token)
   stays page-local: it is that page's anatomy, not the type scale. */
.page-title,
.page-header--plain .page-header__title {
    margin: 0;
    font-size: 22px;
    line-height: 1.2;
}

/* ── The page lede ────────────────────────────────────────────────────
   ONE type scale for the sentence under a page title, wherever that
   sentence is written: the shared header's `page_hero_subtitle` on a
   plain-variant page, or a hand-built head's own `<p>`.

   It is the /library scale — 13px, muted, capped at 68ch — because a
   lede is orientation the reader needs once and then scrolls past on
   every later visit, and at body size it competed with the rows the
   page exists to show. It used to be three sizes: 15px `--ds-text-
   secondary` here, 15px in `.ag-head`/`.ch-head`, and — on any page
   that wrote a bare `<p class="lede">` with no rule behind it —
   whatever the UA gave a paragraph, which was the largest of the
   three.

   `.page-lede` is the one name — deliberately NOT an alias on the bare
   `.lede` the hand-built heads used to carry, because /home writes that
   same class for its marketing hero and would have been dragged down to
   13px muted with them. A page head opts in by name; nothing opts in by
   accident.

   No `font-weight`/`font-family` here — a lede inherits the body's, so
   a mid-page section lede that reaches for this class (e.g.
   /marketplace/guide) keeps whatever its section gives it. */
.page-lede,
.page-header--plain .page-header__subtitle {
    margin: 4px 0 0;
    font-size: 13px;
    line-height: 1.45;
    color: var(--ds-text-muted);
    max-width: 68ch;
}
.page-header--plain .page-header__eyebrow { color: var(--ds-text-muted); }
.page-header--plain a {
    color: var(--ds-primary-dark);
    text-decoration: underline;
    text-underline-offset: 2px;
}
.page-header--plain a:hover { color: var(--ds-primary); }

/* Hero variant — gradient background, larger title, light eyebrow.
   Tuned to match the look of the per-page hero /admin/tokens used to
   carry (which analysts already liked): tighter padding, 14px radius,
   soft blue glow shadow.

   NOT the admin default any more — People, Data and Access (and their tab
   siblings) run the `--plain` variant above, because a workspace page is
   not a page that has to introduce itself. This stays for the surfaces
   that do. */
.page-header--hero {
    padding: 28px 32px 24px;
    /* Spans the parent container width. `.container` already provides
       the canonical max-width (1280px) + 32px side gutters, so the
       hero sits flush with the rest of the page content rather than
       self-constraining. Standalone pages without a `.container`
       wrapper still get a sensible width via `.container--wide` /
       `.container--narrow` modifiers on their own wrappers. */
    margin: 0 0 var(--space-5);
    box-sizing: border-box;
    /* Hero gradient — `--ds-hero-bg` / `--ds-hero-bg-deep` flip
       between themes (navy by default, blue under
       `data-theme="blue"`). Markup and class names don't change. */
    background: linear-gradient(135deg, var(--ds-hero-bg) 0%, var(--ds-hero-bg-deep) 100%);
    color: #fff;
    border-radius: 14px;
    /* Shadow tint follows the gradient via `--ds-hero-shadow`. */
    box-shadow: 0 4px 16px var(--ds-hero-shadow);
    position: relative;
    overflow: hidden;
}
.page-header--hero .page-header__main { color: inherit; }
.page-header--hero .page-header__title {
    font-size: 28px;
    font-weight: 600;
    letter-spacing: -0.01em;
    color: #fff;
    margin: 0 0 6px;
}
.page-header--hero .page-header__subtitle {
    color: rgba(255, 255, 255, 0.85);
    font-size: 13.5px;
    margin: 6px 0 0;
    max-width: 720px;
}
.page-header--hero .page-header__eyebrow {
    /* Eyebrow accent follows the theme: mint-green on navy
       (default) or translucent white on blue. `--ds-hero-eyebrow`
       is set per-theme in design-tokens.css. */
    color: var(--ds-hero-eyebrow);
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 1.2px;
    margin: 0 0 6px;
}
.page-header--hero .page-header__actions a,
.page-header--hero .page-header__actions button {
    color: #fff;
}
/* Any anchor anywhere inside the gradient hero — default `--primary`
   blue would disappear on the blue background, so force a white,
   underlined link with full contrast. Applies without needing a
   per-page class. */
.page-header--hero a {
    color: #fff;
    text-decoration: underline;
    text-underline-offset: 2px;
}
.page-header--hero a:hover { color: rgba(255, 255, 255, 0.85); }
/* `<code>` chips inside the gradient hero — translucent white pill
   with white amber-yellow text so they read as code samples
   against the navy background. The global `code` rule sets a
   dark `color: var(--text-primary)` that turned chips into dark
   text on a faint white pill (invisible on navy). */
.page-header--hero code {
    background: rgba(255, 255, 255, 0.18);
    border: 1px solid rgba(255, 255, 255, 0.22);
    color: #ffd866;
    font-family: var(--font-mono);
    font-size: 12px;
    padding: 1px 6px;
    border-radius: 4px;
}
@media (max-width: 640px) {
    .page-header--hero { padding: 24px 20px 20px; border-radius: 12px; }
    .page-header--hero .page-header__title { font-size: 22px; }
}

/* Compact variant — for dense admin index pages that have many controls
   already and don't need vertical generosity. Smaller title, tighter
   spacing, no border. */
.page-header--compact {
    margin: var(--space-4) 0 var(--space-3);
}
.page-header--compact .page-header__title { font-size: 18px; }

/* Tab strip — the secondary row pattern used by /marketplace?tab=… and
   similar tabbed pages.

   Its rules moved to css/components.css, where the flow variant (Data's
   pipeline) and the Library's strip could join them: four copies of one look
   had drifted onto two token sets, and this was the copy on the legacy ones
   (`--primary`, `--text-secondary`, `--border`), which is why People and
   Access did not match Data. Nothing here now — the selector is defined once,
   on --ds- tokens, in the global component sheet. */

/* =====================================================
   Data display + feedback primitives (Task 7)
   .data-table (+ --compact) | .empty-state | .toast | .stat-card

   Selector-list aliases for legacy per-page tables (   .gp-table, .marketplaces-table, .audit-table) so existing markup
   keeps rendering until migration tasks swap them out.
   ===================================================== */

.data-table,
.ad-table,
.ea-table,
.md-table,
.members-table,
.obs-table,
.overview-stats-table,
.registry-table,
.sample-table,
.sched-table,
.sess-table,
.sub-table,
.subs-table,
.ud-table {
    width: 100%;
    border-collapse: collapse;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    overflow: hidden;
    font-variant-numeric: tabular-nums;
}

.data-table thead,
.ad-table thead,
.ea-table thead,
.md-table thead,
.members-table thead,
.obs-table thead,
.overview-stats-table thead,
.registry-table thead,
.sample-table thead,
.sched-table thead,
.sess-table thead,
.sub-table thead,
.subs-table thead,
.ud-table thead {
    background: var(--border-light);
}

.data-table th,
.ad-table th,
.ea-table th,
.md-table th,
.members-table th,
.obs-table th,
.overview-stats-table th,
.registry-table th,
.sample-table th,
.sched-table th,
.sess-table th,
.sub-table th,
.subs-table th,
.ud-table th {
    padding: var(--space-3) var(--space-4);
    text-align: left;
    font-size: var(--text-xs);
    font-weight: var(--font-semibold);
    text-transform: uppercase;
    letter-spacing: 0.4px;
    color: var(--text-secondary);
    border-bottom: 1px solid var(--border);
}

.data-table td,
.ad-table td,
.ea-table td,
.md-table td,
.members-table td,
.obs-table td,
.overview-stats-table td,
.registry-table td,
.sample-table td,
.sched-table td,
.sess-table td,
.sub-table td,
.subs-table td,
.ud-table td {
    padding: var(--space-3) var(--space-4);
    font-size: var(--text-sm);
    color: var(--text-primary);
    border-bottom: 1px solid var(--border-light);
    vertical-align: middle;
}

.data-table tbody tr:last-child td,
.ad-table tbody tr:last-child td,
.ea-table tbody tr:last-child td,
.md-table tbody tr:last-child td,
.members-table tbody tr:last-child td,
.obs-table tbody tr:last-child td,
.overview-stats-table tbody tr:last-child td,
.registry-table tbody tr:last-child td,
.sample-table tbody tr:last-child td,
.sched-table tbody tr:last-child td,
.sess-table tbody tr:last-child td,
.sub-table tbody tr:last-child td,
.subs-table tbody tr:last-child td,
.ud-table tbody tr:last-child td {
    border-bottom: none;
}

.data-table tbody tr:hover,
.ad-table tbody tr:hover,
.ea-table tbody tr:hover,
.md-table tbody tr:hover,
.members-table tbody tr:hover,
.obs-table tbody tr:hover,
.overview-stats-table tbody tr:hover,
.registry-table tbody tr:hover,
.sample-table tbody tr:hover,
.sched-table tbody tr:hover,
.sess-table tbody tr:hover,
.sub-table tbody tr:hover,
.subs-table tbody tr:hover,
.ud-table tbody tr:hover {
    background: var(--border-light);
}

/* Compact modifier — for dense lists like /admin/activity (audit log). */
.data-table--compact th,
.data-table--compact td {
    padding: var(--space-2) var(--space-3);
    font-size: var(--text-xs);
}

/* =====================================================
   .empty-state — for "no records" / "no results" panels
   ===================================================== */

.empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-8) var(--space-6);
    text-align: center;
    background: var(--surface);
    border: 1px dashed var(--border);
    border-radius: var(--radius-lg);
    color: var(--text-secondary);
}

.empty-state__icon {
    font-size: 32px;
    opacity: 0.5;
    line-height: 1;
}

.empty-state__title {
    margin: 0;
    font-size: var(--text-md);
    font-weight: var(--font-medium);
    color: var(--text-primary);
}

.empty-state__description {
    margin: 0;
    max-width: 480px;
    font-size: var(--text-sm);
    line-height: 1.5;
}

.empty-state__actions {
    margin-top: var(--space-3);
    display: flex;
    gap: var(--space-2);
    flex-wrap: wrap;
    justify-content: center;
}

/* =====================================================
   .toast — global notification surface
   Paired with window.appToast({kind, msg, timeout}) in app.js
   ===================================================== */

.toast-container {
    position: fixed;
    right: var(--space-5);
    bottom: var(--space-5);
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    z-index: 9999;
    pointer-events: none;
}

/* Surface, border, radius, type and the icon layout come from
   `.notice--floating` (app.js composes both classes). What is left is what
   makes it a TOAST: it can be clicked away, it is capped so a long receipt
   wraps instead of spanning the window, and it has a floor so a two-word
   receipt is not a tiny chip. */
.toast {
    pointer-events: auto;
    min-width: 240px;
    max-width: 380px;
    margin: 0;
    cursor: pointer;
    animation: toast-in 200ms ease;
}

/* A receipt can be a sentence ("Loaded SKILL.md — filled in name and
   description"), so it wraps rather than clipping. */
.toast-msg { flex: 1; min-width: 0; overflow-wrap: anywhere; }

/* Dismiss. Low-contrast until hovered — present, never louder than the
   message it sits beside. */
/* The dismiss control is styled with `.notice__x` above (one rule, both
   hooks). The four `border-left: 3px solid` tone stripes that used to sit
   here are gone with them: the stripe was this component's version of
   repainting per outcome, and the glyph says the same thing without turning
   a receipt into an alarm. */

@keyframes toast-in {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
    .toast { animation: none; }
}

/* =====================================================
   .stat-card — for dashboard metric tiles
   ===================================================== */

.stat-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    padding: var(--space-4);
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    min-width: 0;
}

.stat-card__label {
    font-size: var(--text-xs);
    font-weight: var(--font-semibold);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: var(--text-secondary);
}

.stat-card__value {
    font-size: 28px;
    font-weight: var(--font-semibold);
    color: var(--text-primary);
    font-variant-numeric: tabular-nums;
    line-height: 1.1;
}

.stat-card__hint {
    font-size: var(--text-xs);
    color: var(--text-muted);
}

.stat-card__icon {
    font-size: 18px;
    opacity: 0.6;
}

/* Variant: with primary accent. */
.stat-card--accent {
    border-color: var(--primary);
    background: var(--primary-light);
}
.stat-card--accent .stat-card__value { color: var(--primary); }

/* Stat row container — gap-spaced grid for the dashboard tile row. */
.stat-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
    gap: var(--space-3);
    margin-bottom: var(--space-5);
}

/* ═══════════════════════════════════════════════════════════════════════
   Design-system additions driven by .interface-design/system.md
   ─────────────────────────────────────────────────────────────────────
   Net-new canonical class families consumed by the `ds.button`,
   `ds.panel`, `ds.tabs`, and `ds.table` macros in _components.html.
   Append-only; nothing above this block was modified. Order:
     1. `.btn--icon`           — icon-only square modifier
     2. `.ds-card` family      — canonical card/panel surface + accent
     3. `.ds-table` family     — canonical table + 15 legacy aliases
     4. `.badge--*` modifiers  — accent vocabulary for badges
     5. `.code-inline`         — mint inline-code chip
   ═══════════════════════════════════════════════════════════════════════ */

/* ─── Icon-only button modifier ────────────────────────────────────────
   Squares the button so an icon glyph (✕, ↡, etc.) sits centred without
   the default 10×20 padding making it rectangular. Composes with any
   .btn-* variant: `.btn .btn-ghost .btn--icon .btn-sm`. */
.btn--icon {
    width: 32px;
    height: 32px;
    padding: 0;
    flex-shrink: 0;
}
.btn--icon.btn-sm { width: 28px; height: 28px; }

/* ─── Cards — canonical .ds-card family ───────────────────────────────
   `.ds-card`            white surface, 1px border, 8px radius, 24px pad,
                         whisper shadow. The standard admin/data panel.
   `.ds-card__title`     <h3> styling inside the card.
   `.ds-card--accent`    adds a 4px solid left border in `--ds-card-accent`,
                         defaults to `--ds-border`. Pair with an accent
                         helper modifier (`--info`/`--success`/`--warn`/
                         `--danger`) for the unified status vocabulary.
   `.ds-card[data-clickable]`  opt-in hover lift (cards stay still by
                         default — calm). */
.ds-card {
    background: var(--ds-surface);
    border: 1px solid var(--ds-border);
    border-radius: var(--radius-lg);
    padding: var(--space-6);
    box-shadow: var(--shadow-sm);
    margin-bottom: 0;
}
.ds-card > :first-child { margin-top: 0; }
.ds-card > :last-child  { margin-bottom: 0; }

.ds-card h2,
.ds-card h3,
.ds-card__title {
    margin: 0 0 var(--space-3);
    font-size: var(--text-md);
    font-weight: var(--font-semibold);
    color: var(--ds-text-primary);
    line-height: 1.3;
}

.ds-card p,
.ds-card__body {
    margin: 0 0 var(--space-3);
    color: var(--ds-text-secondary);
    line-height: 1.55;
}

.ds-card--accent {
    --ds-card-accent: var(--ds-border);
    border-left: 4px solid var(--ds-card-accent);
}
.ds-card--info    { --ds-card-accent: var(--ds-accent-info-line); }
.ds-card--success { --ds-card-accent: var(--ds-accent-success-line); }
.ds-card--warn    { --ds-card-accent: var(--ds-accent-warn-line); }
.ds-card--danger  { --ds-card-accent: var(--ds-accent-danger-line); }

.ds-card[data-clickable] {
    cursor: pointer;
    text-decoration: none;
    color: inherit;
    transition:
        border-color var(--transition-fast),
        box-shadow var(--transition-fast),
        transform var(--transition-fast);
}
.ds-card[data-clickable]:hover {
    border-color: var(--ds-text-muted);
    box-shadow: var(--shadow-md);
    transform: translateY(-1px);
}
.ds-card[data-clickable]:focus-visible {
    outline: var(--ds-focus-outline);
    outline-offset: var(--ds-focus-outline-offset);
}

/* ─── Tables — canonical .ds-table family ─────────────────────────────
   `.ds-table`            border-collapse, 1px row borders, 8px radius
                          on the wrapping border, sticky header,
                          `--ds-surface-dim` row hover (matches button
                          hover), tabular-nums.
   `.ds-table--dense`     reduced cell padding for reference tables.
   `.ds-table--zebra`     optional row striping for long lists.
   `td.num` / `th.num`    right-aligned monospace column.
   Legacy aliases below   so admin pages can keep their existing class
                          names while inheriting the canonical look. */
.ds-table,
:is(.data-table, .ad-table, .ea-table, .md-table, .members-table,
    .obs-table, .overview-stats-table, .registry-table, .sample-table,
    .sched-table, .sess-table, .sub-table, .subs-table, .ud-table,
    .versions) {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--text-sm);
    background: var(--ds-surface);
    border: 1px solid var(--ds-border);
    border-radius: var(--radius-lg);
    overflow: hidden;
    font-variant-numeric: tabular-nums;
}
.ds-table thead th,
:is(.data-table, .ad-table, .ea-table, .md-table, .members-table,
    .obs-table, .overview-stats-table, .registry-table, .sample-table,
    .sched-table, .sess-table, .sub-table, .subs-table, .ud-table,
    .versions) thead th {
    position: sticky;
    top: 0;
    z-index: 1;
    text-align: left;
    padding: var(--space-3) var(--space-4);
    background: var(--ds-surface-dim);
    border-bottom: 1px solid var(--ds-border);
    font-weight: var(--font-semibold);
    color: var(--ds-text-muted);
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.4px;
    white-space: nowrap;
}
.ds-table tbody td,
:is(.data-table, .ad-table, .ea-table, .md-table, .members-table,
    .obs-table, .overview-stats-table, .registry-table, .sample-table,
    .sched-table, .sess-table, .sub-table, .subs-table, .ud-table,
    .versions) tbody td {
    padding: var(--space-2) var(--space-4);
    border-bottom: 1px solid var(--border-light);
    vertical-align: top;
    color: var(--ds-text-primary);
}
.ds-table tbody tr:last-child td,
:is(.data-table, .ad-table, .ea-table, .md-table, .members-table,
    .obs-table, .overview-stats-table, .registry-table, .sample-table,
    .sched-table, .sess-table, .sub-table, .subs-table, .ud-table,
    .versions) tbody tr:last-child td {
    border-bottom: none;
}
.ds-table tbody tr:hover,
:is(.data-table, .ad-table, .ea-table, .md-table, .members-table,
    .obs-table, .overview-stats-table, .registry-table, .sample-table,
    .sched-table, .sess-table, .sub-table, .subs-table, .ud-table,
    .versions) tbody tr:hover {
    background: var(--ds-surface-dim);
}
.ds-table th.num, .ds-table td.num,
:is(.data-table, .ad-table, .ea-table, .md-table, .members-table,
    .obs-table, .overview-stats-table, .registry-table, .sample-table,
    .sched-table, .sess-table, .sub-table, .subs-table, .ud-table,
    .versions) th.num,
:is(.data-table, .ad-table, .ea-table, .md-table, .members-table,
    .obs-table, .overview-stats-table, .registry-table, .sample-table,
    .sched-table, .sess-table, .sub-table, .subs-table, .ud-table,
    .versions) td.num {
    text-align: right;
    font-family: var(--ds-font-mono);
    font-variant-numeric: tabular-nums;
}

.ds-table--dense thead th,
.data-table--compact thead th { padding: var(--space-1) var(--space-3); }
.ds-table--dense tbody td,
.data-table--compact tbody td  { padding: 6px var(--space-3); }

.ds-table--zebra tbody tr:nth-child(even) td {
    background: var(--ds-surface-dim);
}

/* `.data-table-wrap` — the wrapper ten admin pages put around a `.data-table`
   (semantic sources, users, sync, marketplaces, mcp_sources,
   knowledge_digests, linked_apps, initial_workspace, data_apps, tables) so a
   wide/many-column table scrolls horizontally instead of taking the whole
   page with it. No CSS rule ever backed the class (#1707 A6), so the
   overflow silently landed on the page instead of the table.
   `admin_tables.html` is the odd one out: its wrap holds a JS-hydrated
   layout host (`#adminTablesLayoutRows`), not a literal `<table
   class="data-table">` markup — the hydrator injects one once
   `/api/admin/registry` resolves. It still gets `overflow-x: auto` for
   free from this same rule once that happens.

   Decision (PR #1823 review): overflow-x: auto makes overflow-y compute to
   auto too, which turns the wrap into a NEW scrolling ancestor closer than
   the viewport — so `.data-table thead th`'s `position: sticky` (below)
   would silently re-target that ancestor instead. The wrap is never given a
   height cap, so it never actually scrolls vertically, meaning the header
   would stop sticking at all instead of degrading gracefully, and the same
   ancestor change is what triggers the well-known "rounded corners vanish
   under a stuck thead" clipping bug the `.cloud-chat-table-wrap` recipe in
   chat.css works around for its own tables. Rather than adopt that recipe's
   border-transfer here too (all ten pages would need touching, and
   `admin_tables.html`'s wrap would gain a border around its
   `#adminTablesLayoutRows` "Loading tables…" placeholder even before a
   real table exists inside it — a visual change out of scope for this
   fix), sticky is disabled outright for any thead wrapped in
   `.data-table-wrap`: an unambiguous "off" instead of a silently
   non-functional "on". A bounded,
   internally-scrolling table with real in-wrap sticky is a legitimate
   follow-up for a page that wants it, not a rule that belongs on every
   consumer of this class today. */
.data-table-wrap {
    overflow-x: auto;
    /* A visible scrollbar, not an overlay one. `auto` still means "only when
       it overflows", so a table that fits draws nothing — but the ones that
       DO overflow had no affordance at all on macOS, where the overlay bar is
       painted only while the pointer is already scrolling. On Marketplaces
       that hid the row's own verbs (Sync now / Details / Edit) past the right
       edge with nothing on screen to say they were there (#1956 item 8).

       Deliberately NOT paired with the standard `scrollbar-width` /
       `scrollbar-color`: Chrome honours one model or the other, and setting
       `scrollbar-width` makes it drop these `::-webkit-*` rules and go back
       to an overlay bar, which is the bug. Firefox and the rest fall through
       to their own always-visible default. */
}
.data-table-wrap::-webkit-scrollbar { height: 8px; }
.data-table-wrap::-webkit-scrollbar-track { background: transparent; }
.data-table-wrap::-webkit-scrollbar-thumb {
    background: var(--ds-border);
    border-radius: 4px;
}
.data-table-wrap::-webkit-scrollbar-thumb:hover { background: var(--ds-text-disabled); }

/* Firefox gets the same affordance through the STANDARD properties, gated so
   Chrome never sees them. The note above is right that pairing the two
   unconditionally is a regression — Chrome honours one model or the other and
   `scrollbar-width` makes it drop the `::-webkit-*` rules for an overlay bar.
   It is wrong that Firefox falls through to an always-visible default: on
   macOS Firefox follows the OS "show scroll bars automatically" setting, so
   on a trackpad it paints an overlay bar too, and a table without pinned
   actions hides columns with nothing on screen to say so (Devin Review on
   #1989).

   `@supports not selector(::-webkit-scrollbar)` is the discriminator: engines
   that implement the pseudo-element skip this block entirely and keep the
   rules above, and the ones that do not — Firefox — get the standard pair. */
@supports not selector(::-webkit-scrollbar) {
    .data-table-wrap {
        scrollbar-width: thin;
        scrollbar-color: var(--ds-border) transparent;
    }
}
.data-table-wrap thead th {
    position: static;
}

/* ── "There is more this way" — the cue AT the cut ────────────────────────
   The scrollbar above is an affordance in the wrong PLACE: it sits under the
   last row, so on a twelve-row table it is 400px below the reader's eye and
   on a long one it is off-screen. A table cut mid-column then looks
   complete — /admin/data-packages at a 900px window simply had no "Shared
   with", with nothing near the cut to say one had been taken away.

   `js/table_scroll_cue.js` sets `is-cue-left` / `is-cue-right` on the wrap
   for whichever side still has content behind it (globally, no page opt-in);
   these rules are what those classes draw. Two mechanisms, because the two
   edges are not symmetrical:

   * the LEFT edge, and the right edge of a table with no pinned column, get
     a fade — the mask is in the wrap's own box coordinates, so it stays at
     the edge while the content scrolls under it, and it needs no extra
     element inside a scroll container (where an absolutely-positioned one
     would scroll away and a sticky pseudo-element is not reliable);
   * a table WITH `.data-table--pinned-actions` must not fade its right edge:
     that is where the pin holds the row's verbs, and a mask applies to
     sticky children too — it would fade out exactly the button the pin
     exists to keep. That side gets a shadow under the pinned column instead,
     which is the same statement ("something is hidden beneath this") in the
     one form that leaves the button legible.

   One mask declaration driven by two custom properties rather than four
   selector combinations: a side that is not cut resolves to `0px`, which
   puts both of that side's colour stops at the same offset and draws no
   fade. The selector still excludes a pinned table's right-only state
   outright, so a wrap with nothing to fade carries no mask — and no
   compositing layer — rather than an inert one. */
.data-table-wrap {
    --dtw-fade-left: 0px;
    --dtw-fade-right: 0px;
}
.data-table-wrap.is-cue-left { --dtw-fade-left: 56px; }
.data-table-wrap.is-cue-right:not(:has(.data-table--pinned-actions)) {
    --dtw-fade-right: 56px;
}
.data-table-wrap.is-cue-left,
.data-table-wrap.is-cue-right:not(:has(.data-table--pinned-actions)) {
    -webkit-mask-image: linear-gradient(to right,
        transparent 0, black var(--dtw-fade-left),
        black calc(100% - var(--dtw-fade-right)), transparent 100%);
            mask-image: linear-gradient(to right,
        transparent 0, black var(--dtw-fade-left),
        black calc(100% - var(--dtw-fade-right)), transparent 100%);
}

/* ── The pinned column's own cue, at the weight a frozen pane wants ──────
   Three passes got this wrong in both directions, so the reasoning is worth
   keeping. A `-8px` spread against a 12px blur painted a ~4px feather nobody
   could see. Answering that with 42% ink and a 34% border painted a grey
   slab down the middle of a white table — heavier than every rule and
   divider around it, which is not "visible", it is loud.

   What a frozen column wants is PRECISION, not weight: one crisp hairline
   at a step above the table's own dividers, and a narrow, soft shading to
   its right so the edge reads as an edge rather than another cell border.
   The tones sit between `--ds-border` and `--ds-text-muted` — visible
   against surface at any width, quiet enough that the eye goes to the rows.

   The inset layer is the one that always works: the outer shadow and the
   slide-under band below both paint over the NEIGHBOURING column, so they
   are only as visible as whatever is in it, and the width this was reported
   from had Category next door — "—" and one short word per row, i.e. white
   fading into white. Inset paints on the pinned column's own background, so
   it survives a blank neighbour. It is narrow (6px) and light on purpose:
   its job is to define the edge, not to shade the column. */
.data-table-wrap.is-cue-right .data-table--pinned-actions tbody td:last-child:not([colspan]),
.data-table-wrap.is-cue-right .data-table--pinned-actions thead th:last-child {
    box-shadow:
        inset 6px 0 6px -6px color-mix(in srgb, var(--ds-text-muted) 26%, transparent),
        -8px 0 10px -8px color-mix(in srgb, var(--ds-text-primary) 14%, transparent);
    /* One step above the table's own dividers — enough to read as the edge
       of a frozen pane, not enough to compete with the header rule. */
    border-left-color: color-mix(in srgb, var(--ds-text-muted) 42%, transparent);
}

/* The slide-under band — the pretty half, where there is content to fade.
   A shadow says "raised", which a reader can take for one more piece of
   chrome; what says "the table continues beneath this" is seeing the
   neighbouring column's text DISSOLVE into the pinned column's own
   background, the way a frozen column reads in a spreadsheet. It is not
   sufficient on its own — a blank neighbour has nothing to dissolve, which
   is what the edge above is for — but where there IS text it is the part
   that reads as designed rather than as an effect.

   It has to be a pseudo-element on the pinned cell rather than a mask on the
   wrap: a mask would take the button with it (see the note above the
   `is-cue-*` mask rules). The cell is `position: sticky`, which is a
   positioned box, so `right: 100%` parks the band immediately to its left
   and it travels with the pin — down every row, at whatever scroll offset,
   which is the whole point when the scrollbar is a screen and a half below.

   `--dtw-pin-bg` is set beside each of the three background declarations
   above, so the band fades to the cell's ACTUAL colour in `thead`, at rest,
   and under the cursor — a band that guessed would show the guess as a seam
   the moment anyone hovered a row. */
.data-table-wrap.is-cue-right .data-table--pinned-actions tbody td:last-child:not([colspan])::before,
.data-table-wrap.is-cue-right .data-table--pinned-actions thead th:last-child::before {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    right: 100%;
    width: 24px;
    background: linear-gradient(to left,
        var(--dtw-pin-bg, var(--ds-surface)),
        color-mix(in srgb, var(--dtw-pin-bg, var(--ds-surface)) 0%, transparent));
    /* Decoration only — the cell under it stays clickable. */
    pointer-events: none;
}

/* ── A pinned actions column — `.data-table--pinned-actions` ──────────────
   Opt-in, on the TABLE, for the admin tables whose last column is the row's
   own verbs (Sync now · Details · Edit, the `⋯` menu). Those are the one
   thing a reader cannot recover by scrolling to a different column: a
   destination has a header to scan back to, an action that is off-screen is
   simply unavailable, and on Marketplaces they were the first thing the
   overflow took (#1956 item 8).

   Sticky HORIZONTALLY only. The sibling rule above disables the sticky
   `thead` for everything in a `.data-table-wrap`, for the reason its own
   note gives — the wrap is an unbounded scroll container, so a vertically
   stuck header would silently re-target it and stop working. Nothing here
   revisits that: `right` binds to the wrap's horizontal axis, which is the
   one that actually scrolls, and `top` is never set.

   Opt-in rather than `td:last-child` everywhere, because "the last column"
   is a count, not a meaning — several of these tables end on a timestamp or
   a status, and pinning those would just narrow the table for nothing.

   The background is what makes it a pin instead of a transparent overlap, so
   it has to track the row's own: surface at rest, the shared hover tint on
   hover, and the header band in `thead`. The left hairline is deliberate and
   permanent — it marks the column as pinned, and a divider that appeared
   only once you had already scrolled would arrive after the moment it was
   needed. */
/* `clip`, not the `hidden` the base rule sets for the rounded corners.
   `overflow: hidden` gives the TABLE a scrolling box, and a sticky cell is
   constrained by the nearest ancestor that has one — so the cells below were
   `position: sticky` against a box that never scrolls, which is a no-op, and
   they rode off the right edge exactly as before. `clip` clips identically
   (the corners still work) but establishes no scrolling box, so the pin
   resolves against `.data-table-wrap`, the element that actually scrolls.
   A browser without `clip` drops this line, keeps `hidden`, and gets today's
   unpinned behaviour rather than a broken one. */
.data-table--pinned-actions { overflow: clip; }

/* `:not([colspan])` excludes the full-width cells these tables use for their
   own empty and loading states — "No matches for …", "Loading tables…". A
   `<td colspan="8">` is also a `td:last-child`, and while it cannot actually
   travel (its sticky rect is the row it already fills) it would still draw
   the pinned column's divider as a stray hairline down the left edge of every
   such row.

   `top: auto` because the reset above only ever overrode `position`: the
   canonical `.data-table th { top: 0 }` is still in the cascade, and
   re-establishing `position: sticky` here at a higher specificity would
   quietly re-arm the vertical stickiness that reset exists to prevent. It is
   inert only while no `.data-table-wrap` has a height cap — which is a fact
   about today, not a guarantee. */
.data-table--pinned-actions tbody td:last-child:not([colspan]),
.data-table--pinned-actions thead th:last-child {
    position: sticky;
    right: 0;
    top: auto;
    border-left: 1px solid var(--ds-border);
}
.data-table--pinned-actions tbody td:last-child:not([colspan]) {
    background: var(--ds-surface);
    --dtw-pin-bg: var(--ds-surface);
}
/* `--ds-surface-dim`, the token the header band itself uses — NOT
   `--ds-border-light`. The two are the same only in the base `:root`; in
   paper (the default) and in dark they differ, and this rule outranks the
   band's own, so the wrong one shows as an off-tint block at the end of the
   header row. Most visible on /admin/users, where that cell's label is
   screen-reader-only and the tint is all there is to see. */
.data-table--pinned-actions thead th:last-child {
    background: var(--ds-surface-dim);
    --dtw-pin-bg: var(--ds-surface-dim);
}
/* The hovered row paints `--ds-surface-dim`, not the `--border-light` the
   rule further up this file declares — measured in the browser, because a
   pinned cell that guesses its neighbour's tint shows the guess as a seam
   down the edge of the column the moment anyone hovers a row. */
.data-table--pinned-actions tbody tr:hover td:last-child:not([colspan]) {
    background: var(--ds-surface-dim);
    --dtw-pin-bg: var(--ds-surface-dim);
}

/* ─── Badges — accent vocabulary ──────────────────────────────────────
   The canonical .badge above gets neutral chrome (chip on
   `--ds-surface-dim`). These modifiers add the cross-pattern accent
   vocabulary so a "success" badge reads identically to a
   `.ds-card--success` accent and a `.flash-success`. */
.badge--info {
    background: var(--ds-accent-info-bg);
    color: var(--ds-accent-info-ink);
}
.badge--success {
    background: var(--ds-accent-success-bg);
    color: var(--ds-accent-success-ink);
}
.badge--warn {
    background: var(--ds-accent-warn-bg);
    color: var(--ds-accent-warn-ink);
}
.badge--danger {
    background: var(--ds-accent-danger-bg);
    color: var(--ds-accent-danger-ink);
}

/* ─── Inline-code chip — `.code-inline` ──────────────────────────────
   Mint chip applied to `<code>` outside of `<pre>` blocks. Replaces
   the legacy grey-blue inline-code styling. Used by `ds.code-inline`
   class callsites; bare `<code>` keeps its existing rules from
   elsewhere in this stylesheet (those are scoped to specific
   parents so they don't conflict). */
.code-inline {
    display: inline-block;
    padding: 1px 6px;
    background: var(--ds-primary-light);
    color: var(--ds-primary-dark);
    border-radius: var(--radius-sm);
    font-family: var(--ds-font-mono);
    font-size: 0.9em;
}

/* =====================================================
   /news page — page chrome
   body.news-page comes from news.html's `body_attrs`
   block, so these rules only apply there — and they are
   in effect at first paint. Do not move that class to a
   DOMContentLoaded handler: the padding below differs
   from the shell's, so the page would shift once loaded.
   ===================================================== */
body.news-page { background: var(--background); }
/* max-width intentionally omitted — the shell's `--width-app` is already
   1280px, and restating it here only invites drift from the canonical value. */
body.news-page .container { padding: 0 var(--space-8) var(--space-10); }
body.news-page main { color: var(--text-primary); line-height: 1.6; }

body.news-page .news-card {
    margin-top: var(--space-7);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-xl);
    padding: var(--space-8) var(--space-9);
}
body.news-page .news-meta {
    font-size: var(--text-sm);
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.6px;
    margin-bottom: var(--space-4);
}
body.news-page .news-intro {
    font-size: var(--text-md);
    color: var(--text-secondary);
    line-height: 1.55;
    margin-bottom: 22px;
    padding-bottom: 22px;
    border-bottom: 1px solid var(--border-light);
}
body.news-page .news-intro p { margin: 0 0 8px; }
body.news-page .news-intro p:last-child { margin-bottom: 0; }

body.news-page .news-empty {
    text-align: center;
    padding: var(--space-12) var(--space-6);
    color: var(--text-secondary);
}
body.news-page .news-empty h2 {
    margin-bottom: var(--space-2);
    color: var(--text-primary);
}

/* =====================================================
   /me/profile page — read-only account view
   ===================================================== */
.container:has(.profile-page) { max-width: 1100px; }
.profile-page {
    max-width: 1100px;
    margin: 0 auto;
    padding: var(--space-7) var(--space-2) var(--space-10);
    box-sizing: border-box;
    font-family: var(--font-primary);
}
@media (max-width: 720px) {
    .profile-page { padding: 20px 0 var(--space-8); }
}
.section-card {
    border-radius: var(--radius-xl);
    padding: var(--space-5) var(--space-6);
    margin-bottom: var(--space-4);
}
.section-card h3 {
    margin: 0 0 14px;
    font-size: var(--text-sm);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    color: var(--text-secondary);
}
details.section-card > summary {
    font-size: var(--text-sm);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    color: var(--text-secondary);
    cursor: pointer;
}
.account-grid {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 10px 18px;
    font-size: var(--text-base);
}
.account-grid .k {
    color: var(--text-secondary);
    font-size: var(--text-sm);
    text-transform: uppercase;
    letter-spacing: 0.4px;
    font-weight: 600;
    align-self: center;
}
.account-grid .v {
    color: var(--text-primary);
    font-weight: 500;
    word-break: break-word;
}
.groups-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}
.group-row {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 10px 14px;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--background);
}
.group-row .group-name {
    font-size: var(--text-base);
    font-weight: 600;
    color: var(--text-primary);
    min-width: 0;
    flex: 0 1 auto;
}
.group-row .group-id {
    font-family: var(--font-mono);
    font-size: var(--text-sm);
    color: var(--text-secondary);
    word-break: break-all;
    flex: 1 1 auto;
}
.role-chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1p5, 6px);
    margin: 0 0 4px;
}
.role-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1p5, 6px);
    padding: 4px 10px;
    border-radius: var(--radius-full);
    font-family: var(--font-mono);
    font-size: var(--text-sm);
    font-weight: 600;
    background: color-mix(in srgb, var(--ds-primary) 10%, transparent);
    color: var(--primary);
    border: 1px solid color-mix(in srgb, var(--ds-primary) 25%, transparent);
}
.role-chip.is-core { background: var(--ds-accent-success-bg); color: var(--ds-accent-success-ink); border-color: var(--ds-accent-success-line); }
.grant-list, .group-roles-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}
.grant-row, .group-roles-row {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: 10px 14px;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--background);
    flex-wrap: wrap;
}
.grant-row .grant-key, .group-roles-row .group-roles-name {
    font-family: var(--font-mono);
    font-size: 13px;
    font-weight: 600;
    color: var(--text-primary);
}
.group-roles-row .group-roles-name { font-family: var(--font-primary); }
.grant-row .grant-source {
    font-size: 11.5px;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    padding: 2px var(--space-2);
    border-radius: var(--radius-sm);
    background: rgba(107, 114, 128, 0.12);
    color: var(--text-secondary);
    font-weight: 600;
}
.grant-row .grant-source.source-direct { background: color-mix(in srgb, var(--ds-primary) 12%, transparent); color: var(--primary); }
.grant-row .grant-source.source-auto { background: rgba(245, 158, 11, 0.12); color: #b45309; }
.grant-row .grant-meta {
    font-size: var(--text-sm);
    color: var(--text-secondary);
    margin-left: auto;
}
.group-roles-row .group-roles-arrow { color: var(--text-secondary); font-weight: 400; }
.group-roles-row .role-chips { margin: 0; }
.empty-state {
    padding: 20px 0 4px;
    color: var(--text-secondary);
    font-size: var(--text-base);
    line-height: 1.55;
}
.empty-state .empty-title {
    font-weight: 600;
    color: var(--text-primary);
    margin-bottom: 4px;
}
.effective-help {
    margin-top: var(--space-3);
    font-size: 12.5px;
    color: var(--text-secondary);
    line-height: 1.55;
}

/* Group chip — semantic role pill used on /me/profile and /admin/users.
   Name-match wins: Admin/Everyone keep their canonical color even when
   their server-side origin is google_sync.

   is-admin/is-everyone/is-google_sync ride the --ds-accent-* and
   --ds-border-light family (dark-theme safe, #1625). The base + is-custom violet pair is left
   as raw hex on purpose: no --ds-* token represents a neutral "other/custom
   group" violet without borrowing --ds-kind-agent (entity-kind semantic) or
   --ds-readonly (sharing-state semantic) — either would misattribute meaning.
   See the allowlist note in tests/test_design_system_contract.py. */
.group-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 3px 10px;
    border-radius: var(--radius-full);
    font-size: var(--text-sm);
    font-weight: 500;
    background: #ede9fe;
    color: #6d28d9;
}
.group-chip.is-admin       { background: var(--ds-accent-warn-bg); color: var(--ds-accent-warn-ink); font-weight: 600; }
.group-chip.is-everyone    { background: var(--ds-border-light); color: var(--ds-text-secondary); }
.group-chip.is-google_sync { background: var(--ds-accent-success-bg); color: var(--ds-accent-success-ink); }
.group-chip.is-custom      { background: #ede9fe; color: #6d28d9; }

/* =====================================================
   .notice — the one inline-status component

   Three surfaces were saying the same kind of thing in three visual
   languages: a transcript note (fully tinted pill, centred text, coloured
   ink), an upload dialog's error slot (tinted box, coloured ink) and a toast
   (white card with a 3px coloured stripe). Same message, three looks, and all
   three read as an alarm because the TINT did the talking — a rate limit that
   clears itself arrived in the same saturated red as a crashed runner.

   One component instead, and the tone is carried by a 16px glyph, not by the
   surface. Nothing else changes between tones: same ground, same hairline,
   same ink. Only 5xx-class trouble gets a coloured glyph at all — the same
   rule the error pages follow.

   Two placements, because a floating element and an in-flow one have
   genuinely different jobs:
     .notice--inline    sits IN the content (transcript, dialog). Recessed
                        onto --ds-surface-dim so it reads as an aside rather
                        than as another message in the conversation.
     .notice--floating  sits OVER the content (toast). White and lifted, or
                        it looks like a hole in the page.

   Radius comes from --radius-* (style-custom.css): --ds-radius-md/-lg are NOT
   in the token set, and a var() to an undefined property with no fallback
   voids the whole declaration — the trap already documented on
   .cloud-chat-system-note and the agent dropdown.
   ===================================================== */
.notice {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    /* Left padding is 2px lighter than the right to compensate for the 3px
       stripe, so the glyph sits the same distance from the edge as the
       dismiss control on the other side. */
    padding: 10px 12px 10px 10px;
    border-radius: var(--radius-lg);
    border: 1px solid var(--ds-border);
    border-left: 3px solid var(--ds-border);
    background: var(--ds-surface-dim);
    font-size: var(--text-sm);
    line-height: 1.5;
    /* Body ink, never the tone's own ink. Coloured TEXT on a coloured ground
       is most of what made these read as alarms, and it is the part that
       costs legibility — the stripe, the tint and the glyph carry the tone
       between them without touching the sentence. */
    color: var(--ds-text-primary);
    text-align: left;
}
.notice--inline { margin: var(--space-2) 0; }
.notice--floating { box-shadow: var(--ds-shadow-md); }
/* The glyph. `currentColor` on the svg, so a tone only has to set `color`
   here — no per-tone fill to keep in step. margin-top optically seats a 16px
   square against a 1.5-line-height first line. */
.notice__icon {
    flex-shrink: 0;
    width: 16px;
    height: 16px;
    margin-top: 1px;
    color: var(--ds-text-muted);
}
.notice__icon svg { display: block; width: 100%; height: 100%; }
.notice__msg { flex: 1; min-width: 0; overflow-wrap: anywhere; }
/* Dismiss: present, never louder than the message beside it. `.toast-x` is
   the same control under its established name (app.js sets it, and
   tests/test_web_studio.py pins it) — one rule, two hooks, rather than the
   two identical blocks that used to sit in this file. */
.notice__x,
.toast-x {
    flex-shrink: 0;
    border: none;
    background: none;
    padding: 0;
    margin: 1px 0 0;
    font: inherit;
    font-size: 13px;
    line-height: 1;
    color: var(--ds-text-muted);
    cursor: pointer;
    border-radius: var(--radius-sm);
}
.notice__x:hover,
.toast-x:hover { color: var(--ds-text-primary); }
.notice__x:focus-visible,
.toast-x:focus-visible { outline: 2px solid var(--ds-primary); outline-offset: 2px; }

/* Tone: a light ground, a stripe down the left edge, and the glyph. Three
   quiet signals instead of one loud one — the old version repainted the
   whole element in a saturated tint AND set the text in the matching ink,
   which is why a rate limit that clears itself looked like a crash.
   
   The surrounding hairline stays `--ds-border` in every tone, and only
   `border-left-color` is touched. Painting all four edges in a tone token
   does not survive the themes: `--ds-accent-danger-line` is the SOFT red in
   the light palette and the FULL one in dark, so a rule that reads well on
   paper drew a bright outline around the whole element on the dark theme —
   the exact loudness this component removes. A neutral hairline plus a
   coloured stripe reads the same in both. */
.notice.is-warn,
.notice.is-wait {
    background: var(--ds-accent-warn-bg);
    border-left-color: var(--ds-accent-warn-ink);
}
.notice.is-warn .notice__icon,
.notice.is-wait .notice__icon { color: var(--ds-accent-warn-ink); }

.notice.is-error {
    background: var(--ds-accent-danger-bg);
    border-left-color: var(--ds-accent-danger-ink);
}
.notice.is-error .notice__icon { color: var(--ds-accent-danger-ink); }

.notice.is-ok,
.notice.is-success {
    background: var(--ds-accent-success-bg);
    border-left-color: var(--ds-accent-success-ink);
}
.notice.is-ok .notice__icon,
.notice.is-success .notice__icon { color: var(--ds-accent-success-ink); }

.notice.is-info {
    background: var(--ds-accent-info-bg);
    border-left-color: var(--ds-accent-info-ink);
}
.notice.is-info .notice__icon { color: var(--ds-accent-info-ink); }

/* =====================================================
   Error pages (4xx / 5xx)

   An error page is an empty state with a reason, and it is styled like
   one: a muted mark, a sentence, one line of explanation, the action
   that resolves it. Deliberately NOT a card — a bordered, shadowed panel
   floating on the grey app ground announces "here is an object", and
   there is no object here, only a message. The container paints itself
   `--surface` so the whole region reads as one white field; the rail
   carries its own right border, so nothing merges.

   Severity is carried by ONE token (`.error-page--broken`), applied only
   to 5xx. Every other status renders in muted ink, because "wait a
   moment", "ask your admin" and "that link is stale" are not emergencies
   and colouring them like one is what made a 429 read as a crash.
   ===================================================== */
/* `[data-theme]` is carried by every page (base_ds always stamps it), and the
   attribute is here purely for specificity: paper-skin.css sets
   `[data-theme="paper"] body { background: var(--ds-bg) }` and loads AFTER
   this file, so a plain `body.error-page` ties on specificity and loses on
   order — the page rendered on the grey app ground with a white box in the
   middle, which is the card this redesign removed. */
[data-theme] body.error-page { background: var(--ds-surface); }
.error-container {
    display: flex;
    align-items: center;
    justify-content: center;
    /* Fills the viewport minus everything else in the column: `.container`'s
       own vertical padding plus the footer block (its ~49px and its 48px
       margin-top). At the previous 60vh the container ended well above the
       fold and centred the card inside ITS OWN box — about a third of the way
       down the screen, with a large empty field beneath it. `min-height`, so
       a long traceback grows the page instead of being clipped. */
    min-height: calc(100vh - 11rem);
    padding: var(--space-6) var(--space-3);
}
.error-card {
    max-width: 30rem;
    width: 100%;
    text-align: center;
}
.error-mark {
    display: flex;
    justify-content: center;
    margin-bottom: var(--space-3);
    color: var(--text-muted);
}
.error-mark svg { display: block; }
.error-page--broken .error-mark { color: var(--ds-accent-danger-ink); }
.error-title {
    margin: 0 0 var(--space-2);
    font-size: var(--text-xl);
    font-weight: 600;
    line-height: 1.25;
    text-wrap: balance;
}
.error-message {
    color: var(--text-secondary);
    margin: 0 auto var(--space-6);
    max-width: 42ch;
    line-height: 1.55;
}
.error-actions {
    display: flex;
    gap: var(--space-3);
    justify-content: center;
    flex-wrap: wrap;
}
/* The status code lives here now, beside the request id, instead of at
   4rem in red above the headline. Support keeps every value it had; the
   reader is no longer shouted at by a number that means nothing to them. */
.error-ref {
    margin: var(--space-6) 0 0;
    padding-top: var(--space-4);
    border-top: 1px solid var(--border);
    font-size: var(--text-sm);
    font-family: var(--font-mono);
    color: var(--text-muted);
    user-select: all;
}
/* DEBUG=1 only — the path and traceback are developer surfaces, and the
   path duplicated the address bar for everyone else. */
/* Deliberately the lightest thing on the page: no panel, no border, no
   chips. It is a developer aid on a page whose subject is the message above
   it, and as a filled bordered box with tinted code chips it out-weighed
   both the explanation and the actions. Only the traceback keeps a tint,
   because a multi-line stack needs a block to read as one. */
.error-debug {
    text-align: left;
    margin: var(--space-5) auto 0;
    max-width: 34rem;
}
.error-debug summary {
    cursor: pointer;
    font-size: var(--text-sm);
    color: var(--text-muted);
    user-select: none;
}
.error-debug p {
    margin: var(--space-2) 0 0;
    font-size: var(--text-sm);
    line-height: 1.5;
    color: var(--text-muted);
    overflow-wrap: anywhere;
}
.error-debug code {
    font-family: var(--font-mono);
    font-size: 0.95em;
    color: var(--text-muted);
    background: none;
    padding: 0;
}
.error-debug pre {
    margin: var(--space-3) 0 0;
    padding: var(--space-3);
    font-size: var(--text-sm);
    line-height: 1.4;
    overflow-x: auto;
    white-space: pre-wrap;
    word-break: break-word;
    background: var(--background);
    border-radius: var(--radius-sm);
    color: var(--text-secondary);
    font-family: var(--font-mono);
}

/* =====================================================================
   Modal — global structural styles (#497 §1).

   Promoted verbatim from the page-local <style> in admin_users.html so
   the shared modal helper (static/js/modal.js → window.confirmModal /
   alertModal / promptModal) renders correctly on EVERY page, not just
   the three admin pages that hand-rolled these rules. admin_users.html /
   admin_tokens.html / admin_marketplaces.html still ship identical
   page-local copies; those are redundant now but harmless (same rules)
   and can be stripped in a later cleanup pass.

   Buttons inside .modal-actions use the canonical .btn / .btn-* variants
   (via the ds.button macro or modal.js), so no .modal-btn rules here.
   ===================================================================== */
.modal-backdrop {
    position: fixed; inset: 0; background: rgba(15, 23, 42, 0.55);
    display: none; align-items: center; justify-content: center; z-index: 1000;
    padding: 16px;
}
.modal-backdrop.is-open { display: flex; }
.modal-card {
    background: var(--surface, #fff); border-radius: 12px;
    padding: 24px; width: 100%; max-width: 440px;
    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.25);
}
.modal-card h3 { margin: 0 0 6px; font-size: 17px; font-weight: 600; }
.modal-card p.sub { margin: 0 0 18px; font-size: 13px; color: var(--text-secondary, #6b7280); }
.modal-card label { display: block; font-size: 12px; font-weight: 500; color: var(--text-secondary, #6b7280); margin: 12px 0 6px; }
.modal-card input[type="text"], .modal-card input[type="email"], .modal-card input[type="password"], .modal-card select {
    width: 100%; padding: 9px 12px; border: 1px solid var(--border, #e5e7eb);
    border-radius: 8px; font-size: 13px; box-sizing: border-box;
    background: var(--surface, #fff); color: var(--text-primary, #111827);
}
.modal-card input:focus, .modal-card select:focus { outline: 2px solid var(--ds-primary, #6366f1); outline-offset: -1px; }
.modal-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 20px; }
/* Preserve newlines in helper-supplied messages — native alert/confirm/
   prompt rendered \n as line breaks (e.g. bulleted error lists).
   OPT-IN, via the attribute modal.js stamps on the cards it builds. The
   rule used to be unscoped, and the comment already knew that was wrong
   ("the page-local admin_users copy never needed this") — it just had no
   way to say so in a selector. Unscoped, it also hit STATIC modal copy in
   templates, where the newlines and indentation are source formatting: a
   helper paragraph written across three template lines rendered as three
   jaggedly-indented lines instead of one wrapped paragraph (#1171). */
.modal-card[data-preserve-newlines] h3,
.modal-card[data-preserve-newlines] p.sub { white-space: pre-wrap; }

/* Shared inline-icon glyphs (macros/_icon.html) — replaces the pictographic
   emoji we used to inline. Nudge the baseline so an icon sits centered next
   to heading/label text. Block-level empty-state icons ignore this. */
.ui-ico { vertical-align: -0.18em; }

/* ── Chat landing: admin notice + doors (.cld-*) ─────────────────────────────
   The chat landing page introduces itself in text now, not in a banner. The
   Knowledge Layer hero that used to lead this page asserted a category and
   spent the page's best space on a CTA pointing away from the composer; the
   six-step admin setup panel that briefly replaced it read as onboarding and
   graduated at the wrong moment. Both are gone. What is left is small on
   purpose: one fact for an admin with an empty instance, one capability
   sentence for everyone, and three labelled doors at the foot.

   All `--ds-*` tokens, so every palette theme follows with no override. */

/* The greeting locates the reader; it must not compete with the heading. */
.cld-greet {
    display: flex; align-items: center; justify-content: center; gap: 6px;
    margin: 0;
    font-size: 13px; font-weight: 550;
    color: var(--ds-text-muted);
}
/* Time-of-day glyph. BOTH are rendered and `data-tod` shows one, so
   chat_dashboard.js can correct the time from the browser clock without
   fetching or building an icon — it flips one attribute and the words and the
   glyph change together. A sun over "Good evening" is a small wrongness that
   makes a page feel unattended. */
.cld-greet-ico { display: none; line-height: 0; opacity: .75; }
.cld-greet[data-tod="day"] .cld-greet-ico--day,
.cld-greet[data-tod="night"] .cld-greet-ico--night { display: inline-flex; }
/* The one factual sentence about what this answers. Constrained to a reading
   measure and centred with the heading above the composer.

   `.cld-agent-intro-role` is the same slot saying something more specific — the
   agent's own role line, when the conversation runs as one the caller named. It
   shares the rule rather than the class so a test that reads "the page's lede"
   by splitting on `class="cld-lede"` cannot land on the agent block instead. */
.cld-lede,
.cld-agent-intro-role {
    /* The heading and this lede are one thought. `tight`, because the intro
       column's own flex gap already contributes the other half — the two
       together land on `inner`, which is the step this pair wants. */
    margin: var(--cld-gap-tight, 5px) auto 0;
    max-width: 56ch;
    font-size: 14.5px; line-height: 1.5;
    color: var(--ds-text-secondary);
}

/* ---- The two doors ----
   Plain labelled links: each says where it goes and what is there, so nothing
   needs a banner to explain it. Bordered, not filled — the composer is the
   page's action and these must not compete with it.

   TWO, not three, and BELOW the suggestions rather than above the composer
   (except on an instance with nothing registered — see the two position blocks
   below).
   Three cards between the heading and the input made the reader's first
   decision on an "ask something" page a choice of which way to leave; and the
   third card ("How {brand} works") was reference reading given the same weight
   as setting the instance up. It is a link in the foot now.

   Held to `--cld-doors-w` and centred: at two cards the grid would otherwise
   stretch them to whatever the chat column is, and a 1100px-wide pair of
   three-line cards reads as two banners. This is the same measure the lede
   above the composer uses. */
.cld-doors {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 12px;
    /* The EMPTY-INSTANCE position: end of the intro block, above the composer.
       That column's own 5px flex gap is subtracted so the visible step lands on
       `block` exactly — without the calc every margin in there reads 5px larger
       than the token it names, which is how a scale quietly stops being one.
       (The ordinary position is overridden below; it sits in a container with
       no gap, so it needs no subtraction.) */
    margin: calc(var(--cld-gap-block, 26px) - var(--cld-gap-tight, 5px)) auto 0;
    /* The shared secondary measure — same edges as the chip row above. */
    max-width: var(--cld-measure, 660px);
    text-align: left;
}
.cld-door {
    position: relative;
    display: flex; flex-direction: column; gap: 3px;
    /* Top-aligned, not centred. With three cards the grid stretched all of them
       to the tallest and centring spent the slack on both sides; at two cards
       of near-equal height the slack is small, and top alignment is what keeps
       the two titles on one line as the eye crosses between them. */
    justify-content: flex-start;
    padding: 15px 16px;
    /* TRANSPARENT, not `none`: the border is gone to the eye, but its 1px stays
       in the box so the two cards keep their metrics and nothing shifts by 2px
       when one of them lifts. A resting outline on a card whose only job is to
       be one of two quiet doors was drawing a box around something that needed
       no boxing — the surface already separates it from the page. */
    border: 1px solid transparent;
    border-radius: 14px;
    background: color-mix(in srgb, var(--ds-surface) 70%, transparent);
    text-decoration: none;
    transition: box-shadow var(--ds-motion-fast, 120ms) var(--ds-ease-standard, ease),
                background var(--ds-motion-fast, 120ms) var(--ds-ease-standard, ease);
}
/* A card with no surface of its own needs an EDGE. Keyed on the absence of
   `--lead` rather than named per card, because that is the actual rule: the
   tinted card is held off the page by its own fill and an outline on top of it
   is a box around something already separated, while the plain card's fill is
   70% surface — nearly the page — so with no border at all it stops reading as
   a card and its content floats. Subtle enough to be an edge and not a frame:
   the hover lift below is still what answers the pointer. */
.cld-door:not(.cld-door--lead) {
    border-color: color-mix(in srgb, var(--ds-border) 62%, transparent);
}

/* Hover is a LIFT, not an outline. One channel, so the two cards answer the
   pointer identically whatever surface each carries. */
.cld-door:hover {
    box-shadow:
        0 1px 2px color-mix(in srgb, var(--ds-text-primary) 5%, transparent),
        0 18px 38px -24px color-mix(in srgb, var(--ds-primary) 50%, transparent);
}
.cld-door-t {
    font-size: 13.5px; font-weight: 650; letter-spacing: -0.01em;
    color: var(--ds-text-primary);
}
.cld-door-d {
    font-size: 12px; line-height: 1.4;
    color: var(--ds-text-muted);
}
.cld-door-tools { display: flex; align-items: center; gap: 6px; margin-top: 8px; }
.cld-door-tools img { display: block; object-fit: contain; opacity: .8; }

/* ---- Lead-card layout: a marker column, then the words ----
   The setup card's ring and the other cards' icon tile occupy the same slot, so
   the pair lines up whichever variant renders. `align-items: flex-start` rather
   than centre: the marker belongs beside the TITLE, and centring it against a
   three-line body floats it opposite the description instead. */
/* Marker column, then the words. Carried by its OWN class, not by
   `--lead`: the tinted surface below and this layout are different decisions,
   and the tools card wants the second without the first. */
.cld-door--split {
    flex-direction: row;
    align-items: flex-start;
    gap: 13px;
}
.cld-door-bd {
    display: flex; flex-direction: column; gap: 3px;
    min-width: 0;
}

/* The corner mark — what KIND of move this card is, in the one spot that costs
   no line of text. `arrow-up-right` on a door that hands you off elsewhere,
   `layers` on the one that is about your own instance. Muted at rest so it is
   available rather than announced; it takes the accent when the card is live. */
/* Two classes deep on purpose: `.cld-door--lead > *` below sets every child
   `position: relative` (to lift them over the card's layered ::before fill), and
   at equal specificity that later rule would win and drop this mark back into
   the flex row. */
.cld-door .cld-door-mark {
    position: absolute;
    top: 13px; right: 13px;
    z-index: 2;
    line-height: 0;
    color: var(--ds-text-muted);
    opacity: .8;
    transition: color var(--ds-motion-fast, 120ms) var(--ds-ease-standard, ease);
}
.cld-door:hover .cld-door-mark { color: var(--ds-primary); }

/* ---- The setup progress ring ----
   Replaces "3 of 6 steps done" as a sentence. Same fact, read at a glance
   instead of parsed, and the number sits where the eye lands first rather than
   at the end of a link.

   Server-rendered, unlike the rail's copy of this ring: `admin_setup` already
   knows both halves of the fraction here, so there is no frame on which this
   shows the wrong number. */
.cld-ring {
    position: relative;
    flex-shrink: 0;
    width: 46px; height: 46px;
    display: grid; place-items: center;
}
.cld-ring-svg {
    grid-area: 1 / 1;
    width: 100%; height: 100%;
    /* Starts the arc at 12 o'clock instead of SVG's default 3 o'clock. */
    transform: rotate(-90deg);
}
/* Stroke width is in viewBox units (36 wide, rendered at 46px), so 3.5 here is
   ~4.5 real px — heavy enough to read as a meter rather than as a decorative
   circle, which is the whole job of this element. */
.cld-ring-track { fill: none; stroke: var(--ds-border); stroke-width: 3.5; }
.cld-ring-fill {
    fill: none;
    stroke: var(--ds-primary);
    stroke-width: 3.5;
    stroke-linecap: round;
}
/* The fraction, inside the ring. Tabular figures so 3/6 and 4/6 do not shift
   width and nudge the ring's centre as the count changes. */
.cld-ring-num {
    grid-area: 1 / 1;
    font-size: 12px; font-weight: 700;
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.02em;
    color: var(--ds-text-primary);
}

/* ---- The lead card's SURFACE ----
   The first door is audience-dependent — "Set up Agnes" for an admin and
   "See what Agnes knows" for everyone else — and either way it is the one
   move this reader has before they start asking. So it gets the weight, and both
   variants get the SAME weight: they are one slot rendered twice, not two
   cards that happen to sit in the same place.

   ONLY that slot. The tools card is a peer in layout and type (see
   `.cld-door--split`) but takes the plain `.cld-door` surface: this fill means
   "this is Agnes itself", and spending it on both cards made the pair a band of
   tinted panels with nothing to distinguish the card that is about your own
   instance from the one that points at your editor.

   The surface is the knowledge-surface fill scaled to card size — the same
   gradient `.cbn--bar` paints (see its rule near the top of this file). That
   fill is where the product's visual language for "this is Agnes itself" was
   set, so the card that speaks for Agnes should paint it too. Every layer rides
   `--ds-surface` or a color-mix into it, which is what lets one rule follow
   every palette theme with no dark-mode override. */
.cld-door--lead {
    position: relative;
    overflow: hidden;
    background:
        radial-gradient(70% 130% at 88% 8%, color-mix(in srgb, var(--ds-kind-agent) 9%, transparent), transparent 62%),
        radial-gradient(64% 130% at 4% 94%, color-mix(in srgb, var(--ds-primary) 11%, transparent), transparent 62%),
        radial-gradient(96% 150% at 50% -24%, color-mix(in srgb, var(--ds-assistant) 8%, transparent), transparent 64%),
        linear-gradient(108deg,
            color-mix(in srgb, var(--ds-agnes) 6%, var(--ds-surface)) 0%,
            var(--ds-surface) 50%,
            color-mix(in srgb, var(--ds-kind-agent) 6%, var(--ds-surface)) 100%);
}
/* The banner's dotted field, in the corner the icon does not occupy. Drawn in
   a pseudo-element so the card needs no decorative markup — there is nothing
   here for a reader to reach, and a real element would be one more thing for
   the template to carry. */
.cld-door--lead::before {
    content: "";
    position: absolute; right: 0; top: 0; width: 132px; height: 96px;
    background-image: radial-gradient(color-mix(in srgb, var(--ds-primary) 30%, transparent) 1.2px, transparent 1.3px);
    background-size: 15px 15px;
    -webkit-mask-image: linear-gradient(232deg, rgba(0, 0, 0, .5), transparent 70%);
    mask-image: linear-gradient(232deg, rgba(0, 0, 0, .5), transparent 70%);
    opacity: .5;
    pointer-events: none;
}
/* …and everything real sits above it. */
.cld-door--lead > * { position: relative; z-index: 1; }

/* The title is solid accent ink, NOT the retired hero's blue→purple gradient —
   that was tried here and does not survive the themes at this size. The
   gradient ran `--ds-assistant` → `--ds-kind-agent`: fine on a
   30px/760 headline, which only owes 3:1, and wrong on a 14.5px card title,
   which owes 4.5:1. `--ds-assistant` is 4.1:1 on the paper surface, and under
   `data-theme="dark"` `--ds-kind-agent` collapses to 2.4:1 — so the right end
   of the gradient would be unreadable. No pair of existing accent tokens
   clears 4.5:1 in BOTH themes, and a per-theme override for one flourish is
   not what it is worth. `--ds-primary-dark` is 5.9:1 on paper and 5.7:1 in
   dark; the banner's character is carried by this card's surface, halo and
   dotted field, none of which have this problem. */
.cld-door--split .cld-door-t {
    font-size: 14.5px;
    letter-spacing: -0.02em;
    color: var(--ds-primary-dark, var(--ds-primary));
    /* Clears the corner mark, so a two-word title cannot run under it. */
    padding-right: 20px;
}
.cld-door--split .cld-door-d { color: var(--ds-text-secondary); }

/* The whole-card link still owes the reader a hover. The setup variant opts
   back out below — it is not a link. */
/* No hover rule of its own: `.cld-door:hover` above is the one lift, shared by
   both cards. This used to add a border tint on top of it, which is the whole
   thing being removed. */

/* The admin's "Set up {brand}" card — the ONE action an admin has on this page.
   The hero used to carry a chip and a checklist link as well; three routes into
   one job meant choosing between them before doing any of it, so the card is
   now the whole offer and holds a real button.

   It is a block rather than a whole-card link (it contains that button), and it
   is styled apart from its two neighbours because it is a different KIND of
   thing: a job this reader owns, not a place to browse. Tinted fill, accent
   edge, its own icon, and the button carries the weight — on an unset-up
   instance this is the most important element on the page. */
/* No rules of its own any more. It used to be the one card that was NOT a
   link — a <div> holding an <a> — so it had to suppress the hover the others
   offer and sit at `cursor: default`, leaving the reader a 13px line of text to
   aim at. It is a whole-card link like its neighbours now, and takes
   `.cld-door--lead`'s surface and hover unchanged. */
/* The icon tile, with the orb's soft halo behind it rather than a flat chip —
   the banner's one gesture that survives being shrunk to 32px. */
.cld-door-ico {
    display: inline-grid; place-items: center;
    flex-shrink: 0;
    /* The RING's exact slot, so every card's text column starts on the same
       line whichever leading mark it carries. It was 40px against the ring's
       46px, which left the two titles 6px out of step — invisible as a number
       and perfectly visible as two cards that don't quite align. No bottom
       margin any more: it sits BESIDE the words now, not above them. */
    width: 46px; height: 46px;
    border-radius: 12px;
    background: color-mix(in srgb, var(--ds-primary) 13%, var(--ds-surface));
    color: var(--ds-primary-dark, var(--ds-primary));
    box-shadow:
        0 0 0 4px color-mix(in srgb, var(--ds-primary) 6%, transparent),
        0 6px 14px -8px color-mix(in srgb, var(--ds-primary) 45%, transparent);
}

/* The brand-orb variant of that slot: same 40px box, so the pair still lines
   up, but no chip and no halo. The fill and glow exist to give a monochrome
   line glyph something to sit on; the orb brings its own colour and edge, and
   wrapping it in a tinted square puts a badge around a badge. */
.cld-door-ico--orb {
    background: none;
    box-shadow: none;
}
.cld-door-orb {
    display: block;
    /* The PNG is 192x192, still crisp at this size up to DPR 3. */
    width: 32px; height: 32px;
}

/* The card's action. Primary while there is nothing registered (the page's one
   real move); a quiet text link once the instance is running, where it reports
   progress rather than urging anything. */
.cld-door-btn {
    align-self: flex-start;
    margin-top: 10px;
    padding: 8px 14px;
    border-radius: var(--ds-radius-pill, 999px);
    background: var(--ds-primary); color: var(--ds-text-inverse);
    font-size: 13px; font-weight: 650; text-decoration: none;
    box-shadow: 0 8px 18px -10px color-mix(in srgb, var(--ds-primary) 55%, transparent);
    transition: background var(--ds-motion-fast, 120ms) var(--ds-ease-standard, ease),
                transform var(--ds-motion-fast, 120ms) var(--ds-ease-standard, ease);
}
/* Hover comes from the CARD, not the span: the whole card is the hit area, so
   a fill that only lit when the pointer was over the last 100px of text would
   say the wrong thing about what is clickable.

   `:not(--quiet)` is load-bearing. Without it this rule outranks
   `.cld-door-btn--quiet { background: none }` — one class deeper — so hovering
   the card painted the quiet text link with the PRIMARY fill: "Continue setup"
   turned into a dark blue blob with an underline through it. The quiet variant
   is a text link and stays one in every state. */
.cld-door:hover .cld-door-btn:not(.cld-door-btn--quiet) { background: var(--ds-primary-dark); }
.cld-door:active .cld-door-btn:not(.cld-door-btn--quiet) { transform: translateY(1px); }
.cld-door-btn--quiet {
    padding: 0;
    margin-top: 8px;
    background: none; box-shadow: none;
    color: var(--ds-primary);
    font-size: 12.5px; font-weight: 650;
}
.cld-door-btn--quiet:hover { background: none; text-decoration: underline; }
/* The quiet action is a <span> inside a card-wide link on every variant, so it
   never receives :hover itself — the card does, and hands it down. */
.cld-door:hover .cld-door-btn--quiet { text-decoration: underline; }

/* ---- The foot ----
   The trust claim and the one piece of reference reading that used to be a
   third card. Both are consulted rather than acted on, so they share one quiet
   line under everything else, separated by a hairline rather than a bullet —
   they are two different kinds of statement, not two items in a list. */
.cld-trust {
    display: flex; align-items: center; justify-content: center;
    flex-wrap: wrap;
    gap: 12px;
    /* Closes the block under the cards, so `inner` — it belongs to them rather
       than standing as a region of its own, and it travels with them between the
       two positions. Same flex-gap subtraction as `.cld-doors` above for the
       empty-instance position; overridden below for the ordinary one. */
    margin: calc(var(--cld-gap-inner, 10px) - var(--cld-gap-tight, 5px)) 0 0;
}
/* The trust claim ("Secure. Private. Always in sync."), inlined in chat.html.
   It was `.klb-hub-*` in the retired hero's stylesheet, shared with a hero
   variant that also rendered a title; only the caption survived the hero. */
.cld-trust-claim {
    display: inline-flex; align-items: center; justify-content: center; gap: 6px;
    font-size: 12.5px; color: var(--ds-text-muted);
}
.cld-trust-lock { width: 13px; height: 13px; flex-shrink: 0; }
.cld-trust-sep {
    width: 1px; height: 13px;
    background: var(--ds-border);
}
.cld-trust-link {
    display: inline-flex; align-items: center; gap: 6px;
    color: var(--ds-text-muted);
    font-size: 12.5px; font-weight: 550;
    text-decoration: none;
    transition: color var(--ds-motion-fast, 120ms) var(--ds-ease-standard, ease);
}
.cld-trust-link:hover { color: var(--ds-primary); text-decoration: underline; }

/* ---- The ORDINARY position ----
   Same two cards and the same trust line, rendered after the suggestions
   instead of before the composer — where they sit for admin and member alike
   on any instance that has data (see chat.html; the empty instance is the one
   exception). `#chat-empty-extras` has no flex gap of its own, so these take
   the tokens straight: a full `section` step above the cards — the page's one
   real boundary, here between asking and the ways out — and `inner` under them
   for the line that closes the block. */
#chat-empty-extras .cld-doors {
    margin-top: var(--cld-gap-section, 40px);
}
#chat-empty-extras .cld-trust {
    margin-top: var(--cld-gap-inner, 10px);
}

@media (max-width: 820px) {
    .cld-doors { grid-template-columns: minmax(0, 1fr); }
    /* The separator is a horizontal device; stacked, it reads as a stray tick. */
    .cld-trust-sep { display: none; }
}


/* The hoverable shell `ds.button` puts around a DISABLED LINK. The anchor
   inside is `pointer-events: none` (that is what makes `aria-disabled` bite on
   an <a>), which also takes it out of hit-testing — so the reason the control
   is off has to live out here or it can never be shown. Inline-flex so the
   wrapper is exactly the button's own box and nothing shifts. */
.ds-disabled-wrap { display: inline-flex; }
