/* CCS LLM Gateway -- stylesheet
   University of Kentucky, Center for Computational Sciences

   The rules of this file are written down in docs/ui-conventions.md. In short:

     * Every colour is a custom property. Nothing hard-codes a hex outside the
       token blocks below, so light and dark stay in step by construction.
     * Four button variants, one table style, one badge style. If you find
       yourself needing a fifth, change the four rather than adding one.
     * Space comes from the --sp-* scale, type from the --fs-* scale.
     * Density is a feature: this console is read by operators who want the
       numbers on one screen. Whitespace groups things; it does not fill space.
   ------------------------------------------------------------------------ */

/* =========================================================================
   1. Tokens
   ========================================================================= */

:root {
    /* --- Type scale (root is 16px; the UI runs one step below prose) --- */
    --fs-2xs:   0.6875rem;   /* 11px  table/stat labels, meta */
    --fs-xs:    0.75rem;     /* 12px  badges, captions */
    --fs-sm:    0.8125rem;   /* 13px  dense table text, buttons */
    --fs-base:  0.875rem;    /* 14px  UI body */
    --fs-md:    0.9375rem;   /* 15px  prose body */
    --fs-lg:    1.0625rem;   /* 17px  h3 / card titles */
    --fs-xl:    1.25rem;     /* 20px  h2 */
    --fs-2xl:   1.5rem;      /* 24px  h1 */
    --fs-3xl:   1.875rem;    /* 30px  hero */

    --lh-tight: 1.25;
    --lh-snug:  1.4;
    --lh-body:  1.55;

    /* --- Space scale --- */
    --sp-1: 4px;
    --sp-2: 8px;
    --sp-3: 12px;
    --sp-4: 16px;
    --sp-5: 24px;
    --sp-6: 32px;
    --sp-7: 48px;

    /* --- Radii --- */
    --r-sm:   4px;
    --r-md:   6px;
    --r-lg:   10px;
    --r-pill: 999px;

    /* --- Measure: prose never runs wider than this, at any viewport --- */
    --measure: 74ch;
    --measure-narrow: 62ch;

    /* --- Fonts (system only: this site loads no remote asset, ever) --- */
    --font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
            'Helvetica Neue', Arial, 'Liberation Sans', sans-serif;
    --font-mono: ui-monospace, 'SF Mono', SFMono-Regular, Menlo, Consolas,
                 'Liberation Mono', monospace;

    /* --- Surfaces & text (light) --- */
    --bg-page:     #f4f6f8;
    --bg-surface:  #ffffff;
    --bg-subtle:   #eef1f5;   /* table heads, inline code, inset panels */
    --bg-inset:    #e6eaf0;   /* meter tracks, deeper insets */
    --bg-hover:    #f5f7fa;

    --border:        #dde2e9;
    --border-strong: #c3cad6;

    --text:        #151a21;
    --text-muted:  #56606f;
    --text-faint:  #666e79;
    --text-invert: #ffffff;
    --on-brand:    #ffffff;   /* text on a --brand fill */
    /* Text on a --danger fill (the admin nav's critical count). Same trap
       as --on-brand: the dark theme LIGHTENS --danger, and white on a light
       salmon is unreadable, so the pair is defined in all three blocks. */
    --on-danger:   #ffffff;

    /* --- Brand --- */
    --brand:        #005dab;   /* UK blue */
    --brand-strong: #00427c;
    --brand-weak:   #e7f0f9;
    --brand-text:   #00548f;   /* brand as body text: >= 4.5:1 on surface */

    /* --- Semantic --- */
    --ok:        #0c6b45;
    --ok-bg:     #ddf3e7;
    --ok-border: #a9dcc3;
    --warn:        #8a5300;
    --warn-bg:     #fbeed6;
    --warn-border: #ecd39b;
    --danger:        #b02218;
    --danger-bg:     #fce9e7;
    --danger-border: #f0bbb5;
    --info:        #21497f;
    --info-bg:     #e5ecf8;
    --info-border: #bccfec;

    /* --- Code blocks: one pair, used everywhere --- */
    --code-bg:     #1b2230;
    --code-text:   #e3e9f2;
    --code-border: #2b3446;

    /* --- Data visualisation (charts.py) ---
       charts.py writes no hex; every mark is var(--viz-*), so the palette
       lives here with the rest of the colour and the SVG follows the theme
       without re-rendering. The structural slots track the surface tokens
       above deliberately: a chart drawn on --bg-surface must be measured
       against --bg-surface, and aliasing them keeps the two from drifting.
       Measured on this surface: ink 17.5:1, ink-2 6.4:1, muted 5.2:1 (all
       text, >= 4.5:1); s1 5.1:1, s2 4.5:1, fail 6.8:1 (marks, >= 3:1).
       s1/s2 and s1/fail stay separated under protanopia, deuteranopia and
       tritanopia -- those are the two pairs that ever share a chart. */
    --viz-grid:    #dde2e9;   /* gridlines: the --border hairline */
    --viz-axis:    #c3cad6;   /* baseline and idle track: --border-strong */
    --viz-ink:     #151a21;   /* direct-labelled values */
    --viz-ink-2:   #56606f;   /* row labels */
    --viz-muted:   #666e79;   /* ticks, "idle", empty-state message */
    --viz-s1:      #1f6fbf;   /* slot 1: prompt tokens, 2xx, ranked bars */
    --viz-s2:      #c2560f;   /* slot 2: completion tokens */
    --viz-fail:    #b02218;   /* non-2xx. A status, never a series colour */

    /* --- Effects --- */
    --focus: #0a6fd0;
    --shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.06);
    --shadow-md: 0 2px 8px rgba(16, 24, 40, 0.10);
    --speed: 120ms;

    color-scheme: light;
}

/* Dark theme. Two selectors, one token block: the media query serves visitors
   whose OS asks for dark and who have not chosen otherwise; the attribute
   serves the explicit choice made by the header toggle. Every token defined
   above is redefined here -- a half-swap is how a theme ends up with black
   text on a black panel. */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --bg-page:     #0f141b;
        --bg-surface:  #161c25;
        --bg-subtle:   #1d242f;
        --bg-inset:    #232b37;
        --bg-hover:    #1c2430;

        --border:        #2b3543;
        --border-strong: #3d4959;

        --text:        #e7ecf3;
        --text-muted:  #a6b0be;
        --text-faint:  #808b99;
        --text-invert: #0f141b;
        --on-brand:    #08131d;   /* white on the lightened brand is 2.9:1 */
        --on-danger:   #2a0806;   /* near-black on the lightened danger */

        --brand:        #4f9fe0;
        --brand-strong: #7bbcef;
        --brand-weak:   #14293c;
        --brand-text:   #74b6ec;

        --ok:        #5fd39b;
        --ok-bg:     #102a20;
        --ok-border: #1f4a37;
        --warn:        #f0c36b;
        --warn-bg:     #2b2211;
        --warn-border: #4d3d1a;
        --danger:        #f0938b;
        --danger-bg:     #2d1715;
        --danger-border: #522722;
        --info:        #8ab4ea;
        --info-bg:     #16233a;
        --info-border: #2b4067;

        --code-bg:     #0c1119;
        --code-text:   #d8e2ee;
        --code-border: #202b3a;

        /* Measured on the dark --bg-surface: ink 14.4:1, ink-2 7.8:1,
           muted 5.0:1; s1 6.6:1, s2 6.8:1, fail 6.4:1. Same CVD pairs hold. */
        --viz-grid:    #2b3543;
        --viz-axis:    #3d4959;
        --viz-ink:     #e7ecf3;
        --viz-ink-2:   #a6b0be;
        --viz-muted:   #808b99;
        --viz-s1:      #5aa7e8;
        --viz-s2:      #f0894a;
        --viz-fail:    #ef7d74;

        --focus: #6fb2f0;
        --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.5);
        --shadow-md: 0 2px 10px rgba(0, 0, 0, 0.55);

        color-scheme: dark;
    }
}

