/* Wynxx-aligned theme — derived from the LIVE site CSS (new_style/extracted.css).
   Geist is the platform face; the brand is indigo #2f47e6 on white / neutral-gray
   surfaces with near-black text. This is the LIGHT theme; pair with style.dark.css. */
/* Geist — self-hosted (OFL), matching the live Wynxx site exactly. Served from
   /static/fonts (cli/web/docs/fonts). Avoids a Google-Fonts dependency that
   silently fell back to a system font. */
/* Geist as a single VARIABLE font (weight axis 100–900) — matches the original
   Wynxx site, which serves the variable font. Static per-weight instances rasterize
   a touch heavier than the variable font at the same nominal weight under Windows
   DirectWrite, so the variable file makes our text weight render identically. */
@font-face {
  font-family: "Geist";
  src: url("/static/fonts/Geist-Variable.woff2") format("woff2");
  font-weight: 100 900; font-style: normal; font-display: swap;
}
/* Metrics-matched fallback — Arial scaled/overridden to occupy the SAME box as Geist
   (em 1000, asc 1005, desc -295, xAvg 580 vs Arial em 2048, xAvg 1038), so the
   swap-in of Geist causes zero layout shift / no apparent size change. Pinned to
   local("Arial") (present on Win+Mac) so the override math is identical everywhere. */
@font-face {
  font-family: "Geist Fallback";
  src: local("Arial");
  size-adjust: 114.44%;
  ascent-override: 87.82%;
  descent-override: 25.78%;
  line-gap-override: 0%;
}

:root {
  /* --- Palette (Wynxx light — indigo #2f47e6 on white over neutral gray) --- */
  --bg:          #ffffff;   /* app canvas (page + header) — the pure extreme, mirroring dark's #0a0a0a canvas */
  --panel:       #f5f5f5;   /* ELEVATED surface: cards, sidebar, modals, tables — gray on the white canvas, mirroring dark's #171717 panel on #0a0a0a. */
  --border:      #e5e5e5;   /* neutral hairline (live --border) */
  --input:       #e5e5e5;   /* form/filter/select box border (design-tokens --input) */
  --divider:     var(--border);   /* design-tokens --border (no separate divider token) */
  --text:        #0a0a0a;   /* foreground (design-system --foreground) */
  --muted:       #737373;   /* secondary text (design-system --muted-foreground) */
  --sidebar-fg:  #3f3f46;   /* side-panel nav text + icons (design-system --sidebar-foreground) */
  --sidebar-accent: #e8e8e8;   /* selected side-panel item bg — a step darker than the gray sidebar (--panel) so it reads, mirroring dark's #262626 over #171717 */
  --accent:      #2f47e6;   /* UI accent — deployed light brand --primary; brand mark also #2f47e6 */
  --primary:       #2f47e6;   /* SOLID button fill — deployed light brand --primary */
  --primary-hover: #1e3ac9;   /* solid button hover — brand-strong */
  --accent2:     #ca8a04;   /* design-tokens --warning (yellow-600, oklch 68.1% 0.162 75.834) */
  --green:       #16a34a;   /* design-tokens --success (green-600, oklch 62.7% 0.194 149.214) */
  --red:         #dc2626;   /* design-tokens --destructive (oklch 57.7% 0.245 27.325) */
  --purple:      var(--accent);   /* remapped to design-tokens brand (--accent) */
  --cyan:        var(--accent);   /* remapped to design-tokens brand (--accent) */
  --secondary:   #e8e8e8;   /* flat neutral surface (buttons) — a step darker than --panel so it reads on gray cards, mirroring dark's #262626 */
  --code-bg:     #f5f5f5;   /* --muted surface = --panel (mirrors dark code-bg = panel) */
  --code-string: #b45309;

  /* --- RGB channels (match the hexes above) --- */
  --accent-rgb:  47,71,230;
  --accent2-rgb: 202,138,4;
  --green-rgb:   22,163,74;
  --red-rgb:     220,38,38;
  --muted-rgb:   115,115,115;
  --purple-rgb:  147,51,234;
  --cyan-rgb:    8,145,178;
  --neutral-rgb: 161,161,170;
  --sw-yellow-rgb: 183,121,31;
  --sw-orange-rgb: 217,119,6;

  /* --- Surface tint layers (indigo-on-white elevations) --- */
  --surface-1: rgba(47,71,230,0.02);
  --surface-2: rgba(47,71,230,0.04);
  --surface-3: rgba(47,71,230,0.06);
  --surface-4: rgba(47,71,230,0.10);

  /* --- Overlays & shadows --- */
  --overlay:       rgba(0,0,0,0.40);
  --shadow-modal:  0 8px 32px rgba(0,0,0,0.12);
  --code-overlay:  rgba(47,71,230,0.06);
  --shadow-sm:     0 1px 3px rgba(10,10,10,0.08);   /* design-tokens --shadow-sm (cards/panels) */
  --shadow-header: 0 1px 3px rgba(0,0,0,0.06);
  --shadow-md:     0 4px 6px -1px rgba(0,0,0,0.10), 0 2px 4px -2px rgba(0,0,0,0.10);   /* live --shadow-md (primary button) */
  --shadow-pop:    0 8px 24px rgba(0,0,0,0.12);

  /* --- Brand treatments (header + wordmark) --- */
  --header-bg:      var(--bg);   /* light header = the page canvas (white), mirroring dark's header = --bg */
  --brand-gradient: linear-gradient(90deg, #2f47e6 0%, #1e3ac9 100%);
  --on-accent:      #f9fafb;   /* text on a brand-filled surface (live --primary-foreground) */
  --tick-warn:      #b45309;
  --tick-error:     #b91c1c;
  /* Header brand — the server label adapts to the header bg: dark on the light
     header, white on the dark header. (The wynxx logo keeps its native colour.) */
  --brand-server-color: #000;            /* dark server label on the white header */
  --logo-color:         #000000;         /* Wynxx wordmark letterforms (inline SVG) — live --logo-color */

  /* --- Status colours — use the THEME-AWARE tokens -------------------------
     Status semantics map to the theme tokens: active/system/info -> --accent,
     pending/awaiting-human -> --accent2, completed -> --green, error/blocked ->
     --red, milestone/depended-on-by -> --purple, jarvis-owned -> --cyan,
     neutral/not-started -> --muted. Soft-tinted chips/badges/buttons derive
     bg + text + hairline from one of these via the shared --c driver (see the
     .pill / label-box / .btn rules) and they lighten in dark so coloured text
     stays legible. The old fixed-hue --st-* palette was retired (every chip and
     button now flows through these theme tokens, so a status colour changes in
     ONE place). --chip is a per-row dynamic colour (plan/quest type), set inline
     and read by .tr-badge.type. */

  /* --- Diagram containers (theme-aware) ------------------------------------
     Leaf chips (You/Model/abilities/storage) stay solid-hued in BOTH themes —
     white text reads on them either way, so they keep their fill attribute. A
     CONTAINER box (one that holds other boxes) is light in light mode but must
     go dark on the dark canvas or it glares. SVG presentation attributes can't
     read CSS vars, so a container rect/label carries a class and pulls these
     tokens; style.dark.css re-declares them for dark. --- */
  --dg-cli-bg:      #ffffff;   --dg-cli-stroke:  #c7d2fe;   --dg-cli-label:  #2f47e6;
  --dg-wrap-bg:     #fffaf2;   --dg-wrap-stroke: #f0d4a8;   --dg-wrap-label: #b45309;

  /* --- Provider brand colors (unchanged — third-party brands) --- */
  --prov-claude-color:  #d97757;
  --prov-gemini-color:  #4285f4;
  --prov-cursor-color:  #8a6eff;
  --prov-copilot-color: #2ebad4;
  --prov-codex-color:   #10a37f;
  --prov-claude-rgb:    217,119,87;
  --prov-gemini-rgb:    66,133,244;
  --prov-cursor-rgb:    138,110,255;
  --prov-copilot-rgb:   46,186,212;
  --prov-codex-rgb:     16,163,127;

  /* --- Swatch palette (type colour picker) — light-tuned --- */
  --sw-red:     #d0524f;
  --sw-orange:  #d97706;
  --sw-amber:   #d29a1f;
  --sw-yellow:  #b7791f;
  --sw-lime:    #84a637;
  --sw-green:   #4f9d63;
  --sw-emerald: #2f9e83;
  --sw-teal:    #2b9e94;
  --sw-cyan:    #2e93a8;
  --sw-sky:     #3b8cc4;
  --sw-blue:    #4a7fd0;
  --sw-indigo:  #6168c4;
  --sw-violet:  #7c5fc4;
  --sw-purple:  #9558c0;
  --sw-fuchsia: #b558ba;
  --sw-pink:    #c45a86;
  --sw-rose:    #c45a68;
  --sw-slate:   #64748b;

  /* --- Typography (Wynxx = Geist) --- */
  --font-sans: "Geist", "Geist Fallback", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
  --font-display: "Geist", var(--font-sans);   /* Wynxx wordmark face */

  /* Type scale — the canonical Tailwind/shadcn steps, matching the live
     /typography page exactly (root = 16px). (Was a compressed admin scale.) */
  --text-2xs:  0.6875rem;  /* 11px — app micro-label extension (no live step; just below xs) */
  --text-xs:   0.75rem;    /* 12px — live --text-xs */
  --text-sm:   0.875rem;   /* 14px — live --text-sm */
  --text-base: 1rem;       /* 16px — live --text-base */
  --text-lg:   1.125rem;   /* 18px — live --text-lg */
  --text-xl:   1.25rem;    /* 20px — live --text-xl */
  --text-2xl:  1.5rem;     /* 24px — live --text-2xl */
  --text-3xl:  1.875rem;   /* 30px — live --text-3xl */
  --text-4xl:  2.25rem;    /* 36px — live --text-4xl */
  --text-5xl:  3rem;       /* 48px — live --text-5xl */

  /* Weights / leading / tracking — mirror the live --font-weight-* / --leading-* / --tracking-* */
  --fw-normal: 400; --fw-medium: 500; --fw-semibold: 600; --fw-bold: 700;
  --leading-tight: 1.25; --leading-normal: 1.5; --leading-relaxed: 1.625;
  --tracking-tight: -0.025em; --tracking-normal: 0; --tracking-wider: 0.05em;

  /* --- Layout --- */
  --header-h:            64px;
  --footer-h:            24px;   /* brand footer (GFT logo + version/docs pin) */
  --sidebar-w:           256px;
  --sidebar-collapsed-w: 46px;
  /* Effective side-panel width — flips to the collapsed rail via body.sb-collapsed
     so the docked header `left` and any other consumer update atomically. */
  --current-sidebar-w:   var(--sidebar-w);
  --row-h:               32px;
  --row-h-hdr:           26px;

  /* --- Border radius (Wynxx base 10px — live --radius .625rem) --- */
  --r-sm:   6px;
  --r-md:   8px;
  --r-lg:   10px;
  --r-xl:   14px;
  --r-2xl:  14px;
  --r-pill: 999px;
}

/* -----------------------------------------------------------------------
   Reset + scrollbar
   ----------------------------------------------------------------------- */
* {
  box-sizing: border-box;
  scrollbar-width: thin;
  scrollbar-color: var(--divider) transparent;
}
/* One consistent gray scrollbar — does NOT brighten on hover / focus / drag. */
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--divider); border-radius: var(--r-sm); }
::-webkit-scrollbar-thumb:hover,
::-webkit-scrollbar-thumb:active { background: var(--divider); }
::-webkit-scrollbar-corner { background: transparent; }

/* -----------------------------------------------------------------------
   Documentation pages — typography, prose, layout
   ----------------------------------------------------------------------- */
html { height: 100%; }
body {
  margin: 0; padding-top: var(--header-h);
  font-family: var(--font-sans); background: var(--bg); color: var(--text);
  font-size: var(--text-base); line-height: var(--leading-normal); height: 100%; overflow: hidden;
}
/* Native form controls don't inherit font-family — without this, buttons/inputs
   render in the UA default font instead of Geist (matches Tailwind's preflight,
   which the live Wynxx site relies on). This is why buttons "looked different". */
button, input, select, textarea, optgroup { font-family: inherit; }
/* Docs prose is anchored to the same token scale as the portal so the two
   render at one density. Headings are em-based (relative to this --text-base),
   and the em-based prose below (code, pre, tables, .ascii, .tier) scales with it. */
.container { width: 100%; padding: 40px 24px 80px; box-sizing: border-box; font-size: var(--text-base); }
/* Headings — live Wynxx/shadcn type: Geist display face, tight tracking, and the
   FOREGROUND colour (brand is reserved for links / active / the logo, never title text). */
h1, h2, h3, h4, h5, h6 { font-family: var(--font-display); letter-spacing: -0.01em; color: var(--text); }
h1 { font-size: var(--text-3xl); border-bottom: 1px solid var(--border); padding-bottom: 12px; }
h2 { margin-top: 2.2em; border-bottom: 1px dashed var(--border); padding-bottom: 6px; }
h3 { margin-top: 1.6em; }
.container h1 { font-size: 1.6em; }
.container h2 { font-size: 1.3em; }
.container h3 { font-size: 1.1em; }
code { font-family: var(--font-mono); background: var(--code-bg); padding: 1px 6px; border-radius: var(--r-md); font-size: 0.9em; color: var(--code-string); }
pre {
  background: var(--code-bg); border: 1px solid var(--border); border-radius: var(--r-lg);
  padding: 14px 16px; overflow-x: auto; font-size: 0.85em; line-height: 1.5;
}
pre code { background: none; padding: 0; color: var(--text); }
table { border-collapse: collapse; width: 100%; margin: 14px 0; font-size: 0.9em; }
th, td { border: 1px solid var(--border); padding: 8px 10px; text-align: left; vertical-align: top; }
th { background: var(--panel); color: var(--text); }
.tier { background: var(--panel); border-left: 4px solid var(--accent); padding: 14px 18px; margin: 16px 0; border-radius: var(--r-md); }
.tier.t2 { border-left-color: var(--accent2); }
.tier.t3 { border-left-color: var(--green); }
.badge { display: inline-block; box-sizing: border-box; font-size: 0.7em; padding: 2px 8px; border-radius: var(--r-2xl); font-weight: 600; vertical-align: middle; margin-right: 6px;
  /* Soft-tinted (live convention) — --c hue -> 14% tint + coloured text + hairline. */
  --c: var(--accent);
  background: color-mix(in srgb, var(--c) 14%, var(--panel));
  color: var(--c);   /* deepen the hue toward the foreground so coloured text stays legible on the pale tint (light) / dark panel — adapts per theme via --text */
  border: 1px solid color-mix(in srgb, var(--c) 32%, var(--panel)); }
.badge.t2 { --c: var(--accent2); }
.badge.t3 { --c: var(--green); }
.muted { color: var(--muted); }
.warn { color: var(--red); font-weight: 600; }
ul li { margin: 4px 0; }
.toc { background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-lg); padding: 14px 22px; }
.toc ol, .toc ul { margin: 4px 0; }
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
.ascii { background: var(--code-bg); padding: 14px; border: 1px solid var(--border); border-radius: var(--r-lg); font-family: var(--font-mono); font-size: 0.78em; white-space: pre; overflow-x: auto; line-height: 1.3; }
.diagram { background: var(--code-bg); border: 1px solid var(--border); border-radius: var(--r-lg); padding: 20px; margin: 18px 0; overflow-x: auto; text-align: center; }
/* Diagram text uses the same scale as the regular prose: primary labels at
   --text-base, muted annotations at --text-sm. The svg font-size is the inherited
   base, so a label with no explicit size renders at prose size; densely-packed
   diagrams that set their own smaller font-size keep it (so long lines still fit).
   The .diagram-compact modifier keeps a tight/dense diagram legible at a fixed
   small size (e.g. the landing hero's narrow fixed-width boxes). */
.diagram svg { max-width: 100%; height: auto; font-size: var(--text-base); }
.diagram svg text[fill="#64748b"] { font-size: var(--text-sm); }
.diagram-compact svg { font-size: 11px; }
.diagram-compact svg text[fill="#64748b"] { font-size: 11px; }
/* Diagram container theming — see the --dg-* tokens. Leaf shapes keep their solid
   fill attribute; only CONTAINER boxes (and their labels) are token-driven so they
   flip dark on the dark canvas instead of glaring as light boxes. A CSS rule beats a
   presentation attribute, so the light fill="" attrs stay as a no-CSS fallback. */
.diagram .dg-cli-box    { fill: var(--dg-cli-bg);  stroke: var(--dg-cli-stroke); }
.diagram .dg-wrap-box   { fill: var(--dg-wrap-bg); stroke: var(--dg-wrap-stroke); }
.diagram .dg-wrap-tab   { fill: var(--dg-wrap-bg); }
.diagram .dg-cli-label  { fill: var(--dg-cli-label); }
.diagram .dg-wrap-label { fill: var(--dg-wrap-label); }
/* Muted in-diagram annotations/captions — theme-aware (SVG text can't read a var
   via a presentation attribute, so route it through this class). */
.diagram .dg-note       { fill: var(--muted); }
.legend { display: flex; gap: 18px; flex-wrap: wrap; font-size: 0.82em; margin-top: 10px; color: var(--muted); }
.legend span { display: inline-flex; align-items: center; gap: 6px; }
.legend i { display: inline-block; width: 12px; height: 12px; border-radius: var(--r-sm); }
nav.crumbs { font-size: 0.85em; color: var(--muted); margin-bottom: 18px; }
nav.crumbs a { color: var(--muted); }
nav.crumbs a:hover { color: var(--accent); }
.card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 16px; margin: 22px 0; }
.card { background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-lg); box-shadow: var(--shadow-sm); padding: 16px 18px; }
.card h3 { margin: 0 0 6px 0; }
.card p { margin: 6px 0 0 0; color: var(--muted); font-size: 0.9em; }
/* Services cards (admin Services view + /services) — regular .card; title uses the
   SYSTEM title style (--text, not brand), icon muted. Moved here from a page <style>. */
.svc-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 6px; }
.svc-head-left { display: flex; align-items: center; gap: 8px; min-width: 0; }
.svc-head-left h3 { margin: 0; font-size: var(--text-lg); font-weight: 700; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.svc-icon { display: flex; align-items: center; color: var(--muted); flex-shrink: 0; }
.svc-icon svg { width: 18px; height: 18px; display: block; }
.svc-status { display: flex; align-items: center; gap: 6px; font-size: var(--text-sm); color: var(--muted); flex-shrink: 0; }
.card .status { display: flex; gap: 6px; margin-bottom: 8px; font-size: 0.68em; text-transform: uppercase; letter-spacing: 0.05em; align-items: center; }
.card .status .ver { margin-left: auto; color: var(--muted); font-family: var(--font-mono); letter-spacing: 0; text-transform: none; font-size: 0.95em; }

/* Docs prose utilities — replace per-element inline styles (single source of truth) */
.feature-teaser { text-align: center; font-size: var(--text-xl); font-weight: 600; color: var(--text); margin: 24px 0 16px; }
.ascii.accent  { color: var(--accent); }
.ascii.accent2 { color: var(--accent2); }
.ascii .hl { font-weight: 600; color: var(--text); }
.ascii .hl-accent2 { color: var(--accent2); }
.caption { color: var(--muted); font-size: var(--text-sm); margin: 6px 0 14px; }
.footer-note { color: var(--muted); font-size: var(--text-sm); margin-top: 48px; border-top: 1px solid var(--border); padding-top: 16px; }
.ol-compact { margin: 6px 0 0 18px; }
.ascii-toggle { margin: 12px 0; }
.ascii-toggle > summary { cursor: pointer; color: var(--muted); }

/* -----------------------------------------------------------------------
   Portal chrome — fixed header, nav, content wrapper
   ----------------------------------------------------------------------- */
/* Collapsed rail: flip the shared width var so the docked header `left`, and any
   other consumer, follow the sidebar to the narrow rail in one place. */
body.sb-collapsed { --current-sidebar-w: var(--sidebar-collapsed-w); }
.portal-header {
  /* Docked to the RIGHT of the side panel: starts where the rail ends and tracks
     its width (transition matches the sidebar's width transition). */
  position: fixed; top: 0; left: var(--current-sidebar-w); right: 0; z-index: 300;
  transition: left 0.18s ease;
  background: var(--header-bg);
  border-bottom: 1px solid var(--border);
  box-shadow: var(--shadow-header);
  /* Left padding matches the logo↔text gap (10px) so the brand hugs the left edge with the same
     breathing room on each side of the logo; right is 14px to match the right-group's inner gap
     (`.portal-right`), so the project name / org tag have equal spacing to each other AND to the
     right border in every mode. */
  padding: 0 14px 0 10px; display: flex; align-items: center; gap: 32px; height: var(--header-h);
}
/* "Mind" aligns to the wordmark's vertical centre: the inline Wynxx vector centres
   its letters within its viewBox, so centre-align (not bottom) lines "Mind" up with
   the "Wynxx" text. Server mode also centres the stacked Mind+server. */
.portal-brand {
  text-decoration: none; display: inline-flex; align-items: center; flex-shrink: 0;
}
.portal-brand--server { align-items: center; }
.portal-brand:hover { text-decoration: none; }
/* Wynxx wordmark logo (replaces the old ASCII synapse art). */
.portal-logo-img { height: 37px; width: auto; display: block; }
/* Tool name + optional server sub-label, stacked to the right of the logo — so
   "server" sits BELOW "Mind" in server mode. Centre-aligned with the wordmark
   (no baseline lift needed now the logo is a centred inline vector). */
.portal-brand-tool { display: inline-flex; align-items: center; gap: 6px; margin-left: -3px; }
.portal-brand--server .portal-brand-tool { padding-bottom: 0; }
/* "Mind" = the neural mark + the "Mind" wordmark text after it. Text reuses the
   monospace two-blue gradient (the icon carries the same two brand blues, solid).
   In server mode this becomes a ROW so the "server" mark sits to the RIGHT of "Mind". */
.portal-brand-textcol { display: inline-flex; flex-direction: column; justify-content: center; }
/* Server mode: "server" is stacked BELOW "Mind" but ABSOLUTELY positioned, so it never displaces
   "Mind" — Mind keeps the exact size AND position it has in standalone. */
.portal-brand--server .portal-brand-textcol { align-items: center; position: relative; }
.portal-mind-text {
  font-family: var(--font-mono); font-size: 1.7rem; font-weight: 400;
  text-transform: uppercase; letter-spacing: 0.01em; line-height: 1.05;
  background: linear-gradient(90deg, #2F47E6 0%, #8A9CFA 100%);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
}
/* Server-mode sub-label — a small mark hugging the underside of "Mind" (absolute, so Mind stays
   put), widened with UNIFORM letter-spacing (not justify-stretch) toward "MIND" width. `text-indent`
   offsets the trailing letter-spacing so the spaced word stays centred. */
.portal-brand-server {
  position: absolute; left: 0; right: 0; top: 100%; margin-top: -3px;
  font-family: var(--font-display); font-size: 0.5rem; font-weight: 300;
  line-height: 1; text-transform: lowercase;
  /* The span is exactly "Mind" wide (left/right:0 inside the Mind-width textcol); inter-character
     justify then spreads "server" edge-to-edge to fill it — so its length matches Mind exactly. */
  text-align: justify; text-align-last: justify; text-justify: inter-character;
  color: var(--brand-server-color); opacity: 0.4;
}
.portal-nav { display: flex; gap: 4px; align-items: center; flex: 1; }
/* Most pages put their sections in the sidebar, so the header nav is empty — collapse it
   then, so it doesn't add a second flex gap that would offset the ticker from centre. */
.portal-nav:empty { display: none; }
.nav-link { padding: 6px 14px; border-radius: var(--r-lg); font-size: var(--text-base); color: var(--muted); text-decoration: none; transition: background 0.12s, color 0.12s; }
.nav-link:hover { background: rgba(var(--accent-rgb),0.08); color: var(--text); text-decoration: none; }
.nav-link.active { background: rgba(var(--accent-rgb),0.14); color: var(--accent); font-weight: 600; }
/* Right-side header group: project name + org tag + the server "admin" link, in that order.
   nav's `flex: 1` pushes this group to the right edge; the group's own `gap` (14px) spaces its
   members evenly, matching the header's 14px right padding — so name↔tag, tag↔border and
   name↔border (standalone, no tag) are all the same distance. */
.portal-right { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }

/* -----------------------------------------------------------------------
   Portal footer — the lowest layer: full-width bar pinned to the very bottom;
   every other region ends at its top edge. GFT logo left, version/docs pin right.
   Same surface as the side panel (var(--panel)) with a hairline top border, so it
   reads as one continuous chrome — auto-themes via the token. The GFT mark is
   inlined with currentColor and tinted with --logo-color so it stays legible on
   the panel in both light and dark (the shipped svg is white-on-brand-blue).
   ----------------------------------------------------------------------- */
.portal-footer {
  position: fixed; left: 0; right: 0; bottom: 0; height: var(--footer-h); z-index: 100;
  display: flex; align-items: center; justify-content: space-between; padding: 0 14px;
  background: var(--panel); border-top: 1px solid var(--border);
}
.portal-footer-logo { display: inline-flex; align-items: center; color: var(--logo-color, var(--text)); }
.portal-footer-logo svg, .portal-footer-logo img { height: 12px; width: auto; display: block; }   /* gft wordmark (gft.png 78×18) */
/* Reuse the side panel's version-pin styling as-is (muted slate on the panel), only
   dropping its sidebar padding. */
.portal-footer .sidebar-version { padding: 0; }
/* Admin/org badge: a stretched, vertically-centered box tinted system-yellow — reads as an
   "admin area" badge. Scoped under .portal-header so it beats `.nav-link.active`. Horizontal
   placement now comes from `.portal-right`'s gap (no margin-left:auto), keeping the 8px
   top/bottom inset for the boxed look. */
/* Local/org/admin header tag — ghost-style pill matching the sun/moon buttons:
   same 32px height, gray border, side-menu (--panel) surface, small muted text. */
.portal-header .nav-admin {
  flex-shrink: 0; display: inline-flex; align-items: center;
  height: 32px; padding: 0 10px; box-sizing: border-box;
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-md);
  color: var(--muted); font-size: var(--text-sm); font-weight: 500;
}
.portal-header .nav-admin:hover { background: var(--panel); border-color: var(--accent); color: var(--accent); }
.portal-header .nav-admin.active { background: var(--panel); border-color: var(--border); color: var(--muted); }
/* Org tag — same ghost pill but accent-tinted text to stay distinct. */
.portal-header .nav-org,
.portal-header .nav-org.active { color: var(--accent); font-size: var(--text-sm); }
.portal-header .nav-org:hover { color: var(--accent); border-color: var(--accent); }
/* Project switcher in the header — a widened ghost pill (like the org tag) whose TRIGGER shows the
   current project name + caret, opening a modern popup (project-switcher.js) grouped under Local/org
   dividers. */
.hdr-project { position: relative; flex-shrink: 0; display: inline-flex; }
.hdr-project-btn {
  display: inline-flex; align-items: center; gap: 6px; height: 32px; box-sizing: border-box;
  min-width: 190px; max-width: 340px; padding: 0 10px;
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-md);
  color: var(--text); font-size: var(--text-sm); font-weight: 500; cursor: pointer;
  transition: border-color 0.12s;
}
.hdr-project-btn:hover, .hdr-project-btn[aria-expanded="true"] { border-color: var(--accent); }
.hdr-project-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: left; }
.hdr-project-caret { color: var(--muted); font-size: 0.7rem; flex-shrink: 0; }
/* Popup — mirrors the user-menu popup: floating panel, rounded, shadowed. Fixed-positioned by JS. */
.hdr-project-pop {
  position: fixed; z-index: 1200; min-width: 200px; max-width: 360px; max-height: 60vh; overflow-y: auto;
  background: var(--panel, var(--bg)); border: 1px solid var(--border); border-radius: 10px;
  box-shadow: var(--shadow-pop); padding: 6px;
}
/* Type-to-filter box, pinned at the top of the popup. */
.hdr-project-search {
  width: 100%; box-sizing: border-box; margin: 0 0 6px; padding: 6px 10px;
  background: var(--input, var(--surface-3)); border: 1px solid var(--border); border-radius: var(--r-md);
  color: var(--text); font-size: var(--text-sm); font-family: inherit;
}
.hdr-project-search:focus { outline: none; border-color: var(--accent); }
/* Group header (Local / <org>) — SAME look as a side-panel root group label
   (.sidebar-group-hdr:not(.sidebar-panel-trigger)): brighter-gray, mixed-case, small, weight 300.
   A division line separates each group from the one above. */
