﻿*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}:root{
  /* Slate palette, dark theme. The canvas sits one step below the card
     surface, and hairline borders do the separating work shadows cannot do
     on dark. */
  --bg:#0b1220;--bg2:#0f172a;--bg3:#1e293b;
  --text:#e5e7eb;--text2:#94a3b8;--text3:#64748b;
  /* The interactive layer carries the ONE brand accent: primary actions,
     active navigation, focus, dirty markers and meters all resolve through
     it. Everything else stays ink; the status hues below belong only to
     state (danger/warning/success) and to data marks in charts. The accent
     formulas reference var(--accent), so the light theme retints them by
     redeclaring the accent alone. */
  --accent:#8b5cf6;
  --accent-soft:color-mix(in srgb, var(--accent) 14%, transparent);
  --primary:var(--accent);
  --primary-hover:#a78bfa;
  --primary-bg:color-mix(in srgb, var(--accent) 12%, transparent);
  --hover-wash:color-mix(in srgb, var(--text) 5%, transparent);
  --data-blue:#3b82f6;--blue:var(--data-blue);--border:#1f2937;--border-strong:#334155;
  --danger:#f87171;--warning:#fbbf24;--success:#34d399;
  --shadow-sm:0 1px 2px rgba(0,0,0,.3);
  --shadow-md:0 6px 18px rgba(0,0,0,.45), 0 1px 3px rgba(0,0,0,.25);
  --shadow-lg:0 18px 40px rgba(0,0,0,.55), 0 2px 6px rgba(0,0,0,.28);
  --shadow-seg:0 1px 2px rgba(0,0,0,.18), 0 0 0 1px var(--border);
  --shadow-drawer:-12px 0 40px rgba(0,0,0,.35);
  --skel-shimmer-from:rgba(255,255,255,0.04);
  --skel-shimmer-to:rgba(255,255,255,0.10);
  --focus-ring:0 0 0 2px color-mix(in srgb, var(--accent) 40%, transparent);
  --scrim:rgba(0,0,0,.7);
  /* Monospace stack for identifiers, paths and code. One token, so every
     surface renders code in the same face. */
  --mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  --radius:8px;
  /* Cards and other large surfaces round one step softer than controls. */
  --radius-lg:10px;
  /* Height of the global app bar; every fixed element that must clear it
     offsets by this. */
  --appbar:44px;}
html.theme-light{
  /* Slate palette, light theme. The canvas is one tint below the white card
     surface, so cards read as material lifted by hairline plus soft shadow
     instead of wireframe boxes on a flat white page. */
  --bg:#f8fafc;--bg2:#ffffff;--bg3:#f1f5f9;
  --text:#0f172a;--text2:#475569;--text3:#64748b;
  /* The accent formulas from the dark set recompute against this accent;
     only the literal hover shade needs restating. */
  --accent:#6d28d9;
  --accent-soft:color-mix(in srgb, var(--accent) 9%, transparent);
  --primary-hover:#5b21b6;
  --border:#e2e8f0;--border-strong:#cbd5e1;
  --danger:#dc2626;--warning:#d97706;--success:#059669;
  --shadow-sm:0 1px 2px rgba(15,23,42,.04), 0 1px 3px rgba(15,23,42,.06);
  --shadow-md:0 4px 6px -2px rgba(15,23,42,.05), 0 10px 15px -3px rgba(15,23,42,.08);
  --shadow-lg:0 18px 40px rgba(15,23,42,.14), 0 2px 6px rgba(15,23,42,.06);
  --shadow-seg:0 1px 2px rgba(15,23,42,.10), 0 0 0 1px var(--border);
  --shadow-drawer:-12px 0 40px rgba(15,23,42,.16);
  --skel-shimmer-from:rgba(15,23,42,0.04);
  --skel-shimmer-to:rgba(15,23,42,0.09);
  --scrim:rgba(15,23,42,.4);}
:root{
  /* Admin-only layout variables; the palette and ink layer come from the
     shared tokens above. */
  --sidebar:210px;
  /* Shared width for every dashboard column (stat cards, system resources, GPU activity, VRAM
     attribution, downloads) so they all line up on the same left and right edges. */
  --dash-width:1280px;
  /* One content column for configuration surfaces: section headers, action
     rows and content blocks share it, so an action never anchors to the
     viewport edge far from what it acts on. Full-width surfaces (tables,
     dashboards) opt out explicitly. */
  --content-col:900px;
  --density-pad:48px;
  --density-card-pad:16px;
  --density-heading-mb:28px;
  --density-section-gap:28px;
}

/* Scrollbars (webkit + firefox): thin and low-contrast at rest, brightening
   on hover. The 3px transparent inset leaves a 2px thumb at rest, 3px on
   hover, the native-app feel rather than the chunky default bars. */
*{scrollbar-width:thin;scrollbar-color:color-mix(in srgb, var(--text2) 50%, transparent) transparent}
*::-webkit-scrollbar{width:8px;height:8px}
*::-webkit-scrollbar-track{background:transparent}
*::-webkit-scrollbar-thumb{background:color-mix(in srgb, var(--text2) 45%, transparent);border-radius:999px;border:3px solid transparent;background-clip:content-box;transition:background-color .15s ease}
*::-webkit-scrollbar-thumb:hover{background:color-mix(in srgb, var(--text2) 75%, transparent);background-clip:content-box;border-width:2px}
*::-webkit-scrollbar-thumb:active{background:var(--text2);background-clip:content-box;border-width:2px}
*::-webkit-scrollbar-corner{background:transparent}
html.density-compact{
  --density-pad:24px;
  --density-card-pad:10px;
  --density-heading-mb:16px;
  --density-section-gap:14px;
}
.section{padding:var(--density-pad) var(--density-pad) calc(var(--density-pad) - 8px);animation:pageIn .22s ease}
@keyframes pageIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
/* Every animation and transition on this surface is cosmetic; state is always
   carried by color, text, or geometry. Honouring the OS preference is
   therefore one blanket rule rather than a per-widget audit. */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.01ms !important;animation-iteration-count:1 !important;transition-duration:.01ms !important}
  html{scroll-behavior:auto !important}
}
.logs-panel-header{padding:var(--density-pad) var(--density-pad) calc(var(--density-pad) - 20px)}
html{
  font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','Inter',Roboto,Helvetica,Arial,sans-serif;
  font-size:14px;color:var(--text);background:var(--bg);
  -webkit-font-smoothing:antialiased;
}
body{min-height:100vh;cursor:default}
/* Design-system rule: prevent the text-edit (I-beam) cursor from appearing
   on text content across pages. Real text-input elements still get their
   native I-beam via the explicit override below. Text selection still
   works - we only changed the cursor glyph, not user-select. */
input[type=text],input[type=password],input[type=email],input[type=number],input[type=search],input[type=url],input[type=tel],textarea,[contenteditable=true]{cursor:text}
/* Click targets always use the pointer cursor and are never text-selectable, so the chrome never
   shows a text I-beam or a blinking insertion caret over a button, select, or option. Real text
   inputs keep their I-beam via the rule above (this block deliberately excludes them). */
button,select,summary,[role=button],[role=tab],[role=option],.btn{cursor:pointer}
button,select,summary,[role=button],[role=tab],[role=option],.btn,label{user-select:none;-webkit-user-select:none}
/* Global rule: static labels, badges, tags, and eyebrows are not editable text. They show the arrow
   cursor (never the blinking text caret) and do not capture selection. Add new non-interactive label
   classes here so the whole UI stays consistent. */
.ss-stat-l,.ss-reindex-title,.ss-reindex-l,.ss-reindex-pct,.ss-reindex-grouplabel,.ss-diag-title,
.ovr-name,.ovr-lane,.ovr-shape,.ovr-warn,.ovr-empty{cursor:default;user-select:none;-webkit-user-select:none}
a{color:inherit;text-decoration:none}

/* No native disclosure marker anywhere: each summary carries its own styled
   caret (log-trace's triangle, the shared chevron below for the rest). */
summary{list-style:none}
summary::-webkit-details-marker{display:none}
.ss-mx-trace>summary,.ss-wl-events>summary{display:inline-flex;align-items:center;gap:6px}
.ss-mx-trace>summary::before,.ss-wl-events>summary::before{
  content:'';width:0;height:0;border-style:solid;border-width:4px 0 4px 5px;
  border-color:transparent transparent transparent currentColor;
  transition:transform .15s ease;
}
.ss-mx-trace[open]>summary::before,.ss-wl-events[open]>summary::before{transform:rotate(90deg)}

/* Search fields clear through their own affordances; the OS glyph stays off. */
input[type=search]::-webkit-search-cancel-button{-webkit-appearance:none}

/* File inputs render their button through the ghost recipe. */
input[type=file]{font-size:12px;color:var(--text2)}
input[type=file]::file-selector-button{
  margin-right:10px;padding:6px 12px;border:1px solid var(--border);border-radius:6px;
  background:transparent;color:var(--text2);font-size:12px;font-weight:500;font-family:inherit;
  cursor:pointer;transition:background .12s,color .12s,border-color .12s;
}
input[type=file]::file-selector-button:hover{background:var(--hover-wash);color:var(--text);border-color:var(--border-strong)}


.layout{display:flex;min-height:100vh;padding-top:var(--appbar)}

/* Sidebar */
.sidebar{
  position:fixed;top:var(--appbar);left:0;bottom:0;width:var(--sidebar);
  display:flex;flex-direction:column;padding:24px 12px 16px;
  border-right:1px solid var(--border);background:var(--bg);z-index:10;
  transition:width .18s ease;
}
.nav-link{
  display:flex;align-items:center;gap:10px;padding:7px 12px;margin-bottom:1px;
  font-size:13px;font-weight:500;color:var(--text2);
  border-radius:6px;transition:color .12s,background .12s;
  white-space:nowrap;overflow:hidden;
}
.nav-icon{flex-shrink:0;width:16px;height:16px;color:currentColor}
.nav-label{overflow:hidden;text-overflow:ellipsis}
.nav-link:hover{color:var(--text)}
.nav-link.active{color:var(--text);background:var(--bg3)}
/* Collapsed state: icon-only rail */
body.sidebar-collapsed{--sidebar:64px}
body.sidebar-collapsed .nav-label,
body.sidebar-collapsed .nav-cat{display:none}
body.sidebar-collapsed .nav-link{justify-content:center;padding:9px 0}
body.sidebar-collapsed .nav-link[data-section]{position:relative}
body.sidebar-collapsed .nav-link[data-section]:hover::after,
body.sidebar-collapsed .nav-link[data-section]:focus-visible::after{
  content:attr(aria-label);
  position:absolute;left:calc(100% + 12px);top:50%;transform:translateY(-50%);
  background:var(--bg2);color:var(--text);font-size:12px;font-weight:500;letter-spacing:.01em;
  padding:7px 12px;border-radius:6px;border:1px solid var(--border);
  white-space:nowrap;pointer-events:none;z-index:60;
  box-shadow:var(--shadow-md);
  opacity:0;animation:tipFade .14s ease forwards;
}
body.sidebar-collapsed .nav-link[data-section]:hover::before,
body.sidebar-collapsed .nav-link[data-section]:focus-visible::before{
  content:'';
  position:absolute;left:calc(100% + 8px);top:50%;
  transform:translateY(-50%) rotate(45deg);
  width:8px;height:8px;background:var(--bg2);
  border-left:1px solid var(--border);border-bottom:1px solid var(--border);
  pointer-events:none;z-index:61;opacity:0;animation:tipFade .14s ease forwards;
}
/* Auth modal - flat, typography-driven sign-in dialog.
   Scoped to #auth-modal so the generic .modal (confirm dialogs) stays simple.
   No badge / no rounded opaque logo glyph; the premium feel comes from
   spacing, typographic hierarchy, focused input state, and a solid CTA. */
#auth-modal .modal.auth-modal{width:440px;padding:36px 36px 30px;text-align:left;border-radius:14px;border-color:var(--border-strong,var(--border));box-shadow:var(--shadow-lg)}
.auth-headings{margin-bottom:26px}
.auth-eyebrow{display:block;font-size:10px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;color:var(--primary);margin-bottom:8px}
.auth-modal h2{font-size:22px;font-weight:700;color:var(--text);margin:0 0 8px;letter-spacing:-.012em;text-align:left;line-height:1.15}
.auth-modal p{font-size:13px;color:var(--text2);line-height:1.55;margin:0;text-align:left}
.auth-form{display:flex;flex-direction:column;gap:16px}
.auth-field{display:flex;flex-direction:column;gap:7px}
.auth-field-label{font-size:10px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--text3)}
.auth-input-wrap{position:relative;display:flex;align-items:center}
/* margin:0 kills the modal base rule's bottom margin: the auth form's rhythm
   is container-owned (form/field gaps), and a leftover margin makes the wrap
   taller than the input, sinking the absolutely-centered reveal button. */
.auth-input-wrap input{flex:1;width:100%;height:44px;padding:0 44px 0 14px;margin:0;background:var(--bg);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:14px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.04em;transition:border-color .12s,box-shadow .12s;outline:none}
.auth-input-wrap input::placeholder{color:var(--text3);letter-spacing:.2em}
.auth-input-wrap input:focus{border-color:var(--primary);box-shadow:0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent)}
.auth-input-reveal{position:absolute;right:6px;width:32px;height:32px;border:none;background:transparent;color:var(--text3);cursor:pointer;border-radius:6px;display:inline-flex;align-items:center;justify-content:center;padding:0;transition:color .12s,background .12s}
.auth-input-reveal:hover{color:var(--text2);background:var(--bg2)}
.auth-input-reveal svg{width:16px;height:16px}
.auth-input-reveal.is-on{color:var(--primary)}
/* Remember-on-this-device row: flex container that hugs its content with
   align-self:flex-start (so the column-flex parent doesn't stretch it to
   full width), text-align:left (the generic .modal centered text), and
   gap:8px to glue the checkbox visually next to its label. */
.auth-remember{display:flex;align-items:center;justify-content:flex-start;gap:8px;font-size:12px;color:var(--text2);cursor:pointer;user-select:none;align-self:flex-start;padding:2px 0;margin:0;white-space:nowrap;text-align:left;line-height:1.2}
.auth-remember > *{margin:0}
.auth-submit{height:44px;width:100%;font-size:13px;font-weight:600;letter-spacing:.02em;border-radius:8px;margin-top:6px}
/* Single sign-on: a quiet secondary door under the password form; on an
   SSO-only server the password form hides and this is the whole form. */
.auth-sso-block{display:flex;flex-direction:column;gap:14px;margin-top:2px}
.auth-sso-block[hidden]{display:none}
.auth-divider{display:flex;align-items:center;gap:10px;color:var(--text3);font-size:10px;font-weight:700;letter-spacing:.1em;text-transform:uppercase}
.auth-divider::before,.auth-divider::after{content:'';height:1px;background:var(--border);flex:1}
.auth-sso{height:44px;width:100%;font-size:13px;font-weight:600;letter-spacing:.02em;border-radius:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);cursor:pointer;transition:border-color .12s,background .12s}
.auth-sso:hover{border-color:var(--border-strong,var(--border));background:var(--bg2)}
.auth-sso:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);border-color:var(--primary)}
.auth-error{font-size:12px;color:#ef4444;background:rgba(239,68,68,.08);border:1px solid rgba(239,68,68,.24);border-radius:6px;padding:8px 12px;line-height:1.4}
/* Sign-in method cards (Security page). One bounded card per method so
   every field visibly belongs to the method it configures. Password and
   2FA carry no switch (their content is always relevant): their heads are
   inert and their sub flows as a block. SSO is a GATE: its configuration
   exists on screen only while its switch is on, because fields that do
   nothing must not ask to be filled. */
.row-block--static .row-main{cursor:default}
.row-block--static .row-main:hover{background:transparent}
/* These subs hold free-flowing content (field grids, action rows, guide
   lines), not the generic row-sub field grid, so they flow as a block.
   Three class parts: the generic .row-block .row-sub grid is declared
   later in this sheet and would win on order at equal specificity. */
.row-block .row-sub.auth-sub{display:block}
.row-block--gate:has(.row-main input:not(:checked)) .row-sub{display:none}
.auth-block-state{font-size:12px;font-weight:600;color:var(--text2);white-space:nowrap;flex-shrink:0}
.auth-block-state.is-on{color:#10b981}
.sso-guide{margin:12px 0 0}
.sso-note{font-size:11.5px;line-height:1.5;color:var(--text3);margin:12px 0 0}
.sso-note.warn{color:#f59e0b}
html.theme-light .sso-note.warn{color:#b45309}
.sso-test-row{display:flex;align-items:center;gap:12px;margin-top:14px}
.sso-test-result{font-size:11.5px;line-height:1.5;color:var(--text3);min-height:1.5em}
.sso-test-result.warn{color:#f59e0b}
html.theme-light .sso-test-result.warn{color:#b45309}
.sso-test-result.ok{color:#10b981}
/* Session rows: quiet facts, the subject carries the identity. */
.sess-row{display:flex;align-items:center;gap:14px;padding:9px 0;border-top:1px solid var(--border)}
.sess-row:first-child{border-top:none}
.sess-who{font-size:12.5px;color:var(--text);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.sess-meta{font-size:11.5px;color:var(--text3);white-space:nowrap;margin-left:auto}
.sess-current{font-size:11px;color:#10b981;white-space:nowrap}
.op-row{display:flex;align-items:center;gap:14px;padding:9px 0;border-top:1px solid var(--border)}
.op-row:first-child{border-top:none}
.op-name{font-size:12.5px;color:var(--text);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.op-row.op-off .op-name{color:var(--text3)}
.op-meta{font-size:11.5px;color:var(--text3);white-space:nowrap;margin-left:auto}
.op-acts{display:flex;align-items:center;gap:8px;flex:none}
.op-modal{width:520px}
.op-reset-note{margin:0;font-size:12px;color:var(--text3);line-height:1.5}
.sess-revoke{flex:0 0 auto}
/* Two-factor management. Square-cornered, no pills. */
.twofa-status{display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin-top:12px}
.twofa-secret-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.twofa-secret{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:15px;letter-spacing:.12em;background:var(--code-bg,rgba(127,127,127,.12));border:1px solid var(--border);border-radius:6px;padding:8px 12px;user-select:all;word-break:break-all}
.twofa-confirm-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.twofa-confirm-row input,.twofa-disable input{width:130px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:15px;letter-spacing:.18em;text-align:center;padding:8px 10px;border:1px solid var(--border);border-radius:6px;background:var(--input-bg,transparent);color:var(--text)}
.nav-cat{display:block;font-size:14px;font-weight:700;color:var(--text);padding:0 12px 10px;margin:28px 0 8px;border-bottom:1px solid var(--border)}
.nav-cat:first-child{margin-top:4px}
.nav-spacer{flex:1}

/* Main */
/* min-width:0 breaks the flex-item min-content floor: without it the widest
   grid's one-row max forces the whole column past the viewport and every
   page scrolls sideways on narrower screens. */
.main{margin-left:var(--sidebar);flex:1;min-width:0;padding:0 0 100px}
/* The window never scrolls on the admin: the content column is the
   scroller, so the scrollbar starts UNDER the pinned app bar instead of
   running over it. Desktop only; the narrow layout stacks the sidebar in
   flow and keeps the document scroll. */
@media(min-width:721px){
  body.admin-body{height:100vh;overflow:hidden}
  body.admin-body .layout{box-sizing:border-box;height:100vh;min-height:0}
  body.admin-body .main{height:calc(100vh - var(--appbar));overflow-y:auto;overscroll-behavior:contain}
}

/* Section head: THE page-header template. Every admin page renders the same
   three lines at the same coordinates: the group eyebrow (injected from the
   sidebar's .nav-cat, so it can never drift from navigation), the title line
   (optionally a flex row carrying the section's actions), and one subtitle
   sentence. The geometry lives in the tokens below and NOWHERE else: a page
   must never restate head spacing (no per-section .section-head overrides),
   so the title and subtitle sit at the exact same position on every page. */
.section{
  --head-eyebrow-gap:10px;   /* eyebrow baseline to title */
  --head-title-gap:6px;      /* title to subtitle */
  padding:48px 48px 40px;border-bottom:1px solid var(--border)
}
.section:last-of-type{border-bottom:none}
.section-head{margin-bottom:var(--density-heading-mb)}
/* Section-level "restore defaults" action. Sits on the heading row, so the
   heading becomes a flex line with the button pinned right. Always rendered
   and disabled when the section already matches its defaults: a control that
   appears and disappears moves the heading under the pointer. */
.section-head-row{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;max-width:var(--content-col)}
.sec-rst{
  flex-shrink:0;padding:5px 10px;background:none;
  border:1px solid var(--border);border-radius:4px;
  color:var(--text2);cursor:pointer;font-family:inherit;
  font-size:12px;font-weight:500;transition:color .15s,border-color .15s;
}
.sec-rst:hover:not(:disabled){color:var(--primary);border-color:var(--primary)}
.sec-rst:disabled{opacity:.4;cursor:default}
.section-eyebrow{display:block;font-size:11px;line-height:14px;font-weight:700;letter-spacing:.18em;text-transform:uppercase;color:var(--text2);margin-bottom:var(--head-eyebrow-gap,10px)}
.section-head h1,.logs-panel-title{font-size:18px;line-height:24px;font-weight:600;color:var(--text);margin-bottom:var(--head-title-gap,6px)}
.section-head h1::before,.logs-panel-title::before{content:'';display:inline-block;width:3px;height:16px;background:var(--primary);border-radius:2px;margin-right:10px;vertical-align:middle}
.section-head p,.logs-panel-desc{font-size:13px;color:var(--text2);line-height:1.5}
/* The runtime settings-file path inside the General lead: truncates inside a
   reserved inline box instead of wrapping the sentence, full path on hover. */
.settings-file{display:inline-block;max-width:34ch;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom;font-family:var(--mono,ui-monospace,monospace);font-size:12px;color:var(--text)}

/* Grid */
.grid{display:grid;grid-template-columns:1fr 1fr;gap:20px 32px;max-width:620px}
.grid.narrow{grid-template-columns:1fr;max-width:300px}
.field.wide{grid-column:1/-1}
.rows{max-width:var(--content-col)}

/* A settings grid OWNS the space below it, rather than relying on whatever
   happens to follow it. Sibling-adjacency rules (.grid + .rows) would break the
   moment anything is inserted between them, and a conditionally rendered
   element supplying the only gap leaves the two stuck together the instant it
   is hidden: that is exactly how the Inference grid ended up flush against the
   KV-cache cards. Grid rows sit 20px apart, so a slightly larger step reads as
   a block break. Collapses against a following .dash-heading's own margin, so
   headed blocks do not double up. */
.grid{margin-bottom:28px}

/* Field */
.field{display:flex;flex-direction:column;gap:7px}
/* The hidden ATTRIBUTE has to win over an author display, or setting
   hidden=true on any of these does nothing at all: the class rule beats the
   UA default. Every component whose class sets a display belongs here. */
.side-panel[hidden],.req-breakdown[hidden],.ocr-options[hidden],
.field[hidden],.grid[hidden],.verdict[hidden],.verdict-actions[hidden]{display:none !important}
.field label{display:flex;align-items:center;gap:0;font-size:12px;font-weight:500;color:var(--text2);line-height:1}
/* THE BASE LAYER for every control in a field. The type exclusions sit inside
   :where() so they contribute NO specificity: this rule weighs exactly as much
   as a bare element selector, and any widget rule below it wins on its own
   class without reaching for !important. Weight here is not free. When the
   three :not() clauses counted, this rule silently outranked the edited-value
   border on inputs, the compact sizing of four forms, and the 44px the auth
   field reserves for its trailing button. */
:where(.field) input:where(:not([type=checkbox]):not([type=radio]):not([type=color])),
:where(.field) select,:where(.field) textarea{
  flex:1;min-width:0;padding:8px 10px;background:var(--bg);
  border:1px solid var(--border);border-radius:6px;
  color:var(--text);font-size:13px;font-family:inherit;
  outline:none;transition:border-color .15s,box-shadow .15s;
}
/* Selects size to their own widest option instead of a fixed width, so an option
   label is never clipped to an unreadable stub ("On First Completion", the AWS
   region names, "This machine only"). A select's intrinsic width comes from its
   widest OPTION, not the selected one, so this is stable: choosing a different
   value never resizes the control. The floor keeps short selects (Modern /
   Classic, Queue / Reject) on the rhythm they already had, and the ceiling keeps
   a long option list inside its column.

   fit-content rather than auto: .field is a column flex, so an auto cross size
   would stretch the control to the full column instead of sizing it to content. */
:where(.field) select:where(:not([data-catalog])){flex:none;width:fit-content;min-width:140px;max-width:100%}
.field input:focus,.field select:focus,.field textarea:focus{border-color:#555}
.field input:disabled,.field select:disabled,.field textarea:disabled{opacity:.4;cursor:not-allowed}
/* The three state layers carry the SAME weight and are ordered weakest to
   strongest, so the cascade resolves them by order alone and a control that is
   two states at once shows the one that matters more: focused, then edited,
   then refused. A refused value marks the CONTROL, not only a sentence
   elsewhere, and it marks every control type, since a select or a textarea can
   be refused for the same reasons an input can. */
.field input.dirty,.field select.dirty,.field textarea.dirty{border-color:var(--primary);box-shadow:inset 2px 0 0 var(--primary), 0 0 0 1px color-mix(in srgb, var(--accent) 22%, transparent)}
.field input.is-invalid,.field select.is-invalid,.field textarea.is-invalid{border-color:var(--danger);box-shadow:0 0 0 2px color-mix(in srgb, var(--danger) 18%, transparent)}
.field .f-row:has(.dirty)::before{content:'';position:absolute;left:-14px;top:0;bottom:0;width:3px;background:var(--primary);border-radius:2px}
/* One-shot landing flash for deep links that name a setting
   (#section?focus=path): fades to nothing and never loops. */
@keyframes deepFlash{from{background:color-mix(in srgb, var(--primary) 12%, transparent)}to{background:transparent}}
.deep-flash{animation:deepFlash 2.4s ease-out;border-radius:6px}
.field .f-row{position:relative}
.hint{font-size:11px;color:var(--text3);margin-top:2px}
/* The network-access note says something different per mode, and the two texts
   differ in length. Both are rendered in the same grid cell with only the active
   one visible, so the box always reserves the TALLER of the two and switching
   modes cannot move the controls below it. Measuring beats guessing a min-height:
   this holds at any column width and survives an edit to either sentence. */
.net-note{display:grid;margin-top:6px;line-height:1.5}
.net-note>span{grid-area:1/1;visibility:hidden}
.net-note>span.is-on{visibility:visible}
/* FFmpeg Path verdict: the live probe outcome under the field. A quiet dot
   carries the state; the text stays plain (version line when valid, the
   honest consequence when not). */
.ffmpeg-status{display:flex;align-items:baseline;gap:7px;margin-top:6px;font-size:11px;line-height:1.45;color:var(--text3);font-variant-numeric:tabular-nums}
.ffmpeg-status::before{content:'';flex:0 0 auto;width:7px;height:7px;border-radius:50%;align-self:center}
.ffmpeg-status.is-ok::before{background:#10b981}
.ffmpeg-status.is-missing{color:#f59e0b}
.ffmpeg-status.is-missing::before{background:#f59e0b}
.ffmpeg-status.is-busy{color:var(--text2)}
.ffmpeg-status.is-busy::before{background:var(--primary)}
.ffmpeg-status.is-fail{color:#ef4444}
.ffmpeg-status.is-fail::before{background:#ef4444}
/* THE quiet inline action: a text-weight button that reads as a link, used for
   a per-field reset, an inline fix, an install prompt. Declared once here so
   the three cannot drift apart. Rest is secondary ink and hover is full ink
   plus an underline, matching the section-level reset. Not blue: the
   interactive layer of this surface is monochrome and color is reserved for
   status. */
.inline-act,.rst,.ffmpeg-act,.mcp-wf-fix{
  flex:0 0 auto;padding:0;background:none;border:none;
  color:var(--text2);cursor:pointer;font-family:inherit;
  font-size:11px;font-weight:400;transition:color .15s;
}
.inline-act:hover,.rst.show:hover,.ffmpeg-act:hover,.mcp-wf-fix:hover{
  color:var(--primary);text-decoration:underline;
}

/* Help tooltip: the primitives themselves (".?" icon, .tip wrapper, popover,
   .ttip title bubble) are shared surface-wide and live in SharedUi.TooltipCss,
   composed into this sheet below. *//* The hidden ATTRIBUTE always wins. Without this, any element whose class
   sets a display (flex, grid, block) ignores hidden entirely, because the
   author rule beats the UA's [hidden] default; that is how a popover gets
   stuck open while every close path dutifully sets hidden=true. Shipped as
   a real defect more than once; this ends the class. */
[hidden]{display:none !important}
.help{
  display:inline-flex;align-items:center;justify-content:center;
  width:15px;height:15px;border-radius:50%;flex-shrink:0;
  font-size:9px;font-weight:700;color:var(--text3);
  border:1px solid var(--border);cursor:help;
  margin-left:6px;
  line-height:1;font-style:normal;
}
.help:hover{color:var(--text);border-color:var(--text2)}
/* A "?" icon may be an anchor when its topic has a canonical reference page:
   same look as the <i> variant, but clickable, so the pointer cursor replaces
   the ambient help cursor and link styling is neutralized. */
a.help{color:var(--text3);text-decoration:none;cursor:pointer}
a.help:hover{color:var(--text)}
.tip{display:inline-flex;align-items:center}
/* The data-tip bubble: ONE fixed-position element on the body, filled and
   placed by the shared tooltip engine. Fixed positioning is what makes it
   structurally unclippable: no ancestor overflow, transform, or z-index
   stack can cut it off or grow a scrollbar under it. Explanatory tooltips
   on text labels ALWAYS use the "?" icon (a visible, discoverable
   affordance); pictograms wrap in .tip, the one other legitimate carrier.
   text-transform:none / letter-spacing:normal are explicit so a tooltip
   opened from inside an uppercase label reads in normal sentence case. */
#lmk-tip{
  position:fixed;left:0;top:0;display:none;z-index:10060;
  background:var(--bg2);color:var(--text);font-size:12px;font-weight:400;line-height:1.45;
  text-transform:none;letter-spacing:normal;
  padding:8px 12px;border-radius:6px;border:1px solid var(--border);
  width:max-content;max-width:280px;text-align:left;white-space:normal;
  box-shadow:var(--shadow-md);pointer-events:none;cursor:default;
}
#lmk-tip.is-on{display:block;opacity:0;animation:tipFade .14s ease forwards}
#lmk-tip::before{
  content:'';position:absolute;left:var(--ax,50%);width:8px;height:8px;background:var(--bg2);
  transform:translateX(-50%) rotate(45deg);
}
#lmk-tip.is-above::before{bottom:-4.5px;border-right:1px solid var(--border);border-bottom:1px solid var(--border)}
#lmk-tip.is-below::before{top:-4.5px;border-left:1px solid var(--border);border-top:1px solid var(--border)}
@keyframes tipFade{from{opacity:0}to{opacity:1}}
/* The platform tooltip bubble: one renderer for every title= on the surface
   (behavior in the title-lift module of the shared core script). Inverted
   surface, ink on canvas, so both themes are automatic; fixed-position and
   final-size before it fades in, so it never reflows anything. Sits above
   every layer, because a hover label must never hide under the surface that
   spawned it. */
.ttip{
  position:fixed;z-index:10050;background:var(--text);color:var(--bg);
  font-size:11px;font-weight:500;letter-spacing:.01em;line-height:1.4;
  padding:5px 9px;border-radius:6px;box-shadow:var(--shadow-md);
  max-width:320px;width:max-content;white-space:pre-line;overflow-wrap:break-word;
  pointer-events:none;opacity:0;transition:opacity .12s ease;
}
.ttip.is-visible{opacity:1}/* No native number spinners anywhere: every surface renders the stepper below
   instead, so the two can never both appear. */
input[type=number]{-moz-appearance:textfield;appearance:textfield}
input[type=number]::-webkit-outer-spin-button,
input[type=number]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}

/* Number field with +/- steppers */
.num-wrap{display:flex;max-width:140px;min-width:0;border:1px solid var(--border);border-radius:6px;overflow:hidden;transition:border-color .15s,box-shadow .15s;background:var(--bg2)}
.num-wrap:hover{border-color:var(--border-strong)}
.num-wrap:focus-within{border-color:var(--primary);box-shadow:var(--focus-ring)}
.num-wrap.dirty{border-color:var(--primary);box-shadow:0 0 0 1px color-mix(in srgb, var(--text) 16%, transparent)}
.num-wrap input{border:none!important;border-radius:0;text-align:center;padding:7px 4px;flex:1;min-width:0;max-width:none;background:transparent;font-variant-numeric:tabular-nums;font-weight:500}
.num-wrap input:focus{border-color:transparent!important;box-shadow:none!important}
.num-step{width:30px;display:flex;align-items:center;justify-content:center;background:transparent;color:var(--text3);border:none;cursor:pointer;font-size:15px;line-height:1;font-family:inherit;transition:opacity .12s,color .1s,background .1s;user-select:none;padding:0;opacity:0}
.num-wrap:hover .num-step,.num-wrap:focus-within .num-step{opacity:1}
.num-step:first-child{border-right:1px solid var(--border)}
.num-step:last-child{border-left:1px solid var(--border)}
.num-step:hover{color:var(--text);background:var(--bg3)}
.num-step:active{background:var(--bg)}
@media (hover:none) and (pointer:coarse){
  .num-step{display:none}
}.field-error{color:#ef4444;font-size:12px;margin-top:6px}
/* A field that means nothing under the current choice dims IN PLACE (no
   layout shift, no disabled attribute so binding stays intact) until the
   choice makes it relevant again. */
.field.is-inert{opacity:.45;pointer-events:none;transition:opacity .15s}

/* Field row (input + reset) */
.f-row{display:flex;gap:0;align-items:center}
/* Undo one edit back to its default. It appears when there is an edit to undo,
   which is why it is absent rather than disabled, and it keeps its box either
   way so revealing it moves nothing.

   visibility, not a transparent color: a transparent button is still focusable
   and still hit-tested, so every field carried an invisible live click target
   and a tab stop that announced "Reset" with nothing on screen. */
.rst{
  margin-left:10px;
  visibility:hidden;opacity:0;
  transition:opacity .15s,visibility .15s,color .15s;
}
.rst.show{visibility:visible;opacity:1}

/* Server-side path fields: always readable so every admin sees where the server writes,
   but writable only from a session on the host itself. The locked form keeps the exact
   geometry of the editable one, so the layout does not depend on who is looking. */
.localhost-editable.is-locked input{background:var(--bg2);color:var(--text2);cursor:default}
.localhost-editable.is-locked .rst,
.localhost-editable.is-locked .rst.show{visibility:hidden;opacity:0;cursor:default}

/* Managed deployment: the whole form is the node's effective configuration,
   readable everywhere and writable nowhere: the manifest owns it. Fields keep
   the exact geometry of the editable form; every editing affordance (resets,
   save bar, import, list-editor rows' actions) leaves the page, while the
   collapsible group heads, the select value readouts, and Export remain. */
#config-form.managed input,#config-form.managed textarea{background:var(--bg2);color:var(--text2);cursor:default;pointer-events:none}
#config-form.managed select{pointer-events:none}
#config-form.managed .dsel-btn{background:var(--bg2);color:var(--text2);pointer-events:none}
#config-form.managed button:not(.sec-group-head):not(#btn-config-export):not(.dsel-btn){display:none}
#config-form.managed .btn-import-label{display:none}
.managed-note{display:flex;align-items:flex-start;gap:10px;margin:0 0 18px;padding:12px 14px;
  border:1px solid var(--border);border-radius:6px;background:var(--bg2);color:var(--text2);
  font-size:12.5px;line-height:1.55}
.managed-note-icon{width:15px;height:15px;flex:none;margin-top:2px;color:var(--text3)}
.managed-note-text a{color:var(--text);text-decoration:underline;text-underline-offset:2px}

/* Server-side folder picker: opened by the Browse control beside a path
   field (host sessions only), it walks the SERVER's directories through the
   admin API. One level per view: the path readout, the way up, the
   subdirectories. */
.dir-modal{width:520px;text-align:left}
.dir-modal h2{text-align:left}
.dir-path{font-family:var(--mono);font-size:11.5px;color:var(--text2);background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:7px 10px;margin-bottom:10px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;direction:rtl;text-align:left}
.dir-path span{unicode-bidi:plaintext}
.dir-list{height:300px;overflow-y:auto;overscroll-behavior:contain;border:1px solid var(--border);border-radius:8px;background:var(--bg)}
.dir-row{display:flex;align-items:center;gap:9px;width:100%;padding:7px 12px;border:none;background:transparent;color:var(--text);font-family:inherit;font-size:12.5px;text-align:left;cursor:pointer;transition:background .1s}
.dir-row:hover{background:var(--hover-wash)}
.dir-row svg{flex:0 0 auto;width:14px;height:14px;color:var(--text3)}
.dir-row-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.dir-row--up{color:var(--text2)}
.dir-group{display:block;padding:9px 12px 4px;font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--text3)}
.dir-empty{display:block;padding:12px;font-size:12px;color:var(--text3)}
.dir-row:focus-visible{outline:none;box-shadow:var(--focus-ring)}

/* Download button */
/* Inline action button next to a model-selection dropdown. Download, Load
   and Unload share one quiet monochrome shape; the label carries the verb.
   The .act-load / .act-unload modifiers remain as behavioral hooks only. */
.dl-btn{
  flex-shrink:0;padding:0 10px;height:30px;display:flex;align-items:center;
  background:none;border:1px solid var(--border);border-radius:5px;margin-left:8px;
  color:var(--text2);cursor:pointer;font-family:inherit;
  font-size:11px;font-weight:500;transition:all .12s;
}
.dl-btn:hover{background:var(--hover-wash);color:var(--text);border-color:var(--border-strong)}
.dl-btn:disabled{opacity:.6;cursor:wait}

/* ── Custom select (dsel) ──
   ONE dropdown look for every plain <select>; the model pickers keep their
   richer msel treatment. The native element stays in the DOM as the value
   holder the form machinery reads and writes (display:none preserves value,
   options and change events); the wrapper takes its layout slot. */
select.dsel-host{display:none !important}
.dsel{position:relative;display:inline-flex;min-width:0;vertical-align:middle}
.dsel-btn{
  display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:8px;
  width:100%;min-width:0;padding:8px 10px;background:var(--bg);
  border:1px solid var(--border);border-radius:6px;
  color:var(--text);font-size:13px;font-family:inherit;line-height:1.2;
  cursor:pointer;text-align:left;transition:border-color .15s,box-shadow .15s;
}
.dsel-btn:hover:not(:disabled){border-color:var(--border-strong)}
.dsel-btn:focus-visible{border-color:var(--primary);box-shadow:var(--focus-ring);outline:none}
.dsel.open .dsel-btn{border-color:var(--primary)}
.dsel-btn:disabled{opacity:.4;cursor:not-allowed}
/* Label and sizer overlap in the first grid cell. The sizer stacks every
   option label one per line, invisible and zero-height, so the cell's
   intrinsic width is the WIDEST option rather than the selected one: an
   auto-width trigger reserves its footprint once and never resizes when the
   value changes. In a fixed-width slot the minmax(0,1fr) column still
   shrinks below the sizer and the label ellipsizes as before. */
.dsel-label{grid-area:1/1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.dsel-size{grid-area:1/1;height:0;overflow:hidden;visibility:hidden;white-space:pre}
.dsel-caret{grid-area:1/2;width:12px;height:12px;color:var(--text3);transition:transform .15s}
.dsel.open .dsel-caret{transform:rotate(180deg)}
.dsel-pop{
  position:absolute;top:calc(100% + 4px);left:0;min-width:100%;max-width:340px;z-index:80;
  background:var(--bg2);border:1px solid var(--border);border-radius:8px;
  box-shadow:var(--shadow-md);padding:4px;max-height:280px;overflow-y:auto;
  animation:fadeIn .12s ease;
}
.dsel.drop-up .dsel-pop{top:auto;bottom:calc(100% + 4px)}
.dsel-opt{
  display:flex;align-items:center;gap:8px;width:100%;padding:7px 9px;
  background:none;border:none;border-radius:5px;color:var(--text2);
  font-size:12.5px;font-family:inherit;cursor:pointer;text-align:left;
  transition:background .1s,color .1s;
}
.dsel-opt:hover,.dsel-opt.is-active{background:var(--hover-wash);color:var(--text)}
.dsel-opt[aria-selected="true"]{color:var(--text);font-weight:600}
.dsel-opt:disabled{opacity:.4;cursor:not-allowed}
.dsel-check{flex:0 0 auto;width:12px;height:12px;color:var(--text);opacity:0}
.dsel-opt[aria-selected="true"] .dsel-check{opacity:1}
.dsel-opt-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Slot geometry per context, mirroring what governed the native element.
   The look never changes; only the width the surface grants it. A field
   dropdown sizes to its widest option (the sizer above), floored so short
   lists keep the rhythm and capped so a long option list stays in column.
   fit-content rather than auto: .field is a column flex, so an auto cross
   size would stretch the control to the full column. */
.field .dsel{flex:none;width:fit-content;min-width:140px;max-width:100%}
.field .dsel.dsel-grow,.modal-form .dsel,.cs-field .dsel,.ts-modal .dsel{width:100%;max-width:none;flex:1}
.mm-filter-row .dsel{min-width:170px}
/* Compact surfaces (request toolbar, database forms) shrink the trigger. */
.dsel-sm .dsel-btn{padding:4px 8px;font-size:11px}

/* Duration picker */
.dur{display:flex;align-items:center;gap:8px;flex:1;min-width:0}
.dur-seg{
  display:flex;align-items:center;gap:0;
  border:1px solid var(--border);border-radius:6px;overflow:hidden;
  background:var(--bg);transition:border-color .15s,box-shadow .15s;
}
.dur-seg:hover{border-color:var(--border-strong)}
.dur-seg:focus-within{border-color:var(--primary);box-shadow:var(--focus-ring)}
.dur.dirty .dur-seg{border-color:var(--primary);box-shadow:0 0 0 1px color-mix(in srgb, var(--accent) 22%, transparent)}
/* The box is sized for the WIDEST value a segment can hold (three digits for
   days), so typing 365 never clips the value it is meant to show. The
   horizontal padding is forced for the same reason the border is: the shared
   .field control rule above carries four class-level selectors, which no
   widget-level selector can outrank, and its 10px sides leave a 34px box only
   14px of room. */
.dur-seg input{
  width:34px;text-align:center;padding:8px 2px!important;
  border:none!important;background:transparent;color:var(--text);
  font-size:13px;font-family:inherit;outline:none;max-width:none;
  font-variant-numeric:tabular-nums;font-weight:500;
}
.dur-seg input:focus{border-color:transparent!important;box-shadow:none!important}
.dur-seg span{font-size:10px;color:var(--text3);padding-right:7px;user-select:none}
/* Compact vertical up/down stepper per duration segment. Native number
   spinners are killed site-wide and the .num-wrap stepper pass skips the
   composite duration widget, so without this the segments would be the only
   numeric controls on the page with no clickable affordance. Vertical keeps
   the three segments narrow enough to sit two-up in the settings grid.

   QUIET at rest, per the shared stepper contract below: a duration renders
   three of these side by side, and three always-on arrow stacks read as
   chrome shouting over the value they decorate. */
.dur-spin{
  display:flex;flex-direction:column;align-self:stretch;
  width:17px;flex:0 0 auto;
  opacity:0;transition:opacity .12s;
}
.dur-seg:hover .dur-spin,.dur-seg:focus-within .dur-spin{opacity:1}
.dur-step{
  flex:1 1 0;display:flex;align-items:center;justify-content:center;
  background:transparent;color:var(--text3);border:none;padding:0;
  cursor:pointer;font-family:inherit;transition:color .1s;user-select:none;
}
/* The arrow is a hairline chevron drawn from two borders, not a triangle
   glyph: filled triangles render as heavy dark wedges at every size a
   segment can afford, which is what made a row of them read as aggressive.
   Each chevron is centred in its own half, so the two halves are equal
   click targets and the pair stays visually balanced. */
.dur-step::before{
  content:'';width:5px;height:5px;
  border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;
}
.dur-step--up::before{transform:rotate(-135deg)}
.dur-step--down::before{transform:rotate(45deg)}
.dur-step:hover{color:var(--text)}


/* ── Numeric stepper contract ──────────────────────────
   The duration widget above follows the same contract as the shared +/-
   number field, which lives in SharedUi.NumberFieldCss and is rendered by
   every surface.

   A stepper is an affordance for the pointer, not a permanent decoration.
   The footprint is ALWAYS reserved: revealing or hiding an arrow changes
   opacity only, so no value and no neighbour ever moves. At rest the control
   reads as a plain value box; the arrows fade in while the pointer is over
   that control or the caret is inside it, which is exactly when a click on
   them is possible. Nothing is lost by hiding them: arrow keys step a number
   input natively at all times, which is also why the buttons stay out of the
   tab order.

   A coarse pointer gets no arrows at all. There is no hover state to reveal
   them, a 17px arrow half is well under a comfortable tap target, and
   stepping a retention window to 365 one tap at a time was never the input
   method anyway: the value boxes grow instead and the device keypad does the
   work. */
@media (hover:none) and (pointer:coarse){
  /* THE MINIMUM LEGIBLE SIZE ON A TOUCH DEVICE, stated once for every control
     on the surface. Under 16px iOS zooms the whole page the instant a control
     takes focus, and it does not zoom back: the admin is left panned sideways
     with the sidebar off screen. That is a property of the DEVICE, not of any
     widget, so it outranks every local type choice, and one !important here
     replaces the 43 per-widget overrides the alternative would need. There is
     no case where an 11px filter box is the right thing on a phone.

     Type scales, geometry does not: the boxes below keep their own sizing and
     the containers wrap, so this cannot push the page sideways. */
  input,select,textarea{font-size:16px !important}
  /* Field controls also take the roomier tap target; a dense toolbar keeps
     its height so a filter row stays a filter row. */
  :where(.field) input:where(:not([type=checkbox]):not([type=radio]):not([type=color])),
  :where(.field) select,:where(.field) textarea{padding:12px 10px}
  /* Every control cluster folds instead of overflowing. A phone cannot show a
     six-filter toolbar in one line at a legible size, and a page that scrolls
     sideways loses its left edge for good. */
  .req-filters,.logs-toolbar,.mm-filter-row,.alerts-toolbar,.tm-toolbar,
  .ss-diag-controls,.req-views-save,.req-pager,.f-row{flex-wrap:wrap;row-gap:8px}
  /* A width measured for a 11px face clips a 16px one, so the filter size
     classes yield and the boxes share the folded rows instead. */
  .log-search,.log-search--w4,.log-search--w6,.log-search--w8,.log-search--w10{
    max-width:none;height:auto;padding:9px 10px;flex:1 1 140px;
  }
  .log-capture-select,.req-select,.req-page-input{height:auto;padding:9px 10px}
  .dur-spin{display:none}
  /* The three segments and the Reset beside them fold rather than push the
     page into a sideways scroll on a narrow screen. The segment padding is
     forced for the same specificity reason as its resting value above, and
     matches the vertical padding of every other control so a duration row is
     the same height as the rows around it. */
  .dur{flex-wrap:wrap;gap:8px}
  .dur-seg{flex:0 0 auto}
  .dur-seg input{width:64px;padding:12px 4px!important}
  .dur-seg span{font-size:11px;padding-right:9px}
}

/* Inline restart-required marker. Renders as plain parenthesised text
   ("(restart required)") in a subdued color - NOT a badge, NOT a pill,
   no background, no border, no uppercase, no letter-spacing tricks
   that read as chip chrome. The design system has no badge / chip /
   pill primitive for in-label markers; this is just typography. */
.tag{
  font-size:11px;
  font-weight:400;
  color:#b45309;
  font-style:italic;
  margin-left:8px;
  white-space:nowrap;
}
.tag::before{content:'(';font-style:normal}
.tag::after{content:')';font-style:normal}
html.theme-dark .tag{color:#f59e0b}

/* Toggle rows */
.rows{display:flex;flex-direction:column;gap:1px;margin-bottom:16px;border-radius:var(--radius);overflow:hidden}
/* When a toggle-rows block follows a field grid directly (no sub-heading
   between them, e.g. the Inference section), separate them so the toggle
   doesn't sit flush against the grid. */
.grid+.rows{margin-top:24px}
.row{
  display:flex;align-items:center;justify-content:space-between;gap:24px;
  padding:16px;background:var(--bg2);cursor:pointer;
}
/* The group is a visible unit in BOTH themes: it paints the hairlines (its
   background shows through the 1px row gaps) and carries a border, so a
   switch reads as attached to its words instead of floating at the far edge
   of an invisible card. The light theme's row surface matches the page, so
   without this the rows would have no shape at all there. */
.rows{background:var(--border);border:1px solid var(--border)}
/* A row holding a switch is the switch's whole hit target (wired in the
   page script), so it answers the pointer it promises with a hover wash. */
.row:has(.row-ctl .toggle):hover,.row-block .row-main:hover{background:color-mix(in srgb, var(--text) 4%, var(--bg2))}
/* Card variant: each toggle is an independent setting, so render the rows as
   separated, individually-bordered cards instead of the hairline-divided list. */
.rows-cards{gap:12px;overflow:visible;border-radius:0;background:transparent;border:none}
.rows-cards .row{border:1px solid var(--border);border-radius:var(--radius)}
/* A toggle card carrying dependent settings: the sub-row holds the inputs
   that only apply while the toggle is on, and dims in place (no layout
   shift) when it is off. */
.rows-cards .row-block{display:block;padding:0;cursor:default}
.row-block .row-main{display:flex;align-items:center;justify-content:space-between;gap:24px;padding:16px;cursor:pointer}
.row-block .row-sub{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,320px));gap:16px 24px;margin:0 16px;padding:14px 0 16px;border-top:1px dashed var(--border)}
.row-block:has(.row-main input:not(:checked)) .row-sub{opacity:.45;pointer-events:none}
.row-text{display:flex;flex-direction:column;gap:5px}
.row-title{font-size:13px;font-weight:500;color:var(--text)}
.row-desc{font-size:12px;line-height:1.5;color:var(--text3)}
/* A row can carry a control cluster beside its toggle (the web areas pair an
   access picker with the serve switch). The select keeps a fixed footprint so
   Public and Admin session swap in place, and it sleeps while the area is off:
   access means nothing for a page that is not served. */
.row-ctl{display:flex;align-items:center;gap:14px;flex:0 0 auto}
.row-ctl select,.row-ctl .dsel{width:150px}
/* A control that configures a switched-off option does not exist on
   screen: dynamic UI, never a dimmed dead editor. */
.row:has(.row-ctl .toggle input:not(:checked)) .row-ctl select,
.row:has(.row-ctl .toggle input:not(:checked)) .row-ctl .dsel{display:none}
/* data-gate containers hide the same way whatever display their class
   sets; applyGates() drives the attribute from the owning toggle. */
[data-gate][hidden]{display:none !important}
/* A picker's popover must escape the group box: the container's
   overflow-hidden (there for the rounded clip) would cut the list off at
   the group's edge, unopenable on the last row. The rounding moves onto
   the end rows themselves. */
.rows:has(.row-ctl){overflow:visible}
.rows:has(.row-ctl) .row:first-child{border-radius:var(--radius) var(--radius) 0 0}
.rows:has(.row-ctl) .row:last-child{border-radius:0 0 var(--radius) var(--radius)}
/* Switch: monochrome like every control. The checked track is INK, and the
   knob is the surface color, so both themes keep full contrast without a
   per-theme rule. Hairline border so the resting track reads on the card. */
.toggle{position:relative;display:inline-block;width:36px;height:20px;flex-shrink:0;cursor:pointer}
.toggle input{opacity:0;width:0;height:0;position:absolute}
.track{position:absolute;inset:0;background:var(--bg3);border:1px solid var(--border);border-radius:10px;transition:background .15s,border-color .15s}
.track::before{content:'';position:absolute;left:2px;top:2px;width:14px;height:14px;border-radius:50%;background:var(--bg2);box-shadow:0 1px 2px rgba(0,0,0,.28), 0 0 0 1px rgba(0,0,0,.04);transition:transform .15s cubic-bezier(.22,.61,.36,1),background .15s}
.toggle input:checked+.track{background:var(--primary);border-color:var(--primary)}
.toggle input:checked+.track::before{transform:translateX(16px);background:var(--bg2)}
html.theme-light .toggle input:checked+.track::before{background:#fff}
/* The input itself is visually hidden, so keyboard focus lands on nothing the
   eye can find; the track carries the ring on its behalf. */
.toggle input:focus-visible+.track{box-shadow:var(--focus-ring)}
/* Off and unavailable are otherwise the same picture, so a toggle the
   context forbids dims its whole track and says so to the pointer. */
.toggle input:disabled+.track{opacity:.45}
.toggle:has(input:disabled){cursor:not-allowed}

/* Checkboxes and radios: one custom paint for every remaining native input,
   so no OS-rendered control appears anywhere on the surface. Checked state is
   INK with a surface-colored glyph, exactly like the switch. */
/* ONE geometry for every checkbox and radio on the surface, declared here and
   nowhere else. Five widgets used to restate a size of their own (15, 15, 15,
   14, 16) and every one of them was outranked by this rule, so the whole admin
   has always rendered at 16px: the sizes were drift, not design. The pointer
   and the flex behaviour live here for the same reason. */
input[type=checkbox]:where(:not(.toggle input)),input[type=radio]{
  appearance:none;-webkit-appearance:none;width:16px;height:16px;margin:0;flex-shrink:0;
  cursor:pointer;
  border:1px solid var(--border-strong);border-radius:4px;background:var(--bg2);
  display:inline-grid;place-content:center;vertical-align:middle;
  transition:background .12s,border-color .12s;
}
input[type=radio]{border-radius:50%}
input[type=checkbox]:where(:not(.toggle input))::before,input[type=radio]::before{
  content:'';width:10px;height:10px;transform:scale(0);transition:transform .12s cubic-bezier(.22,.61,.36,1);
  background:var(--bg2);
  clip-path:polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
}
input[type=radio]::before{clip-path:none;border-radius:50%;width:8px;height:8px}
input[type=checkbox]:where(:not(.toggle input)):checked,input[type=radio]:checked{background:var(--primary);border-color:var(--primary)}
input[type=checkbox]:where(:not(.toggle input)):checked::before,input[type=radio]:checked::before{transform:scale(1)}
html.theme-light input[type=checkbox]:where(:not(.toggle input)):checked::before,
html.theme-light input[type=radio]:checked::before{background:#fff}
input[type=checkbox]:where(:not(.toggle input)):disabled,input[type=radio]:disabled{opacity:.45;cursor:not-allowed}

/* GPU selection */
.gpu-section{max-width:620px;margin-top:8px}
.gpu-label{display:block;font-size:13px;font-weight:500;color:var(--text2);margin-bottom:8px}
.gpu-list{display:flex;flex-direction:column;gap:1px;border-radius:var(--radius);overflow:hidden}
.gpu-card{
  display:flex;align-items:center;gap:12px;padding:10px 14px;
  background:var(--bg2);cursor:pointer;transition:background .1s;
  user-select:none;-webkit-user-select:none;
}
.gpu-card:hover{background:var(--bg3)}
.gpu-card-info{display:flex;flex-direction:column;gap:1px;flex:1;min-width:0}
.gpu-card-name{font-size:13px;font-weight:500;color:var(--text)}
.gpu-card-meta{font-size:11px;color:var(--text3)}
.gpu-card.disabled{opacity:.4;pointer-events:none}
/* Resolved-backend readout. One line is reserved so the value swapping in never
   moves the device list below it, and the block owns the gap to that list.
   A backend that fell short of the configured one is flagged through the shared
   .slot-fit callout, so "needs your attention" keeps ONE shape across the admin. */
.gpu-backend{font-size:12px;color:var(--text3);min-height:17px;line-height:17px;margin-bottom:8px}
.gpu-backend .gpu-backend-name{color:var(--text2);font-weight:500}

/* Download panel (top-right, stacked cards) */
.dl-panel{position:fixed;top:calc(var(--appbar) + 12px);right:16px;z-index:90;width:260px;display:flex;flex-direction:column;gap:8px}
.dl-card{background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px;box-shadow:var(--shadow-md);animation:fadeIn .2s ease}
.dl-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}
.dl-title{font-size:12px;font-weight:500;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1;min-width:0}
.dl-cancel{background:none;border:none;color:var(--text3);font-size:14px;cursor:pointer;padding:0 0 0 8px;line-height:1;transition:color .12s}
.dl-cancel:hover{color:#ef4444}
.dl-bar{height:3px;background:var(--bg3);border-radius:2px;overflow:hidden}
.dl-fill{height:100%;width:0;background:var(--data-blue);border-radius:2px;transition:width .3s}
.dl-meta{display:flex;justify-content:space-between;margin-top:5px;font-size:10px;color:var(--text3)}

/* Save bar */
/* Savebar floats above the 32px status footer. The bottom offset is the
   footer height plus a comfortable gap so the card never visually
   touches the footer chrome. Horizontal padding mirrors the page gutter
   so the rounded card lines up with the content area. */
.savebar{position:fixed;bottom:56px;left:var(--sidebar);right:0;z-index:30;padding:0 48px;pointer-events:none;opacity:0;transform:translateY(20px);transition:opacity .22s ease,transform .22s ease}
.savebar.visible{opacity:1;transform:translateY(0);pointer-events:auto}
.savebar-inner{display:flex;align-items:center;justify-content:space-between;padding:11px 18px 11px 16px;background:var(--bg2);border:1px solid var(--border);border-radius:10px;box-shadow:var(--shadow-lg);gap:14px}
.savebar-inner::before{content:'';width:4px;height:24px;background:var(--primary);border-radius:2px;margin-right:6px;flex-shrink:0}
.savebar-label{font-size:13px;color:var(--text);font-weight:500;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.savebar-actions{display:flex;gap:8px}
.btn-primary{padding:6px 16px;border:1px solid var(--primary);border-radius:6px;background:var(--primary);color:#fff;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;transition:background .12s,border-color .12s,transform .12s,box-shadow .12s;line-height:1.4}
.btn-primary:hover:not(:disabled){background:var(--primary-hover);border-color:var(--primary-hover);transform:translateY(-1px);box-shadow:var(--shadow-sm)}
.btn-primary:active:not(:disabled){transform:translateY(0);box-shadow:none}
.btn-primary:disabled{opacity:.3;cursor:not-allowed;transform:none;box-shadow:none}
.btn-ghost{padding:6px 14px;border:1px solid var(--border);border-radius:6px;background:transparent;color:var(--text2);font-size:13px;font-weight:500;cursor:pointer;font-family:inherit;transition:color .12s,border-color .12s}
.btn-ghost:hover:not(:disabled){color:var(--text);border-color:var(--border-strong)}
.btn-ghost:disabled{opacity:.3;cursor:not-allowed}

/* Toast */
#toast-container{position:fixed;bottom:80px;right:20px;z-index:1000;display:flex;flex-direction:column-reverse;gap:8px;pointer-events:none;max-width:380px}
/* One quiet notification card for every surface: ink on the ambient
   surface, the outcome carried by the glyph's color alone, never by a
   filled slab. The glyph is a mask so one shape declaration serves both
   themes. */
.toast{pointer-events:auto;display:flex;align-items:center;gap:9px;padding:10px 14px 10px 12px;border:1px solid var(--border);border-radius:10px;background:var(--bg2);color:var(--text);font-size:12.5px;font-weight:500;line-height:1.45;box-shadow:var(--shadow-lg);cursor:pointer;animation:toastIn .22s cubic-bezier(.2,.8,.3,1)}
.toast::before{content:'';width:15px;height:15px;flex-shrink:0;background:var(--success,#059669);-webkit-mask:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>') no-repeat center / contain;mask:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>') no-repeat center / contain}
.toast.error::before{background:var(--danger,#dc2626);-webkit-mask-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>');mask-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>')}
.toast.warning::before{background:var(--warning,#b45309);-webkit-mask-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 9v4M12 17h.01M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/></svg>');mask-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 9v4M12 17h.01M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/></svg>')}
@keyframes toastIn{from{opacity:0;transform:translateY(8px) scale(.98)}to{opacity:1;transform:none}}
@keyframes fadeIn{from{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:translateY(0)}}

/* Banner */
.banner{position:fixed;top:var(--appbar);left:var(--sidebar);right:0;z-index:50;display:flex;align-items:center;justify-content:space-between;padding:8px 20px;background:#a16207;color:#fef3c7;font-size:13px;font-weight:500}
.banner-actions{display:inline-flex;align-items:center;gap:6px}
.banner-cta{background:rgba(255,255,255,.15);border:1px solid rgba(255,255,255,.4);color:#fef3c7;font-size:12px;font-weight:600;padding:4px 12px;border-radius:4px;cursor:pointer;font-family:inherit;transition:background .12s}
.banner-cta:hover{background:rgba(255,255,255,.28)}
.banner-dismiss{background:none;border:none;color:inherit;font-size:16px;cursor:pointer;padding:0 4px}


/* Auth modal */
.modal-overlay{position:fixed;inset:0;background:var(--scrim);display:flex;align-items:center;justify-content:center;z-index:100;backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px)}
/* The shared confirmation dialog can be opened from within another modal (e.g. Tenant settings), so it sits above the standard modal and popover layers to never render behind its opener. */
#confirm-modal{z-index:300}
/* App footer */
.app-footer{position:fixed;bottom:0;left:var(--sidebar);right:0;display:flex;align-items:center;gap:14px;padding:6px 20px;background:var(--bg2);border-top:1px solid var(--border);font-size:11px;color:var(--text3);z-index:50;transition:left .18s ease;height:32px;box-sizing:border-box}
body.sidebar-collapsed .app-footer{left:64px}
.app-foot-item{display:flex;align-items:center;gap:6px}
.app-foot-key{font-weight:600;color:var(--text2);text-transform:uppercase;letter-spacing:.08em;font-size:10px}
.app-foot-val{color:var(--text)}
.app-foot-sep{width:1px;height:12px;background:var(--border)}
.main{padding-bottom:36px !important}

/* Accessible focus rings */
*:focus{outline:none}
*:focus-visible{outline:none;box-shadow:var(--focus-ring)}
.nav-link:focus-visible{box-shadow:var(--focus-ring);border-radius:6px}
input:focus-visible,select:focus-visible,textarea:focus-visible,.num-wrap:focus-within{box-shadow:var(--focus-ring);border-color:var(--primary) !important}
button:focus-visible{box-shadow:var(--focus-ring)}
.btn-primary:focus-visible{box-shadow:0 0 0 2px var(--bg2), 0 0 0 4px var(--primary)}

/* Sidebar footer (preferences + collapse) */
.nav-footer{display:flex;gap:6px;margin-top:8px;padding:0 4px}
body.sidebar-collapsed .nav-footer{flex-direction:column;align-items:center;padding:0}
.nav-foot-btn{flex:1;display:flex;align-items:center;justify-content:center;height:32px;border:1px solid var(--border);border-radius:6px;background:transparent;color:var(--text3);cursor:pointer;transition:color .12s,border-color .12s,background .12s}
.nav-foot-btn:hover{color:var(--text);border-color:var(--text3);background:var(--bg2)}
.nav-foot-btn svg{width:15px;height:15px}
body.sidebar-collapsed .nav-foot-btn{width:32px;flex:0 0 32px}
body.sidebar-collapsed #nav-collapse svg{transform:rotate(180deg)}
/* Preferences popover (positioned by JS relative to the prefs button) */
/* The preferences menu and the .prefs-segmented primitive live with the app
   bar (AppBar.Css), which every surface includes. */
.ss-tabs{max-width:420px;margin:0 0 16px}
.ss-mx-stores{font-size:11px;color:var(--text3);white-space:nowrap}
.modal{background:var(--bg2);border:1px solid var(--border);border-radius:14px;padding:28px 28px 22px;width:380px;text-align:center;max-width:92vw;box-shadow:var(--shadow-lg);animation:modalIn .18s ease}
.modal h2{font-size:16px;font-weight:600;color:var(--text);margin-bottom:10px}
.modal p{font-size:13px;color:var(--text2);line-height:1.5;margin-bottom:18px}
.modal-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:18px}
@keyframes modalIn{from{opacity:0;transform:translateY(-8px) scale(.98)}to{opacity:1;transform:none}}
.modal:has(.diff-table){width:600px;text-align:left;padding:24px 26px}
.modal:has(.diff-table) h2{font-size:15px;font-weight:600;color:var(--text);margin-bottom:4px;text-align:left}
.diff-intro{font-size:13px;color:var(--text2);margin-bottom:14px}
.diff-table{width:100%;border-collapse:collapse;font-size:13px;max-height:50vh;display:block;overflow:auto}
.diff-table tbody{display:table;width:100%}
.diff-table td{padding:8px 10px;border-bottom:1px solid var(--border);vertical-align:top}
.diff-table tr:last-child td{border-bottom:none}
.diff-label{font-weight:600;color:var(--text);width:35%}
.diff-before{color:var(--text3);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;text-decoration:line-through;text-decoration-color:rgba(239,68,68,.5)}
.diff-arrow{color:var(--text3);width:1em;text-align:center}
.diff-after{color:#10b981;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;font-weight:600}

/* Empty-state */
.empty-state{padding:14px 16px;display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.empty-state .hint{margin:0;padding:0}
.empty-cta{font-size:12px;font-weight:600;color:var(--primary);text-decoration:none;border-bottom:1px solid color-mix(in srgb, var(--text) 30%, transparent);padding-bottom:1px}
.empty-cta:hover{border-bottom-color:var(--primary)}

/* Skeleton shimmer */
.skel{display:inline-block;background:linear-gradient(90deg,var(--skel-shimmer-from) 0%,var(--skel-shimmer-to) 50%,var(--skel-shimmer-from) 100%);background-size:200% 100%;animation:skel-shimmer 1.4s ease-in-out infinite;border-radius:4px;color:transparent;user-select:none}
.skel-block{display:block}
@keyframes skel-shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}
.modal h2{font-size:16px;font-weight:600;color:var(--text);margin-bottom:6px}
.modal p{font-size:13px;color:var(--text2);margin-bottom:20px}
/* Generic text-input styling inside a modal. Explicitly excludes checkboxes
   and radios - those have their own intrinsic sizing and live inside flex
   row labels (e.g. .auth-remember) where width:100% would stretch the box
   to the full row width. */
/* Base layer, same contract as the field controls: the exclusions live in
   :where() so a form inside a modal can state its own sizing and win. */
:where(.modal) input:where(:not([type=checkbox]):not([type=radio]):not([type=color]):not(.toolopt-color-hex)){width:100%;padding:9px 12px;margin-bottom:14px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:14px;outline:none;font-family:inherit}
:where(.modal) input:where(:not([type=checkbox]):not([type=radio]):not([type=color]):not(.toolopt-color-hex)):focus{border-color:#555}
.modal .btn-primary{width:100%;padding:9px}
.modal-actions .btn-primary,.modal-actions .btn-cleanup{width:auto;flex:0 0 auto;padding:8px 18px}

/* The loading hint owns NO layout: it floats over the content column and
   simply disappears when the config lands, so the sections beneath render
   in their final position from the first paint and nothing jumps. */
.loading{position:fixed;top:calc(var(--appbar) + 14px);left:calc(var(--sidebar) + 48px);z-index:5;color:var(--text3);font-size:13px;pointer-events:none}

/* Dashboard */
.stat-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:16px;margin-bottom:var(--density-section-gap);max-width:var(--dash-width)}
@media(max-width:1100px){.stat-cards{grid-template-columns:repeat(2,1fr)}}
/* Read-only metric tiles: arrow cursor (never the text I-beam) and not
   selectable, so the value text shows no caret. Interactive children keep
   their own cursor via the button{cursor:pointer} rule above. */
.stat-card{background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius);padding:var(--density-card-pad);display:flex;flex-direction:column;gap:6px;cursor:default;user-select:none;-webkit-user-select:none}
/* A stat card that deep-links elsewhere on click (e.g. Active requests ->
   Telemetry). Pointer cursor + a quiet hover border signal it is actionable;
   an explicit "View details" button inside still runs its own action. */
.stat-card-linked{cursor:pointer;transition:border-color .12s,background-color .12s}
.stat-card-linked:hover{border-color:var(--primary);background:var(--bg3)}
.stat-label{font-size:11px;font-weight:500;color:var(--text3);text-transform:uppercase;letter-spacing:.04em}
.stat-value{font-size:28px;font-weight:600;color:var(--text)}
.stat-value-sm{font-size:14px}
/* Per-window request RATE under the Total requests value. One clean row per
   window: the window label on the left, the rate (req/s) right-aligned, so the
   numbers form a single tidy column. Flat, tabular, no graph, no pills. */
.stat-rates{display:flex;flex-direction:column;gap:4px;margin-top:auto;padding-top:10px;border-top:1px solid var(--border);font-variant-numeric:tabular-nums}
.stat-rate-row{display:flex;align-items:baseline;justify-content:space-between;gap:10px}
.stat-rate-win{font-size:10px;font-weight:600;color:var(--text3);text-transform:uppercase;letter-spacing:.04em}
.stat-rate-val{font-size:13px;font-weight:600;color:var(--text)}
.stat-spark{display:block;width:100%;height:24px;margin-top:6px;overflow:visible}
.stat-spark polyline{fill:none;stroke:var(--data-blue);stroke-width:1.5;vector-effect:non-scaling-stroke;stroke-linejoin:round;stroke-linecap:round;opacity:.85}
.gpu-spark{height:22px;margin-top:8px;opacity:.9}

/* Premium chart used by the monitoring cards (CPU, memory, GPU). Y-axis label
   at the top-left shows the scale max, bottom-left shows 0. The line is a
   stroke with a translucent area fill under it, on a bordered chart panel. */
.chart{position:relative;margin-top:12px;background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:18px 14px 22px 50px;min-height:96px;overflow:visible}
/* "No data yet" left strip: shown when the time window exceeds how long we've
   been collecting samples. Tells the user the left region is unrecorded rather
   than zero. --chart-cov is the missing fraction of the window [0..1], set by
   drawChart. The strip is absolutely positioned, so a bare percentage width
   would resolve against the chart's PADDING box; the plot (the SVG's 0..100
   viewBox) spans only the content box, so scale by the content width instead:
   100% minus the 50px left + 14px right chart padding. Keeping that 64px here,
   next to .chart's padding, keeps the two in one place. */
.chart-unfilled{position:absolute;left:50px;top:18px;bottom:22px;width:calc((100% - 64px)*var(--chart-cov,0));background:repeating-linear-gradient(135deg,var(--bg2),var(--bg2) 6px,transparent 6px,transparent 12px);border-right:1px dashed var(--border-strong,var(--border));display:none;align-items:center;justify-content:center;pointer-events:none;border-top-left-radius:4px;border-bottom-left-radius:4px;opacity:.55}
.chart-unfilled-l{font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--text3);background:var(--bg);padding:2px 8px;border-radius:3px;white-space:nowrap}
.chart>svg{overflow:hidden;border-radius:4px}
.chart svg{display:block;width:100%;height:60px;overflow:visible}
.chart .chart-line{
  fill:none;stroke:var(--data-blue);stroke-width:1.5;
  vector-effect:non-scaling-stroke;
  /* miter joins (with a tight miterlimit fallback) avoid the rounded
     "dot" blobs that stroke-linejoin:round renders at every sharp
     polyline turn. Near-zero zigzag data produces sharp turns every
     sample, so round joins are visually noisy and look like random
     stuck dots; miter joins collapse cleanly into the line. */
  stroke-linejoin:miter;stroke-miterlimit:2;
  stroke-linecap:butt;
}
.chart .chart-fill{fill:var(--data-blue);fill-opacity:.14;stroke:none}
.chart-y-max,.chart-y-min{position:absolute;left:10px;font-size:10px;color:var(--text2);font-variant-numeric:tabular-nums;line-height:1;font-weight:500;background:var(--bg);padding:2px 5px;border-radius:3px;z-index:3}
.chart-y-max{top:14px}
.chart-y-min{bottom:24px}
.chart-window{position:absolute;bottom:6px;right:12px;font-size:9px;color:var(--text3);letter-spacing:.08em;text-transform:uppercase;font-weight:600}
.chart-stats{position:absolute;bottom:6px;left:50%;transform:translateX(-50%);font-size:9px;color:var(--text3);letter-spacing:.06em;text-transform:uppercase;font-weight:600;font-variant-numeric:tabular-nums;white-space:nowrap;background:var(--bg);padding:2px 6px;border-radius:3px;z-index:3;pointer-events:none}

/* Chart interactivity: hover guideline + tooltip */
.chart{cursor:crosshair}
.chart-cursor{position:absolute;top:14px;bottom:26px;left:0;width:1px;background:var(--text2);opacity:0;pointer-events:none;transition:opacity .08s}
.chart.is-hovered .chart-cursor{opacity:.55}
.chart-cursor-dot{position:absolute;width:7px;height:7px;border-radius:50%;background:var(--data-blue);border:2px solid var(--bg2);opacity:0;pointer-events:none;transform:translate(-50%,-50%);transition:opacity .08s}
.chart.chart-compute .chart-cursor-dot{background:#8b5cf6}
.chart.is-hovered .chart-cursor-dot{opacity:1}
.chart-tip{position:absolute;background:var(--bg2);border:1px solid var(--border);border-radius:6px;padding:6px 10px;font-size:11px;color:var(--text);box-shadow:var(--shadow-md);pointer-events:none;white-space:nowrap;z-index:20;opacity:0;transition:opacity .08s;font-variant-numeric:tabular-nums;line-height:1.3;font-weight:600}
.chart.is-hovered .chart-tip{opacity:1}
.chart-tip-time{display:block;font-size:10px;color:var(--text3);margin-top:2px;letter-spacing:.04em}

/* Threshold lines */
.chart-thresh{stroke-dasharray:3 4;vector-effect:non-scaling-stroke;opacity:.55}
.chart-thresh-warn{stroke:#f59e0b}
.chart-thresh-crit{stroke:#ef4444}
.chart-warn-zone{fill:#f59e0b;fill-opacity:.06;stroke:none}
.chart-crit-zone{fill:#ef4444;fill-opacity:.08;stroke:none}

/* Annotations (event dots on the X axis) */
/* Activity-event annotation dots pinned to the time axis. Bigger than
   the previous 6×6 (which was 2px of visible color after the border)
   so events are spotted at a glance; bordered ring keeps them legible
   on top of the chart fill.

   Critical: NO transition on `left`. The chart-line polyline snaps to
   its new geometry at every 1 Hz tick (SVG points attribute is set
   without animation). If we slid the dot smoothly over the full
   second, the dot would be continuously moving while the line jumps
   discretely - two different cadences that read as out-of-sync. The
   absent transition also kills the startup "sweep" effect where every
   dot animated across the screen during the first second after the
   page loaded. Hover feedback transitions stay because they animate
   transform/shadow on user input, not `left` on tick. */
.chart-anno{
  position:absolute;bottom:22px;
  width:10px;height:10px;border-radius:50%;
  border:2px solid var(--bg);
  transform:translateX(-50%);
  pointer-events:auto;cursor:help;
  box-shadow:0 0 0 1px rgba(0,0,0,.18);
  transition:transform .12s ease, box-shadow .12s ease;
  z-index:3;
}
.chart-anno:hover{transform:translateX(-50%) scale(1.4);box-shadow:0 0 0 1px rgba(0,0,0,.28),0 2px 6px rgba(0,0,0,.18)}
.chart-anno.level-success{background:#10b981}
.chart-anno.level-warning{background:#f59e0b}
.chart-anno.level-error{background:#ef4444}
.chart-anno.level-info{background:var(--data-blue)}

/* When an annotation dot is hovered, the chart-level scrub UI (cursor
   line, data-point dot, value tooltip) is suppressed so only ONE
   tooltip is visible at a time. The .anno-hover class is added/removed
   by the dot's own mouseenter/mouseleave handlers. */
.chart.anno-hover .chart-cursor,
.chart.anno-hover .chart-cursor-dot,
.chart.anno-hover .chart-tip{opacity:0 !important;pointer-events:none}

/* Annotation tooltip: matches the chart-tip visual recipe but shows
   message + source + age, with a level-coloured left border so the
   severity is readable without re-reading the dot's color. */
.chart-anno-tip{
  position:absolute;
  background:var(--bg2);border:1px solid var(--border);border-radius:6px;
  padding:7px 11px;
  font-size:11px;color:var(--text);line-height:1.4;
  box-shadow:var(--shadow-md);
  pointer-events:none;
  max-width:280px;
  opacity:0;transition:opacity .1s;
  z-index:30;
  border-left-width:3px;
}
.chart-anno-tip.visible{opacity:1}
.chart-anno-tip.level-success{border-left-color:#10b981}
.chart-anno-tip.level-warning{border-left-color:#f59e0b}
.chart-anno-tip.level-error{border-left-color:#ef4444}
.chart-anno-tip.level-info{border-left-color:var(--data-blue)}
.chart-anno-tip-msg{display:block;font-weight:600;color:var(--text);word-break:break-word}
.chart-anno-tip-meta{display:block;font-size:10px;color:var(--text3);margin-top:3px;letter-spacing:.02em}

/* Pause indicator */
.chart.is-paused .chart-window{color:var(--data-blue);font-weight:700}
.time-range-pause{padding:5px 9px !important;display:inline-flex;align-items:center;gap:4px}
.time-range-pause svg{width:9px;height:9px}

/* Toolbar row on the right of System resources heading. Flex-wraps
   when the heading + controls together overflow the row width, so a
   narrow viewport gracefully bumps later buttons to a new line
   instead of squeezing them and wrapping text inside. */
.dash-controls-right{display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:flex-end;min-height:28px}
/* Shared button height for the dashboard control row (time range, pause, reset, export). */
.ctrl-btn{height:28px;display:inline-flex;align-items:center;gap:6px;padding:0 12px;background:transparent;border:1px solid var(--border);border-radius:6px;color:var(--text2);font-size:11px;font-weight:600;cursor:pointer;font-family:inherit;transition:color .12s,border-color .12s,background .12s;white-space:nowrap;flex-shrink:0}
.ctrl-btn:hover{color:var(--text);border-color:var(--border-strong);background:var(--bg2)}
.ctrl-btn:focus-visible{outline:none;border-color:var(--primary);box-shadow:0 0 0 2px color-mix(in srgb, var(--accent) 22%, transparent)}
/* Icons sized for legibility (was 9px - unreadable on the pause icon
   in particular). 14px gives clean detail at the current 11px label
   font without making the buttons feel chunky. */
.ctrl-btn svg{width:14px;height:14px;flex-shrink:0;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
/* Some icons (pause / play) read better as solid glyphs than as
   strokes. Opt in via .icon-solid on the SVG. */
.ctrl-btn svg.icon-solid{fill:currentColor;stroke:none}

/* Responsive collapse: on narrow viewports the labels next to icons
   eat all the horizontal room, so collapse to icon-only and expose
   the meaning via the existing title attribute (already set on every
   ctrl-btn for tooltip purposes). Square 28x28 hit targets, no text.
   The breakpoint matches when the dashboard layout starts narrowing
   the system-resources tiles. */
@media(max-width:900px){
  .dash-controls-right .ctrl-btn{padding:0;width:28px;justify-content:center}
  .dash-controls-right .ctrl-btn>span{display:none}
}
.time-range{height:28px}
.time-range button{height:22px}
/* =============================================================
   Telemetry monitoring page
   - KPI strip + toolbar + master/detail layout
   - Designed to fit a single viewport without page scroll
   ============================================================= */

/* Fit the telemetry page to a single viewport - no page scroll.
   The section becomes a flex column; the master-detail split fills the
   remaining height between the KPI strip + toolbar above and the bottom
   of the main content area. */
/* Height math: 100vh minus the 32px status footer minus a 12px gap
   between section and footer top border. With padding-bottom:20px
   inside the section, the visible space below the master/detail
   card adds up to 32px, matching the 32px right padding so the
   card sits inside an even gutter on all visible sides. */
#telemetry-mon{display:flex;flex-direction:column;padding:20px 32px 20px;min-height:0;height:calc(100vh - 44px)}

/* KPI strip: tight inline cards, no large vertical block. */
.tm-kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-bottom:10px;flex-shrink:0}
.tm-kpi{display:grid;grid-template-columns:1fr auto;align-items:baseline;column-gap:10px;background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius);padding:8px 12px;min-width:0}
.tm-kpi-l{font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--text2);grid-column:1;grid-row:1}
.tm-kpi-v{font-size:18px;font-weight:600;color:var(--text);font-variant-numeric:tabular-nums;line-height:1.1;white-space:nowrap;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;grid-column:2;grid-row:1 / span 2;align-self:center}
.tm-kpi-sub{font-size:10px;color:var(--text3);letter-spacing:.04em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;grid-column:1;grid-row:2}

/* Toolbar */
.tm-toolbar{display:flex;align-items:center;gap:10px;margin-bottom:10px;flex-wrap:wrap;flex-shrink:0}
.tm-toolbar .tm-spacer{flex:1 1 auto}
.tm-search{position:relative;display:flex;align-items:center;background:var(--bg2);border:1px solid var(--border);border-radius:6px;height:28px;padding:0 10px 0 8px;min-width:280px;flex:0 1 360px;transition:border-color .12s}
.tm-search:focus-within{border-color:var(--primary)}
.tm-search svg{width:13px;height:13px;color:var(--text3);flex-shrink:0;margin-right:6px}
.tm-search input{flex:1;min-width:0;background:transparent;border:none;outline:none;font:inherit;font-size:12px;color:var(--text);padding:0;height:26px}
.tm-search input::placeholder{color:var(--text3)}
.tm-kbd{display:inline-flex;align-items:center;padding:1px 6px;background:var(--bg);border:1px solid var(--border);border-radius:3px;font-size:10px;color:var(--text2);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-weight:600}
.tm-chips{display:inline-flex;background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:3px;gap:2px;height:28px}
.tm-chip{padding:0 11px;height:22px;border:none;background:transparent;color:var(--text2);font-size:11px;font-weight:600;border-radius:4px;cursor:pointer;font-family:inherit;display:inline-flex;align-items:center;letter-spacing:.02em;transition:color .12s,background .12s,box-shadow .12s}
.tm-chip:hover:not(.active){color:var(--text)}
.tm-chip.active{background:var(--bg2);color:var(--text);box-shadow:var(--shadow-seg)}
.tm-sort{padding:0 8px;height:28px;font-size:11px}
.tm-toggle{display:inline-flex;align-items:center;gap:6px;font-size:11px;color:var(--text2);cursor:pointer;user-select:none;height:28px;padding:0 10px;border:1px solid var(--border);border-radius:6px;background:var(--bg2);transition:color .12s,border-color .12s}
.tm-toggle:hover{color:var(--text);border-color:var(--border-strong)}
/* Live / paused indicator: same vertical-bar primitive as the row activity bar. */
.tm-live-tag{display:inline-flex;align-items:center;height:28px;padding:0 4px;font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:#10b981}
.tm-live-tag.paused{color:#f59e0b;opacity:.7}

/* Master-detail split - fills remaining viewport via flex:1 of the section. */
.tm-split{display:grid;grid-template-columns:minmax(340px,420px) minmax(0,1fr);gap:12px;flex:1 1 auto;min-height:0;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg2);overflow:hidden}
.tm-rail{position:relative;overflow-y:auto;overflow-x:hidden;background:var(--bg2);border-right:1px solid var(--border);outline:none}
.tm-rail::-webkit-scrollbar{width:8px}
.tm-rail::-webkit-scrollbar-track{background:transparent}
.tm-rail::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px}
.tm-rail-empty{padding:20px 16px;color:var(--text3);font-size:12px}
.tm-rail-group{padding:0}
.tm-rail-group-head{display:flex;align-items:center;gap:8px;padding:8px 14px;background:var(--bg);border-bottom:1px solid var(--border);border-top:1px solid var(--border);position:sticky;top:0;z-index:2}
.tm-rail-group:first-child .tm-rail-group-head{border-top:none}
.tm-rail-group-name{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:var(--text);font-weight:700}
.tm-rail-group-count{margin-left:auto;font-size:10px;font-weight:600;letter-spacing:.04em;color:var(--text3)}
/* Clickable list rows: suppress text selection + I-beam cursor so the
   whole row reads as a single click target. Without user-select:none
   the row's text children would still surface the text-edit caret on
   hover and leave a blinking insertion point when text gets selected
   incidentally. The container's cursor:pointer wins at the row level,
   but inline text nodes override it without this rule. */
.tm-row{display:grid;grid-template-columns:auto 1fr auto;gap:8px;align-items:center;padding:9px 14px;border-bottom:1px solid var(--border);cursor:pointer;transition:background .1s;position:relative;user-select:none;-webkit-user-select:none}
.tm-row:last-child{border-bottom:none}
.tm-row:hover{background:var(--bg3)}
.tm-row.selected{background:var(--hover-wash)}
.tm-row.selected::before{content:'';position:absolute;left:0;top:0;bottom:0;width:2px;background:var(--primary)}
.tm-row-l{display:flex;align-items:center;gap:8px;min-width:0}
.tm-row-glyph{width:18px;height:18px;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}
.tm-row-glyph svg{width:14px;height:14px}
.tm-row-glyph.counter{color:#3b82f6}
.tm-row-glyph.gauge{color:#10b981}
.tm-row-glyph.histogram{color:#8b5cf6}
.tm-row-glyph.obs_counter{color:#3b82f6}
.tm-row-glyph.updowncounter{color:#06b6d4}
.tm-row-glyph.obs_updowncounter{color:#06b6d4}
/* Activity bar: thin vertical primitive that echoes the selected-row left-accent
   and the bucket-distribution bars. Color carries the state (fresh / warm /
   cold). No pulse, no breathing - the color is enough. */
.tm-row-act{width:3px;height:18px;border-radius:1px;flex-shrink:0;background:var(--border);transition:background .2s,opacity .2s}
.tm-row-act.fresh{background:#10b981}
.tm-row-act.warm{background:#f59e0b;opacity:.7}
.tm-row-mid{min-width:0;display:flex;flex-direction:column;gap:1px}
.tm-row-name{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;color:var(--text);line-height:1.25;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tm-row-meta{font-size:10px;color:var(--text3);font-weight:600;letter-spacing:.04em;display:flex;gap:6px;align-items:center}
.tm-row-meta-dot{width:2px;height:2px;border-radius:50%;background:currentColor;flex-shrink:0;opacity:.6}
.tm-row-mini{width:80px;height:24px;flex-shrink:0;display:block;cursor:crosshair}
.tm-row-mini polyline{fill:none;stroke-width:1.4;vector-effect:non-scaling-stroke}
.tm-row-mini polygon{opacity:.16;stroke:none}
.tm-row-r{display:flex;flex-direction:column;align-items:flex-end;gap:1px;min-width:90px}
.tm-row-val{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;color:var(--text);font-weight:600;font-variant-numeric:tabular-nums;white-space:nowrap}
.tm-row-val.dim{color:var(--text3);font-weight:400;font-style:italic}
.tm-row-vsub{font-size:9px;color:var(--text3);letter-spacing:.04em;text-transform:uppercase;font-weight:600;white-space:nowrap}

/* Detail panel */
.tm-detail{overflow-y:auto;padding:0}
.tm-detail::-webkit-scrollbar{width:8px}
.tm-detail::-webkit-scrollbar-track{background:transparent}
.tm-detail::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px}
.tm-detail-empty{height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:10px;padding:40px}
.tm-detail-empty-glyph{width:48px;height:48px;border-radius:50%;background:var(--bg);border:1px solid var(--border);display:inline-flex;align-items:center;justify-content:center;color:var(--text3)}
.tm-detail-empty-glyph svg{width:24px;height:24px}
.tm-detail-empty-title{font-size:14px;font-weight:600;color:var(--text2)}
.tm-detail-empty-hint{font-size:12px;color:var(--text3);max-width:340px;line-height:1.5}
.tm-detail-head{padding:16px 18px 14px;border-bottom:1px solid var(--border);background:var(--bg2);position:sticky;top:0;z-index:3}
.tm-detail-head-top{display:flex;align-items:flex-start;justify-content:space-between;gap:14px}
.tm-detail-head-l{display:flex;flex-direction:column;gap:4px;min-width:0;flex:1}
/* Kind / meter / unit labels: flat colored text, no chip outline.
   Each segment is separated visually by a thin middle-dot. */
.tm-detail-kind-row{display:flex;align-items:center;gap:8px;font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--text3)}
.tm-detail-kind-row .tm-kind-tag{font-size:10px;font-weight:700;letter-spacing:.08em;line-height:1;text-transform:uppercase}
.tm-detail-kind-row .tm-kind-tag::after{content:'·';margin-left:8px;color:var(--text3);font-weight:400}
.tm-detail-kind-row .tm-kind-tag.counter,.tm-detail-kind-row .tm-kind-tag.obs_counter{color:#3b82f6}
.tm-detail-kind-row .tm-kind-tag.gauge{color:#10b981}
.tm-detail-kind-row .tm-kind-tag.histogram{color:#8b5cf6}
.tm-detail-kind-row .tm-kind-tag.updowncounter,.tm-detail-kind-row .tm-kind-tag.obs_updowncounter{color:#06b6d4}
.tm-detail-name{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:18px;font-weight:600;color:var(--text);line-height:1.2;word-break:break-all}
.tm-detail-sub{font-size:12px;color:var(--text2);line-height:1.5}
.tm-detail-head-r{display:flex;flex-direction:column;align-items:flex-end;gap:4px;flex-shrink:0;text-align:right}
.tm-detail-when{font-size:11px;color:var(--text3);font-weight:600;letter-spacing:.04em;text-transform:uppercase}
/* Unit label in the detail header: flat monospace, no chip background. */
.tm-detail-pill{display:inline-flex;align-items:center;gap:6px;font-size:11px;letter-spacing:.04em;color:var(--text2);font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
.tm-detail-pill::before{content:'unit';color:var(--text3);font-size:9px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;margin-right:6px}

.tm-section{padding:14px 18px;border-bottom:1px solid var(--border)}
.tm-section:last-child{border-bottom:none}
.tm-section-title{font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--text3);margin-bottom:8px}
.tm-section-title .tm-section-meta{margin-left:8px;color:var(--text3);font-size:10px;letter-spacing:.04em}

/* Live chart in detail pane. The Y-axis min/max labels sit in opposite
   corners; the 60s window framing is conveyed by the section title above so
   there is no bottom-left "60s ago" / bottom-right "now" footer (which was
   overlapping the Y-min label). */
.tm-bigchart{position:relative;height:148px;background:var(--bg);border:1px solid var(--border);border-radius:4px;overflow:visible;cursor:crosshair}
.tm-bigchart-unfilled{position:absolute;left:1px;top:1px;bottom:1px;background:repeating-linear-gradient(135deg,var(--bg2),var(--bg2) 6px,transparent 6px,transparent 12px);border-right:1px dashed var(--border-strong,var(--border));display:none;align-items:center;justify-content:center;pointer-events:none;border-top-left-radius:3px;border-bottom-left-radius:3px;opacity:.55;z-index:1}
.tm-bigchart-unfilled-l{font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--text3);background:var(--bg);padding:2px 8px;border-radius:3px;white-space:nowrap}
.tm-bigchart > svg{width:100%;height:100%;overflow:hidden;border-radius:4px;display:block}
.tm-bigchart polyline.line{fill:none;stroke:var(--data-blue);stroke-width:1.6;vector-effect:non-scaling-stroke;pointer-events:none}
.tm-bigchart polygon.fill{fill:var(--data-blue);opacity:.13;stroke:none;pointer-events:none}
.tm-bigchart .tm-bigchart-yax{position:absolute;top:6px;right:8px;font-size:10px;color:var(--text3);letter-spacing:.04em;font-variant-numeric:tabular-nums;font-weight:600;line-height:1;pointer-events:none}
.tm-bigchart .tm-bigchart-yax.min{top:auto;bottom:6px}
/* Crosshair + dot are drawn inside the SVG so they clip with the chart. The
   cursor line uses the row-activity-bar accent color for visual unity. */
.tm-bigchart .tm-bigchart-cursor{stroke:var(--text2);stroke-width:1;stroke-dasharray:2 2;opacity:.55;vector-effect:non-scaling-stroke;pointer-events:none}
.tm-bigchart .tm-bigchart-dot{fill:var(--data-blue);stroke:var(--bg);stroke-width:.6;vector-effect:non-scaling-stroke;pointer-events:none}
/* Floating tooltip rendered above the chart cursor. Translated -50% on X so
   it centers on the cursor point; flips below the dot when too close to the
   top of the chart. */
.tm-chart-tip{position:absolute;pointer-events:none;background:var(--bg2);border:1px solid var(--border-strong,var(--border));border-radius:4px;padding:5px 9px;min-width:64px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;box-shadow:var(--shadow-md);transform:translate(-50%, -100%);opacity:0;transition:opacity .08s;z-index:5;white-space:nowrap}
.tm-chart-tip.visible{opacity:1}
.tm-chart-tip.below{transform:translate(-50%, 0)}
.tm-chart-tip-v{font-size:12px;color:var(--text);font-weight:700;font-variant-numeric:tabular-nums;line-height:1.1}
.tm-chart-tip-sub{font-size:10px;color:var(--text3);letter-spacing:.02em;margin-top:2px;font-family:inherit}
.tm-chart-tip-sub .pos{color:#10b981;font-weight:600}
.tm-chart-tip-sub .neg{color:#ef4444;font-weight:600}

/* Window-selector tabs in the detail pane (1m / 5m / 15m / 1h). */
.tm-section-title-row{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:8px}
.tm-section-title-row .tm-section-title{margin-bottom:0}
.tm-window-tabs{display:inline-flex;background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:2px;gap:2px;height:24px;flex-shrink:0}
.tm-window-tab{padding:0 9px;height:18px;border:none;background:transparent;color:var(--text3);font-size:10px;font-weight:700;letter-spacing:.04em;border-radius:3px;cursor:pointer;font-family:inherit;display:inline-flex;align-items:center;transition:color .12s,background .12s,box-shadow .12s;font-variant-numeric:tabular-nums}
.tm-window-tab:hover:not(.active){color:var(--text2)}
.tm-window-tab.active{background:var(--bg2);color:var(--text);box-shadow:var(--shadow-seg)}

/* Bucket hover tooltip - appears next to the cursor inside the buckets
   section, no transform offset so the value sits under the mouse. */
.tm-buckets{position:relative}
.tm-bucket-tip{position:absolute;pointer-events:none;background:var(--bg2);border:1px solid var(--border-strong,var(--border));border-radius:4px;padding:7px 10px;min-width:160px;box-shadow:var(--shadow-md);opacity:0;transition:opacity .08s;z-index:5;font-family:inherit}
.tm-bucket-tip.visible{opacity:1}
.tm-bucket-tip-range{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:var(--text);font-weight:700;margin-bottom:5px;padding-bottom:5px;border-bottom:1px solid var(--border)}
.tm-bucket-tip-row{display:flex;justify-content:space-between;gap:14px;font-size:10px;color:var(--text2);font-variant-numeric:tabular-nums}
.tm-bucket-tip-row span:first-child{color:var(--text3);font-weight:600;letter-spacing:.02em;text-transform:uppercase;font-size:9px}
.tm-bucket-tip-row span:last-child{color:var(--text);font-weight:600;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}

/* Rail mini-spark tooltip - peek at value/age at the cursor position. */
.tm-spark-tip{position:absolute;pointer-events:none;background:var(--bg2);border:1px solid var(--border-strong,var(--border));border-radius:4px;padding:4px 8px;box-shadow:var(--shadow-md);opacity:0;transition:opacity .08s;z-index:6;white-space:nowrap;font-family:inherit}
.tm-spark-tip.visible{opacity:1}
.tm-spark-tip-v{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:var(--text);font-weight:700;font-variant-numeric:tabular-nums;line-height:1.1}
.tm-spark-tip-sub{font-size:9px;color:var(--text3);letter-spacing:.04em;margin-top:1px}

/* Mark clickable tag chips so users discover the click-to-filter affordance. */
.tm-tag-pair.clickable{cursor:pointer;transition:color .1s}
.tm-tag-pair.clickable:hover .v{text-decoration:underline;text-decoration-color:var(--text3);text-decoration-thickness:1px;text-underline-offset:2px}

/* Stats grid */
.tm-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(110px,1fr));gap:8px}
.tm-stat{background:var(--bg);border:1px solid var(--border);border-radius:4px;padding:8px 10px;display:flex;flex-direction:column;gap:2px;min-width:0}
.tm-stat-l{font-size:9px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--text3)}
.tm-stat-v{font-size:14px;font-weight:600;color:var(--text);font-variant-numeric:tabular-nums;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;line-height:1.2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.tm-stat-v.dim{color:var(--text3);font-weight:400}
.tm-stat.highlight .tm-stat-v{color:var(--data-blue)}

/* Bucket distribution */
.tm-buckets{display:flex;flex-direction:column;gap:2px;font-variant-numeric:tabular-nums}
.tm-bucket{display:grid;grid-template-columns:120px 1fr 50px;gap:8px;align-items:center;font-size:11px;color:var(--text2);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;cursor:default}
.tm-bucket-range{color:var(--text3);text-align:right;white-space:nowrap}
.tm-bucket-bar{height:14px;background:var(--bg);border-radius:2px;position:relative;overflow:hidden}
.tm-bucket-bar-fill{position:absolute;left:0;top:0;bottom:0;background:linear-gradient(90deg,rgba(139,92,246,.6),rgba(139,92,246,.85));border-radius:2px;transition:width .35s ease-out}
.tm-bucket-count{color:var(--text);font-weight:600;text-align:right;white-space:nowrap}
.tm-bucket.empty .tm-bucket-bar-fill{background:transparent}
.tm-bucket.empty .tm-bucket-count{color:var(--text3);font-weight:400}

/* Tag breakdown table.
   Per row: stacked tag list (primary differentiators on top, invariants
   below in a dimmer secondary line), samples count, sparkline, primary
   value with kind-aware label (total / p95 / current). */
.tm-tags{display:flex;flex-direction:column;gap:0;border:1px solid var(--border);border-radius:4px;overflow:hidden;background:var(--bg)}
.tm-tag-row{display:grid;grid-template-columns:minmax(0,1fr) auto auto auto;gap:16px;align-items:center;padding:10px 14px;border-bottom:1px solid var(--border);font-size:11px}
.tm-tag-row:last-child{border-bottom:none}
.tm-tag-row:hover{background:var(--bg2)}
.tm-tag-pairs-stack{display:flex;flex-direction:column;gap:3px;min-width:0}
/* Tag key=value pairs: flat monospace text, no chip outline. */
.tm-tag-pairs{display:flex;flex-wrap:wrap;column-gap:14px;row-gap:2px;min-width:0;align-items:baseline}
.tm-tag-pair{display:inline-flex;align-items:baseline;gap:0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:var(--text2);white-space:nowrap;max-width:340px;overflow:hidden;text-overflow:ellipsis;line-height:1.4}
.tm-tag-pair .k{color:var(--text3);margin-right:4px}
.tm-tag-pair .k::after{content:'='}
.tm-tag-pair .v{color:var(--text);font-weight:600}
.tm-tag-pair.error .v{color:#ef4444}
.tm-tag-pair.success .v{color:#10b981}
/* Primary line: differentiating tags - slightly larger, full text weight. */
.tm-tag-pairs.primary .tm-tag-pair .v{color:var(--text);font-weight:700}
/* Secondary line: invariant tags shared across all series - smaller, dimmer. */
.tm-tag-pairs.secondary{column-gap:12px;opacity:.72}
.tm-tag-pairs.secondary .tm-tag-pair{font-size:10px}
.tm-tag-pairs.secondary .tm-tag-pair .k,.tm-tag-pairs.secondary .tm-tag-pair .v{color:var(--text3);font-weight:500}
.tm-tag-stat{display:flex;flex-direction:column;align-items:flex-end;gap:1px;min-width:64px;text-align:right}
.tm-tag-stat-l{font-size:9px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--text3)}
.tm-tag-stat-v{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:var(--text);font-weight:600;font-variant-numeric:tabular-nums;white-space:nowrap}
.tm-tag-stat-val{min-width:88px}
.tm-tag-mini{width:80px;height:18px;flex-shrink:0;display:block;align-self:end}
.tm-tag-mini polyline{fill:none;stroke:var(--data-blue);stroke-width:1.2;vector-effect:non-scaling-stroke}
.tm-tag-mini polygon{fill:var(--data-blue);opacity:.13;stroke:none}

.tm-empty-section{padding:20px;text-align:center;color:var(--text3);font-size:12px;font-style:italic}

/* Compact density */
html.density-compact .tm-kpi{padding:8px 10px}
html.density-compact .tm-kpi-v{font-size:18px}
html.density-compact .tm-row{padding:7px 12px}
html.density-compact .tm-section{padding:10px 14px}
html.density-compact .tm-bigchart{height:130px}

/* Responsive: collapse to single column on narrow viewports */
@media (max-width: 900px){
  .tm-split{grid-template-columns:1fr;height:auto}
  .tm-kpis{grid-template-columns:repeat(2,1fr)}
}

.alerts-toolbar{display:flex;gap:8px;margin-bottom:12px;max-width:1080px}
.alerts-list{display:flex;flex-direction:column;gap:0;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg2);max-width:1080px;overflow:hidden}
.alerts-list .hint{padding:14px 16px}
.alert-row{display:grid;grid-template-columns:auto 90px 1fr;align-items:center;gap:14px;padding:12px 16px;border-bottom:1px solid var(--border);font-size:12px}
.alert-row:last-child{border-bottom:none}
.alert-time{color:var(--text3);font-variant-numeric:tabular-nums;font-size:11px;white-space:nowrap}
.alert-level{display:inline-flex;align-items:center;gap:6px;font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase}
.alert-level::before{content:'';width:6px;height:6px;border-radius:50%;background:currentColor}
.alert-row.level-warning .alert-level{color:#f59e0b}
.alert-row.level-error .alert-level{color:#ef4444}
.alert-row.level-info .alert-level{color:var(--data-blue)}
.alert-msg{color:var(--text)}

/* Time range selector */
.time-range{display:inline-flex;background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:3px;gap:2px}
.time-range button{padding:5px 11px;border:none;background:transparent;color:var(--text2);font-size:11px;font-weight:600;border-radius:4px;cursor:pointer;font-family:inherit;transition:color .12s,background .12s,box-shadow .12s;font-variant-numeric:tabular-nums}
.time-range button:hover:not(.active){color:var(--text)}
.time-range button.active{background:var(--bg2);color:var(--text);box-shadow:var(--shadow-seg)}
.chart-window-l{position:absolute;bottom:6px;left:14px;font-size:9px;color:var(--text3);letter-spacing:.08em;text-transform:uppercase;font-weight:600}
.chart-grid{stroke:var(--border);stroke-width:1;vector-effect:non-scaling-stroke;stroke-dasharray:3 3;opacity:.7}
/* Violet variant for GPU compute */
.chart.chart-compute .chart-line{stroke:#8b5cf6}
.chart.chart-compute .chart-fill{fill:#8b5cf6;fill-opacity:.16}
.gpu-spark-row{display:grid;grid-template-columns:62px 1fr;align-items:start;gap:10px;margin-top:8px}
.gpu-spark-row .gpu-spark{margin-top:0}
.gpu-spark-row .spark-range{margin-top:2px}
.gpu-spark-label{font-size:10px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--text2);padding-top:4px;display:flex;align-items:center;gap:6px}
.gpu-spark-label::before{content:'';width:8px;height:2px;border-radius:1px;background:var(--data-blue)}
.gpu-spark-label.compute::before{background:#8b5cf6}
.gpu-spark-col{min-width:0}
.gpu-spark-row .stat-spark.spark-util polyline{stroke:#8b5cf6}
.spark-range{display:flex;justify-content:space-between;font-size:10px;color:var(--text3);margin-top:2px;font-variant-numeric:tabular-nums;letter-spacing:.01em;min-height:12px}
.spark-range::before{content:attr(data-min);color:var(--text3)}
.spark-range::after{content:attr(data-max);color:var(--text2);font-weight:500}
.spark-range[data-min=""][data-max=""]{visibility:hidden}
.stat-card-cta{margin-top:8px;align-self:flex-start;padding:5px 10px;background:transparent;border:1px solid var(--border);border-radius:6px;font-size:11px;font-weight:600;color:var(--text2);cursor:pointer;font-family:inherit;transition:background .12s,border-color .12s,color .12s}
.stat-card-cta:hover{background:var(--hover-wash);border-color:var(--border-strong);color:var(--text)}
/* Per-lane breakdown for the Active-inferences card: two stacked lines (emb. / comp.) placed in the empty
   space to the right of the value, so the card keeps its original height and nothing below it shifts. */
.stat-value-row{display:flex;align-items:center;gap:12px}
/* Its data-tip renders through the shared fixed-layer tooltip engine. */
.stat-breakdown{display:flex;flex-direction:column;gap:1px;font-size:11px;color:var(--text2);font-weight:500;line-height:1.2}
/* Generic right-anchored side panel (used by Active Inferences) */
.side-panel{position:fixed;top:var(--appbar);right:0;bottom:0;width:440px;min-width:320px;max-width:92vw;background:var(--bg2);border-left:1px solid var(--border);display:flex;flex-direction:column;z-index:90;box-shadow:-18px 0 50px rgba(0,0,0,.42), -2px 0 6px rgba(0,0,0,.18);animation:slidein .18s ease}
/* Resize grip on the left edge. Minimal pull-tab: a small rounded
   pill (3 px × 44 px) centered vertically on the edge. Subtle gray at
   rest, brightens to blue on hover or drag. Generous 10 px hot zone
   makes grabbing forgiving without being visually loud. */
.side-panel-grip{position:absolute;left:-5px;top:0;bottom:0;width:10px;cursor:col-resize;z-index:91;user-select:none;display:flex;align-items:center;justify-content:center}
.side-panel-grip::after{
  content:'';
  width:3px;height:44px;
  background:var(--border-strong,var(--text3));
  border-radius:2px;
  opacity:.55;
  transition:background .15s,opacity .15s,height .15s;
}
.side-panel-grip:hover::after,.side-panel-grip.dragging::after{background:var(--primary);opacity:1;height:56px}
/* Disable text selection across the document while dragging so the
   drag doesn't highlight content under the cursor. NO animation /
   transition overrides on the panel itself: changing the animation
   property mid-drag was causing the slide-in keyframe to replay on
   release, which looked like the panel was being hidden and shown. */
body.side-panel-resizing{cursor:col-resize;user-select:none;-webkit-user-select:none}
.side-panel-head{display:flex;align-items:center;justify-content:space-between;padding:24px 24px 14px;border-bottom:1px solid var(--border);gap:16px}
.side-panel-eyebrow{display:block;font-size:10px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;color:var(--text2);margin-bottom:4px}
.side-panel-title{font-size:18px;font-weight:700;color:var(--text);margin:0}
.side-panel-body{flex:1;overflow:auto;padding:16px 24px}
.side-panel-empty{padding:24px 0;color:var(--text3);font-size:13px;text-align:center}
/* OCR status panel: a hero "running / max" figure above a labelled key/value list. The OCR engine has
   no per-request registry, so the panel reports engine status rather than a live per-operation list. */
.ocr-hero{display:flex;flex-direction:column;gap:4px;padding:6px 0 18px;border-bottom:1px solid var(--border);margin-bottom:14px}
.ocr-hero-v{font-size:34px;font-weight:700;color:var(--text);font-variant-numeric:tabular-nums;line-height:1}
.ocr-hero-sep{color:var(--text3);font-weight:500;margin:0 6px}
.ocr-hero-l{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--text3)}
.ocr-dl{display:flex;flex-direction:column}
.ocr-dl-row{display:flex;align-items:baseline;justify-content:space-between;gap:16px;padding:9px 0;border-bottom:1px solid var(--border)}
.ocr-dl-k{font-size:12px;color:var(--text2)}
.ocr-dl-v{font-size:13px;font-weight:500;color:var(--text);text-align:right;font-variant-numeric:tabular-nums}
.ocr-dl-sub{font-weight:400;color:var(--text3);font-size:11px}
.ocr-note{font-size:12px;line-height:1.5;color:var(--text3);margin:14px 0 0}
.side-panel-row{display:grid;grid-template-columns:auto 1fr auto;gap:12px;align-items:baseline;padding:10px 0;border-bottom:1px solid var(--border)}
.side-panel-row:last-child{border-bottom:none}
.side-panel-method{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:var(--data-blue);font-weight:700}
.side-panel-path{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.side-panel-meta{font-size:11px;color:var(--text3);font-variant-numeric:tabular-nums}
.side-panel-row-inference{display:flex;flex-direction:column;gap:6px;align-items:stretch}
.side-panel-row-inference.is-canceling{opacity:.7}
.side-panel-row-top{display:grid;grid-template-columns:auto 1fr auto auto;gap:10px;align-items:center}
.side-panel-row-bot{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.side-panel-model{font-size:10px;font-weight:600;color:var(--primary);background:var(--hover-wash);border:1px solid color-mix(in srgb, var(--text) 14%, transparent);padding:2px 7px;border-radius:4px;letter-spacing:.04em;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
.side-panel-stream{display:inline-flex;align-items:center;gap:10px;font-variant-numeric:tabular-nums}
.side-panel-tps{font-size:12px;font-weight:700;color:#10b981}
.side-panel-tok{font-size:11px;color:var(--text2)}
.side-panel-cancel{height:24px;padding:0 10px;font:inherit;font-size:11px;font-weight:600;color:#ef4444;background:transparent;border:1px solid rgba(239,68,68,.32);border-radius:4px;cursor:pointer;letter-spacing:.02em;white-space:nowrap;transition:color .12s,background .12s,border-color .12s}
.side-panel-cancel:hover:not(:disabled){background:rgba(239,68,68,.10);border-color:rgba(239,68,68,.55);color:#ef4444}
.side-panel-cancel:disabled{cursor:default;color:var(--text3);border-color:var(--border);background:transparent}

/* H2 section headings inside the Dashboard. Generous top margin
   visually separates one block from the next; the first heading in a
   section gets its top margin collapsed via :first-of-type so it
   doesn't push down from the section header. */
.dash-heading{font-size:13px;font-weight:600;color:var(--text2);margin-bottom:10px;margin-top:28px}
/* One muted sentence under a heading whose first child is a bare field:
   a heading never sits directly on another label. */
.dash-sub{font-size:12px;color:var(--text3);margin:-6px 0 12px;max-width:760px}
.dash-heading:first-of-type{margin-top:4px}

/* Resolved outcome of the slot-pool shape: what the configured slots and
   window actually cost, and whether the device memory ceiling would cut
   them down. Static colour carries the verdict; there is no dot. */
/* Slot-pool fit warning. Same left-accent callout primitive as .ss-mx--warn and
   .verdict.warn, so the admin has ONE shape for "this needs your attention"
   rather than a third invention. Owns margin on BOTH sides: the grid above
   spaces itself, and without a bottom margin this sat flush against the toggle
   cards below it. */
.slot-fit{margin:4px 0 24px;padding:12px 14px;border:1px solid var(--border);
          border-left:3px solid #f59e0b;border-radius:var(--radius);
          background:rgba(245,158,11,.06);max-width:820px}
.slot-fit-head{display:flex;align-items:baseline;gap:8px;margin-bottom:6px}
.slot-fit-title{font-size:12px;font-weight:600;color:#b45309;letter-spacing:.01em}
.slot-fit-line{display:block;font-size:12px;line-height:1.65;color:var(--text2)}
.slot-fit-line + .slot-fit-line{margin-top:6px;padding-top:6px;border-top:1px solid var(--border)}
.slot-fit-shape{font-weight:600;color:var(--text);font-variant-numeric:tabular-nums}

/* ── Per-model slot overrides ──
   One card per model that departs from the shared shape. Each card states the
   model, the two inputs (blank = inherit, with the inherited number as the
   placeholder), and one sentence resolving what the model will actually be
   served at. The sentence reserves its height so a row never changes size as
   the numbers change. */
/* ── MCP ── */
/* Every block in this section shares one measure so nothing runs past the
   column the text above it occupies. */
.mcp-hint,.mcp-endpoint,.mcp-toolbar,.mcp-ceiling,#mcp-catalog-body,.mcp-limits,#mcp-prompts-body,.mcp-connect,.mcp-connect-off,.mcp-sessions-cap,#mcp-sessions-wrap,.mcp-roots{max-width:900px}

/* Endpoint card. The serving verdict sits beside the switch and reserves the
   width of its longest form, so flipping the toggle never moves it. Amber is
   for the two states where saved config and runtime disagree (a restart is
   owed); agreement stays quiet. */
.mcp-endpoint .row-sub{grid-template-columns:repeat(auto-fit,minmax(220px,280px))}
.mcp-live{flex:0 0 auto;font-size:12px;color:var(--text3);white-space:nowrap;min-width:22ch;max-width:34ch;overflow:hidden;text-overflow:ellipsis;text-align:right;transition:color .15s}
.mcp-live.is-on{color:var(--text2)}
.mcp-live.is-pending{color:#f59e0b}
html.theme-light .mcp-live.is-pending{color:#b45309}

/* Catalog toolbar: exposure summary and warnings on the left, the view and
   preset controls on the right. Count and note reserve their widest forms so
   toggling a tool never nudges the controls beside them. */
.mcp-toolbar{display:flex;align-items:flex-start;justify-content:space-between;gap:12px 16px;margin-bottom:12px;flex-wrap:wrap}
.mcp-toolbar-state{display:flex;flex-direction:column;gap:2px;min-width:0}
.mcp-exposed{font-size:13px;font-weight:600;color:var(--text);font-variant-numeric:tabular-nums;white-space:nowrap;min-height:26px;line-height:26px}
.mcp-catalog-note{font-size:11px;line-height:1.5;color:#f59e0b;min-height:1.5em}
html.theme-light .mcp-catalog-note{color:#b45309}

/* Skills inventory: one hairline-divided card, plain facts per skill; only
   problems (spec issues, unparseable folders) carry color. */
#skills .storage-grid{max-width:900px;margin-bottom:20px}
#skills-catalog-body,#skills .skills-foot-row{max-width:900px}
.skills-inv{border:1px solid var(--border);border-radius:var(--radius);background:var(--bg2);overflow:hidden}
.skills-item{display:flex;align-items:flex-start;gap:12px;padding:12px 16px}
.skills-item+.skills-item{border-top:1px solid var(--border)}
.skills-item-text{display:flex;flex-direction:column;gap:3px;flex:1;min-width:0}
.skills-item-title{display:flex;align-items:baseline;gap:8px;min-width:0;flex-wrap:wrap}
.skills-item-name{font-family:var(--mono);font-size:12px;font-weight:500;color:var(--text)}
.skills-item-meta{font-size:11px;color:var(--text3);white-space:nowrap}
.skills-item-desc{font-size:12px;line-height:1.5;color:var(--text3)}
.skills-item-issue{display:flex;align-items:baseline;gap:6px;font-size:11px;line-height:1.5;color:#f59e0b}
.skills-item-issue svg{flex:0 0 auto;align-self:center}
html.theme-light .skills-item-issue{color:#b45309}
.skills-empty{display:block;font-size:12px;line-height:1.6;color:var(--text3);padding:14px 16px}
/* Offer toggle: always rendered (fixed footprint), it carries the row's
   enabled state; a disabled skill dims its facts, never its controls. */
.skills-item-toggle{flex:0 0 auto;align-self:center}
.skills-item.is-off .skills-item-text{opacity:.55}
/* Declarative-source report: one quiet line per configured URL. */
.skills-src-report{display:flex;flex-direction:column;gap:6px;margin-top:10px}
.skills-src-row{display:flex;flex-direction:column;gap:2px;min-width:0}
.skills-src-url{font-family:var(--mono);font-size:11px;color:var(--text3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.skills-src-facts{font-size:11px;line-height:1.5;color:var(--text3)}
/* Tool inventory rows: the tool's wire name reads as code. */
.tool-name{font-family:var(--mono);font-size:12.5px}
/* Connector editor: stacked fields; the transport choice dims whichever
   target fields do not apply, in place. */
.conn-modal{width:480px;text-align:left}
.conn-modal h2{text-align:left}
.conn-form{display:flex;flex-direction:column;gap:14px;margin-bottom:16px}
.conn-form input:where(:not([type=checkbox]):not([type=radio])),
.conn-form select{width:100%;box-sizing:border-box}
.conn-status-error{color:#f59e0b}
html.theme-light .conn-status-error{color:#b45309}
/* Per-key tool grants editor: one follow-policy switch over the curated
   list; the list dims in place while the key follows the policy, so
   nothing moves when the choice flips. */
.keytools-modal{width:480px;text-align:left}
.keytools-modal h2{text-align:left}
.keytools-follow{display:flex;align-items:center;gap:10px;padding:10px 12px;margin:0 0 10px;border:1px solid var(--border);border-radius:8px;background:var(--bg);cursor:pointer;font-size:12.5px;color:var(--text)}
.keytools-list{max-height:300px;overflow-y:auto;overscroll-behavior:contain;border:1px solid var(--border);border-radius:8px;background:var(--bg);margin-bottom:14px}
.keytools-list.is-following{opacity:.45;pointer-events:none}
.keytools-row{display:flex;align-items:flex-start;gap:10px;padding:9px 12px;cursor:pointer}
.keytools-row+.keytools-row{border-top:1px solid var(--border)}
.keytools-row:hover{background:var(--hover-wash)}
.keytools-row input{flex:0 0 auto;margin-top:2px}
.keytools-row-text{display:flex;flex-direction:column;gap:2px;min-width:0}
.keytools-row-name{font-family:var(--mono);font-size:11.5px;font-weight:500;color:var(--text)}
.keytools-row-desc{font-size:11px;line-height:1.45;color:var(--text3)}
/* Source headings inside a tool picker: built-ins first, then each
   connector's group, so where a tool comes from is always visible. */
.keytools-group{padding:10px 12px 4px;font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--text3)}
.keytools-group+.keytools-row{border-top:none}
/* Per-key search-access editor: one any-cluster switch over the cluster list,
   with a reserved status line below it that always states the key's reach, so
   the modal never resizes as choices change and a severed tenant grant is
   called out in place. */
.keyaccess-modal{width:560px;text-align:left}
.keyaccess-modal h2{text-align:left}
.keyaccess-list{max-height:320px;overflow-y:auto;overscroll-behavior:contain;border:1px solid var(--border);border-radius:8px;background:var(--bg)}
.keyaccess-list.is-following{opacity:.45;pointer-events:none}
.keyaccess-row{display:flex;align-items:flex-start;gap:10px;padding:10px 12px;cursor:pointer}
.keyaccess-row+.keyaccess-row{border-top:1px solid var(--border)}
.keyaccess-row:hover{background:var(--hover-wash)}
.keyaccess-row input{flex:0 0 auto;margin-top:2px}
.keyaccess-row-text{display:flex;flex-direction:column;gap:2px;min-width:0}
.keyaccess-row-name{font-family:var(--mono);font-size:11.5px;font-weight:500;color:var(--text)}
.keyaccess-row-desc{font-size:11px;line-height:1.45;color:var(--text3)}
.keyaccess-note{min-height:34px;margin:10px 0 14px;font-size:11.5px;line-height:1.5;color:var(--text3)}
.keyaccess-note.is-warning{color:#f59e0b}
html.theme-light .keyaccess-note.is-warning{color:#b45309}
.keyaccess-empty{display:block;padding:12px;font-size:11.5px;color:var(--text3)}
/* Key onboarding: the create dialog mints a key with its reach chosen up front. The tenant
   block exists only while a specific cluster is selected (gated, never dimmed), so the form
   stays as small as the choice made. */
.keycreate-modal{width:520px;text-align:left}
.keycreate-modal h2{text-align:left}
.keycreate-tenants{display:flex;flex-direction:column;gap:8px;margin-top:2px}
.keycreate-tenants-title{font-size:11px;font-weight:600;color:var(--text2)}
.keycreate-tenants-list{max-height:180px;overflow-y:auto;overscroll-behavior:contain;border:1px solid var(--border);border-radius:8px;background:var(--bg)}
.keycreate-tenant-row{display:flex;align-items:center;gap:10px;padding:8px 12px;cursor:pointer;font-size:12px;color:var(--text)}
.keycreate-tenant-row+.keycreate-tenant-row{border-top:1px solid var(--border)}
.keycreate-tenant-row:hover{background:var(--hover-wash)}
.keycreate-tenant-row input{flex:0 0 auto;margin:0}
.keycreate-tenants-empty{padding:10px 12px;font-size:11.5px;color:var(--text3)}
/* Group headings inside a .rows inventory (the Tools catalog): same voice
   as .keytools-group, painted on the rows' own background. */
.rows .row-group{padding:12px 16px 5px;font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--text3);background:var(--bg2)}
.rows .row-groupnote{padding:0 16px 10px;font-size:11.5px;line-height:1.5;color:var(--text3);background:var(--bg2)}
.rows .row-groupnote a{color:var(--primary);text-decoration:none}
.rows .row-groupnote a:hover{text-decoration:underline}
/* Agent builder: a full section subview, not a modal. Identity on the
   left, the tool picker on the right; on narrow screens they stack. */
.agb-head{position:relative}
.agb-back{display:inline-flex;align-items:center;gap:6px;margin:0 0 10px;padding:4px 10px 4px 6px;background:none;border:1px solid var(--border);border-radius:6px;color:var(--text2);cursor:pointer;font-family:inherit;font-size:12px;font-weight:500;transition:color .12s,border-color .12s}
.agb-back:hover{color:var(--text);border-color:var(--border-strong)}
.agb-back svg{width:14px;height:14px}
.agb-actions{display:flex;gap:8px;flex:0 0 auto}
.agb-grid{display:grid;grid-template-columns:minmax(320px,5fr) minmax(340px,6fr);gap:28px;align-items:start}
@media (max-width:1100px){.agb-grid{grid-template-columns:1fr}}
/* Each column OWNS its vertical rhythm: stacked children can never sit
   flush, whatever gets added later. Never rely on a child's own margin
   for the gap between two form elements. */
.agb-col{min-width:0;display:flex;flex-direction:column;gap:16px}
.agb-col-title{margin:0;font-size:11px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--text3)}
.field+.agb-col-title{margin-top:10px}
.agb-col-sub{margin:-8px 0 0;font-size:12px;line-height:1.55;color:var(--text2)}
.agb-col textarea{resize:vertical}
/* The memory row is the switch's whole hit target (wired by delegation);
   the switch itself stays the one label.toggle primitive. */
.agb-memrow{margin:0;cursor:pointer}
/* Search over the tool catalog: matches auto-expand their category. */
.agb-search{position:relative}
.agb-search svg{position:absolute;left:10px;top:50%;transform:translateY(-50%);width:14px;height:14px;color:var(--text3);pointer-events:none}
.agb-search input{width:100%;box-sizing:border-box;height:34px;padding:0 10px 0 32px;font-size:12.5px;font-family:inherit;color:var(--text);background:var(--bg);border:1px solid var(--border);border-radius:8px;outline:none;transition:border-color .12s}
.agb-search input:focus{border-color:var(--border-strong)}
/* Category accordions: closed by default, a category with picked tools
   opens itself. The count reserves its widest footprint so checking a
   box never moves the header. */
.agb-cats{display:flex;flex-direction:column;gap:8px}
.agb-cat{border:1px solid var(--border);border-radius:8px;background:var(--bg);overflow:hidden}
.agb-cat-head{display:flex;align-items:center;gap:8px;width:100%;padding:10px 12px;background:none;border:none;color:var(--text);cursor:pointer;font-family:inherit;text-align:left;transition:background .12s}
.agb-cat-head:hover{background:var(--hover-wash)}
.agb-cat-caret{width:13px;height:13px;flex:0 0 auto;color:var(--text3);transition:transform .15s}
.agb-cat-head[aria-expanded="true"] .agb-cat-caret{transform:rotate(90deg)}
.agb-cat-name{font-size:12.5px;font-weight:600}
.agb-cat-note{font-size:11px;color:var(--text3)}
.agb-cat-count{margin-left:auto;min-width:9ch;text-align:right;font-size:11px;font-variant-numeric:tabular-nums;color:var(--text3);white-space:nowrap}
.agb-cat-count.has-picked{color:var(--primary);font-weight:600}
.agb-cat-body{border-top:1px solid var(--border)}
.agb-cat-tools .keytools-row:first-child{border-top:none}
.agb-cat-allrow{display:flex;justify-content:flex-end;gap:10px;padding:7px 12px;border-bottom:1px solid var(--border);background:var(--hover-wash)}
.agb-cat-allbtn{padding:2px 8px;background:none;border:1px solid var(--border);border-radius:4px;color:var(--text2);cursor:pointer;font-family:inherit;font-size:10.5px;font-weight:500;transition:color .12s,border-color .12s}
.agb-cat-allbtn:hover{color:var(--text);border-color:var(--border-strong)}
.agb-tools-count{font-size:11px;font-weight:600;letter-spacing:0;text-transform:none;color:var(--text2);margin-left:6px}
/* Live gallery preview: the card exactly as Chat with Agents renders it,
   updating as the identity fields type, so naming and describing the
   agent is designing its card. */
.agb-preview{display:flex;flex-direction:column;gap:6px}
.agb-preview-card{display:flex;flex-direction:column;gap:8px;padding:16px;border:1px solid var(--border);border-radius:12px;background:var(--bg);max-width:340px}
.agb-preview-top{display:flex;align-items:center;gap:10px}
.agb-preview-avatar{display:flex;align-items:center;justify-content:center;width:34px;height:34px;flex:0 0 auto;border-radius:8px;border:1px solid var(--border);background:var(--bg2);color:var(--text2)}
.agb-preview-avatar svg{width:18px;height:18px}
.agb-preview-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13.5px;font-weight:600;color:var(--text)}
.agb-preview-desc{font-size:12px;line-height:1.55;color:var(--text2);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;min-height:1.3em}
.agb-preview-meta{font-size:11px;color:var(--text3)}
.agb-preview-cap{font-size:10.5px;color:var(--text3)}
/* Group quick-actions on the inventory header and the gate note a denied
   tool states in place of a silently dead switch. */
.rows .row-group{display:flex;align-items:center;justify-content:space-between;gap:10px}
.row-group-acts{display:flex;gap:6px}
.row-gate{flex:0 0 auto;margin-right:10px;font-size:11px;color:var(--warning);white-space:nowrap}
/* Agents render as gallery tiles, the same card language the playground
   uses, so what the admin shapes is what people will pick. */
.agb-tiles{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:10px;max-width:var(--content-col)}
.agb-agent-tile{position:relative;display:flex;flex-direction:column;gap:6px;padding:14px 16px;background:var(--bg);border:1px solid var(--border);border-radius:8px;cursor:pointer;transition:border-color .12s,background .12s}
.agb-agent-tile:hover{border-color:var(--border-strong);background:var(--hover-wash)}
/* The name row RESERVES the action corner, so the hover-revealed icons can
   never sit on the agent's name. */
.agb-tile-top{display:flex;align-items:center;gap:9px;min-width:0;padding-right:58px}
.agb-tile-avatar{display:flex;align-items:center;justify-content:center;width:28px;height:28px;flex:0 0 auto;border-radius:7px;border:1px solid var(--border);background:var(--bg2);color:var(--text2)}
.agb-tile-avatar svg{width:15px;height:15px}
.agb-tile-acts{position:absolute;top:10px;right:10px;display:flex;gap:2px;opacity:0;transition:opacity .12s}
.agb-agent-tile:hover .agb-tile-acts,.agb-agent-tile:focus-within .agb-tile-acts{opacity:1}
/* Behavior selects take their field's full width, like every input above
   them: a model's full name is the value, not a truncation exercise. The
   max-width lift matters: the generic field dropdown caps at its column. */
#agent-builder .field .dsel{width:100%;max-width:none;flex:1 1 auto}
#agent-builder .field .dsel-btn{width:100%}
.agb-tile-act{display:flex;align-items:center;justify-content:center;width:24px;height:24px;border:none;border-radius:6px;background:transparent;color:var(--text3);cursor:pointer;transition:color .12s,background .12s}
.agb-tile-act:hover{color:var(--text);background:var(--bg3)}
.agb-tile-act svg{width:13px;height:13px;display:block}
/* The one door to a new thing: a dropdown of named ways to create it,
   opening toward the larger viewport half from the section footer. Shared
   by the Agents and Skills sections; one primitive, two consumers. */
.addmenu-wrap{position:relative;display:inline-flex}
.addmenu-wrap>.btn-primary{display:inline-flex;align-items:center;gap:6px}
.addmenu-wrap>.btn-primary svg{width:12px;height:12px}
.addmenu-pop{position:absolute;bottom:calc(100% + 6px);left:0;z-index:80;min-width:300px;max-height:360px;overflow-y:auto;display:flex;flex-direction:column;padding:5px;background:var(--bg2);border:1px solid var(--border);border-radius:8px;box-shadow:var(--shadow-md)}
.addmenu-pop.is-down{bottom:auto;top:calc(100% + 6px)}
.addmenu-item{display:flex;flex-direction:column;align-items:flex-start;gap:2px;width:100%;padding:8px 10px;border:none;border-radius:6px;background:transparent;color:var(--text);font-family:inherit;text-align:left;cursor:pointer}
.addmenu-item:hover{background:var(--hover-wash)}
.addmenu-item-name{font-size:12.5px;font-weight:600}
.addmenu-item-desc{font-size:11px;color:var(--text3);line-height:1.45}
.addmenu-item:first-child{border-bottom:1px solid var(--border);border-radius:6px 6px 0 0;padding-bottom:10px;margin-bottom:4px}
/* A designed empty state inside a .rows list: icon, one-line name, one
   sentence of orientation. The action stays in the foot row below, so
   the empty state never duplicates a button. */
.rows-emptystate{display:flex;flex-direction:column;align-items:center;gap:8px;padding:30px 24px;background:var(--bg2);text-align:center}
.rows-emptystate svg{width:24px;height:24px;color:var(--text3)}
.rows-emptystate-title{font-size:13px;font-weight:600;color:var(--text)}
.rows-emptystate-sub{max-width:420px;font-size:12px;line-height:1.6;color:var(--text3)}
/* A heading's explanatory line: pairs with .dash-heading above it and
   holds the ROWS COLUMN's width, never the viewport's, so the reading
   measure matches the content it introduces. */
.dash-subnote{max-width:620px;margin:-4px 0 14px;font-size:12px;line-height:1.6;color:var(--text2)}
/* Memory stores: the editor grid pairs its eight policy fields; the
   inspection panel swaps in below the store list, holding the same
   column width as the rows it descends from. */
.ms-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px 14px}
.modal-actions-gap{flex:1}
.toolopt-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px 14px}
.toolopt-grid .field.is-wide{grid-column:1 / -1}
.toolopt-toggle{display:flex;align-items:center;gap:10px;min-height:30px}
.toolopt-toggle .toolopt-state{font-size:12px;color:var(--text-dim)}
.toolopt-color{display:flex;align-items:center;gap:8px}
.toolopt-color input[type=color]{flex:0 0 auto;width:34px;height:30px;margin:0;padding:2px;border:1px solid var(--border);border-radius:6px;background:var(--bg);cursor:pointer}
.toolopt-color .toolopt-color-hex{margin:0}
.toolopt-color input[type=color]::-webkit-color-swatch-wrapper{padding:2px}
.toolopt-color input[type=color]::-webkit-color-swatch{border:none;border-radius:3px}
.toolopt-color input[type=color]::-moz-color-swatch{border:none;border-radius:3px}
/* The hex twin styles itself: the generic modal input rule excludes it,
   so it can share the swatch's exact height and sit flush on its row. */
.toolopt-color-hex{flex:1;min-width:0;box-sizing:border-box;height:30px;margin:0;padding:0 10px;font-family:var(--mono,monospace);font-size:12.5px;color:var(--text);background:var(--bg);border:1px solid var(--border);border-radius:6px;outline:none}
.toolopt-color-hex:focus{border-color:var(--border-strong);box-shadow:var(--focus-ring)}
.toolopt-multi{display:grid;grid-template-columns:1fr 1fr;gap:9px 18px;padding:12px 14px;border:1px solid var(--border);border-radius:8px}
/* Beats the generic .field label{gap:0} on specificity, so the box keeps its gap. */
.toolopt-multi .toolopt-check{display:flex;align-items:center;gap:10px;min-width:0;font-size:12px;font-weight:400;color:var(--text);cursor:pointer;line-height:1.3}
.toolopt-multi .toolopt-check input{flex:0 0 auto;width:15px;height:15px;margin:0;padding:0;border:0;accent-color:var(--primary);cursor:pointer}
.toolopt-multi .toolopt-check span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.toolopt-multi-note{grid-column:1/-1;margin-top:2px;font-size:11.5px;color:var(--text-dim)}
.toolopt-adjusted{font-size:11.5px;color:var(--text-dim)}
.row-cfg{flex:0 0 auto;padding:3px 9px;border:1px solid var(--border);border-radius:6px;background:transparent;color:var(--text-dim);font-size:11.5px;font-family:inherit;cursor:pointer;transition:color .12s,border-color .12s}
.row-cfg:hover{color:var(--text);border-color:var(--border-strong)}
.mcp-tool .row-cfg{margin-right:10px}
.mem-inspect{max-width:620px;margin:20px 0 8px}
.mem-inspect-head{display:flex;flex-direction:column;gap:10px;margin-bottom:12px}
.mem-inspect-head .dash-heading{margin:0}
.mem-inspect-acts{display:flex;align-items:center;gap:10px}
.mem-inspect-note{font-size:11.5px;color:var(--text3)}
.mem-fact-text{font-size:12.5px;line-height:1.55;color:var(--text);white-space:pre-wrap;overflow-wrap:anywhere}
/* Templates: prefilled starting points, shown while the list is empty. */
.agb-tpl-title{display:block;margin:16px 0 10px;font-size:11px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--text3)}
.agb-tpl-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:10px}
.agb-tpl{display:flex;flex-direction:column;gap:6px;padding:14px 16px;background:var(--bg);border:1px solid var(--border);border-radius:8px;color:var(--text);cursor:pointer;font-family:inherit;text-align:left;transition:border-color .12s,background .12s}
.agb-tpl:hover{border-color:var(--border-strong);background:var(--hover-wash)}
.agb-tpl-name{font-size:13px;font-weight:600}
.agb-tpl-desc{font-size:11.5px;line-height:1.5;color:var(--text3)}
.agb-tpl-meta{font-size:11px;color:var(--text3)}
/* The tile's one structured facts row: glyphs carry the meaning, values
   ride beside them, the effect cluster and the usage number anchor right.
   A quiet divider separates facts from prose, so the card reads in three
   distinct voices instead of four identical grey lines. */
.agb-tpl-desc.is-empty{font-style:italic}
.agb-tile-facts{display:flex;align-items:center;gap:11px;min-width:0;margin-top:auto;padding-top:9px;border-top:1px solid var(--border)}
.agb-fact{display:inline-flex;align-items:center;gap:5px;min-width:0;color:var(--text3);font-size:11px;font-weight:600}
.agb-fact svg{width:13px;height:13px;flex:0 0 auto}
.agb-fact b{font-weight:600;color:var(--text2);max-width:13ch;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-variant-numeric:tabular-nums}
.agb-fact-model b{font-family:var(--mono);font-size:10.5px;font-weight:500}
.agb-fact-spacer{flex:1}
.agb-effect{display:inline-flex;color:var(--text3)}
.agb-effect svg{width:13px;height:13px}
.agb-fact-used{font-size:10.5px;font-weight:600;color:var(--text2);white-space:nowrap;font-variant-numeric:tabular-nums}
.skills-foot-row{display:flex;align-items:center;gap:12px;margin-top:12px;max-width:var(--content-col)}
.agents-foot-right{margin-left:auto}
.skills-foot-row .hint{flex:1;min-width:0}
/* The three add paths share one height and rhythm; primary carries the
   verb that creates, the library and link rides are quiet secondaries. */
#skills-add-btn{flex-shrink:0;font-size:12px;padding:6px 14px}
/* Row actions: quiet until the row is under the pointer or holds focus. */
.skills-item-acts{flex:0 0 auto;display:flex;gap:6px;opacity:0;transition:opacity .12s}
.skills-item:hover .skills-item-acts,.skills-item:focus-within .skills-item-acts{opacity:1}
.skills-item-act{padding:3px 9px;background:none;border:1px solid var(--border);border-radius:4px;color:var(--text2);cursor:pointer;font-family:inherit;font-size:11px;font-weight:500;transition:color .12s,border-color .12s}
.skills-item-act:hover{color:var(--text);border-color:var(--border-strong)}

/* Skill library picker: checkbox rows; already-installed entries state it
   and cannot be re-picked. */
.predef-modal{width:560px;text-align:left}
.predef-modal h2{text-align:left}
.predef-sub{margin:0 0 12px;font-size:12px;line-height:1.55;color:var(--text2);text-align:left}
.predef-list{max-height:340px;overflow-y:auto;overscroll-behavior:contain;border:1px solid var(--border);border-radius:8px;background:var(--bg)}
.predef-row{display:flex;align-items:flex-start;gap:10px;padding:10px 14px;cursor:pointer}
.predef-row+.predef-row{border-top:1px solid var(--border)}
.predef-row:hover{background:var(--hover-wash)}
.predef-row input{flex:0 0 auto;margin-top:3px}
.predef-row-text{display:flex;flex-direction:column;gap:2px;min-width:0}
.predef-row-name{font-family:var(--mono);font-size:12px;font-weight:500;color:var(--text)}
.predef-row-desc{font-size:11.5px;line-height:1.5;color:var(--text3)}
.predef-row.is-installed{cursor:default}
.predef-row.is-installed .predef-row-name,.predef-row.is-installed .predef-row-desc{opacity:.55}
.predef-row-state{flex:0 0 auto;font-size:10.5px;color:var(--text3);white-space:nowrap;margin-top:3px}

/* Install from a link: one focused modal, one field, one action. The
   install button reserves its widest label so the swap to "Installing"
   moves nothing. */
.skill-link-modal{width:520px;text-align:left}
.skill-link-modal h2{text-align:left}
.skill-link-modal input{width:100%;box-sizing:border-box}
.skill-link-modal .btn-primary{min-width:104px;text-align:center}

/* Skill references inside the editor: a quiet file list with its remove
   actions, present once the skill exists on disk. */
.skill-res{display:flex;flex-direction:column;gap:7px}
.skill-res-list{display:flex;flex-direction:column;border:1px solid var(--border);border-radius:8px;background:var(--bg);overflow:hidden;max-height:160px;overflow-y:auto}
.skill-res-list:empty{display:none}
.skill-res-item{display:flex;align-items:center;gap:10px;padding:7px 12px}
.skill-res-item+.skill-res-item{border-top:1px solid var(--border)}
.skill-res-path{flex:1;min-width:0;font-family:var(--mono);font-size:11.5px;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.skill-res-size{flex:0 0 auto;font-size:10.5px;color:var(--text3);font-variant-numeric:tabular-nums}
.skill-res-del{flex:0 0 auto;padding:2px 8px;background:none;border:1px solid var(--border);border-radius:4px;color:var(--text2);cursor:pointer;font-family:inherit;font-size:10.5px;font-weight:500;transition:color .12s,border-color .12s}
.skill-res-del:hover{color:var(--text);border-color:var(--border-strong)}
.skill-res-row{display:flex;align-items:center;gap:10px}
.skill-res-row .hint{flex:1;min-width:0}
#skill-res-add{padding:5px 12px;font-size:11.5px}

/* Skill editor: the modal primitive, form-shaped. */
.skill-modal{width:640px;text-align:left}
.skill-modal h2{text-align:left}
.skill-form{display:flex;flex-direction:column;gap:14px}
.skill-form .field label{display:flex;align-items:center;gap:6px}
.skill-form input[type="text"],.skill-form textarea{width:100%}
#skill-f-body{font-family:var(--mono);font-size:12px;line-height:1.55;resize:vertical}
.skill-f-guide{display:flex;flex-direction:column;gap:7px}
.skill-f-guide-title{display:flex;align-items:center;gap:6px;font-size:12px;font-weight:500;color:var(--text2)}
.skill-f-guide-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}
.skill-f-guide-grid input{width:100%;font-size:12px}
.skill-f-manual{display:flex;align-items:flex-start;gap:9px;cursor:pointer}
.skill-f-manual-text{display:flex;flex-direction:column;gap:2px;font-size:12.5px;color:var(--text)}
.skill-f-manual-text .hint{font-size:11px}
.skill-modal .field-error{margin:0}
.skill-modal-actions{align-items:center}
.skill-modal-gap{flex:1}
/* The draft control swaps its label in place; the footprint is reserved so
   the busy state never shuffles the action row. */
#skill-f-draft{min-width:150px}
.mcp-toolbar-actions{display:flex;align-items:center;gap:10px;flex-wrap:wrap;min-width:0}

/* Tool search: leading glyph, and a clear affordance that exists only while
   there is something to clear. */
.mcp-find{position:relative;display:flex;align-items:center}
.mcp-find-icon{position:absolute;left:9px;width:12px;height:12px;color:var(--text3);pointer-events:none}
#mcp-search{width:170px;padding:6px 26px 6px 27px;font-size:12px;background:var(--bg2);border:1px solid var(--border);border-radius:6px;color:var(--text);outline:none;transition:border-color .15s,box-shadow .15s}
#mcp-search:hover{border-color:var(--border-strong)}
#mcp-search:focus{border-color:var(--primary);box-shadow:var(--focus-ring)}
.mcp-find-clear{position:absolute;right:5px;width:18px;height:18px;display:flex;align-items:center;justify-content:center;background:none;border:none;border-radius:4px;color:var(--text3);font-size:14px;line-height:1;cursor:pointer;transition:color .12s,background .12s}
.mcp-find-clear:hover{color:var(--text);background:var(--bg3)}

/* The preset and snippet pickers ride the shared .prefs-segmented primitive;
   only the geometry is local (inline and content-sized for a toolbar, rather
   than stretched across a drawer column). */
.mcp-presets,.mcp-snippet-tabs{display:inline-flex;padding:2px;gap:2px}
.mcp-presets button,.mcp-snippet-tabs button{flex:0 0 auto;padding:5px 12px;font-size:11px}

/* Risk ceiling: a single definition row directly above the catalog its verdict
   renders in. Shares the definition-row anatomy of the per-call limits. */
.mcp-ceiling{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:8px 24px;padding:12px 16px;margin-bottom:12px;background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius)}
.mcp-ceiling-meta,.mcp-limit-meta{display:flex;flex-direction:column;gap:3px;min-width:0}
.mcp-ceiling-meta label,.mcp-limit-meta label{font-size:13px;font-weight:500;color:var(--text)}
.mcp-ceiling-help,.mcp-limit-help{font-size:11px;color:var(--text3);line-height:1.5}

/* Catalog groups: one card per tool family, hairline-divided rows inside. The
   entrance fade lives on the container's first fill (see .mcp-fresh), never on
   the cards themselves: the catalog redraws on every toggle, and a per-card
   animation would replay as flicker each time. */
.mcp-fresh{animation:fadeIn .18s ease}
.mcp-group{margin-bottom:12px;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg2);overflow:hidden}
/* The group header is a fold: collapsed it is the whole group, one line
   with its exposure count, so the catalog reads as a directory and scales.
   The bulk button stays its own control on the same line. */
.mcp-group-head{display:flex;align-items:baseline;gap:10px;padding:11px 16px;cursor:pointer;user-select:none;transition:background .12s}
.mcp-group-head:hover{background:var(--hover-wash)}
.mcp-group-caret{flex:0 0 auto;width:13px;height:13px;align-self:center;color:var(--text3);transform:rotate(-90deg);transition:transform .18s ease,color .12s}
.mcp-group-head:hover .mcp-group-caret{color:var(--text2)}
.mcp-group.is-open .mcp-group-caret{transform:rotate(0deg)}
.mcp-group-body{border-top:1px solid var(--border)}
.mcp-group-name{font-size:11px;font-weight:600;color:var(--text);text-transform:uppercase;letter-spacing:.06em}
.mcp-group-hint{flex:1;min-width:0;font-size:11px;color:var(--text3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.mcp-group-count{font-size:11px;color:var(--text3);font-variant-numeric:tabular-nums;white-space:nowrap;min-width:9ch;text-align:right}
.mcp-group-all{flex:0 0 auto;font-size:11px;font-weight:500;color:var(--text3);background:none;border:none;cursor:pointer;padding:0;min-width:12ch;text-align:right;transition:color .12s}
.mcp-group-all:hover{color:var(--text)}

/* One tool: a disclosure TILE. Clicking the row (or Enter on it) unfolds the
   full model-facing description; only the switch itself changes exposure, so
   reading the catalog can never create an unsaved change. The caret is the
   visible promise that the tile opens. */
.mcp-tool{display:flex;align-items:flex-start;gap:12px;padding:13px 16px;cursor:pointer;transition:background .12s}
.mcp-tool:hover{background:var(--hover-wash)}
.mcp-tool+.mcp-tool{border-top:1px solid var(--border)}
.mcp-tool .toggle{margin-top:2px;margin-left:12px}
.mcp-tool-caret{flex:0 0 auto;width:13px;height:13px;margin-top:3px;color:var(--text3);transform:rotate(-90deg);transition:transform .18s ease,color .12s}
.mcp-tool:hover .mcp-tool-caret{color:var(--text2)}
.mcp-tool.is-open .mcp-tool-caret{transform:rotate(0deg)}
.mcp-tool-text{display:flex;flex-direction:column;gap:4px;flex:1;min-width:0}
.mcp-tool-title{display:flex;align-items:baseline;gap:8px;min-width:0;flex-wrap:wrap}
.mcp-tool-name{font-family:var(--mono);font-size:12px;font-weight:500;color:var(--text)}
/* Facts about the tool, not decoration: what it touches, how much a wrong call
   could cost, and whether the ceiling hides it. Plain text, no chrome. */
.mcp-tool-meta{font-size:11px;color:var(--text3);white-space:nowrap}
.mcp-tool-ceiled{color:#f59e0b}
html.theme-light .mcp-tool-ceiled{color:#b45309}
/* Exposure state carries through ink strength; the switch stays full-strength
   so the way back on never dims. */
.mcp-tool-text{transition:opacity .15s}
.mcp-tool.is-off .mcp-tool-text,.mcp-tool.is-ceiled .mcp-tool-text{opacity:.55}
/* Full-description disclosure: animated through grid rows so it unfolds in
   place instead of popping. The open state lives on the tile. */
.mcp-fullwrap{display:grid;grid-template-rows:0fr;transition:grid-template-rows .2s ease}
.mcp-tool.is-open .mcp-fullwrap{grid-template-rows:1fr}
.mcp-full{overflow:hidden;min-height:0;margin:0;font-size:12px;line-height:1.6;color:var(--text2);white-space:pre-wrap;cursor:auto}
.mcp-tool.is-open .mcp-full{margin-top:8px;padding:10px 12px;background:var(--bg3);border:1px solid var(--border);border-radius:6px}
.mcp-empty{display:block;font-size:12px;line-height:1.6;color:var(--text3);padding:14px 16px;border:1px dashed var(--border);border-radius:var(--radius);background:var(--bg2)}

/* Per-call limits: definition rows inside one card, numbers in one column so
   the eye scans values top to bottom. The unit reserves one width everywhere,
   which is what keeps the steppers aligned. */
.mcp-limits{border:1px solid var(--border);border-radius:var(--radius);background:var(--bg2);padding:0 16px;margin-bottom:28px}
.mcp-limit{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:24px;padding:13px 0}
.mcp-limit+.mcp-limit{border-top:1px solid var(--border)}
.mcp-limit-unit{font-size:11px;color:var(--text3);margin-left:10px;min-width:10ch;user-select:none}

/* Allowed-roots editor: a path list is code, and the verdict line below it is
   reserved so appearing feedback never moves the heading after it. */
.mcp-roots{margin-bottom:28px}
.mcp-roots textarea{width:100%;min-height:96px;resize:vertical;font-family:var(--mono);font-size:12px;line-height:1.6}
.mcp-roots-note{font-size:11px;color:var(--text3);line-height:1.5;min-height:1.5em;transition:color .15s}
.mcp-roots-note.is-warn{color:#f59e0b}
html.theme-light .mcp-roots-note.is-warn{color:#b45309}

/* Workflows: one card each. Being offered is the quiet normal; only the
   withheld state speaks, names what is missing, and offers the one-click way
   to expose it. */
.mcp-wf{border:1px solid var(--border);border-radius:var(--radius);background:var(--bg2);padding:13px 16px;margin-bottom:10px}
.mcp-wf-head{display:flex;align-items:baseline;gap:10px;min-width:0}
.mcp-wf-name{font-family:var(--mono);font-size:12px;font-weight:500;color:var(--text)}
.mcp-wf-args{flex:1;min-width:0;font-family:var(--mono);font-size:11px;color:var(--text3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.mcp-wf-state{font-size:11px;color:#f59e0b;white-space:nowrap}
html.theme-light .mcp-wf-state{color:#b45309}
.mcp-wf-desc{font-size:12px;line-height:1.5;color:var(--text3);margin-top:4px;transition:opacity .15s}
.mcp-wf-tools{font-size:11px;line-height:1.6;color:var(--text3);margin-top:6px}
.mcp-wf-tools .mcp-wf-tool{font-family:var(--mono)}
.mcp-wf-miss{color:#f59e0b}
html.theme-light .mcp-wf-miss{color:#b45309}
.mcp-wf.is-unavailable .mcp-wf-name,.mcp-wf.is-unavailable .mcp-wf-desc{opacity:.55}
.mcp-wf-fix{margin-left:8px}

/* Connect a client: picker for the two configuration shapes, one snippet, one
   copy action whose confirmation swaps in place. */
.mcp-connect{margin-bottom:var(--density-section-gap)}
.mcp-connect-off{margin:4px 0 14px}
.mcp-connect-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px}
.mcp-copy{min-width:78px;text-align:center}
#mcp-connect-snippet{margin:0;padding:14px 16px;background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius);font-family:var(--mono);font-size:12px;line-height:1.65;color:var(--text2);overflow-x:auto;white-space:pre}

/* Live sessions: the capacity line reserves its height, so the table below it
   never moves as sessions come and go. */
.mcp-sessions-cap{font-size:12px;color:var(--text2);font-variant-numeric:tabular-nums;min-height:18px;line-height:18px;margin:0 0 10px}
#mcp-sessions-wrap{border:1px solid var(--border);border-radius:var(--radius);overflow:auto;background:var(--bg2)}
#mcp-sessions-body td.hint{padding:14px 16px}

/* Below the two-column comfort point, the definition rows stack their control
   under the text and the connect header may fold, so the section keeps the
   same no-horizontal-scroll contract as every other settings page. */
@media (max-width:760px){
  .mcp-limit,.mcp-ceiling{grid-template-columns:1fr;gap:8px}
  .mcp-connect-head{flex-wrap:wrap}
}
.ovr-list{max-width:820px}
.ovr-empty{display:block;font-size:12px;line-height:1.6;color:var(--text3);
           padding:12px 14px;border:1px dashed var(--border);border-radius:var(--radius);
           background:var(--bg2)}
.ovr-head{justify-content:space-between;max-width:820px}
.ovr-row{display:grid;grid-template-columns:minmax(0,1fr) max-content max-content max-content;
         align-items:center;column-gap:26px;row-gap:2px;padding:12px 16px;
         border:1px solid var(--border);border-radius:var(--radius);
         background:var(--bg2);margin-bottom:8px}
.ovr-ident{min-width:0}
.ovr-name{display:block;font-size:13px;font-weight:600;color:var(--text);
          overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Holds the shared .mm-id button, which brings its own monospace face and
   click-to-copy; only the line geometry is local. */
.ovr-id{display:block;font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin-top:2px}
/* The lane and the shape are the row's data: quiet words, one bold figure. */
.ovr-lane{font-size:11.5px;color:var(--text3);white-space:nowrap}
.ovr-shape{font-size:12.5px;font-weight:600;color:var(--text);font-variant-numeric:tabular-nums;
           white-space:nowrap;text-align:right;min-width:12ch}
.ovr-shape.is-inherit{color:var(--text3);font-weight:400}
.ovr-actions{display:flex;align-items:center;gap:6px}
.ovr-edit{font-size:11px;font-weight:600;padding:6px 14px;background:transparent;
          border:1px solid var(--border);border-radius:4px;color:var(--text2);cursor:pointer;
          font-family:inherit;transition:color .12s,border-color .12s;line-height:1.2}
.ovr-edit:hover{color:var(--primary);border-color:var(--primary)}
.ovr-remove-x{display:flex;align-items:center;justify-content:center;width:27px;height:27px;
              padding:0;background:transparent;border:1px solid var(--border);border-radius:4px;
              color:var(--text3);cursor:pointer;transition:color .12s,border-color .12s,background-color .12s}
.ovr-remove-x svg{width:13px;height:13px;display:block}
.ovr-remove-x:hover{color:#ef4444;border-color:rgba(239,68,68,.55);background:rgba(239,68,68,.08)}
.ovr-warn{grid-column:1 / -1;font-size:12px;line-height:1.5;color:#b45309;margin-top:4px}
.ovr-warn:empty{display:none;margin:0}
.ovr-add-btn{font-size:11px;font-weight:600;padding:7px 14px;background:transparent;
             border:1px solid var(--border);border-radius:4px;color:var(--text2);cursor:pointer;
             font-family:inherit;transition:color .12s,border-color .12s}
.ovr-add-btn:hover{color:var(--primary);border-color:var(--primary)}
/* The dialog: a guided picker, then the shape. The base .modal centers its
   text for confirmation prompts; a form dialog is left-aligned with compact,
   uniform controls, or every element reads as a different size. */
#ovr-modal .modal{width:440px;max-width:calc(100vw - 48px);text-align:left;padding:24px}
#ovr-modal h2{font-size:15px;margin:0 0 4px}
#ovr-modal .ovr-m-sub{margin:0 0 16px}
#ovr-modal .skill-form{display:flex;flex-direction:column;gap:14px}
#ovr-modal .field{display:flex;flex-direction:column;gap:6px;margin:0}
#ovr-modal .field label{font-size:12px;font-weight:600;color:var(--text2)}
#ovr-modal input[type="text"],#ovr-modal input[type="number"],#ovr-modal select{
  width:100%;box-sizing:border-box;margin:0;padding:7px 10px;font-size:13px;line-height:1.4;
  text-align:left;border:1px solid var(--border);border-radius:6px;
  background:var(--bg);color:var(--text);font-variant-numeric:tabular-nums;font-family:inherit}
#ovr-modal input:focus,#ovr-modal select:focus{outline:none;border-color:var(--primary);
  box-shadow:0 0 0 2px rgba(124,58,237,.15)}
#ovr-modal .modal-actions{margin-top:20px}
#ovr-modal .modal-actions button{padding:8px 16px;font-size:12.5px;line-height:1.2;
  white-space:nowrap;border-radius:6px}
/* Picking collapses the list to the chosen model, so the dialog is never a
   wall of catalog once the choice is made. */
.ovr-chosen{display:flex;align-items:center;gap:10px;padding:8px 12px;
            border:1px solid var(--border);border-radius:6px;background:var(--bg)}
.ovr-chosen-name{flex:1 1 auto;min-width:0;font-size:13px;font-weight:600;
                 overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ovr-chosen-size{font-size:11px;color:var(--text3);font-variant-numeric:tabular-nums;white-space:nowrap}
.ovr-chosen-change{font-size:11px;font-weight:600;padding:4px 10px;background:transparent;
                   border:1px solid var(--border);border-radius:4px;color:var(--text2);
                   cursor:pointer;font-family:inherit;transition:color .12s,border-color .12s}
.ovr-chosen-change:hover{color:var(--primary);border-color:var(--primary)}
.ovr-pick{margin-top:2px;border:1px solid var(--border);border-radius:6px;
          background:var(--bg);max-height:196px;overflow:auto}
.ovr-pick-opt{display:flex;align-items:center;gap:10px;width:100%;padding:7px 12px;border:none;
              border-bottom:1px solid var(--border);background:none;cursor:pointer;font:inherit;
              color:var(--text);text-align:left}
.ovr-pick-opt:last-child{border-bottom:none}
.ovr-pick-opt:hover{background:var(--bg3)}
.ovr-pick-opt.is-selected{background:var(--bg3);font-weight:600}
.ovr-pick-name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12.5px}
.ovr-pick-size{font-size:11px;color:var(--text3);font-variant-numeric:tabular-nums}
.ovr-pick-empty{display:block;padding:12px;font-size:12px;color:var(--text3)}
.ovr-m-sub{font-size:12.5px;line-height:1.6;color:var(--text2);margin:6px 0 14px}
.ovr-m-shape{display:flex;gap:14px}
.ovr-m-shape .field{flex:1}
.ovr-m-shape input::placeholder{color:var(--text3);opacity:1}
.ovr-m-fit{min-height:18px;margin:6px 0 0;font-size:12px;line-height:1.5;color:var(--text2)}
.ovr-m-fit.warn{color:#b45309}
.ovr-m-actions{display:flex;align-items:center;gap:10px}
.ovr-m-gap{flex:1}
.ovr-remove{font-size:11px;font-weight:600;padding:6px 12px;background:transparent;
            border:1px solid var(--border);border-radius:4px;color:var(--text2);cursor:pointer;
            font-family:inherit;transition:color .12s,border-color .12s,background-color .12s}
.ovr-remove:hover{color:#ef4444;border-color:rgba(239,68,68,.55);background:rgba(239,68,68,.08)}

/* Breakdown drawer: a right slide-over hosting the per-endpoint /
   per-key / per-client aggregation at FULL height, so neither the
   breakdown nor the audit-log table has to share one viewport column.
   Mirrors the request-inspector pattern; sits just under it in the
   stacking order so an open inspector wins. */
.req-breakdown{
  position:absolute;top:0;right:0;bottom:0;width:900px;max-width:92vw;
  background:var(--bg2);border-left:1px solid var(--border);
  display:flex;flex-direction:column;z-index:24;
  box-shadow:var(--shadow-drawer);animation:slidein .18s ease;
}
.req-breakdown-head{display:flex;align-items:center;gap:12px;padding:20px 24px 12px;border-bottom:1px solid var(--border)}
.req-breakdown-head h2{font-size:14px;font-weight:600;color:var(--text);margin:0 auto 0 0;letter-spacing:.04em;text-transform:uppercase}
.req-breakdown-body{flex:1;min-height:0;overflow:auto;padding:14px 24px 20px}
.req-breakdown-hint{margin:0 0 12px;font-size:12px;color:var(--text3);line-height:1.5}
.req-agg-tabs{display:inline-flex;gap:4px}
.req-agg-tab{height:24px;padding:0 10px;font-size:11px}
#dash-endpoints{border-radius:var(--radius)}

/* Pagination strip alongside the "Recent requests" heading. Holds the
   filtered "Showing X-Y of Z" badge, prev/next page buttons, the per-
   page selector, and the CSV export trigger. Right-aligned via the
   dash-heading-row flex layout so the heading stays at the left and
   the controls cluster on the right. */
.req-pager{
  display:inline-flex;align-items:center;gap:6px;
  margin-left:auto;
  flex-wrap:wrap;
  font-size:12px;color:var(--text2);
}
.req-pager-divider{display:inline-block;width:1px;height:18px;background:var(--border);margin:0 4px;flex-shrink:0}
.req-pager-range{color:var(--text3);font-variant-numeric:tabular-nums;white-space:nowrap;margin-right:4px}
.req-pager-btn{
  height:28px;min-width:32px;padding:0 8px;
  background:transparent;border:1px solid var(--border);border-radius:4px;
  color:var(--text2);font-size:13px;font-family:inherit;cursor:pointer;
  transition:color .12s,border-color .12s,opacity .12s;
}
.req-pager-btn:hover:not(:disabled){color:var(--text);border-color:var(--border-strong)}
.req-pager-btn:disabled{opacity:.4;cursor:not-allowed}
/* Direct page jump: "Page [n] of M". The number input matches the
   .req-select geometry; spinners are dropped because the adjacent
   prev/next buttons already cover single-step moves. */
.req-pager-jump{display:inline-flex;align-items:center;gap:5px;white-space:nowrap;color:var(--text3);font-variant-numeric:tabular-nums}
/* Native spinners are already suppressed for every number input by the shared
   number-field rules, so this only carries the pager's own geometry. */
.req-page-input{
  width:58px;height:28px;padding:0 6px;text-align:center;
  background:var(--bg);border:1px solid var(--border);border-radius:4px;
  color:var(--text);font-size:11px;font-family:inherit;outline:none;
}
.req-page-input:focus{border-color:#555}
/* datetime-local input. Force the picker chrome to match the active
   theme so the browser-rendered calendar uses our dark / light palette
   instead of the OS default. The color-scheme property is the
   officially-supported way to influence form-control widget colors. */
.req-date{min-width:170px}
html.theme-dark .req-date{color-scheme:dark}
html.theme-light .req-date{color-scheme:light}
.req-date::-webkit-calendar-picker-indicator{cursor:pointer;opacity:.55;transition:opacity .12s}
.req-date::-webkit-calendar-picker-indicator:hover{opacity:1}
.req-date::-webkit-datetime-edit{color:var(--text)}
.req-date::-webkit-datetime-edit-fields-wrapper{padding:0}
.req-date::-webkit-datetime-edit-text{color:var(--text3);padding:0 2px}

/* Clickable column headers on the Recent requests table. The active sort
   column shows an upward / downward caret next to its label so the
   operator always knows which axis is driving the order. */
.req-table th[data-sort]{cursor:pointer;user-select:none;-webkit-user-select:none;transition:color .12s}
.req-table th[data-sort]:hover{color:var(--text)}
.req-table th[data-sort-active]{color:var(--text)}
.req-table th[data-sort]::after{
  content:'';display:inline-block;margin-left:6px;
  width:0;height:0;
  border-style:solid;border-width:0 4px 5px 4px;
  border-color:transparent transparent currentColor transparent;
  opacity:0;transition:opacity .12s,transform .12s;
}
.req-table th[data-sort-active="asc"]::after{opacity:.8}
.req-table th[data-sort-active="desc"]::after{opacity:.8;transform:rotate(180deg)}
.dash-heading-row{display:flex;align-items:center;margin-top:28px;margin-bottom:10px;gap:16px;flex-wrap:wrap;max-width:var(--content-col)}
.dash-heading-row:first-of-type{margin-top:4px}
.dash-heading-row .dash-heading{margin:0}
/* Shares the dashboard column width so the download progress card sits flush with the panels above
   and below it rather than stretching to the viewport edge. */
.dash-downloads{display:flex;flex-direction:column;gap:6px;margin-bottom:16px;max-width:var(--dash-width)}
.dash-dl-row{position:relative;overflow:hidden;display:flex;align-items:center;gap:12px;padding:10px 14px;background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius)}
.dash-dl-row .dash-dl-bg{position:absolute;top:0;left:0;bottom:0;background:linear-gradient(90deg,#3b82f633,#3b82f618);transition:width .4s ease}
.dash-dl-row>*{position:relative;z-index:1}
.dash-dl-name{font-weight:600;color:var(--text);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.dash-dl-pct{font-size:12px;font-weight:600;color:var(--data-blue);min-width:40px;text-align:right}
.dash-dl-size{font-size:11px;color:var(--text3)}

/* Prevent grid cells from vertically stretching when a sibling cell is taller (e.g. has a hint). */
.grid .field{align-self:start}

/* OCR provider-specific sub-panels */
.ocr-options{margin-top:24px;padding:18px 22px 22px;background:var(--bg2);border-radius:var(--radius);max-width:620px}
/* Auto-fit columns so a panel with 1 field doesn't leave an empty column,
   and a panel with 2–4 fields uses the card's full width. */
.ocr-options .grid{max-width:none;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:18px 28px}
.ocr-options .rows{margin-bottom:0;margin-top:18px;max-width:none}
.ocr-options .row{background:var(--bg3)}
.ocr-options>.grid+.rows{margin-top:18px}
.ocr-sub-heading{font-size:10px;font-weight:600;color:var(--text3);text-transform:uppercase;letter-spacing:.08em;margin-bottom:16px}

/* Reusable sub-group primitive for a configuration section. Pair a small
   uppercase label (.cfg-sub-heading) with one or more children (.rows /
   .grid / .field) inside a .cfg-subgroup wrapper to visually separate a
   distinct cluster of settings within a section without resorting to a
   nested card. Used by the Models section to set the "Behavior" toggle
   apart from the default-model dropdowns above it; available to any
   other section that needs the same break. */
.cfg-subgroup{margin-top:24px;max-width:620px}
.cfg-subgroup>.rows{margin-bottom:0}
.cfg-sub-heading{font-size:10px;font-weight:600;color:var(--text3);text-transform:uppercase;letter-spacing:.08em;margin:0 0 10px}
/* Region dropdown should fill the field width, overriding the default 140px enum cap. */
.ocr-options .field select.region-select{max-width:none;width:100%;flex:1}
/* Test connection action row */
.ocr-actions{display:flex;align-items:center;gap:14px;margin-top:18px;padding-top:16px;border-top:1px solid var(--border)}
.btn-test{padding:7px 16px;border:1px solid var(--border);border-radius:6px;background:transparent;color:var(--text2);font-size:12px;font-weight:500;font-family:inherit;cursor:pointer;transition:background .12s,border-color .12s,color .12s}
.btn-test:hover:not(:disabled){background:#3b82f622}
.btn-test:disabled{opacity:.5;cursor:wait}
.test-result{font-size:12px;color:var(--text3);line-height:1.4;min-width:0;word-break:break-word}
.test-result.ok{color:#22c55e}
.test-result.err{color:#ef4444}

.dash-gpus{display:flex;flex-direction:column;gap:14px;max-width:var(--dash-width);margin-bottom:var(--density-section-gap)}
.dash-gpus .gpu-bar{background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius);padding:16px 18px}
.dash-gpus .gpu-bar-head{margin-bottom:8px}
.dash-gpus .gpu-bar-name{font-weight:600;color:var(--text)}
/* Live readout: tabular numerals keep digits from jittering, but the STRING
   also changes width ("0% GPU" -> "100% GPU", "9.9 GB" -> "19.9 GB"), and under
   space-between that walks the block's left edge on every poll. Reserving the
   widest form in ch (one ch == one digit advance in a monospace face) fixes the
   footprint, so digits change in place and nothing around them moves. */
.dash-gpus .gpu-bar-usage{font-size:13px;color:var(--text);font-variant-numeric:tabular-nums;font-weight:500;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;white-space:nowrap;min-width:34ch;text-align:right}
.gpu-bar{background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px}
.gpu-bar-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}
.gpu-bar-name{font-size:13px;font-weight:500;color:var(--text)}
.gpu-bar-usage{font-size:12px;color:var(--text2)}
.gpu-bar-track{height:8px;background:var(--bg);border-radius:4px;overflow:hidden}
.gpu-bar-fill{height:100%;border-radius:4px;background:var(--data-blue);transition:width .4s ease}
.gpu-bar-fill.warn{background:#f59e0b}
.gpu-bar-fill.crit{background:#ef4444}

/* Process VRAM attribution: a single stacked bar splitting the process's
   device memory into weights / KV+compute / draft / output / runtime, plus a
   legend. Reconciles to the per-process NVML figure; the labelled runtime
   segment absorbs CUDA-context and driver reserve so there is no silent gap. */
.vram-attr{max-width:var(--dash-width);margin:-4px 0 var(--density-section-gap);background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius);padding:14px 18px}
.vram-attr-head{display:flex;justify-content:space-between;align-items:baseline;gap:12px;margin-bottom:10px;flex-wrap:wrap}
.vram-attr-title{font-size:12px;font-weight:600;color:var(--text2)}
.vram-attr-total{font-size:12px;color:var(--text2);font-variant-numeric:tabular-nums;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
.vram-attr-bar{display:flex;height:10px;border-radius:4px;overflow:hidden;background:var(--bg)}
.vram-attr-bar-seg{height:100%;min-width:1px;transition:width .4s ease}
.vram-attr-seg-weights{background:var(--data-blue)}
.vram-attr-seg-draftw{background:#6366f1}
.vram-attr-seg-kv{background:#10b981}
.vram-attr-seg-draftc{background:#14b8a6}
.vram-attr-seg-output{background:#f59e0b}
.vram-attr-seg-runtime{background:var(--text3)}
.vram-attr-legend{display:flex;flex-wrap:wrap;gap:6px 18px;margin-top:12px}
.vram-attr-item{display:flex;align-items:center;gap:7px;font-size:12px;color:var(--text2);font-variant-numeric:tabular-nums}
.vram-attr-swatch{width:9px;height:9px;border-radius:2px;flex:none}
.vram-attr-item .val{color:var(--text);font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
/* Fixed two-line slot (2 x 11px x 1.5). The note's sentences appear and disappear
   between polls, so an auto-height note resized the whole tile and shifted the page
   below on every refresh; the reserved slot keeps the tile height constant. Text that
   cannot fit is clipped, and the div's title carries the full text. */
.vram-attr-note{margin-top:10px;font-size:11px;color:var(--text3);line-height:1.5;height:33px;overflow:hidden}

/* Loaded models list. Grid layout with FIXED column widths so size, refs,
   KV-cache count, capabilities, last-used, and action button line up cleanly
   across every row. Without this, each row's flex children would size
   to their own content and columns would shift row-to-row depending
   on which capability list is rendered.
   Columns:
     1. name (flexible, ellipsis on overflow)
     2. size (right-aligned, fixed 70px)
     3. refs (in-flight acquisitions, right-aligned, fixed 56px)
     4. KV-cache count (clickable, opens the per-context popover, fixed 56px)
     5. capabilities, plain comma-separated text, right-aligned,
        fixed 156px, wraps to a second line for very rich models
     6. last-used "Xm ago" (right-aligned, fixed 64px)
     7. unload button (auto width) */
.dash-model-list{display:flex;flex-direction:column;gap:1px;max-width:920px;border-radius:var(--radius);overflow:hidden}
.dash-model{
  display:grid;
  grid-template-columns:minmax(0,1fr) 70px 56px 56px 156px 64px auto;
  gap:14px;align-items:center;
  padding:10px 14px;background:var(--bg2);
}
.dash-model-name{font-size:13px;font-weight:500;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
.dash-model-col{font-size:11px;color:var(--text3);font-variant-numeric:tabular-nums;text-align:right;white-space:nowrap}
.dash-model-col.caps{text-align:right;white-space:normal;color:var(--text2);font-size:11px;line-height:1.3}

/* KV-cache count cell on a loaded-model row. Plain right-aligned text when the
   model holds no context; when it holds one or more it becomes a quiet clickable
   link that opens the per-context memory popover. No badge, no pill - matches the
   row's flat treatment. */
.dash-ctx{
  font:inherit;font-size:11px;font-variant-numeric:tabular-nums;
  color:var(--text3);background:none;border:none;padding:0;
  justify-self:end;white-space:nowrap;cursor:default;
}
.dash-ctx:enabled{color:var(--primary);cursor:pointer;text-decoration:underline;text-decoration-style:dotted;text-underline-offset:2px}
.dash-ctx:enabled:hover{text-decoration-style:solid}
.dash-ctx:disabled{color:var(--text3)}

/* Per-context (KV-cache) popover opened from the KV-cache count cell. The
   resident memory figure is foregrounded above the per-context breakdown.
   Flat card on design-system tokens, no pill chrome. */
.refs-pop{
  position:fixed;z-index:200;min-width:440px;max-width:560px;
  background:var(--bg2);border:1px solid var(--border);border-radius:10px;
  box-shadow:var(--shadow-md);padding:14px 16px;font-size:12px;color:var(--text);
}
.refs-pop-head{font-size:13px;font-weight:600;color:var(--text);margin-bottom:10px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.refs-pop-loading,.refs-pop-empty{font-size:12px;color:var(--text3);padding:2px 0}
.refs-pop-sum{display:flex;flex-direction:column;gap:2px;margin-bottom:12px;padding-bottom:12px;border-bottom:1px solid var(--border)}
.refs-pop-sum-val{font-size:22px;font-weight:600;color:var(--text);font-variant-numeric:tabular-nums;line-height:1}
.refs-pop-sum-lbl{font-size:11px;color:var(--text3)}
/* Projected footprint riding beside a resident figure of zero: an estimate of
   what the contexts will claim once they allocate, so "0 B" reads as headroom
   rather than as a missing value. Recessive next to the measured number. */
.refs-pop-sum-alt{font-size:13px;font-weight:500;color:var(--text3)}
/* Why requests are not fusing on the shared decoder, listed above the contexts
   because it explains the shape of the list below it. Flat block on design-system
   tokens, no chrome. */
.refs-pop-note{margin:-4px 0 12px;padding:9px 11px;background:var(--bg3);border-radius:4px;font-size:11px;line-height:1.5;color:var(--text2)}
.refs-pop-note-head{font-weight:600;color:var(--text);margin-bottom:4px}
.refs-pop-note-row{display:flex;gap:8px;align-items:baseline}
.refs-pop-note-n{min-width:32px;text-align:right;font-variant-numeric:tabular-nums;font-weight:600;color:var(--text);flex:none}
/* Tall enough that typical context counts never scroll; scrollbar-gutter keeps
   the row width identical whether or not the bar appears, so a scrollbar never
   reflows the detail line. */
.refs-pop-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:1px;max-height:420px;overflow:auto;overscroll-behavior:contain;scrollbar-gutter:stable;border-radius:4px}
/* Two-line context row. The memory figure and the state chip share the top
   line (state anchored top-right beside the value it describes); the detail
   line spans the full width on the second line so it never competes with the
   state column for horizontal room. */
.refs-pop-ctx{display:grid;grid-template-columns:1fr auto;grid-template-areas:"mem state" "meta meta";column-gap:12px;row-gap:3px;align-items:center;padding:9px 11px;background:var(--bg3)}
.refs-pop-ctx-mem{grid-area:mem;font-size:13px;font-weight:600;color:var(--text);font-variant-numeric:tabular-nums}
.refs-pop-ctx-mem.dim{color:var(--text3);font-weight:500}
/* Per-row projection, same role as .refs-pop-sum-alt one level down. */
.refs-pop-alt{font-weight:500;color:var(--text3);font-size:11px}
/* Detail line owns the full row width now that the state moved up beside the
   memory figure, so it wraps gracefully instead of truncating when the
   per-buffer split (main + draft + output) is shown. */
.refs-pop-ctx-meta{grid-area:meta;font-size:11px;line-height:1.45;color:var(--text3);overflow-wrap:anywhere}
/* Residency marker: where the context's KV-cache actually lives. Color makes
   "on the GPU vs not" scannable at a glance. Flat colored text, no chip. */
.refs-pop-loc{font-weight:600}
.refs-pop-loc.loc-vram{color:#34d399}
.refs-pop-loc.loc-ram{color:var(--data-blue)}
.refs-pop-loc.loc-disk{color:var(--text3)}
/* Kind marker for a parallel decoder's shared context: one allocation serving
   many concurrent requests, which is what makes its capacity a pool rather than
   a session window. Flat emphasized text, same weight as the residency marker. */
.refs-pop-kind{font-weight:600;color:var(--text2)}
/* Prose line explaining a non-resident row. "0 B" plus a residency word states
   a fact and answers nothing; this says why the context holds no memory and
   what brings it back. Brighter than the token list above it, because it is the
   part worth reading. */
.refs-pop-why{display:block;margin-top:4px;color:var(--text2);line-height:1.5}
/* State chip, top-right of the row. "in use" is filled amber to pull the eye
   to an active context; "cached" and "idle" recede as neutral outlines. Both
   share one box model (transparent 1px border) so the chips line up whatever
   their fill. Flat 4px corners, no pill, no halo. */
.refs-pop-state{grid-area:state;justify-self:end;font-size:10px;font-weight:600;letter-spacing:.03em;text-transform:uppercase;padding:2px 8px;border:1px solid transparent;border-radius:4px;white-space:nowrap}
.refs-pop-state.in-use{color:#f59e0b;background:#f59e0b1f}
.refs-pop-state.cached{color:var(--text2);border-color:var(--border-strong)}
.refs-pop-state.idle{color:var(--text3);border-color:var(--border)}

.dash-sys-row{display:grid;grid-template-columns:repeat(3, minmax(300px, 1fr));gap:16px;max-width:var(--dash-width);margin-bottom:var(--density-section-gap)}
.dash-sys-row .gpu-bar{margin:0;background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius);padding:16px 18px}
.dash-sys-row .gpu-bar-head{margin-bottom:6px}
.dash-sys-row .gpu-bar-name{font-weight:600;color:var(--text)}
.dash-sys-row .gpu-bar-usage{font-size:13px;color:var(--text);font-variant-numeric:tabular-nums;font-weight:500;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;white-space:nowrap}
/* "View processes" sits in the System resources toolbar alongside
   Pause / Reset / Export - it inspects both CPU AND memory per
   process, so it isn't tied to any single tile. The .ctrl-btn class
   already styles it consistently with its toolbar siblings. */

/* Host processes side panel. Same fixed-column grid idiom used by the
   loaded-models list so PID / name / CPU% / memory align across rows. */
.proc-table{display:flex;flex-direction:column;gap:0;border-radius:var(--radius);overflow:hidden;border:1px solid var(--border)}
/* Toolbar between the side-panel header and body. Hosts the live
   filter input + group-by-name toggle for the processes panel. */
.side-panel-toolbar{display:flex;align-items:center;gap:12px;padding:10px 24px 12px;border-bottom:1px solid var(--border);background:var(--bg2)}
.side-panel-search{flex:1;height:30px;padding:0 10px;background:var(--bg);border:1px solid var(--border);border-radius:5px;color:var(--text);font-size:12px;font-family:inherit;outline:none;transition:border-color .12s,box-shadow .12s}
.side-panel-search:focus{border-color:var(--primary);box-shadow:0 0 0 2px color-mix(in srgb, var(--text) 16%, transparent)}
.side-panel-toggle{display:inline-flex;align-items:center;gap:6px;font-size:11px;font-weight:500;color:var(--text2);cursor:pointer;user-select:none;-webkit-user-select:none;white-space:nowrap}

/* Summary row at the top of the process table. Mirrors the column
   layout of the rows below so the totals visually align with the
   columns they aggregate. Tabular numerics so values stay anchored
   even when CPU% values jitter tick-to-tick. */
.proc-summary{display:grid;grid-template-columns:60px minmax(0,1fr) 80px 100px;gap:10px;align-items:center;padding:10px 12px;border-bottom:1px solid var(--border);background:var(--bg);font-size:11px;font-variant-numeric:tabular-nums;color:var(--text2)}
.proc-summary-label{color:var(--text3);font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase}
.proc-summary-count{color:var(--text);font-weight:600}
.proc-summary-cpu,.proc-summary-mem{text-align:right;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--text);font-weight:600}

.proc-row-head,.proc-row{display:grid;grid-template-columns:60px minmax(0,1fr) 80px 100px;gap:10px;align-items:center;padding:8px 12px;border-bottom:1px solid var(--border);font-size:12px;font-variant-numeric:tabular-nums}
.proc-row-head{background:var(--bg);color:var(--text3);font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase}
/* Sortable column headers. Cursor + hover state make the affordance
   discoverable. Active sort column glows in the design system's blue
   with a directional caret rendered via ::after so the markup stays
   plain text. */
.proc-row-head [data-sort]{cursor:pointer;user-select:none;-webkit-user-select:none;display:inline-flex;align-items:center;gap:4px;transition:color .12s}
.proc-row-head [data-sort]:hover{color:var(--text)}
.proc-row-head [data-sort].sort-active{color:var(--primary)}
.proc-row-head [data-sort].sort-active::after{content:'▾';font-size:9px;opacity:.9}
.proc-row-head [data-sort].sort-active.sort-asc::after{content:'▴'}
/* Right-align the sort-key visuals on numeric columns so the caret
   sits next to the value below, not floating away from the column. */
.proc-row-head .proc-cpu[data-sort],.proc-row-head .proc-mem[data-sort]{justify-content:flex-end}
.proc-row{background:var(--bg2);color:var(--text2)}
.proc-row:last-child{border-bottom:none}
.proc-row:hover{background:var(--bg3)}
/* This server's own row: name reads in normal text color + bold so
   the eye still picks it out, but no stripe, no background tint, no
   chrome that breaks the row alignment with the others. */
.proc-row.self .proc-name{color:var(--text);font-weight:600}
.proc-row.self .proc-pid{color:var(--text2)}
.proc-pid{color:var(--text3)}
.proc-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
.proc-cpu,.proc-mem,.proc-threads{text-align:right;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
.proc-cpu.hot{color:#ef4444;font-weight:700}
.proc-cpu.warm{color:#f59e0b;font-weight:600}
@media(max-width:1100px){.dash-sys-row{grid-template-columns:1fr 1fr}}
@media(max-width:720px){.dash-sys-row{grid-template-columns:1fr}}

#requests{border-bottom:none}
/* Recent-requests scroll container. Adds a subtle scroll-shadow on
   the right edge when horizontal scrolling is in play, signalling
   "more content to the right" without claiming chrome space. */
.dash-requests{
  border-radius:var(--radius);overflow:auto;position:relative;
  background-image:linear-gradient(to left, var(--bg) 0%, transparent 16px);
  background-attachment:local;
  background-repeat:no-repeat;
  background-position:right;
  background-size:16px 100%;
}
.logs-panel .dash-requests{flex:1;min-height:0;margin:0;max-height:none}
.req-table{width:100%;border-collapse:collapse;font-size:12px;table-layout:auto}
.req-table th{text-align:left;padding:9px 12px;background:var(--bg3);color:var(--text2);font-weight:500;font-size:11px;text-transform:uppercase;letter-spacing:.03em;position:sticky;top:0;z-index:1;vertical-align:middle;box-shadow:inset 0 -1px 0 var(--border)}
.req-table td{padding:8px 12px;background:var(--bg2);border-top:1px solid var(--border);color:var(--text2);white-space:nowrap;vertical-align:middle;line-height:1.35}
/* Default: the last column of any req-table that does NOT use explicit
   column classes (logs panel, simple ad-hoc tables) absorbs the
   leftover width and wraps its content. Tables that DO use column
   classes (Recent requests, see .col-* below) override this per-cell. */
.req-table td:last-child:not([class*="col-"]){white-space:normal;word-break:break-word;width:100%}

/* Recent-requests column layout. Percentage widths with
   table-layout:fixed: every column gets a defined share of the
   container, the row spans the FULL container width, and the
   leftover space is distributed proportionally across all columns
   rather than parked behind one big spacer. Percentages sum to 100%.
   Each cell keeps ellipsis so long content truncates rather than
   blowing the column out. */
.req-table-requests{table-layout:fixed;width:100%}
.req-table-requests th,
.req-table-requests td {overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Fixed pixel width: the Time column must always hold its widest value
   ("2026-06-12 11:14:59" in UTC mode) regardless of viewport width; a
   percentage share shrinks below the text on laptop screens and crops
   the timestamp. */
.req-table-requests th.col-time,    .req-table-requests td.col-time    {width:148px;font-variant-numeric:tabular-nums}
.req-table-requests th.col-rid,     .req-table-requests td.col-rid     {width:7%;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}
.req-table-requests th.col-client,  .req-table-requests td.col-client  {width:8%}
.req-table-requests th.col-key,     .req-table-requests td.col-key     {width:9%}
.req-table-requests th.col-method,  .req-table-requests td.col-method  {width:5%;font-variant-numeric:tabular-nums}
.req-table-requests th.col-path,    .req-table-requests td.col-path    {width:28%}
.req-table-requests th.col-num.req-col-status, .req-table-requests td.col-num.req-col-status {width:5%;text-align:right;font-variant-numeric:tabular-nums}
.req-table-requests th.col-num.req-col-dur,    .req-table-requests td.col-num.req-col-dur    {width:7%;text-align:right;font-variant-numeric:tabular-nums}
.req-table-requests th.col-num.req-col-in,     .req-table-requests td.col-num.req-col-in     {width:5%;text-align:right;font-variant-numeric:tabular-nums}
.req-table-requests th.col-num.req-col-out,    .req-table-requests td.col-num.req-col-out    {width:5%;text-align:right;font-variant-numeric:tabular-nums}
.req-table-requests th.col-error,   .req-table-requests td.col-error   {width:12%}

/* By-endpoint summary table. Capped at 1100px (it doesn't need the
   full container width - Method is tiny, Endpoint takes the bulk,
   numerics are short) so the row reads as a focused summary rather
   than a stretched expanse. table-layout:fixed + pixel widths so
   no column flexes on viewport change. */
.req-table-endpoints{table-layout:fixed;width:100%;max-width:1100px}
.req-table-endpoints th, .req-table-endpoints td {overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.req-table-endpoints th.col-ep-method, .req-table-endpoints td.col-ep-method {width:64px;font-variant-numeric:tabular-nums}
.req-table-endpoints th.col-ep-path,   .req-table-endpoints td.col-ep-path   {width:auto}
.req-table-endpoints th.col-num,       .req-table-endpoints td.col-num       {width:80px;text-align:right;font-variant-numeric:tabular-nums}
/* Click-to-filter: every body row in the By-endpoint table is a
   shortcut that drops the row's method + path-prefix into the audit
   log filter. Cursor + hover state make the affordance discoverable;
   the .req-ep-selected highlight lets the operator see which row is
   currently driving the filter set below. */
.req-table-endpoints tbody tr.req-ep-row{cursor:pointer;user-select:none;-webkit-user-select:none;transition:background .08s ease}
.req-table-endpoints tbody tr.req-ep-row:hover td{background:var(--hover-wash)}
.req-table-endpoints tbody tr.req-ep-selected td{background:color-mix(in srgb, var(--text) 12%, transparent);box-shadow:inset 3px 0 0 var(--primary)}
html.theme-light .req-table-endpoints tbody tr.req-ep-row:hover td{background:var(--hover-wash)}
html.theme-light .req-table-endpoints tbody tr.req-ep-selected td{background:color-mix(in srgb, var(--text) 14%, transparent)}
/* Active sort caret on right-aligned numeric headers stays glued to the
   label rather than the cell edge so the heading reads cleanly. */
.req-table-requests th.col-num[data-sort]::after{margin-left:4px}

/* Anomaly highlighting on the Recent-requests rows. A coloured 3px
   stripe down the first cell of every error row, so the eye finds
   rows that matter without reading every column. Amber for 4xx,
   red for 5xx. No "slow" stripe - duration is already coloured in
   its own cell and a separate row marker just confused the layout. */
.req-table-requests tbody tr.req-row-4xx > td:first-child{box-shadow:inset 3px 0 0 #d97706}
.req-table-requests tbody tr.req-row-5xx > td:first-child{box-shadow:inset 3px 0 0 #dc2626}
html.theme-light .req-table-requests tbody tr.req-row-4xx > td:first-child{box-shadow:inset 3px 0 0 #b45309}
html.theme-light .req-table-requests tbody tr.req-row-5xx > td:first-child{box-shadow:inset 3px 0 0 #b91c1c}

/* Column visibility dropdown anchored to the "Columns" button in the
   pager. Toggled by aria-expanded on the button; checkbox state is
   persisted to localStorage so the operator's column preferences
   survive page reloads. Each checkbox flips a hide-col-* class on
   the table; the matching rule below collapses every cell in that
   column to display:none, leaving table-layout:fixed to redistribute
   the freed width to the Path column. */
.req-columns-wrap{position:relative;display:inline-flex}
.req-columns-menu{
  position:absolute;top:calc(100% + 6px);right:0;z-index:50;
  display:flex;flex-direction:column;gap:6px;
  padding:10px 14px;min-width:160px;
  background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);
  box-shadow:var(--shadow-md);
  font-size:12px;color:var(--text2);
}
.req-columns-menu[hidden]{display:none}
.req-columns-menu label{display:flex;align-items:center;gap:8px;cursor:pointer;line-height:1}
.req-columns-menu input[type=checkbox]{margin:0;cursor:pointer}
.req-table-requests.hide-col-rid     th.col-rid,     .req-table-requests.hide-col-rid     td.col-rid     {display:none}
.req-table-requests.hide-col-client  th.col-client,  .req-table-requests.hide-col-client  td.col-client  {display:none}
.req-table-requests.hide-col-key     th.col-key,     .req-table-requests.hide-col-key     td.col-key     {display:none}
.req-table-requests.hide-col-method  th.col-method,  .req-table-requests.hide-col-method  td.col-method  {display:none}
.req-table-requests.hide-col-num-in  th.col-num.req-col-in,  .req-table-requests.hide-col-num-in  td.col-num.req-col-in  {display:none}
.req-table-requests.hide-col-num-out th.col-num.req-col-out, .req-table-requests.hide-col-num-out td.col-num.req-col-out {display:none}
.req-table-requests.hide-col-error   th.col-error,   .req-table-requests.hide-col-error   td.col-error   {display:none}

/* Saved views dropdown. Shares the .req-columns-menu shell (positioned
   dropdown panel under its trigger button) but adds an inline name
   input + Save button at the top and a scrollable list of named
   filter sets below. Persisted to localStorage; the items show name +
   load + delete actions on hover for keyboard-free recall. */
.req-views-menu{min-width:240px;gap:0;padding:0}
.req-views-save{display:flex;gap:6px;padding:10px 12px;border-bottom:1px solid var(--border)}
.req-views-save .log-search{flex:1;min-width:0;max-width:none;height:26px}
.req-views-save .log-action{height:26px;padding:0 10px}
.req-views-sep{display:none}
.req-views-list{display:flex;flex-direction:column;max-height:240px;overflow-y:auto;padding:6px 0}
.req-views-list .hint{padding:8px 12px;color:var(--text3);font-size:11px}
.req-views-item{
  display:flex;align-items:center;justify-content:space-between;gap:8px;
  padding:6px 12px;cursor:pointer;color:var(--text2);font-size:12px;
}
.req-views-item:hover{background:var(--bg2);color:var(--text)}
.req-views-item-actions{display:inline-flex;gap:4px;flex-shrink:0}
.req-views-item-x{
  background:transparent;border:0;padding:0 4px;cursor:pointer;
  color:var(--text3);font:inherit;font-size:14px;line-height:1;
}
.req-views-item-x:hover{color:#dc2626}



/* Time-range preset chips. Render as a tight inline group inside the
   filter row, reusing the .log-filter geometry so they line up with
   the status pills + selects. The `req-preset[data-active=true]`
   attribute mirrors the active visual state used by .log-filter.active
   without polluting the underlying mutually-exclusive status state. */
.req-time-presets{display:inline-flex;align-items:center;gap:4px}
.req-preset{height:28px;padding:0 10px;font-size:11px}
.req-preset[data-active="true"]{color:var(--text);background:var(--bg3);border-color:var(--bg3)}

/* Active filter summary: each applied filter renders as a flat, boxed
   token so it is unmistakable that the audit log is being filtered. The
   token uses the design system's standard --radius (8px square-ish
   corners, not a capsule) with a subtle fill + border, a labelled value,
   and a dedicated × button to remove just that filter. A leading
   "Filtering by" caption and a trailing "Clear all" close the row. */
.req-active-filters{display:flex;align-items:center;flex-wrap:wrap;gap:6px;font-size:12px;color:var(--text2)}
.req-active-filters[hidden]{display:none}
.req-active-filters .req-filter-lead{
  color:var(--text3);font-size:11px;text-transform:uppercase;letter-spacing:.06em;
  margin-right:2px;user-select:none;-webkit-user-select:none;flex-shrink:0;
}
.req-active-filters .req-filter-chip{
  display:inline-flex;align-items:center;gap:6px;
  background:var(--bg3);
  border:1px solid var(--border-strong);
  border-radius:var(--radius);
  padding:3px 4px 3px 9px;
  color:var(--text2);
  max-width:320px;
  white-space:nowrap;
}
.req-active-filters .req-filter-chip strong{color:var(--text3);font-weight:500;flex-shrink:0}
.req-active-filters .req-filter-chip > span:not(strong){color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.req-active-filters .req-filter-chip-x{
  display:inline-flex;align-items:center;justify-content:center;
  width:18px;height:18px;flex-shrink:0;
  background:transparent;border:0;border-radius:var(--radius);padding:0;cursor:pointer;
  color:var(--text3);font:inherit;font-size:15px;line-height:1;
  transition:color .12s,background .12s;
}
.req-active-filters .req-filter-chip-x:hover{color:#fff;background:#dc2626}
.req-active-filters .req-filter-clear{
  margin-left:auto;background:transparent;border:0;
  color:var(--text2);font:inherit;font-size:11px;cursor:pointer;
  text-decoration:underline;text-decoration-color:var(--border-strong);text-underline-offset:2px;
}
.req-active-filters .req-filter-clear:hover{color:var(--text)}
.req-table .status-2xx,.req-table .status-4xx,.req-table .status-5xx{font-weight:600;font-variant-numeric:tabular-nums}
.req-table .status-2xx{color:#16a34a}
.req-table .status-4xx{color:#d97706}
.req-table .status-5xx{color:#dc2626}
html.theme-light .req-table .status-2xx{color:#15803d}
html.theme-light .req-table .status-4xx{color:#b45309}
html.theme-light .req-table .status-5xx{color:#b91c1c}

.dash-activity{display:flex;flex-direction:column;gap:0;margin-bottom:var(--density-section-gap);max-width:1080px;min-height:260px;max-height:420px;overflow:auto;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg2)}
.dash-activity .hint{padding:14px 16px}
.dash-act{display:grid;grid-template-columns:auto 90px 1fr;align-items:center;gap:10px 14px;padding:10px 16px;border-bottom:1px solid var(--border);font-size:12px;transition:background .12s}
.dash-act:last-child{border-bottom:none}
.dash-act:hover{background:var(--bg3)}
.dash-act-time{color:var(--text3);font-variant-numeric:tabular-nums;white-space:nowrap;font-size:11px}
.dash-act-source{font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--text2);display:inline-flex;align-items:center;gap:6px}
.dash-act-source::before{content:'';width:6px;height:6px;border-radius:50%;background:currentColor;opacity:.7}
.dash-act-msg{color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.dash-act.level-success .dash-act-source{color:#10b981}
.dash-act.level-warning .dash-act-source{color:#f59e0b}
.dash-act.level-error   .dash-act-source{color:#ef4444}
.dash-act.level-info    .dash-act-source{color:var(--data-blue)}
/* Scheduler page: one row per recurring job. Layout mirrors the
   alert + activity list idiom (flat rows, no pill chrome, dot glyph
   for status, monospace timestamps on the right). */
.sch-list{display:flex;flex-direction:column;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg2);max-width:1080px;overflow:hidden}
.sch-empty{padding:18px 16px;color:var(--text3);font-size:12px}
.sch-row{display:grid;grid-template-columns:18px 1fr auto auto;gap:12px 18px;align-items:center;padding:14px 18px;border-bottom:1px solid var(--border)}
.sch-row:last-child{border-bottom:none}
.sch-row-status{width:8px;height:8px;border-radius:50%;background:#10b981;flex-shrink:0;justify-self:center}
.sch-row-status.fail{background:#ef4444}
.sch-row-status.idle{background:var(--text3)}
.sch-row-mid{min-width:0;display:flex;flex-direction:column;gap:3px}
.sch-row-name{font-size:13px;font-weight:600;color:var(--text);font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
.sch-row-desc{font-size:11px;color:var(--text3)}
.sch-row-meta{display:flex;flex-direction:column;align-items:flex-end;gap:2px;font-size:11px;color:var(--text2);font-variant-numeric:tabular-nums;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;min-width:200px}
.sch-row-meta-k{font-size:9px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--text3)}
.sch-row-msg{font-size:10px;color:var(--text3);font-style:italic}
.sch-row-msg.fail{color:#ef4444;font-style:normal}
.sch-run-btn{padding:6px 14px;border:1px solid var(--border);border-radius:6px;background:transparent;color:var(--text2);font-size:11px;font-weight:600;cursor:pointer;font-family:inherit;transition:color .12s,border-color .12s,opacity .12s;white-space:nowrap}
.sch-run-btn:hover:not(:disabled){color:var(--text);border-color:var(--primary)}
.sch-run-btn:disabled{opacity:.5;cursor:wait}

/* TLS info table on Security page. Flat label/value rows, no chrome
   beyond a thin divider, monospace values where helpful. */
/* Red banner shown when ACME is enabled but no trusted cert was issued (serving self-signed fallback). */
/* Security audit panel: posture summary + per-control checklist. */
/* Security domains: one disclosure card per domain, so the page reads as a
   map (the audit above) plus four quiet doors instead of a dozen stacked
   editors. The container owns the rhythm; the head keeps a fixed height and
   the summary truncates inside its own flexible slot, so nothing in a head
   ever moves a neighbor. Opening a group is a deliberate mode change. */
.sec-groups{display:flex;flex-direction:column;gap:12px;max-width:1080px}
.sec-group{border:1px solid var(--border);border-radius:8px;background:var(--bg2);overflow:hidden}
.sec-group-head{display:flex;align-items:center;gap:14px;width:100%;padding:15px 18px;background:transparent;border:none;cursor:pointer;color:var(--text);font:inherit;text-align:left}
.sec-group-head:hover{background:var(--bg3)}
.sec-group-head:focus-visible{outline:none;box-shadow:var(--focus-ring) inset}
.sec-group-title{font-size:13.5px;font-weight:650;letter-spacing:-.008em;white-space:nowrap}
.sec-group-sum{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:right;font-size:12px;color:var(--text3)}
.sec-group-chev{flex:0 0 auto;width:16px;height:16px;color:var(--text3);transition:transform .15s ease}
.sec-group.open .sec-group-chev{transform:rotate(90deg)}
.sec-group-body{display:none;border-top:1px solid var(--border);padding:2px 18px 20px}
.sec-group.open .sec-group-body{display:block}
.sec-group-body > h2.dash-heading:first-child{margin-top:16px}
@media (prefers-reduced-motion:reduce){.sec-group-chev{transition:none}}
/* The audit rides the same disclosure chrome as the domain panels below it;
   only its status words carry color (flag-only), so the stack reads as one
   quiet family with the review on top. */
.audit-summary{user-select:none}
.audit-level{font-weight:650;color:var(--text2)}
.lvl-critical .audit-level{color:#ef4444}
.lvl-warn .audit-level{color:#f59e0b}
.lvl-hardened .audit-level{color:#10b981}
.audit-score{font-size:12px;font-weight:600;color:var(--text2);white-space:nowrap;flex:0 0 auto;font-variant-numeric:tabular-nums}
.audit-list.sec-group-body{padding:0}
.audit-cat{font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--text3);padding:11px 16px 5px;background:var(--bg);border-top:1px solid var(--border)}
.audit-list>.audit-cat:first-child{border-top:none}
.audit-item{display:grid;grid-template-columns:12px 1fr auto;gap:12px;padding:11px 16px;border-top:1px solid var(--border);align-items:start;font-size:13px}
.audit-mark{width:10px;height:10px;border-radius:3px;margin-top:4px}
.audit-item-main{min-width:0}
.audit-item-title{font-weight:600;color:var(--text)}
.audit-item-rec{font-size:12px;color:var(--text2);line-height:1.5;margin-top:4px;word-break:break-word}
.audit-item-state{font-size:11px;font-weight:600;white-space:nowrap;padding-top:1px}
.audit-mark.st-ok{background:#10b981}
.audit-mark.st-warn{background:#f59e0b}
.audit-mark.st-critical{background:#ef4444}
.audit-mark.st-info{background:var(--text3)}
.audit-item-state.st-ok{color:#10b981}
.audit-item-state.st-warn{color:#f59e0b}
.audit-item-state.st-critical{color:#ef4444}
.audit-item-state.st-info{color:var(--text3)}
.audit-item-right{display:flex;align-items:center;gap:12px;white-space:nowrap}
.audit-item--link{cursor:pointer}
.audit-item--link:hover{background:var(--bg)}
.audit-item--link:focus-visible{outline:none;box-shadow:var(--focus-ring);border-radius:4px}
.audit-action{display:inline-flex;align-items:center;gap:5px;border:1px solid var(--border);border-radius:5px;background:var(--bg);color:var(--text2);font-size:11.5px;font-weight:600;padding:4px 10px;line-height:1.2}
.audit-item--link:hover .audit-action{border-color:var(--primary);color:var(--primary)}
.audit-action-arrow{font-size:14px;line-height:1}
/* One-shot highlight when an audit item navigates to its control: a single
   fade-to-transparent that confirms where the operator landed. Not a
   continuous pulse. */
@keyframes audit-flash-kf{0%{background:color-mix(in srgb, var(--text) 16%, transparent)}100%{background:transparent}}
.audit-flash{animation:audit-flash-kf 1.6s ease-out 1;border-radius:6px}
.tls-alert{max-width:1080px;border:1px solid rgba(239,68,68,.5);background:rgba(239,68,68,.08);border-radius:8px;padding:14px 16px;margin-bottom:16px}
.tls-alert-title{font-size:14px;font-weight:700;color:#ef4444;margin-bottom:6px}
.tls-alert-body{font-size:13px;color:var(--text2);line-height:1.5}
.tls-alert-err{margin-top:8px;font-size:12px;color:#ef4444;background:rgba(239,68,68,.10);border:1px solid rgba(239,68,68,.22);border-radius:6px;padding:8px 10px;word-break:break-word}
.tls-alert-actions{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-top:12px}
.tls-alert-hint{font-size:12px;color:var(--text2)}
.tls-summary{display:flex;flex-direction:column;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg2);overflow:hidden;max-width:1080px}
.tls-summary-row{display:grid;grid-template-columns:200px 1fr;gap:14px;padding:10px 16px;border-bottom:1px solid var(--border);align-items:flex-start;font-size:12px}
.tls-summary-row:last-child{border-bottom:none}
.tls-summary-label{font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--text3)}
.tls-summary-value{color:var(--text);word-break:break-word}
.tls-summary-value.mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}
.tls-expiry-ok{color:#10b981;font-weight:600}
.tls-expiry-warn{color:#f59e0b;font-weight:600}
.tls-expiry-fail{color:#ef4444;font-weight:700}
.tls-upload-form{max-width:620px;display:flex;flex-direction:column;gap:12px}
.tls-upload-actions{display:flex;gap:8px}
.acme-shell{max-width:720px;border:1px solid var(--border);border-radius:var(--radius);padding:14px 18px;background:var(--bg2)}
/* Multi-line, fixed, non-resizable monospace box. Shared by the ACME domain
   list and the admin IP allowlist so the styling lives in exactly one place. */
.lmk-monobox{resize:none;display:block;width:100%;max-width:560px;min-height:64px;box-sizing:border-box;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px;line-height:1.6;padding:8px 11px;border:1px solid var(--border);border-radius:6px;background:var(--bg);color:var(--text)}
.lmk-monobox:focus{border-color:var(--primary);box-shadow:var(--focus-ring);outline:none}
.lmk-monobox.invalid,.lmk-monobox.invalid:focus{border-color:#ef4444;box-shadow:0 0 0 2px rgba(239,68,68,.18)}
/* Live effective-state line under the admin IP allowlist: states whether anything is restricting access right now. */
.ip-allow-status{font-size:12px;margin-top:8px;color:var(--text2);line-height:1.5}
.ip-allow-status strong{font-weight:600;color:var(--text)}
.ip-allow-status.restricted,.ip-allow-status.restricted strong{color:#b45309}
html.theme-dark .ip-allow-status.restricted,html.theme-dark .ip-allow-status.restricted strong{color:#f59e0b}
/* Format examples shown as documentation, never mistakable for entered content. */
.ip-allow-examples{margin-top:6px}
.ip-allow-examples code{background:var(--bg3);border:1px solid var(--border);border-radius:4px;padding:0 4px;font-size:11px}
#acme-email{display:block;width:100%;max-width:560px;box-sizing:border-box;padding:8px 11px;border:1px solid var(--border);border-radius:6px;background:var(--bg);color:var(--text);font-size:13px}
.acme-status{font-size:12px;font-weight:600;padding:8px 10px;border-radius:var(--radius);border:1px solid var(--border);background:var(--bg)}
.acme-status.idle{color:var(--text3)}
.acme-status.active{color:#10b981;border-color:rgba(16,185,129,.4)}
.acme-status.inactive{color:#f59e0b;border-color:rgba(245,158,11,.4)}
.acme-form{display:flex;flex-direction:column;gap:14px;margin-top:14px;max-width:560px}
.acme-form .field{margin:0}
.acme-form .ts-check{margin:0}

/* TLS status block shown on the dashboard.
   - .dash-cert-expiry is the colored status line (single dot glyph
     + label). Flat typography, no pill.
   - .dash-cert-meta carries supporting details below the status:
     expiration date + days remaining + a link to the Security page.
   - The link inherits the design system's link styling but with the
     subtle underline + accent color so it doesn't shout. */
.dash-cert-block{display:flex;flex-direction:column;gap:4px;margin-bottom:8px;max-width:1080px}
.dash-cert-expiry{font-size:12px;font-weight:600;color:var(--text2);display:inline-flex;align-items:center;gap:8px}
.dash-cert-expiry::before{content:'';width:7px;height:7px;border-radius:50%;background:currentColor;opacity:.85;flex-shrink:0}
.dash-cert-expiry.ok{color:#10b981}
.dash-cert-expiry.warn{color:#f59e0b}
.dash-cert-expiry.fail{color:#ef4444}
.dash-cert-expiry.idle{color:var(--text3)}
.dash-cert-meta{font-size:11px;color:var(--text3);margin-left:15px;display:inline-flex;align-items:center;gap:10px;flex-wrap:wrap}
.dash-cert-meta-link{color:var(--primary);border-bottom:1px solid color-mix(in srgb, var(--text) 30%, transparent);cursor:pointer;transition:border-color .12s}
.dash-cert-meta-link:hover{border-bottom-color:var(--primary)}

.dash-file-row{display:flex;flex-direction:column;gap:12px;margin-bottom:28px;max-width:920px}
.dash-file-stats{display:flex;gap:12px;flex-wrap:wrap}
.dash-file-stats .stat-card{flex:1 1 200px;min-width:0}
/* Disk usage bar: flat slim track with a tinted fill that shifts colour
   as the volume fills (green → amber → red). Color thresholds set in JS
   so the same fill element handles all states without extra classes. */
.dash-disk-bar{position:relative;height:8px;background:var(--bg);border:1px solid var(--border);border-radius:4px;overflow:hidden}
.dash-disk-bar-fill{position:absolute;left:0;top:0;bottom:0;width:0;background:#10b981;transition:width .25s ease,background .25s ease}
.dash-disk-bar-text{position:absolute;right:8px;top:-18px;font-size:11px;color:var(--text3);font-variant-numeric:tabular-nums}
.dash-file-actions{display:flex;justify-content:flex-end;gap:8px}
.btn-cleanup{flex-shrink:0;padding:8px 16px;border:1px solid var(--border);border-radius:6px;background:transparent;color:var(--text2);font-size:12px;font-weight:500;cursor:pointer;font-family:inherit;transition:color .12s,border-color .12s,opacity .12s}
.btn-cleanup:hover:not(:disabled){color:var(--text);border-color:var(--border-strong)}
.btn-cleanup:disabled{opacity:.45;cursor:not-allowed;color:var(--text3)}
/* Danger-action button used for cancel-all / restart server. Same shape as
   .btn-cleanup, red color signaling destructive intent. */
.btn-danger{flex-shrink:0;padding:8px 16px;border:1px solid rgba(239,68,68,.32);border-radius:6px;background:transparent;color:#ef4444;font-size:12px;font-weight:500;cursor:pointer;font-family:inherit;transition:color .12s,border-color .12s,background .12s}
.btn-danger:hover{background:rgba(239,68,68,.08);border-color:rgba(239,68,68,.55)}
.btn-danger:disabled{opacity:.5;cursor:not-allowed;background:transparent}
.dash-server-controls{display:flex;gap:10px;flex-wrap:wrap;margin-bottom:18px}
/* Header row above the keys table: live counts on the left, toolbar on the right. */
.apikeys-header{display:flex;align-items:center;gap:16px;flex-wrap:wrap;justify-content:space-between;max-width:1080px;margin:0 0 12px}
.apikeys-summary{display:flex;align-items:center;gap:18px}
.apikey-stat{display:inline-flex;align-items:baseline;gap:6px;font-size:12px;color:var(--text2)}
.apikey-stat-n{color:var(--text);font-weight:700;font-variant-numeric:tabular-nums}
.apikey-stat-l{font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:var(--text3)}
.apikeys-toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.apikeys-search{position:relative;display:inline-flex;align-items:center}
.apikeys-search svg{position:absolute;left:9px;width:13px;height:13px;color:var(--text3);pointer-events:none}
.apikeys-search input{padding:6px 10px 6px 28px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:12px;font-family:inherit;outline:none;width:220px;transition:border-color .12s ease, box-shadow .12s ease}
.apikeys-search input:focus{border-color:var(--border-strong);box-shadow:var(--focus-ring)}
.apikeys-toggle{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--text2);cursor:pointer;user-select:none}
.apikeys-toggle input{margin:0}
.apikeys-create{padding:6px 14px;font-size:12px}
/* Key rows, not a grid: information lives LEFT (identity over its facts), actions live RIGHT
   in their own bordered zone, vertically centered on the card, with a hard gap between the two
   so controls and text can never read as one block. Wraps cleanly at every viewport width. */
.apikeys-list{border:1px solid var(--border);border-radius:var(--radius);background:var(--bg2);max-width:1080px}
.apikey-card{display:flex;align-items:center;flex-wrap:wrap;gap:10px 32px;padding:13px 16px;border-bottom:1px solid var(--border)}
.apikey-card:last-child{border-bottom:none}
.apikey-card-info{flex:1 1 360px;min-width:0;display:flex;flex-direction:column;gap:6px}
.apikey-card-id{display:flex;align-items:center;gap:10px;min-width:0}
.apikey-name{display:inline-block;max-width:280px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text);font-size:12.5px;font-weight:600}
.apikey-card-token{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--text3);font-size:11px;white-space:nowrap}
.apikey-status-revoked{color:var(--text3);font-weight:600;font-size:10px;letter-spacing:.06em;text-transform:uppercase;white-space:nowrap}
.apikey-card-actions{display:flex;align-items:center;gap:6px;margin-left:auto}
/* The reach editors read as controls (bordered, ghost), never as more text. */
.apikey-act{background:transparent;border:1px solid var(--border);padding:5px 11px;border-radius:6px;color:var(--text2);font-size:11.5px;font-weight:500;cursor:pointer;font-family:inherit;white-space:nowrap;transition:color .12s ease, border-color .12s ease, background .12s ease}
.apikey-act:hover{color:var(--text);border-color:var(--border-strong);background:var(--bg3)}
.apikey-act:focus-visible{outline:none;box-shadow:var(--focus-ring)}
/* Everything rare or destructive lives behind one overflow menu per row. */
.apikey-menu-host{position:relative;display:inline-flex}
.apikey-menu-btn{background:transparent;border:1px solid var(--border);padding:5px 7px;border-radius:6px;color:var(--text3);cursor:pointer;display:inline-flex;align-items:center;line-height:0;transition:color .12s ease, border-color .12s ease, background .12s ease}
.apikey-menu-btn:hover{color:var(--text);border-color:var(--border-strong);background:var(--bg3)}
.apikey-menu-btn:focus-visible{outline:none;box-shadow:var(--focus-ring)}
.apikey-menu-btn svg{width:15px;height:15px}
.apikey-menu{position:absolute;top:calc(100% + 4px);right:0;z-index:40;min-width:170px;padding:4px;background:var(--bg2);border:1px solid var(--border);border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,.14);display:flex;flex-direction:column}
.apikey-menu-item{display:block;width:100%;text-align:left;background:transparent;border:none;padding:7px 10px;border-radius:5px;color:var(--text);font-size:12px;font-family:inherit;cursor:pointer}
.apikey-menu-item:hover{background:var(--bg3)}
.apikey-menu-item.apikey-menu-danger{color:#ef4444}
.apikey-menu-item.apikey-menu-restore{color:#10b981}
/* The facts line sits under the identity, left-aligned with it: policy deviations first with
   emphasized values, then the quiet activity trio. Defaults say nothing at all. */
.apikey-card-meta{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:22px;row-gap:4px;font-size:11.5px}
.apikey-meta-flags{display:flex;flex-wrap:wrap;column-gap:14px;row-gap:4px;min-width:0;color:var(--text2)}
.apikey-flag{white-space:nowrap}
.apikey-flag-v{font-weight:600;color:var(--text)}
.apikey-meta-activity{display:flex;flex-wrap:wrap;column-gap:14px;row-gap:4px;color:var(--text3);font-variant-numeric:tabular-nums}
.apikey-fact{white-space:nowrap}
.apikey-activity-v{color:var(--text2)}
.apikey-fact-expired{color:#ef4444;font-weight:600}
html.theme-light .apikey-fact-expired{color:#dc2626}
/* Revoked keys dim their information so the active set stands out; the Revoked word and the
   Restore/Delete menu keep full color. */
.apikey-card.revoked .apikey-card-id,
.apikey-card.revoked .apikey-meta-flags > :not(.apikey-status-revoked),
.apikey-card.revoked .apikey-meta-activity > *{opacity:.55}
/* Create-key modal body: stacked labelled fields. */
/* Shared layout for stacked labelled fields inside a modal (create API key,
   edit cluster connection, ...). One definition; every modal form reuses it. */
.modal-form{display:flex;flex-direction:column;gap:6px;text-align:left}
.modal-form label{font-size:11px;font-weight:600;color:var(--text2);margin-top:8px}
.modal-form label:first-child{margin-top:0}
.modal-form input,.modal-form select{width:100%;box-sizing:border-box;padding:8px 11px;border:1px solid var(--border);border-radius:6px;background:var(--bg);color:var(--text);font-size:13px;font-family:inherit}
.modal-form input:focus,.modal-form select:focus{border-color:var(--primary);box-shadow:var(--focus-ring);outline:none}
.ss-edit-opt{font-weight:400;color:var(--text3)}
.ss-edit-actions{margin-top:14px}
/* Two-column variant of .modal-form: each label+input pair is wrapped in a .modal-field cell and the
   pairs flow into two columns. Used by wider forms (e.g. Edit connection) to stay compact. The actions
   row and any .modal-field--full span both columns. The containing .modal widens to fit. */
.modal-form--grid{display:grid;grid-template-columns:1fr 1fr;gap:8px 14px}
.modal-form--grid .modal-field{display:flex;flex-direction:column;min-width:0}
.modal-form--grid .modal-field label{margin-top:0}
.modal-form--grid .modal-field--full,.modal-form--grid .ss-edit-actions{grid-column:1 / -1}
.modal:has(.modal-form--grid){width:560px;text-align:left}
@media (max-width:560px){.modal-form--grid{grid-template-columns:1fr}}
/* Restore button reuses the cleanup-button shell with a green accent so the
   "bring this back" action reads as recovery, not destruction. */
.btn-restore{flex-shrink:0;padding:6px 12px;border:1px solid rgba(16,185,129,.36);border-radius:6px;background:transparent;color:#10b981;font-size:11px;font-weight:600;cursor:pointer;font-family:inherit;transition:color .12s,border-color .12s,background .12s}
.btn-restore:hover{background:rgba(16,185,129,.10);border-color:rgba(16,185,129,.55)}
.btn-restore:disabled{opacity:.5;cursor:not-allowed;background:transparent}
.apikeys-list .btn-cleanup,.apikeys-list .btn-danger,.apikeys-list .btn-restore{padding:5px 12px;font-size:11px;font-weight:600}
.apikeys-list .apikey-empty{padding:18px 16px;color:var(--text3);font-size:12px}
/* Legacy class kept for any caller that still uses the plain block form. */
.apikey-token-reveal{background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:10px 12px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;color:var(--text);word-break:break-all;margin:10px 0}

/* API key reveal modal: warning + monospace token block with inline Copy
   button + usage hint. Anchored to the existing modal token scale so it
   sits inside a 380px overlay without overflowing. */
/* API key reveal: the modal that presents the one-time token after
   creation. Wide format, left-aligned, with the token displayed
   prominently in its own block. The :has() selector targets this
   specific modal so other confirm dialogs stay compact. */
.modal:has(.apikey-reveal-token){width:580px;text-align:left;padding:28px 32px 24px}
.modal:has(.apikey-reveal-token) h2{font-size:17px;margin-bottom:8px;text-align:left}
.apikey-reveal-warning{font-size:13px;color:var(--text2);margin:0 0 20px;line-height:1.5;text-align:left}
.apikey-reveal-token{display:flex;align-items:stretch;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;margin:0 0 16px}
.apikey-reveal-token code{flex:1;min-width:0;padding:14px 16px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px;letter-spacing:.02em;color:var(--text);word-break:break-all;background:transparent;border:none;display:flex;align-items:center;line-height:1.5}
.apikey-reveal-copy-btn{display:inline-flex;align-items:center;gap:6px;padding:0 18px;background:var(--bg3);border:none;border-left:1px solid var(--border);color:var(--text2);font-size:12px;font-weight:600;cursor:pointer;font-family:inherit;flex-shrink:0;transition:background .12s ease, color .12s ease}
.apikey-reveal-copy-btn:hover{background:var(--border);color:var(--text)}
.apikey-reveal-copy-btn:focus-visible{outline:none;box-shadow:var(--focus-ring) inset}
.apikey-reveal-copy-btn.copied{background:rgba(16,185,129,.16);color:#10b981}
.apikey-reveal-copy-btn svg{flex-shrink:0}
.apikey-reveal-usage{font-size:12px;color:var(--text3);margin:0;text-align:left;font-family:inherit;line-height:1.5}
.apikey-reveal-usage code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--text2);background:var(--bg);padding:2px 6px;border-radius:4px;border:1px solid var(--border)}

/* Logs toolbar. Three visual clusters (level filters / search inputs /
   actions) separated by a larger gap. Every interactive control shares
   one geometry contract (.log-filter / .log-toggle / .log-action /
   .log-search) so heights and paddings line up across the row, no
   matter the mix of buttons, toggles, and inputs.
     - .log-filter    -> mutually-exclusive level pick, .active class
     - .log-toggle    -> state toggle, aria-pressed="true" when on
     - .log-action    -> stateless command (Pause / Refresh / Export / Clear)
   The action cluster pushes itself to the right via margin-left:auto so
   destructive verbs (Clear) sit at the far end where the eye expects them. */
.logs-toolbar{display:flex;align-items:center;flex-wrap:wrap;gap:18px;margin-bottom:10px}
.logs-group{display:flex;align-items:center;gap:6px;flex-shrink:0;min-width:0}
.logs-search-group{flex:1 1 320px;min-width:240px}
.logs-actions{margin-left:auto}
.log-filter,.log-toggle,.log-action{
  height:28px;padding:0 12px;
  border:1px solid var(--border);border-radius:4px;
  background:transparent;color:var(--text3);
  font-size:11px;font-weight:500;font-family:inherit;
  cursor:pointer;
  transition:color .12s,border-color .12s,background .12s,opacity .12s;
}
.log-filter:hover,.log-toggle:hover,.log-action:hover:not(:disabled){color:var(--text2);border-color:var(--border-strong)}
.log-action:disabled{opacity:.45;cursor:not-allowed;color:var(--text3)}
.log-filter.active,.log-toggle[aria-pressed="true"]{color:var(--text);background:var(--bg3);border-color:var(--bg3)}
.log-search{
  flex:1;min-width:140px;max-width:260px;
  height:28px;padding:0 10px;
  background:var(--bg);border:1px solid var(--border);border-radius:4px;
  color:var(--text);font-size:11px;font-family:inherit;outline:none;
  transition:border-color .12s;
}
.log-search:focus{border-color:#555}
/* Filter width is expressed in the widest value the field holds, as a size
   class rather than an inline style: an inline width cannot be adapted by a
   media query without !important, and the narrow layout has to be able to
   widen these. The number is the digit or character count the box reserves. */
.log-search--w4{max-width:74px}
.log-search--w6{max-width:90px}
.log-search--w8{max-width:130px}
.log-search--w10{max-width:160px}
/* "Show" / "Capture" cluster labels. They name what each group controls so
   the view filter (Show: hides already-recorded lines) reads as a different
   thing from the server-side write floor (Capture: what gets recorded). */
.logs-group-label{font-size:10px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--text3);white-space:nowrap}
/* Capture-level select. Shares the control height with the filter buttons so
   the row lines up, but carries the input look (it's a setting, not a view
   toggle). */
.log-capture-select{
  height:28px;padding:0 8px;
  background:var(--bg);border:1px solid var(--border);border-radius:4px;
  color:var(--text);font-size:11px;font-family:inherit;cursor:pointer;outline:none;
  transition:border-color .12s;
}
.log-capture-select:focus{border-color:#555}
.logs-capture .help{margin-left:2px}
/* Capture/Show mismatch advisory: shown above the stream (or centered in the
   empty state) when the Show filter targets a severity the capture floor
   isn't recording, so a thinned or empty view is explained rather than
   mysterious. The amber accent flags "your view and your write floor
   disagree"; the inline button raises the floor to start recording it. */
.log-capture-notice{
  display:flex;align-items:center;gap:12px;flex-wrap:wrap;
  margin:0 0 10px;padding:9px 12px;
  font-size:12px;line-height:1.5;color:var(--text2);
  background:var(--bg3);border:1px solid var(--border-strong);
  border-left:3px solid #d97706;border-radius:var(--radius);
}
.log-capture-notice .log-capture-raise{
  height:24px;padding:0 10px;flex-shrink:0;
  border:1px solid var(--border-strong);border-radius:4px;
  background:transparent;color:var(--text);
  font-size:11px;font-weight:600;font-family:inherit;cursor:pointer;
  transition:color .12s,border-color .12s,background .12s;
}
.log-capture-notice .log-capture-raise:hover{background:var(--bg2);border-color:var(--text3)}
.dash-logs .log-capture-notice--empty{margin:32px auto;max-width:540px;justify-content:center;text-align:center}
.dash-logs{overflow:auto;border-radius:var(--radius);overscroll-behavior:contain}
/* Each rendered log row opts into content-visibility so the browser skips
   layout and paint for off-screen entries. The intrinsic-size hint keeps
   scroll bar geometry stable. Combined with paginated scroll-back, this
   carries the table to ~50k rows without UI hitches; beyond that the
   operator should use Download. */
.dash-logs .req-table tbody tr{content-visibility:auto;contain-intrinsic-size:0 32px}
.dash-logs .lazy-loading-banner{padding:8px 12px;font-size:12px;color:var(--text2);text-align:center;background:var(--bg-subtle,#f8f8f8);border-bottom:1px solid var(--border,#e5e5e5)}
.dash-logs .lazy-end-banner{padding:14px 12px;font-size:12px;color:var(--text2);text-align:center;border-top:1px solid var(--border-strong);background:var(--bg3)}
/* Log message cell. Each entry is a single row; if the event carries
   a stack trace, it folds behind a <details> expander so the table
   stays one-line-per-event by default. The trace renders as a
   monospace block so file paths and method signatures line up. */
.req-table td.log-msg-cell{white-space:normal;word-break:break-word;vertical-align:top}
.log-msg-text{display:block;line-height:1.45}
.log-trace{margin-top:4px;font-size:11px}
.log-trace>summary{
  list-style:none;cursor:pointer;
  display:inline-flex;align-items:center;gap:6px;
  color:var(--text3);font-weight:500;
  padding:1px 0;user-select:none;-webkit-user-select:none;
  transition:color .12s;
}
.log-trace>summary::-webkit-details-marker{display:none}
.log-trace>summary::before{
  content:'';
  width:0;height:0;border-style:solid;border-width:4px 0 4px 5px;
  border-color:transparent transparent transparent currentColor;
  flex-shrink:0;transition:transform .12s;
}
.log-trace[open]>summary::before{transform:rotate(90deg)}
.log-trace>summary:hover{color:var(--text2)}
.log-trace>pre{
  margin:6px 0 0;padding:10px 12px;
  background:var(--bg);border:1px solid var(--border);border-radius:4px;
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:11px;line-height:1.45;
  color:var(--text2);
  white-space:pre-wrap;word-break:break-word;
  max-height:340px;overflow:auto;
}
#dashboard,#requests,#telemetry-mon{border-bottom:none}

/* Collections */
/* Generic stacked-card list container (Models loaded/catalog lists, custom models). */
.col-clusters{display:flex;flex-direction:column;gap:16px;max-width:900px;margin-bottom:28px}
.col-cluster-error{font-size:12px;color:#ef4444;padding:10px 16px}
.col-empty{font-size:12px;color:var(--text3);padding:10px 16px}
/* Tenant-list status panel: a structured load-error / unreachable state (not a bare red line). A left accent
   and quiet fact grid make the failure legible at a glance, with the Retry action pinned to the right. */
.col-cluster-status{display:flex;align-items:flex-start;gap:14px;margin:12px 16px;padding:12px 14px;border:1px solid var(--border);border-left:3px solid var(--text3);border-radius:var(--radius);background:var(--bg)}
.col-cluster-status--error{border-left-color:#dc2626;background:rgba(220,38,38,.06)}
.ccs-main{flex:1 1 auto;min-width:0}
.ccs-title{font-size:13px;font-weight:600;color:var(--text)}
.col-cluster-status--error .ccs-title{color:#dc2626}
.ccs-sub{font-size:12px;color:var(--text2);line-height:1.5;margin-top:3px}
.ccs-facts{display:flex;flex-wrap:wrap;gap:8px 20px;margin:11px 0 0}
.ccs-facts>div{display:flex;flex-direction:column;gap:2px;min-width:0}
.ccs-facts dt{font-size:10px;text-transform:uppercase;letter-spacing:.04em;color:var(--text3)}
.ccs-facts dd{margin:0;font-size:12px;color:var(--text2);font-variant-numeric:tabular-nums;overflow-wrap:anywhere}
.ccs-facts dd.ccs-mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
.ccs-actions{flex:none;display:flex;gap:8px}
@media(max-width:640px){.col-cluster-status{flex-direction:column}.ccs-actions{width:100%}}
.col-item{display:flex;align-items:center;justify-content:space-between;padding:8px 16px;border-top:1px solid var(--border)}
.col-item:first-child{border-top:none}
.col-item-name{font-size:13px;color:var(--text)}
.col-item-meta{font-size:11px;color:var(--text3);margin-left:8px}
.col-item-actions{display:flex;gap:6px;align-items:center;flex-wrap:wrap;justify-content:flex-end}
/* Header-action grouping: buttons of the same intent sit in a .cag group, groups are split by a thin
   .cag-sep divider, and the destructive .cag-end button is pushed to the far right. */
.cag{display:inline-flex;gap:6px;align-items:center}
.cag-sep{width:1px;align-self:stretch;min-height:20px;background:var(--border);margin:0 4px}
.cag-end{margin-left:auto}
.col-item-actions .btn-cleanup.active,.ss-page-actions .btn-cleanup.active{color:var(--text);border-color:var(--border-strong);background:var(--bg2)}
.btn-del{padding:3px 8px;border:1px solid #ef444444;border-radius:4px;background:transparent;color:#ef4444;font-size:11px;cursor:pointer;font-family:inherit;transition:background .12s}
.btn-del:hover{background:#ef444422}
/* Every action button in a cluster / tenant / collection row shares one height, so a row never
   mixes tall and short buttons regardless of which primitive (btn-cleanup vs btn-del) it uses. */
.col-item-actions .btn-cleanup,
.col-item-actions .btn-del{display:inline-flex;align-items:center;justify-content:center;height:30px;padding:0 12px;font-size:12px;line-height:1;border-radius:6px;box-sizing:border-box;white-space:nowrap;flex-shrink:0}
.btn-caret{margin-left:7px;font-size:9px;line-height:1;opacity:.75}
/* Row-action dropdown: a single body-level, fixed-position menu (so the card's overflow:hidden never clips
   it), shared by every collection Rebuild control to offer the semantic / full-text targets from one button.
   Flat items with a two-line label (name + what it does); no pill/chip chrome. */
.ss-menu{position:fixed;z-index:200;min-width:220px;max-width:300px;display:flex;flex-direction:column;padding:6px;gap:2px;background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius);box-shadow:0 12px 32px rgba(0,0,0,.35)}
.ss-menu[hidden]{display:none}
.ss-menu-item{display:flex;flex-direction:column;gap:3px;align-items:flex-start;width:100%;padding:8px 10px;border:0;border-radius:6px;background:transparent;color:var(--text);font-family:inherit;font-size:12px;font-weight:600;text-align:left;cursor:pointer}
.ss-menu-item:hover,.ss-menu-item:focus-visible{background:var(--bg);outline:none}
.ss-menu-item small{font-size:11px;font-weight:400;color:var(--text3);line-height:1.4;white-space:normal}
/* Search drill-down chrome: the breadcrumb naming the current location, and the three
   levels (clusters list, one cluster, one tenant). Rows are data plus one chevron; every
   per-object action lives on that object's own page. */
.ss-crumb{display:flex;align-items:center;gap:8px;margin:0 0 14px;font-size:13px;max-width:var(--content-col)}
.ss-crumb-link{border:none;background:none;padding:2px 4px;font-family:inherit;font-size:13px;color:var(--text2);cursor:pointer;border-radius:4px}
.ss-crumb-link:hover{color:var(--text);background:var(--bg2)}
.ss-crumb-sep{color:var(--text3);user-select:none;-webkit-user-select:none}
.ss-crumb-cur{color:var(--text);font-weight:600;padding:2px 0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
/* Level 0: one row per cluster inside the Clusters card. Fixed grid columns so live figures
   update in place without moving a neighbour; ONE alignment (left) for every column so the
   header labels sit exactly over their values. */
.ss-cl-list{display:flex;flex-direction:column;user-select:none;-webkit-user-select:none}
.ss-cl-list>.hint{padding:14px 16px}
.ss-cl-head,.ss-cl-row{display:grid;grid-template-columns:minmax(0,1fr) 80px 100px 90px 110px 24px;gap:12px;align-items:center;padding:12px 16px}
.ss-cl-head{padding-top:10px;padding-bottom:8px;border-bottom:1px solid var(--border);font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--text3)}
.ss-cl-row{border-top:1px solid var(--border);cursor:pointer;transition:background .12s}
.ss-cl-row:first-of-type{border-top:none}
.ss-cl-row:hover,.ss-cl-row:focus-visible{background:var(--hover-wash);outline:none}
.ss-cl-name-cell{display:flex;flex-direction:column;gap:2px;min-width:0}
.ss-cl-name{font-size:13px;font-weight:600;color:var(--text);display:flex;align-items:baseline;gap:8px;min-width:0}
.ss-cl-name>span:first-child{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ss-cl-default{font-size:11px;font-weight:600;color:var(--primary);flex:0 0 auto}
.ss-cl-stores{font-size:11px;color:var(--text3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ss-cl-num{font-size:13px;font-weight:600;color:var(--text);font-variant-numeric:tabular-nums;white-space:nowrap}
.ss-cl-num--pending{color:var(--text3);font-weight:500}
.ss-cl-status{font-size:11px;font-weight:600;white-space:nowrap}
.ss-cl-status--ok{color:#10b981}
.ss-cl-status--bad{color:#dc2626}
.ss-cl-status--pending{color:var(--text3);font-weight:500}
.ss-cl-chev,.ss-tn-chev{display:flex;align-items:center;justify-content:flex-end;color:var(--text3)}
/* One-shot confirmation flash on the object a navigation just landed on (action confirmation,
   not a live indicator; runs once and fades). */
@keyframes ssFlash{from{background:var(--hover-wash)}to{background:transparent}}
.ss-flash{animation:ssFlash 1.6s ease-out 1}
/* Level pages share one header shape: identity left, that object's actions right, both
   bound to the content column. */
.ss-page-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px 16px;flex-wrap:wrap;max-width:var(--content-col)}
.ss-page-id{display:flex;flex-direction:column;gap:4px;min-width:0}
.ss-page-title{font-size:17px;font-weight:600;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ss-page-sub{display:flex;align-items:center;gap:10px;font-size:12px;color:var(--text3);flex-wrap:wrap;min-width:0}
.ss-page-actions{display:flex;gap:6px;align-items:center;flex-wrap:wrap;justify-content:flex-end}
.ss-page-actions .btn-cleanup,.ss-page-actions .btn-del,.ss-page-actions .ss-tenant-delsel{display:inline-flex;align-items:center;justify-content:center;height:30px;padding:0 12px;font-size:12px;line-height:1;border-radius:6px;box-sizing:border-box;white-space:nowrap;flex-shrink:0}
.ss-cluster-error{max-width:var(--content-col);margin-top:14px;padding:12px 14px;border:1px solid rgba(220,38,38,.45);border-radius:var(--radius);background:rgba(220,38,38,.07);font-size:12px;line-height:1.5;color:#dc2626}
/* Sections are CARDS with an in-card header bar: title left, the section's count and one
   action right, a divider, then the content. Each page reads as a short stack of labelled
   surfaces instead of floating text; the section owns its top margin (container-owned rhythm). */
.ss-sec{max-width:var(--content-col);margin-top:20px;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg2);overflow:hidden}
.ss-sec--flush{margin-top:0}
.ss-sec-head{display:flex;align-items:center;justify-content:space-between;gap:12px;min-height:48px;padding:9px 16px;border-bottom:1px solid var(--border)}
.ss-sec-title{font-size:13px;font-weight:600;color:var(--text)}
.ss-sec-tools{display:flex;align-items:center;gap:12px}
.ss-sec-tools .btn-cleanup{display:inline-flex;align-items:center;justify-content:center;height:30px;padding:0 12px;font-size:12px;line-height:1;border-radius:6px;box-sizing:border-box;white-space:nowrap}
.ss-sec-meta{font-size:12px;color:var(--text3);font-variant-numeric:tabular-nums}
/* Filter shown only while managing a LONG key list (and in the create modal): dynamic
   disclosure, never a standing control. */
.ss-access-filter{display:block;width:280px;max-width:calc(100% - 32px);margin:12px 16px 0;padding:6px 10px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:12px;font-family:inherit;outline:none;box-sizing:border-box}
.ss-access-filter:focus{border-color:var(--primary)}
.ss-access-filter::placeholder{color:var(--text3)}
#nt-keys-filter{width:100%;margin:0 0 8px}
/* Level 1: one row per tenant inside the Tenants card, same fixed-grid discipline and the
   same single left alignment as the clusters list. The .col-item / .col-item-actions hooks
   stay because the deletion machinery locks a row in place through them. */
.ss-tenants{display:flex;flex-direction:column;user-select:none;-webkit-user-select:none}
.ss-tenants>.col-empty{padding:14px 16px}
.ss-tn-head{display:grid;grid-template-columns:minmax(0,1.3fr) 60px 90px 90px minmax(0,1fr) 110px 24px;gap:12px;align-items:center;padding:10px 16px 8px;border-bottom:1px solid var(--border);font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--text3)}
.ss-tenant{border-top:1px solid var(--border)}
.ss-tenant:first-of-type,.ss-tn-head+.ss-tenant{border-top:none}
.col-item.ss-tn-row{display:grid;grid-template-columns:minmax(0,1.3fr) 60px 90px 90px minmax(0,1fr) 110px 24px;gap:12px;align-items:center;padding:12px 16px;cursor:pointer;border-top:none;transition:background .12s}
.ss-tn-row:hover,.ss-tn-row:focus-visible{background:var(--hover-wash);outline:none}
.ss-tenant--deleting .ss-tn-row{cursor:default}
.ss-tenant--deleting .ss-tn-row:hover{background:transparent}
.ss-tn-name-cell{display:flex;flex-direction:column;gap:2px;min-width:0}
.ss-tn-name{font-size:13px;font-weight:600;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ss-tn-num{font-size:13px;color:var(--text);font-variant-numeric:tabular-nums;white-space:nowrap}
.ss-tn-num--muted{color:var(--text3)}
.ss-tn-num--none{color:#dc2626;font-weight:600}
.ss-tn-model{font-size:12px;color:var(--text2);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ss-tn-mode{font-size:12px;color:var(--text2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ss-tn-state{color:#f59e0b;font-weight:600}
.ss-tn-state--del{color:#dc2626}
.ss-tn-row>.col-item-actions{justify-content:flex-end}
/* Tenant and collection ids share one monospace style and a hover-revealed copy button next to the id. */
.ss-tenant-id,.ss-col-id{font-size:12px;color:var(--text2);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;display:inline-flex;align-items:center;gap:6px;align-self:flex-start;max-width:100%}
.ss-id-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
.ss-id-copy{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;padding:0;background:transparent;border:1px solid transparent;border-radius:4px;color:var(--text3);cursor:pointer;flex-shrink:0;opacity:.5;transition:opacity .12s ease,background .12s ease,color .12s ease,border-color .12s ease}
.ss-id-copy:hover{opacity:1;background:var(--bg3);border-color:var(--border);color:var(--text)}
.ss-id-copy:focus-visible{outline:none;opacity:1;box-shadow:var(--focus-ring)}
.ss-id-copy.copied{opacity:1;color:#10b981;border-color:rgba(16,185,129,.4);background:rgba(16,185,129,.12)}
.ss-id-copy svg{display:block}
/* Level 2: the tenant page. A stronger identity scale (title + figures), then the section
   cards; access and setup content render borderless inside their cards. */
.ss-tstats{display:flex;gap:12px 44px;flex-wrap:wrap;max-width:var(--content-col);margin-top:18px}
.ss-tstat{display:flex;flex-direction:column;line-height:1.2;gap:2px}
.ss-tstat-v{font-size:17px;font-weight:600;color:var(--text);font-variant-numeric:tabular-nums}
.ss-tstat-l{font-size:10px;color:var(--text3);text-transform:uppercase;letter-spacing:.04em}
.ss-access-list{display:flex;flex-direction:column;overflow-y:auto;max-height:332px}
.ss-access-row{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 16px;border-top:1px solid var(--border)}
.ss-access-row:first-child{border-top:none}
.ss-access-keyinfo{display:flex;align-items:baseline;gap:10px;min-width:0}
.ss-access-keyname{font-size:13px;font-weight:500;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ss-access-keymeta{font-size:10px;color:var(--text3);text-transform:uppercase;letter-spacing:.04em;flex:0 0 auto}
.ss-access-state{font-size:12px;color:var(--text3);padding:12px 16px}
.ss-pairs{display:grid;grid-template-columns:repeat(auto-fill,minmax(190px,1fr));gap:14px 18px;padding:14px 16px}
.ss-pair{display:flex;flex-direction:column;gap:3px;min-width:0}
.ss-pair-l{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--text3);display:flex;align-items:center;gap:5px}
.ss-pair-v{font-size:13px;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ss-pair-v--muted{color:var(--text3);font-style:italic}
.ss-pair-v--mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}
.ss-pair-note{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--text3);margin-left:6px;font-style:normal}
/* Tenant deletion: on the cluster page the row is locked with a live bar via the shared
   .col-item-actions / .ss-tenant-del hooks; on the tenant page the header actions collapse
   to Cancel and .ss-page-del carries the same bar. */
.ss-tenant--deleting{opacity:.95}
.ss-tenant-deleting{font-size:12px;color:var(--text2);font-style:italic}
.ss-tenant-delerror{font-size:12px;color:#c0392b;padding:2px 16px 10px}
.ss-tenant-delerror--page{padding:0;margin-top:12px;max-width:var(--content-col)}
.ss-tenant-del{display:flex;align-items:flex-start;gap:14px;padding:0 16px 12px}
.ss-tenant-del .ss-del{margin-top:0;max-width:560px;flex:1 1 auto;min-width:0}
.ss-tenant-del-actions{flex:0 0 auto;padding-top:1px}
.ss-tenant-del-actions .btn-cleanup{height:26px;padding:0 10px;font-size:12px}
.ss-page-del{max-width:var(--content-col);margin-top:14px}
.ss-page-del .ss-del{margin-top:0;max-width:560px}
/* Destructive combo box that replaces the single Delete button: pick whole-tenant or collections-only. Flat
   corners (the design-system max), red to read as destructive, sized like the adjacent action buttons. */
.ss-tenant-delsel{height:30px;padding:0 8px;font-size:12px;line-height:1;border:1px solid #ef444444;border-radius:6px;background:transparent;color:#ef4444;font-family:inherit;cursor:pointer;box-sizing:border-box;flex-shrink:0}
.ss-tenant-delsel:hover{background:#ef444422}
.ss-tenant-delsel:focus-visible{outline:none;border-color:#ef4444;box-shadow:0 0 0 2px #ef444433}
.ss-tenant-delsel option{color:var(--text);background:var(--bg)}
/* Creation modals: Add cluster (the connection form) and New tenant (name + key grants). */
.modal.sc-modal{width:640px;max-height:88vh;overflow-y:auto}
.sc-modal-actions{align-items:center}
.modal-actions-spacer{flex:1}
.modal.nt-modal{width:440px}
.nt-keys{display:flex;flex-direction:column;border:1px solid var(--border);border-radius:6px;background:var(--bg);overflow:hidden;overflow-y:auto;max-height:240px}
.nt-key-row{display:flex;align-items:center;gap:10px;padding:9px 12px;border-top:1px solid var(--border);cursor:pointer;user-select:none;-webkit-user-select:none;font-size:13px;color:var(--text)}
.nt-key-row:first-child{border-top:none}
.nt-key-row input{flex:0 0 auto}
.nt-key-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.nt-key-scope{margin-left:auto;font-size:10px;color:var(--text3);text-transform:uppercase;letter-spacing:.04em;flex:0 0 auto}
.nt-keys-state{font-size:12px;color:var(--text3);padding:10px 12px}
/* Failed-input capture diagnostics control (server-wide; top of the Search section). */
.ss-diag{display:flex;flex-direction:column;gap:10px;padding:12px 14px;margin-bottom:16px;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg2);max-width:900px;user-select:text;-webkit-user-select:text}
.ss-diag--row{flex-direction:row;align-items:center;gap:20px}
.ss-diag--row .ss-diag-main{flex:1}
.ss-diag--row .ss-diag-controls{flex:0 0 auto}
.ss-diag-main{display:flex;flex-direction:column;gap:3px;min-width:0}
.ss-diag-title{font-size:13px;font-weight:600;color:var(--text)}
.ss-diag-sub{font-size:11px;color:var(--text3);line-height:1.45}
.ss-diag-controls{display:flex;align-items:center;gap:8px}
.ss-diag-dir{flex:1;min-width:0;padding:7px 9px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:12px;font-family:inherit;outline:none;user-select:text;-webkit-user-select:text}
.ss-diag-dir:focus{border-color:var(--primary)}
.ss-diag-select{padding:7px 9px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:12px;font-family:inherit;outline:none;cursor:pointer}
.ss-diag-select:focus{border-color:var(--primary)}
.ss-capture-toggle.on{color:#10b981;border-color:#10b981}
.ss-diag-path{font-size:11px;color:var(--text3)}
/* Two-column form grid: labels size to their content and inputs align in a tight column right after the
   longest label, instead of being pushed to the far edge of a wide row. Each label uses display:contents
   so its span + input become direct grid items (the label still wraps the input for click focus). */
.ss-limits-grid{display:grid;grid-template-columns:max-content 160px;align-items:center;gap:10px 16px;width:auto;max-width:560px}
.ss-limit-field{display:contents;font-size:12px;color:var(--text2)}
.ss-limit-field span{cursor:default;user-select:none;-webkit-user-select:none}
.ss-limit-input{width:160px;text-align:right}
.ss-limits-grid #ss-limits-save{grid-column:2;justify-self:start;margin-top:2px}
.ss-diag-effective{color:var(--text2);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;word-break:break-all;user-select:text;-webkit-user-select:text;cursor:text}
/* Search: cluster health summary card on the cluster page. */
.ss-health{max-width:var(--content-col)}
.ss-health-card{padding:12px 14px;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg2);margin-top:14px;user-select:none;-webkit-user-select:none}
.ss-health-top{display:flex;gap:24px;flex-wrap:wrap;margin-bottom:8px}
.ss-stat{display:flex;flex-direction:column;line-height:1.2}
.ss-stat-v{font-size:18px;font-weight:600;color:var(--text)}
.ss-stat-l{font-size:11px;color:var(--text3);text-transform:uppercase;letter-spacing:.04em}
.ss-health-status{display:flex;gap:14px;flex-wrap:wrap;font-size:12px;margin-bottom:8px}
.ss-health-ok{color:#10b981}
.ss-health-warn{color:#f59e0b}
/* The indexing tile lives on the tenant page (and mirrored inside the tenant-settings modal);
   each host owns its outer spacing, the tile itself carries none. */
.ss-reindex-host--page{max-width:var(--content-col)}
.ss-reindex-host--page .ss-reindex{margin:24px 0 0}
.ss-reindex{padding:12px 14px;border:1px solid var(--border);border-radius:6px;background:var(--bg2)}
.ss-reindex-head{display:flex;align-items:baseline;justify-content:space-between;gap:10px;margin-bottom:7px}
.ss-reindex-title{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--text2)}
.ss-reindex-pct{font-size:15px;font-weight:700;color:#10b981;font-variant-numeric:tabular-nums}
.ss-reindex--embed{border-style:dashed}
.ss-reindex-pct--muted{font-size:12px;font-weight:600;color:var(--text3)}
.ss-reindex-bar{height:8px;border-radius:3px;background:rgba(127,127,127,.18);overflow:hidden;margin-bottom:8px}
.ss-reindex-fill{height:100%;background:#10b981;transition:width .4s ease}
.ss-reindex-sub{font-size:11px;color:var(--text3);margin-bottom:7px;font-variant-numeric:tabular-nums}
.ss-reindex-coll{font-size:11px;color:var(--text2);margin-bottom:6px}
.ss-reindex-coll b{font-weight:600;color:var(--text)}
/* Tenant-wide semantic rebuild, erase act (tracked server-side): amber accent so it reads as a live
   erase-then-rebuild operation, distinct from the green re-embed drain that takes over once it hands off. */
.ss-reindex--rebuild .ss-reindex-pct{color:#d97706}
.ss-reindex--rebuild .ss-reindex-fill{background:#d97706}
.ss-reindex-note{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:2px;font-size:11px;color:var(--text3)}
.ss-reindex-cancelling{font-size:12px;color:var(--text2);font-style:italic;white-space:nowrap}
.ss-rebuild-cancel{flex:none}
.ss-reindex-error{font-size:12px;color:#c0392b}
.ss-reindex-groups{display:flex;flex-wrap:wrap;gap:20px 56px;margin-top:11px}
.ss-reindex-group{min-width:0}
.ss-reindex-grouplabel{font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--text3);margin:0 0 5px}
.ss-reindex-rates{display:flex;gap:28px;flex-wrap:wrap}
.ss-reindex-stat{display:flex;flex-direction:column;line-height:1.25}
.ss-reindex-v{font-size:16px;font-weight:600;color:var(--text);font-variant-numeric:tabular-nums}
.ss-reindex-l{font-size:10px;color:var(--text3);text-transform:uppercase;letter-spacing:.04em}
.ss-col--deleting{opacity:.95}
.ss-col-deleting{font-size:12px;color:var(--text2);font-style:italic}
.ss-col-delerror{font-size:12px;color:#c0392b;margin-top:6px}
.ss-del{margin-top:8px;max-width:520px}
.ss-del-head{display:flex;align-items:baseline;justify-content:space-between;gap:10px;margin-bottom:5px}
.ss-del-phase{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--text2)}
.ss-del-pct{font-size:14px;font-weight:700;color:#c0392b;font-variant-numeric:tabular-nums}
.ss-del-bar{height:8px;border-radius:3px;background:rgba(127,127,127,.18);overflow:hidden;margin-bottom:5px}
.ss-del-fill{height:100%;background:#c0392b;transition:width .4s ease}
.ss-del-sub{font-size:11px;color:var(--text3);font-variant-numeric:tabular-nums;min-height:14px}
.ss-health-table{width:100%;border-collapse:collapse;font-size:12px}
.ss-health-table th{text-align:left;color:var(--text3);font-weight:500;padding:4px 10px 4px 0;border-bottom:1px solid var(--border)}
.ss-health-table td{padding:4px 10px 4px 0;color:var(--text2);font-variant-numeric:tabular-nums}
/* Search: database recommendations. The advisor's REPORT tier as its own labeled section under
   the health card (never in the worker event timeline). Each finding carries a severity accent, the
   current -> recommended values, and a "?" help icon with the rationale. Empty container renders nothing. */
.ss-reco:empty{display:none}
.ss-reco{max-width:var(--content-col)}
.ss-reco-card{padding:12px 16px;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg2);margin-top:14px}
.ss-reco-head{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:10px}
.ss-reco-title{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--text3)}
.ss-reco-meta{font-size:11px;color:var(--text3)}
.ss-reco-refresh{margin-left:auto}
.ss-reco-ok{font-size:12px;color:var(--text3)}
.ss-reco-list{display:flex;flex-direction:column;gap:8px}
.ss-reco-item{display:flex;flex-direction:column;gap:3px;padding:8px 11px;border:1px solid var(--border);border-left:3px solid var(--text3);border-radius:6px;background:var(--bg2)}
.ss-reco--high{border-left-color:#dc2626}
.ss-reco--medium{border-left-color:#f59e0b}
.ss-reco--info{border-left-color:var(--text3)}
.ss-reco-row{display:flex;align-items:center;gap:8px}
.ss-reco-param{font-size:12px;font-weight:600;color:var(--text);font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
.ss-reco-sev{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--text3)}
.ss-reco--high .ss-reco-sev{color:#dc2626}
.ss-reco--medium .ss-reco-sev{color:#f59e0b}
.ss-reco-vals{display:flex;align-items:baseline;gap:7px;flex-wrap:wrap;font-size:12px;color:var(--text2);font-variant-numeric:tabular-nums}
.ss-reco-arrow{color:var(--text3)}
.ss-reco-rec{color:var(--text);font-weight:600}
.ss-reco-applied{margin-top:10px;font-size:11px;color:var(--text3)}
/* Search: database diagnostics panel (overview tiles, index/table stats, explain). Its own
   ss-dbd-* prefix, distinct from the unrelated ss-diag-* capture-settings controls above. */
.ss-dbd{max-width:var(--content-col);margin-top:14px;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg)}
.ss-dbd-card{padding:14px 16px}
.ss-dbd-bar{display:flex;align-items:center;gap:8px;user-select:none;margin-bottom:14px;padding-bottom:10px;border-bottom:1px solid var(--border)}
.ss-dbd-title{flex:1;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--text2)}
.ss-dbd-refresh{font-size:11px;color:var(--text2);cursor:pointer;padding:3px 8px;border-radius:4px}
.ss-dbd-refresh:hover{color:var(--text);background:var(--bg2)}
.ss-dbd-refresh.busy{opacity:.5;pointer-events:none}
.ss-dbd-close{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;font-size:18px;line-height:1;color:var(--text3);cursor:pointer;border-radius:4px}
.ss-dbd-close:hover{color:var(--text);background:var(--bg2)}
/* Overview tiles: bordered boxes in an auto-fill grid with a normal gap, so each metric is clearly
   separated and empty trailing cells in the last row are simply absent (no filler rectangle). */
.ss-dbd-stats{display:grid;grid-template-columns:repeat(auto-fill,minmax(116px,1fr));gap:8px}
.ss-dbd-tile{display:flex;flex-direction:column;gap:4px;padding:10px 12px;border:1px solid var(--border);border-radius:6px;background:var(--bg2);min-width:0}
.ss-dbd-tile--wide{grid-column:span 2}
.ss-dbd-tile-v{font-size:16px;font-weight:600;color:var(--text);font-variant-numeric:tabular-nums;line-height:1.15;word-break:break-word}
.ss-dbd-tile--wide .ss-dbd-tile-v{font-size:13px;font-weight:500}
.ss-dbd-tile-l{font-size:10px;color:var(--text3);text-transform:uppercase;letter-spacing:.04em}
/* Live embedding-worker panel: an always-visible status surface at the top of Search so the
   operator sees what the background drain is doing, what is waiting, and what is blocked, without opening a
   modal. Severity tints the left rail and headline; section tags are plain colored uppercase labels (no
   pill/badge chrome, no animated indicators), matching the rest of the admin design. */
/* Search telemetry panel: a live-metrics board (sparkline stat-cards) above the embedding-worker panel.
   Reuses the dashboard's .stat-card / .stat-spark primitives so the charts match the rest of the admin UI;
   the left rail and headline tint by database-health severity. No pill/badge chrome, no animated indicators. */
.ss-mx{margin:0 0 18px;padding:14px 16px 14px;border:1px solid var(--border);border-left:3px solid var(--text3);border-radius:var(--radius);background:var(--bg2);max-width:900px}
.ss-mx--ok{border-left-color:#10b981}
.ss-mx--warn{border-left-color:#f59e0b;background:rgba(245,158,11,.06)}
.ss-mx--bad{border-left-color:#dc2626;background:rgba(220,38,38,.07)}
.ss-mx-head{display:flex;align-items:center;gap:10px;flex-wrap:nowrap;margin-bottom:12px}
.ss-mx-title{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--text3);white-space:nowrap;flex:none}
/* Headline is a single-line status bar: it truncates with an ellipsis so a long verdict (e.g. a full slow-query
   text) never wraps and pushes the cards below. The complete text is on the element title (truncated-string reveal). */
.ss-mx-headline{font-size:13px;font-weight:600;color:var(--text2);flex:1 1 auto;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ss-mx-cluster{margin-left:auto;flex-shrink:0;font-size:12px;padding:4px 9px;border:1px solid var(--border);border-radius:4px;background:var(--bg);color:var(--text);font-family:inherit;cursor:pointer}
.ss-mx-cluster:focus{outline:none;border-color:var(--primary)}
.ss-mx--ok .ss-mx-headline{color:#10b981}
.ss-mx--warn .ss-mx-headline{color:#f59e0b}
.ss-mx--bad .ss-mx-headline{color:#dc2626}
.ss-mx>.stat-cards{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin:0}
/* Slow-queries block: a collapsible trace + cumulative top-queries view under the telemetry cards, styled
   like the worker panel's event timeline (plain summary toggle, table rows, no chrome). */
.ss-mx-trace{margin-top:12px}
.ss-mx-trace summary{cursor:pointer;color:var(--text3);font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;user-select:none;-webkit-user-select:none}
.ss-mx-trace summary:hover{color:var(--text)}
.ss-mx-trace-body{margin-top:10px}
.ss-mx-trace-label{display:flex;align-items:center;gap:10px;font-size:11px;font-weight:600;color:var(--text2);margin-bottom:6px}
.ss-mx-trace-label--gap{margin-top:14px}
.ss-mx-trace .ss-health-table td,.ss-mx-trace .ss-health-table th{padding:5px 14px 5px 0;white-space:nowrap;vertical-align:top}
.ss-mx-trace .ss-health-table td:last-child,.ss-mx-trace .ss-health-table th:last-child{white-space:normal;overflow-wrap:anywhere;padding-right:0}
.ss-mx-trace .ss-qlive{color:#f59e0b;font-weight:600}
.ss-mx>.stat-cards .stat-card{margin:0;cursor:pointer;transition:border-color .12s}
.ss-mx>.stat-cards .stat-card:hover{border-color:var(--primary)}
@media(max-width:820px){.ss-mx>.stat-cards{grid-template-columns:repeat(2,minmax(0,1fr))}}
/* Metric detail: a right-side drawer (same chrome as the traffic Breakdown drawer) with a full-history chart
   and window stats. Opened by clicking any telemetry card. */
.ss-mx-scrim{position:fixed;inset:0;background:var(--scrim);z-index:23}
.ss-mx-scrim[hidden]{display:none}
.ss-mx-drawer{position:fixed;top:0;right:0;bottom:0;width:620px;max-width:92vw;background:var(--bg2);border-left:1px solid var(--border);display:flex;flex-direction:column;z-index:24;box-shadow:var(--shadow-drawer);animation:slidein .18s ease}
.ss-mx-drawer[hidden]{display:none}
.ss-mx-drawer-head{display:flex;align-items:center;gap:12px;padding:20px 24px 12px;border-bottom:1px solid var(--border)}
.ss-mx-drawer-head h2{font-size:14px;font-weight:600;color:var(--text);margin:0 auto 0 0;letter-spacing:.04em;text-transform:uppercase}
.ss-mx-drawer-body{flex:1;min-height:0;overflow:auto;padding:18px 24px 24px}
.ssd-value{font-size:34px;font-weight:600;color:var(--text);font-variant-numeric:tabular-nums;line-height:1.1;margin-bottom:14px}
.ss-mx-drawer .chart{height:170px}
.ssd-range{margin-bottom:14px}
.ssd-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin:16px 0 18px}
.ssd-stats>div{display:flex;flex-direction:column;gap:3px;padding:9px 11px;border:1px solid var(--border);border-radius:6px;background:var(--bg)}
.ssd-stat-l{font-size:10px;text-transform:uppercase;letter-spacing:.04em;color:var(--text3)}
.ssd-stat-v{font-size:15px;font-weight:600;color:var(--text);font-variant-numeric:tabular-nums}
.ssd-desc{font-size:12px;color:var(--text2);line-height:1.6;margin:0}
.ssd-meta{font-size:11px;color:var(--text3);line-height:1.6;margin:10px 0 0;padding-top:10px;border-top:1px solid var(--border)}
.ss-wl{margin:0 0 18px;padding:14px 16px 12px;border:1px solid var(--border);border-left:3px solid var(--text3);border-radius:var(--radius);background:var(--bg2);max-width:900px}
.ss-wl--ok{border-left-color:#10b981;background:rgba(16,185,129,.06)}
.ss-wl--warn{border-left-color:#f59e0b;background:rgba(245,158,11,.06)}
.ss-wl--bad{border-left-color:#dc2626;background:rgba(220,38,38,.07)}
.ss-wl--neutral{border-left-color:var(--text3)}
.ss-wl-head{display:flex;align-items:center;gap:10px;flex-wrap:nowrap}
.ss-wl-title{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--text3);white-space:nowrap;flex:none}
.ss-wl-headline{font-size:14px;font-weight:700;color:var(--text);flex:1 1 auto;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ss-wl--ok .ss-wl-headline{color:#10b981}
.ss-wl--warn .ss-wl-headline{color:#f59e0b}
.ss-wl--bad .ss-wl-headline{color:#dc2626}
/* Detail reserves a fixed two-line band (clamped top and bottom) so a longer or shorter verdict sentence never
   shifts the tiles below. The complete text is on the element title. */
.ss-wl-detail{font-size:12px;color:var(--text2);line-height:1.5;margin:4px 0 10px;min-height:calc(1.5em * 2);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.ss-wl-running{display:flex;align-items:center;gap:10px;flex-wrap:nowrap;margin-bottom:10px}
.ss-wl-phase{font-size:13px;font-weight:600;color:var(--text);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ss-wl-elapsed{font-size:12px;color:var(--text3);font-variant-numeric:tabular-nums}
.ss-wl-tag{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--text3)}
.ss-wl-tag--wait{color:#f59e0b}
.ss-wl-tag--bad{color:#dc2626}
.ss-wl-tiles{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px}
@media(max-width:620px){.ss-wl-tiles{grid-template-columns:repeat(2,minmax(0,1fr))}}
.ss-wl-tile{display:flex;flex-direction:column;gap:4px;padding:9px 11px;border:1px solid var(--border);border-radius:6px;background:var(--bg);min-width:0}
.ss-wl-tile-l .help{margin-left:4px}
.ss-wl-tile--ok{border-color:rgba(16,185,129,.4)}
.ss-wl-tile--warn{border-color:rgba(245,158,11,.45)}
.ss-wl-tile-v{font-size:16px;font-weight:600;color:var(--text);font-variant-numeric:tabular-nums;line-height:1.15;word-break:break-word}
.ss-wl-tile-l{font-size:10px;color:var(--text3);text-transform:uppercase;letter-spacing:.04em}
.ss-wl-q{font-size:10px;font-weight:600;color:#f59e0b}
.ss-wl-line{display:flex;gap:8px;align-items:baseline;font-size:12px;color:var(--text2);margin-top:10px;flex-wrap:wrap}
.ss-wl-block{margin-top:12px}
.ss-wl-blocklabel{display:flex;gap:8px;align-items:baseline;font-size:11px;font-weight:600;color:var(--text2);margin-bottom:6px}
.ss-wl-tablewrap{overflow-x:auto}
.ss-wl-tablewrap .ss-health-table td,.ss-wl-tablewrap .ss-health-table th{padding:5px 16px 5px 0;white-space:nowrap;vertical-align:top}
.ss-wl-tablewrap .ss-health-table td:last-child,.ss-wl-tablewrap .ss-health-table th:last-child{white-space:normal;overflow-wrap:anywhere;padding-right:0}
.ss-wl-events{margin-top:12px}
.ss-wl-events summary{cursor:pointer;color:var(--text3);font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;user-select:none}
.ss-wl-events summary:hover{color:var(--text)}
.ss-wl-events .ss-wl-tablewrap{margin-top:8px}
.ss-wl-empty{font-size:12px;color:var(--text3);padding:6px 0}
.ss-dbd-section{margin-top:18px;padding-top:14px;border-top:1px solid var(--border)}
.ss-dbd-tablewrap{overflow-x:auto}
.ss-dbd-tablewrap .ss-health-table td,.ss-dbd-tablewrap .ss-health-table th{padding:5px 16px 5px 0;white-space:nowrap;vertical-align:top}
.ss-dbd-tablewrap .ss-health-table td:last-child,.ss-dbd-tablewrap .ss-health-table th:last-child{white-space:normal;overflow-wrap:anywhere;padding-right:0}
.ss-dbd-tablewrap .ss-health-table tbody tr:hover td{color:var(--text)}
.ss-dbd-label{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--text3);margin-bottom:8px}
.ss-dbd-hint{font-size:11px;color:var(--text3);margin-bottom:10px;line-height:1.5}
.ss-dbd-empty{font-size:12px;color:var(--text3);padding:4px 0}
.ss-dbd-form{display:flex;gap:8px;flex-wrap:wrap;align-items:center}
.ss-dbd-form select,.ss-dbd-form input{font-size:12px;padding:5px 9px;border:1px solid var(--border);border-radius:4px;background:var(--bg2);color:var(--text);font-family:inherit}
.ss-dbd-form select:focus,.ss-dbd-form input:focus{outline:none;border-color:var(--primary)}
.ss-dbd-form .ss-dbd-query{flex:1;min-width:200px}
.ss-dbd-form .ss-dbd-lang{display:inline-flex;align-items:center;gap:6px;font-size:11px;color:var(--text3);text-transform:uppercase;letter-spacing:.03em}
.ss-dbd-pre{margin-top:12px;padding:12px 14px;border:1px solid var(--border);border-radius:6px;background:var(--bg2);color:var(--text2);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;line-height:1.5;white-space:pre;overflow:auto;max-height:360px;user-select:text;-webkit-user-select:text}
.ss-collections{user-select:none;-webkit-user-select:none}
.ss-col-row{align-items:flex-start}
.ss-col-row:first-child{border-top:none}
.ss-col-name{font-size:13px;font-weight:600;color:var(--text);margin-bottom:1px}
.ss-col-props{display:flex;flex-wrap:wrap;gap:3px 12px;margin-top:4px;font-size:11px;color:var(--text3)}
.ss-col-props b{color:var(--text2);font-weight:600}
.ss-col-status{margin-top:4px;font-size:11px;font-weight:500}
.ss-col-status.idle{color:var(--text3)}
.ss-col-status.busy{color:#f59e0b}
.ss-col-empty{font-size:12px;color:var(--text3);padding:12px 16px}
/* Collections toolbar: server-side search plus the total/match count, above the paged row list. The
   input mirrors the admin form fields; the count is quiet metadata, not a control. */
.ss-col-toolbar{display:flex;align-items:center;gap:12px;flex-wrap:wrap;padding:12px 16px 4px}
.ss-col-search{flex:0 1 320px;min-width:180px;padding:6px 10px;background:var(--bg2);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:12px;font-family:inherit;outline:none;box-sizing:border-box}
.ss-col-search:focus{border-color:var(--primary)}
.ss-col-search::placeholder{color:var(--text3)}
.ss-col-count{font-size:11px;color:var(--text3);white-space:nowrap}
.ss-col-list[aria-busy=true]{opacity:.55;pointer-events:none;transition:opacity .12s}
/* Pager: flat numbered buttons (design-system radius), the current page carried by the filled accent,
   Previous/Next as quiet nav buttons that disable at the ends. */
.ss-col-pager{display:flex;align-items:center;gap:4px;flex-wrap:wrap;padding:10px 16px 14px}
.ss-pg{min-width:28px;height:28px;padding:0 8px;font-size:12px;line-height:1;border:1px solid transparent;border-radius:6px;background:transparent;color:var(--text2);font-family:inherit;font-variant-numeric:tabular-nums;cursor:pointer}
.ss-pg:hover:not(:disabled):not(.active){background:var(--bg2);border-color:var(--border)}
.ss-pg.active{background:var(--primary);border-color:var(--primary);color:var(--bg2);font-weight:600;cursor:default}
.ss-pg:disabled{opacity:.4;cursor:default}
.ss-pg--nav{padding:0 10px}
.ss-pg-gap{color:var(--text3);font-size:12px;padding:0 2px;user-select:none;-webkit-user-select:none}
/* A page/vector total still being aggregated server-side renders as a quiet placeholder, filled in
   place by the poll once ready. */
.ss-tstat-v--pending{color:var(--text3);font-weight:500}
/* Collection settings modal. The dialog is a control surface, not a document: nothing is selectable
   and only interactive rows show the pointer cursor. */
.modal.cs-modal{width:440px;text-align:left;user-select:none;-webkit-user-select:none}
.modal.cs-modal h2{text-align:left}
.cs-sub{font-size:12px;color:var(--text3);margin-bottom:16px;word-break:break-all}
.cs-field{margin-bottom:14px}
.cs-field>label{display:block;font-size:12px;color:var(--text2);margin-bottom:6px}
.cs-toggle-row{display:flex;align-items:center;justify-content:space-between;cursor:pointer;margin-bottom:14px}
.cs-toggle-row>span:first-child{font-size:13px;color:var(--text)}
.cs-field select{width:100%;padding:8px 10px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:13px;font-family:inherit;outline:none;cursor:pointer}
.cs-field input[type=text]{width:100%;padding:8px 10px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:13px;font-family:inherit;outline:none;box-sizing:border-box}
.cs-field input[type=text]:focus{border-color:var(--primary)}
.cs-field select:disabled{opacity:.5;cursor:not-allowed}
.cs-field.disabled{opacity:.5}
.cs-hint{font-size:11px;color:var(--text3);margin:-4px 0 16px}
.modal.ts-modal{width:600px;max-height:88vh;display:flex;flex-direction:column}
.ts-body{flex:1 1 auto;min-height:0;overflow-y:auto;margin:0 -4px;padding:0 4px}
.ts-row{display:flex;gap:16px;padding:2px 0 10px}
.ts-col{flex:1 1 0;min-width:0}
.ts-section{padding:11px 0;border-top:1px solid var(--border)}
.ts-grid{display:grid;grid-template-columns:1fr 1fr;column-gap:18px;row-gap:11px}
.ts-grid-3{grid-template-columns:1fr 1fr 1fr;column-gap:14px}
.ts-grid .ts-check{margin-bottom:0}
.ts-section-head{display:block;font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--text3);font-weight:700;margin:0 0 8px}
.ts-modal select{width:100%;padding:9px 10px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:13px;font-family:inherit;outline:none;cursor:pointer;box-sizing:border-box}
.ts-modal select:focus{border-color:var(--primary)}
/* Standalone selects size to their content (full width is for the two-column model row). */
.ts-modal select#ts-qgate{width:auto;min-width:280px;max-width:100%}
/* Compact inline field: label and control on one line, so the section stays a single block
   and the dialog fits without a vertical scrollbar. */
.ts-inline-field{display:flex;align-items:center;gap:12px;margin-top:12px}
.ts-inline-label{font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--text3);white-space:nowrap}
.ts-check{display:flex;align-items:flex-start;gap:10px;font-size:13px;color:var(--text);margin-bottom:11px;cursor:pointer;line-height:1.35}
.ts-mode-ocr .ts-check{margin-bottom:0}
.ts-sub-check{display:flex;align-items:center;gap:7px;margin:9px 0 0 3px;padding-left:11px;border-left:2px solid var(--border)}
.ts-sub-check .ts-check{margin-bottom:0}
.ts-sub-check.is-disabled{opacity:.45}
.ts-sub-check.is-disabled .ts-check{cursor:default}
.ts-grid-4{grid-template-columns:repeat(4,auto);justify-content:start;column-gap:26px}
.ts-actions-row{display:flex;gap:10px}
.ts-check:last-of-type{margin-bottom:0}
.ts-check>span{display:block}
.ts-hint{font-size:11px;color:var(--text3);margin:8px 0 0;line-height:1.45}
.ts-col .ts-hint{margin-top:6px}
/* Status line under each rebuild button: reports live rebuild state only (empty when idle) inside a
   fixed two-line footprint, clamped so a long failure message can never grow it; state changes
   arriving from the poll swap the text in place without moving anything. */
/* One reserved line: the status never wraps, never clips mid-sentence, and
   never moves its neighbors when it fills or empties. */
.ts-action-status{margin-top:6px;height:16px;line-height:16px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* The modal's live rebuild tile: spaced only while it holds a tile. */
.ts-reindex-live:empty{display:none}
.ts-reindex-live{margin-top:10px}
.ts-modal .modal-actions{flex:0 0 auto;padding-top:14px}
.cs-warn{font-size:12px;color:#ef4444;margin-bottom:12px}
.btn-icon{display:inline-flex;align-items:center;justify-content:center;padding:4px;border:none;border-radius:4px;background:transparent;color:var(--text3);cursor:pointer;transition:color .12s,background .12s}
.btn-icon:hover{color:#ef4444;background:#ef444418}
#mm-catalog{gap:3px}
/* Loaded-models container in the Model Manager. The Manager has three
   sibling groups stacked vertically: the disk-usage strip, the loaded
   models list, and the model catalog. They must share the same right
   edge so the section reads as one coherent column. The catalog is
   anchored by .col-clusters (max-width:900px), so the strip and the
   loaded list are pinned to the same value. */
#mm-loaded{max-width:900px;gap:3px}
#mm-loaded .col-item{max-width:1052px;padding:12px 18px;font-size:13px}
.mm-row-wrap{display:grid;grid-template-columns:1fr 32px;align-items:center;max-width:var(--content-col);gap:4px}
/* Catalog row: two-line grid. Line 1 carries the display name plus
   capability icons, the hardware-fit flag, size, and action button;
   line 2 carries the machine-facing details (model ID, MTEB) in one
   quiet meta line under the name. The vendor label spans both lines on
   the left. This puts the model ID (what API requests actually take in
   their "model" field) one glance under the marketing name instead of
   hiding it behind the API. */
.mm-catalog-item{
  display:grid;
  grid-template-columns:110px minmax(0,1fr) auto 28px 70px 110px;
  grid-template-areas:"vendor name caps fit size action" "vendor meta meta meta meta action";
  align-items:center;gap:3px 16px;padding:10px 20px;
  background:var(--bg2);border-radius:var(--radius);position:relative;transition:background .1s;font-size:13px}
.mm-catalog-item:hover{background:var(--bg3)}
.mm-catalog-item .col-item-name{grid-area:name;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text);font-weight:500}
.mm-catalog-item.mm-local{border-left:3px solid #22c55e}
.mm-vendor{grid-area:vendor;font-size:11px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--text3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.mm-meta{grid-area:meta;font-size:11px;color:var(--text3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
/* Capability icons: one small monochrome glyph per role, each carrying
   its name as a .tip tooltip. Icons instead of words keep the row quiet
   while staying scannable ("what can this model do") at a glance. */
.mm-caps{grid-area:caps;display:inline-flex;justify-content:flex-end;align-items:center;gap:7px;color:var(--text3)}
.cap-ic{width:14px;height:14px;flex-shrink:0}
/* Fit column: a gauge pictogram on EVERY row, colored by verdict
   (green fits, amber tight, red too large) with distinct shapes for
   color-blind users (check circle / gauge / warning triangle). The
   wrapping .tip span carries the per-model explanation (model size vs
   this machine's device memory) as a styled tooltip. */
.mm-fit{grid-area:fit;display:inline-flex;justify-content:flex-end;font-size:11px;font-weight:600;white-space:nowrap}
.mm-fit .cap-ic{width:15px;height:15px}
.mm-size{grid-area:size;font-size:12px;color:var(--text2);text-align:right;font-variant-numeric:tabular-nums}
.mm-action{grid-area:action;display:flex;justify-content:flex-end;gap:6px;align-items:center}
.mm-family-gap{height:10px}
.mm-dl-row .mm-caps,.mm-dl-row .mm-fit,.mm-dl-row .mm-size{display:none}
.mm-dl-row .mm-action{grid-area:auto;grid-row:1;grid-column:3/-1}
/* Custom models: the second lane's rows share the catalog's geometry
   (same width, same two-line shape) with the model's NAME as the
   click-to-copy element, since a custom model's name IS its API id;
   the meta line carries only facts the title does not already state.
   Verdicts are plain text and only speak when something is broken or
   unmeasured: measured-fine stays silent. */
.cm-item{
  display:grid;
  grid-template-columns:minmax(0,1fr) auto 70px auto;
  grid-template-areas:"name flags size action" "meta meta size action";
  align-items:center;gap:3px 16px;padding:10px 20px;
  background:var(--bg2);border-radius:var(--radius);position:relative;transition:background .1s;font-size:13px}
.cm-item{border-left:3px solid #22c55e}
.cm-item:hover{background:var(--bg3)}
.cm-item .cm-name{
  grid-area:name;justify-self:start;max-width:100%;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  font-family:inherit;font-size:13px;font-weight:600;color:var(--text);
  background:none;border:none;padding:0;cursor:pointer;
  text-decoration:underline;text-decoration-style:dotted;text-decoration-color:transparent;text-underline-offset:3px;
  transition:text-decoration-color .12s}
.cm-item .cm-name:hover{text-decoration-color:var(--text3)}
.cm-item .cm-name:focus-visible{outline:2px solid var(--primary);outline-offset:2px;border-radius:2px}
.cm-flags{grid-area:flags;display:inline-flex;justify-content:flex-end;align-items:center;gap:12px;font-size:11px;font-weight:600;white-space:nowrap}
.cm-flag-broken{color:#ef4444}
.cm-flag-unprobed{color:var(--text3);font-weight:400}
.cm-item .mm-meta{grid-area:meta}
.cm-item .mm-size{grid-area:size;align-self:center}
.cm-item .mm-action{grid-area:action}
.cm-item .mm-action .rst{white-space:nowrap}
/* The import form lives in its own modal (the page carries only the
   list and the opener button): the skill-modal primitives do the
   heavy lifting, and the mode switch changes labels and the name
   field's dormancy with STABLE geometry (the note line under the
   name is reserved even when empty, so switching modes never
   reflows). */
.cm-import-open{margin-left:auto}
.cm-import-modal{width:560px;text-align:left}
.cm-import-sub{font-size:13px;color:var(--text2);margin:-4px 0 16px;text-align:left}
.cm-import-modal .cm-mode{align-self:flex-start;margin:0}
/* The shared segmented primitive stretches its buttons; here the
   control shrink-wraps, so each option keeps its own one-line
   footprint instead of splitting a too-small width and wrapping. */
.cm-import-modal .cm-mode button{flex:0 0 auto;white-space:nowrap;padding:8px 18px}
.cm-two{display:grid;grid-template-columns:1fr 1fr;gap:14px}
/* Hardware-fit verdict colors. Static colored text, no chip, no dot:
   green = fits in device memory, amber = nearly fills it, red = far
   exceeds it. Shared by the catalog fit column and the Defaults meta
   lines. */
.fit-ok{color:#22c55e}
.fit-tight{color:#f59e0b}
.fit-over{color:#ef4444}
/* Model ID: the machine-facing identifier requests use. Monospace so it
   reads as a value, dotted underline as the standard quiet click
   affordance (same idiom as .dash-ctx). Rendered as a <button> for
   keyboard access; clicking copies the ID. */
.mm-id{
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:inherit;
  color:var(--text2);background:none;border:none;padding:0;cursor:pointer;
  text-decoration:underline;text-decoration-style:dotted;text-underline-offset:2px;
  text-decoration-color:var(--border-strong);white-space:nowrap;
}
.mm-id:hover{color:var(--text);text-decoration-style:solid}
.row-disabled{opacity:.35;cursor:not-allowed}

/* Alerts */
/* Dashboard alert strip. Card-style row with a 3px colored stripe on
   the left for visual signal (amber for warnings, red for errors) and
   normal-contrast text on the regular panel background so the message
   is easy to read at a glance. Matches the standard dashboard list
   panel width (.alerts-list / .dash-activity / .sch-list all use
   max-width:1080px) so it sits flush with the content below. No pill,
   no tinted-on-tinted color combo. Whole row is clickable and routes
   to the Alerts page. */
.alert-strip{
  display:flex;align-items:center;gap:12px;
  padding:10px 14px 10px 12px;
  background:var(--bg2);
  border:1px solid var(--border);
  border-left:3px solid #f59e0b;
  border-radius:var(--radius);
  margin-bottom:16px;
  font-size:13px;
  color:var(--text);
  max-width:1080px;
  cursor:pointer;
  user-select:none;-webkit-user-select:none;
  transition:background .12s,border-color .12s;
}
.alert-strip:hover{background:var(--bg3);border-color:var(--border-strong)}
.alert-strip.error{border-left-color:#ef4444}
.alert-strip-icon{font-size:14px;flex-shrink:0;color:#f59e0b}
.alert-strip.error .alert-strip-icon{color:#ef4444}
.alert-strip-text{flex:1;line-height:1.4}
/* Alert-strip count: quiet right-side metadata in normal-case text.
   No pill, no uppercase, no chip background - just a smaller, dimmer
   readout of "how many alerts in total". */
.alert-strip-count{flex-shrink:0;font-size:11px;color:var(--text3);white-space:nowrap}
/* Unexpected-restart banner. Same card language as .alert-strip (3px
   red stripe, panel background) but two text rows: a bold title line
   with the quiet timestamp, then the cause summary in secondary text.
   Actions sit on the right: a real "View report" button (.ctrl-btn)
   plus the dismiss cross. The whole strip opens the report dialog; the
   cross acknowledges server-side, so the dismissal is shared by every
   admin browser. Static colors only. */
.crash-strip{
  display:flex;align-items:flex-start;gap:12px;
  padding:12px 16px 12px 13px;
  background:var(--bg2);
  border:1px solid var(--border);
  border-left:3px solid #ef4444;
  border-radius:var(--radius);
  margin-bottom:16px;
  max-width:var(--dash-width);
  cursor:pointer;
  user-select:none;-webkit-user-select:none;
  transition:background .12s,border-color .12s;
}
.crash-strip:hover{background:var(--bg3);border-color:var(--border-strong)}
.crash-strip-icon{font-size:14px;flex-shrink:0;color:#ef4444;line-height:1.5}
.crash-strip-main{flex:1;min-width:0;overflow:hidden}
.crash-strip-title{font-size:13px;font-weight:600;color:var(--text);line-height:1.5;overflow-wrap:anywhere}
.crash-strip-time{font-weight:400;font-size:11px;color:var(--text3);margin-left:8px}
/* The detail line carries machine output (mangled symbols, paths, hex offsets) with no
   spaces to break on. Break anywhere AND clamp to three lines with a hard clip, so the
   strip can never grow or bleed past its box; the full text lives in the report modal. */
.crash-strip-detail{
  font-size:12.5px;color:var(--text2);line-height:1.5;margin-top:2px;
  min-width:0;overflow-wrap:anywhere;word-break:break-word;
  display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3;overflow:hidden;
}
.crash-strip-actions{display:flex;align-items:center;gap:8px;flex-shrink:0;margin-top:2px}
.alert-strip-dismiss{
  flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;
  width:22px;height:22px;border:none;border-radius:4px;background:transparent;
  color:var(--text3);font-size:13px;line-height:1;cursor:pointer;
  transition:color .12s,background .12s;
}
.alert-strip-dismiss:hover{color:var(--text);background:var(--bg3)}
/* Restart-report dialog: left-aligned document-style modal. Facts grid
   on top, recommendation callout, then the raw evidence lines in a
   monospace block (selectable, unlike control-surface modals, because
   operators copy these lines into tickets). */
.modal.crash-modal{width:680px;max-width:92vw;text-align:left;max-height:88vh;display:flex;flex-direction:column}
.modal.crash-modal h2{text-align:left}
.crash-modal-body{flex:1 1 auto;min-height:0;overflow-y:auto;user-select:text;-webkit-user-select:text}
/* minmax(0,1fr) lets the value column shrink below its content's intrinsic width; a plain
   1fr track keeps min-width:auto and an unbreakable token would push the grid wider. */
.crash-fact-grid{display:grid;grid-template-columns:140px minmax(0,1fr);gap:6px 14px;font-size:13px;margin-bottom:14px}
.crash-fact-label{color:var(--text3)}
.crash-fact-value{color:var(--text);min-width:0;overflow-wrap:anywhere;word-break:break-word}
.crash-reco{font-size:13px;color:var(--text);background:var(--bg2);border:1px solid var(--border);border-left:3px solid var(--data-blue);border-radius:var(--radius);padding:10px 12px;margin-bottom:14px;line-height:1.5;overflow-wrap:anywhere}
.crash-evidence-head{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--text3);font-weight:700;margin:0 0 6px}
.crash-evidence{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11.5px;line-height:1.55;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);padding:10px 12px;white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word;overflow-x:auto;color:var(--text2);margin:0}
/* Radical safety net: every element inside the restart banner and the report modal is
   forced to allow shrinking (min-width:0) and to break long unbreakable tokens
   (overflow-wrap:anywhere), regardless of markup added later. Action buttons keep their
   width via their own flex-shrink:0, so only the text containers ever give. This is the
   guarantee that machine output can never bleed past the rendering area again. */
#dash-crash-banner, #dash-crash-banner *,
.crash-modal-body, .crash-modal-body *{min-width:0;overflow-wrap:anywhere}

/* Memory diagnostics modal */
/* Fixed height (not content-driven) so the modal opens at its final size on
   the loading frame and the body scrolls internally: no layout jump when the
   diagnostics payload arrives. */
.modal.mem-modal{width:760px;max-width:94vw;text-align:left;height:min(80vh,760px);display:flex;flex-direction:column}
.modal.mem-modal h2{text-align:left}
.modal.mem-modal>.cs-sub{margin-bottom:14px}
.mem-modal-body{flex:1 1 auto;min-height:0;overflow-y:auto;user-select:text;-webkit-user-select:text;padding-right:4px}
.mem-modal-body, .mem-modal-body *{min-width:0;overflow-wrap:anywhere}
.mem-modal-actions{align-items:center;flex-wrap:wrap}
.mem-auto{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--text2);cursor:pointer;user-select:none}
.mem-auto input{margin:0}
.mem-actions-spacer{flex:1 1 auto}

.verdict{border:1px solid var(--border);border-left-width:3px;border-radius:6px;padding:12px 14px;margin-bottom:16px;background:var(--bg)}
.verdict.ok{border-left-color:#10b981}
.verdict.info{border-left-color:#3b82f6}
.verdict.warn{border-left-color:#f59e0b}
.verdict.critical{border-left-color:#ef4444}
.verdict-head{font-size:13px;font-weight:600;color:var(--text);margin-bottom:6px}
.verdict.warn .verdict-head{color:#f59e0b}
.verdict.critical .verdict-head{color:#ef4444}
.verdict ul{margin:6px 0 0;padding-left:18px}
.verdict li{font-size:12px;color:var(--text2);line-height:1.5;margin-bottom:3px}
.verdict .verdict-suggest li{color:var(--text)}
.verdict .verdict-suggest{margin-top:8px}
.verdict p{margin:0;font-size:12px;color:var(--text2);line-height:1.55}
.verdict p + p{margin-top:7px}
.verdict ol{margin:8px 0 0;padding-left:18px}
.verdict ol li{font-size:12px;color:var(--text2);line-height:1.55;margin-bottom:6px}
.verdict code{font-family:var(--mono,monospace);font-size:11.5px;color:var(--text);background:var(--bg2);border:1px solid var(--border);border-radius:4px;padding:1px 5px}
/* The check's own row keeps its rhythm whether or not a result is in it. */
.verdict-actions{display:flex;align-items:center;gap:12px;margin-top:12px}
.verdict-result{font-size:12px;color:var(--text2);line-height:1.5}
.verdict-result.ok{color:#10b981}
.verdict-result.bad{color:#f59e0b}

.mem-bar{display:flex;height:18px;border-radius:6px;overflow:hidden;border:1px solid var(--border);background:var(--bg)}
.mem-bar-seg{height:100%;min-width:2px}
.mem-bar-seg.managed{background:#3b82f6}
.mem-bar-seg.native{background:#a855f7}
.mem-bar-headline{display:flex;align-items:baseline;gap:10px;margin-bottom:8px}
.mem-bar-headline .mem-rss{font-size:22px;font-weight:700;color:var(--text);font-variant-numeric:tabular-nums}
.mem-bar-headline .mem-rss-label{font-size:12px;color:var(--text3)}
.mem-legend{display:flex;flex-wrap:wrap;gap:14px;margin-top:8px}
.mem-legend span{display:inline-flex;align-items:center;gap:6px;font-size:11px;color:var(--text2)}
.mem-legend i{width:9px;height:9px;border-radius:2px;display:inline-block;font-style:normal}
.mem-legend i.managed{background:#3b82f6}
.mem-legend i.native{background:#a855f7}

.mem-section{border:1px solid var(--border);border-radius:6px;padding:12px 14px;margin-bottom:12px;background:var(--bg)}
.mem-section>h3{font-size:12px;font-weight:600;color:var(--text);margin:0 0 2px;display:flex;align-items:center;gap:8px}
.mem-section>h3 .mem-tag{font-size:10px;font-weight:600;color:var(--text3);border:1px solid var(--border);border-radius:4px;padding:1px 6px}
.mem-section>.mem-note{font-size:11px;color:var(--text3);line-height:1.45;margin:2px 0 10px}
.mem-grid{display:grid;grid-template-columns:1fr 1fr;gap:2px 24px}
.mem-grid.one{grid-template-columns:1fr}
.mem-kv{display:flex;justify-content:space-between;align-items:baseline;gap:12px;padding:4px 0;border-bottom:1px solid var(--border)}
.mem-kv:last-child{border-bottom:none}
.mem-k{font-size:12px;color:var(--text2)}
.mem-k .help{vertical-align:middle}
.mem-v{font-size:12px;color:var(--text);font-weight:600;font-variant-numeric:tabular-nums;white-space:nowrap}
.mem-v.muted{color:var(--text3);font-weight:500}
.mem-v.warn{color:#f59e0b}
.mem-v.crit{color:#ef4444}

.mem-gen{display:grid;grid-template-columns:auto 1fr auto;gap:8px 10px;align-items:center;margin-top:4px}
.mem-gen-name{font-size:11px;color:var(--text2);font-weight:600}
.mem-gen-track{height:8px;border-radius:4px;background:var(--bg2);border:1px solid var(--border);overflow:hidden}
.mem-gen-fill{height:100%;background:#3b82f6}
.mem-gen-val{font-size:11px;color:var(--text);font-variant-numeric:tabular-nums;white-space:nowrap}

.mem-models{margin-top:6px}
.mem-model-row{display:flex;justify-content:space-between;gap:12px;padding:6px 0;border-bottom:1px solid var(--border);font-size:12px}
.mem-model-row:last-child{border-bottom:none}
.mem-model-name{color:var(--text);font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.mem-model-meta{color:var(--text3);font-size:11px}
.mem-model-bytes{color:var(--text2);font-variant-numeric:tabular-nums;white-space:nowrap;text-align:right}
.mem-empty{font-size:12px;color:var(--text3);padding:4px 0}

/* LM-Kit cache inventory: one row per category LM-Kit holds in memory, with a
   static left-edge lane (green = reclaimable, grey = pinned/empty) and a plain
   colored status word. No badges, no pills, no animated dots: the lane and the
   word carry the state. */
.mem-cache-list{margin-top:2px}
/* Fixed amount/status tracks (not auto) so every row's columns land in the same
   lane: each row is its own grid, and auto tracks would size per-row and drift. */
.mem-cache-row{display:grid;grid-template-columns:minmax(0,1fr) 104px 88px;align-items:start;gap:4px 16px;padding:9px 0 9px 10px;border-bottom:1px solid var(--border);border-left:2px solid transparent}
.mem-cache-row:last-child{border-bottom:none}
.mem-cache-row.reclaim{border-left-color:#10b981}
.mem-cache-row.pinned{border-left-color:var(--border-strong)}
.mem-cache-row.empty{border-left-color:var(--border)}
.mem-cache-main{min-width:0}
.mem-cache-name{font-size:12px;font-weight:600;color:var(--text)}
.mem-cache-desc{font-size:11px;color:var(--text3);line-height:1.4;margin-top:2px}
.mem-cache-amt{display:flex;flex-direction:column;align-items:flex-end;gap:1px;font-size:12px;color:var(--text);font-weight:600;font-variant-numeric:tabular-nums;white-space:nowrap;text-align:right}
.mem-cache-size{font-size:11px;font-weight:500;color:var(--text3)}
.mem-cache-status{font-size:11px;font-weight:600;white-space:nowrap;text-align:right}
.mem-cache-status.reclaim{color:#10b981}
.mem-cache-status.pinned,.mem-cache-status.empty{color:var(--text3)}
.mem-cache-sum{font-size:11px;color:var(--text3);line-height:1.45;margin-top:10px}
.mem-cache-sum.has-reclaim{color:var(--text2)}
/* Restart history (Logs page). Collapsible strip between the toolbar
   and the live log stream: one row per server start, with the
   diagnosed cause of the stop that preceded it. Crash rows open the
   same report dialog the dashboard banner uses. Collapsed by default
   so the log stream stays the page's main surface. */
.restart-history{margin:0 var(--density-pad) 12px;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg2);flex-shrink:0}
.restart-history-toggle{display:flex;align-items:center;gap:10px;width:100%;padding:10px 14px;background:transparent;border:none;color:var(--text);font-size:12.5px;font-weight:600;cursor:pointer;font-family:inherit;text-align:left}
.restart-history-toggle:hover{color:var(--text)}
.rh-chevron{width:12px;height:12px;flex-shrink:0;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;transition:transform .12s}
.restart-history-toggle[aria-expanded="true"] .rh-chevron{transform:rotate(90deg)}
.rh-count{color:var(--text3);font-weight:400;font-size:11px;white-space:nowrap}
.rh-hint{margin-left:auto;color:var(--text3);font-weight:400;font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.restart-history-body{border-top:1px solid var(--border);max-height:280px;overflow-y:auto}
.restart-history-body .hint{display:block;padding:12px 14px}
.restart-row{display:grid;grid-template-columns:auto 110px 1fr auto;align-items:center;gap:14px;padding:9px 14px;border-bottom:1px solid var(--border);font-size:12px}
.restart-row:last-child{border-bottom:none}
.restart-row.has-report{cursor:pointer;transition:background .1s}
.restart-row.has-report:hover{background:var(--bg3)}
.restart-kind{font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;white-space:nowrap}
.restart-row.kind-crash .restart-kind{color:#ef4444}
.restart-row.kind-admin .restart-kind{color:var(--data-blue)}
.restart-row.kind-clean .restart-kind{color:var(--text3)}
.restart-summary{color:var(--text2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.restart-view{font-size:11px;color:var(--text3);white-space:nowrap}

/* Config actions */
.config-actions{display:flex;gap:8px;margin-top:8px}
.btn-import-label{cursor:pointer;display:inline-flex;align-items:center}

/* Model Manager disk-usage indicator. Single compact horizontal strip
   that surfaces three pieces of info at-a-glance: total bytes on disk,
   file count, and the resolved storage directory. Filesystem scan is
   the authoritative on-disk size for the model storage directory (NOT
   the sum of catalog metadata, which would miss out-of-catalog
   downloads and stale cached copies). */
/* Width matches the .col-clusters lists rendered directly below
   (loaded models, catalog) so the disk strip lines up with the
   content it summarizes instead of stretching past it. */
.mm-disk-strip{display:flex;align-items:center;gap:18px;padding:10px 16px;background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius);margin-bottom:20px;max-width:var(--content-col);min-width:0}
/* Stats hold their content size and never shrink, so a tight strip narrows only the directory path
   (which ellipsises) rather than collapsing a stat and letting its value overflow onto its neighbour. */
.mm-disk-stat{display:inline-flex;align-items:baseline;gap:8px;flex:0 0 auto}
.mm-disk-stat-label{font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--text3);white-space:nowrap}
.mm-disk-stat-value{font-size:14px;font-weight:600;color:var(--text);font-variant-numeric:tabular-nums;line-height:1.2;white-space:nowrap}
.mm-disk-stat-path{flex:1 1 auto;min-width:0}
.mm-disk-stat-path .mm-disk-stat-value{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-weight:500;font-size:13px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
.mm-disk-sep{width:1px;align-self:stretch;background:var(--border);margin:-2px 0}
/* Flexible cell that absorbs the strip's free space so the action buttons stay pinned right
   whether or not the strip carries a directory path. It is ALWAYS present and only its text
   changes, so a scan warning appearing or clearing never moves a button. */
.mm-disk-warn{flex:1 1 auto;min-width:0;font-size:12px;font-weight:600;color:#f59e0b;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* A strip that still shows a directory path already has its flexible spacer, so there the
   warning cell only takes the room its text needs and collapses entirely when silent. */
.mm-disk-stat-path ~ .mm-disk-warn{flex:0 1 auto}
.mm-disk-stat-path ~ .mm-disk-warn:empty{display:none}
.mm-disk-refresh{flex-shrink:0;font-size:11px;font-weight:600;padding:5px 12px;background:transparent;border:1px solid var(--border);border-radius:4px;color:var(--text2);cursor:pointer;font-family:inherit;transition:color .12s,border-color .12s}
.mm-disk-refresh:hover{color:var(--primary);border-color:var(--primary)}
.mm-disk-delete-all{flex-shrink:0;font-size:11px;font-weight:600;padding:5px 12px;background:transparent;border:1px solid var(--border);border-radius:4px;color:var(--text2);cursor:pointer;font-family:inherit;transition:color .12s,border-color .12s,background-color .12s}
.mm-disk-delete-all:not(:disabled):hover{color:#ef4444;border-color:rgba(239,68,68,.55);background:rgba(239,68,68,.08)}
.mm-disk-delete-all:disabled{opacity:.45;cursor:not-allowed}
@media(max-width:760px){.mm-disk-strip{flex-wrap:wrap;gap:10px 16px}.mm-disk-stat-path{flex-basis:100%}.mm-disk-sep{display:none}}

/* Upload file browser: a compact table of stored files with a per-file download, revealed by the
   "Browse" button on the upload storage strip. Corners stay at the design-system radius (no pills). */
.upl-file-browser{max-width:900px;margin:-12px 0 20px;border:1px solid var(--border);border-radius:var(--radius);overflow:hidden}
.upl-file-table{width:100%;border-collapse:collapse;font-size:13px}
.upl-file-table th{text-align:left;font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--text3);padding:8px 12px;background:var(--bg2);border-bottom:1px solid var(--border);white-space:nowrap}
.upl-file-table td{padding:8px 12px;border-bottom:1px solid var(--border);color:var(--text2);vertical-align:middle}
.upl-file-table tr:last-child td{border-bottom:none}
.upl-file-name{font-weight:500;color:var(--text);word-break:break-word}
.upl-file-bucket{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:var(--text3)}
.upl-file-dl{font-size:11px;font-weight:600;padding:4px 10px;background:transparent;border:1px solid var(--border);border-radius:4px;color:var(--text2);cursor:pointer;font-family:inherit;transition:color .12s,border-color .12s}
.upl-file-dl:hover{color:var(--primary);border-color:var(--primary)}
.upl-file-empty{padding:14px 16px;color:var(--text3);font-size:13px}

/* Model Manager subsections (Storage / Loaded / Catalog) live inside the
   unified Models config page; styles below apply wherever the markup is
   rendered. */

/* The Models page stacks five subsections (Defaults, Behavior, Loaded
   models, Catalog, Storage) on one config page. Inherited margins are
   tuned for one block per page, so the merged page needs extra breathing
   room above each subsection heading and visual rhythm between the two
   Defaults sub-groups. */
:where(#models) .dash-heading{margin-top:44px}
/* Catalog header: the trailing count is a textual continuation of the
   heading, not a control cluster, so baseline-align the two and tighten the
   gap to 8px. With the leading "·" separator carrying margin-right:8px that
   yields symmetric 8px spacing on both sides of the dot. */
#models .dash-heading-row{margin-top:44px;align-items:baseline;gap:8px}
#models .dash-heading:first-of-type,
#models .dash-heading-row:first-of-type{margin-top:8px}
:where(#inference) .dash-heading,:where(#fileManagement) .dash-heading{margin-top:44px}

/* Inference carries the most settings of any section, and after the slot-pool
   fields merged into the same grid two columns pushed the block past a
   screenful. Three keeps it to three rows. .field.wide already spans 1/-1, so
   the full-width rows follow without a second rule. Collapses back to two
   columns where a third would squeeze the numeric inputs. */
#inference .grid{grid-template-columns:1fr 1fr 1fr;max-width:var(--content-col)}
@media (max-width:1100px){#inference .grid{grid-template-columns:1fr 1fr;max-width:620px}}
#models .mm-disk-strip{margin-bottom:0}
#models .grid{margin-bottom:0}

/* The Defaults container is wider than the canonical 620px config grid
   because catalog option labels run long ("LightOn LightOnOCR 2 1B
   (659 MB)", "Microsoft Phi-4 Reasoning Plus 14.7B (8.5 GB)", plus the
   MTEB suffix on embedding models). At 960px the two columns get
   ~450px each, which fits every catalog name without truncating the
   selected option text. */
#models .defaults-group{margin-top:14px;width:100%;max-width:var(--content-col)}
#models .defaults-actions{margin-top:22px}
/* Storage: the directory field and the strip that measures that folder share one
   right edge, and the field owns the space down to the strip (an id-scoped rule
   because both sections override plain .grid geometry). */
#models .storage-grid,#inference .storage-grid{max-width:900px;margin-bottom:20px}

/* Uniform dropdown widths across every Defaults row. Native <select>
   elements size themselves to their widest option label, so a purely
   flex-based .f-row renders different widths per role: a catalog of
   long vendor names pushes that column wide while a sparser catalog
   shrinks. Switching the row to CSS grid with minmax(0, 1fr) on the
   select cell pins every column to identical width regardless of
   intrinsic content. The grid also stretches the inner .grid to fill
   the 800px container so all six defaults occupy a single 3x2 grid. */
#models .defaults-group .grid{width:100%;max-width:none}
#models .defaults-group .field .f-row{display:grid;grid-template-columns:minmax(0,1fr) auto auto;align-items:center;gap:0}
#models .defaults-group .field .f-row > select{width:100%;min-width:0}

/* Selected-model meta line under each Defaults selector: the model ID
   (click to copy) plus a hardware-fit flag when the selection would
   strain this machine. Height is reserved (min-height = one 17px line)
   so the line populating after the catalog fetch, or changing on
   selection, never shifts the grid below. */
#models .model-meta{
  min-height:17px;font-size:11px;line-height:17px;color:var(--text3);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;
}

/* ── Custom model dropdown (msel) ──
   Native <select> options cannot be styled, which crammed every fact
   (disk marker, size, MTEB, fit) into one run of text per row. The
   native select stays in the DOM as the form-state holder: config
   dirty-tracking, reset, and save all keep reading and writing it. The
   msel renders the same options as aligned columns (name left; MTEB,
   fit flag, and size right) grouped under vendor headers, and proxies
   selection back to the native select via input + change events.
   The .msel-hidden class swaps the native select out only once the
   custom control exists, so the row never renders empty. */
.msel-hidden{display:none !important}
.msel{position:relative;min-width:0}
.msel-btn{
  display:grid;grid-template-columns:minmax(0,1fr) auto 14px;align-items:center;gap:8px;
  width:100%;padding:8px 10px;background:var(--bg);
  border:1px solid var(--border);border-radius:6px;
  color:var(--text);font-size:13px;font-family:inherit;text-align:left;
  cursor:pointer;outline:none;transition:border-color .15s;
}
.msel-btn:hover{border-color:var(--border-strong)}
.msel-btn:focus-visible{border-color:var(--primary);box-shadow:0 0 0 2px color-mix(in srgb, var(--text) 16%, transparent)}
/* Dirty state mirrors the hidden native select's .dirty class (set by
   config dirty-tracking) onto the visible trigger. */
select.dirty + .msel .msel-btn{border-color:var(--primary);box-shadow:inset 2px 0 0 var(--primary),0 0 0 1px color-mix(in srgb, var(--text) 16%, transparent)}
.msel-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.msel-size{font-size:11px;color:var(--text3);font-variant-numeric:tabular-nums;white-space:nowrap}
.msel-caret{width:14px;height:14px;color:var(--text3);flex-shrink:0;transition:transform .12s}
.msel.open .msel-caret{transform:rotate(180deg)}
.msel-pop{
  position:absolute;top:calc(100% + 4px);left:0;right:0;z-index:80;
  background:var(--bg2);border:1px solid var(--border);border-radius:6px;
  box-shadow:var(--shadow-md);max-height:320px;overflow:auto;overscroll-behavior:contain;
  padding:4px;
}
.msel-group{font-size:10px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--text3);padding:8px 10px 4px}
.msel-opt{
  display:grid;grid-template-columns:14px minmax(0,1fr) auto auto 64px;align-items:center;gap:8px;
  width:100%;padding:7px 10px;background:none;border:none;border-radius:4px;
  color:var(--text);font-size:13px;font-family:inherit;text-align:left;cursor:pointer;
}
.msel-opt:hover,.msel-opt:focus-visible{background:var(--bg3);outline:none}
.msel-opt[aria-selected="true"]{background:var(--bg3);font-weight:500}
.msel-mark{display:inline-flex;color:var(--text3)}
.msel-mark svg{width:14px;height:14px}
.msel-opt-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.msel-mteb{font-size:11px;color:var(--text3);font-variant-numeric:tabular-nums;white-space:nowrap}
.msel-flag{font-size:11px;font-weight:600;white-space:nowrap}
.msel-opt-size{font-size:11px;color:var(--text3);text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}

.mm-filter-row{display:flex;align-items:center;gap:12px;margin-bottom:14px;max-width:var(--content-col)}
.mm-filter-row select{padding:8px 12px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:13px;font-family:inherit;outline:none}
.mm-filter-row #mm-cat-search{flex:1;max-width:340px;padding:8px 12px;font-size:13px}
.mm-local-toggle{display:inline-flex;align-items:center;gap:6px;font-size:13px;color:var(--text2);cursor:pointer;user-select:none;-webkit-user-select:none}
.mm-local-toggle input{margin:0;cursor:pointer}
.mm-local-toggle:hover{color:var(--text)}
.mm-result-count{font-size:12px;color:var(--text3);font-variant-numeric:tabular-nums;white-space:nowrap}
.mm-result-count::before{content:"·";margin-right:8px;color:var(--text3);opacity:.5}
.mm-result-count:empty{display:none}
.mm-dl-status{display:flex;align-items:center;gap:12px;padding:12px 16px;background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius);max-width:var(--content-col);margin-bottom:16px}
.mm-dl-status span{font-size:12px;color:var(--text2);white-space:nowrap}
.btn-download{padding:5px 12px;border:1px solid var(--border);border-radius:4px;background:transparent;color:var(--text2);font-size:12px;cursor:pointer;font-family:inherit;transition:background .12s,border-color .12s,color .12s}
.btn-download:hover{background:#3b82f622}
.btn-download:disabled{opacity:.4;cursor:not-allowed}
.btn-load{padding:5px 12px;border:1px solid #22c55e;border-radius:4px;background:transparent;color:#22c55e;font-size:12px;cursor:pointer;font-family:inherit;transition:background .12s}
.btn-load:hover{background:#22c55e22}
.mm-dl-row{position:relative;overflow:hidden}
.mm-dl-row .mm-dl-bg{position:absolute;top:0;left:0;bottom:0;background:linear-gradient(90deg,#3b82f633,#3b82f618);transition:width .4s ease}
.mm-dl-row>*{position:relative;z-index:1}
.mm-dl-pct{font-size:13px;font-weight:600;color:var(--data-blue);min-width:44px;text-align:right;margin-right:8px}
.mm-dl-size{font-size:12px;color:var(--text3);margin-right:12px}
.btn-unload{padding:5px 12px;border:1px solid #f59e0b44;border-radius:4px;background:transparent;color:#f59e0b;font-size:12px;cursor:pointer;font-family:inherit;transition:background .12s}
.btn-unload:hover{background:#f59e0b22}
.mm-local-badge{font-size:10px;color:#22c55e;margin-left:6px}
.cluster-form-row{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:12px}
.cluster-form-field{display:flex;flex-direction:column;gap:4px}
/* Reserved one-line footprint: the note explains a disabled option (e.g. why the Matched vector
   store is unavailable) and must appear without moving the form, so the line exists either way. */
.cluster-form-note{font-size:11px;color:var(--text3);min-height:15px;line-height:15px}
/* The role primer between the connection fields: teaches which login is created for the operator
   and which one must already exist, so the two password fields cannot be confused. */
.cluster-form-intro{font-size:12px;line-height:1.6;color:var(--text2);margin:2px 0 12px}
.cluster-form-intro code{font-family:ui-monospace,Consolas,monospace;font-size:11px;color:var(--text)}
.cluster-form-field label{font-size:11px;font-weight:500;color:var(--text2)}
.cluster-form-field input,.cluster-form-field select{padding:7px 10px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:13px;font-family:inherit;outline:none}
.cluster-form-field input:focus,.cluster-form-field select:focus{border-color:#555}

/* Logs panel: fixed overlay that fills the content area */
.logs-panel{position:fixed;top:var(--appbar);left:var(--sidebar);right:0;bottom:32px;z-index:20;background:var(--bg);display:flex;flex-direction:column}
/* Panel header shared by the Logs and Requests overlays. At wide
   viewports the title sits on the left and the toolbar (count badge +
   Refresh + Clear, or the full Logs filter strip) hugs the right.
   When the viewport narrows past the point where both fit on the same
   line, the WHOLE toolbar wraps below the title instead of letting its
   individual buttons stack vertically (which is what produced the
   "Refresh on top of Clear" pile). The title block has flex-basis 320
   so the description keeps a readable column width before forcing the
   wrap; the toolbar stays as one nowrap unit so its buttons line up
   horizontally on whichever row they end up on. */
.logs-panel-header{
  flex-shrink:0;padding:36px 48px 14px;
  display:flex;align-items:flex-start;
  justify-content:space-between;
  gap:16px 24px;
  flex-wrap:wrap;
}
.logs-panel-header > :first-child{flex:1 1 320px;min-width:0}
.logs-panel-header > .logs-toolbar{flex:0 0 auto}
/* Compact action toolbar (count + a couple of buttons) on the Requests
   panel header. Reduce the gap so it fits the rightmost slot before the
   wrap kicks in, and keep its children on one row even when the
   parent flex-wrap puts it on its own line. */
.logs-panel-header > .logs-toolbar:not(:has(.logs-group)){
  gap:8px;flex-wrap:nowrap;
}
/* The Requests panel runs a tighter vertical budget than Logs: every
   saved pixel above the audit table is one more visible row, so the
   header and the heading row drop the generous gutters. */
#requests-panel .logs-panel-header{padding:22px 48px 8px}
#requests-panel .dash-heading-row{margin-top:8px;margin-bottom:6px;max-width:none}
#dashboard .dash-heading-row{max-width:var(--dash-width)}
/* Logs panel toolbar: lay it out on two lines. The level + source +
   search filters (and Reset) stay on the first line; the action cluster
   (Pause / Refresh / Load full file / Download file / Export view /
   Clear) breaks onto a second line, set off by a hairline rule. Scoped
   to #logs-panel so the Requests panel toolbar (same .logs-toolbar
   class, different id) is left alone. The toolbar spans the full header
   width here so the forced 100% break resolves against a real width. */
#logs-panel .logs-panel-header > .logs-toolbar{flex:1 1 100%}
#logs-panel .logs-toolbar .logs-actions{
  flex-basis:100%;
  margin-bottom:2px;
  padding-bottom:12px;
  border-bottom:1px solid var(--border);
}
.req-inspector{position:absolute;top:0;right:0;bottom:0;width:420px;max-width:90vw;background:var(--bg2);border-left:1px solid var(--border);display:flex;flex-direction:column;z-index:25;box-shadow:var(--shadow-drawer);animation:slidein .18s ease}
.req-inspector[hidden]{display:none}
@keyframes slidein{from{transform:translateX(20px);opacity:0}to{transform:translateX(0);opacity:1}}
.req-inspector-head{display:flex;align-items:center;gap:8px;padding:20px 24px 12px;border-bottom:1px solid var(--border)}
.req-inspector-head h2{font-size:14px;font-weight:600;color:var(--text);margin:0 auto 0 0;letter-spacing:.04em;text-transform:uppercase}
.req-inspector-nav{display:inline-flex;gap:4px}
.req-inspector-body{flex:1;overflow:auto;padding:20px 24px;font-size:13px}
.req-inspector-row{display:grid;grid-template-columns:110px 1fr;gap:12px;padding:8px 0;border-bottom:1px solid var(--border)}
.req-inspector-row:last-child{border-bottom:none}
.req-inspector-row dt{color:var(--text3);font-size:11px;text-transform:uppercase;letter-spacing:.08em;padding-top:2px}
.req-inspector-row dd{margin:0;color:var(--text);word-break:break-all;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}
.req-inspector-curl{margin-top:18px;padding-top:14px;border-top:1px solid var(--border)}
.req-inspector-curl pre{background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:10px 12px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11.5px;color:var(--text);overflow:auto;margin:6px 0 0}
.req-inspector-curl button{margin-top:8px}
.dash-requests tbody tr{cursor:pointer;user-select:none;-webkit-user-select:none;transition:background .08s ease}
.dash-requests tbody tr:hover td{background:var(--hover-wash)}
/* Selected row: a solid left accent bar plus a stronger fill that beats the
   hover tint, so the selection is unmistakable and never reads as "moved"
   when the pointer hovers a different row. The selected look is kept even
   while the selected row itself is hovered. */
.dash-requests tbody tr.req-selected td{background:color-mix(in srgb, var(--text) 14%, transparent)}
.dash-requests tbody tr.req-selected:hover td{background:color-mix(in srgb, var(--text) 16%, transparent)}
.dash-requests tbody tr.req-selected td:first-child{box-shadow:inset 3px 0 0 var(--primary)}
html.theme-light .dash-requests tbody tr:hover td{background:var(--hover-wash)}
html.theme-light .dash-requests tbody tr.req-selected td{background:color-mix(in srgb, var(--text) 16%, transparent)}
html.theme-light .dash-requests tbody tr.req-selected:hover td{background:color-mix(in srgb, var(--text) 18%, transparent)}
/* "Live updates paused" pill next to the row count. Static (no pulsing). */
/* Floating status note ("Paused while the pointer is over the list",
   "Auto-refresh off"). Anchored to the panel's bottom-right corner as an
   overlay so showing or hiding it NEVER reflows the surrounding layout;
   an inline copy in the toolbar shifted everything under the cursor each
   time the pointer crossed the table. */
.req-live-state{
  position:absolute;right:64px;bottom:18px;z-index:26;
  font-size:11px;color:var(--text2);background:var(--bg3);
  border:1px solid var(--border-strong);border-radius:var(--radius);
  padding:3px 10px;white-space:nowrap;pointer-events:none;
  box-shadow:var(--shadow-md);
}
.req-live-state[hidden]{display:none}
.log-live-state{font-size:11px;color:var(--text2);align-self:center;margin-left:2px;white-space:nowrap}
.log-live-state[hidden]{display:none}

/* Cell-level treatments: dim placeholder dashes, bytes/duration
   alignment, and the Status pill-less treatment. The 2xx green, 4xx
   amber, 5xx red treatment lives on .status-* span colors; we add a
   subtle background tint to the entire Status cell so the most
   important column reads at a glance without resorting to a badge.*/
.req-table-requests td:empty::after,
.req-table-requests td.req-cell-empty::after{content:'-';color:var(--text3);opacity:.5}
.req-table-requests td.col-num.status-2xx{box-shadow:inset 0 0 0 9999px rgba(22,163,74,0.06)}
.req-table-requests td.col-num.status-4xx{box-shadow:inset 0 0 0 9999px rgba(217,119,6,0.08)}
.req-table-requests td.col-num.status-5xx{box-shadow:inset 0 0 0 9999px rgba(220,38,38,0.10)}

/* Duration buckets: green fast, amber slow, red very slow. Neutral
   bucket has no extra color so the normal-case rows stay clean. */
.req-table-requests td.col-num.req-dur-fast{color:#16a34a}
.req-table-requests td.col-num.req-dur-slow{color:#d97706;font-weight:600}
.req-table-requests td.col-num.req-dur-vslow{color:#dc2626;font-weight:600}
html.theme-light .req-table-requests td.col-num.req-dur-fast{color:#15803d}
html.theme-light .req-table-requests td.col-num.req-dur-slow{color:#b45309}
html.theme-light .req-table-requests td.col-num.req-dur-vslow{color:#b91c1c}

/* Dim placeholder span inside a cell (used when bytes / key are null). */
.req-cell-dim{color:var(--text3);opacity:.55}

/* Better empty-state. Centered title + descriptive copy + a single
   contextual action button instead of the bare hint text. */
.req-empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:40px 24px;text-align:center}
.req-empty-title{font-size:14px;font-weight:600;color:var(--text2)}
.req-empty-msg{font-size:12px;color:var(--text3);max-width:420px;line-height:1.5}
.req-empty-action{margin-top:8px;height:28px;padding:0 16px;border:1px solid var(--border);border-radius:4px;background:transparent;color:var(--text2);font-size:12px;font-family:inherit;cursor:pointer;transition:color .12s,border-color .12s}
.req-empty-action:hover{color:var(--text);border-color:var(--border-strong)}
/* Requests filter row: status pills + selects + free-text inputs, packed in
   a single wrap-friendly flex row. Reuses .log-filter / .log-search / .btn-cleanup
   primitives so it visually matches the Logs page filter strip. */
/* Filter rows above the audit log. Two fixed bands with stable
   assignments: the first holds the "what" predicates (status, method,
   key, client, path, id, duration, error), the second the "when"
   predicates (presets + UTC bounds) and Reset. A band NEVER wraps: if
   its controls exceed the viewport it scrolls horizontally, so every
   control keeps a fixed home and muscle memory holds across window
   sizes. */
.req-filters{display:flex;align-items:center;gap:8px;flex-wrap:nowrap;overflow-x:auto;padding-bottom:2px}
.req-filters > *{flex-shrink:0}
.req-filters .logs-filters{margin-right:4px}
/* Use a slimmer scrollbar so the always-visible overflow indicator
   doesn't dominate the row's vertical footprint. */
.req-filters::-webkit-scrollbar{height:6px}
.req-filters::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}
.req-filters::-webkit-scrollbar-thumb:hover{background:var(--border-strong)}
/* Matches the shared 28px height used by .log-filter / .log-toggle /
   .log-search so the Requests filter row reads as one chrome band. */
.req-select{height:28px;padding:0 10px;background:var(--bg);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:11px;font-family:inherit;outline:none;cursor:pointer}
.req-select:focus{border-color:#555}
.req-count{font-size:11px;color:var(--text3);font-variant-numeric:tabular-nums;margin-right:6px;white-space:nowrap}
/* Stats strip: whole-filter aggregate (status mix, error rate, latency
   percentiles, byte totals) so the operator reads the shape of the
   matching traffic without paging through it. Static text only. */
/* Inline aggregate summary in the Audit-log heading row, between the
   heading and the pager. One line, clipping gracefully when the pager
   needs the room; the full figures live in the title tooltip. */
.req-stats{display:inline-flex;align-items:baseline;gap:12px;flex-wrap:nowrap;overflow:hidden;min-width:0;flex-shrink:1;font-size:11px;color:var(--text2);font-variant-numeric:tabular-nums}
.req-stats[hidden]{display:none}
.req-stat{display:inline-flex;align-items:baseline;gap:5px;white-space:nowrap}
.req-stat-label{color:var(--text3);font-size:10px;text-transform:uppercase;letter-spacing:.06em}
.req-stat strong{color:var(--text);font-weight:600}
.req-stat strong.status-4xx{color:#d97706}
.req-stat strong.status-5xx{color:#dc2626}
html.theme-light .req-stat strong.status-4xx{color:#b45309}
html.theme-light .req-stat strong.status-5xx{color:#b91c1c}
/* Request-volume chart over the filtered window: one stacked column per
   time bucket (2xx/3xx bottom, 4xx middle, 5xx top), driven by the
   /requests/timeseries endpoint. Click a column to zoom the time
   filter to that bucket. Pure static color blocks. */
.req-spark{
  display:flex;flex-direction:column;max-width:1100px;box-sizing:border-box;
  padding:4px 6px 3px;
  background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);
  overflow:hidden;
}
.req-spark[hidden]{display:none}
.req-spark-plot{display:flex;align-items:flex-end;gap:1px;height:50px}
.req-spark-bar{flex:1 1 0;min-width:2px;max-width:26px;height:100%;display:flex;flex-direction:column;justify-content:flex-end;cursor:pointer;border-radius:1px}
.req-spark-bar:hover{background:var(--hover-wash)}
.req-spark-seg{width:100%;min-height:0}
/* Zero-traffic buckets keep a faint baseline tick so the time axis
   reads as continuous; a fully invisible bucket makes a quiet window
   look like a rendering failure. */
.req-spark-seg.sp-zero{height:2px;background:var(--border)}
.req-spark-seg.sp-ok{background:rgba(22,163,74,.55)}
.req-spark-seg.sp-redir{background:rgba(59,130,246,.45)}
.req-spark-seg.sp-4xx{background:rgba(217,119,6,.65)}
.req-spark-seg.sp-5xx{background:rgba(220,38,38,.75)}
/* The time axis is its own row UNDER the plot, split from it by a
   hairline: labels own reserved space and can never paint across a bar,
   however tall the first or last bucket runs. */
.req-spark-axis{display:flex;justify-content:space-between;gap:12px;margin-top:3px;padding:3px 2px 0;border-top:1px solid var(--border);font-size:10px;color:var(--text3)}
.req-spark-axis span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* Request id stub rendered inside the recent-requests table. Short monospace
   text with a hover affordance signalling that clicking copies the id. */
.req-rid{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:var(--text3);cursor:pointer;border-bottom:1px dotted var(--border)}
.req-rid:hover{color:var(--primary);border-bottom-color:var(--primary)}
/* Client / Key / Path cell values that filter the audit log on click. The
   dotted underline appears on hover so the cell reads as plain text until
   the operator hovers it, then signals it is actionable. */
.req-cell-filter{cursor:pointer;border-bottom:1px dotted transparent}
.req-cell-filter:hover{color:var(--primary);border-bottom-color:var(--primary)}
/* Filtering overlay, shared by the Logs and Requests panels (single source of
   truth). The scrolling list lives inside a positioned wrapper so the overlay can
   sit over the visible viewport (centered, sharp) while the rows behind it blur.
   The wrapper carries the outer margin the list used to own. */
.filter-wrap{position:relative;flex:1;min-height:0;display:flex;flex-direction:column;margin:0 48px 24px}
.logs-panel .dash-logs{flex:1;min-height:0;margin:0;border-radius:var(--radius);max-height:none;max-width:none}
.filter-wrap.is-filtering > .dash-logs,
.filter-wrap.is-filtering > .dash-requests{filter:blur(2px);opacity:.55;pointer-events:none;transition:filter .12s ease,opacity .12s ease}
.filter-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;z-index:6;pointer-events:none}
.filter-overlay[hidden]{display:none}
.filter-overlay-card{display:flex;align-items:center;gap:10px;padding:11px 18px;background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow-lg);font-size:13px;font-weight:500;color:var(--text)}
.filter-spinner{width:15px;height:15px;border:2px solid var(--border-strong);border-top-color:var(--primary);border-radius:50%;animation:filterspin .7s linear infinite;flex:none}
@keyframes filterspin{to{transform:rotate(360deg)}}
.log-line{display:block}
.log-line .log-ts{color:var(--text3)}
.log-line .log-lvl-warn{color:#f59e0b}
.log-line .log-lvl-err{color:#ef4444}
.log-line .log-lvl-info{color:var(--data-blue)}
.dash-model-meta{display:flex;gap:14px;align-items:center;font-size:11px;color:var(--text3);flex-shrink:0;font-variant-numeric:tabular-nums}
/* Capability label on a dashboard model row ("embedding", "vision").
   Flat colored uppercase text only - no background / border / pill
   rounding - so it reads as an inline marker rather than a clickable
   badge. Matches the .tm-kind-tag idiom (no chrome, just color). */
/* Capability list on a loaded model row is styled via the
   .dash-model-col.caps rule above. Plain comma-separated text only:
   NO chip background, NO border, NO pill - the design system
   rejects badge-style decorations. */
.dash-model-when{color:var(--text2);font-weight:500}

/* Per-model action cluster: Hibernate then Unload, packed together in the
   row's last grid cell so the two verbs read as one control group. */
.dash-model-actions{display:flex;align-items:center;gap:6px;justify-self:end}
/* Hibernate and Unload share the quiet secondary look; the verb, not a
   color code, says which one is heavier. */
.dash-model-hibernate{padding:3px 10px;border:1px solid var(--border-strong);border-radius:4px;background:transparent;color:var(--text2);font-size:11px;font-weight:600;cursor:pointer;font-family:inherit;transition:background .12s,color .12s,border-color .12s,opacity .12s}
.dash-model-hibernate:hover:not(:disabled){color:var(--text);border-color:var(--text3)}
.dash-model-hibernate:disabled{opacity:.4;cursor:not-allowed}
.dash-model-unload{padding:3px 10px;border:1px solid var(--border-strong);border-radius:4px;background:transparent;color:var(--text2);font-size:11px;font-weight:600;cursor:pointer;font-family:inherit;transition:background .12s,color .12s,border-color .12s,opacity .12s}
.dash-model-unload:hover:not(:disabled){color:var(--text);border-color:var(--text3)}
.dash-model-unload:disabled{opacity:.45;cursor:not-allowed}

@media(max-width:720px){
  .stat-cards{grid-template-columns:repeat(2,1fr)}
  .sidebar{position:sticky;top:var(--appbar);width:100%;flex-direction:row;padding:8px 12px;border-right:none;border-bottom:1px solid var(--border);overflow-x:auto;gap:4px;background:var(--bg2);z-index:20}
  .nav-link{white-space:nowrap;padding:7px 12px;font-size:12px;gap:6px}
  .nav-icon{width:14px;height:14px}
  .nav-label{display:inline}
  .nav-cat,.nav-spacer,.nav-back,.nav-footer{display:none}
  .main{margin-left:0;padding-bottom:48px !important}.section{padding:24px 20px 0}
  .grid{grid-template-columns:1fr}.field.wide{grid-column:auto}
  .banner,.savebar,.app-footer{left:0}.savebar{padding:0 16px}
  body.sidebar-collapsed .app-footer{left:0}
  .layout{flex-direction:column}
  .app-footer{font-size:10px;padding:5px 14px;gap:10px}
  .app-foot-sep{display:none}
}
@media(max-width:1100px){
  .stat-cards{grid-template-columns:repeat(3,1fr)}
}
/* Section jump palette (Ctrl+K): a floating command line over the page.
   Rows carry the category as a quiet eyebrow, so "Tools" under Agent and
   a future "Tools" elsewhere could never be confused. */
.jump-pal{position:fixed;inset:0;z-index:300;display:flex;align-items:flex-start;justify-content:center;padding-top:14vh;background:var(--scrim)}
.jump-pal[hidden]{display:none}
.jump-pal-card{width:min(460px,calc(100vw - 48px));background:var(--bg2);border:1px solid var(--border);border-radius:12px;box-shadow:var(--shadow-lg);overflow:hidden;display:flex;flex-direction:column}
.jump-pal-card input{height:44px;padding:0 16px;border:none;border-bottom:1px solid var(--border);background:transparent;color:var(--text);font-size:14px;font-family:inherit;outline:none}
.jump-pal-list{max-height:min(320px,50vh);overflow-y:auto;overscroll-behavior:contain;padding:6px}
.jump-pal-row{display:flex;align-items:center;gap:10px;width:100%;padding:9px 11px;border:none;border-radius:7px;background:transparent;color:var(--text);font-family:inherit;font-size:13px;text-align:left;cursor:pointer}
.jump-pal-row:hover,.jump-pal-row.is-sel{background:var(--hover-wash)}
.jump-pal-row .nav-icon{width:15px;height:15px;color:var(--text3);flex:0 0 auto}
.jump-pal-row-cat{margin-left:auto;font-size:10px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--text3);white-space:nowrap}
.jump-pal-empty{display:block;padding:16px 14px;font-size:12.5px;color:var(--text3);text-align:center}
.jump-pal-foot{display:flex;gap:14px;justify-content:flex-end;padding:7px 12px;border-top:1px solid var(--border);font-size:10.5px;color:var(--text3)}
/* Inner scrollers wear a slim, themed scrollbar instead of the OS chrome:
   one rule for every surface composing this sheet. The page scroller
   keeps the native bar, which users expect for whole-document scrolling. */
.section *{scrollbar-width:thin;scrollbar-color:var(--border-strong) transparent}
.pg-app *,.modal *,.lmk-pop{scrollbar-width:thin;scrollbar-color:var(--border-strong) transparent}
.section ::-webkit-scrollbar,.pg-app ::-webkit-scrollbar,.modal ::-webkit-scrollbar{width:8px;height:8px}
.section ::-webkit-scrollbar-thumb,.pg-app ::-webkit-scrollbar-thumb,.modal ::-webkit-scrollbar-thumb{background:var(--border-strong);border-radius:4px;border:2px solid transparent;background-clip:content-box}
.section ::-webkit-scrollbar-track,.pg-app ::-webkit-scrollbar-track,.modal ::-webkit-scrollbar-track{background:transparent}
/* Text selection follows the ink system in both themes. */
::selection{background:color-mix(in srgb, var(--text) 18%, transparent)}
/* Keyboard travel is visible on every interactive surface these pages
   compose, not only on native inputs. */
.agb-cat-head:focus-visible,.agb-tpl:focus-visible,.agb-agent-tile:focus-visible,
.jump-pal-row:focus-visible,.agb-back:focus-visible,.agb-cat-allbtn:focus-visible,
.skills-item-act:focus-visible,.row-group-acts button:focus-visible{outline:none;box-shadow:var(--focus-ring)}
/* Vestibular safety: every animation and transition collapses to a
   frame when the OS asks for reduced motion. One-shot entrances land
   instantly; nothing keeps moving. */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.01ms !important;animation-iteration-count:1 !important;transition-duration:.01ms !important;scroll-behavior:auto !important}
}/* Anchor jumps land instantly: an animated glide across a long article reads as delay, not
   polish, so no surface of the shell opts into smooth scrolling. */
/* The documentation accent is the BRAND purple, themed for contrast: structure (eyebrow, title
   stroke, section rules, links, active states, the reading indicator) carries it, so hierarchy
   is color-coded instead of one more shade of body text. --gd-top is the ONE head baseline all
   three rails share. */
.gd-shell{--gd-accent:#a78bfa;--gd-accent-soft:rgba(167,139,250,.13);--gd-top:56px;display:grid;grid-template-columns:var(--gd-nav-w,280px) minmax(0,1fr) 232px;column-gap:clamp(36px,3.5vw,72px);max-width:1800px;margin:0 auto;padding:0 clamp(24px,3vw,56px);background:var(--bg);min-height:100vh;color:var(--text)}
html.theme-light .gd-shell{--gd-accent:#6d28d9;--gd-accent-soft:rgba(109,40,217,.08)}
.gd-side{position:relative;border-right:1px solid var(--border)}
/* The tree's edge is draggable: an invisible hit zone over the divider, whose line answers the
   hover and turns brand-colored while a drag is live. Widths persist per browser. */
.gd-resize{position:absolute;top:0;right:-4px;width:9px;height:100%;cursor:col-resize;z-index:5}
.gd-resize::after{content:'';position:absolute;left:4px;top:0;width:1px;height:100%;background:transparent;transition:background .15s ease}
.gd-resize:hover::after{background:var(--border-strong)}
body.gd-resizing{cursor:col-resize;user-select:none}
body.gd-resizing .gd-resize::after{background:var(--gd-accent)}
.gd-side{padding-top:var(--appbar)}
.gd-side-inner{position:sticky;top:var(--appbar);max-height:calc(100vh - var(--appbar));overflow-y:auto;overscroll-behavior:contain;padding:var(--gd-top) 20px 48px 2px;display:flex;flex-direction:column}
/* The label voice shared by the outline's head and the tree's tier labels. */
.gd-cat{font-size:9.5px;font-weight:650;letter-spacing:.14em;text-transform:uppercase;color:var(--text3);opacity:.85;padding:0 10px;margin:0 0 2px}
/* The drawer's own header exists only where the rail IS the drawer. */
.gd-mhead{display:none;align-items:center;justify-content:space-between;padding:0 4px 10px 10px}
.gd-mhead span{font-size:10px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--text3)}
.gd-mclose{display:flex;align-items:center;justify-content:center;width:26px;height:26px;padding:0;border:none;border-radius:6px;background:transparent;color:var(--text2);cursor:pointer;transition:color .12s ease,background .12s ease}
.gd-mclose:hover{color:var(--text);background:var(--hover-wash)}
.gd-mclose svg{width:13px;height:13px;display:block}
/* The filter heads the rail in the shared column baseline, so the reserved head carries
   something: type-to-filter over every page title, focused from anywhere with "/". */
.gd-filter{position:relative;flex:0 0 auto;margin:0 6px 12px 2px}
.gd-filter svg{position:absolute;left:9px;top:50%;transform:translateY(-50%);width:13px;height:13px;color:var(--text3);pointer-events:none}
.gd-filter input{width:100%;height:31px;padding:0 32px 0 29px;border:1px solid var(--border);border-radius:6px;background:var(--bg2);color:var(--text);font-size:12.5px;font-family:inherit;transition:border-color .12s ease}
.gd-filter input::placeholder{color:var(--text3)}
.gd-filter input:hover{border-color:var(--border-strong)}
.gd-filter input:focus{outline:none;border-color:transparent;box-shadow:var(--focus-ring)}
.gd-filter kbd{position:absolute;right:7px;top:50%;transform:translateY(-50%);padding:1px 5.5px;border:1px solid var(--border);border-radius:4px;background:var(--bg3);font-family:var(--mono);font-size:10px;color:var(--text3);pointer-events:none}
.gd-tree{display:flex;flex-direction:column}
/* Three voices, quietly: part labels are ruled tracked caps naming who a section is for;
   category rows read one weight step above page titles and carry the disclosure; page
   titles stay the rail's readable dark text the eye lands on. */
.gd-part{font-size:9.5px;font-weight:650;letter-spacing:.14em;text-transform:uppercase;color:var(--text3);opacity:.85;padding:0 10px;margin:2px 0 3px}
.gd-sec+.gd-part{margin-top:16px;padding-top:13px;border-top:1px solid var(--border)}
.gd-sec{display:flex;flex-direction:column;gap:1px}
.gd-sec+.gd-sec{margin-top:2px}
.gd-catrow{display:flex;align-items:center;gap:7px;width:100%;padding:5.5px 8px 5.5px 10px;border:none;border-radius:6px;background:transparent;color:var(--text);font-size:12.5px;font-weight:600;letter-spacing:-.004em;text-align:left;cursor:pointer;transition:background .12s ease;font-family:inherit}
.gd-catrow:hover{background:var(--hover-wash)}
.gd-catrow span:first-child{flex:1;min-width:0}
.gd-ctw{display:flex;align-items:center;justify-content:center;width:16px;height:16px;color:var(--text3);flex:0 0 auto}
.gd-ctw svg{width:9px;height:9px;display:block;transition:transform .16s ease}
.gd-catsec.is-open>.gd-catrow .gd-ctw svg,.gd-catsec.gd-flt-open>.gd-catrow .gd-ctw svg{transform:rotate(90deg)}
.gd-catsec.is-open>.gd-kids,.gd-catsec.gd-flt-open>.gd-kids{grid-template-rows:1fr}
.gd-item{display:block;padding:6px 10px;border-radius:6px;font-size:13px;font-weight:480;line-height:1.45;color:var(--text);text-decoration:none;transition:color .12s ease,background .12s ease}
/* Category members read as members: one indent step, and chapters' children one more. */
.gd-catsec .gd-item{padding-left:20px}
.gd-catsec .gd-item--child{padding-left:36px}
.gd-item:hover{background:var(--hover-wash)}
.gd-item.is-active{color:var(--gd-accent);background:var(--gd-accent-soft);font-weight:600}
/* Sub-topics indent under their chapter hub and recede a step, so the hierarchy reads
   from weight and color before indentation is even noticed. */
.gd-item--child{padding-left:26px;font-size:12.5px;font-weight:400;color:var(--text2)}
.gd-item--child:hover{color:var(--text)}
.gd-item--child.is-active{color:var(--gd-accent)}
/* A chapter with sub-topics is a disclosure branch: the parent link fills the row and
   navigates, the chevron beside it toggles. Children collapse to a zero-height grid row and
   open by growing it, so the motion is the browser's own layout interpolation. */
.gd-branch{display:flex;flex-direction:column}
.gd-row{display:flex;align-items:center;gap:2px}
.gd-row .gd-item{flex:1;min-width:0}
.gd-tw{flex:0 0 auto;display:flex;align-items:center;justify-content:center;width:22px;height:22px;margin-right:2px;padding:0;border:none;border-radius:5px;background:transparent;color:var(--text3);cursor:pointer;transition:color .12s ease,background .12s ease}
.gd-tw:hover{color:var(--text);background:var(--hover-wash)}
.gd-tw:focus-visible{outline:none;box-shadow:var(--focus-ring)}
.gd-tw svg{width:10px;height:10px;display:block;transition:transform .16s ease}
.gd-branch.is-open>.gd-row .gd-tw svg,.gd-branch.gd-flt-open>.gd-row .gd-tw svg{transform:rotate(90deg)}
.gd-kids{display:grid;grid-template-rows:0fr;transition:grid-template-rows .18s ease}
.gd-branch.is-open>.gd-kids,.gd-branch.gd-flt-open>.gd-kids{grid-template-rows:1fr}
.gd-kids-in{overflow:hidden;min-height:0;display:flex;flex-direction:column;gap:2px}
/* The row-to-first-child rhythm rides INSIDE the clipped box (a spacer child, not padding),
   so a collapsed branch measures exactly zero. */
.gd-kids-in::before{content:'';height:2px;flex:0 0 auto}
/* Filtering hides by class, never by inline style, so clearing the query is one class
   sweep and the reader's own disclosure state underneath is untouched. */
.gd-hide{display:none!important}
.gd-none{padding:8px 10px;font-size:12.5px;color:var(--text3)}
html.gd-lock{overflow:hidden}
/* The stacked topic bar and both overlays render on every page and cost nothing on wide
   viewports, where they never display. */
.gd-mtop{display:none;align-items:center;gap:8px;position:sticky;top:var(--appbar);z-index:40;margin-top:var(--appbar);padding:5px 10px 5px 4px;background:var(--bg);border-bottom:1px solid var(--border)}
.gd-mtoggle{flex:1;min-width:0;display:flex;align-items:center;gap:8px;height:32px;padding:0 8px;border:none;border-radius:6px;background:transparent;color:var(--text);cursor:pointer;text-align:left;transition:background .12s ease;font-family:inherit}
.gd-mtoggle:hover{background:var(--hover-wash)}
.gd-mtoggle svg{flex:0 0 auto;width:14px;height:14px;color:var(--text3)}
.gd-mtitle{min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-size:13px;font-weight:600}
.gd-mcat{flex:0 0 auto;font-size:11px;font-weight:500;color:var(--text3)}
.gd-mtoc{flex:0 0 auto;height:32px;padding:0 10px;border:none;border-radius:6px;background:transparent;color:var(--text2);font-size:12px;font-weight:600;cursor:pointer;transition:color .12s ease,background .12s ease;font-family:inherit}
.gd-mtoc:hover{color:var(--text);background:var(--hover-wash)}
.gd-scrim{display:none;position:fixed;inset:0;z-index:80;background:var(--scrim);opacity:0;pointer-events:none;transition:opacity .18s ease}
.gd-ohead{display:flex;align-items:center;justify-content:space-between}
.gd-oclose{display:none}
.gd-main{min-width:0;padding:calc(var(--appbar) + var(--gd-top)) 0 88px}
.gd-doc{max-width:880px}
.gd-eyebrow{font-size:11px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--gd-accent);margin:0 0 12px}
.gd-eyebrow a{color:inherit;text-decoration:none}
.gd-eyebrow a:hover{text-decoration:underline}
.gd-eyebrow-sep{margin:0 7px;color:var(--text3);font-weight:500}
/* The library speaks the product's own voice: the brand stroke the whole console opens its
   sections with leads the title, and each section opens under a short brand rule with its
   numeral set apart as a quiet tabular figure. */
.gd-doc h1{font-size:30px;font-weight:680;letter-spacing:-.018em;line-height:1.2;margin:0 0 12px;color:var(--text)}
.gd-doc h1::before{content:'';display:inline-block;width:4px;height:24px;background:var(--gd-accent);border-radius:2px;margin-right:14px;vertical-align:-2px}
.gd-doc h1+p{font-size:15.5px;line-height:1.75;color:var(--text2)}
.gd-doc h2{font-size:20px;font-weight:650;letter-spacing:-.012em;margin:52px 0 14px;color:var(--text);scroll-margin-top:calc(var(--appbar) + 24px)}
.gd-doc h2::before{content:'';display:block;width:30px;height:3px;border-radius:2px;background:var(--gd-accent);margin-bottom:14px}
.gd-n{color:var(--text3);font-weight:500;font-variant-numeric:tabular-nums;margin-right:9px}
.gd-doc h3{font-size:15.5px;font-weight:650;margin:28px 0 10px;color:var(--text);scroll-margin-top:calc(var(--appbar) + 24px)}
.gd-hash{margin-left:9px;font-weight:400;font-size:16px;color:var(--gd-accent);text-decoration:none;opacity:0;transition:opacity .12s ease}
.gd-doc h2:hover .gd-hash{opacity:.7}
.gd-hash:hover{opacity:1}
.gd-doc p{margin:0 0 14px;font-size:14.5px;line-height:1.75;color:var(--text2)}
.gd-doc strong{color:var(--text);font-weight:600}
.gd-doc a{color:var(--gd-accent);text-decoration:none}
.gd-doc a:hover{text-decoration:underline}
/* Outbound references carry a quiet departure mark so a reader knows the link leaves the site. */
.gd-doc a.gd-ext::after{content:'\2197';font-size:.72em;margin-left:3px;vertical-align:.18em;opacity:.65}
/* On the exported site, a reference to one of the product's own surfaces (the admin panel,
   the playground) is a fact about the product rather than a destination the site can offer,
   so it reads as quiet emphasis instead of a dead link. */
.gd-doc .gd-inapp{color:var(--text);font-weight:600}
.gd-doc code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12.5px;padding:1.5px 6px;border-radius:5px;background:var(--bg2);border:1px solid var(--border);color:var(--text)}
.gd-doc pre{position:relative;margin:0 0 18px;padding:14px 18px;border:1px solid var(--border);border-radius:10px;background:var(--bg2);overflow-x:auto}
.gd-doc pre code{padding:0;border:none;background:transparent;font-size:13px;line-height:1.65;color:var(--text)}
/* Copy control: hover-revealed, reserved footprint so the Copied swap never shifts a pixel. */
.gd-copy{position:absolute;top:9px;right:9px;min-width:68px;padding:4px 10px;border:1px solid var(--border);border-radius:6px;background:var(--bg);color:var(--text2);font-size:11px;font-weight:600;font-family:inherit;text-align:center;cursor:pointer;opacity:0;transition:opacity .15s ease,color .12s ease,border-color .12s ease}
.gd-doc pre:hover .gd-copy,.gd-copy:focus-visible{opacity:1}
.gd-copy:hover{color:var(--text);border-color:var(--border-strong)}
.gd-copy.is-done{color:#10b981;border-color:rgba(16,185,129,.4);opacity:1}
.gd-doc ul,.gd-doc ol{margin:0 0 14px;padding-left:24px}
.gd-doc li{margin:0 0 7px;font-size:14.5px;line-height:1.75;color:var(--text2)}
.gd-doc li>ul,.gd-doc li>ol{margin:7px 0 0}
.gd-doc li::marker{color:var(--text3)}
.gd-doc hr{border:none;border-top:1px solid var(--border);margin:30px 0}
.gd-doc h2+.gd-scroll,.gd-doc h2+p+.gd-scroll{margin-top:4px}
.gd-doc blockquote{margin:0 0 14px;padding:10px 16px;border-left:3px solid var(--border-strong);background:var(--bg2);border-radius:0 8px 8px 0}
.gd-doc blockquote p{margin:0}
.gd-scroll{overflow-x:auto;margin:6px 0 20px;border:1px solid var(--border);border-radius:10px}
.gd-doc table{width:100%;border-collapse:collapse;font-size:13.5px}
.gd-doc th,.gd-doc td{padding:10px 14px;text-align:left;vertical-align:top;border-bottom:1px solid var(--border);line-height:1.6}
.gd-doc th{font-size:10.5px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--text2);background:var(--bg2);white-space:nowrap}
.gd-doc td{color:var(--text2);min-width:120px}
.gd-doc tbody tr{transition:background .12s ease}
.gd-doc tbody tr:hover{background:var(--bg2)}
.gd-doc tr:last-child td{border-bottom:none}
.gd-pager{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:56px}
.gd-page{display:flex;flex-direction:column;gap:4px;padding:14px 18px;border:1px solid var(--border);border-radius:10px;text-decoration:none;transition:border-color .12s ease,background .12s ease}
.gd-page:hover{border-color:var(--border-strong);background:var(--bg2)}
.gd-page-next{text-align:right}
.gd-page-l{font-size:10.5px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--text3)}
.gd-page-t{font-size:14px;font-weight:600;color:var(--text)}
.gd-outline{position:sticky;top:var(--appbar);align-self:start;margin-top:var(--appbar);max-height:calc(100vh - var(--appbar));overflow-y:auto;overscroll-behavior:contain;padding:var(--gd-top) 0 48px}
.gd-outline .gd-cat{padding:0 0 8px 16px;margin:0}
/* The reading indicator: one accent segment on the rail that GLIDES to the current section
   (transform only, so no label ever re-wraps), beside a color-only current link. */
.gd-toc{position:relative;display:flex;flex-direction:column;border-left:1px solid var(--border)}
.gd-toc-ind{position:absolute;left:-1px;top:0;width:2px;height:18px;border-radius:1px;background:var(--gd-accent);opacity:0;transform:translateY(0);transition:transform .22s cubic-bezier(.3,.8,.4,1),opacity .15s ease}
.gd-toc-ind.is-on{opacity:1}
.gd-toc a{padding:5px 0 5px 16px;font-size:12.5px;line-height:1.5;color:var(--text3);text-decoration:none;transition:color .15s ease}
.gd-toc a:hover{color:var(--text)}
.gd-toc a.is-current{color:var(--gd-accent)}
.gd-item:focus-visible,.gd-toc a:focus-visible,.gd-page:focus-visible,.gd-hash:focus-visible,.gd-copy:focus-visible,.gd-catrow:focus-visible,.gd-mtoggle:focus-visible,.gd-mtoc:focus-visible,.gd-mclose:focus-visible{outline:none;box-shadow:var(--focus-ring)}
@media (prefers-reduced-motion:reduce){
  .gd-item,.gd-toc a,.gd-page,.gd-copy,.gd-hash,.gd-resize::after,.gd-doc tbody tr,.gd-tw,.gd-tw svg,.gd-kids,.gd-catrow,.gd-ctw svg,.gd-side,.gd-outline,.gd-scrim,.gd-mtoggle,.gd-mtoc,.gd-mclose,.gd-filter input{transition:none}
}
@media (max-width:1180px){
  .gd-shell{grid-template-columns:260px minmax(0,1fr)}
  .gd-outline{display:none}
}
@media (max-width:860px){
  .gd-shell{grid-template-columns:minmax(0,1fr)}
  .gd-resize{display:none}
  .gd-mtop{display:flex}
  .gd-mhead{display:flex}
  .gd-scrim{display:block}
  .gd-shell.gd-drawer-open .gd-scrim,.gd-shell.gd-sheet-open .gd-scrim{opacity:1;pointer-events:auto}
  /* The rail becomes the drawer: the same element and the same tree, repositioned, so the
     article starts immediately and the library stays one tap away. */
  .gd-side{position:fixed;top:0;bottom:0;left:0;z-index:85;width:min(320px,86vw);padding-top:0;background:var(--bg2);border-right:1px solid var(--border);box-shadow:var(--shadow-md);transform:translateX(-102%);transition:transform .2s ease}
  .gd-shell.gd-drawer-open .gd-side{transform:none}
  .gd-side-inner{position:static;max-height:none;height:100%;padding:14px 14px 24px 10px}
  .gd-main{padding:20px 0 72px}
  .gd-pager{grid-template-columns:1fr}
  .gd-page-next{text-align:left}
  /* The outline becomes a bottom sheet, summoned from the topic bar. */
  .gd-outline{display:flex;flex-direction:column;position:fixed;top:auto;left:0;right:0;bottom:0;z-index:85;margin:0;max-height:62vh;padding:12px 10px 20px;overflow:hidden;background:var(--bg2);border-top:1px solid var(--border);border-radius:12px 12px 0 0;box-shadow:var(--shadow-md);transform:translateY(104%);transition:transform .2s ease}
  .gd-shell.gd-sheet-open .gd-outline{transform:none}
  .gd-ohead{padding:0 6px 6px 0;flex:0 0 auto}
  .gd-oclose{display:flex}
  .gd-toc{overflow-y:auto;border-left:none}
  .gd-toc-ind{display:none}
  .gd-toc a{padding:8px 12px;border-radius:6px;font-size:13px}
  .gd-toc a.is-current{background:var(--accent-soft)}
}.md-code,.gd-doc pre{--tok-c:#64748b;--tok-s:#86d3a3;--tok-n:#e5b567;--tok-k:#8fb8f6}
html.theme-light .md-code,html.theme-light .gd-doc pre{--tok-c:#64748b;--tok-s:#0a7d52;--tok-n:#b45309;--tok-k:#1d4ed8}
.tok-c{color:var(--tok-c)}
.tok-s{color:var(--tok-s)}
.tok-n{color:var(--tok-n)}
.tok-k{color:var(--tok-k)}.dch-bar{position:fixed;top:0;left:0;right:0;height:var(--appbar);z-index:70;display:flex;align-items:center;gap:14px;padding:0 18px;background:var(--bg);border-bottom:1px solid var(--border)}
.dch-brand{display:flex;align-items:center;gap:9px;text-decoration:none;color:var(--text);font-size:13px;font-weight:650;letter-spacing:-.012em;white-space:nowrap}
.dch-brand img{width:21px;height:21px;display:block}
.dch-sep{width:1px;height:16px;flex:0 0 auto;background:var(--border-strong);opacity:.55}
.dch-kind{font-size:11px;font-weight:650;letter-spacing:.1em;text-transform:uppercase;color:var(--text3)}
.dch-nav{display:flex;align-items:center;gap:4px;margin-left:10px}
.dch-link{padding:5px 10px;border-radius:6px;font-size:12.5px;font-weight:550;color:var(--text2);text-decoration:none;transition:color .12s ease,background .12s ease}
.dch-link:hover{color:var(--text);background:var(--hover-wash)}
.dch-link.is-active{color:var(--text);background:var(--hover-wash)}
.dch-spacer{flex:1}
.dch-version{font-family:var(--mono);font-size:11px;color:var(--text3)}
.dch-out{font-size:12px;font-weight:550;color:var(--text2);text-decoration:none;white-space:nowrap}
.dch-out:hover{color:var(--text);text-decoration:underline}
.dch-theme{display:flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;border:none;border-radius:6px;background:transparent;color:var(--text2);cursor:pointer;transition:color .12s ease,background .12s ease}
.dch-theme:hover{color:var(--text);background:var(--hover-wash)}
.dch-theme svg{width:15px;height:15px;display:block}
.dch-link:focus-visible,.dch-theme:focus-visible,.dch-brand:focus-visible,.dch-out:focus-visible{outline:none;box-shadow:var(--focus-ring)}
@media (max-width:640px){.dch-version,.dch-out{display:none}}
/* ── The landing ── */
body{background:var(--bg);color:var(--text)}
.dcl-main{max-width:760px;margin:0 auto;padding:calc(var(--appbar) + 72px) 24px 96px}
.dcl-title{font-size:32px;font-weight:680;letter-spacing:-.02em;line-height:1.2;margin:0 0 14px}
.dcl-title::before{content:'';display:inline-block;width:4px;height:26px;background:#a78bfa;border-radius:2px;margin-right:14px;vertical-align:-2px}
html.theme-light .dcl-title::before{background:#6d28d9}
.dcl-lead{font-size:15.5px;line-height:1.7;color:var(--text2);margin:0 0 36px}
.dcl-cards{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.dcl-card{position:relative;display:flex;flex-direction:column;gap:8px;padding:20px 22px 22px;border:1px solid var(--border);border-radius:10px;background:var(--bg2);text-decoration:none;transition:border-color .12s ease,background .12s ease}
.dcl-card:hover{border-color:var(--border-strong)}
.dcl-card-t{font-size:16px;font-weight:650;color:var(--text)}
.dcl-card-b{font-size:13px;line-height:1.65;color:var(--text2)}
.dcl-card-go{position:absolute;top:18px;right:20px;font-size:15px;color:var(--text3);transition:color .12s ease,transform .12s ease}
.dcl-card:hover .dcl-card-go{color:var(--text);transform:translateX(2px)}
.dcl-card:focus-visible{outline:none;box-shadow:var(--focus-ring)}
@media (max-width:640px){.dcl-cards{grid-template-columns:1fr}}/* ── API reference ── */
/* The signature row: the verb speaks in color and weight alone (flat text, never a capsule),
   beside the path at full width. Reserved line height, so hover states move nothing. */
.ar-sig{display:flex;align-items:center;gap:12px;margin:0 0 14px;flex-wrap:wrap}
.ar-verb{font-family:var(--mono);font-size:12.5px;font-weight:750;letter-spacing:.04em}
.ar-get{color:#10b981}
.ar-post{color:#60a5fa}
.ar-put{color:#f59e0b}
.ar-patch{color:#a78bfa}
.ar-delete{color:#f87171}
html.theme-light .ar-get{color:#059669}
html.theme-light .ar-post{color:#2563eb}
html.theme-light .ar-put{color:#b45309}
html.theme-light .ar-patch{color:#6d28d9}
html.theme-light .ar-delete{color:#dc2626}
.ar-path{font-family:var(--mono);font-size:13.5px;padding:4px 10px;border:1px solid var(--border);border-radius:6px;background:var(--bg2);color:var(--text)}
/* The path's copy control: pictogram only, the confirmation swapping in place. */
.ar-copy{display:flex;align-items:center;justify-content:center;width:26px;height:26px;padding:0;border:none;border-radius:6px;background:transparent;color:var(--text3);cursor:pointer;transition:color .12s ease,background .12s ease;flex:0 0 auto}
.ar-copy:hover{color:var(--text);background:var(--hover-wash)}
.ar-copy:focus-visible{outline:none;box-shadow:var(--focus-ring)}
.ar-copy svg{width:13px;height:13px;display:block}
.ar-copy .ar-copy-ok{display:none}
.ar-copy.is-done{color:var(--success)}
.ar-copy.is-done .ar-copy-i{display:none}
.ar-copy.is-done .ar-copy-ok{display:block}
.ar-deprecated{font-size:11px;font-weight:650;letter-spacing:.08em;text-transform:uppercase;color:var(--warning)}
.ar-req{margin-left:8px;font-size:10px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--warning)}
.ar-media{font-size:13px;color:var(--text2)}
.ar-status{font-family:var(--mono);font-weight:650}
.ar-s2{color:var(--success)}
.ar-s4,.ar-s5{color:var(--danger)}
/* Named types open the schema popover in place: the control reads as a reference (accent,
   dotted rule), never as navigation away from the endpoint. */
.ar-ref{padding:0;border:none;background:transparent;font:inherit;color:var(--gd-accent);cursor:pointer;text-decoration:underline;text-decoration-style:dotted;text-decoration-color:color-mix(in srgb, var(--gd-accent) 55%, transparent);text-underline-offset:3px}
.ar-ref:hover{text-decoration-style:solid;text-decoration-color:var(--gd-accent)}
.ar-ref:focus-visible{outline:none;border-radius:4px;box-shadow:var(--focus-ring)}
/* ── The schema popover ── */
.ar-pop-scrim{position:fixed;inset:0;z-index:90;background:var(--scrim);opacity:0;pointer-events:none;transition:opacity .15s ease}
.ar-pop-scrim.is-open{opacity:1;pointer-events:auto}
/* Anchored from the top, never centered: drilling between types of different sizes grows
   the body downward while the head (and the controls the pointer is on) holds still. */
.ar-pop{position:fixed;left:50%;top:9vh;z-index:91;width:min(760px,92vw);max-height:82vh;display:flex;flex-direction:column;background:var(--bg2);border:1px solid var(--border);border-radius:10px;box-shadow:var(--shadow-lg);opacity:0;pointer-events:none;transform:translateX(-50%) scale(.985);transition:opacity .15s ease,transform .15s ease}
.ar-pop.is-open{opacity:1;pointer-events:auto;transform:translateX(-50%)}
.ar-pop-head{display:flex;align-items:center;gap:8px;padding:11px 12px 11px 10px;border-bottom:1px solid var(--border);flex:0 0 auto}
.ar-pop-back,.ar-pop-close{display:flex;align-items:center;justify-content:center;width:26px;height:26px;padding:0;border:none;border-radius:6px;background:transparent;color:var(--text2);cursor:pointer;transition:color .12s ease,background .12s ease;flex:0 0 auto}
.ar-pop-back:hover,.ar-pop-close:hover{color:var(--text);background:var(--hover-wash)}
.ar-pop-back svg{width:11px;height:11px;display:block}
.ar-pop-close svg{width:13px;height:13px;display:block}
.ar-pop-back:focus-visible,.ar-pop-close:focus-visible{outline:none;box-shadow:var(--focus-ring)}
.ar-pop-title{flex:1;min-width:0;font-family:var(--mono);font-size:14px;font-weight:650;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ar-pop-body{overflow-y:auto;overscroll-behavior:contain;padding:14px 18px 18px}
.ar-pop-doc{max-width:none}
@media (prefers-reduced-motion:reduce){.ar-pop,.ar-pop-scrim{transition:none}}
@media (max-width:640px){.ar-pop{width:94vw;max-height:84vh}}
/* Description markdown inside table cells keeps the cell's rhythm. */
.gd-doc td p{margin:0 0 6px;font-size:13.5px}
.gd-doc td p:last-child{margin-bottom:0}
/* Property-style tables adapt to the width they actually get (a phone, the popover, a narrow
   split): below a readable column budget the grid dissolves and each row becomes a stacked
   block, identity and type inline, the description at full measure beneath. Container-driven,
   so the same rule serves every host without knowing the viewport. */
.ar-props{container-type:inline-size}
@container (max-width:620px){
  .ar-props table,.ar-props tbody{display:block;width:100%}
  .ar-props thead{display:none}
  .ar-props tr{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:12px;row-gap:2px;padding:11px 14px;border-bottom:1px solid var(--border)}
  .ar-props tr:last-child{border-bottom:none}
  .ar-props td{display:block;border:none;padding:0;min-width:0;line-height:1.6}
  .ar-props td:last-child{flex:1 0 100%;margin-top:3px}
}