:root[data-theme="dark"] {
    --bg-page:     #0f141b;
    --bg-surface:  #161c25;
    --bg-subtle:   #1d242f;
    --bg-inset:    #232b37;
    --bg-hover:    #1c2430;

    --border:        #2b3543;
    --border-strong: #3d4959;

    --text:        #e7ecf3;
    --text-muted:  #a6b0be;
    --text-faint:  #808b99;
    --text-invert: #0f141b;
    --on-brand:    #08131d;   /* white on the lightened brand is 2.9:1 */
    --on-danger:   #2a0806;   /* near-black on the lightened danger */

    --brand:        #4f9fe0;
    --brand-strong: #7bbcef;
    --brand-weak:   #14293c;
    --brand-text:   #74b6ec;

    --ok:        #5fd39b;
    --ok-bg:     #102a20;
    --ok-border: #1f4a37;
    --warn:        #f0c36b;
    --warn-bg:     #2b2211;
    --warn-border: #4d3d1a;
    --danger:        #f0938b;
    --danger-bg:     #2d1715;
    --danger-border: #522722;
    --info:        #8ab4ea;
    --info-bg:     #16233a;
    --info-border: #2b4067;

    --code-bg:     #0c1119;
    --code-text:   #d8e2ee;
    --code-border: #202b3a;

    /* Same dark viz set as the media-query block above. Both are needed: the
       media query serves an OS-dark visitor who has not chosen, the attribute
       serves the header toggle. Defining the viz slots in only one of them is
       how a chart ends up light-only while the page around it is dark. */
    --viz-grid:    #2b3543;
    --viz-axis:    #3d4959;
    --viz-ink:     #e7ecf3;
    --viz-ink-2:   #a6b0be;
    --viz-muted:   #808b99;
    --viz-s1:      #5aa7e8;
    --viz-s2:      #f0894a;
    --viz-fail:    #ef7d74;

    --focus: #6fb2f0;
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.5);
    --shadow-md: 0 2px 10px rgba(0, 0, 0, 0.55);

    color-scheme: dark;
}

/* =========================================================================
   2. Reset and base
   ========================================================================= */

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

html { scroll-behavior: smooth; scroll-padding-top: 72px; }

body {
    font-family: var(--font);
    font-size: var(--fs-md);
    line-height: var(--lh-body);
    color: var(--text);
    background: var(--bg-page);
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    -webkit-text-size-adjust: 100%;
}

img { max-width: 100%; height: auto; }

/* =========================================================================
   3. Typography and links
   ========================================================================= */

h1, h2, h3, h4 { line-height: var(--lh-tight); font-weight: 650; color: var(--text); }
h1 { font-size: var(--fs-2xl); letter-spacing: -0.015em; }
h2 { font-size: var(--fs-xl); letter-spacing: -0.01em; }
h3 { font-size: var(--fs-lg); }
h4 { font-size: var(--fs-base); }

p + p { margin-top: var(--sp-2); }

a {
    color: var(--brand-text);
    text-decoration: underline;
    text-underline-offset: 0.15em;
    text-decoration-thickness: 1px;
    text-decoration-color: color-mix(in srgb, currentColor 40%, transparent);
    transition: color var(--speed), text-decoration-color var(--speed);
}
a:hover { color: var(--brand-strong); text-decoration-color: currentColor; }

strong, b { font-weight: 650; }
small { font-size: var(--fs-xs); }

/* One focus treatment for the whole site. Never remove it; if a control needs
   a different shape, change the radius, not the ring. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: 2px;
    border-radius: var(--r-sm);
}

.skip-link {
    position: absolute;
    top: -100%;
    left: var(--sp-4);
    background: var(--brand);
    color: var(--on-brand);
    padding: var(--sp-2) var(--sp-4);
    border-radius: var(--r-md);
    z-index: 999;
    text-decoration: none;
    font-weight: 600;
}
.skip-link:focus { top: var(--sp-2); }

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

/* =========================================================================
   4. Layout
   ========================================================================= */

.container {
    width: 100%;
    max-width: 1040px;
    margin: 0 auto;
    padding: var(--sp-5) var(--sp-4) var(--sp-6);
}
/* Opt-in for the admin console, which is tabular and wants the width. Capped
   so a 2560px display gets margins instead of 2400px-long table rows. */
.container-wide { max-width: 1440px; }

/* Prose that is not in a card still gets a measure. A paragraph should never
   run the full width of a wide container. */
.prose, .lede, .page-note { max-width: var(--measure); }

/* Grid, not flex: the lede has to occupy its own row while still being capped
   at a readable measure. In a flex row, max-width shrinks the item enough that
   it stops wrapping and sits beside the title. */
.page-header {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    column-gap: var(--sp-4);
    row-gap: var(--sp-2);
    padding-bottom: var(--sp-3);
    margin-bottom: var(--sp-4);
    border-bottom: 1px solid var(--border);
}
.page-header > * { min-width: 0; }
.page-header h1 { font-size: var(--fs-2xl); color: var(--text); }
.page-header .lede {
    grid-column: 1 / -1;
    color: var(--text-muted);
    font-size: var(--fs-base);
}

.section { margin-bottom: var(--sp-5); }
.section > h2 {
    font-size: var(--fs-lg);
    margin-bottom: var(--sp-3);
    padding-bottom: var(--sp-2);
    border-bottom: 1px solid var(--border);
}
.section-header {
    display: flex;
    align-items: baseline;
    justify-content: flex-start;
    flex-wrap: wrap;
    gap: var(--sp-2);
    margin-bottom: var(--sp-3);
    padding-bottom: var(--sp-2);
    border-bottom: 1px solid var(--border);
}
.section-header h2 { font-size: var(--fs-lg); border: 0; padding: 0; margin: 0; }
.section-header .text-muted { font-size: var(--fs-xs); }

/* Two-up grid used across the admin console. */
.admin-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 400px), 1fr));
    gap: var(--sp-4);
    align-items: start;
}
/* min-width: 0 for the same reason .page-header > * has it. A grid item's
   automatic minimum is its min-content width, so one wide child -- a chart
   with a min-width, a table of unbreakable model ids -- widens the track past
   the viewport and scrolls the whole PAGE sideways, instead of scrolling
   inside its own .viz-scroll / .table-wrap where it was meant to. */
.admin-grid > .section { margin-bottom: 0; min-width: 0; }

/* =========================================================================
   5. Site header, navigation, footer
   ========================================================================= */

.site-header {
    background: var(--bg-surface);
    border-bottom: 1px solid var(--border);
    box-shadow: var(--shadow-sm);
    padding: 0 var(--sp-4);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    flex-wrap: wrap;
    min-height: 56px;
    position: sticky;
    top: 0;
    z-index: 50;
}
.header-left { display: flex; align-items: center; gap: var(--sp-3); min-width: 0; }
.header-logos { display: flex; align-items: center; gap: var(--sp-2); }
.header-logos img {
    height: 34px;
    width: auto;
    display: block;
    border-radius: 2px;
}
/* The lockups are JPEGs on white. In dark mode they need a plate, or they read
   as two glowing rectangles floating on a dark bar. */
:root[data-theme="dark"] .header-logos img { background: #fff; padding: 2px 3px; }
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .header-logos img { background: #fff; padding: 2px 3px; }
}
.header-title a {
    font-size: var(--fs-base);
    font-weight: 650;
    color: var(--text);
    text-decoration: none;
    white-space: nowrap;
}
.header-title a:hover { color: var(--brand-text); }

.header-nav { display: flex; align-items: center; }
.nav-links {
    display: flex;
    align-items: center;
    gap: var(--sp-1);
    list-style: none;
}
.nav-links a {
    display: block;
    padding: 6px 10px;
    color: var(--text-muted);
    text-decoration: none;
    font-size: var(--fs-base);
    font-weight: 500;
    border-radius: var(--r-md);
    white-space: nowrap;
    transition: background var(--speed), color var(--speed);
}
.nav-links a:hover { background: var(--bg-subtle); color: var(--text); }
.nav-links a[aria-current="page"] {
    color: var(--brand-text);
    background: var(--brand-weak);
    font-weight: 600;
}
.nav-admin { color: var(--brand-text); font-weight: 600; }
.nav-user {
    display: flex; align-items: center; gap: var(--sp-2);
    margin-left: var(--sp-2); padding-left: var(--sp-3);
    border-left: 1px solid var(--border);
}
.nav-user-name { font-size: var(--fs-sm); color: var(--text-faint); }
.btn-logout { color: var(--text-muted); }

/* Theme toggle: two glyphs, one button, no library. */
.theme-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px; height: 30px;
    padding: 0;
    border: 1px solid var(--border);
    border-radius: var(--r-md);
    background: var(--bg-surface);
    color: var(--text-muted);
    cursor: pointer;
    transition: background var(--speed), color var(--speed), border-color var(--speed);
}
.theme-toggle:hover { background: var(--bg-subtle); color: var(--text); border-color: var(--border-strong); }
.theme-toggle svg { width: 15px; height: 15px; }
.theme-toggle .icon-dark { display: none; }
:root[data-theme="dark"] .theme-toggle .icon-dark { display: block; }
:root[data-theme="dark"] .theme-toggle .icon-light { display: none; }
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .theme-toggle .icon-dark { display: block; }
    :root:not([data-theme="light"]) .theme-toggle .icon-light { display: none; }
}

