/* 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 left, and the fill is kept as its own rule so a future
   "this is Agnes itself" surface reaches for it rather than re-deriving the
   gradient. (The chat landing's lead door imitated it for a while; that card
   is gone.)

   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,
.site-footer__privacy {
    color: var(--ds-text-muted);
}
.site-footer__privacy a {
    color: inherit;
    text-decoration: underline;
    text-decoration-color: var(--ds-border);
}
.site-footer__privacy a:hover {
    color: var(--ds-text-secondary);
}
/* 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,
.site-footer__privacy::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-operator {
    font-size: var(--text-sm);
    opacity: 0.75;
    margin: 0 0 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: var(--app-header-height, 0px);
    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; }
/* An action inside the note: the one control that resolves what the note
   says, for the cases where the sentence would otherwise ask the reader to
   type it. Seated on the FIRST line (`flex-start`, not centred) so a message
   that wraps to two lines does not drag the button down past the glyph it
   answers to. Styling is the global `.btn` — this rule only places it. */
.notice__action { flex-shrink: 0; align-self: flex-start; }
/* 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; }

/* --wide: a scrollable content card (e.g. the privacy popup) instead of a
   short confirm/alert/prompt dialog. Same backdrop/open mechanics, just a
   bigger, scrolling card with room for a dismiss button in the corner. */
.modal-card--wide {
    max-width: 620px; max-height: 82vh; overflow-y: auto; position: relative;
}
.modal-card__close {
    position: absolute; top: 14px; right: 14px; width: 28px; height: 28px;
    display: grid; place-items: center; border: none; background: transparent;
    color: var(--ds-text-muted, #6b7280); font-size: 20px; line-height: 1;
    cursor: pointer; border-radius: 6px;
}
.modal-card__close:hover { background: var(--ds-surface-sunken, rgba(0, 0, 0, 0.05)); }

/* Privacy content (_privacy_content.html) — shared by the bare /privacy
   page and the in-app popup (_privacy_modal.html). Lives here rather than
   in the partial's own <style> block because the partial now renders in
   the BODY of every chrome that carries the footer or the pre-auth popup;
   repo convention keeps page CSS out of the body. Colors ride --ds-*
   tokens only. */
.pv-eyebrow { font-size: 12px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ds-text-muted); margin: 0 0 6px; }
.pv-title { font-size: 30px; line-height: 1.2; margin: 0 0 14px; color: var(--ds-text-primary); }
.pv-lede { font-size: 16px; line-height: 1.6; color: var(--ds-text-secondary); margin: 0 0 26px; }
.pv-card { border: 1px solid var(--ds-border); border-radius: 14px; background: var(--ds-surface); padding: 6px 18px; }
.pv-row { display: flex; gap: 12px; align-items: flex-start; padding: 15px 0; line-height: 1.6; color: var(--ds-text-secondary); }
.pv-row + .pv-row { border-top: 1px solid var(--ds-border); }
.pv-row strong { color: var(--ds-text-primary); }
.pv-mark { flex-shrink: 0; width: 22px; height: 22px; border-radius: 50%; display: grid; place-items: center; font-size: 13px; background: var(--ds-accent-success-bg); color: var(--ds-accent-success-ink); }
/* An arrow, not a tick: this row describes what DOES travel. */
.pv-mark.is-flow { background: var(--ds-info-bg); color: var(--ds-info-ink); }
.pv-code { background: var(--ds-surface-sunken); border: 1px solid var(--ds-border); border-radius: 4px; padding: 1px 6px; font-family: var(--ds-font-mono, monospace); font-size: 13px; }
.pv-note { margin-top: 30px; color: var(--ds-text-secondary); line-height: 1.65; }
.pv-h2 { font-size: 17px; margin: 0 0 8px; color: var(--ds-text-primary); }
.pv-note p { margin: 0 0 10px; }
/* In the popup, the content sits inside .modal-card--wide's own padding —
   the bare /privacy page's .pv-wrap max-width/centering does not apply, so
   nothing here assumes a fixed reading width. */