.hdr-project-group {
  font-size: var(--text-xs); font-weight: 300; text-transform: none; letter-spacing: normal;
  line-height: 18px; padding: 6px 10px 4px;
  color: var(--muted);
  color: color-mix(in srgb, var(--muted), var(--text) 45%);
}
.hdr-project-pop .hdr-project-group:not(:first-of-type) { margin-top: 8px; padding-top: 10px; border-top: 1px solid var(--border); }
/* Client org header: org name + its server domain (small, dimmed) so two servers hosting the same
   org name read as distinct groups. */
.hdr-project-group-server { margin-left: 6px; font-size: calc(var(--text-xs) - 1px); color: var(--muted); opacity: 0.7; }
/* Project row — full-width option, rounded hover pill (like the sidebar nav links). */
.hdr-project-item {
  display: flex; align-items: center; width: 100%; box-sizing: border-box; gap: 8px;
  padding: 7px 10px; background: none; border: none; border-radius: var(--r-md);
  color: var(--text); font-size: var(--text-sm); font-family: inherit; text-align: left; cursor: pointer;
}
/* `hidden` must win over display:flex (filter hides non-matching rows / empty group headers). */
.hdr-project-item[hidden], .hdr-project-group[hidden] { display: none; }
.hdr-project-item:hover { background: var(--sidebar-accent); color: var(--accent); }
.hdr-project-item.active { background: var(--sidebar-accent); color: var(--text); font-weight: 600; }
.hdr-project-item-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Client-init affordance — a terminal icon BESIDE the project switcher (frontend-server mode only)
   that opens a popup with the `jmind init …` command + a Copy button. */
/* No margin — spacing comes from `.portal-right { gap: 10px }`, uniform with the other header items. */
.hdr-init { position: relative; flex-shrink: 0; display: inline-flex; }
/* Match the header theme-toggle buttons (.header-theme-opt): 36x32 ghost pill. */
.hdr-init-btn { display: inline-flex; align-items: center; justify-content: center; width: 36px; height: 32px;
  padding: 0; box-sizing: border-box; background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--r-md); color: var(--muted); cursor: pointer; }
.hdr-init-btn:hover, .hdr-init-btn[aria-expanded="true"] { color: var(--accent); border-color: var(--accent); }
.hdr-init-btn svg { display: block; width: 16px; height: 16px; }
.hdr-init-pop { position: fixed; z-index: 1200; display: flex; flex-direction: column; gap: 8px;
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-md);
  box-shadow: var(--shadow-pop); padding: 12px 14px; max-width: min(560px, 92vw); }
/* `display:flex` above would defeat the `hidden` attribute — restore hide when hidden (JS toggles it). */
.hdr-init-pop[hidden] { display: none; }
.hdr-init-hd { font-size: var(--text-2xs); font-weight: 600; color: var(--muted);
  text-transform: uppercase; letter-spacing: .04em; }
/* Command line + copy icon on ONE row. */
.hdr-init-row { display: flex; align-items: center; gap: 8px; }
.hdr-init-cmd { flex: 1 1 auto; min-width: 0; font-family: var(--font-mono, ui-monospace, monospace);
  font-size: var(--text-xs); color: var(--text); background: var(--secondary); border-radius: var(--r-sm, 6px);
  padding: 6px 8px; overflow-x: auto; white-space: nowrap; user-select: all; }
.hdr-init-copy { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; background: var(--secondary); border: 1px solid var(--border);
  border-radius: var(--r-sm, 6px); color: var(--muted); cursor: pointer; }
.hdr-init-copy:hover { color: var(--accent); border-color: var(--accent); }
.hdr-init-copy svg { display: block; width: 15px; height: 15px; }
.portal-content { width: 100%; padding: 28px 24px 80px; box-sizing: border-box; }
.portal-content { width: 100%; padding: 28px 24px 80px; box-sizing: border-box; }
.portal-project { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.portal-project-name { font-size: var(--text-sm); font-weight: 600; color: var(--text); white-space: nowrap; }
.portal-project-pid  { font-size: var(--text-xs); color: var(--muted); font-family: var(--font-mono); white-space: nowrap; }
/* Header event ticker — vertically cycles recent audit events */
.hdr-ticker {
  /* In-flow flex item filling the gap between the nav (left) and .portal-right (right).
     The header's 32px flex `gap` gives equal breathing room on each side, and
     .portal-right (flex-shrink:0) reserves its own width — so the ticker can never
     overlap the project name. The div is ALWAYS rendered (empty when the polling is
     suppressed) and a dominant flex-grow makes IT — not the nav — take the free middle
     space, so it right-pins .portal-right in every mode. The empty header nav is hidden
     (.portal-nav:empty), so the ticker stays centred between the toggle and the project
     group with equal gaps. min-width:0 lets it self-hide when the header is narrow. */
  position: relative; flex: 100 1 auto; min-width: 0;
  height: var(--header-h);
  overflow: hidden; pointer-events: none;
}
.hdr-tick-item {
  position: absolute; width: 100%;
  /* Center the dynamic event content within the ticker grower (which itself fills the header's
     middle), so the event reads centered in the header. Paired with .tick-data's flex:0 1 auto
     below — a growing data span would eat the free space and defeat the centering. */
  display: flex; align-items: center; justify-content: center; gap: 6px;
  height: var(--header-h);
  font-size: var(--text-sm); white-space: nowrap; overflow: hidden;
  animation: tick-drop 5s ease-in-out forwards;
}
/* Event type: regular weight, same size as the event-data text (.hdr-tick-item
   --text-sm) — only the severity colour distinguishes it, not a heavier weight. */
.tick-info  { color: var(--divider); font-weight: 400; }
.tick-warn  { color: var(--tick-warn); font-weight: 400; }
.tick-error { color: var(--tick-error); font-weight: 400; }
.tick-debug { color: var(--border); font-weight: 400; }
.tick-chat  { color: var(--divider); font-weight: 400; }
/* Sized to its content (no flex-grow) so the whole event group stays centered; min-width:0 +
   overflow lets it still shrink and CSS-ellipsis-truncate when the data is too long for the row. */
.tick-data  { color: var(--muted); flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
/* Provider mark LEADS the row, a touch smaller than the audit pages' 1.2rem, with a
   little extra breathing room before the event type (on top of the row's 6px gap). */
.tick-prov { flex: 0 0 auto; margin-right: -2px; opacity: 0.5; }
.hdr-tick-item .tick-prov.prov-badge::before { width: 0.95rem; height: 0.95rem; }
.tick-origin { color: var(--divider); font-size: var(--text-xs); font-family: var(--font-mono); }
/* Origin icon-only: jarvis-origin events show the Mind mark; the user/jarvis text
   label is dropped (the span is only emitted for jarvis). */
.tick-origin-jarvis { display: inline-flex; align-items: center; }
.tick-origin-jarvis::before {
  content: ""; display: block; width: 1rem; height: 1rem;
  background: url("/static/mind-mark.svg") center/contain no-repeat;
}
@keyframes tick-drop {
  0%   { transform: translateY(-52px); opacity: 0; }
  12%  { transform: translateY(0);     opacity: 1; }
  80%  { transform: translateY(0);     opacity: 1; }
  100% { transform: translateY(52px);  opacity: 0; }
}
@keyframes tick-hold {
  0%   { transform: translateY(-52px); opacity: 0; }
  20%  { transform: translateY(0);     opacity: 1; }
  100% { transform: translateY(0);     opacity: 1; }
}
.hdr-tick-item.tick-held { animation: tick-hold 0.6s ease-out forwards; }
.portal-upgrade-btn {
  display: flex; align-items: center; gap: 6px; flex-shrink: 0;
  padding: 5px 12px; border-radius: var(--r-lg);
  background: rgba(var(--sw-yellow-rgb),0.12); border: 1px solid rgba(var(--sw-yellow-rgb),0.35);
  color: var(--sw-yellow); font-size: var(--text-sm); font-family: inherit;
  cursor: pointer; transition: background 0.12s, border-color 0.12s; white-space: nowrap;
}
.portal-upgrade-btn:hover { background: rgba(var(--sw-yellow-rgb),0.22); border-color: rgba(var(--sw-yellow-rgb),0.60); }
.portal-upgrade-btn:disabled { opacity: 0.55; cursor: not-allowed; }

/* Self-upgrade progress modal (upgrade.js) — non-closable while running. Each
   step is a shared `.task-row`; the layout mirrors the document-type creation
   rows (label on the left, status indicator on the right: the `.doc-spinner`
   ring while in-flight, a colour-coded ✓ / ! / ✗ when done). */
.jm-upg-sub { margin: 2px 0 12px; font-size: var(--text-sm); }
/* Sync modal subline: spinner ring + text inline while syncing. */
.jm-sync-sub { display: flex; align-items: center; gap: 8px; }
/* The sync progress popup is a slim transient — only as wide as its one-line description text
   (hug the content, with a sensible cap), tight padding, no close button. */
.modal-box.jm-sync-box { width: max-content; max-width: 320px; padding: 14px 16px; }
.modal-box.jm-sync-box .modal-header { margin-bottom: 8px; }
.modal-box.jm-sync-box .jm-upg-sub { margin: 0; }
.jm-upg-sub.jm-upg-ok-text { color: var(--green); }
.jm-upg-sub.jm-upg-err-text { color: var(--red); }
.jm-upg-steps { max-height: 46vh; overflow-y: auto; }
.jm-upg-step.task-row { align-items: center; font-size: var(--text-sm); }
.jm-upg-step .tr-activity { flex: 1; min-width: 0; }
.jm-upg-glyph { flex: 0 0 auto; width: 1.1em; text-align: center; font-weight: 700; }
.jm-upg-ok   .jm-upg-glyph { color: var(--green); }
.jm-upg-warn .jm-upg-glyph { color: var(--sw-yellow); }
.jm-upg-err  .jm-upg-glyph { color: var(--red); }
.jm-upg-info .jm-upg-glyph { color: var(--muted); }
.jm-upg-active .jm-upg-label { color: var(--text); }
/* Not-yet-started phase: muted label + hollow dot, so the full list reads as
   upcoming work while only the active phase spins. */
.jm-upg-pending .jm-upg-glyph,
.jm-upg-pending .jm-upg-label { color: var(--muted); }

/* -----------------------------------------------------------------------
   Portal sidebar — collapsible nav panel
   ----------------------------------------------------------------------- */
/* Frame spanning the whole viewport down to the footer's top edge. The side
   panel is the left column (top → footer); the header docks over the right column's
   top strip (.docs-main reserves header-h so content clears it).
   `position: absolute` (NOT fixed) is deliberate: body is height:100%/overflow:hidden so
   it never scrolls — absolute pins to the same viewport rect as fixed would — but a fixed
   ancestor ALWAYS creates a stacking context, which would trap every overlay rendered
   inside this frame (modals z1000, toasts z10000, …) below the header's z-index:300.
   Absolute with no z-index creates no stacking context, so those fixed overlays escape to
   the root and stack by their own z-index per the documented ladder. Do NOT change to fixed. */
.docs-layout { position: absolute; top: 0; left: 0; right: 0; bottom: var(--footer-h); display: flex; align-items: stretch; overflow: hidden; }
.docs-sidebar {
  position: relative; /* containing block for absolutely-pinned children */
  width: var(--sidebar-w); flex-shrink: 0; background: var(--panel); border-right: 1px solid var(--border);
  transition: width 0.18s ease; overflow: hidden; display: flex; flex-direction: column; height: 100%; z-index: 10;
}
.docs-sidebar.collapsed { width: var(--sidebar-collapsed-w); }
.docs-main { flex: 1; min-width: 0; height: 100%; overflow-y: auto; padding-top: var(--header-h); }
/* Brand at the very top of the side panel (relocated from the header). Expanded:
   the Wynxx wordmark + "Mind". Collapsed (46px rail): just the favicon — the
   wordmark is hidden and the favicon shown, mirroring the icon/text swap used by
   the sidebar groups. */
/* The brand sits in a top band the same height as the header, so the Wynxx logo
   lines up on the header's centre-line; left-aligned within the side panel. */
.sidebar-brand {
  flex-shrink: 0; height: var(--header-h); box-sizing: border-box;
  display: flex; align-items: center; justify-content: flex-start; gap: 4px;
  padding: 0 14px 0 16px; text-decoration: none;
}
.sidebar-brand:hover { text-decoration: none; }
.sidebar-brand .sidebar-wordmark { display: inline-flex; align-items: center; gap: 4px; min-width: 0; }
/* Wynxx wordmark — tuned brand size in the side panel. */
.sidebar-brand .portal-logo-img { height: 36.5px; }
/* "Mind" text runs a touch smaller in the side panel than in the header. */
.sidebar-brand .portal-mind-text { font-size: 1.5rem; }
/* Server mode keeps "Mind" the SAME size/place as standalone — the "server" label is a compact
   vertical mark beside it (see .portal-brand-server), not a stacked second line, so no shrink. */
.sidebar-brand-favicon { display: none; align-items: center; justify-content: center; }
.sidebar-brand-favicon img { width: 22px; height: 22px; display: block; }
/* The mind node-graph mark sits AFTER the "Mind" word (expanded wordmark only — it lives inside
   .sidebar-wordmark, so it's hidden when collapsed, where the wynxx mark takes over via the favicon).
   Flex-centered so the mark aligns to the optical middle of the "Mind" text, not its baseline. */
.sidebar-brand-mind { display: inline-flex; align-items: center; align-self: center; margin-left: -4px; }
.sidebar-brand-mind img { height: 28px; width: auto; display: block; }
/* Standalone (no "server" sub-label) is a single "Mind" line, so match the mark to the "Mind" cap
   height (1.5rem) and let it sit level with the word — server mode keeps the larger 28px mark
   against its taller Mind+server stack. */
.sidebar-brand:not(.portal-brand--server) .sidebar-brand-mind img { height: 1.5rem; }
.docs-sidebar.collapsed .sidebar-brand { justify-content: center; padding-left: 0; padding-right: 0; }
.docs-sidebar.collapsed .sidebar-brand .sidebar-wordmark { display: none; }
.docs-sidebar.collapsed .sidebar-brand-favicon { display: flex; }
.sidebar-toggle {
  /* Pinned to the top-right, on the Dashboard title's line. Absolute (out of flow)
     so its position never depends on which siblings are hidden when collapsed —
     fixed top in both states. top centers it on the 44px title row (nav pad 2 + 22 - 13 = 11). */
  position: absolute; top: 11px; right: 10px; z-index: 2;
  display: flex; align-items: center; justify-content: center;
  width: 26px; height: 26px;
  background: none; border: 1px solid var(--border); border-radius: var(--r-md);
  color: var(--muted); cursor: pointer; font-size: 0.65rem; flex-shrink: 0;
  transition: color 0.12s, border-color 0.12s;
}
.sidebar-toggle:hover { color: var(--text); border-color: var(--accent); }
.sidebar-toggle-icon { display: inline-block; transition: transform 0.18s ease; }
/* Header expand/contract control (first child, left) — icon button with a subtle
   button-style background (a muted tint that reads on both light and dark headers).
   Its collapse state is keyed off body.sb-collapsed. */
.header-sidebar-toggle {
  display: flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; flex-shrink: 0; margin-left: 3px;
  /* Background matches the PAGE background (black in dark, white in light) so the control reads
     flush with the canvas rather than as a tinted button. */
  background: var(--bg); border: none; border-radius: var(--r-md);
  color: var(--text); cursor: pointer; padding: 0;   /* --text reads white on the dark header */
  transition: background 0.12s, color 0.12s;
}
.header-sidebar-toggle:hover { background: var(--bg); color: var(--accent); }
.header-sidebar-toggle svg { display: block; width: 16px; height: 16px; }
/* Light/dark toggle (last child, far right) — both sun + moon always visible, each in
   its OWN button container. The non-selected option is GHOST (transparent, faded); the
   selected option is REGULAR (filled button background) with a blue (--accent) icon. */
.header-theme-toggle { display: flex; align-items: center; gap: 10px; flex-shrink: 0; margin-right: 1px; }
.header-theme-opt {
  display: flex; align-items: center; justify-content: center;
  width: 36px; height: 32px; padding: 0; box-sizing: border-box; border-radius: var(--r-md);
  /* ghost: the side-menu surface (var(--panel) — dark gray in dark mode) + gray border. */
  background: var(--panel); border: 1px solid var(--border);
  color: var(--text); cursor: pointer;
  transition: background 0.18s, border-color 0.18s, color 0.18s, box-shadow 0.18s;
}
.header-theme-opt:hover { color: var(--accent); border-color: var(--accent); }
.header-theme-opt.active {
  /* selected = .btn-primary: solid --primary fill, --on-accent text, medium shadow. */
  background: var(--primary); border-color: var(--primary); color: var(--on-accent);
  box-shadow: var(--shadow-md);
}
.header-theme-opt.active:hover { background: var(--primary-hover); border-color: var(--primary-hover); color: var(--on-accent); }
.header-theme-opt svg { display: block; width: 16px; height: 16px; flex-shrink: 0; }
/* Nudge the moon (second) button 1px left. */
.header-theme-toggle .header-theme-opt:last-child { margin-left: -1px; }
.docs-sidebar.collapsed .sidebar-toggle-icon { transform: rotate(180deg); }
/* Project switcher — pinned at the bottom of the sidebar (above the footer), below
   the Configuration trigger. Built by sidebar.js, filled by project-switcher.js.
   A modern <select>: native arrow removed, ▾ caret via ::after; hidden when collapsed. */
/* ┌──────────────────────────────────────────────────────────────────────────────────────┐
   │ COLLAPSED-RAIL CONVENTION — keep ALL rail controls consistent, or they drift/jump.     │
   │ This bit it twice (project/user selectors + org-combo). The rules:                     │
   │  1. LEFT-ANCHOR every leading icon at the 12px column — NEVER `justify-content:center`. │
   │     The rail width animates (240→46px); a centered icon re-flows rightward during that  │
   │     transition while left-anchored nav icons stay put, so the centered one visibly      │
   │     drifts and lands off the column. 12px = items'/box's 8px left pad + 4px icon inset  │
   │     = (46-22)/2, the centre of the collapsed rail. Same value in BOTH states → no jump. │
   │  2. RESERVE the dropdown's height on the icon's row (`min-height`, ≈30px for these,     │
   │     42px for org-combo). Collapsed the <select> is display:none; without a reserved     │
   │     height the row shrinks to bare icon height and stacked selectors bunch together.    │
   │  3. Expanded-only tightening (negative margins, padding-top:0) must be UNDONE in the    │
   │     `.collapsed` rules so the equal-height containers space evenly like the nav rows.   │
   └──────────────────────────────────────────────────────────────────────────────────────┘ */
.sidebar-project { order: 100; flex-shrink: 0; padding: 3px 8px; display: flex; flex-direction: column; align-items: stretch; gap: 4px; }
/* Small caption above the selector — labels the switcher, no icon. */
.sidebar-project-label { font-size: var(--text-2xs); font-weight: 500; text-transform: uppercase; letter-spacing: 0.06em; color: rgba(var(--muted-rgb),0.55); padding: 0 2px; }
/* The control row holds the select + rename pencil + collapsed icon; it's the positioning context
   for the ▾ caret so the caret centers on the select, not the label+row wrapper. */
/* min-height reserves the expanded <select>'s height (≈30px), so when collapsed (icon only)
   the row stays a full-height container — the icon centres in it and the two selectors keep the
   same vertical rhythm as the dropdowns instead of shrinking to bare icon height and bunching up. */
.sidebar-project-row { position: relative; display: flex; align-items: center; gap: 6px; min-height: 30px; }
.sidebar-project-row::after {
  content: "▾"; position: absolute; right: 42px; top: 50%;
  transform: translateY(-50%); pointer-events: none;
  color: var(--muted); font-size: 0.7rem;
}
/* When the rename pencil is hidden (server mode) the select fills the row (the collapsed-rail
   icon is display:none when expanded), so the caret sits at the select's own right edge — inside
   its 28px padding-right reserve, matching the org-combo caret (14px). */
.sidebar-project.no-edit .sidebar-project-row::after { right: 14px; }

/* Manager USERS (data-visibility) selector — pinned directly under the Project box (drop the top
   padding so the two read as one stacked control). Select-only row, so it reuses `.no-edit`. */
/* Users box sits tight under the Project box (no top padding) — they read as one stacked control.
   In the collapsed rail it stays visible: the select hides and its leading icon centers (clicking
   it expands the sidebar), exactly like the Project switcher. */
.sidebar-view-scope { padding-top: 0; margin-top: -2px; }
/* Collapsed rail: drop the expanded-only tightening so the two equal-height icon containers
   (Project + Users) space evenly, like the nav rows. */
.docs-sidebar.collapsed .sidebar-view-scope { margin-top: 0; padding-top: 3px; }
.sidebar-proj-edit { background: none; border: none; color: var(--muted); cursor: pointer; font-size: var(--text-base); line-height: 1; padding: 4px; flex-shrink: 0; }
/* Constrain the pencil SVG to 16px — otherwise it renders at its intrinsic size, widening the
   button and pushing the row's ▾ caret (positioned a fixed distance from the right) off the
   select's edge. 16px + 4px padding ≈ the old '✎' glyph width the caret offset was tuned for. */
.sidebar-proj-edit svg { width: 16px; height: 16px; display: block; }
.sidebar-proj-edit:hover { color: var(--accent); }
.sidebar-project select {
  appearance: none; -webkit-appearance: none; -moz-appearance: none;
  flex: 1; width: auto; box-sizing: border-box; padding: 6px 28px 6px 12px;
  background: var(--bg); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--r-lg);
  font-family: inherit; font-size: var(--text-sm); font-weight: 600;
  cursor: pointer; outline: none;
  transition: border-color 0.12s, box-shadow 0.12s, background-color 0.12s;
}
.sidebar-project select:hover { border-color: var(--muted); background: var(--panel); }
.sidebar-project select:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(var(--accent-rgb), 0.18); }
.sidebar-project option { background: var(--panel); color: var(--text); }
/* In-group combobox (server Organization current-org switcher) — same control look as the project
   switcher, sized to the group's indented items. */