.nav-toggle {
    display: none;
    background: none;
    border: 1px solid var(--border);
    border-radius: var(--r-md);
    padding: 6px 8px;
    cursor: pointer;
}
.nav-toggle-bar {
    display: block; width: 18px; height: 2px;
    background: var(--text); border-radius: 2px;
}
.nav-toggle-bar + .nav-toggle-bar { margin-top: 4px; }

.site-footer {
    margin-top: auto;
    background: var(--bg-surface);
    border-top: 1px solid var(--border);
    color: var(--text-faint);
    font-size: var(--fs-xs);
    text-align: center;
    padding: var(--sp-4) var(--sp-4);
}
.footer-links { margin-top: var(--sp-1); }
.footer-links a { color: var(--text-muted); text-decoration: none; margin: 0 var(--sp-2); }
.footer-links a:hover { color: var(--brand-text); text-decoration: underline; }

/* =========================================================================
   6. Buttons -- four variants and one size modifier. That is the whole set.
   ========================================================================= */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-1);
    padding: 5px 12px;
    font-family: inherit;
    font-size: var(--fs-sm);
    font-weight: 550;
    line-height: 1.45;
    color: inherit;
    /* Stated, not inherited. Without it a .btn carrying no variant -- every
       .btn-copy on /, /status and /try -- falls through to the UA's ButtonFace
       and paints itself flat grey: #efefef in light, #6b6b6b with white text in
       dark. That is a fifth button look nobody designed, arriving by default. */
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--r-md);
    cursor: pointer;
    text-decoration: none;
    white-space: nowrap;
    transition: background var(--speed), border-color var(--speed), color var(--speed);
}
.btn-sm { padding: 3px 8px; font-size: var(--fs-xs); }

.btn-primary { background: var(--brand); border-color: var(--brand); color: var(--on-brand); }
.btn-primary:hover { background: var(--brand-strong); border-color: var(--brand-strong); color: var(--on-brand); }

/* The workhorse. .btn-outline is the historical name for the same thing and is
   kept so templates need not all change at once; do not add a third. */
.btn-secondary, .btn-outline {
    background: var(--bg-surface);
    border-color: var(--border-strong);
    color: var(--text);
}
.btn-secondary:hover, .btn-outline:hover {
    background: var(--bg-subtle);
    border-color: var(--text-faint);
    color: var(--text);
}

.btn-ghost { background: transparent; border-color: transparent; color: var(--text-muted); }
.btn-ghost:hover { background: var(--bg-subtle); color: var(--text); }

/* Destructive and affirmative actions are tinted, not filled: a table row with
   four filled buttons in it is a slot machine. */
.btn-danger { background: var(--danger-bg); border-color: var(--danger-border); color: var(--danger); }
.btn-danger:hover { background: var(--danger); border-color: var(--danger); color: var(--text-invert); }
.btn-success { background: var(--ok-bg); border-color: var(--ok-border); color: var(--ok); }
.btn-success:hover { background: var(--ok); border-color: var(--ok); color: var(--text-invert); }
.btn-warning { background: var(--warn-bg); border-color: var(--warn-border); color: var(--warn); }
.btn-warning:hover { background: var(--warn); border-color: var(--warn); color: var(--text-invert); }

.btn[disabled], .btn-disabled { opacity: 0.45; pointer-events: none; }
.btn-copy { font-size: var(--fs-xs); padding: 3px 8px; }
.btn-copied { background: var(--ok-bg); border-color: var(--ok-border); color: var(--ok); }

.btn-row { display: flex; flex-wrap: wrap; gap: var(--sp-2); }

/* Segmented navigation (the admin tab bar, the day-window picker). One control,
   not seven loose buttons. */
.tabs {
    display: flex;
    flex-wrap: wrap;
    gap: 2px;
    padding: 2px;
    background: var(--bg-subtle);
    border: 1px solid var(--border);
    border-radius: var(--r-md);
}
/* Items are links where the tab is a navigation (the admin switcher) and
   buttons where it only swaps a panel in place (the /try example formats).
   One look, one control: a tab that changes what is on screen without changing
   the URL must not become a fifth button variant. */
.tabs a, .tabs button {
    padding: 4px 10px;
    font-family: inherit;
    font-size: var(--fs-sm);
    font-weight: 550;
    color: var(--text-muted);
    background: transparent;
    border: 0;
    cursor: pointer;
    text-decoration: none;
    border-radius: var(--r-sm);
    white-space: nowrap;
    transition: background var(--speed), color var(--speed);
}
.tabs a:hover, .tabs button:hover { background: var(--bg-surface); color: var(--text); }
.tabs a.is-current, .tabs button.is-current {
    background: var(--bg-surface);
    color: var(--brand-text);
    box-shadow: var(--shadow-sm);
}
.tabs-sep { width: 1px; background: var(--border); margin: 2px 4px; }

/* Two-level admin switcher: the top-level sections and, under them, the sub-nav
   for the active section. It is a direct child of the .page-header grid and
   spans BOTH columns on its own row, so it is always full-width and left-aligned
   under the page title -- never crammed into the header's narrow right column. */
.admin-nav {
    grid-column: 1 / -1;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--sp-2);
}
/* The sub-nav is a second level, so it drops the boxed frame and reads lighter;
   the active sub-tab still gets the surface + shadow from .is-current. */
.tabs-sub { background: transparent; border-color: transparent; padding: 0; margin-left: var(--sp-1); }

/* Count on a tab: the unresolved-critical badge the admin nav carries on every
   page. Filled rather than tinted, because unlike a .badge in a table this one
   has to be findable in peripheral vision -- and it is a NUMBER on a filled
   ground, so colour is not the only channel and neither is it the only cue
   (the anchor's aria-label spells out "unresolved critical alerts"). Sized
   from the tab's own font rather than a fixed height so it stays on the
   baseline when the browser text size changes. */
.tab-count {
    display: inline-block;
    min-width: 1.35em;
    padding: 0 0.35em;
    margin-left: 5px;
    border-radius: var(--r-pill);
    background: var(--danger);
    color: var(--on-danger);
    font-size: var(--fs-2xs);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    text-align: center;
    line-height: 1.5;
}

/* =========================================================================
   7. Forms
   ========================================================================= */

.form-input, select.form-input, textarea.form-input {
    width: 100%;
    padding: 5px 8px;
    font-family: inherit;
    font-size: var(--fs-sm);
    color: var(--text);
    background: var(--bg-surface);
    border: 1px solid var(--border-strong);
    border-radius: var(--r-md);
    transition: border-color var(--speed), box-shadow var(--speed);
}
.form-input::placeholder { color: var(--text-faint); }
.form-input:focus {
    outline: none;
    border-color: var(--focus);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--focus) 25%, transparent);
}
.form-input-sm { padding: 3px 7px; font-size: var(--fs-xs); width: auto; }

.form-group { position: relative; }
.form-group label {
    display: block;
    font-size: var(--fs-2xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-faint);
    margin-bottom: 2px;
}
.form-help { font-size: var(--fs-2xs); color: var(--text-faint); margin-top: 2px; }
.form-row, .filter-row {
    display: flex;
    gap: var(--sp-2);
    align-items: flex-end;
    flex-wrap: wrap;
}
.filter-actions { display: flex; gap: var(--sp-1); align-items: flex-end; }
.inline-form { display: inline-flex; gap: var(--sp-1); align-items: center; vertical-align: middle; }
.inline-actions { display: flex; gap: var(--sp-1); }
.actions-cell { white-space: nowrap; }
.actions-cell .inline-form + .inline-form { margin-left: var(--sp-1); }
.model-note-input { width: 180px; min-width: 120px; }
.search-bar { display: flex; gap: var(--sp-2); align-items: center; margin-bottom: var(--sp-3); }
.search-bar .form-input { flex: 1; max-width: 420px; }
.log-filters { margin-bottom: var(--sp-3); }

/* =========================================================================
   8. Cards, callouts, notices
   ========================================================================= */

.card {
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    padding: var(--sp-3) var(--sp-4);
}
.card-compact { padding: var(--sp-2) var(--sp-3); }
.card-centered {
    max-width: 560px;
    margin: var(--sp-6) auto;
    text-align: center;
    padding: var(--sp-5);
}
.card-icon { margin-bottom: var(--sp-3); color: var(--text-faint); }
.card-icon svg { width: 40px; height: 40px; }
.pending-icon svg { color: var(--warn); }
.error-icon svg { color: var(--danger); }
.card-centered h1 { margin-bottom: var(--sp-2); }
.card-centered p { color: var(--text-muted); font-size: var(--fs-base); }
.card-centered .hero-actions { justify-content: center; }
.card-centered .disclaimer { text-align: left; }

/* Callout: the one component for "read this before you continue". A left rule
   carries the severity; the body stays on the page background family so a page
   of callouts does not read as a page of alerts. */