/* 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 (.cld-*) ───────────────────────────────────────────────────
   The pre-conversation landing: an introduction, the suggestions, the
   composer, and one contextual offer. Small on purpose, and smaller the longer
   you have used it.

   Two large permanent cards used to close this page — "Set up {brand}" and
   "Take {brand} to your tools" — each restating a row the rail carries on
   every page (the setup ring down to the same fraction, the tools row down to
   the same label). The rules that governed their layout went with them:
   `.cld-doors`, `.cld-door*`, `.cld-ring*`, and the two position blocks that
   moved the pair above the composer on an empty instance and back below it
   otherwise. What replaced ~200 lines of card CSS is `.cld-act` (one button,
   in the one state where an action outranks the input) and `.cld-offer` (one
   line, for one reader, once).

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

/* ══ The hero: orb, identity, description, reach ═══════════════════════════
   One focal point and one identity. The previous pass hung nine elements off
   a single left edge with nothing anchoring the column, which reads as
   unstyled rather than as composition — a centred axis only works when
   something SHAPED sits on it, and the orb is that thing.

   Everything here is centred on the column, and the vertical steps are the
   page's own scale (see the `--cld-gap-*` tokens in chat.css). */
.cld-hero {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    width: 100%;
    /* The one place this page spends real space. The orb needs air above it or
       it reads as a favicon that wandered onto the page. */
    margin: 0 0 var(--cld-gap-section, 40px);
}

/* ---- The orb ----
   The brand mark at page scale, with a halo that IS its own gradient. The orb
   is a raster (192px source) whose palette runs indigo #001860 → #4860c0 →
   teal #189090; `--ds-kind-agent` (violet) through `--ds-primary` (sky) is
   that same arc in tokens, so the halo reads as the orb's light rather than as
   a coloured circle someone put behind it. Two radials, offset from each
   other, so the light has a direction. */
.cld-orb {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-bottom: 22px;
}
.cld-orb::before {
    content: "";
    position: absolute;
    /* Wider than the mark and centred on it: a halo the same size as the orb
       is a ring, not light. */
    inset: -58px;
    border-radius: 50%;
    background:
        radial-gradient(46% 46% at 38% 30%,
            color-mix(in srgb, var(--ds-kind-agent) 22%, transparent), transparent 70%),
        radial-gradient(52% 52% at 68% 72%,
            color-mix(in srgb, var(--ds-primary) 20%, transparent), transparent 72%);
    filter: blur(18px);
    opacity: 0.85;
    pointer-events: none;
}
.cld-orb-img {
    position: relative;
    display: block;
    /* The mark sits ON the light, so it gets the lift and the halo does not. */
    filter: drop-shadow(0 6px 18px color-mix(in srgb, var(--ds-kind-agent) 26%, transparent));
}

/* ---- Someone else's mark in that slot ----
   A white-labelled instance puts its own mark here (chat.html gates this on
   `LOGO_SVG` + `LOGO_MARK_SVG`, the same pair the rail reads). Nothing of ours
   is applied to it: no halo, no drop shadow in our accent, no recolouring. An
   operator's SVG carries its own fills, and one drawn in `currentColor`
   inherits the hero's ink — the single case where following the theme is the
   right answer rather than an override.

   Sized by HEIGHT with the width free, because a mark is only USUALLY square
   and a wordmark-ish one must not be squashed into a box we picked. The cap is
   what stops a near-lockup from spanning the reading column. */
.cld-orb.is-brand {
    /* Mirrors the `size=76` chat.html passes the orb macro, so the two marks
       occupy the same slot. */
    --cld-mark-size: 76px;
}
.cld-orb.is-brand::before {
    display: none;
}
.cld-brand-mark {
    display: block;
}
.cld-brand-mark svg,
.cld-brand-mark img {
    display: block;
    height: var(--cld-mark-size);
    width: auto;
    max-width: calc(var(--cld-mark-size) * 1.6);
}

/* ---- A lockup with no monogram: the derived initial ----
   `.rail-logo-mono`'s recipe (rail.css) at hero scale — same tokens, same
   proportion of glyph to tile, so an instance's initial reads as one mark
   whether it is 28px in the collapsed rail or 76px here. Square, unlike
   `.cld-brand-mark` above: a derived initial has no natural aspect ratio to
   preserve by sizing on height alone. */
.cld-brand-mono {
    display: grid;
    place-items: center;
    width: var(--cld-mark-size);
    height: var(--cld-mark-size);
    border-radius: var(--ds-radius-btn);
    background: var(--ds-surface-dim);
    color: var(--ds-text-primary);
    font-size: calc(var(--cld-mark-size) * 0.5);
    font-weight: 700;
    line-height: 1;
}