/* Sits inside .sidebar-items (already 8px side padding) — so only a small 4px inset to align with
   the group's links, no extra horizontal padding (which doubled the side space). The native arrow
   is removed (appearance:none), so a ▾ caret is drawn via ::after, like the project switcher. */
/* min-height reserves the expanded select's height so that, collapsed (icon only), the rows
   BELOW the combo stay at the same Y — no vertical jump on contract. */
.sidebar-org-combo { position: relative; padding: 6px 4px; display: flex; align-items: center; gap: 6px; min-height: 42px; box-sizing: border-box; }
.sidebar-org-combo::after {
  content: "▾"; position: absolute; right: 14px; top: 50%;
  transform: translateY(-50%); pointer-events: none;
  color: var(--muted); font-size: 0.7rem;
}
.sidebar-org-combo-icon { flex-shrink: 0; color: var(--muted); background: none; border: none; padding: 0; cursor: pointer; }
/* Hidden when expanded (the select carries the label); shown only as the collapsed-rail
   affordance — same pattern as the project switcher's icon. High-specificity so it beats the
   generic `.sidebar-icon { display:flex }` (the combo icon carries both classes). */
.docs-sidebar:not(.collapsed) .sidebar-org-combo-icon { display: none; }
.sidebar-org-combo-icon:hover { color: var(--accent); }
.sidebar-org-combo-icon svg { width: 16px; height: 16px; display: block; }
.sidebar-org-combo select {
  appearance: none; -webkit-appearance: none; -moz-appearance: none;
  flex: 1; width: auto; box-sizing: border-box; padding: 6px 24px 6px 10px;
  background: var(--bg); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--r-md);
  font-family: inherit; font-size: var(--text-sm); font-weight: 600; cursor: pointer; outline: none;
}
.sidebar-org-combo select:hover { border-color: var(--muted); background: var(--panel); }
.sidebar-org-combo select:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(var(--accent-rgb), 0.18); }
/* Collapsed rail: hide the <select> + caret but KEEP the leading icon, LEFT-anchored at the same
   12px edge as every other rail icon (items' 8px + 4px). Don't center — centering re-flows the
   icon rightward as the rail width animates, so it drifts away from the fixed project/user/nav
   icons during the collapse. Left-anchored = pinned, no drift. */
.docs-sidebar.collapsed .sidebar-org-combo { padding: 6px 0 6px 4px; justify-content: flex-start; }
.docs-sidebar.collapsed .sidebar-org-combo-icon { display: flex; align-items: center; justify-content: center; }
.docs-sidebar.collapsed .sidebar-org-combo select,
.docs-sidebar.collapsed .sidebar-org-combo::after { display: none; }
/* Project icon — the leading meaning-icon (tooltip "Project") to the LEFT of the select, always
   visible; in the collapsed rail it also replaces the hidden <select> and, clicked, expands the
   sidebar + opens the picker (wired in sidebar.js). */
/* Leading meaning-icons for the Project + Users selectors. Sized to the 22px nav-icon slot with a
   4px margin (inside the box's 8px padding ⇒ left edge at 12px), so they sit on the SAME vertical
   line as the nav-group icons, and the select that follows starts at ~40px — the nav-text inset. */
.sidebar-proj-icon, .sidebar-sel-icon {
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
  width: 22px; padding: 0; margin-left: 4px; line-height: 0;
  background: none; border: none; color: var(--muted); cursor: pointer;
}
.sidebar-proj-icon:hover, .sidebar-sel-icon:hover { color: var(--accent); }
.sidebar-proj-icon svg, .sidebar-sel-icon svg { width: 18px; height: 18px; display: block; }
/* Collapsed rail: hide the select / rename pencil / caption / caret, but KEEP the box + icon at
   their EXPANDED left positioning (box padding-left 8px + icon margin-left 4px = the 12px left
   edge every nav icon uses). Do NOT recenter — recentering makes the icon slide right during the
   collapse animation and land off the nav-icon line. The leading icon stays put, fixed like the
   rest. */
.docs-sidebar.collapsed .sidebar-project-label,
.docs-sidebar.collapsed .sidebar-project-row > select,
.docs-sidebar.collapsed .sidebar-proj-edit,
.docs-sidebar.collapsed .sidebar-project-row::after { display: none; }
.sidebar-nav { flex: 1; overflow-y: auto; overflow-x: hidden; padding: 2px 0 24px; }
.sidebar-version { flex-shrink: 0; padding: 8px 14px 12px; font-size: var(--text-2xs); color: rgba(var(--muted-rgb),0.55); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: right; }
/* The Config trigger's synced-revision badge — same muted color/size as the footer version pin. */
.sidebar-rev { margin-left: 6px; font-size: var(--text-2xs); color: rgba(var(--muted-rgb),0.55); }
/* Server-edit config-page banner: unpublished staged changes → Apply / Discard (below the header).
   Same surface as the side panel (--panel bg + --border); the Apply CTA is orange (primary). */