.callout {
    max-width: 92ch;
    border: 1px solid var(--border);
    border-left: 3px solid var(--text-faint);
    border-radius: var(--r-md);
    background: var(--bg-surface);
    padding: var(--sp-3);
    font-size: var(--fs-base);
    margin-bottom: var(--sp-3);
}
.callout > h3, .callout > h2 {
    font-size: var(--fs-base);
    margin-bottom: var(--sp-1);
}
.callout p, .callout li { max-width: var(--measure); }
.callout-info   { border-left-color: var(--brand); background: var(--info-bg); border-color: var(--info-border); }
.callout-warn   { border-left-color: var(--warn); background: var(--warn-bg); border-color: var(--warn-border); }
.callout-danger { border-left-color: var(--danger); background: var(--danger-bg); border-color: var(--danger-border); }
.callout-ok     { border-left-color: var(--ok); background: var(--ok-bg); border-color: var(--ok-border); }
/* The rule and the heading carry the severity. Tinting the body text as well
   makes a paragraph harder to read and every callout look like an alarm. */
.callout-warn > h3, .callout-warn > h2     { color: var(--warn); }
.callout-danger > h3, .callout-danger > h2 { color: var(--danger); }
.callout-ok > h3, .callout-ok > h2         { color: var(--ok); }
.callout-info > h3, .callout-info > h2     { color: var(--info); }

.testing-banner {
    display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap;
    background: var(--warn-bg);
    border: 1px solid var(--warn-border);
    border-left: 3px solid var(--warn);
    border-radius: var(--r-md);
    padding: var(--sp-2) var(--sp-3);
    margin-bottom: var(--sp-3);
    font-size: var(--fs-sm);
    color: var(--warn);
}
.testing-banner .btn { margin-left: auto; }

/* Policy block on the landing page: dense, legible, not a shouting box. */
.disclaimer {
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    padding: var(--sp-3) var(--sp-4);
    font-size: var(--fs-base);
    /* Two columns only where a column is still wide enough to read: the same
       block is used on a 500px card. */
    columns: 2 34ch;
    column-gap: var(--sp-5);
    column-rule: 1px solid var(--border);
}
.disclaimer p { break-inside: avoid; margin-bottom: var(--sp-2); max-width: var(--measure); }
.disclaimer p:last-child { margin-bottom: 0; }
@media (max-width: 760px) { .disclaimer { columns: 1; } }

/* =========================================================================
   9. Tables
   ========================================================================= */

.table-wrap {
    overflow-x: auto;
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    background: var(--bg-surface);
}
.data-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--fs-sm);
    font-variant-numeric: tabular-nums;
}
.data-table th {
    position: sticky;
    top: 0;
    z-index: 1;
    background: var(--bg-subtle);
    padding: 6px 10px;
    text-align: left;
    font-weight: 600;
    font-size: var(--fs-2xs);
    text-transform: uppercase;
    letter-spacing: 0.045em;
    color: var(--text-faint);
    white-space: nowrap;
    border-bottom: 1px solid var(--border);
}
.data-table td {
    padding: 5px 10px;
    border-top: 1px solid var(--border);
    vertical-align: top;
}
.data-table tbody tr:first-child td { border-top: 0; }
.data-table tbody tr:hover td { background: var(--bg-hover); }
.data-table-compact th, .data-table-compact td { padding: 4px 8px; font-size: var(--fs-xs); }

/* Links inside a data table borrow the row's colour. Forty blue underlines in
   a column is not information, it is noise; the underline appears on hover. */
.data-table a { color: inherit; text-decoration: none; }
.data-table a:hover { color: var(--brand-text); text-decoration: underline; }
.data-table code { background: transparent; border: 0; padding: 0; color: var(--text-muted); }

/* Numeric columns. Add to both the th and the td. */
.data-table .num { text-align: right; font-variant-numeric: tabular-nums; }
.data-table .nowrap { white-space: nowrap; }

.empty-state { text-align: center; padding: var(--sp-6) var(--sp-4); color: var(--text-faint); }

.pagination {
    display: flex; align-items: center; justify-content: center;
    gap: var(--sp-1); margin-top: var(--sp-3);
}
.page-info { font-size: var(--fs-xs); color: var(--text-muted); padding: 0 var(--sp-2); }

.stats-row {
    display: flex; gap: var(--sp-4); flex-wrap: wrap;
    font-size: var(--fs-sm); margin-bottom: var(--sp-3);
    padding: var(--sp-2) var(--sp-3);
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: var(--r-md);
}

/* =========================================================================
   10. Badges, chips, tags
   ========================================================================= */

.badge {
    display: inline-block;
    padding: 1px 7px;
    font-size: var(--fs-2xs);
    font-weight: 600;
    line-height: 1.5;
    border-radius: var(--r-pill);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    white-space: nowrap;
    border: 1px solid transparent;
}
.badge-success { background: var(--ok-bg);     color: var(--ok);     border-color: var(--ok-border); }
.badge-warning { background: var(--warn-bg);   color: var(--warn);   border-color: var(--warn-border); }
.badge-danger  { background: var(--danger-bg); color: var(--danger); border-color: var(--danger-border); }
.badge-muted   { background: var(--bg-subtle); color: var(--text-muted); border-color: var(--border); }
.badge-cold, .badge-info { background: var(--info-bg); color: var(--info); border-color: var(--info-border); }
.badge-service { background: var(--bg-subtle); color: var(--text-muted); border: 1px solid var(--border);
                 border-radius: var(--r-pill); padding: 0 6px; font-size: var(--fs-2xs);
                 text-transform: uppercase; letter-spacing: 0.04em; }
.user-cell-service { display: inline-flex; align-items: center; gap: var(--sp-1); }

/* Tag: a lower-key label than a badge. Sentence case, no pill. */
.model-tag, .tag {
    display: inline-block;
    padding: 1px 6px;
    font-size: var(--fs-2xs);
    color: var(--text-muted);
    background: var(--bg-subtle);
    border: 1px solid var(--border);
    border-radius: var(--r-sm);
    white-space: nowrap;
}
/* Warn variant: a tag that flags a footgun, not just an attribute. */
.model-tag-warn, .tag-warn {
    color: var(--warn);
    background: var(--warn-bg);
    border-color: var(--warn-border);
}
.tag-list { display: flex; gap: var(--sp-1); flex-wrap: wrap; }

.filter-label {
    font-size: var(--fs-2xs); font-weight: 600; text-transform: uppercase;
    letter-spacing: 0.045em; color: var(--text-faint);
}
.filter-sep { color: var(--border-strong); padding: 0 var(--sp-1); }

/* =========================================================================
   11. Stat tiles
   ========================================================================= */

.stats-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    gap: var(--sp-2);
    margin-bottom: var(--sp-4);
}
.stats-grid-6 { grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); }
.stat-card {
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: var(--r-md);
    padding: var(--sp-2) var(--sp-3);
}
.stat-value {
    font-size: var(--fs-xl);
    font-weight: 650;
    line-height: 1.15;
    color: var(--text);
    font-variant-numeric: tabular-nums;
}
.stat-label {
    font-size: var(--fs-2xs);
    color: var(--text-faint);
    margin-top: 2px;
    text-transform: uppercase;
    letter-spacing: 0.045em;
    line-height: 1.3;
}
/* Two severities on a tile, and the difference is load-bearing on the gateway
   console: .stat-danger is a service failure (something broke), .stat-warning
   is a request refused at the door (somebody's key is wrong). Painting both the
   same colour is how a bad-key flood gets read as an outage. Neither carries
   the meaning alone -- the label and the section heading say which is which. */
.stat-warning .stat-value { color: var(--warn); }
.stat-danger .stat-value  { color: var(--danger); }
a.stat-card, a.stat-link {
    text-decoration: none; color: inherit; display: block;
    transition: border-color var(--speed), background var(--speed);
}
a.stat-card:hover, a.stat-link:hover { border-color: var(--border-strong); background: var(--bg-hover); }

/* =========================================================================
   12. Code
   ========================================================================= */

/* Inline code carries a tint, not a border: a paragraph with six bordered
   chips in it reads as a form. */
code {
    font-family: var(--font-mono);
    font-size: 0.9em;
    background: var(--bg-subtle);
    padding: 0 4px;
    border-radius: var(--r-sm);
    word-break: break-word;
}
pre {
    background: var(--code-bg);
    color: var(--code-text);
    padding: var(--sp-3);
    border-radius: var(--r-md);
    overflow-x: auto;
    font-size: var(--fs-xs);
    line-height: 1.55;
    tab-size: 2;
}
pre code {
    background: transparent; border: 0; padding: 0; color: inherit;
    font-size: inherit; white-space: pre;
}
.code-block {
    border: 1px solid var(--border);
    border-radius: var(--r-md);
    overflow: hidden;
    background: var(--code-bg);
}
.code-block pre { border-radius: 0; }
.code-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--sp-2);
    background: var(--bg-subtle);
    border-bottom: 1px solid var(--border);
    padding: 4px var(--sp-3);
    font-size: var(--fs-xs);
    font-weight: 600;
    color: var(--text-muted);
}
.code-examples {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 380px), 1fr));
    gap: var(--sp-3);
    align-items: start;
}