/* ---- The description ----
   Who is answering, for EVERYONE. Held to a reading measure and centred under
   the identity it belongs to. */
.cld-desc {
    margin: 10px 0 0;
    max-width: 52ch;
    font-size: 15px;
    line-height: 1.55;
    color: var(--ds-text-secondary);
}

/* ---- What it can reach ----
   The other half of trusting an answer. Small, muted, and a LIST rather than
   a sentence: these are named things, and a reader scans them for one they
   recognise. Empty (and gone) when there is nothing specific to name. */
.cld-reach {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: 6px 10px;
    margin: var(--space-3) 0 0;
    font-size: 12.5px;
    color: var(--ds-text-muted);
}
.cld-reach:empty { display: none; }
.cld-reach-item {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
/* A dot, not an icon: these are three different KINDS of thing (packages,
   memory domains, collections) and a per-kind glyph at 12px would be three
   unreadable shapes. The dot says "one of several"; the name does the work. */
.cld-reach-item::before {
    content: "";
    width: 4px;
    height: 4px;
    border-radius: 50%;
    background: color-mix(in srgb, var(--ds-primary) 55%, transparent);
}
.cld-reach-all { color: var(--ds-text-muted); }

/* "and N more" — the remainder, when an agent is pointed at more than the
   line names. NO dot: with one it read as a fifth dataset called "and 21
   more", which is the one thing this chip must not look like. It is the
   sentence's tail, not another item in the list. */
.cld-reach-item.is-more::before { display: none; }
.cld-reach-item.is-more { color: var(--ds-text-muted); }

/* A package the agent is scoped to that cannot be read right now. Admin-only
   — a member cannot act on an expired credential, so they are told the agent
   is unavailable and not which source failed. Marked by weight and a hollow
   dot rather than by a warning colour: this is the reach line, not an alert,
   and it sits under an agent's name where red would read as the AGENT being
   broken. */
/* WARN ink, not the muted body colour. At weight 500 and a hollow dot it was
   the same 12.5px muted grey as the sentence beside it, so "Everything you can
   reach · Support Ops unavailable" read as two things the reader can reach —
   and since `.cld-reach-all` has no dot of its own, the one visible dot read
   as a separator rather than as a marker. The only outage information on an
   otherwise healthy screen was undetectable.
   Warn, not danger: this is a source that will come back, under an agent's
   name where red would read as the AGENT being broken. */
.cld-reach-item.is-blocked {
    color: var(--ds-accent-warn-ink);
    font-weight: var(--font-medium);
}
.cld-reach-item.is-blocked::before {
    background: transparent;
    box-shadow: inset 0 0 0 1px currentColor;
}

/* The line stops being a list of names and becomes a sentence — an agent with
   nothing assigned, or nothing readable. Prose, so it drops the dot markers
   and the row gap that separated chips. */
.cld-reach.is-note { gap: var(--space-1) 6px; }
.cld-reach-note { color: var(--ds-text-muted); }
.cld-reach-act {
    color: var(--ds-primary);
    text-decoration: none;
    font-weight: 500;
}
.cld-reach-act:hover { text-decoration: underline; }

/* ---- What else it can reach: the skills it is narrowed to ----
   The reach line above is the DATA half of "what can this agent access"; this
   is the tool half, and it renders only when the agent narrows it (chat.js
   hides the element otherwise — see `_paintSkills`). One step smaller and
   quieter than the reach line: a reader scanning for a dataset name must not
   have to read past a list of skill slugs to find it.

   A label rather than a leading dot, because unlike the reach line these are
   all one kind of thing and the question a reader has is "narrowed to what". */
.cld-skills {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: var(--space-1) var(--space-2);
    margin: var(--space-2) 0 0;
    font-size: var(--text-sm);
    color: var(--ds-text-muted);
}
.cld-skills-label {
    font-weight: var(--font-medium);
    text-transform: uppercase;
    font-size: var(--text-xs);
}
.cld-skills-item {
    display: inline-flex;
    align-items: center;
    padding: 0 var(--space-2);
    border: 1px solid var(--ds-border);
    border-radius: var(--radius-full);
    line-height: calc(var(--text-md) + var(--space-1));
}
.cld-skills-item.is-more { border-style: dashed; }
/* Same treatment as the reach line in the two states that drop the identity:
   an agent's own restrictions are not the subject when there is no data at
   all. */
.cld-hero.is-setup .cld-skills,
.cld-hero.is-bare .cld-skills { display: none; }

/* ---- The recompose ----
   Switching agents must not blink between two unrelated layouts. The orb and
   the composer never move; the four things that DO change — the name, the
   description, the reach line and the starters — settle together, lifting a
   few pixels as they arrive.

   A one-shot ANIMATION rather than a transition between two class states: a
   transition needs the start state painted before the end state, which means
   deferring the content into a frame callback, and `requestAnimationFrame`
   does not fire in a hidden tab. Content must never wait on an animation, so
   chat.js paints synchronously and restarts this with a reflow. */
@keyframes cldSettle {
    from { opacity: 0; transform: translateY(5px); }
    to   { opacity: 1; transform: none; }
}
.cld-settle {
    animation: cldSettle var(--ds-motion-med, .24s) var(--ds-ease-enter, ease-out) both;
}
/* The starters settle a beat after the identity above them, so the block reads
   as one movement with a direction rather than four things twitching at once. */
#rdb-actions-list.cld-settle { animation-delay: 60ms; }
@media (prefers-reduced-motion: reduce) {
    .cld-settle { animation: none; }
}

/* ---- The setup state keeps the orb and drops the identity ----
   Nothing is registered, so the only agent is the default one and it reaches
   nothing; "who is answering" is not the question. The branch below the hero
   states the job instead. */
.cld-hero.is-setup #chat-agent-select,
.cld-hero.is-setup .cld-desc,
.cld-hero.is-setup .cld-reach { display: none; }
.cld-hero.is-setup { margin-bottom: var(--cld-gap-block, 26px); }

/* A reader with nothing shared with them gets the same treatment one tier
   down. The description — "Agnes answers from your company's own data" — is
   a promise the state contradicts, and the reach line and the paragraph
   below it were saying the same absence a third and second time. The agent
   switcher STAYS, unlike `is-setup`: this reader may well have agents, they
   just have no data yet, and taking the control away would suggest
   otherwise. */
.cld-hero.is-bare .cld-desc,
.cld-hero.is-bare .cld-reach { display: none; }
/* Both states drop two elements from the hero and keep its bottom margin, so
   the gap under the identity became the hero's margin PLUS the following
   paragraph's own — ~100px of nothing between a name and the sentence
   explaining it, which reads as a failed render rather than as breathing
   room. One step from the scale, in both. */
.cld-hero.is-bare { margin-bottom: var(--cld-gap-block, 26px); }

/* The greeting locates the reader; it must not compete with the heading. */
.cld-greet {
    /* Centred with the rest of the column — it opens a composition whose axis
       is the orb below it. */
    display: flex; align-items: center; justify-content: center; gap: 6px;
    margin: 0;
    font-size: 13px; font-weight: 500;
    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. FIRST VISIT only in
   the ordinary state — see the template; the zero state keeps it, because
   there it is a live fact about the instance rather than an introduction.

   It used to share this rule with `.cld-agent-intro-role`, the retired
   "you're chatting with X" block's own line. The hero's `.cld-desc` carries
   the agent's role now, at its own size, so nothing else needs the lede's. */
.cld-lede {
    /* Centred on the column, held to a reading measure. It was `margin: … 0 0`
       for one pass, when the whole page hung off a left edge; on a centred
       axis that gives the lede an edge of its own. */
    margin: var(--cld-gap-tight, 5px) auto 0;
    max-width: 56ch;
    /* 15px. At 14px it sat 1px above the greeting and 1.5px above the trust
       line, so a first-time reader's main orientation text read at the same
       weight as the two grey captions bracketing it. */
    font-size: 15px; line-height: 1.55;
    color: var(--ds-text-secondary);
}

/* ---- The zero state's one action ----
   An admin looking at an instance with nothing registered is the single case
   on this page where a move outranks the composer, and the template's heading
   and lede already say so. One filled button closes that block — filled
   because it IS the action (design-system rule 5), and `.btn.btn-primary`
   rather than a hand-built control so it takes the same 9px button radius as
   every other labelled button in the product.

   It replaces a whole second page layout: the setup CARD used to render above
   the composer in this state and below it in every other, so the landing's
   slot order depended on instance state and the row visibly moved the first
   time a table landed. */
.cld-act {
    display: flex;
    /* `inner`, less the intro column's own flex gap, so it BINDS to the lede
       above it. At `block` it had 26px above and 25px below and belonged to
       neither the sentence it answers nor the composer under it — the goal,
       the gap and the move are one thought and should read as one. */
    margin: calc(var(--cld-gap-inner, 10px) - var(--cld-gap-tight, 5px)) 0 0;
}
.cld-act-btn { text-decoration: none; }

/* ---- The introduction's closing line ----
   ONE claim, no link, first visit only.

   It was "Secure. Private. Always in sync." beside a "See how {brand} works"
   link, and both halves had to go:

     • The third of those three words is a promise this product cannot keep,
       and the proof was on the same screen — the rail's own conversation list
       carried "Why did Tuesday's sync fail?". Three abstract adjectives were
       also doing less work than one specific sentence, so the copy names the
       property a reader of a self-hosted product actually wants to know.
     • The link had to go for a mechanical reason as well as a compositional
       one. Compositionally it was a third copy of a route the rail already
       carries ("How {brand} works") and the only thing pointing AWAY from the
       composer inside the introduction. Mechanically it was worse: arriving at
       /how-it-works sets `use_anywhere` on the reader's journey row, which is
       the flag that retires the contextual tools offer — so a first-time
       reader who clicked "See how {brand} works" out of ordinary curiosity
       permanently burned an offer they had not yet qualified to see and now
       never would.

   Left-aligned on the column with the rest of the introduction. */

/* ---- The one contextual offer, under the composer ----
   A LINE, not a card, and the difference is deliberate: it navigates away from
   the composer, so it may be discoverable and must not be prominent. It is on
   screen only for a reader who has asked something and never opened the
   connect page, and it retires itself server-side the moment they follow it
   (see the template).

   Bordered on hover only. At rest it is text with a glyph — the quietest thing
   on the page, one step below the greeting — because a reader who does not want
   it should be able to not see it. */
.cld-offer {
    /* WRAPS INSIDE THE SENTENCE, not around it. It ellipsised at 375px, hiding
       a third of the line and cutting it mid-clause after "Claude Code,";
       letting the ROW wrap instead was worse — the text claimed its full
       content width and ejected both glyphs onto lines of their own, so at a
       388px column the line became three left-flush pieces 97px tall.
       So the row never wraps, the text is the flexible item and wraps within
       itself, and both glyphs sit on its first line. */
    display: inline-flex; align-items: flex-start; flex-wrap: nowrap; gap: 8px;
    max-width: 100%;
    /* No horizontal padding at rest, so the glyph starts on the column's edge
       with the selector's glyph above it and the heading's first letter above
       that. The earlier version padded the box and pulled it back by the same
       12px, which put its hover surface 12px OUTSIDE the column — a broken
       left edge on the one page whose point is that the edge is unbroken.
       The hover surface is drawn by an inset shadow instead (below), which
       needs no box offset at all. */
    padding: 7px 0;
    color: var(--ds-text-muted);
    font-size: 13px; font-weight: 500;
    text-decoration: none;
    transition: color var(--ds-motion-fast, 120ms) var(--ds-ease-standard, ease);
}
/* Both glyphs hold their size and sit on the sentence's first line — the
   14px/13px boxes are centred against a 13px x 1.45 line. */
.cld-offer > svg { flex: 0 0 auto; margin-top: 2px; }
.cld-offer > span:not(.cld-offer-go) { flex: 1 1 auto; min-width: 0; line-height: 1.45; }
.cld-offer-go { flex: 0 0 auto; display: inline-flex; margin-top: 3px; opacity: 0.7; }
/* Hover is ink and an underline, with no box at all. A resting box would have
   to be padded, and padding moves the glyph off the column's edge — the one
   thing this page's composition is built on. It is a text link; text links do
   not need a surface to be hoverable. */
.cld-offer:hover {
    color: var(--ds-primary);
}
.cld-offer:hover span:not(.cld-offer-go) { text-decoration: underline; }
.cld-offer:hover .cld-offer-go { opacity: 1; }

@media (max-width: 820px) {
    /* The separator is a horizontal device; wrapped, it reads as a stray tick. */
}


/* 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; }