.jm-cfg-banner { display: flex; align-items: center; gap: 12px; margin: 0 0 14px; padding: 10px 14px; border: 1px solid var(--border); background: var(--panel); border-radius: var(--r-md); font-size: var(--text-sm); }
.jm-cfg-banner-icon { flex: 0 0 auto; width: 24px; height: 24px; background-color: var(--accent2); -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10.29 3.86L1.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'/%3E%3Cline x1='12' y1='9' x2='12' y2='13'/%3E%3Cline x1='12' y1='17' x2='12.01' y2='17'/%3E%3C/svg%3E") center/contain no-repeat; mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10.29 3.86L1.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'/%3E%3Cline x1='12' y1='9' x2='12' y2='13'/%3E%3Cline x1='12' y1='17' x2='12.01' y2='17'/%3E%3C/svg%3E") center/contain no-repeat; }
.jm-cfg-banner-msg { flex: 1; color: var(--text); }
.jm-cfg-banner-actions { display: flex; gap: 8px; flex-shrink: 0; }
/* In the sidebar, the version is its OWN line — the very last entry (after Sign out, order 220),
   smaller than the trigger rows above it. */
.docs-sidebar > .sidebar-version { order: 230; padding: 4px 14px 6px; font-size: var(--text-2xs); }
/* Compact "docs ↗" link on the version line. */
.sidebar-docs-link { color: rgba(var(--muted-rgb),0.55); text-decoration: none; display: inline-flex; align-items: center; gap: 3px; }
.sidebar-docs-link:hover { color: var(--accent); }
.sidebar-docs-icon { display: inline-flex; }
.sidebar-docs-icon svg { width: 11px; height: 11px; display: block; }
.docs-sidebar.collapsed .sidebar-version { display: none; }
/* Collapsed: keep the standalone version line's SPACE (just hide its text), so the items above
   it don't shift up when the sidebar contracts. */
.docs-sidebar.collapsed > .sidebar-version { display: block; visibility: hidden; }
.sidebar-group-hdr {
  display: flex; align-items: center; gap: 8px; width: 100%; padding: 8px 12px;
  background: none; border: none; color: var(--text); font-size: var(--text-sm); font-weight: 600;
  cursor: pointer; text-align: left; white-space: nowrap; transition: color 0.12s;
}
.sidebar-group-hdr:hover { color: var(--accent); }
/* Root group headers (Journey, System, …): line ends at the left of the name
   (──────── NAME ▾). Clicking collapses/expands the group. In collapsed mode the
   text/caret are hidden leaving just the ::before line as a thin separator. */
/* Root group headers (Journey, System, …): reworked into plain WHITE, left-aligned,
   mixed-case section labels — no leading divider line, no caret, not collapsible
   (so styled as a label, not a button). */
.sidebar-group-hdr:not(.sidebar-panel-trigger) {
  height: auto; padding: 6px 10px 6px 14px; min-height: 0; overflow: visible; gap: 6px;
  margin-top: 16px;   /* breathing room above each section label */
  line-height: 18px;
  font-size: var(--text-xs); font-weight: 300; text-transform: none; letter-spacing: normal;
  color: var(--muted);   /* fallback for browsers without color-mix */
  color: color-mix(in srgb, var(--muted), var(--text) 45%);   /* brighter gray (toward white in dark) */
  cursor: default;
}
.sidebar-group-hdr:not(.sidebar-panel-trigger):hover {
  color: var(--muted);
  color: color-mix(in srgb, var(--muted), var(--text) 45%);
}
/* No leading divider line, no dropdown caret. */
.sidebar-group-hdr:not(.sidebar-panel-trigger)::before { content: none; }
.sidebar-group-hdr:not(.sidebar-panel-trigger) .sidebar-caret { display: none; }
/* Label sits at the left; the group icon is hidden when expanded. */
.docs-sidebar:not(.collapsed) .sidebar-group-hdr:not(.sidebar-panel-trigger) .sidebar-text,
.overlay-panel .sidebar-group-hdr:not(.sidebar-panel-trigger) .sidebar-text { flex: 0 0 auto; }
.docs-sidebar:not(.collapsed) .sidebar-group-hdr:not(.sidebar-panel-trigger) .sidebar-icon,
.overlay-panel .sidebar-group-hdr:not(.sidebar-panel-trigger) .sidebar-icon { display: none; }
/* No separate border or extra top margin — the header's own symmetric padding gives
   equal space above and below the divider line. */
.sidebar-group + .sidebar-group { margin-top: 0; padding-top: 0; }
/* Bare separator line for a header-less group (`divider: true`) — same colour/thickness as a group
   header's ::before line, with the header's symmetric 4px+18px+4px vertical rhythm so rows below sit
   at the same Y as a labeled group's would. */
.sidebar-divider { height: 1px; margin: 12.5px 10px; background: rgba(var(--muted-rgb),0.25); }
/* Governed "No access" placeholder shown in place of the ability groups when the signed-in user
   is not a data participant on the active project (see build_sidebar_payload). A muted empty-state
   message centred in the sidebar body: the nav is `flex:1` (fills the height), so centring the nav
   itself when it holds only the note puts the message at the true vertical middle. */
.sidebar-nav:has(.sidebar-note) { display: flex; align-items: center; justify-content: center; }
.sidebar-note {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 10px; padding: 0 14px; text-align: center;
  color: var(--muted); font-size: 0.85em; opacity: 0.5;   /* deliberately faint — a quiet empty state */
}
.sidebar-note-icon { color: var(--muted); opacity: 0.7; }
.sidebar-note-icon svg { width: 30px; height: 30px; }
.docs-sidebar.collapsed .sidebar-note { display: none; }
.docs-sidebar.collapsed .sidebar-divider { margin: 12.5px 0; }
/* Collapsed rail: group header becomes a thin divider line. Keep the SAME padding,
   min-height and inter-group margin as the expanded header (the text just goes
   invisible) so every row below sits at the same Y in both states — no vertical
   jump. The ::before line centers in the reserved height as the separator. */
/* Collapsed rail: with no divider line, the root label has nothing to show, so hide
   it entirely (its items still render as the icon rail below). */
/* Collapsed rail: the section label KEEPS its space (margin + min-height matching the
   expanded box) so icons below stay at the same Y; its text/caret/icon are hidden and
   a small centered divider line (::before) marks the section boundary. */
.docs-sidebar.collapsed .sidebar-group-hdr:not(.sidebar-panel-trigger) { min-height: 30px; }
.docs-sidebar.collapsed .sidebar-group-hdr:not(.sidebar-panel-trigger)::before {
  content: ''; flex: 1; height: 1px; background: rgba(var(--muted-rgb),0.25); align-self: center;
}
.docs-sidebar.collapsed .sidebar-group-hdr:not(.sidebar-panel-trigger) .sidebar-icon { display: none; }
/* Every other row (flat links, direct section links, subgroup headers, panel
   triggers) KEEPS its expanded padding/margin in collapsed mode — text/caret are
   hidden but the icon stays at its expanded 14px left edge and same Y, so nothing
   jumps when the sidebar toggles. No per-row collapsed override is needed. */
/* Icon box is as tall as the text line (≥ inherited 1.6 line-height) so the icon —
   not the text — sets the row height. The row is then the SAME height whether text
   shows (expanded) or is hidden (collapsed): no cumulative vertical drift. */
.sidebar-icon { font-size: 1.05rem; flex-shrink: 0; width: 22px; height: 22px; text-align: center; display: flex; align-items: center; justify-content: center; }
.sidebar-icon--svg { color: inherit; }
.sidebar-icon--svg svg { display: block; }
.sidebar-link .sidebar-icon { color: var(--sidebar-fg); }
.sidebar-link:hover .sidebar-icon { color: var(--accent); }
.sidebar-link.active .sidebar-icon { color: var(--text); }
/* Caret SVG sizing */
.sidebar-caret--svg { display: flex; align-items: center; }
.sidebar-caret--svg svg { width: 10px; height: 10px; transition: transform 0.14s; }
.sidebar-group-hdr[aria-expanded="false"] .sidebar-caret--svg svg,
.sidebar-subgroup-hdr[aria-expanded="false"] .sidebar-caret--svg svg { transform: rotate(-90deg); }
.sidebar-text { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.docs-sidebar.collapsed .sidebar-text,
.docs-sidebar.collapsed .sidebar-caret { display: none; }
/* Items container stays visible in collapsed mode. Author display:block beats the
   UA [hidden]{display:none} by origin precedence, so direct section links
   (Documents/Planner/Quests) and subgroup-header icons show even for closed groups.
   Padding is left at the base (0 8px) — identical to expanded — so icons never move. */
.docs-sidebar.collapsed .sidebar-items { display: block; }
.sidebar-caret { font-size: 0.6rem; color: var(--muted); transition: transform 0.14s; }
/* Rotate ONLY the text-fallback caret span (▾). When the caret is an SVG, the inner <svg> is rotated
   instead (see .sidebar-caret--svg svg rule); rotating BOTH the span AND the svg compounds to -180°,
   which is why the collapsed chevron pointed UP instead of right. */
.sidebar-group-hdr[aria-expanded="false"] .sidebar-caret:not(.sidebar-caret--svg),
.sidebar-subgroup-hdr[aria-expanded="false"] .sidebar-caret:not(.sidebar-caret--svg) { transform: rotate(-90deg); }
/* No bottom padding: the group divider's own symmetric padding provides the gap, so
   the space above a separator equals the space below it (no extra trailing gap). */
.sidebar-items { padding: 0 8px; }
.sidebar-link {
  display: flex; align-items: center; gap: 6px; padding: 4px 8px 4px 26px;
  font-size: var(--text-sm); color: var(--sidebar-fg); text-decoration: none; border-radius: var(--r-md);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; transition: background 0.1s, color 0.1s;
}
.sidebar-link:hover { background: var(--sidebar-accent); color: var(--accent); text-decoration: none; }
.sidebar-link.disabled { opacity: 0.45; pointer-events: none; }   /* e.g. Config with no active project */
.sidebar-link.active { background: var(--sidebar-accent); color: var(--text); font-weight: 600; }
/* Framework release-stage pill (ability.yaml `stage:`) beside a sidebar link — poc=red,
   alpha=yellow, beta=green, rc=blue. A generic mechanism: any ability declaring `stage:` gets one. */
.stage-pill { flex: 0 0 auto; margin-left: 4px; font-size: 9px; font-weight: 700; line-height: 1;
  letter-spacing: 0.04em; padding: 2px 5px; border-radius: 999px; border: 1px solid transparent;
  text-transform: uppercase; }
.stage-pill.stage-poc   { color: #b91c1c; background: rgba(239, 68, 68, 0.16); border-color: rgba(239, 68, 68, 0.45); }
.stage-pill.stage-alpha { color: #a16207; background: rgba(234, 179, 8, 0.16); border-color: rgba(234, 179, 8, 0.45); }
.stage-pill.stage-beta  { color: #15803d; background: rgba(34, 197, 94, 0.16); border-color: rgba(34, 197, 94, 0.45); }
.stage-pill.stage-rc    { color: #1d4ed8; background: rgba(59, 130, 246, 0.16); border-color: rgba(59, 130, 246, 0.45); }
/* Keep the release-stage pill hugging the ability NAME: the label (.sidebar-text) is flex:1 and would
   otherwise stretch and shove the pill to the far right of the row. On links that carry a pill, let the
   label size to its content (still ellipsis-truncates via min-width:0) so the pill sits right after it. */
.sidebar-link:has(.stage-pill) .sidebar-text { flex: 0 1 auto; min-width: 0; }
/* Collapsed rail hides text — hide the pill too. */
.docs-sidebar.collapsed .stage-pill { display: none; }
/* Same pill mirrored beside a page's main title (see reflectStageOnTitle) — align to the heading. */
.section-title .stage-pill { margin-left: 8px; vertical-align: middle; }
/* All icon rows put the icon's LEFT EDGE at 12px = (46-22)/2, the centre of the 46px
   collapsed rail. Fixed in both states so icons never move when toggling. Here:
   sidebar-items has 8px left padding, so 4px = 12px. */
.sidebar-items > .sidebar-link { padding-left: 4px; }
.sidebar-items > .sidebar-link .sidebar-icon { font-size: 0.95rem; }
/* Extra vertical separation between the 2nd-level options (a group's direct links) in the main
   side panel. Scoped to .docs-sidebar so the config/docs overlay lists are unaffected. */
.docs-sidebar .sidebar-items > .sidebar-link,
.overlay-panel .sidebar-items > .sidebar-link { margin-bottom: 4px; }
/* Flat-group links (e.g. Home) match the standard item geometry so their active
   pill is the SAME size as the others: 8px container inset + 4px link padding. */
.sidebar-group--flat { padding: 0 8px; }
.sidebar-group--flat .sidebar-link { gap: 6px; padding: 4px 8px 4px 4px; font-size: var(--text-sm); font-weight: 400; }
/* Subgroup sub-header — middle tier of the sidebar (group → subgroup → links) */
.sidebar-subgroup-hdr {
  display: flex; align-items: center; gap: 6px; width: 100%; padding: 5px 8px 5px 4px;
  background: none; border: none; color: var(--muted); font-size: var(--text-sm); font-weight: 700;
  cursor: pointer; text-align: left; white-space: nowrap; transition: color 0.12s;
}
.sidebar-subgroup-hdr:hover { color: var(--accent); }
.sidebar-subgroup-hdr .sidebar-icon { font-size: 0.95rem; }
.sidebar-subgroup-hdr .sidebar-caret { font-size: 0.55rem; }
.sidebar-subgroup-items { padding: 0; }
/* Nested leaf links indent past an icon-bearing subgroup header's text (8px container
   + 6px header-padding + 22px icon + 6px gap = 42px total → 34px relative). */
.sidebar-subgroup-items > .sidebar-link { padding-left: 34px; }
/* Subgroup headers keep their expanded geometry in collapsed mode (icon stays at
   14px); only the nested leaf links are hidden from the rail. */
.docs-sidebar.collapsed .sidebar-subgroup-items { display: none; }

/* -----------------------------------------------------------------------
   Overlay panels (Documentation, Configuration) — a bottom-pinned trigger
   opens a panel that slides OVER the sidebar. Reuses .modal-header/.modal-close
   for the header, .side-panel-title for the title, and .sidebar-items for the
   body — no duplicate blocks.
   ----------------------------------------------------------------------- */
.overlay-panel {
  /* Slides over the side panel, so it spans the same extent: viewport top → footer
     top edge (matching .docs-sidebar, which now starts at top:0). Width tracks the
     SHARED rail var so it collapses/expands in lockstep with the sidebar + docked
     header (header toggle sets body.sb-collapsed) — never overlapping the header. */
  position: fixed; top: 0; left: 0; bottom: var(--footer-h);
  width: var(--current-sidebar-w); box-sizing: border-box; padding: 0;
  transition: width 0.18s ease;
  background: var(--panel); border-right: 1px solid var(--border);
  box-shadow: var(--shadow-modal); z-index: 20;   /* above sidebar(10), below header(300) */
  /* The panel itself does NOT scroll — the items region does — so the version
     footer stays bottom-pinned (mirrors .docs-sidebar: nav scrolls, version
     fixed). padding-bottom:0 keeps the version flush at the bottom like the
     side panel. */
  display: none; flex-direction: column; overflow: hidden;
}
/* The overlay body (the buildGroup nav, same as the main side panel) fills + scrolls, so the brand
   (top) and the user identity row (bottom) stay pinned — mirroring the main sidebar. */
.overlay-panel > .sidebar-nav { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
.overlay-panel .sidebar-items { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
/* The SAME user identity row as the main side panel, pinned at the overlay's bottom. */
.overlay-panel > .sidebar-user-menu-wrap { position: relative; padding: 5px 8px 8px; }
.overlay-panel.open { display: flex; }
/* Collapsed rail: the overlay reuses the sidebar's collapse behavior (driven by the
   shared body.sb-collapsed) — text, carets and the (icon-less) section labels hide,
   leaving the link icons as a 46px rail, so the panel contracts/expands in lockstep
   with the header toggle instead of covering it. */
body.sb-collapsed .overlay-panel .sidebar-text,
body.sb-collapsed .overlay-panel .sidebar-caret { display: none; }
/* Collapsed rail: overlay section headers keep their space + show a small centered
   divider line (matching the main sidebar's root groups), so icons stay aligned. */
/* The overlay's section headers sit INSIDE .sidebar-items (8px side padding), whereas
   the main sidebar's root-group labels are direct children of .sidebar-group (no inset).
   Trim 8px off each side here so the collapsed divider line spans the SAME extent
   ([14px, rail-10px]) as the main sidebar's — otherwise it reads ~16px shorter. */
body.sb-collapsed .overlay-panel .sidebar-subgroup-hdr { min-height: 30px; padding-left: 6px; padding-right: 2px; }
body.sb-collapsed .overlay-panel .sidebar-subgroup-hdr::before {
  content: ''; flex: 1; height: 1px; background: rgba(var(--muted-rgb),0.25); align-self: center;
}
body.sb-collapsed .overlay-panel .modal-header { display: none; }   /* no close in the 46px rail */
/* The overlay's cloned brand collapses to the favicon too (the .docs-sidebar.collapsed
   brand rules are scoped to the sidebar, not the overlay). */
body.sb-collapsed .overlay-panel .sidebar-brand { justify-content: center; padding-left: 0; padding-right: 0; }
body.sb-collapsed .overlay-panel .sidebar-brand .sidebar-wordmark { display: none; }
body.sb-collapsed .overlay-panel .sidebar-brand-favicon { display: flex; }
/* No horizontal panel padding (above): the panel slides over the sidebar, so its
   content aligns with the main sidebar's — section links land at the shared 12px
   column (8px .sidebar-items + 4px link), like a main-sidebar direct item. The
   header keeps a 14px inset so its title lines up with the Dashboard title (14px). */
/* The close (×) floats in the top-right of the brand band (height = --header-h),
   so the overlay's top reads identically to the main side panel's brand band — the
   brand sits flush-left at the same Y, with only the × added in the corner. The
   modal-header is taken out of flow so it adds no extra row above the items. */
.overlay-panel .modal-header {
  position: absolute; top: 0; right: 0; z-index: 2;
  height: var(--header-h); margin: 0; padding: 0 10px;
  display: flex; align-items: center;
}
/* Overlay panels run through the SAME buildGroup pipeline as the main side panel. Levels:
   • ROOT group header (General, CLI, Development) → the standard faint section label
     (.sidebar-group-hdr:not(.sidebar-panel-trigger)) — inherited, no override.
   • Direct group links (Overview, a root group's pages) → the standard .sidebar-link OPTION. */
/* ── ONE rule for EVERY 1st-level option, whatever its render path — a flat-group link (Overview),
   a root group's direct link (CLI/Development pages), or a subgroup header (Journey/System/
   Configuration). None carry an icon, so a small +4px left nudge (padding-left 8px, container adds
   8px → text at 16px) keeps them off the edge; sharing ONE indent makes them all align identically,
   whether or not they have sub-options. */
.overlay-panel .sidebar-group--flat > .sidebar-link,
.overlay-panel .sidebar-items > .sidebar-link,
.overlay-panel .sidebar-subgroup-hdr { margin-bottom: 4px; font-weight: 400; font-size: 0.92rem; }
/* The +4px nudge applies ONLY to rows WITHOUT an icon (docs Overview, subgroup headers, CLI/Dev
   pages). Icon-BEARING rows (most config sections, and the main sidebar) keep the base 4px so their
   icon sits on the same 12px rail — so config renders pixel-identical to the main side panel. */
.overlay-panel .sidebar-group--flat > .sidebar-link:not(:has(.sidebar-icon)),
.overlay-panel .sidebar-items > .sidebar-link:not(:has(.sidebar-icon)),
.overlay-panel .sidebar-subgroup-hdr:not(:has(.sidebar-icon)) { padding-left: 10px; }
/* Subgroup-header extras (it's collapsible): the row look + a white expand caret. padding-left comes
   from the shared rule above, so Journey aligns exactly with Overview. */
.overlay-panel .sidebar-subgroup-hdr {
  margin-top: 0; gap: 6px; padding-top: 5px; padding-bottom: 5px; padding-right: 8px; padding-left: 6px;
  border-radius: var(--r-md); font-size: 0.92rem; text-transform: none; letter-spacing: normal;
  line-height: normal; color: var(--sidebar-fg); cursor: pointer;
}
.overlay-panel .sidebar-subgroup-hdr .sidebar-icon { font-size: 0.95rem; }
.overlay-panel .sidebar-subgroup-hdr::before { content: none; }
.overlay-panel .sidebar-subgroup-hdr:hover { background: var(--sidebar-accent); color: var(--accent); }
/* Expand caret: white, bigger; pinned right; points RIGHT when collapsed, down when expanded. */
.overlay-panel .sidebar-subgroup-hdr .sidebar-caret { display: flex; margin-left: auto; color: var(--sidebar-fg); }
.overlay-panel .sidebar-subgroup-hdr .sidebar-caret--svg svg { width: 15px; height: 15px; transform-origin: center; }
.overlay-panel .sidebar-subgroup-hdr[aria-expanded="false"] .sidebar-caret--svg svg { transform: rotate(-90deg); }
.overlay-panel .sidebar-subgroup-hdr[aria-expanded="true"] .sidebar-caret--svg svg { transform: rotate(0deg); }
/* 2nd level — leaf pages under a subgroup: deeper, with the left LINE embracing them; smaller + lighter. */
/* The embracing LINE sits at the MIDDLE of the 1st-level icon rail: icon left edge = 12px (8px items
   padding + 4px header padding-left), icon ~16px wide → centre ≈ 20px. margin-left is measured inside
   the General group's .sidebar-items (8px padding), so 12px → line at 20px. Fixed position, so it
   stays put whether or not the header row carries an icon. */
.overlay-panel .sidebar-subgroup-items { margin-left: 16px; margin-top: -4px; border-left: 1px solid var(--border); padding-left: 4px; }
.overlay-panel .sidebar-subgroup-items > .sidebar-link { padding-left: 14px; margin-bottom: 2px; font-size: 0.84rem; font-weight: 300; }
/* Version pin — its own small last line, matching the main sidebar's version style
   (smaller font + tighter padding). It's bottom-pinned by the flex-grow .sidebar-items
   above it, not margin tricks. */
.overlay-panel .sidebar-version { padding: 4px 14px 6px; font-size: var(--text-2xs); }
/* Bottom panel triggers — reuse .sidebar-group-hdr (which already carries the
   collapsed-state centering); only the bottom pin is new.
   Stacked triggers (direct children of aside, not in footer) appear below the
   project switcher (order:100) but above the footer (order:200). */
.sidebar-panel-trigger { flex-shrink: 0; color: var(--muted); font-weight: 500; }
.docs-sidebar > .sidebar-panel-trigger { order: 150; }
.sidebar-panel-trigger:hover { color: var(--text); }
/* Disabled trigger (e.g. Config with no active project) — greyed and non-clickable. */
.sidebar-panel-trigger.disabled { opacity: .45; pointer-events: none; }
.sidebar-panel-trigger.active { color: var(--accent); }
/* Footer row — the icon-only Configuration cog (left) on the same line as the
   version (right). The row owns the top divider so its children don't each draw
   one; collapsed, it stacks back to a centred cog (the version is hidden). */
.sidebar-footer { order: 200; display: flex; align-items: center; }
.sidebar-footer .sidebar-panel-trigger { width: auto; border-top: none; }
.sidebar-footer .sidebar-version { margin-left: auto; border-top: none; padding: 8px 14px; }
/* The Admin entry is a bottom trigger-styled LINK (reuses .sidebar-group-hdr.sidebar-panel-trigger):
   reset the anchor defaults and tint it system-yellow so it reads as the admin entry. Ordered
   BELOW the Documentation footer (order:200) so Documentation sits above Admin/Projects. */
.sidebar-server-link { text-decoration: none; color: var(--sw-yellow); }
.docs-sidebar > .sidebar-server-link { order: 210; }
/* Sign out — a red bottom option, the LAST entry (below Admin/Projects). */
.docs-sidebar > .sidebar-signout { order: 220; }
.sidebar-signout, .sidebar-signout .sidebar-icon { text-decoration: none; color: var(--red); }
.sidebar-signout:hover, .sidebar-signout:hover .sidebar-icon { color: var(--red); filter: brightness(1.15); }
/* User menu (server mode) — a bottom trigger showing the signed-in username. Clicking it opens a
   FLOATING popup (Admin/Projects + Sign out) that overlays the side panel — it does not expand
   inline, so the rail's layout never shifts. The wrapper takes the slot the standalone Sign out
   used (order 220) and is the positioning context; the version line (order 230) stays below it. */
.docs-sidebar > .sidebar-user-menu-wrap { order: 220; position: relative; padding: 5px 8px 8px; }
.docs-sidebar.collapsed > .sidebar-user-menu-wrap { padding: 4px 0; }
/* Taller identity row: avatar + two-line text + kebab, vertically centred. An inset ROUNDED box
   (like the nav links / .sidebar-link) — the hover pill sits inset from the panel edges. */
.sidebar-user-menu {
  border-top: none; align-items: center; gap: 10px; min-height: 46px;
  padding: 6px 8px; margin: 0; width: 100%; box-sizing: border-box; border-radius: var(--r-md);
  transition: background 0.1s, color 0.1s;
}
.sidebar-user-menu[aria-expanded="true"] { color: var(--accent); }
/* Hover: the same rounded pill highlight the nav links use. */
.sidebar-user-menu:hover { background: var(--sidebar-accent); color: var(--text); }
/* Other bottom side-panel triggers (Configuration, mode switch, Sign out) highlight on hover too. */
.docs-sidebar > .sidebar-panel-trigger:hover { background: rgba(var(--muted-rgb), 0.14); }
/* Rounded-square avatar with the user's initials — like the sun/moon toggle button but NO border. */
.sidebar-user-avatar {
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
  width: 32px; height: 32px; margin-left: -2px; border-radius: var(--r-lg); border: none;
  background: var(--secondary, rgba(var(--muted-rgb), 0.18)); color: var(--text);
  font-family: var(--font-display); font-size: 0.78rem; font-weight: 600; letter-spacing: 0.02em;
  text-transform: uppercase; line-height: 1;
}
/* Username (top) + email (bottom, future) stacked; both ellipsize. */
.sidebar-user-text { display: flex; flex-direction: column; justify-content: center; min-width: 0; gap: 1px; line-height: 1.2; }
.sidebar-user-name { font-size: var(--text-sm); font-weight: 600; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sidebar-user-email { font-size: var(--text-xs); color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sidebar-user-email:empty { display: none; }
/* Kebab (3 dots) pinned far right. */
.sidebar-user-kebab { margin-left: auto; margin-right: 0; display: flex; align-items: center; justify-content: center; flex-shrink: 0; color: var(--muted); }
.sidebar-user-kebab svg { width: 16px; height: 16px; display: block; }
.sidebar-user-menu[aria-expanded="true"] .sidebar-user-kebab { color: var(--accent); }
/* The popup opens to the RIGHT of the row. position:fixed (left/bottom set by sidebar.js) so it
   escapes the sidebar's overflow:hidden clip and overlays the page. Items stack in DOM order. */
.sidebar-user-menu-pop {
  position: fixed; z-index: 1200; min-width: 180px;
  background: var(--panel, var(--bg)); border: 1px solid var(--border); border-radius: 8px;
  box-shadow: var(--shadow-pop); padding: 4px 0; overflow: hidden;
}
.sidebar-user-menu-pop .sidebar-panel-trigger { border-top: none; }
/* Highlight each popup option on hover. */
.sidebar-user-menu-pop .sidebar-panel-trigger:hover { background: rgba(var(--muted-rgb), 0.16); color: var(--text); }
.sidebar-user-menu-pop .sidebar-panel-trigger:hover .sidebar-icon { color: var(--text); }
/* Collapsed rail: the username text/caret hide (icon only); the popup would have nowhere sensible
   to anchor, so suppress it entirely (the toggle re-opens the rail first anyway). */
/* Collapsed rail: show the avatar only (hide the text column + kebab), centred; the popup would
   have nowhere sensible to anchor, so suppress it (the toggle re-opens the rail first anyway). */
.docs-sidebar.collapsed .sidebar-user-menu { justify-content: center; gap: 0; padding-left: 0; padding-right: 0; margin-left: 0; margin-right: 0; }
.docs-sidebar.collapsed .sidebar-user-avatar { margin-left: 0; }
.docs-sidebar.collapsed .sidebar-user-text,
.docs-sidebar.collapsed .sidebar-user-kebab { display: none; }
.docs-sidebar.collapsed .sidebar-user-menu-pop { display: none; }
/* Divider line above each bottom stacked trigger (Configuration, and the Server/Client
   mode-switch entry) — regular group headers get a ::before divider but panel-triggers are
   excluded, so the bottom section would otherwise blend into the nav. Scoped under .docs-sidebar
   to out-specify `.sidebar-group-hdr { border: none }`. The footer (Documentation + version) draws
   its own top border, so it's excluded. */
.docs-sidebar > .sidebar-panel-trigger { border-top: 1px solid var(--border); }
/* Config trigger row (client mode): the trigger fills the row, a sync icon sits at the far right.
   The row owns the top divider so the trigger inside doesn't draw its own. */
.sidebar-trigger-row { order: 150; display: flex; align-items: stretch; border-top: 1px solid var(--border); }
.sidebar-trigger-row > .sidebar-panel-trigger { flex: 1; border-top: none; }
.sidebar-sync-btn { flex-shrink: 0; display: flex; align-items: center; background: none; border: none; color: var(--muted); cursor: pointer; padding: 8px 12px; }
.sidebar-sync-btn:hover { color: var(--accent2); }
.sidebar-sync-btn.jm-sync-stale { color: var(--red); }            /* local config is behind the server */
.sidebar-sync-btn.jm-sync-stale:hover { color: var(--red); filter: brightness(1.15); }
.sidebar-sync-btn svg { width: 16px; height: 16px; display: block; }
.docs-sidebar.collapsed .sidebar-sync-btn { display: none; }
.sidebar-server-link:hover { text-decoration: none; color: var(--sw-yellow); }
.sidebar-server-link .sidebar-icon { color: var(--sw-yellow); }
/* "Back to projects" (the client_link / config-panel exit) reads WHITE (--text),
   not the admin-yellow — it's a plain context switch, not the Admin entry. */
.sidebar-back-link, .sidebar-back-link:hover { color: var(--text); }
.sidebar-back-link .sidebar-icon { color: var(--text); }
.docs-sidebar.collapsed .sidebar-footer { display: block; }
.docs-sidebar.collapsed .sidebar-footer .sidebar-panel-trigger { width: 100%; }
/* .sidebar-group-hdr carried only a :hover rule; add the canonical active token
   so the trigger (and any active header) highlights like an active link. */
.sidebar-group-hdr.active { color: var(--accent); }

/* -----------------------------------------------------------------------
   Portal dashboard — home / landing page
   ----------------------------------------------------------------------- */
.lp-hero { margin-bottom: 28px; }
.lp-hero-row { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; border-bottom: 1px solid var(--border); padding-bottom: 12px; }
/* The full-width divider lives on the row; the inline-flex h1 drops the global
   h1 underline so the line spans the whole hero, not just the name. */
.lp-hero h1 { font-size: var(--text-2xl); font-weight: 700; color: var(--text); margin: 0; display: inline-flex; align-items: center; gap: 8px; border-bottom: none; padding-bottom: 0; }
.lp-pid { color: var(--muted); font-size: var(--text-base); font-family: var(--font-mono); white-space: nowrap; }
.lp-edit { background: none; border: none; color: var(--muted); cursor: pointer; font-size: var(--text-base); line-height: 1; padding: 0 2px; }
.lp-edit:hover { color: var(--accent); }
.lp-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 16px; }
.lp-card { background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-lg); box-shadow: var(--shadow-sm); padding: 18px 20px; }
/* The remaining .lp-* row/kv/link/badge classes were specific to the old static
   landing page and were removed with it; the overview dashboard reuses only
   .lp-hero / .lp-grid / .lp-card above. */

/* -----------------------------------------------------------------------
   Overview dashboard widgets — layered ON the .lp-card / .lp-grid classes
   (markup uses class="lp-grid dash-grid" / "lp-card dash-card"), so grid
   sizing and card surface are NOT duplicated here; only the widget chrome,
   body, drag states, and the mini bar / status dot tokens are added.
   ----------------------------------------------------------------------- */
.dash-card { display: flex; flex-direction: column; }
.dash-card-hdr { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 10px; cursor: grab; }
.dash-card.dragging .dash-card-hdr { cursor: grabbing; }
.dash-card-title { display: flex; align-items: center; gap: 6px; min-width: 0; font-size: var(--text-sm); font-weight: 700; letter-spacing: normal; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dash-card-icon { display: inline-flex; align-items: center; flex-shrink: 0; color: var(--muted); }
.dash-card-icon svg { width: 14px; height: 14px; display: block; }
.dash-grip { color: var(--muted); opacity: 0.4; font-size: var(--text-base); line-height: 1; flex-shrink: 0; }
.dash-card-hdr:hover .dash-grip { opacity: 0.8; }
.dash-card-link { color: var(--accent); text-decoration: none; font-size: var(--text-base); flex-shrink: 0; }
.dash-card-link:hover { opacity: 0.8; text-decoration: none; }
/* Right-side header actions as ONE flex child (preserves the header's two-child
   space-between): × (hover-revealed) left of the always-visible ↗, which stays
   flush right. */
.dash-card-actions { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
/* Remove × reveals on card hover via OPACITY (keeps its slot, so no layout shift —
   mirrors the grip's reveal). */
.dash-card-remove { background: none; border: none; color: var(--muted); cursor: pointer; font-size: var(--text-base); line-height: 1; padding: 0 2px; flex-shrink: 0; opacity: 0; pointer-events: none; transition: opacity 0.12s, color 0.12s; }
.dash-card:hover .dash-card-remove { opacity: 1; pointer-events: auto; }
.dash-card-remove:hover { color: var(--text); }
.dash-card.is-hidden { display: none; }
.dash-card-body { padding: 0; font-size: var(--text-sm); color: var(--text); }
/* Dashboard add affordance — a single circular "+" PINNED to the viewport
   bottom-right (fixed, so it stays in the corner regardless of grid height); its
   dropdown opens UPWARD. z-index sits above content/overlay-panel but below the
   header (300) and modals (1000). */
.dash-toolbar { position: fixed; right: 24px; bottom: calc(var(--footer-h) + 24px); z-index: 50; }
.dash-add-wrap { position: relative; display: inline-block; }
.dash-add { display: inline-flex; align-items: center; justify-content: center; width: 36px; height: 36px; padding: 0; border-radius: 50%; font-size: 1.35rem; line-height: 1; cursor: pointer; border: 1px solid rgba(var(--accent-rgb),0.40); background: rgba(var(--accent-rgb),0.10); color: var(--accent); transition: background 0.12s, border-color 0.12s, opacity 0.12s; }
.dash-add:hover { background: rgba(var(--accent-rgb),0.18); border-color: rgba(var(--accent-rgb),0.60); }
.dash-add:disabled { opacity: 0.4; cursor: not-allowed; }
.dash-add-menu { position: absolute; bottom: 100%; right: 0; margin-bottom: 6px; z-index: 10; min-width: 200px; max-height: 280px; overflow-y: auto; background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-md); box-shadow: var(--shadow-modal); padding: 4px; }
.dash-add-menu[hidden] { display: none; }
.dash-add-item { display: flex; align-items: center; gap: 8px; width: 100%; text-align: left; background: none; border: none; color: var(--text); padding: 6px 8px; border-radius: var(--r-sm); cursor: pointer; font-size: var(--text-sm); }
.dash-add-item:hover { background: var(--surface-2); }
.dash-add-item-icon { display: inline-flex; align-items: center; flex-shrink: 0; color: var(--muted); }
.dash-add-item-icon svg { width: 14px; height: 14px; display: block; }
.dash-all-hidden { grid-column: 1 / -1; }
.dash-row { display: flex; align-items: center; gap: 8px; padding: 5px 0; font-size: var(--text-sm); }
.dash-row + .dash-row { border-top: 1px solid var(--border); }
.dash-stat { font-size: var(--text-sm); color: var(--text); }
.dash-stat strong { color: var(--text); font-weight: 700; }
.dash-muted { font-size: var(--text-xs); color: var(--muted); }
.dash-empty { grid-column: 1 / -1; text-align: center; }
/* Governed "No access" main-panel state — centred column (access-denied icon on top, heading,
   then the sub-message), filling the content area so it mirrors the sidebar placeholder. */
/* `.dash-notice` shares this centred icon-on-top layout for other full-panel overview states
   (e.g. the server/client-mode "Overview unavailable" info state). */
.dash-no-access, .dash-notice {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 10px; padding: 0 20px;
  /* Fill the visible content region so the message sits at the TRUE vertical middle: viewport
     minus the fixed header (--header-h) minus .portal-content's own 28px top + 80px bottom
     padding (= 108px). Centring within a short block (e.g. 55vh) reads as "above middle". */
  min-height: calc(100vh - var(--header-h) - 108px);
}
.dash-no-access-icon, .dash-notice-icon { color: var(--muted); opacity: 0.7; line-height: 0; }
.dash-no-access-text, .dash-notice-title { font-size: var(--text-lg, 1.1em); color: var(--fg, inherit); font-weight: 600; }
.dash-no-access-sub, .dash-notice-sub { color: var(--muted); max-width: 420px; }
.dash-card.dragging { opacity: 0.45; outline: 2px dashed var(--accent); }
.dash-card.drop-target { outline: 2px dashed var(--accent); background: rgba(var(--accent-rgb),0.06); }
.dash-bar { display: inline-block; width: 64px; height: 6px; background: var(--surface-3); border-radius: var(--r-sm); overflow: hidden; vertical-align: middle; }
.dash-bar > i { display: block; height: 100%; background: rgba(var(--accent-rgb),0.55); border-radius: var(--r-sm); }
.dash-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; vertical-align: middle; margin-right: 6px; }
.dash-dot.running { background: var(--green); }
.dash-dot.stopped { background: var(--muted); }

/* Widget stat cards — a compact `.pipeline-card` analog for the planner/quests
   widgets: a small grid of cards, each with the status label on top and the
   count below. Both label and number take the per-status colour (the page-level
   `.pipeline-card` colours only the label; here the whole card "respects colours"
   per the dashboard spec), driven by the `--sc` custom property set per status. */
.dash-stats { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 6px; }
.dash-stat-card { position: relative; overflow: hidden; flex: 1 1 0; min-width: 44px; background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-md); padding: 5px 4px 4px; display: flex; flex-direction: column; align-items: center; gap: 1px; }
/* Title + number stay the system text colour (white), exactly like the planner/
   quests page .pipeline-card — a status card conveys its colour via the accent-top
   bar, NOT by tinting the text. */
.dash-stat-card .dash-stat-lbl { font-size: var(--text-2xs); font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; color: var(--text); white-space: nowrap; }
.dash-stat-card .dash-stat-num { font-size: var(--text-lg); font-weight: 700; line-height: 1.1; color: var(--text); }
.dash-stat-card .dash-stat-sub { font-size: var(--text-2xs); font-weight: 600; color: var(--muted); white-space: nowrap; }
/* Accent-top bar only for status cards (a c-<status> modifier sets --sc): the
   planner/quests/events/daemon cards. Status-less metric cards (memory, documents,
   advisor, usage) carry no c- class and so get no bar. Bar colours mirror the page
   .pipeline-card --ca mapping (c-open/c-cancelled -> muted). */
.dash-stat-card[class*=" c-"]::before { content: ""; position: absolute; left: 0; right: 0; top: 0; height: 2px; background: var(--sc, var(--accent)); }
.dash-stat-card.c-open      { --sc: var(--muted); }
.dash-stat-card.c-pending   { --sc: var(--accent2); }
.dash-stat-card.c-active    { --sc: var(--accent); }
.dash-stat-card.c-blocked   { --sc: var(--red); }
.dash-stat-card.c-completed { --sc: var(--green); }
.dash-stat-card.c-cancelled { --sc: var(--muted); }
.dash-stat-card.c-reviewing { --sc: var(--cyan); }
.dash-stat-card.c-accepted, .dash-stat-card.c-ready { --sc: var(--accent2); }
.dash-stat-card.c-executed  { --sc: var(--green); }
.dash-stat-card.c-warn      { --sc: var(--accent2); }
.dash-stat-card.c-error     { --sc: var(--red); }
/* Inline row link-icon (the widgets' ⤢ open-detail affordance) — a borderless
   accent glyph that reads like the card-header `↗` link. */
.dash-row-link { background: none; border: 0; padding: 0 2px; margin: 0; color: var(--accent); cursor: pointer; font-size: var(--text-base); line-height: 1; flex-shrink: 0; text-decoration: none; }
.dash-row-link:hover { opacity: 0.8; }
/* Body of the shared dashboard detail popup (jmDashboard.openDetail) — the
   `.td-section`/`.td-value-md` blocks a widget fills it with own their styling. */
.dash-detail-body { padding: 2px 0 4px; }
.dash-detail-body .td-section + .td-section { margin-top: 12px; }
/* Markdown tables (markdown-it GFM tables) inside any rendered detail body. */
.td-value-md table, .md-body table { border-collapse: collapse; margin: 6px 0; font-size: 0.92em; }
.td-value-md th, .td-value-md td,
.md-body th, .md-body td { border: 1px solid var(--border); padding: 3px 8px; text-align: left; vertical-align: top; }
.td-value-md th, .md-body th { background: var(--surface-2); font-weight: 700; color: var(--text); }

/* =======================================================================
   Shared portal components — used across all ability pages
   ======================================================================= */

/* Page layout */
.page-header { display: flex; align-items: baseline; gap: 14px; margin-bottom: 6px; }
/* Page topmost title — text-3xl across all system pages (matches the docs h1). */
.section-title { font-size: var(--text-3xl); font-weight: 700; color: var(--text); margin: 0; }
/* Inside modals/overlays a .section-title is a sub-view heading, not the page title — keep it smaller. */
.modal-overlay .section-title, .modal-box .section-title, .overlay-panel .section-title { font-size: var(--text-xl); }
/* TITLE-DESCRIPTION — the muted description line directly under a page .section-title
   (formerly the "sub-title"). Also used for descriptive section labels. */
.page-subtitle, .section-subtitle, .title-description { color: var(--muted); font-size: var(--text-base); margin-bottom: 14px; }
.empty-state { color: var(--muted); font-size: var(--text-base); padding: 20px 0; }
.empty-state.error   { color: var(--red); }
.empty-state.compact { padding: 8px 0; }
/* Compact "No access" (dashboard widget cards): small muted lock + label, same colour
   as the main no-access state. jmAccessDeniedHtml({compact:true}). */
.jm-noaccess-compact { color: var(--muted); display: flex; align-items: center; justify-content: center; gap: 6px; }
.jm-noaccess-compact .jm-noaccess-lock { line-height: 0; opacity: 0.7; }
.jm-noaccess-compact .jm-noaccess-lock svg { width: 16px; height: 16px; }
/* Spinner ring (reuses the doc-spin keyframe). Large form for standalone use; jm-spinner-sm is
   the small ring used by the flat loader. */
.jm-spinner { width: 34px; height: 34px; border: 3px solid var(--border); border-top-color: var(--accent);
  border-radius: 50%; animation: doc-spin 0.8s linear infinite; }
/* When a panel list holds ONLY a state placeholder (loading / empty / no-access), drop its table
   chrome (border/background/grid) so the state shows centred on the bare area — the table itself
   appears only once data rows render. */
.task-list:has(> .jm-flat:only-child), .card-grid:has(> .jm-flat:only-child),
.task-list:has(> .dash-no-access:only-child), .card-grid:has(> .dash-no-access:only-child) {
  background: transparent; border: none; display: block !important; }
/* Flat state — the ONE format for loading AND empty (data panels + admin/config): a small icon to
   the LEFT of the text, centred HORIZONTALLY at natural height, so it never expands the section. */
.jm-flat { display: flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 0; padding: 28px 0; color: var(--muted); grid-column: 1 / -1; }
.jm-spinner-sm { width: 14px; height: 14px; border-width: 2px; flex-shrink: 0; }
/* Atomic-operation busy lock (window._jmModal.busy / pageBusy) — a spinner scrim that covers its
   host and eats every click, so an in-flight atomic change can't be double-fired or dismissed. */
.modal-overlay[data-jm-busy] .modal-box { position: relative; }
.jm-busy-scrim { position: absolute; inset: 0; z-index: 20; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 10px; cursor: wait; border-radius: inherit;
  background: color-mix(in srgb, var(--panel) 72%, transparent); }
.jm-busy-scrim .jm-busy-label { color: var(--muted); font-size: var(--text-sm); }
.jm-page-busy { position: fixed; inset: 0; z-index: 3000; border-radius: 0;
  background: color-mix(in srgb, var(--overlay) 80%, transparent); }
/* Destructive-action warning callout (red, exclamation) — irreversible operations. */
.jm-warn-box { display: flex; align-items: flex-start; gap: 8px; margin: 10px 0; padding: 10px 12px;
  border: 1px solid var(--red); border-radius: var(--r-lg); font-size: var(--text-sm);
  background: color-mix(in srgb, var(--red) 12%, transparent); color: var(--red); }
.jm-warn-box .jm-warn-icon { flex-shrink: 0; font-weight: 700; line-height: 1.2; }
/* Small info glyph for the flat empty state (matches the small spinner). */
.jm-info-sm { display: inline-flex; flex-shrink: 0; opacity: 0.7; line-height: 0; }
.jm-info-sm svg { width: 16px; height: 16px; }
.ellipsis { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* SYSTEM sub-title — the section headings one level below .section-title (formerly
   the "sub-sub-title"; e.g. memory "Interactions", documents "Monitored documents").
   White var(--text), --text-xl, sentence-case (NOT an uppercase muted eyebrow).
   Pipeline/widget .tier-label keeps its own smaller size via `.pipeline-card > .tier-label`. */
.tier-label, .td-label, .usage-section-title { font-size: var(--text-xl); font-weight: 400; text-transform: none; letter-spacing: normal; color: var(--text); margin-bottom: 6px; }
.td-label { display: flex; align-items: center; justify-content: space-between; }
.tier-hdr { display: flex; align-items: center; justify-content: space-between; padding-bottom: 5px; margin-bottom: 4px; flex-shrink: 0; }
.tier-link { font-size: var(--text-xs); color: var(--muted); text-decoration: none; padding: 1px 5px; border: 1px solid var(--border); border-radius: var(--r-sm); }
.tier-link:hover { color: var(--accent); border-color: var(--accent); text-decoration: none; }

/* Shared — toast notifications (window.jmToast).
   z-index ladder: header 300 → sidebar 10 → modal 1000 → modal-top 9999 → toast 10000. */
.jm-toast-layer { position: fixed; right: 16px; bottom: calc(var(--footer-h) + 16px); z-index: 10000; display: flex; flex-direction: column; gap: 8px; align-items: flex-end; pointer-events: none; }
.jm-toast { pointer-events: auto; max-width: 360px; padding: 6px 12px; border-radius: var(--r-md); border: 1px solid var(--border); background: var(--panel); color: var(--text); font-size: var(--text-sm); box-shadow: var(--shadow-modal); cursor: pointer; word-break: break-word; opacity: 0; transform: translateY(8px); transition: opacity 0.18s ease, transform 0.18s ease; }
.jm-toast.show { opacity: 1; transform: translateY(0); }
.jm-toast.ok  { background: rgba(var(--green-rgb),0.12); border-color: rgba(var(--green-rgb),0.35); color: var(--green); }
.jm-toast.err { background: rgba(var(--red-rgb),0.12);   border-color: rgba(var(--red-rgb),0.35);   color: var(--red); }

/* Scope badges */
.scope-badge { display: block; text-align: center; --c: var(--muted); }
.scope-user    { --c: var(--accent); }
.scope-project { --c: var(--muted); }

/* Provider badges — soft-tinted in the brand hue (live convention); --c carries
   the third-party brand colour, applied as tint + coloured text by the shared box rule. */
.prov-badge { display: block; text-align: center; --c: var(--muted); }
.prov-claude  { --c: var(--prov-claude-color); }
.prov-gemini  { --c: var(--prov-gemini-color); }
.prov-cursor  { --c: var(--prov-cursor-color); }
.prov-copilot { --c: var(--prov-copilot-color); }
.prov-codex   { --c: var(--prov-codex-color); }
/* The provider box rendered INLINE inside a flex row (advisor cards, the Audit
   Agents/event-detail rows) — a compact box, not a column-filling block. The
   single definition; never inline these declarations at the call site. */
.prov-badge.inline { display: inline-block; min-width: 52px; vertical-align: middle; }

/* Pills — docs base, portal compact override, all colour states */
.card .status .pill,
.pill {
  /* EXACT live /components .pill: round, .72rem / 700, .1rem .55rem; hue in --c
     -> 15% tint surface + pure-hue text + 35% tinted border. (No uppercase.) */
  display: inline-block; box-sizing: border-box; white-space: nowrap;
  border-radius: var(--r-pill); padding: 0.1rem 0.55rem;
  font-size: 0.72rem; font-weight: 700; line-height: 1.5;
  --c: var(--muted);
  background: color-mix(in srgb, var(--c) 15%, var(--panel));
  color: var(--c);
  border: 1px solid color-mix(in srgb, var(--c) 35%, var(--panel));
}
.portal-content .pill { text-align: center; }
/* ---------------------------------------------------------------------------
   CANONICAL STATUS-COLOUR SEMANTICS (every portal section maps to these tokens
   — do not invent per-section status colours; map your status -> one of these):
     active     (accent  / blue)   — system in-flight / processing
     pending    (accent2 / amber)  — awaiting a human action
     completed  (green)            — positive terminal outcome
     cancelled / blocked / error (red) — negative terminal / failure
     open       (neutral)          — neutral / not started
     on-hold    (muted)            — paused
   Interviews reuse these via JS maps (STATUS_PILL/COMPOSITE_PILL/MATCH_PILL):
     validating/generating->pending (amber, in-flight), validated->completed,
     review->pending (awaiting interviewer), go->completed, no-go->cancelled.
   --------------------------------------------------------------------------- */
/* SOFT-TINTED status chips (live Wynxx convention) — each variant sets ONLY the
   --c hue; the base .pill rule turns it into a 14% tint + coloured text + tinted
   hairline. Hues are the theme-aware tokens so they lighten in dark for legible
   coloured text. A status colour changes in ONE place (the theme token). */
/* docs ability states */
.pill.stable   { --c: var(--green); }
.pill.beta     { --c: var(--accent2); }
.pill.alpha    { --c: var(--red); }
.pill.system   { --c: var(--accent); }
.pill.optional { --c: var(--muted); }
.pill.roadmap  { --c: var(--accent2); }
.pill.milestone{ --c: var(--purple); }
/* Work-item status bubbles — unified with the page cards' colour philosophy:
   idle/draft = neutral, queued/ready = amber, in-progress = blue, blocked = red,
   done = green, cancelled = muted grey. Covers both quest and plan statuses. */
.pill.open, .pill.updated, .pill.reviewing, .pill.on-hold { --c: var(--muted); }
.pill.pending, .pill.verified, .pill.accepted { --c: var(--accent2); }
.pill.active    { --c: var(--accent); }
.pill.blocked   { --c: var(--red); }
.pill.completed, .pill.executed { --c: var(--green); }
.pill.cancelled { --c: var(--muted); }
/* planner + quests status bubbles: ONE uniform width on every surface (board,
   detail header, history rows, widgets) so a column of statuses lines up. */
.plan-pill { min-width: 90px; text-align: center; box-sizing: border-box; }
/* audit severity states */
.pill.debug { --c: var(--muted); }
.pill.info  { --c: var(--accent); }
.pill.warn  { --c: var(--accent2); }
.pill.error { --c: var(--red); }
/* provider brand colours — solid brand fill, white text. */
.pill.prov-claude  { --c: var(--prov-claude-color); }
.pill.prov-gemini  { --c: var(--prov-gemini-color); }
.pill.prov-copilot { --c: var(--prov-copilot-color); }
.pill.prov-cursor  { --c: var(--prov-cursor-color); }
.pill.prov-codex   { --c: var(--prov-codex-color); }
/* Box variant — square-cornered (not a rounded bubble). Used for provider name
   boxes and the system-ability "required" marker. */
.pill.boxed { border-radius: var(--r-sm); }

/* ---------------------------------------------------------------------------
   CANONICAL LABEL BOX — every small inline label box across the portal (row
   type/parent/dep/plan badges, origin, scope, provider, config-name, doc-id)
   shares ONE height + font, matching the status bubble (.portal-content .pill)
   but square-cornered. Per-class rules below set ONLY colour, width, display,
   and overflow — never font-size / weight / line-height / padding here-on, so a
   single change here resizes every box in lockstep. */
.tr-badge, .org-badge, .scope-badge, .prov-badge, .cfg-name-box, .doc-id {
  font-size: var(--text-xs); font-weight: 700; line-height: 1.6;
  padding: 1px 6px; border-radius: var(--r-sm); box-sizing: border-box;
  /* Soft-tinted label boxes (live convention) — same --c mechanism as .pill.
     Variants set ONLY --c; .tr-badge.date opts out (plain muted text). */
  --c: var(--muted);
  background: color-mix(in srgb, var(--c) 14%, var(--panel));
  color: var(--c);   /* deepen the hue toward the foreground so coloured text stays legible on the pale tint (light) / dark panel — adapts per theme via --text */
  border: 1px solid color-mix(in srgb, var(--c) 32%, var(--panel));
}

/* Buttons. .btn-primary is the single solid indigo fill; the default/secondary
   button is a neutral ghost (foreground text + --border); danger is a red outline. bg/fg/border flow through --btn-bg / --btn-fg / --btn-bd.
   Dense rows can opt into .btn-sm. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.45rem;
  box-sizing: border-box; padding: 0.55rem 1.05rem; border-radius: var(--r-md);
  font-size: var(--text-sm); font-weight: 600; cursor: pointer; text-decoration: none; white-space: nowrap;
  background: var(--btn-bg, transparent);
  color: var(--btn-fg, var(--text));
  border: 1px solid var(--btn-bd, var(--border));
  transition: background 0.18s, border-color 0.18s, color 0.18s, box-shadow 0.18s, transform 0.12s;
}
.btn:active:not(:disabled) { transform: translateY(1px); }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
/* `hidden` MUST win over the .btn display above: the UA rule `[hidden]{display:none}` has the SAME
   specificity as `.btn` and loses on source order, so a JS-hidden button (`el.hidden = true`) would
   otherwise still render. This defeated the permission gating on the admin action buttons (Add
   organization/user/member/group, Upload, From source, Add project — all `.btn`, all toggled via
   `.hidden`), leaking them to users who lack the grant. Mirrors the `.hdr-project-item[hidden]` fix. */
.btn[hidden] { display: none !important; }
/* Default (ghost) hover — border + text go brand (live .btn-ghost). */
.btn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }

/* Small variant (live .btn-sm). */
.btn.btn-sm { padding: 0.35rem 0.7rem; font-size: 0.78rem; }

/* The live /components Buttons section has exactly THREE types: primary (solid
   indigo), ghost (neutral outline), danger (red outline). Every app button maps
   onto one of those — no green/amber/purple button colours. */

/* PRIMARY — solid indigo + medium shadow. Affirmative / main actions (save,
   apply, create, queue, confirm-outcome). hover darkens. */
.btn.btn-primary,
.btn.btn-queue,
.btn.btn-new,
.btn.btn-success,
.btn.btn-outcome,
.btn.btn-reopen,
.btn.jm-cfg-apply { --btn-bg: var(--primary); --btn-fg: var(--on-accent); --btn-bd: var(--primary); box-shadow: var(--shadow-md); }
.btn.btn-primary:hover:not(:disabled),
.btn.btn-queue:hover:not(:disabled),
.btn.btn-new:hover:not(:disabled),
.btn.btn-success:hover:not(:disabled),
.btn.btn-outcome:hover:not(:disabled),
.btn.btn-reopen:hover:not(:disabled),
.btn.jm-cfg-apply:hover:not(:disabled) { background: var(--primary-hover); border-color: var(--primary-hover); color: var(--on-accent); }

/* DANGER — red outline over transparent (live .btn-danger); hover faint red wash. */
.btn.btn-danger { --btn-fg: var(--red); --btn-bd: color-mix(in srgb, var(--red) 30%, var(--panel)); }
.btn.btn-danger:hover:not(:disabled) { background: color-mix(in srgb, var(--red) 10%, var(--panel)); border-color: var(--red); color: var(--red); }

/* GHOST — neutral outline (secondary: hold, cancel, and the bare .btn). The base
   .btn already renders ghost + the brand hover, so these just reset to it. */
/* .btn-hold and the bare .btn are the neutral ghost from the base .btn rule. */
/* MUTED — borderless ghost (e.g. the Advices Expand toggle). */
.btn.btn-muted    { --btn-fg: var(--muted); --btn-bd: transparent; }
.btn.btn-muted:hover:not(:disabled) { background: color-mix(in srgb, var(--muted) 10%, transparent); border-color: transparent; color: var(--muted); }
/* btn-new is a semantic alias of primary — colour only, NO size override, so it
   follows the central .btn / .btn-sm sizes (the /components button sizes) like every
   other variant. (Previously a padding override here defeated .btn-sm.) */

/* Modals */
.modal-overlay { display: none; position: fixed; inset: 0; background: var(--overlay); z-index: 1000; align-items: center; justify-content: center; }
.modal-overlay.open { display: flex; }
.modal-box { background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-xl); padding: 22px 26px 20px; width: 520px; max-width: calc(100vw - 32px); box-shadow: var(--shadow-modal); }
.modal-box.modal-wide { width: 1000px; max-height: calc(100vh - 48px); display: flex; flex-direction: column; overflow: hidden; }
.modal-box.modal-sm   { width: 480px; }
.modal-box.modal-lg   { width: 720px; }
.modal-overlay.modal-top { z-index: 9999; }
.modal-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 16px; flex-shrink: 0; }
/* ===========================================================================
   SYSTEM RULE — POPUP STYLE (the login popup is the canonical reference).
   Every detail / edit popup across the system MUST follow these three aspects so
   titles never drift (documented in WEB-DEVELOPMENT.md › "Popup style"):
     • TITLE     — white (var(--text)), text-lg, bold (700), the system font
                   (never monospace). Selectors: `.modal-box .modal-header h3`
                   (scoped to popups so it never hits the overlay-panel header,
                   which keeps `.side-panel-title`), the audit
                   `.modal-hdr h3`/`.ev-type-text`, and `.td-id-title`.
     • DESCRIPTION (the tenant crumb under the title) — accent2 (yellow), text-xs;
                   `.modal-ctx` (+ bold `.modal-ctx-user`).
     • SUBTITLES (section / field labels) — system blue (var(--accent)); see the
                   `.modal-box … label` rule below.
   A badge in a popup TITLE sizes to its content + planner's badge height (never a
   list/table column min-width); see `.modal-hdr .org-badge`.
   Side panels (slide-overs: the Configuration / Documentation overlays) are NOT
   popups — they keep the accent2 `.side-panel-title`.
   =========================================================================== */
.modal-box .modal-header h3 { margin: 0; font-size: var(--text-lg); font-weight: 700; color: var(--text); }
.side-panel-title { margin: 0; font-size: var(--text-lg); color: var(--accent2); }
/* Discreet tenant breadcrumb under a popup title: "User: <user> (org - project)". Pulled up
   close under the title (the modal-header carries a 16px bottom margin). */
.modal-ctx { margin: -10px 0 14px; font-size: var(--text-xs); color: var(--accent2); letter-spacing: 0.02em; }
.modal-ctx-user { color: var(--accent2); font-weight: 700; }
/* Side-panel titles (the main sidebar title + the Documentation / Configuration
   overlay titles) are larger than the shared base — but NOT the modal-dialog
   headers, which keep var(--text-lg). The .overlay-panel selector beats the
   `.modal-header h3` specificity so the overlay h3 titles grow too. */
.side-panel-title, .overlay-panel .side-panel-title { font-size: var(--text-lg); }
/* Main sidebar title — a non-linking heading (its landing page lives under the
   Overview item below it). Scoped to .sidebar-nav so the overlay titles keep the
   modal-header layout. */
/* Fixed-height title row: the "Dashboard" label (left, vertically centred) shares this
   line with the collapse toggle pinned at its right. Fixed height (not text-driven)
   means it reserves the exact same space in both states. Right padding clears the toggle. */
.sidebar-nav .side-panel-title {
  display: flex; align-items: center; height: 44px; box-sizing: border-box;
  padding: 0 44px 0 14px; font-weight: 700; white-space: nowrap; overflow: hidden;
}
/* Collapsed: the title TEXT goes invisible but the 44px box stays, so every section
   icon below keeps the same vertical Y as expanded — no jump when toggling. */
.docs-sidebar.collapsed .sidebar-nav .side-panel-title { color: transparent; }
/* Titles removed from the side panels — the Wynxx brand at the top of each panel
   replaces them. Scoped to the side panel + overlays so modal/popup titles elsewhere
   keep theirs; the overlay close (×) stays pinned top-right with the title gone. */
.sidebar-nav .side-panel-title,
.overlay-panel .side-panel-title { display: none; }
.overlay-panel .modal-header { justify-content: flex-end; }
.modal-close { background: none; border: none; color: var(--muted); font-size: var(--text-xl); cursor: pointer; padding: 0 4px; line-height: 1; flex-shrink: 0; }
.modal-close:hover { color: var(--text); }
/* Maximize button */
.modal-header-actions { display: flex; gap: 4px; align-items: center; flex-shrink: 0; }
.modal-maximize { background: none; border: none; color: var(--muted); cursor: pointer; padding: 2px 4px; line-height: 1; flex-shrink: 0; display: flex; align-items: center; justify-content: center; }
.modal-maximize:hover { color: var(--text); }
/* Resizable modal box — add modal-resizable to .modal-box to activate */
.modal-box.modal-resizable { position: relative; overflow: hidden; display: flex; flex-direction: column; max-height: calc(100vh - 48px); }
.modal-box.modal-resizable.modal-maximized { width: calc(100vw - 32px) !important; height: calc(100vh - 32px) !important; max-width: none !important; max-height: none !important; }
/* Body grows into container height — flex: 1 1 auto uses content as basis so sections don't collapse in auto-height boxes */
.modal-box.modal-resizable > .td-value { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
.modal-box.modal-resizable > .sh-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
.modal-box.modal-resizable > .pd-actions { flex-shrink: 0; margin-top: auto; }
.modal-box.modal-resizable > .sh-detail-footer { flex-shrink: 0; margin-top: auto; }
.modal-box.modal-resizable .pd-md-max { max-height: none; }
/* Resize handle overlay divs — transparent, z-index above content, cursor per edge */
.modal-rh { position: absolute; z-index: 20; }
.modal-rh-n  { top:0;    left:8px;  right:8px;  height:6px; cursor:ns-resize;   }
.modal-rh-s  { bottom:0; left:8px;  right:8px;  height:6px; cursor:ns-resize;   }
.modal-rh-e  { right:0;  top:8px;   bottom:8px; width:6px;  cursor:ew-resize;   }
.modal-rh-w  { left:0;   top:8px;   bottom:8px; width:6px;  cursor:ew-resize;   }
.modal-rh-ne { top:0;    right:0;   width:8px;  height:8px; cursor:nesw-resize; }
.modal-rh-nw { top:0;    left:0;    width:8px;  height:8px; cursor:nwse-resize; }
.modal-rh-se { bottom:0; right:0;   width:12px; height:12px; cursor:nwse-resize; border-radius: 0 0 var(--r-xl) 0; }
.modal-rh-se::after { content:''; position:absolute; right:2px; bottom:2px; width:8px; height:8px; background: repeating-linear-gradient(-45deg, transparent 0, transparent 2px, var(--muted) 2px, var(--muted) 3px); opacity:0.4; }
.modal-rh-sw { bottom:0; left:0;    width:8px;  height:8px; cursor:nesw-resize; }
#plan-detail-modal #pd-body { flex: 1; overflow-y: auto; min-height: 0; }
#plan-detail-modal .pd-md-max { max-height: none; }
#plan-detail-modal #pd-footer { flex-shrink: 0; border-top: 1px solid var(--border); }
/* task-detail-modal: td-body fills the box, pd-actions inside it sticks to the bottom */
#task-detail-modal #td-body { flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; }
#task-detail-modal #td-body .pd-actions { margin-top: auto; flex-shrink: 0; }

/* Forms */
.form-row { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
.form-field { display: flex; flex-direction: column; gap: 4px; flex: 1; min-width: 140px; }
.form-field label { font-size: var(--text-sm); color: var(--muted); font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; }
.form-field input, .form-field select, .form-field textarea { background: var(--bg); border: 1px solid var(--input); border-radius: var(--r-md); color: var(--text); font-size: var(--text-sm); font-family: inherit; outline: none; }
/* shadcn Input size: h-9 (36px) + px-3 py-1; textarea uses px-3 py-2 (no fixed height). */
.form-field input, .form-field select { height: 2.25rem; padding: 4px 12px; }
.form-field input:focus, .form-field select:focus, .form-field textarea:focus { border-color: var(--accent); }
.form-field textarea { padding: 8px 12px; resize: vertical; min-height: 56px; }
/* Portal — planner section: Markdown editor textarea (monospace, tall for easy editing) */
.form-field textarea.plan-editor { min-height: 50vh; font-family: var(--font-mono); font-size: var(--text-sm); line-height: 1.5; }
/* Documents — "Create documentation" modal: read-only, tall, scrollable prompt
   box. Not inside a .form-field, so it sets its own surface tokens; background
   is var(--bg) (the dark base) so it reads as inset depth inside the --panel
   modal, matching .plan-editor / .detail-pre. */
.doc-prompt-box { width: 100%; box-sizing: border-box; height: 50vh; resize: vertical; background: var(--bg); border: 1px solid var(--border); border-radius: var(--r-md); color: var(--text); padding: 10px 12px; font-family: var(--font-mono); font-size: var(--text-sm); line-height: 1.5; white-space: pre-wrap; overflow: auto; outline: none; }
.doc-prompt-box:focus { border-color: var(--accent); }
/* Portal — planner section: rendered plan body in the detail popup — tall for easy reading */
.pd-md-max { max-height: 60vh; }
/* Portal — planner section: detail footer — actions left, updated centered, history+close right */
.pd-actions { display: flex; align-items: center; gap: 10px; margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--border); }
.modal-box.modal-wide > .td-section { flex-shrink: 0; }
.modal-box.modal-wide > .td-section-def { flex-shrink: 0; margin-bottom: 0; }
.modal-box.modal-wide > .td-section-def .td-value-scroll { max-height: 58vh; overflow-y: auto; }
.modal-box.modal-wide .pd-actions { flex-shrink: 0; }
/* Resizable overrides: sections grow into box height; 1 1 auto keeps content as basis so multi-section auto-height boxes don't collapse */
.modal-box.modal-resizable > .td-section { flex: 1 1 auto; min-height: 0; overflow: hidden; display: flex; flex-direction: column; }
.modal-box.modal-resizable > .td-section > .td-value { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
/* Explicit ratio overrides for two-section popups: 1/3 and 2/3 — flex-basis:0 forces size from flex-grow only */
.modal-box.modal-resizable > .td-section.td-section-sm { flex: 1 1 0; }
.modal-box.modal-resizable > .td-section.td-section-lg { flex: 2 1 0; }
/* Dashboard openDetail body: grows and scrolls within the resizable box */
.modal-box.modal-resizable > .dash-detail-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
.adv-cfg-inline { display: flex; flex-wrap: wrap; gap: 0; margin-top: 6px; }
.adv-cfg-item { display: flex; align-items: baseline; gap: 4px; font-size: var(--text-sm); padding: 2px 12px 2px 0; margin-right: 12px; border-right: 1px solid var(--border); margin-bottom: 4px; }
.adv-cfg-item:last-child { border-right: none; }
.adv-cfg-key { color: var(--muted); white-space: nowrap; }
.adv-cfg-key::after { content: ':'; }
.adv-cfg-val { color: var(--text); }
.pd-actions-left   { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.pd-actions-center { flex: 1; text-align: center; }
.pd-actions-right  { display: flex; gap: 6px; align-items: center; }
/* created / updated timestamps stacked one on top of the other (updated shown only when it differs). */
.pd-updated { color: var(--muted); font-size: var(--text-sm);
  display: flex; flex-direction: column; align-items: center; gap: 1px; line-height: 1.3; }
.form-submit { margin-top: 6px; display: flex; align-items: center; gap: 12px; }
.form-field.field-xs { max-width: 80px; }
.form-field.field-sm { max-width: 160px; }
.form-field.field-md { max-width: 110px; }
.form-field.field-lg { max-width: 180px; }

/* Key/value grids and content blocks */
.kv-grid { display: grid; grid-template-columns: 100px 1fr; gap: 6px 10px; font-size: var(--text-sm); margin-bottom: 16px; }
.kv-label { color: var(--muted); font-weight: 700; text-transform: uppercase; font-size: var(--text-xs); letter-spacing: 0.06em; padding-top: 2px; }
.kv-value, .sh-detail-val { color: var(--text); word-break: break-all; }
.content-block { white-space: pre-wrap; font-size: var(--text-sm); color: var(--text); background: var(--surface-1); border: 1px solid var(--border); border-radius: var(--r-md); padding: 8px 10px; line-height: 1.5; }

/* ===========================================================================
   Markdown rendered content (window.jmRenderMd / markdown-it output).
   BASELINE default styling — intentionally plain: a clear h1>h2>h3 size+weight
   hierarchy and normal block spacing so headings read as headings, not body
   text. This is the single place to fine-tune Markdown appearance; tweak the
   sizes/margins/colours here. Applies to .md-body and the popup .td-value-md.
   =========================================================================== */
.md-body { font-size: var(--text-base); color: var(--text); line-height: 1.5; white-space: normal; background: var(--surface-3); border-radius: var(--r-md); padding: 8px 10px; }
.td-value.td-value-md { white-space: normal; background: var(--surface-3); border-radius: var(--r-md); padding: 8px 10px; }

/* Section titles carry a solid divider underline (var(--divider)) to set them
   off from the body — same line used by the <hr> rule below. */
.md-body h1, .td-value-md h1 { font-size: 1.6em;  font-weight: 700; line-height: 1.25; color: var(--text); margin: 0.8em 0 0.4em; padding-bottom: 0.2em; border-bottom: 1px solid var(--divider); }
.md-body h2, .td-value-md h2 { font-size: 1.35em; font-weight: 700; line-height: 1.3;  color: var(--text); margin: 0.8em 0 0.4em; padding-bottom: 0.2em; border-bottom: 1px solid var(--divider); }
.md-body h3, .td-value-md h3 { font-size: 1.15em; font-weight: 600; line-height: 1.3;  color: var(--text); margin: 0.7em 0 0.3em; }
.md-body h4, .td-value-md h4 { font-size: 1em;    font-weight: 700; color: var(--text); margin: 0.7em 0 0.3em; }
.md-body h5, .td-value-md h5,
.md-body h6, .td-value-md h6 { font-size: 0.9em; font-weight: 700; color: var(--muted); text-transform: uppercase; letter-spacing: 0.04em; margin: 0.6em 0 0.3em; }
/* A leading heading shouldn't push the whole block down. */
.md-body > :first-child, .td-value-md > :first-child { margin-top: 0; }
.md-body p, .td-value-md p { margin: 0.6em 0; }
.md-body ul, .md-body ol, .td-value-md ul, .td-value-md ol { margin: 0.6em 0; padding-left: 1.6em; }
.md-body li, .td-value-md li { margin: 0.25em 0; }
.md-body blockquote, .td-value-md blockquote { margin: 0.6em 0; padding: 2px 0 2px 12px; border-left: 3px solid var(--border); color: var(--muted); }
.md-body hr, .td-value-md hr { border: 0; border-top: 1px solid var(--divider); margin: 0.9em 0; }
.md-body pre, .td-value-md pre { margin: 0.6em 0; padding: 8px 10px; background: var(--code-overlay); border-radius: var(--r-md); overflow-x: auto; }
.md-body code, .td-value-md code { font-size: 0.9em; background: var(--code-overlay); padding: 1px 4px; border-radius: var(--r-sm); }
.md-body pre code, .td-value-md pre code { background: none; padding: 0; }
/* Markdown links render as non-navigating styled text (see markdown.js): keep
   the link LOOK (accent + underline) but no anchor, so a click goes nowhere. */
.md-body .md-link, .td-value-md .md-link { color: var(--accent); text-decoration: underline; cursor: default; }
.td-value-scroll { overflow-y: auto; }

/* Flex-table shared base */
.board-table, .ev-list, .agent-list, .usage-list, .task-list { background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-lg); overflow: hidden; }

/* ===== Live /components Table component (.tbl) — used verbatim ============== */
.tbl { border-collapse: collapse; width: 100%; font-size: var(--text-sm); margin: 0; }
.tbl th { text-align: left; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); border-bottom: 1px solid var(--border); background: var(--secondary); padding: 0.6rem 0.75rem; font-size: 0.7rem; font-weight: 600; }
.tbl td { border-bottom: 1px solid var(--border); vertical-align: middle; padding: 0.6rem 0.75rem; }
.tbl tr:last-child td { border-bottom: none; }
.tbl tr:hover td { background: color-mix(in srgb, var(--accent) 5%, transparent); }
/* The global prose-table rules (table{margin}; th,td{border:1px solid all sides})
   leak into .tbl — neutralise them: .tbl keeps ONLY the bottom hairline. */
.tbl th, .tbl td { border-top: 0; border-left: 0; border-right: 0; }
/* Audit events table: fixed layout so Data truncates; mono event col; right age/btn. */
.ev-tbl { table-layout: fixed; font-size: var(--text-xs); }
/* Compact (system-size) rows + header for the dense events table. */
.ev-tbl th, .ev-tbl td { padding: 4px 10px; }
.ev-tbl col.evc-prov { width: 46px; } .ev-tbl col.evc-org { width: 92px; }
.ev-tbl col.evc-evt { width: 150px; } .ev-tbl col.evc-age { width: 58px; } .ev-tbl col.evc-btn { width: 92px; }
.ev-tbl td.ev-c-evt, .ev-tbl td.ev-c-data { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ev-tbl td.ev-c-evt { font-family: var(--font-mono); font-size: var(--text-xs); text-align: center; }
.ev-tbl td.ev-c-data { color: var(--text); }
.ev-tbl td.ev-c-age { text-align: center; color: var(--muted); font-size: var(--text-xs); white-space: nowrap; }
.ev-tbl th { text-align: center; }
.ev-tbl td.ev-c-prov, .ev-tbl td.ev-c-org, .ev-tbl td.ev-c-btn { text-align: center; }
/* Origin column = icon-only: the origin renders via .org-badge-icon (the shared
   icon schema — user/agent/jarvis each a coloured registry icon). */
.ev-tbl td.ev-c-org .org-badge, .uc-root .org-badge { vertical-align: middle; }
.ev-tbl tr.ev-sev-debug td { background: rgba(var(--muted-rgb),0.06); }
.ev-tbl tr.ev-sev-warn  td { background: rgba(var(--accent2-rgb),0.08); }
.ev-tbl tr.ev-sev-error td { background: rgba(var(--red-rgb),0.09); }
.ev-tbl tr.ev-sev-debug:hover td { background: rgba(var(--muted-rgb),0.12); }
.ev-tbl tr.ev-sev-warn:hover  td { background: rgba(var(--accent2-rgb),0.14); }
.ev-tbl tr.ev-sev-error:hover td { background: rgba(var(--red-rgb),0.15); }
/* Audit Events — full-height page: the table card fills the viewport and scrolls
   INTERNALLY (sticky header) so the page (.docs-main) doesn't scroll. :has scopes
   the fill-height flex to this page only; every other page is untouched. */
/* Full-height events page: anchor on .docs-main (definite height) so flex:1
   fills robustly; docs-main stops scrolling, the table scrolls internally. */
.docs-main:has(.ev-fullpage) { overflow: hidden; display: flex; flex-direction: column; }
.docs-main:has(.ev-fullpage) > .portal-content { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; padding-bottom: 24px; }
.ev-fullpage { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.ev-fullpage > #ev-board, .ev-fullpage > #usage-board { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.ev-fullpage .ev-list { flex: 1 1 auto; min-height: 0; overflow: hidden; }
/* Only the BODY scrolls, so the scrollbar starts BELOW the fixed header. The table
   becomes a flex column (thead pinned, tbody scrolls). Widths are set per column
   position because the colgroup is ignored once the rows are display:table. */
.ev-tbl { display: flex; flex-direction: column; height: 100%; }
.ev-tbl colgroup { display: none; }
.ev-tbl thead { display: block; flex: 0 0 auto; padding-right: 6px; background: var(--secondary); }
.ev-tbl tbody { display: block; flex: 1 1 auto; min-height: 0; overflow-y: scroll; overflow-x: hidden; }
.ev-tbl thead tr, .ev-tbl tbody tr { display: table; width: 100%; table-layout: fixed; }
.ev-tbl tr > *:nth-child(1) { width: 46px; }
.ev-tbl tr > *:nth-child(2) { width: 30px; padding-left: 4px; padding-right: 4px; }   /* narrow origin column; trim padding so the icon keeps full size */
.ev-tbl tr > *:nth-child(3) { width: 150px; }
.ev-tbl tr > *:nth-child(5) { width: 58px; }
.ev-tbl tr > *:nth-child(6) { width: 92px; }
.task-list { margin-top: 8px; }
.board-head { display: flex; align-items: center; gap: 8px; padding: 0 10px; min-height: var(--row-h-hdr); background: var(--surface-1); font-size: var(--text-xs); font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); }
.board-row  { display: flex; align-items: center; gap: 8px; padding: 0 10px; min-height: var(--row-h); border-top: 1px solid var(--border); font-size: var(--text-sm); transition: background .1s; }
.board-row:hover { background: var(--surface-2); }

/* Stat chips */
.stat-chip { display: inline-flex; align-items: baseline; gap: 6px; padding: 5px 12px; border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface-1); }
.stat-chip-label { font-size: var(--text-xs); font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
.stat-chip-value { font-size: var(--text-base); font-weight: 700; }
.pipeline { display: flex; align-items: stretch; gap: 8px; margin-bottom: 14px; flex-wrap: wrap; }

/* -----------------------------------------------------------------------
   Memory page — tier tabs + connected table panel. The selected card (.mem-tab.active)
   carries the panel surface and merges into the table below (a tab -> body shape); the
   other cards read as de-emphasised, clickable tabs. "Search hits" is a stat, not a tab.
   ----------------------------------------------------------------------- */
.mem-view { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
.mem-tabs { margin-bottom: 0; align-items: stretch; position: relative; z-index: 1; flex: 0 0 auto; }
.mem-tab { cursor: pointer; background: var(--bg); box-shadow: none; }
.mem-tab:not(.active), .mem-hits { margin-bottom: 10px; }   /* gap to the table; only the active card connects. Search-hits (never a tab) sits with the inactive cards so it's the same height. */
.mem-tab:hover { border-color: var(--accent); }
.mem-tab > .tier-label, .mem-tab > .pipeline-count { color: var(--muted); }
.mem-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.mem-tab.active { background: var(--bg); box-shadow: none; border-bottom-color: var(--bg);
  border-bottom-left-radius: 0; border-bottom-right-radius: 0; z-index: 2; }
.mem-tab.active > .tier-label { color: var(--text); }
.mem-tab.active > .pipeline-count { color: var(--accent); }
.mem-hits { cursor: default; }
.mem-panel { background: var(--bg); border: 1px solid var(--border); border-radius: 0 0 var(--r-lg) var(--r-lg);
  margin-top: -1px; padding: 8px; flex: 1 1 auto; min-height: 0; overflow-y: auto; }   /* square TOP corners so the active tab (squared bottom) merges flush — no rounded-corner "chip" under the leftmost (Interactions) tab */
/* Graph 2D/Flow views: the board becomes a flex column (no scroll) so the canvas fills the whole
   panel and tracks its size, instead of a fixed-height canvas inside the scrolling list panel. */
.mem-panel.mem-panel-graph { display: flex; flex-direction: column; overflow: hidden; }
/* Full-height memory page: anchor on .docs-main (definite height) so the table flex:1 fills. */
.docs-main:has(.mem-view) { overflow: hidden; display: flex; flex-direction: column; }
.docs-main:has(.mem-view) > .portal-content { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; padding-bottom: 24px; }
/* KPI layout: left column = title (top) then description (below); right column = the big number,
   vertically centered against the left stack. */
.pipeline-card { position: relative; flex: 1; min-width: 120px; background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-lg); box-shadow: var(--shadow-sm); padding: 12px 16px; display: grid; grid-template-columns: 1fr auto; column-gap: 12px; align-content: start; transition: background 0.1s, border-color 0.1s; }
.pipeline-card > .tier-label, .pipeline-card > .pipeline-sub, .pipeline-card > .pipeline-avg { grid-column: 1; }
/* Top-card title = system title style (not the muted eyebrow). */
.pipeline-card > .tier-label { font-size: var(--text-sm); font-weight: 700; text-transform: none; letter-spacing: normal; color: var(--text); }
/* Span every content row (the span-9 reaches past any realistic row count; surplus rows are
   zero-height) so `align-self: center` centers the number against the full left-column height. */
.pipeline-card > .pipeline-count { grid-column: 2; grid-row: 1 / span 9; align-self: center; }
.pipeline-card:hover { background: var(--surface-3); border-color: rgba(var(--accent-rgb),0.35); }
/* A status top card shows an ACCENT-TOP bar in its status colour (--ca); the
   card body stays a regular card. Cards with no status get no bar. card-alert = red. */
.pipeline-card.card-alert { --ca: var(--red); }
.pipeline-card.accent-top::before, .pipeline-card.card-alert::before, .mem-tab.active::before {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 3px;
  border-radius: var(--r-lg) var(--r-lg) 0 0; background: var(--ca, var(--accent)); }
.pipeline-arrow { display: flex; align-items: center; padding: 0 10px; color: var(--muted); flex-shrink: 0; }
.cfg-header-actions { display: flex; gap: 8px; margin-left: auto; }
/* The number sits in the right column, top-aligned with the title (margin:0); the per-day average
   stays under the title on the left with the rest of the description. */
.pipeline-count { font-size: var(--text-3xl); font-weight: 700; color: var(--text); line-height: 1.15; margin: 0; text-align: right; }
.pipeline-sub { font-size: var(--text-xs); color: var(--muted); }
.pipeline-avg { font-size: var(--text-xs); color: var(--muted); font-weight: 400; opacity: 0.85; }

/* Memory recall-usefulness page — compact KPI layout (verdict line + tiles + slim bars). Colours
   come from theme tokens so it works light/dark with no per-mode overrides. */
.rc-detail { background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-lg);
  box-shadow: var(--shadow-sm); padding: 12px 16px; margin-top: 10px; }
.rc-row { margin: 10px 0; }
.rc-row:first-child { margin-top: 4px; }
.rc-row-head { display: flex; justify-content: space-between; align-items: baseline; gap: 8px;
  font-size: var(--text-sm); color: var(--muted); margin-bottom: 5px; }
.rc-row-title { font-weight: 600; color: var(--text); }
/* Small dimmed info affordance next to a title — the metric's description shows on hover. */
/* Slim sub-section divider inside a card: a thin rule with a centered label (carries no number). */
.rc-divider { display: flex; align-items: center; gap: 10px; margin: 14px 0 6px; }
.rc-divider::before, .rc-divider::after { content: ""; flex: 1; height: 1px; background: var(--border); }
.rc-divider > span { font-size: var(--text-xs); font-weight: 700; text-transform: uppercase;
  letter-spacing: .05em; color: var(--muted); }
.rc-info { display: inline-block; font-size: var(--text-xs); color: var(--muted); cursor: help;
  border: 1px solid currentColor; border-radius: 999px; width: 13px; height: 13px; line-height: 11px;
  text-align: center; vertical-align: middle; opacity: 0.7; font-weight: 700; }
.rc-info:hover { opacity: 1; }
.rc-bar { display: flex; height: 12px; border-radius: 5px; overflow: hidden; background: var(--secondary); }
.rc-bar > span { height: 100%; display: flex; align-items: center; justify-content: center;
  font-size: 10px; font-weight: 700; white-space: nowrap; overflow: hidden; padding: 0 5px;
  box-sizing: border-box; min-width: 0; }
.rc-foot { font-size: var(--text-sm); color: var(--muted); margin-top: 12px; line-height: 1.6; }
/* Verdict pill next to a graded number (Good / OK / Low …). Colour set inline per judgment. */
.rc-badge { display: inline-block; font-size: 10px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .03em; padding: 1px 7px; border-radius: 999px; border: 1px solid currentColor;
  vertical-align: middle; }
/* Status-summary card titles — coloured to match the status pill colours
   (the count stays the default text colour). */
.tier-label.c-open      { color: var(--text); }
.tier-label.c-pending   { color: var(--accent2); }
.tier-label.c-active    { color: var(--accent); }
.tier-label.c-blocked   { color: var(--red); }
.tier-label.c-completed { color: var(--green); }
.tier-label.c-cancelled { color: var(--muted); }
.tier-label.c-reviewing { color: var(--cyan); }
.tier-label.c-accepted  { color: var(--accent2); }
.tier-label.c-executed  { color: var(--green); }
/* Planner/Quests status buckets are top-cards WITH a status: show an accent-top bar
   tinted to the bucket's status colour. Title stays the system (--text) title — the
   bar conveys status. (Memory's status-less .pipeline-cards have no c- class -> no bar.) */
.pipeline-card:has(> .tier-label.c-open),
.pipeline-card:has(> .tier-label.c-cancelled)  { --ca: var(--muted); }
.pipeline-card:has(> .tier-label.c-active)      { --ca: var(--accent); }
.pipeline-card:has(> .tier-label.c-pending),
.pipeline-card:has(> .tier-label.c-accepted)    { --ca: var(--accent2); }
.pipeline-card:has(> .tier-label.c-reviewing)   { --ca: var(--cyan); }
.pipeline-card:has(> .tier-label.c-blocked)     { --ca: var(--red); }
.pipeline-card:has(> .tier-label.c-completed),
.pipeline-card:has(> .tier-label.c-executed)    { --ca: var(--green); }
.pipeline-card:has(> .tier-label[class*=" c-"])::before {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 3px;
  border-radius: var(--r-lg) var(--r-lg) 0 0; background: var(--ca, var(--accent)); }
.pipeline-card > .tier-label[class*=" c-"] { color: var(--text); }

/* =======================================================================
   Memory components
   ======================================================================= */

.mem-board { background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-lg); overflow: hidden; }
.mem-card { display: flex; align-items: center; gap: 8px; padding: 4px 10px; min-height: var(--row-h); border-bottom: 1px solid var(--border); font-size: var(--text-xs); transition: background .1s; }
.mem-card:last-child { border-bottom: none; }
.mem-card:hover { background: var(--surface-2); }
.mc-age   { width: 50px; flex-shrink: 0; font-size: var(--text-xs); color: var(--muted); }
.mc-rel   { width: 34px; flex-shrink: 0; font-size: var(--text-xs); font-weight: 700; }
/* Search-hit count — explicit --cyan (the relevance column has no stable colour,
   it is tinted inline by pctColor(), so there is nothing to "match"). */
.mc-hits  { width: 40px; flex-shrink: 0; font-size: var(--text-xs); font-weight: 700; color: var(--cyan); text-align: center; }
/* Interaction event-type — a memory-family column; do NOT reuse audit's
   .ev-col-evt (150px, sized for the audit row) inside a .mem-card. */
.mc-evt   { width: 90px; flex-shrink: 0; font-family: var(--font-mono); font-size: var(--text-xs); color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mc-prov  { width: 58px; flex-shrink: 0; }
.mc-sid   { width: 52px; flex-shrink: 0; font-family: var(--font-mono); font-size: var(--text-xs); color: var(--accent); text-align: right; }
.mc-scope { width: 58px; flex-shrink: 0; }
.mc-key   { width: 160px; flex-shrink: 0; font-family: var(--font-mono); font-size: var(--text-xs); color: var(--accent2); text-align: right; }
.mc-text  { flex: 1; min-width: 0; font-size: var(--text-xs); color: var(--text); }
/* Action wrappers — auto-width so the central .btn.btn-sm buttons inside size naturally
   (bespoke .mc-btn / .mc-btn-del removed in favour of .btn.btn-sm.btn-primary / .btn-danger). */
.mc-act   { flex-shrink: 0; text-align: right; }
.mc-act-2 { display: flex; gap: 4px; justify-content: flex-end; flex-shrink: 0; }
.mc-val { flex-shrink: 0; font-family: var(--font-mono); font-size: var(--text-xs); font-weight: 700; color: var(--text); }
.mc-val.ok { color: var(--green); }
.mc-val.warn { color: var(--accent2); }
.mc-val.err { color: var(--red); }
.mem-section { margin-bottom: 20px; }
.mem-warn { font-size: var(--text-sm); color: var(--red); padding: 4px 0; }
.ep-card { border: 1px solid var(--border); border-radius: var(--r-md); padding: 8px 12px; background: var(--surface-1); cursor: pointer; transition: background .1s; }
.ep-card:hover { background: var(--surface-2); }
.ep-card-meta { display: flex; align-items: center; gap: 10px; margin-bottom: 4px; }
.ep-card-snippet { font-size: var(--text-sm); color: var(--text); line-height: 1.4; }
.int-row { flex-shrink: 0; border: 1px solid var(--border); border-radius: var(--r-md); padding: 5px 8px; background: var(--surface-1); }
.int-row.demoted { opacity: .6; }
.int-snippet { font-size: var(--text-sm); color: var(--text); margin-top: 3px; white-space: pre-wrap; }

/* =======================================================================
   Quest components
   ======================================================================= */

.task-row { display: flex; align-items: center; gap: 8px; padding: 4px 10px; min-height: var(--row-h); border-bottom: 1px solid var(--border); font-size: var(--text-xs); transition: background 0.1s; }
.task-row:last-child { border-bottom: none; }
.task-row:hover { background: var(--surface-2); }
.task-row.row-terminal { opacity: 0.62; }
.task-row.row-terminal:hover { opacity: 1; }
.tr-id { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--muted); white-space: nowrap; flex-shrink: 0; min-width: 38px; text-align: left; }
.tr-meta { display: flex; align-items: center; gap: 5px; flex-shrink: 0; flex-wrap: wrap; }
/* SOLID badge chips — solid fill, white text, no border (match the pills). Neutral
   default; semantic colours where they carry meaning. Square-cornered (--r-sm from
   the shared shape) so badges stay visually distinct from the fully-round pills. */
.tr-badge { white-space: nowrap; text-align: center; }   /* colour from shared box rule (--c, default muted) */
.tr-badge.pri  { width: 26px; --c: var(--accent2); }
.tr-badge.custom { --c: var(--accent); }
/* type chip: solid, white text; per-type colour comes in as the --chip custom
   property (set inline by planner/quests typeBadgeHtml); falls back to neutral. */
.tr-badge.type { width: 100px; flex-shrink: 0; overflow: hidden; text-overflow: ellipsis; text-align: center; --c: var(--chip, var(--muted)); }
/* date is a timestamp, not a chip — keep it plain muted text */
.tr-badge.date { color: var(--muted); border: none; background: none; font-size: var(--text-sm); font-weight: 400; }
.tr-badge.par  { --c: var(--muted); }
.tr-badge.dep  { --c: var(--red); }
.tr-badge.depof { --c: var(--purple); }
.tr-badge.plan { --c: var(--green); text-decoration: none; }
.tr-badge.plan:hover { filter: brightness(1.08); text-decoration: none; }
/* ID badge — any chip whose content is an entity id (#Q5, #P3, #A7, …). Add
   `.id-badge` to such a box: it renders in the mono id font (matching the .tr-id
   column), NON-bold and a touch smaller, so ids read as lightweight ids rather
   than emphasized labels. The per-class colour (.dep/.par/.plan/.depof) is
   preserved. CENTRALIZED — every id box across the portal shares this one font
   treatment, so the look changes in lockstep. `.td-id-badge` (the detail-modal /
   dashboard header id) shares the same treatment and only adds its accent colour. */
.id-badge, .td-id-badge { font-family: var(--font-mono); font-weight: 400; font-size: var(--text-2xs); }
.id-badge { padding: 0 5px; }
/* Row age column — fixed-width (right-aligned) so the trailing id-chip group's
   right edge and the Details button line up across rows regardless of how long the
   age text is ("5m" vs "365d"). Shared by the quests list and the advices list
   (their right side mirrors the quests row). Add `.tr-age` to the `.tr-badge.date`. */
.tr-age { min-width: 42px; text-align: right; flex-shrink: 0; }
.tr-meta .pill { text-align: center; }
.tr-activity { flex: 1; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }

/* Quest detail modal */
.td-id-title { font-size: var(--text-lg); font-weight: 700; color: var(--text); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.td-meta { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-bottom: 16px; }
.td-section { margin-bottom: 12px; }
.td-value { font-size: var(--text-base); color: var(--text); line-height: 1.5; white-space: pre-wrap; }
.td-id-badge { color: var(--accent2); }   /* font treatment shared with .id-badge above */
.td-dates { display: flex; gap: 20px; flex-wrap: wrap; }
.td-dates .td-section { flex: 1; }
.td-header-row { display: flex; align-items: center; gap: 8px; flex: 1; min-width: 0; flex-wrap: wrap; }
#td-type-badge, #pd-type-badge, .dash-d-type { flex-shrink: 0; }
#td-meta-inline, #pd-meta-inline, .dash-d-meta { display: flex; gap: 5px; align-items: center; flex-wrap: wrap; margin-left: auto; flex-shrink: 0; }
/* An empty type-badge slot shouldn't leave a phantom gap before the title. */
.dash-d-type:empty { display: none; }
.instr-none { color: var(--muted); font-size: var(--text-xs); }
.sh-detail-footer { text-align: right; margin-top: 16px; }

/* _jmModal.prompt() — the in-app text-input dialog (replaces window.prompt). */
.jm-prompt-body { padding: 14px 16px 4px; display: flex; flex-direction: column; gap: 8px; }
.jm-prompt-msg { margin: 0; color: var(--muted); font-size: var(--text-sm); }
.jm-prompt-input { width: 100%; box-sizing: border-box; background: var(--bg); color: var(--text);
  border: 1px solid var(--border); border-radius: 6px; padding: 7px 9px; font: inherit; outline: none; }
textarea.jm-prompt-input { resize: vertical; min-height: 62px; }
.jm-prompt-input:focus { border-color: var(--accent); }
.jm-prompt-input.jm-prompt-err { border-color: var(--red, #e5484d); }
.jm-prompt-body + .sh-detail-footer { margin-top: 0; padding: 4px 16px 14px;
  display: flex; gap: 8px; justify-content: flex-end; }

/* Status history */
.sh-list { display: flex; flex-direction: column; gap: 4px; margin-top: 4px; }
.sh-row { display: grid; grid-template-columns: 90px 152px 1fr auto; align-items: center; gap: 8px; padding: 4px 10px; border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface-1); font-size: var(--text-sm); }
.sh-range { color: var(--muted); font-size: var(--text-sm); }
.sh-row .pill { justify-self: center; text-align: center; }
.sh-title { color: var(--muted); font-style: italic; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* (Removed bespoke .sh-link — quest history "Details" now uses the central .btn.btn-sm.btn-primary.) */
.sh-detail-grid { display: grid; grid-template-columns: 90px 1fr; gap: 6px 10px; font-size: var(--text-base); margin-top: 10px; }
.sh-detail-key { color: var(--muted); font-weight: 600; text-transform: uppercase; font-size: var(--text-xs); letter-spacing: 0.04em; padding-top: 2px; }
.sh-detail-note { grid-column: 1/-1; margin-top: 4px; padding: 8px 10px; background: var(--surface-2); border-radius: var(--r-md); border: 1px solid var(--border); white-space: pre-wrap; color: var(--text); font-size: var(--text-base); line-height: 1.5; }

/* =======================================================================
   Audit components
   ======================================================================= */

/* Origin badge */
/* min-width fits the widest root tier ("jarvis"/"system") so the owner-type boxes
   (user / jarvis / agent) all render at the SAME width — in the event list, the
   usage table, and the dashboard widgets. Longer full labels grow past it; the
   usage column overrides min-width to 0 (it sets its own width:100%). */
.org-badge { display: inline-block; white-space: nowrap; text-align: center; min-width: 56px; max-width: 100%; overflow: hidden; text-overflow: ellipsis; box-sizing: border-box; --c: var(--muted); }
/* Origin + provider are ROUND, uppercase pills (per the /components badge spec),
   not the square label boxes. Placed after the label-box group so r-pill wins. */
.org-badge, .prov-badge { display: inline-flex; align-items: center; justify-content: center; gap: 0.3rem; border-radius: var(--r-pill); text-transform: uppercase; letter-spacing: 0.05em; font-size: 0.7rem; font-weight: 600; padding: 0.2rem 0.55rem; }

/* Provider shown as ICON ONLY — no pill, no name. Every .prov-badge across the
   app (audit / advisor / documents / usage) becomes just the brand icon; the
   provider name stays in the DOM (font-size:0) for screen readers + title hover.
   Central: a provider icon is added/changed ONLY here, no call-site edits.
   Coloured logos render as images; the monochrome marks (cursor, copilot) use a
   mask tinted to the provider colour so they stay visible in light AND dark. */
.prov-badge { background: none; border: none; padding: 0; gap: 0; min-width: 0; border-radius: 0; font-size: 0; line-height: 0; vertical-align: middle; }
.prov-badge.inline { display: inline-flex; min-width: 0; }
.prov-badge::before {
  content: ""; flex: 0 0 auto; display: inline-block;
  width: 1.2rem; height: 1.2rem; margin: 0;
  background-position: center; background-repeat: no-repeat; background-size: contain;
}
.prov-claude::before  { background-image: url("/static/providers/claude.svg"); }
.prov-gemini::before  { background-image: url("/static/providers/gemini.svg"); }
.prov-codex::before   { background-image: url("/static/providers/codex.svg"); }
.prov-antigravity::before { background-image: url("/static/providers/antigravity.svg"); }
.prov-cursor::before  { background-color: var(--prov-cursor-color);  -webkit-mask: url("/static/providers/cursor.svg") center/contain no-repeat;  mask: url("/static/providers/cursor.svg") center/contain no-repeat; }
.prov-copilot::before { background-color: var(--prov-copilot-color); -webkit-mask: url("/static/providers/copilot.svg") center/contain no-repeat; mask: url("/static/providers/copilot.svg") center/contain no-repeat; }
/* Unknown provider (no icon) -> fall back to its name as plain text (no empty box). */
.prov-badge:not(.prov-claude):not(.prov-gemini):not(.prov-codex):not(.prov-cursor):not(.prov-copilot):not(.prov-antigravity) { font-size: var(--text-xs); }
.prov-badge:not(.prov-claude):not(.prov-gemini):not(.prov-codex):not(.prov-cursor):not(.prov-copilot):not(.prov-antigravity)::before { display: none; }
/* Providers config: brand-icon leading column (icon-only .prov-badge). */
#prov-root .task-row > .prov-badge { flex: 0 0 22px; }
.org-badge.user, .org-badge.agent, .org-badge.jarvis { --c: var(--muted); }
/* Origin shown as ICON ONLY (no pill) — follows the icon schema, coloured by --c
   per the origin scheme above. Central: change an origin icon in dashboard.js. */
.org-badge-icon { background: none; border: none; padding: 0; gap: 0; min-width: 0; border-radius: 0; line-height: 0; }
.org-badge-icon svg { width: 15px; height: 15px; display: block; color: var(--c); }

/* Audit page header */
.audit-header { display: flex; align-items: baseline; gap: 14px; margin-bottom: 6px; flex-wrap: wrap; }

/* Filter bar */
.filter-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
.filter-bar label { font-size: var(--text-xs); color: var(--muted); font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; }
.filter-bar select, .filter-bar input { background: var(--bg); border: 1px solid var(--input); border-radius: var(--r-md); color: var(--text); height: 2.25rem; padding: 4px 12px; font-size: var(--text-sm); font-family: inherit; outline: none; }
.filter-bar select { cursor: pointer; }
.filter-bar select:focus, .filter-bar input:focus { border-color: var(--accent); }
/* Audit Events: origin/severity dropdowns live in the header, pushed right so
   they sit directly to the left of the main filter box (which no longer needs
   its own auto margin inside this group). */
.audit-header-filters { margin-left: auto; margin-bottom: 0; }
.audit-header-filters .list-filter { margin-left: 0; }
.qp-search { width: 100%; box-sizing: border-box; }
/* Canonical top-right list filter (page-header): one shared look across every
   list section (positions/candidates/interviews/plans/quests). */
.list-filter { margin-left: auto; background: var(--bg); border: 1px solid var(--input);
  border-radius: var(--r-md); color: var(--text); height: 2.25rem; padding: 4px 12px; font-size: var(--text-sm);
  font-family: inherit; width: 220px; max-width: 40vw; box-sizing: border-box; }
.list-filter:focus { outline: none; border-color: var(--accent); }
/* Clear-✕ for every .list-filter (added at runtime by list-filter.js). The wrapper
   takes the input's right-push so layout is unchanged; the ✕ shows only when the box
   has text and clears the filter on click. */
.list-filter-wrap { position: relative; display: inline-flex; align-items: center; margin-left: auto; max-width: 40vw; }
.audit-header-filters .list-filter-wrap { margin-left: 0; }
.list-filter-wrap > .list-filter { margin-left: 0; max-width: none; }
.list-filter-wrap.has-text > .list-filter { padding-right: 28px; }
.list-filter-clear { position: absolute; right: 5px; top: 50%; transform: translateY(-50%); display: none;
  align-items: center; justify-content: center; width: 20px; height: 20px; padding: 0; border: 0;
  border-radius: var(--r-sm); background: none; color: var(--muted); cursor: pointer;
  font-size: var(--text-lg); line-height: 1; }
.list-filter-clear:hover { color: var(--text); background: var(--surface-2); }
.list-filter-wrap.has-text .list-filter-clear { display: inline-flex; }

/* Flex table rows — shared base for events / agents / usage */
.ev-row, .agent-row, .usage-row { display: flex; align-items: center; gap: 8px; padding: 4px 10px; border-bottom: 1px solid var(--border); font-size: var(--text-xs); }
.ev-row:last-child, .agent-row:last-child, .usage-row:last-child { border-bottom: none; }
.ev-row.ev-data-row, .agent-row.ag-data-row, .usage-row.ur-data { min-height: var(--row-h); }
/* All system tables share ONE row height: var(--row-h) (events / agents / usage / task lists). */
.ev-tbl tbody tr, .usage-row.ur-data { min-height: var(--row-h); }
.ev-tbl thead tr, .usage-row.ur-hdr { min-height: var(--row-h-hdr); }
/* The in-table Details button follows the central .btn.btn-sm size (same as every
   other row/list button) — no vertical-padding override. */
/* On the full-height usage page: the first sections are natural height; the LAST
   (Sessions) expands to fill the remaining space and scrolls internally. */
.ev-fullpage #usage-board .usage-section { flex: 0 0 auto; }
.ev-fullpage #usage-board .usage-section:last-child { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.ev-fullpage #usage-board .usage-section:last-child .usage-list { flex: 1 1 auto; min-height: 0; max-height: none; overflow-y: auto; }
.ev-fullpage #usage-board .usage-section:last-child .usage-row.ur-hdr { position: sticky; top: 0; z-index: 3; background: var(--secondary); }
.ev-row.ev-data-row:hover, .agent-row.ag-data-row:hover, .usage-row.ur-data:hover { background: color-mix(in srgb, var(--accent) 5%, transparent); transition: background 0.1s; }
/* Severity row tint — non-info audit events get a subtle background wash (info
   stays neutral). Colours map to the canonical status tokens: debug→muted,
   warn→amber (accent2), error→red. The .ev-data-row hover variants are scoped
   one class deeper so they keep (a stronger) tint on hover instead of the
   neutral surface-2 default. */
.ev-row.ev-sev-debug { background: rgba(var(--muted-rgb),0.06); }
.ev-row.ev-sev-warn  { background: rgba(var(--accent2-rgb),0.08); }
.ev-row.ev-sev-error { background: rgba(var(--red-rgb),0.09); }
.ev-row.ev-data-row.ev-sev-debug:hover { background: rgba(var(--muted-rgb),0.12); }
.ev-row.ev-data-row.ev-sev-warn:hover  { background: rgba(var(--accent2-rgb),0.14); }
.ev-row.ev-data-row.ev-sev-error:hover { background: rgba(var(--red-rgb),0.15); }
.ev-row.ev-header-row, .agent-row.ag-header-row, .usage-row.ur-hdr { min-height: var(--row-h-hdr); background: var(--secondary); border-radius: var(--r-lg) var(--r-lg) 0 0; }
.ev-header-row .ev-hdr, .ag-header-row .ev-hdr, .ur-hdr .uc-hdr {
  /* Higher specificity than the .ev-col-* data rules so headers don't inherit
     their size/colour/mono. Live .tbl th: .7rem / 600 / uppercase / muted. */
  font-size: 0.7rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--muted); font-family: var(--font-sans); }

/* Event columns */
.ev-col-age  { width: 36px; flex-shrink: 0; text-align: right; font-size: var(--text-xs); color: var(--muted); white-space: nowrap; }
.ev-col-org  { width: 60px; flex-shrink: 0; display: flex; align-items: center; justify-content: flex-end; overflow: hidden; }
.ev-col-prov { width: 62px; flex-shrink: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--text-xs); color: var(--muted); text-align: center; }
.ev-col-evt  { width: 150px; flex-shrink: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--text-xs); font-family: var(--font-mono); color: var(--muted); text-align: center; }
.ev-col-data { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text); }
.ev-col-btn  { flex-shrink: 0; width: 74px; display: flex; justify-content: flex-end; }

/* (Removed bespoke .btn-details — detail/row buttons now use the central
   .btn.btn-sm.btn-primary, like every other button.) */

/* Modern on/off switch — shared portal control (documents catalog + edit popup,
   planner/quests instruction list). Off = muted knob on a faint track; on = the system
   accent colour. Markup: <label class="ui-switch"><input type=checkbox><span
   class="ui-switch-slider"></span></label>. */
.ui-switch { position: relative; display: inline-block; width: 34px; height: 18px; flex-shrink: 0; }
.ui-switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.ui-switch-slider { position: absolute; inset: 0; cursor: pointer; background: var(--surface-3); border: 1px solid var(--border); border-radius: 999px; transition: background 0.15s, border-color 0.15s; }
.ui-switch-slider::before { content: ''; position: absolute; height: 12px; width: 12px; left: 2px; top: 2px; background: var(--muted); border-radius: 50%; transition: transform 0.15s, background 0.15s; }
.ui-switch input:checked + .ui-switch-slider { background: var(--primary); border-color: var(--primary); }
.ui-switch input:checked + .ui-switch-slider::before { transform: translateX(16px); background: var(--on-accent, #fff); }
/* Colour variants — SAME style as the default (solid coloured track + plain white knob), each
   keeping its own enabled colour. */
/* Amber variant — on but in a "warn" state (e.g. a document enabled but not tracked). */
.ui-switch.sw-warn input:checked + .ui-switch-slider { background: var(--accent2); border-color: var(--accent2); }
/* Blue "global scope" variant (the instruction popup's Global toggle). */
.ui-switch.sw-global input:checked + .ui-switch-slider { background: var(--accent); border-color: var(--accent); }
/* Yellow variant — diagnostic / debug mode (e.g. guardian debug toggle). */
.ui-switch.sw-yellow input:checked + .ui-switch-slider { background: var(--accent2); border-color: var(--accent2); }
/* Red variant — "on" carries extra risk (e.g. the beta-versions update opt-in). */
.ui-switch.sw-red input:checked + .ui-switch-slider { background: var(--red); border-color: var(--red); }
/* Orange variant — a dependency ability (other enabled abilities require it), so turning it off
   cascades. Uses the system WARNING colour (--accent2 = design-tokens --warning), not the
   off-palette --sw-orange. */
.ui-switch.sw-orange input:checked + .ui-switch-slider { background: var(--accent2); border-color: var(--accent2); }
/* Locked switch (e.g. a required system ability) — non-interactive, default cursor. */
.ui-switch input:disabled + .ui-switch-slider { cursor: default; }

/* Config-page id box — the leading key/name box in an ability Configuration list
   row (planner/quests instructions, advisor profiles). Shared so the box shape and
   the enabled/disabled tint live in ONE place; each config page scopes only its own
   width (e.g. #pq-cfg-root .cfg-name-box { width:150px }, #adv-cfg-root .cfg-name-box
   { width:140px }). The base carries no colour — a state modifier always applies.
   The blue "global scope" override (.pq-global) stays planner/quests-scoped. */
.cfg-name-box { display: inline-block; flex: 0 0 auto; box-sizing: border-box; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: center; }
.cfg-name-box.cfg-enabled  { --c: var(--accent); }
.cfg-name-box.cfg-disabled { --c: var(--muted); }
/* Advisor config list (#adv-cfg-root) — its own id-box width plus the row's
   flexible description column + fixed Details button (the planner/quests list sets
   the same inside its #pq-cfg-root scope). */
#adv-cfg-root .cfg-name-box { width: 140px; }
#adv-cfg-root .task-row .tr-activity { flex: 1; min-width: 0; }
#adv-cfg-root .task-row .btn { flex: 0 0 auto; }

/* Config-page text/combo inputs — shared base so every config page gets the same
   input shape without duplicating the rules; each page scopes only size overrides
   (e.g. #mem-cfg-root .cfg-ctrl-text { width: 90px; … }). */
.cfg-ctrl-text,
.cfg-ctrl-combo {
  background: var(--bg); border: 1px solid var(--input); border-radius: var(--r-md);
  color: var(--text); height: 2.25rem; padding: 4px 12px; font-size: var(--text-sm); font-family: inherit;
  box-sizing: border-box; outline: none; transition: border-color 0.12s;
}
.cfg-ctrl-text:focus,
.cfg-ctrl-combo:focus { border-color: var(--accent); }
/* Text boxes INSIDE a table row (.task-row) or card (.lp-card) shrink to the small
   in-row control size — matching the .btn.btn-sm height (~30px) so inputs and row
   buttons line up. Inputs in page headers / modals keep the full h-9 (36px) size.
   Checkboxes/radios (the .ui-switch toggle) are excluded. */
.task-row .cfg-ctrl-text, .task-row .cfg-ctrl-combo,
.task-row input:not([type="checkbox"]):not([type="radio"]), .task-row select,
.lp-card .cfg-ctrl-text, .lp-card .cfg-ctrl-combo,
.lp-card input:not([type="checkbox"]):not([type="radio"]), .lp-card select {
  height: 1.9rem; padding: 0.35rem 0.7rem; font-size: 0.78rem;
}

/* Agent columns */
.ag-age  { width: 38px; flex-shrink: 0; text-align: right; font-size: var(--text-xs); color: var(--muted); }
.ag-org  { width: 160px; flex-shrink: 0; display: flex; align-items: center; justify-content: flex-start; overflow: hidden; }
.ag-prov { width: 62px; flex-shrink: 0; font-size: var(--text-xs); color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: center; }
.ag-name { width: 160px; flex-shrink: 0; font-size: var(--text-sm); font-family: var(--font-mono); color: var(--accent); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ag-role { width: 120px; flex-shrink: 0; font-size: var(--text-sm); font-weight: 600; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ag-task { flex: 1; min-width: 0; font-size: var(--text-sm); color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Stalled worker (open agent-start past the staleness cutoff — almost certainly hard-killed). */
.agent-row.ag-stalled { opacity: 0.72; }
.agent-row.ag-stalled .ag-age { color: var(--red); font-weight: 600; }

/* Usage columns */
.usage-section { margin-bottom: 24px; }
.usage-row.ur-total { min-height: var(--row-h); font-weight: 700; color: var(--accent); }
/* Scroll a usage table once it passes ~10 data rows; the header stays pinned. */
.usage-list { max-height: calc(var(--row-h-hdr) + 10 * var(--row-h) + 10px); overflow-y: auto; }
.usage-list .usage-row.ur-hdr { position: sticky; top: 0; z-index: 1; }
.uc-dim   { color: var(--muted); }
.uc-name  { flex: 1; min-width: 80px; font-size: var(--text-sm); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.uc-turns { width: 50px;  flex-shrink: 0; text-align: right; font-family: var(--font-mono); font-size: var(--text-xs); }
/* Conversation-volume columns on the by-provider / by-owner usage tables (prompts = user turns,
   responses = assistant turns). Sized to fit the header words at --text-xs. */
.uc-prompts   { width: 58px; flex-shrink: 0; text-align: right; font-family: var(--font-mono); font-size: var(--text-xs); }
.uc-responses { width: 68px; flex-shrink: 0; text-align: right; font-family: var(--font-mono); font-size: var(--text-xs); }
.uc-in    { width: 72px;  flex-shrink: 0; text-align: right; font-family: var(--font-mono); font-size: var(--text-xs); }
.uc-out   { width: 72px;  flex-shrink: 0; text-align: right; font-family: var(--font-mono); font-size: var(--text-xs); }
.uc-total { width: 90px;  flex-shrink: 0; text-align: right; font-family: var(--font-mono); font-size: var(--text-sm); }
.uc-cache { width: 72px;  flex-shrink: 0; text-align: right; font-family: var(--font-mono); font-size: var(--text-xs); }
.uc-age   { width: 36px;  flex-shrink: 0; text-align: right; font-size: var(--text-xs); white-space: nowrap; }
.uc-sid   { width: 220px; flex-shrink: 0; font-size: var(--text-xs); font-family: var(--font-mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.uc-root  { width: 30px;  flex-shrink: 0; display: flex; align-items: center; justify-content: center; }
/* Owner badges fill the column so every root (user/agent/jarvis) is one uniform
   box sized to the widest value ("jarvis"). */
.uc-root .org-badge { width: 100%; min-width: 0; }
.uc-prov  { width: 40px;  flex-shrink: 0; display: flex; align-items: center; justify-content: center; }
.uc-model { flex: 1; min-width: 0; font-size: var(--text-xs); font-family: var(--font-mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.uc-pctcol { width: 52px; flex-shrink: 0; text-align: right; font-family: var(--font-mono); font-size: var(--text-sm); }
/* Est. Jarvis-injected tokens column (chars/4) — mirrors the Sessions table's injected cell; sits
   alongside the tax% column on the By-provider totals. Distinct from .uc-in (input tokens). */
.uc-inj   { width: 72px;  flex-shrink: 0; text-align: right; font-family: var(--font-mono); font-size: var(--text-xs); }
/* #P94 Jarvis-context-tax column (est. injected tokens); the header carries the by-ability popup link.
   Width matches the Sessions table's tax cell (.uc-pctcol, 52px) so the injected+tax pair reads the same. */
.uc-tax   { width: 52px;  flex-shrink: 0; text-align: right; font-family: var(--font-mono); font-size: var(--text-xs); }
.uc-tax-link { cursor: pointer; margin-left: 4px; text-decoration: none; opacity: 0.7; }
.uc-tax-link:hover { opacity: 1; }
.uc-sid code { font-size: .8em; }

/* Audit detail modal */
.modal-box.modal-audit { width: 820px; max-height: 88vh; display: flex; flex-direction: column; }
/* #P94 slim variant for the compact "Context injected by ability" popup — 3-class specificity so it
   beats the 820px .modal-box.modal-audit rule above (equal-specificity tie would otherwise win here). */
.modal-box.modal-audit.modal-slim { width: 400px; padding: 16px 18px 14px; }
.modal-hdr { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 14px; flex-shrink: 0; }
/* Title matches the login popup heading: white, text-lg, bold (system-wide popup title style). */
.modal-hdr h3 { margin: 0; font-size: var(--text-lg); font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: flex; align-items: center; gap: 7px; min-width: 0; }
/* Title text uses the system title font (not mono), matching planner/login popup titles. */
.ev-type-text { color: var(--text); overflow: hidden; text-overflow: ellipsis; }
/* Provider icon + model grouped as ONE header unit (kept tight together, set off from the event
   type by margin — so it reads as "type … [provider] model"). Model uses the small muted scale. */
.ev-type-meta { display: inline-flex; align-items: center; gap: 5px; margin-left: 6px; flex-shrink: 0; }
.ev-type-model { font-size: var(--text-xs); font-weight: 500; color: var(--muted); }
/* The owner box in the popup TITLE sizes to its content (like planner's detail badge), not the
   56px column-alignment min-width the .org-badge carries in list/table rows; trim its vertical
   footprint so its height matches planner's inline type badge. */
.modal-hdr .org-badge { min-width: 0; line-height: 1.4; padding-top: 0; padding-bottom: 0; }
.ev-hdr-right { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.ev-id-pill { display: flex; align-items: center; gap: 6px; font-size: var(--text-xs); color: var(--muted); font-family: var(--font-mono); white-space: nowrap; }
.modal-body { overflow-y: auto; flex: 1; }
.modal-box.modal-resizable > .modal-body { min-height: 0; }
.detail-kv { margin-bottom: 10px; }
.detail-label { font-size: var(--text-xs); font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); margin-bottom: 2px; }
/* Popup FIELD/detail captions (.modal-box form-field / detail / kv labels) stay
   MUTED — they are field captions, distinct from a section sub-sub-title
   (.tier-label / .td-label, which is white var(--text); see the SYSTEM
   sub-sub-title rule above). The popup TITLE is white var(--text) and the tenant
   crumb is accent2 — see the "POPUP STYLE" rule near `.modal-header h3`. */
.modal-box .form-field label,
.modal-box .doc-skill-line label,
.modal-box .doc-track-lbl,
.modal-box .detail-label,
.modal-box .kv-label { color: var(--muted); }
.detail-val { font-size: var(--text-base); color: var(--text); word-break: break-all; }
.detail-val.mono { font-family: var(--font-mono); font-size: var(--text-sm); }
.detail-pre { font-family: var(--font-mono); font-size: var(--text-sm); white-space: pre-wrap; word-break: break-word; background: var(--bg); border: 1px solid var(--border); border-radius: var(--r-md); padding: 8px 10px; margin-top: 3px; max-height: 220px; overflow-y: auto; color: var(--text); line-height: 1.5; transition: max-height 0.15s ease; }
.detail-pre.expanded { max-height: none; }
.detail-expand-btn { float: right; font-size: var(--text-xs); color: var(--muted); cursor: pointer; padding: 0; background: none; border: none; font-family: inherit; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; }
.detail-expand-btn:hover { color: var(--accent); }
.detail-meta { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 14px; align-items: center; }
.detail-footer { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border); display: flex; justify-content: flex-end; flex-shrink: 0; align-items: center; }
/* Event detail footer: spacer | timestamp (center) | close (right) */
.ev-detail-footer { justify-content: space-between; }
.ev-footer-pad { flex: 1; }
.ev-footer-ts { color: var(--muted); font-size: var(--text-xs); font-family: var(--font-mono); }
.ev-footer-end { flex: 1; display: flex; justify-content: flex-end; gap: 8px; }
/* Event info box: floats right, groups provider + model + tokens */
/* Audit modal body: flex-column so top meta shrinks and data section grows */
.modal-box.modal-audit .modal-body { overflow-y: hidden; display: flex; flex-direction: column; }
.ev-body-top { flex-shrink: 0; }
.ev-body-data { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: 10px; }
/* Each data/metadata kv block grows proportionally and its pre fills it */
.ev-data-kv { display: flex; flex-direction: column; flex: 1; min-height: 0; margin-bottom: 0; }
.ev-data-kv .detail-label { flex-shrink: 0; }
.ev-data-kv .detail-pre { flex: 1; min-height: 0; max-height: none; overflow-y: auto; }
.ev-info-box { float: right; margin: 0 0 12px 16px; display: inline-flex; flex-direction: column; gap: 5px; min-width: 128px; max-width: 200px; border: 1px solid var(--border); border-radius: var(--r-md); padding: 8px 10px; background: var(--surface-2); align-self: flex-start; }
.ev-info-row { display: flex; flex-direction: row; align-items: baseline; gap: 6px; white-space: nowrap; }
.ev-info-label { font-size: var(--text-2xs); font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); flex-shrink: 0; }
.ev-info-val { font-size: var(--text-xs); color: var(--text); overflow: hidden; text-overflow: ellipsis; }
.ev-info-val.mono { font-family: var(--font-mono); }
/* (Removed bespoke .btn-close-detail — the detail panel's Close uses the central .btn.) */

/* ───────────────────────────────────────────────────────────────────────
   Portal — documents section
   Reuses the shared list / event / modal / pill families; only the
   documents-specific column widths and a few muted helpers live here. Colours
   come from design tokens only (no hex literals).
   ─────────────────────────────────────────────────────────────────────── */

/* Monitored-documents list: a fixed mtime column sits between the path
   (.tr-activity, flex) and the Details button. */
.doc-mtime { flex-shrink: 0; width: 150px; text-align: right; font-size: var(--text-xs); color: var(--muted); white-space: nowrap; }

/* Monitored row, left → right: type badge · path · green "tracked" box · flexible
   gap · "updated" bubble · date · Details. The path (.doc-path) shrinks but does
   NOT grow, so the green box hugs it on the left; .doc-row-spacer takes the slack
   so the "updated" bubble sits right against the date. The "tracked" box is the
   compact in-row .tr-badge (same size as the type badge), tinted green; the
   system-amber (--accent2) "updated" bubble (a fixed-width slot that keeps the
   date column aligned) marks a file modified in the last 12h. No new tokens. */
.tr-activity.doc-path { flex: 0 1 auto; }
/* Merged Documents page: a section head — sub-sub-title (left) + that table's
   filter (and the events Show-more) pushed right. */
.doc-section-head { display: flex; align-items: center; gap: 10px; margin: 18px 0 6px; }
.doc-section-head .td-label { margin-bottom: 0; }
.doc-section-head .btn { margin-left: auto; }
.doc-row-spacer { flex: 1 1 auto; min-width: 8px; }
.doc-updated-col { flex-shrink: 0; width: 80px; display: flex; align-items: center; justify-content: flex-end; }
.pill.doc-tracked { flex-shrink: 0; --c: var(--green); }
.pill.doc-fresh { --c: var(--accent2); }

/* Change-events feed columns. The shared .ev-col-* set is sized for the audit
   feed; documents needs its own when / user / type widths (reusing .ev-col-prov
   and .ev-col-data as-is for the provider and document cells). */
.ev-col-time  { width: 150px; flex-shrink: 0; font-size: var(--text-xs); color: var(--muted); white-space: nowrap; }
.ev-col-user  { width: 110px; flex-shrink: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ev-col-dtype { width: 112px; flex-shrink: 0; display: flex; align-items: center; }

/* Config catalog editor: the document-id box (first column) mirrors the
   planner/quests instruction id box — system `--text-xs`, fixed width — and is
   colour-coded by status (enabled + tracked = green, enabled + untracked =
   amber, disabled = grey). Enable/Disable sits right after it and Edit at the
   row's end; both reuse the central `.btn.btn-sm`. The read-only
   "managed by" note replaces the buttons for foreign-owned types. */
.doc-id { display: inline-block; flex: 0 0 auto; width: 120px; box-sizing: border-box; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: center; --c: var(--muted); }
/* Enabled = indigo tint; disabled = neutral tint (status colour also on the switch). */
.doc-id.st-on   { --c: var(--accent); }
.doc-id.st-off  { --c: var(--muted); }
/* Edit popup: Track is a modern on/off switch (the shared .ui-switch) pinned at
   title level in the header; the skill shows as a single read-only line. */
.doc-head-right { display: flex; align-items: center; gap: 14px; }
.doc-track-toggle { display: flex; align-items: center; gap: 8px; }
.doc-track-lbl { font-size: var(--text-xs); color: var(--muted); font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; }
.doc-skill-line { display: flex; align-items: baseline; gap: 8px; margin-bottom: 12px; }
.doc-skill-line label { font-size: var(--text-sm); color: var(--muted); font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; }
.doc-skill-val { font-family: var(--font-mono); font-size: var(--text-sm); color: var(--text); }
.doc-managed { flex-shrink: 0; margin-left: 6px; font-size: var(--text-xs); color: var(--muted); font-style: italic; }
/* Edit/create popups (document type + path token) stack bare .form-field divs
   directly in the .modal-box — no .form-row wrapper, so they carry no margin of
   their own and each field's label crowds the field above. Give them the same
   12px rhythm as the .doc-skill-line, plus a taller textarea floor so multi-line
   When/What/Guidance are comfortable to edit. The direct-child (`>`) selectors
   keep this scoped to these bare-stacked popups: planner/quests/interviews/
   providers wrap their fields in .form-row / container divs (already spaced and
   independently sized), so those popups are untouched. */
.modal-box > .form-field { margin-bottom: 12px; }
.modal-box > .form-field textarea { min-height: 76px; padding: 8px 12px; }
/* Guidance seeds the generated SKILL.md body — give it a much taller box than
   the short When/What triggers. The class selector out-specifies the shared
   textarea rule above so this min-height wins. */
.modal-box > .form-field textarea.doc-guidance { min-height: 200px; }
/* Float a footer action to the right edge (Save + Delete stay clustered left).
   The planner uses #pq-cfg-root-scoped .pq-cancel for this; documents popups are
   not under that root, so this unscoped utility carries the same intent. */
.form-submit .btn-push-right { margin-left: auto; }

/* Details popup footer: metadata on the left, Close on the right. */
.doc-detail-footer { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.doc-foot-meta { font-size: var(--text-xs); color: var(--muted); }

/* In-flight custom-type creation rows (top of the catalog list). A `generating`
   row shows a minimal token-based spinner in the switch column; a `failed` row
   shows the error text plus a dismiss ×. The spinner is a thin rotating ring —
   neutral --border with an --accent leading edge — sized to sit where the
   .ui-switch normally does. */
@keyframes doc-spin { to { transform: rotate(360deg); } }
.doc-spinner { flex-shrink: 0; width: 14px; height: 14px; border: 2px solid var(--border); border-top-color: var(--accent); border-radius: var(--r-pill); animation: doc-spin 0.7s linear infinite; }
/* Larger standalone spinner + a compact busy popup (e.g. memory "Fix indexes"). */
.spinner-lg { width: 30px; height: 30px; border: 3px solid var(--border); border-top-color: var(--accent); border-radius: var(--r-pill); animation: doc-spin 0.8s linear infinite; }
.modal-box.modal-busy { width: auto; min-width: 0; max-width: 320px; display: flex; flex-direction: column; align-items: center; gap: 14px; padding: 28px 34px; text-align: center; color: var(--text); font-size: var(--text-sm); }
.doc-pending-sub { font-size: var(--text-xs); color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.doc-pending-err { flex: 1; min-width: 0; font-size: var(--text-xs); color: var(--red); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: right; }
.doc-pending-x { flex-shrink: 0; font-size: var(--text-lg); }

/* Advices (#P22) — the advisor "Advices" view + the per-plan/quest popups.
   Verdict pills reuse the existing .pill colour classes (mapped in JS), so no
   colour is declared here — only layout/structure. */
.advice-list { display: flex; flex-direction: column; gap: 10px; }
.advice-item { border: 1px solid var(--border); border-radius: var(--r-md); background: var(--panel); padding: 12px 14px; }
.advice-head { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; }
.advice-profile { font-weight: 600; font-size: var(--text-sm); }
.advice-head .tr-badge.date { margin-left: auto; }
.advice-summary { margin-top: 6px; font-size: var(--text-sm); color: var(--text); }
.advice-item .td-value-md { margin-top: 4px; }
/* fixed-width result pill so the board's verdict bubbles align across rows
   (mirrors .plan-pill) */
.advice-pill { width: 92px; text-align: center; box-sizing: border-box; }
/* trace cards + popup: the Request / Advice (or Outcome) sections, split by a line */
.advice-req, .advice-resp { margin-top: 8px; }
.advice-req .td-value-md, .advice-resp .td-value-md { max-height: 240px; overflow: auto; }
.advice-divider { border: 0; border-top: 1px solid var(--border); margin: 14px 0; }

/* =======================================================================
   Guardian config components
   ======================================================================= */

#grd-cfg-root .cfg-name-box { width: 160px; }
#grd-cfg-root .task-row .tr-activity { flex: 1; min-width: 0; }
#grd-cfg-root #grd-groups { margin-top: 20px; }
#grd-cfg-root #grd-groups .td-label { margin-top: 20px; }
#grd-cfg-root #grd-groups .td-label:first-child { margin-top: 0; }
/* Rules modal list — scrollable, fixed height so the modal stays compact */
.grd-rules-list { max-height: 360px; overflow-y: auto; }
/* Rule rows inside the Customize modal: severity badge → description → switch */
.grd-rule-row { display: flex; align-items: center; gap: 12px; padding: 6px 0; border-top: 1px solid var(--border); }
.grd-rule-row:first-child { border-top: none; }
.grd-rule-desc { flex: 1; font-size: var(--text-sm); }
/* Severity badge — fixed-width so all badges align; colour overrides inherit from .tr-badge.dep / .tr-badge.warn */
.grd-rule-sev { flex-shrink: 0; min-width: 62px; text-align: center; }
.grd-rule-sev.warn { --c: var(--accent2); }
/* Header action strip (debug toggle + Save / Reset buttons) */
#grd-cfg-root .cfg-header-actions { display: flex; align-items: center; gap: 12px; margin-left: auto; }
.grd-debug-wrap { display: flex; align-items: center; gap: 8px; font-size: var(--text-sm); }

/* ---- Server project view-only (non-admin viewing via ?server=) -------------
   The hard read-only boundary is server-side (mutating requests are 403'd); this is the
   matching visual treatment: a muted notice plus dimmed, non-interactive form controls so a
   viewer sees they cannot edit. Navigation anchors stay live (only form controls are gated). */
.view-only-note { margin: 0 0 14px; padding: 8px 12px; border: 1px solid var(--border); border-radius: var(--r-md); background: var(--panel); color: var(--muted); font-size: var(--text-sm); }
.portal-content.view-only button, .portal-content.view-only input, .portal-content.view-only select, .portal-content.view-only textarea { pointer-events: none; opacity: .55; }

/* ---- Backend-unavailable takeover (backend-guard.js) ------------------------
   Full-screen "reconnecting" overlay shown when the stateless frontend cannot reach the backend.
   Sits above EVERYTHING (header z300, modals z1000, toasts z10000) so nothing shows through, and
   reuses the login page's dimmed brand watermark background. */
html.jm-backend-down-lock { overflow: hidden; }
.jm-backend-down { position: fixed; inset: 0; z-index: 100000; display: none; align-items: center; justify-content: center; background: var(--bg); }
.jm-backend-down.open { display: flex; }
.jm-backend-down .login-bg { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; z-index: 0; opacity: 0.06; pointer-events: none; user-select: none; color: var(--accent); }
.jm-backend-down .login-bg img { width: min(90vw, 900px); height: auto; }
.jm-backend-down-card { position: relative; z-index: 1; display: flex; flex-direction: column; align-items: center; gap: 14px; text-align: center; padding: 28px 32px; }
.jm-backend-down-card .section-title { margin: 0; }
.jm-backend-down-card .title-description { margin: 0; color: var(--muted); }