/* A labelled value with a copy button: base URLs, endpoints, key prefixes. */
.copyfield {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    flex-wrap: wrap;
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: var(--r-md);
    padding: var(--sp-2) var(--sp-3);
    max-width: 640px;
}
.copyfield-label {
    font-size: var(--fs-2xs); font-weight: 600; text-transform: uppercase;
    letter-spacing: 0.045em; color: var(--text-faint); flex-basis: 100%;
}
.copyfield code {
    font-size: var(--fs-base);
    background: transparent;
    border: 0;
    padding: 0;
    color: var(--text);
}

/* =========================================================================
   13. Model catalogue (public /status)
   ========================================================================= */

.catalog-summary {
    display: flex; flex-wrap: wrap; align-items: center;
    gap: var(--sp-1) var(--sp-3);
    font-size: var(--fs-sm); color: var(--text-muted);
    margin-bottom: var(--sp-4);
}
.catalog-summary .badge { margin-right: 2px; }

/* One grid for every model, with the category headings as full-width rows.
   Separate grids per category left half-empty rows wherever a category had one
   member, which reads as an unfinished page. */
.cat-head {
    grid-column: 1 / -1;
    display: flex; align-items: baseline; gap: var(--sp-2); flex-wrap: wrap;
    margin: var(--sp-2) 0 0;
    padding-bottom: var(--sp-1);
    border-bottom: 1px solid var(--border);
}
.cat-head:first-child { margin-top: 0; }
.cat-head h3 { font-size: var(--fs-base); text-transform: capitalize; }
.cat-head p { font-size: var(--fs-xs); color: var(--text-muted); margin: 0; max-width: var(--measure); }

.model-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 330px), 1fr));
    gap: var(--sp-2) var(--sp-3);
    align-items: stretch;
}
.model-card {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    padding: var(--sp-3);
    /* The old detail sat in a colspan cell on a full-width table and ran to
       1900px lines. Cards cap the measure structurally. */
    max-width: 100%;
}
.model-card > * { min-width: 0; }
.model-card-head { display: flex; align-items: baseline; gap: var(--sp-2); }
.model-card-head h4 { font-size: var(--fs-base); font-weight: 650; flex: 1; }
.model-state {
    display: inline-flex; align-items: center; gap: 5px;
    font-size: var(--fs-2xs); font-weight: 600; text-transform: uppercase;
    letter-spacing: 0.04em; white-space: nowrap;
}
.model-state .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--text-faint); flex: none; }
.state-up      { color: var(--ok); }
.state-up .dot { background: var(--ok); }
.state-down      { color: var(--danger); }
.state-down .dot { background: var(--danger); }
.state-stale      { color: var(--warn); }
.state-stale .dot { background: var(--warn); }
.state-unknown      { color: var(--text-faint); }
.state-unknown .dot { background: var(--text-faint); }

.model-tagline { font-size: var(--fs-sm); color: var(--text-muted); max-width: var(--measure-narrow); }

.model-id {
    display: flex; align-items: center; gap: var(--sp-2);
    background: var(--bg-subtle);
    border: 1px solid var(--border);
    border-radius: var(--r-sm);
    padding: 2px var(--sp-2);
}
.model-id .btn {
    background: transparent; border-color: transparent; color: var(--text-muted);
    padding: 1px 6px;
}
.model-id .btn:hover { background: var(--bg-surface); border-color: var(--border-strong); color: var(--text); }
.model-id code {
    font-size: var(--fs-xs); background: transparent; padding: 0;
    color: var(--text); flex: 1;
    /* Wrap rather than scroll: a model id truncated mid-word reads as a bug,
       and the id is the one string on the card people need in full. */
    white-space: normal; overflow-wrap: anywhere;
}

/* Spec strip: label and value stay glued as one unit, which is precisely what
   the old <dl> failed to do -- a term and its value read as two loose lines. */
.spec-strip { display: flex; flex-wrap: wrap; gap: var(--sp-1) var(--sp-3); }
.spec { display: inline-flex; align-items: baseline; gap: 5px; font-size: var(--fs-xs); }
.spec-k {
    font-size: var(--fs-2xs); text-transform: uppercase; letter-spacing: 0.045em;
    color: var(--text-faint); font-weight: 600;
}
.spec-v { color: var(--text); font-variant-numeric: tabular-nums; }
.spec-v .qual { color: var(--text-faint); }

.model-for { font-size: var(--fs-xs); }
.model-for-k {
    font-size: var(--fs-2xs); text-transform: uppercase; letter-spacing: 0.045em;
    color: var(--text-faint); font-weight: 600; display: block; margin-bottom: 2px;
}
.model-list { list-style: none; display: flex; flex-direction: column; gap: 2px; }
.model-list li {
    position: relative; padding-left: var(--sp-3);
    color: var(--text-muted); max-width: var(--measure-narrow);
}
.model-list li::before {
    content: ""; position: absolute; left: 3px; top: 0.55em;
    width: 4px; height: 4px; border-radius: 50%; background: var(--border-strong);
}
.model-list-warn li::before { background: var(--warn); }
.model-list-danger li::before { background: var(--danger); }

.model-foot {
    display: flex; flex-wrap: wrap; gap: 2px var(--sp-3);
    font-size: var(--fs-2xs); color: var(--text-faint);
    border-top: 1px solid var(--border); padding-top: var(--sp-2);
    margin-top: auto;
    font-variant-numeric: tabular-nums;
}
.model-foot .spec-k { margin-right: 2px; }

/* Progressive disclosure. Closed by default: the common path is "which model
   should I use", not "what is its quantisation". */
.disclose > summary {
    cursor: pointer;
    font-size: var(--fs-xs);
    font-weight: 600;
    color: var(--brand-text);
    list-style: none;
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 2px 0;
}
.disclose > summary::-webkit-details-marker { display: none; }
.disclose > summary::before {
    content: "";
    width: 0; height: 0;
    border-left: 4px solid currentColor;
    border-top: 3.5px solid transparent;
    border-bottom: 3.5px solid transparent;
    transition: transform var(--speed);
}
.disclose[open] > summary::before { transform: rotate(90deg); }
.disclose > summary:hover { color: var(--brand-strong); }
.disclose-body {
    display: flex; flex-direction: column; gap: var(--sp-2);
    margin-top: var(--sp-2);
    padding-top: var(--sp-2);
    border-top: 1px dashed var(--border);
    font-size: var(--fs-xs);
}
.disclose-body a { word-break: break-word; }

.model-extra { display: flex; flex-wrap: wrap; gap: var(--sp-1); }

/* Live-but-undescribed models: present, honest, and out of the way. */
.model-plain {
    display: flex; flex-wrap: wrap; gap: var(--sp-1) var(--sp-2);
    align-items: center;
}
.model-plain code { font-size: var(--fs-xs); }

/* =========================================================================
   14. Server / health cards (admin)
   ========================================================================= */

.server-grid {
    display: grid;
    /* Capped tracks, not 1fr: there is usually one upstream, and a single card
       stretched across 1400px reads as a mistake. */
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 360px));
    gap: var(--sp-2);
}
.server-card {
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-left: 3px solid var(--border-strong);
    border-radius: var(--r-md);
    padding: var(--sp-2) var(--sp-3);
    text-decoration: none;
    color: inherit;
    display: block;
}
.server-online  { border-left-color: var(--ok); }
.server-offline { border-left-color: var(--danger); }
.server-disabled { border-left-color: var(--text-faint); opacity: 0.65; }
.server-header { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-1); }
.server-header h3 { font-size: var(--fs-base); flex: 1; }
.server-indicator { width: 8px; height: 8px; border-radius: 50%; background: var(--text-faint); flex: none; }
.server-online .server-indicator, .indicator-online { background: var(--ok); }
.server-offline .server-indicator, .indicator-offline { background: var(--danger); }
.indicator-disabled { background: var(--text-faint); }
.server-meta { font-size: var(--fs-xs); color: var(--text-muted); display: flex; gap: var(--sp-3); flex-wrap: wrap; }

/* =========================================================================
   15. Charts without a chart library
   Server-rendered inline SVG, drawn by charts.py. No canvas, no CDN, no
   client-side script: this site loads nothing from a third party, and the
   admin console is the last page on which to make an exception.

   There is exactly one chart implementation. There used to be two -- a set of
   Jinja div-bar macros and this -- which is how the same quantity ended up
   drawn two different ways on two admin pages. The macros are gone; if you
   need a new chart shape, add it to charts.py.

   Presentation only: every colour is a --viz-* token from section 1, and
   every geometry decision lives in charts.py. Nothing here is a hex.
   ========================================================================= */

/* The chart group. Wrapping the charts rather than spacing each one keeps the
   "everything in here is drawn by charts.py" boundary visible in the markup,
   and gives the group one gap instead of a margin on every section. */
.viz-root { display: grid; gap: var(--sp-4); margin-bottom: var(--sp-5); }
.viz-root > .admin-grid { margin: 0; }
/* Exactly the trap documented on .admin-grid > .section, one level up:
   .viz-root is a grid too, so a FULL-WIDTH section dropped straight into it --
   a nine-column table that spans both chart columns, as on /admin/gateway --
   has an automatic minimum of its own min-content width, widens the single
   implicit track past the viewport and scrolls the whole PAGE sideways. It
   cost a 985px-wide document at a 360px viewport before this line existed. */
.viz-root > .section { min-width: 0; }

/* The chart's surface. Matches .table-wrap so a chart and the table that
   carries its numbers read as one pair of panels, and -- more importantly --
   so the marks are always judged against the surface they were measured
   against (--bg-surface, both themes). */
.viz-card {
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: var(--r-md);
    padding: var(--sp-2) var(--sp-3) var(--sp-1);
}
/* Below the SVG's min-width the card scrolls rather than shrinking 10px type
   into illegibility. main.js gives an overflowing wrap a tab stop. */
.viz-scroll { overflow-x: auto; }

/* A chart's table twin is capped rather than allowed to run four times the
   height of the chart it sits beside -- thirty rows of daily totals next to a
   200px chart is a column of whitespace and a page nobody can take in. The
   table head is already sticky, so the columns stay named while it scrolls. */
.viz-root .table-wrap { max-height: 20rem; overflow-y: auto; }
.viz-svg { display: block; width: 100%; height: auto; min-width: 520px; }

.viz-grid  { stroke: var(--viz-grid);  stroke-width: 1; }
.viz-axis  { stroke: var(--viz-axis);  stroke-width: 1; }
.viz-track { stroke: var(--viz-axis);  stroke-width: 1; }

.viz-tick  { font-size: 10px;   fill: var(--viz-muted); font-variant-numeric: tabular-nums; }
.viz-label { font-size: 11px;   fill: var(--viz-ink-2); }
.viz-value { font-size: 10.5px; fill: var(--viz-ink); font-weight: 600; font-variant-numeric: tabular-nums; }
.viz-idle-text { font-size: 10px; fill: var(--viz-muted); }
.viz-empty     { font-size: 12px; fill: var(--viz-muted); }

/* Hover is a readout affordance, not decoration: the mark's <title> is the
   pointer's only feedback, so the mark brightens to say it is the one being
   read. Killed by prefers-reduced-motion along with everything else. */
.viz-mark path, .viz-mark rect { transition: filter var(--speed); }
.viz-mark:hover path, .viz-mark:hover rect { filter: brightness(1.12); }

/* Legend for the stacked charts only. A single-series chart does not get one:
   a legend with one entry is a caption pretending to be a key. */
.viz-legend {
    display: flex; gap: var(--sp-3); flex-wrap: wrap;
    font-size: var(--fs-2xs); color: var(--text-muted);
}
.viz-legend span { display: inline-flex; align-items: center; gap: 5px; }
.viz-key { width: 9px; height: 9px; border-radius: 2px; display: inline-block; }
.viz-key-s1   { background: var(--viz-s1); }
.viz-key-s2   { background: var(--viz-s2); }
.viz-key-fail { background: var(--viz-fail); }

/* Sub-line on a stat tile: the second dimension of a calendar bucket, kept to
   one line so the tile stays one value deep. */
.stat-sub {
    font-size: var(--fs-2xs); color: var(--text-muted);
    margin-top: var(--sp-1); font-variant-numeric: tabular-nums;
}
.stat-period .stat-value { font-size: var(--fs-lg); }

/* =========================================================================
   16. Hero and landing
   ========================================================================= */

.hero { padding: var(--sp-4) 0 var(--sp-3); }
.hero h1 { font-size: var(--fs-3xl); color: var(--text); letter-spacing: -0.02em; }
.hero-subtitle {
    font-size: var(--fs-md);
    color: var(--text-muted);
    max-width: var(--measure);
    margin-top: var(--sp-2);
}
.hero-actions { display: flex; gap: var(--sp-2); flex-wrap: wrap; margin-top: var(--sp-3); }

/* Numbered path: three steps, side by side, no giant circles. */
.steps {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr));
    gap: var(--sp-3);
    counter-reset: step;
    list-style: none;
}
.steps > li {
    counter-increment: step;
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    padding: var(--sp-3);
    font-size: var(--fs-base);
}
.steps > li > h3 {
    font-size: var(--fs-base);
    display: flex; align-items: baseline; gap: var(--sp-2);
    margin-bottom: var(--sp-1);
}
.steps > li > h3::before {
    content: counter(step);
    flex: none;
    display: inline-flex; align-items: center; justify-content: center;
    width: 18px; height: 18px;
    background: var(--brand-weak); color: var(--brand-text);
    border-radius: var(--r-pill);
    font-size: var(--fs-2xs); font-weight: 700;
}
.steps > li p { color: var(--text-muted); font-size: var(--fs-sm); }
/* The numbered sub-steps inside a step card (e.g. the four "Get a key" steps):
   a normal decimal list, muted and indented so it reads as detail within the card. */
.steps .substeps { margin: var(--sp-2) 0 var(--sp-2) var(--sp-4); padding: 0; color: var(--text-muted); font-size: var(--fs-sm); }
.steps .substeps > li { margin-bottom: var(--sp-1); }
.steps > li .copyfield { margin-top: var(--sp-2); max-width: none; }

.alternatives-list { list-style: none; text-align: left; }
.alternatives-list li { padding: var(--sp-2) 0; border-bottom: 1px solid var(--border); }
.alternatives-list li:last-child { border-bottom: 0; }

/* =========================================================================
   17. Documentation layout (/help)
   ========================================================================= */

.help-container { max-width: 1080px; }
.help-layout { display: grid; grid-template-columns: 210px minmax(0, 1fr); gap: var(--sp-5); }

.help-sidebar {
    position: sticky;
    top: 68px;
    align-self: start;
    max-height: calc(100vh - 84px);
    overflow-y: auto;
    padding-right: var(--sp-2);
    scrollbar-width: thin;
}
.help-nav-wrap > summary {
    display: none;                       /* desktop: the nav is simply there */
    cursor: pointer;
    padding: var(--sp-2) 0;
    list-style: none;
}
.help-nav-wrap > summary::-webkit-details-marker { display: none; }
.help-sidebar h2 {
    font-size: var(--fs-2xs); text-transform: uppercase; letter-spacing: 0.05em;
    color: var(--text-faint); margin-bottom: var(--sp-2);
}
.help-nav, .help-nav ul { list-style: none; }
.help-nav-section { break-inside: avoid; display: block; }
.help-nav-group {
    display: block;
    font-size: var(--fs-2xs);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-weight: 700;
    color: var(--text-faint);
    margin: var(--sp-3) 0 var(--sp-1);
    padding-top: var(--sp-2);
    border-top: 1px solid var(--border);
}
.help-nav-section:first-of-type .help-nav-group { margin-top: 0; border-top: 0; padding-top: 0; }
.help-link {
    display: block;
    padding: 3px var(--sp-2);
    font-size: var(--fs-sm);
    color: var(--text-muted);
    text-decoration: none;
    border-radius: var(--r-sm);
    border-left: 2px solid transparent;
}
.help-link:hover { background: var(--bg-subtle); color: var(--text); }
.help-link.active {
    border-left-color: var(--brand);
    color: var(--brand-text);
    background: var(--brand-weak);
    font-weight: 600;
}

.help-content { --measure: 80ch; }
.help-content section {
    margin-bottom: var(--sp-5);
    padding-bottom: var(--sp-4);
    border-bottom: 1px solid var(--border);
}
.help-content section:last-child { border-bottom: 0; }
.help-content h2 { font-size: var(--fs-xl); margin-bottom: var(--sp-2); scroll-margin-top: 72px; }
.help-content h3 { font-size: var(--fs-base); margin: var(--sp-3) 0 var(--sp-1); color: var(--text); }
.help-content p, .help-content li { font-size: var(--fs-base); max-width: var(--measure); }
.help-content ol, .help-content ul { padding-left: 1.3em; margin: var(--sp-1) 0; }
.help-content li { margin-bottom: 2px; }
.help-content .card { margin: var(--sp-3) 0; }
.help-content pre, .help-content .code-block, .help-content .table-wrap { margin: var(--sp-2) 0; }
.card ol, .card ul { padding-left: 1.2em; }

/* Jump grid: "find my tool" without reading the sidebar. */
.jump-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: var(--sp-1);
    list-style: none;
    padding: 0;
    margin: var(--sp-2) 0 0;
}
.jump-grid a {
    display: block;
    padding: 5px var(--sp-2);
    font-size: var(--fs-sm);
    color: var(--text);
    text-decoration: none;
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: var(--r-md);
}
.jump-grid a:hover { border-color: var(--brand); color: var(--brand-text); background: var(--bg-hover); }

.troubleshoot-list dt, .faq-list dt {
    font-weight: 650; margin-top: var(--sp-3); font-size: var(--fs-base);
    max-width: var(--measure);
}
.troubleshoot-list dd, .faq-list dd {
    margin-left: 0; color: var(--text-muted); font-size: var(--fs-base);
    max-width: var(--measure); margin-top: 2px;
}

/* Definition list where a term and its value must read as one unit. */
.detail-list {
    display: grid;
    grid-template-columns: max-content minmax(0, 1fr);
    gap: var(--sp-1) var(--sp-4);
    font-size: var(--fs-base);
    align-items: baseline;
}
.detail-list dt {
    font-size: var(--fs-2xs); font-weight: 600; text-transform: uppercase;
    letter-spacing: 0.045em; color: var(--text-faint);
}
.detail-list dd { margin: 0; max-width: var(--measure); }
.user-detail-grid { display: flex; justify-content: space-between; gap: var(--sp-5); flex-wrap: wrap; }
.user-actions { display: flex; flex-direction: column; gap: var(--sp-2); }

/* =========================================================================
   18. Flash messages
   ========================================================================= */

.flash-messages { max-width: 1440px; margin: 0 auto; padding: var(--sp-2) var(--sp-4) 0; }
.flash {
    display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2);
    padding: var(--sp-2) var(--sp-3);
    border-radius: var(--r-md);
    border: 1px solid var(--border);
    margin-bottom: var(--sp-1);
    font-size: var(--fs-sm);
    transition: opacity 250ms, transform 250ms;
}
.flash-success { background: var(--ok-bg);     color: var(--ok);     border-color: var(--ok-border); }
.flash-danger  { background: var(--danger-bg); color: var(--danger); border-color: var(--danger-border); }
.flash-warning { background: var(--warn-bg);   color: var(--warn);   border-color: var(--warn-border); }
.flash-info    { background: var(--info-bg);   color: var(--info);   border-color: var(--info-border); }
.flash-close {
    background: none; border: 0; font-size: 1.1rem; line-height: 1;
    cursor: pointer; padding: 0 var(--sp-1); color: inherit; opacity: 0.7;
}
.flash-close:hover { opacity: 1; }

/* =========================================================================
   19. Utilities
   ========================================================================= */

.text-muted  { color: var(--text-muted); }
.text-faint  { color: var(--text-faint); }
.text-danger { color: var(--danger); }
/* The counterpart to .text-danger for a number that needs attention but is not
   a failure -- the gateway console's "rejected before a model" column, where
   colouring a bad-key spike red would say "outage" about something that is not
   one. Same token as the .badge-warning and .callout-warn family. */
.text-warning { color: var(--warn); }
.text-ok     { color: var(--ok); }
.mono        { font-family: var(--font-mono); }
.nowrap      { white-space: nowrap; }
.measure     { max-width: var(--measure); }
.mt-0 { margin-top: 0; }
.mt-2 { margin-top: var(--sp-2); }
.mt-3 { margin-top: var(--sp-3); }
.mb-2 { margin-bottom: var(--sp-2); }
.mb-3 { margin-bottom: var(--sp-3); }
.note { font-size: var(--fs-xs); color: var(--text-muted); max-width: var(--measure); }

.tooltip { position: relative; cursor: help; border-bottom: 1px dotted var(--text-faint); }

/* =========================================================================
   20. Responsive
   ========================================================================= */

@media (max-width: 1080px) {
    .help-layout { grid-template-columns: 180px minmax(0, 1fr); gap: var(--sp-4); }
}

@media (max-width: 860px) {
    .page-header { grid-template-columns: minmax(0, 1fr); }
    .site-header { padding: var(--sp-2) var(--sp-3); min-height: 0; }
    .header-title { display: none; }
    .nav-toggle { display: block; }
    .header-nav { margin-left: auto; }
    .nav-links {
        display: none; flex-direction: column; align-items: stretch;
        width: 100%; order: 3; padding: var(--sp-2) 0 0; gap: 2px;
    }
    .nav-links.nav-open { display: flex; }
    .nav-user {
        border-left: 0; padding-left: 0; margin-left: 0;
        padding-top: var(--sp-2); border-top: 1px solid var(--border);
        justify-content: space-between;
    }
    .container { padding: var(--sp-4) var(--sp-3) var(--sp-5); }
    .help-layout { grid-template-columns: minmax(0, 1fr); }
    .help-sidebar {
        position: static; max-height: none;
        border-bottom: 1px solid var(--border); padding-bottom: var(--sp-3);
        margin-bottom: var(--sp-3);
    }
    .help-nav { columns: 2 14rem; column-gap: var(--sp-4); }
    .help-nav-wrap > summary {
        display: flex; align-items: center; gap: var(--sp-2);
        border: 1px solid var(--border); border-radius: var(--r-md);
        background: var(--bg-surface); padding: var(--sp-2) var(--sp-3);
    }
    .help-nav-wrap > summary h2 { margin: 0; }
    .help-nav-wrap > summary::after {
        content: ""; margin-left: auto;
        border-left: 4px solid var(--text-faint);
        border-top: 3.5px solid transparent; border-bottom: 3.5px solid transparent;
    }
    .help-nav-wrap[open] > summary::after { transform: rotate(90deg); }
    .help-nav-wrap[open] > .help-nav { margin-top: var(--sp-3); }
    .stats-grid, .stats-grid-6 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .user-detail-grid { flex-direction: column; }
    .filter-row > .form-group { flex: 1 1 140px; }
}

@media (max-width: 420px) {
    .stats-grid, .stats-grid-6 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .model-card { padding: var(--sp-2); }
    /* The card loses its inner padding before the chart loses its min-width:
       at 360px every pixel inside .viz-scroll is one less pixel of scroll. */
    .viz-card { padding: var(--sp-1) var(--sp-2); }
}

/* =========================================================================
   21. Motion and print
   ========================================================================= */

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

@media print {
    .site-header, .site-footer, .nav-toggle, .flash-messages, .theme-toggle,
    .tabs, .btn { display: none !important; }
    body { background: #fff; color: #000; }
    .card, .table-wrap, .model-card, .viz-card { break-inside: avoid; border-color: #999; }
    .disclose[open] .disclose-body, details { display: block !important; }
    a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 0.8em; color: #555; }
    /* Print forces a white ground, so a reader in dark mode would otherwise
       send pale marks and pale chart text to paper. Restate the light viz set
       against the ground that is actually being printed on. */
    :root {
        --viz-grid:  #dde2e9;
        --viz-axis:  #c3cad6;
        --viz-ink:   #151a21;
        --viz-ink-2: #56606f;
        --viz-muted: #666e79;
        --viz-s1:    #1f6fbf;
        --viz-s2:    #c2560f;
        --viz-fail:  #b02218;
    }
    .viz-card { background: #fff; }
    /* Nothing to scroll on paper: show the chart at its natural width. */
    .viz-scroll { overflow-x: visible; }
}

/* Live-but-undescribed list wrapper (see /status). */
.model-plain-list { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4); }

/* Category legend: the words "chat", "reasoning", "vision" and "embeddings"
   are not self-explanatory to a first-time visitor, and four headings scattered
   through the grid cost four half-empty rows. Define them once, here. */
.legend {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr));
    gap: var(--sp-1) var(--sp-4);
    margin-bottom: var(--sp-3);
    padding: var(--sp-2) var(--sp-3);
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: var(--r-md);
}
.legend-item { display: flex; gap: var(--sp-2); align-items: baseline; }
.legend dt {
    font-size: var(--fs-2xs); font-weight: 700; text-transform: uppercase;
    letter-spacing: 0.045em; color: var(--text); white-space: nowrap;
}
.legend dd { margin: 0; font-size: var(--fs-xs); color: var(--text-muted); }

.model-cat {
    font-size: var(--fs-2xs); font-weight: 600; text-transform: uppercase;
    letter-spacing: 0.05em; color: var(--text-faint); margin-top: -6px;
}

/* Three small panels across, where two would leave each of them half empty. */
.admin-grid-3 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr)); }

/* An action link inside a data row still has to look like a link. */
.data-table a.row-link {
    color: var(--brand-text);
    text-decoration: underline;
    font-size: var(--fs-2xs);
    margin-left: var(--sp-1);
}

/* Inline edit field: shrinks with its column instead of forcing the page wide. */
.name-input { width: 200px; max-width: 100%; }
.inline-form { flex-wrap: wrap; max-width: 100%; }

/* Narrow variants and one-off placements that would otherwise become inline
   styles. Keep this list short: a growing tail here means a missing component. */
.table-narrow { max-width: 420px; }
.search-input { width: 220px; max-width: 100%; }
.page-footnote { text-align: center; margin-left: auto; margin-right: auto; }
.server-indicator { display: inline-block; vertical-align: middle; }

/* =========================================================================
   22. Try it panel (/try)

   Dense on purpose: key bar, one control row, prompt, results, code tabs.
   The whole page should fit a laptop screen with the first answer visible.

   This section owns LAYOUT ONLY. Every surface, every colour and every
   severity on this page comes from a component defined above -- .card for the
   key bar and each result, .callout(-warn|-danger) for the explainers and the
   errors, .badge for the per-result state, .tabs for the example switcher,
   .disclose for the reasoning and privacy disclosures, and the four buttons.
   There is no hex and no bare pixel spacing below; if a colour looks wrong
   here, the token is wrong, not this rule.
   ========================================================================= */

.try-keybar { margin-bottom: var(--sp-3); }
.try-keyrow { display: flex; gap: var(--sp-2); align-items: center; flex-wrap: wrap; }
.try-keylabel {
    font-size: var(--fs-2xs); font-weight: 600; text-transform: uppercase;
    letter-spacing: 0.045em; color: var(--text-faint);
    white-space: nowrap; margin: 0;
}
.try-keyinput { flex: 1; min-width: 220px; font-family: var(--font-mono); }
.try-key-msg {
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    margin-top: var(--sp-1);
    max-width: var(--measure);
}
.try-privacy { margin-top: var(--sp-2); }
.try-privacy-body p { max-width: var(--measure); }

/* [hidden] comes from the UA stylesheet, so ANY author `display` rule beats
   it. Without this, the compare row (display:flex) and the Stop button (.btn
   is inline-flex) would both ignore the attribute and sit there permanently
   visible. This bites every time and is invisible in review. */
.try-live [hidden], .code-block[hidden] { display: none !important; }

.try-controls { margin-bottom: var(--sp-2); }
.try-control-grow { flex: 1; min-width: 220px; }
.try-num { width: 90px; }
/* A checkbox and its word, sitting on the baseline of the controls row rather
   than on the baseline of the inputs beside it. */
.try-toggle {
    display: inline-flex; align-items: center; gap: 5px;
    font-size: var(--fs-sm); color: var(--text);
    white-space: nowrap; cursor: pointer;
    padding-bottom: 6px;
}
.try-model-note {
    font-size: var(--fs-xs);
    color: var(--text-muted);
    line-height: var(--lh-snug);
    min-height: 1.2rem;
    margin-bottom: var(--sp-2);
    max-width: var(--measure);
}
.try-inline-badge { margin: 0 var(--sp-1); }

.try-promptrow { display: flex; gap: var(--sp-2); align-items: stretch; }
.try-prompt {
    flex: 1;
    resize: vertical;
    font-size: var(--fs-base);
    line-height: var(--lh-body);
}
.try-sendcol { display: flex; flex-direction: column; gap: var(--sp-2); justify-content: flex-start; }
.try-sendcol .btn { justify-content: center; }
.try-examples {
    display: flex; gap: var(--sp-1); align-items: center; flex-wrap: wrap;
    margin-top: var(--sp-2);
}
.try-kbd { font-size: var(--fs-2xs); margin-left: auto; }

/* --- Results -------------------------------------------------------------
   One .card per model. In compare mode the same card is laid out in a grid,
   so nothing about the renderer changes between one model and three. */
.try-results { margin-top: var(--sp-3); }
.try-grid { display: grid; gap: var(--sp-3); }
.try-grid-multi {
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
    align-items: start;
}
.try-card { display: flex; flex-direction: column; gap: var(--sp-2); min-width: 0; }
.try-card > * { min-width: 0; }
.try-card-hint { color: var(--text-muted); font-size: var(--fs-base); }
.try-card-head {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--sp-2);
}
/* The model id is the one string that must survive in full; wrap it rather
   than truncate it, exactly as the /status model cards do. */
.try-card-model {
    font-size: var(--fs-xs); background: transparent; padding: 0;
    color: var(--text-muted); white-space: normal; overflow-wrap: anywhere;
}
.try-answer {
    font-size: var(--fs-base);
    line-height: var(--lh-body);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    max-width: var(--measure);
}
/* The cursor that says "still generating". Killed with every other bit of
   motion under prefers-reduced-motion -- it is a static glyph, not a blink. */
.try-answer-live::after { content: '\258B'; opacity: 0.5; }
.try-meta {
    font-size: var(--fs-2xs);
    color: var(--text-faint);
    font-variant-numeric: tabular-nums;
    border-top: 1px solid var(--border);
    padding-top: var(--sp-1);
}
.try-note { font-size: var(--fs-xs); color: var(--text-muted); max-width: var(--measure); }

/* The empty-reasoning explainer and the error box are both .callout: the
   left rule and the heading carry the severity, the body stays readable.
   These rules only tighten a callout that is nested inside a result card. */
.try-card .callout { margin-bottom: 0; max-width: none; }
.try-card .callout > h3 { font-size: var(--fs-sm); }
.try-card .callout > .btn { margin-top: var(--sp-2); }
.try-warn-hint { display: block; margin-top: var(--sp-2); font-size: var(--fs-xs); color: var(--text-muted); }
.try-warn-soft {
    display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap;
    font-size: var(--fs-xs); color: var(--text-muted);
}
.try-error-detail { max-width: var(--measure); }
/* The redacted upstream body. A <pre> already carries --code-bg/--code-text,
   so it reads as machine output rather than as more of the explanation. */
.try-raw { font-size: var(--fs-2xs); max-height: 10rem; overflow: auto; margin-top: var(--sp-1); }
/* The reasoning is one block of preformatted text, not a column of children:
   restate display so it does not inherit .disclose-body's flex context. */
.try-think-body {
    display: block;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    color: var(--text-muted);
    max-height: 15rem;
    overflow-y: auto;
    max-width: var(--measure);
}

.try-footnote { max-width: var(--measure); }

/* 860px, not a breakpoint of this page's own: it is where section 20 already
   collapses the header and tightens the container, so the prompt row stacks at
   the same width as everything else rather than at a width only /try knows. */
/* The "What to test" modes (issue #197). */
.try-mode-note { font-size: var(--fs-xs); max-width: var(--measure); margin: 0 0 var(--sp-1); }
.try-image-row { align-items: flex-end; }
.try-image-row .try-key-msg { display: block; margin-top: var(--sp-1); }
.try-image-preview { max-height: 72px; max-width: 120px; border: 1px solid var(--border); border-radius: var(--radius, 4px); }
.try-thumb { max-height: 160px; max-width: 100%; border: 1px solid var(--border); border-radius: var(--radius, 4px); align-self: flex-start; }

/* Answer on the left, the model's thinking beside it. Two columns only when
   there is room for both to be readable; otherwise the thinking drops under
   the answer. */
.try-split { display: grid; grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); gap: var(--sp-3); align-items: start; }
.try-main { min-width: 0; display: flex; flex-direction: column; gap: var(--sp-2); }
.try-think {
    min-width: 0;
    border-left: 3px solid var(--border-strong);
    padding-left: var(--sp-2);
}
.try-think-head { font-size: var(--fs-xs); color: var(--text-muted); margin-bottom: var(--sp-1); }
.try-think .try-think-body { max-height: 24rem; font-size: var(--fs-xs); }
@media (max-width: 860px) {
    .try-split { grid-template-columns: minmax(0, 1fr); }
}

/* The tool round trip, as numbered steps. */
.try-step-label { font-size: var(--fs-xs); font-weight: 600; color: var(--text-muted); }
.try-step-body {
    font-size: var(--fs-2xs);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    margin: var(--sp-1) 0 0;
    max-height: 12rem;
    overflow: auto;
}

@media (max-width: 860px) {
    .try-promptrow { flex-direction: column; }
    .try-sendcol { flex-direction: row; }
    .try-kbd { margin-left: 0; }
}

/* Long-prompt silent-truncation warning on /status model cards. Uses the
   existing --warn token (defined in every theme block), so it is legible in
   light and dark without a new colour. */
.model-list li.warn { color: var(--warn); }
