/* --aether-surface: an inset surface ON a panel -- a slider track, a progress trough, a
   filter field. It was CONSUMED in seven places and DEFINED nowhere, so every consumer
   rendered the dark fallback #374151 in all nine themes, including the two light ones:
   measured 2026-10-26, a blend slider's thumb border (which follows --aether-text and so
   goes dark in a light theme) against that permanently dark track gave 1.26 in sepia and
   1.72 in daylight, failing SC 1.4.11's 3:1 (ToDo.md §200.35, BugToDo Issue 41).

   It aliases --aether-panel-strong, which every theme already defines with exactly this
   semantic and which flips the right way: lighter than the panel in a dark theme
   (#2a2a3e -> #34344f), darker in a light one (#ffffff -> #edf1f7, #fbf5e6 -> #efe4c8).
   --aether-control was tried first and reverted: it is effectively the panel's own colour
   in the dark themes, so the track vanished as a surface. The fallback is kept for a
   document with no data-theme set, where --aether-panel-strong is itself undefined. */
:root { --aether-surface: var(--aether-panel-strong, #374151); }

html[data-theme="aether-dark"] {
    --aether-bg: #1e1e2e;
    --aether-panel: #2a2a3e;
    --aether-panel-strong: #34344f;
    --aether-control: #171725;
    --aether-control-text: #f4f4f5;
    --aether-text: #f4f4f5;
    --aether-muted: #a1a1aa;
    --aether-border: #3f3f58;
    --aether-accent: #7c3aed;
    --aether-accent-hover: #6d28d9;
    --aether-focus: #a78bfa;
    --aether-danger: #f87171;
    --aether-danger-strong: #b91c1c;
    --aether-success: #4ade80;
    --aether-success-strong: #15803d;
    --aether-warning: #fbbf24;
    --aether-warning-strong: #b45309;
    --aether-info: #60a5fa;
    --aether-info-strong: #1d4ed8;
    --aether-overlay: rgba(0, 0, 0, 0.6);
    --aether-code-bg: #171725;
    --aether-bubble-user-bg: #4c1d95;
    --aether-bubble-user-text: #f4f4f5;
    --aether-bubble-assistant-bg: #34344f;
    --aether-bubble-assistant-text: #f4f4f5;
}

:root,
html[data-theme="graphite"] {
    --aether-bg: #181a1f;
    --aether-panel: #252830;
    --aether-panel-strong: #30343d;
    --aether-control: #101217;
    --aether-control-text: #f1f5f9;
    --aether-text: #e5e7eb;
    --aether-muted: #a3aab7;
    --aether-border: #454b57;
    --aether-accent: #2f80ed;
    --aether-accent-hover: #256bd1;
    --aether-focus: #7db7ff;
    --aether-danger: #ff7b7b;
    --aether-danger-strong: #b91c1c;
    --aether-success: #6ee7a8;
    --aether-success-strong: #15803d;
    --aether-warning: #f5c451;
    --aether-warning-strong: #b45309;
    --aether-info: #7db7ff;
    --aether-info-strong: #1d4ed8;
    --aether-overlay: rgba(0, 0, 0, 0.62);
    --aether-code-bg: #101217;
    --aether-bubble-user-bg: #1f3a68;
    --aether-bubble-user-text: #f1f5f9;
    --aether-bubble-assistant-bg: #30343d;
    --aether-bubble-assistant-text: #e5e7eb;
}

html[data-theme="daylight"] {
    --aether-bg: #f5f7fb;
    --aether-panel: #ffffff;
    --aether-panel-strong: #edf1f7;
    --aether-control: #ffffff;
    --aether-control-text: #111827;
    --aether-text: #111827;
    --aether-muted: #4b5563;
    --aether-border: #cbd5e1;
    --aether-accent: #2563eb;
    --aether-accent-hover: #1d4ed8;
    --aether-focus: #2563eb;
    --aether-danger: #b91c1c;
    --aether-danger-strong: #7f1d1d;
    --aether-success: #047857;
    --aether-success-strong: #064e3b;
    --aether-warning: #b45309;
    --aether-warning-strong: #78350f;
    --aether-info: #1d4ed8;
    --aether-info-strong: #1e3a8a;
    --aether-overlay: rgba(15, 23, 42, 0.45);
    --aether-code-bg: #e2e8f0;
    --aether-bubble-user-bg: #dbeafe;
    --aether-bubble-user-text: #111827;
    --aether-bubble-assistant-bg: #edf1f7;
    --aether-bubble-assistant-text: #111827;
}

html[data-theme="high-contrast"] {
    --aether-bg: #000000;
    --aether-panel: #111111;
    --aether-panel-strong: #1d1d1d;
    --aether-control: #000000;
    --aether-control-text: #ffffff;
    --aether-text: #ffffff;
    --aether-muted: #e5e7eb;
    --aether-border: #ffffff;
    --aether-accent: #ffd400;
    --aether-accent-hover: #ffea66;
    --aether-focus: #00e5ff;
    --aether-danger: #ff6b6b;
    --aether-danger-strong: #ff2d2d;
    --aether-success: #00ff9d;
    --aether-success-strong: #00c878;
    --aether-warning: #ffd400;
    --aether-warning-strong: #b45309;
    --aether-info: #00e5ff;
    --aether-info-strong: #0086a8;
    --aether-overlay: rgba(0, 0, 0, 0.82);
    --aether-code-bg: #000000;
    --aether-bubble-user-bg: #1d1d1d;
    --aether-bubble-user-text: #ffd400;
    --aether-bubble-assistant-bg: #111111;
    --aether-bubble-assistant-text: #ffffff;
}

html[data-theme="nord"] {
    --aether-bg: #2e3440;
    --aether-panel: #3b4252;
    --aether-panel-strong: #434c5e;
    --aether-control: #272c36;
    --aether-control-text: #eceff4;
    --aether-text: #e5e9f0;
    --aether-muted: #9aa5b8;
    --aether-border: #4c566a;
    --aether-accent: #88c0d0;
    --aether-accent-hover: #8fbcbb;
    --aether-focus: #81a1c1;
    --aether-danger: #bf616a;
    --aether-danger-strong: #a54a52;
    --aether-success: #a3be8c;
    --aether-success-strong: #7f9e6a;
    --aether-warning: #ebcb8b;
    --aether-warning-strong: #c9a96a;
    --aether-info: #81a1c1;
    --aether-info-strong: #5e81ac;
    --aether-overlay: rgba(46, 52, 64, 0.7);
    --aether-code-bg: #272c36;
    --aether-bubble-user-bg: #4c566a;
    --aether-bubble-user-text: #eceff4;
    --aether-bubble-assistant-bg: #3b4252;
    --aether-bubble-assistant-text: #e5e9f0;
}

html[data-theme="dracula"] {
    --aether-bg: #282a36;
    --aether-panel: #343746;
    --aether-panel-strong: #424458;
    --aether-control: #21222c;
    --aether-control-text: #f8f8f2;
    --aether-text: #f8f8f2;
    --aether-muted: #9ca0b0;
    --aether-border: #44475a;
    --aether-accent: #bd93f9;
    --aether-accent-hover: #a87fe0;
    --aether-focus: #ff79c6;
    --aether-danger: #ff5555;
    --aether-danger-strong: #d63d3d;
    --aether-success: #50fa7b;
    --aether-success-strong: #2fbf57;
    --aether-warning: #f1fa8c;
    --aether-warning-strong: #c4cf5a;
    --aether-info: #8be9fd;
    --aether-info-strong: #5fc7e0;
    --aether-overlay: rgba(20, 21, 28, 0.7);
    --aether-code-bg: #21222c;
    --aether-bubble-user-bg: #44475a;
    --aether-bubble-user-text: #f8f8f2;
    --aether-bubble-assistant-bg: #343746;
    --aether-bubble-assistant-text: #f8f8f2;
}

html[data-theme="solarized-dark"] {
    --aether-bg: #002b36;
    --aether-panel: #073642;
    --aether-panel-strong: #0a4a5a;
    --aether-control: #00212b;
    --aether-control-text: #fdf6e3;
    --aether-text: #eee8d5;
    --aether-muted: #93a1a1;
    --aether-border: #194e5a;
    --aether-accent: #268bd2;
    --aether-accent-hover: #1f74ad;
    --aether-focus: #2aa198;
    --aether-danger: #dc322f;
    --aether-danger-strong: #b5211f;
    --aether-success: #859900;
    --aether-success-strong: #6b7a00;
    --aether-warning: #b58900;
    --aether-warning-strong: #936e00;
    --aether-info: #268bd2;
    --aether-info-strong: #1f74ad;
    --aether-overlay: rgba(0, 20, 26, 0.7);
    --aether-code-bg: #00212b;
    --aether-bubble-user-bg: #0a4a5a;
    --aether-bubble-user-text: #fdf6e3;
    --aether-bubble-assistant-bg: #073642;
    --aether-bubble-assistant-text: #eee8d5;
}

html[data-theme="forest"] {
    --aether-bg: #0f1c14;
    --aether-panel: #16271c;
    --aether-panel-strong: #1f3528;
    --aether-control: #0a140e;
    --aether-control-text: #e7f3ea;
    --aether-text: #dceadf;
    --aether-muted: #8fa896;
    --aether-border: #2c4636;
    --aether-accent: #3fae6b;
    --aether-accent-hover: #339158;
    --aether-focus: #6ee79f;
    --aether-danger: #e06b6b;
    --aether-danger-strong: #b94a4a;
    --aether-success: #5fce86;
    --aether-success-strong: #3f9e63;
    --aether-warning: #d6b14e;
    --aether-warning-strong: #a8872f;
    --aether-info: #5bb0c9;
    --aether-info-strong: #3d86a0;
    --aether-overlay: rgba(8, 18, 12, 0.7);
    --aether-code-bg: #0a140e;
    --aether-bubble-user-bg: #1f3528;
    --aether-bubble-user-text: #e7f3ea;
    --aether-bubble-assistant-bg: #16271c;
    --aether-bubble-assistant-text: #dceadf;
}

html[data-theme="sepia"] {
    --aether-bg: #f4ecd8;
    --aether-panel: #fbf5e6;
    --aether-panel-strong: #efe4c8;
    --aether-control: #fffaf0;
    --aether-control-text: #3b2f1e;
    --aether-text: #3b2f1e;
    --aether-muted: #6b5d49;
    --aether-border: #d8c9a8;
    --aether-accent: #a8762b;
    --aether-accent-hover: #8c601f;
    --aether-focus: #a8762b;
    --aether-danger: #b5341f;
    --aether-danger-strong: #8a2718;
    --aether-success: #5a7d2b;
    --aether-success-strong: #43601f;
    --aether-warning: #b5791f;
    --aether-warning-strong: #8a5b16;
    --aether-info: #2f6f9e;
    --aether-info-strong: #235578;
    --aether-overlay: rgba(59, 47, 30, 0.4);
    --aether-code-bg: #ece0c4;
    --aether-bubble-user-bg: #e7d6a8;
    --aether-bubble-user-text: #3b2f1e;
    --aether-bubble-assistant-bg: #efe4c8;
    --aether-bubble-assistant-text: #3b2f1e;
}

/* ---------------------------------------------------------------------------
   §161.8  Derived sidebar-nav tokens.

   Declared once rather than per theme: each is a mix toward `--aether-text`,
   `--aether-accent` or `--aether-focus`, so it self-corrects across theme
   polarity and one declaration covers all nine themes.

   The 75% mixes are load-bearing, not adjustable decoration. Pure
   `--aether-accent` measures 2.19:1 against the active tint in one theme and
   pure `--aether-focus` 2.81:1 against the band in another, both below the
   3:1 that WCAG 1.4.11 asks of a state indicator. `--aether-nav-quiet` exists
   because plain `--aether-muted` on the panel is a 1.4.3 failure in one theme
   (4.05:1).
   --------------------------------------------------------------------------- */
:root {
    --aether-nav-band:       color-mix(in srgb, var(--aether-text)   16%, var(--aether-panel));
    --aether-nav-band-hover: color-mix(in srgb, var(--aether-text)   22%, var(--aether-panel));
    --aether-nav-hover:      color-mix(in srgb, var(--aether-text)    8%, var(--aether-panel));
    --aether-nav-active-bg:  color-mix(in srgb, var(--aether-accent) 14%, var(--aether-panel));
    --aether-nav-rail:       color-mix(in srgb, var(--aether-accent) 75%, var(--aether-text));
    --aether-nav-focus:      color-mix(in srgb, var(--aether-focus)  75%, var(--aether-text));
    --aether-nav-quiet:      color-mix(in srgb, var(--aether-muted)  70%, var(--aether-text));
}

html, body {
    background: var(--aether-bg) !important;
    color: var(--aether-text) !important;
}

.bg-surface { background-color: var(--aether-bg) !important; }
.bg-panel, .card { background-color: var(--aether-panel) !important; }
.bg-accent { background-color: var(--aether-accent) !important; }
.hover\:bg-purple-600:hover,
.hover\:bg-purple-700:hover { background-color: var(--aether-accent-hover) !important; }
.text-accent { color: var(--aether-accent) !important; }
.text-gray-100, .text-gray-200, .text-gray-300 { color: var(--aether-text) !important; }
.text-gray-400, .text-gray-500 { color: var(--aether-muted) !important; }
.border-gray-600, .border-gray-700, .border-gray-800 { border-color: var(--aether-border) !important; }
.bg-black\/60 { background-color: var(--aether-overlay) !important; }
.text-red-400, .text-red-300 { color: var(--aether-danger) !important; }
.text-green-400, .text-green-300 { color: var(--aether-success) !important; }
.text-yellow-400, .text-yellow-300 { color: var(--aether-warning) !important; }
.text-blue-400, .text-blue-300 { color: var(--aether-info) !important; }

/* Bridge raw Tailwind palette utilities so every page picks up the active
   theme rather than the hardcoded dark palette. */
.bg-gray-700 { background-color: var(--aether-panel-strong) !important; }
.bg-gray-800 { background-color: var(--aether-panel) !important; }
.bg-gray-900 { background-color: var(--aether-bg) !important; }
.bg-gray-900\/40,
.bg-gray-800\/50 { background-color: color-mix(in srgb, var(--aether-panel) 70%, transparent) !important; }
.bg-gray-700\/50 { background-color: color-mix(in srgb, var(--aether-panel-strong) 70%, transparent) !important; }

.bg-blue-500,
.bg-blue-600 { background-color: var(--aether-info) !important; }
.hover\:bg-blue-400:hover,
.hover\:bg-blue-500:hover,
.hover\:bg-blue-600:hover { background-color: var(--aether-info-strong) !important; }
.bg-blue-900\/20,
.bg-blue-900\/40,
.bg-blue-900\/50,
.bg-blue-900\/60 { background-color: color-mix(in srgb, var(--aether-info) 25%, transparent) !important; }
.border-blue-700,
.border-blue-800,
.border-blue-800\/40 { border-color: var(--aether-info) !important; }

.bg-red-500,
.bg-red-600,
.bg-red-700,
.bg-red-800 { background-color: var(--aether-danger) !important; color: var(--aether-control-text) !important; }
.hover\:bg-red-700:hover,
.hover\:bg-red-800:hover { background-color: var(--aether-danger-strong) !important; }
.bg-red-900\/30,
.bg-red-900\/40,
.bg-red-900\/50,
.bg-red-900\/60,
.bg-red-900\/70 { background-color: color-mix(in srgb, var(--aether-danger) 25%, transparent) !important; }
.border-red-700,
.border-red-800 { border-color: var(--aether-danger) !important; }

.bg-green-500,
.bg-green-600 { background-color: var(--aether-success) !important; }
.bg-green-900\/50,
.bg-green-900\/60 { background-color: color-mix(in srgb, var(--aether-success) 25%, transparent) !important; }
.border-green-700,
.border-green-800 { border-color: var(--aether-success) !important; }
.bg-emerald-900\/50 { background-color: color-mix(in srgb, var(--aether-success) 25%, transparent) !important; }
.text-emerald-300,
.text-emerald-400 { color: var(--aether-success) !important; }

.bg-yellow-500,
.bg-yellow-600 { background-color: var(--aether-warning) !important; }
.bg-yellow-900\/30,
.bg-yellow-900\/50,
.bg-yellow-900\/60 { background-color: color-mix(in srgb, var(--aether-warning) 25%, transparent) !important; }
.border-yellow-700 { border-color: var(--aether-warning) !important; }

/* Chat bubbles (user_chat.html). The base template referenced these classes
   with hardcoded hex backgrounds; centralise them here so they follow the
   active theme. */
.chat-bubble-user {
    background-color: var(--aether-bubble-user-bg) !important;
    color: var(--aether-bubble-user-text) !important;
    border-radius: 1rem 1rem 0.25rem 1rem !important;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
}

.chat-bubble-assistant {
    background-color: var(--aether-bubble-assistant-bg) !important;
    color: var(--aether-bubble-assistant-text) !important;
    border-radius: 1rem 1rem 1rem 0.25rem !important;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
}

/* Card surface used across portal pages. */
.card {
    background-color: var(--aether-panel) !important;
    color: var(--aether-text) !important;
    border-radius: 0.75rem;
}

/* §207.20.1  The side panel's brand row: wordmark left, platform version right.

   One flex row rather than a float, so the version keeps to the header's inner
   right edge while the wordmark holds its natural width. `nowrap` on both spans
   is what puts them on one line wherever the row has the width for both, which
   on the 13rem drawer is every viewport down to 320px: 176px of content width
   against the wordmark's ~85px.

   `flex-wrap: wrap` is §209.2. Below 768px §209.1 caps the drawer at the visual
   viewport less a peek, so the row can be as narrow as 103px of content at a
   160 CSS px viewport — which the wordmark and the version cannot share. The
   version then takes a line of its own, still on the content right edge through
   `margin-left: auto`, instead of overhanging the panel: the clipping §209
   removes must not merely move into this row. The wordmark keeps its own
   `nowrap` and is never broken.

   The version takes `--aether-nav-quiet` — the token the identity footer already
   uses — because at 11px it needs 4.5:1 (WCAG 1.4.3) and plain `--aether-muted`
   on the panel measures 4.05:1 in `nord`. No font-family of its own: it inherits
   the row's, and only its size, weight and colour step down from the wordmark. */
.sidebar-brand {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 8px;
}
.sidebar-brand-wordmark {
    white-space: nowrap;
}
.sidebar-brand-version {
    margin-left: auto;
    font-size: 11px;
    font-weight: 500;
    line-height: 1;
    white-space: nowrap;
    color: var(--aether-nav-quiet);
}

/* Section heading inside the sidebar nav (§161.8, supersedes §161.1).

   The heading sits inside a shelf rather than on its lip: the fill starts 10px
   before the first glyph (operator decision D2) and still runs to the sidebar's
   content edge. The bottom rule is gone — a fill the eye cannot see plus a hard
   hairline is what made the panel read as a legacy admin table. Type scale:
   section 15px > sub-heading 14px > item row 13px, so a heading is still
   strictly larger than what it heads. */
.nav-section {
    display: block;
    margin: 16px 0 4px 6px;
    padding: 6px 12px 6px 10px;
    font-size: 15px;
    font-weight: 600;
    line-height: 20px;
    letter-spacing: 0;
    color: var(--aether-text) !important;
    background-color: var(--aether-nav-band);
    border-radius: 4px 0 0 4px;
}
/* A tier caption already separates; the heading under it does not need the
   full 16px of air as well. */
.nav-tier + .nav-section {
    margin-top: 8px;
}

/* Tier caption inside the sidebar nav (§161.8 D4, supersedes §161.1's "the
   tier banner is deliberately untouched").

   A tier splits the panel into ADMIN and USER areas; a section labels one
   group inside it. It used to do that as a third filled full-bleed bar, so
   tier, heading and active row stacked at near-identical mass (33.8 / 33.78 /
   36px) with nothing to tell container from selection. Demoted to a caption
   over a rule, it separates without competing. Uppercase is right here and
   only here: one short word at 11px. */
.nav-tier {
    display: block;
    padding: 20px 16px 4px;
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: var(--aether-nav-quiet);
    background: none;
    border-top: 1px solid var(--aether-border);
}
.nav-tier:first-child {
    border-top: none;
    padding-top: 12px;
}

/* Tab bar (tab-btn / tab-btn.active). */
.tab-btn { color: var(--aether-muted) !important; }
.tab-btn.active {
    border-bottom-color: var(--aether-accent) !important;
    color: var(--aether-text) !important;
}
.tab-btn:hover:not(.active) { color: var(--aether-text) !important; }

/* §194.4.2 — a themed selected-tab underline for the tab bars that do **not** use
   `tab-btn`. Tailwind's `border-accent` has no rule in this file and resolves to the
   accent hardcoded in the Tailwind config, so the underline stays that one colour while
   the theme's accent changes beneath it. Those tab bars keep their own state classes and
   their own label colours: unlike `user_settings`, their selected labels already render
   distinctly, so making them share `tab-btn` would have been the wrong fix (§194.4.1). */
.tab-underline-active { border-bottom-color: var(--aether-accent) !important; }

/* Status badges.

   §215.1.1 (ticket 01M583MD7K111D7TKK1WJMS1KZ) — the four hue classes printed a hue on a
   30 % tint of itself, failing SC 1.4.3 in 33 of their 36 theme-and-class combinations.
   Two properties of the replacement are load-bearing:

   * The fill mixes into `--aether-panel`, not `transparent`. The old mix was alpha-0.3,
     so the rendered colour depended on whichever ancestor painted behind the badge:
     `badge-red` in `nord` measured 1.91:1 over `--aether-panel` and 1.73:1 over
     `--aether-panel-strong`. Opaque is what makes the family testable at all.
   * The foreground mixes toward the theme's own `--aether-text`, which self-corrects
     across theme polarity, so one declaration serves all nine themes — the §161.8
     derived-token pattern. A hand-picked per-class foreground reproduces the original
     defect the next time a theme is added.

   The tint came down from 30 % to 20 % because at 30 % **no** foreground clears 4.5:1 in
   all nine themes, not even pure `--aether-text`, which still reads 4.34:1 for
   `badge-amber` in `nord`. At 20 % / 35 % the worst of the 36 is 4.75:1 (`badge-red` in
   `nord`). `.badge-gray` is not part of the defect and is unchanged: it pairs
   `--aether-text` with `--aether-panel-strong` and measures 7.09:1 to 16.86:1. */
.badge-green {
    background-color: color-mix(in srgb, var(--aether-success) 20%, var(--aether-panel)) !important;
    color: color-mix(in srgb, var(--aether-success) 35%, var(--aether-text)) !important;
}
.badge-red {
    background-color: color-mix(in srgb, var(--aether-danger) 20%, var(--aether-panel)) !important;
    color: color-mix(in srgb, var(--aether-danger) 35%, var(--aether-text)) !important;
}
.badge-gray {
    background-color: var(--aether-panel-strong) !important;
    color: var(--aether-text) !important;
}
.badge-purple {
    background-color: color-mix(in srgb, var(--aether-accent) 20%, var(--aether-panel)) !important;
    color: color-mix(in srgb, var(--aether-accent) 35%, var(--aether-text)) !important;
}
.badge-amber {
    background-color: color-mix(in srgb, var(--aether-warning) 20%, var(--aether-panel)) !important;
    color: color-mix(in srgb, var(--aether-warning) 35%, var(--aether-text)) !important;
}

.ctx-bar {
    width: 4.5rem;
    height: 0.55rem;
    background: var(--aether-code-bg);
    border: 1px solid var(--aether-border);
    border-radius: 0.2rem;
    overflow: hidden;
}

.ctx-bar-fill {
    height: 100%;
    width: 0%;
    background: var(--aether-accent);
    transition: width 0.15s ease;
}

.ctx-bar-fill.warn {
    background: var(--aether-danger);
}

.activity-bubble summary {
    cursor: pointer;
    list-style: none;
}

.activity-bubble summary::-webkit-details-marker {
    display: none;
}

.activity-line {
    border-left: 2px solid var(--aether-border);
    padding-left: 0.75rem;
}

.activity-line.error {
    color: var(--aether-danger);
    border-left-color: var(--aether-danger);
}

/* A turn that ended in error. `user_chat.html` toggles `.error` on the activity
   <summary>, but nothing styled it, so a failed turn was byte-identical to a
   successful one in colour, size and weight — only the leading glyph differed
   (BugToDo Issue 17). `!important` is required: the element carries the Tailwind
   utility `text-gray-300`, which this file already remaps with `!important` above. */
.activity-bubble summary.error,
summary.error {
    color: var(--aether-danger) !important;
    font-weight: 600;
}

.activity-line.reasoning {
    color: var(--aether-muted);
    font-style: italic;
}

.activity-line.activity-notice {
    color: var(--aether-warning);
    border-left-color: var(--aether-warning);
    font-weight: 600;
}

.agent-proposal-layer {
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: 20;
}

.agent-proposal-review {
    position: absolute;
    left: 0.75rem;
    right: 0.75rem;
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 0.75rem;
    align-items: start;
    pointer-events: auto;
}

.agent-proposal-diff {
    max-height: 22rem;
    overflow: auto;
    border: 1px solid var(--aether-border);
    border-radius: 0.5rem;
    background-color: color-mix(in srgb, var(--aether-control) 92%, transparent);
    box-shadow: 0 12px 24px rgba(0, 0, 0, 0.28);
}

.agent-proposal-context,
.agent-proposal-old,
.agent-proposal-new {
    margin: 0;
    padding: 0.35rem 0.5rem;
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
    font-size: 0.8125rem;
    line-height: 1.35rem;
    white-space: pre-wrap;
}

.agent-proposal-context {
    color: var(--aether-muted);
}

.agent-proposal-old {
    background-color: color-mix(in srgb, var(--aether-danger) 24%, transparent);
    color: var(--aether-text);
}

.agent-proposal-new {
    background-color: color-mix(in srgb, var(--aether-success) 24%, transparent);
    color: var(--aether-text);
}

.agent-proposal-actions {
    min-width: 7rem;
    border: 1px solid var(--aether-border);
    border-radius: 0.5rem;
    background-color: var(--aether-panel);
    padding: 0.5rem;
    box-shadow: 0 12px 24px rgba(0, 0, 0, 0.32);
}

.agent-proposal-actions button {
    width: 100%;
    border-radius: 0.375rem;
    padding: 0.35rem 0.5rem;
    font-size: 0.75rem;
    font-weight: 600;
}

.agent-proposal-take {
    background-color: color-mix(in srgb, var(--aether-success) 36%, transparent);
    color: var(--aether-success);
}

.agent-proposal-ignore {
    margin-top: 0.35rem;
    background-color: color-mix(in srgb, var(--aether-danger) 30%, transparent);
    color: var(--aether-danger);
}

.chat-bubble-user code,
.chat-bubble-user pre,
.chat-bubble-assistant code,
.chat-bubble-assistant pre {
    background-color: var(--aether-code-bg) !important;
    color: var(--aether-text) !important;
}

.nav-link {
    display: block;
    width: 100%;
    padding: 6px 12px 6px 16px;
    font-size: 13px;
    line-height: 20px;
    color: var(--aether-text) !important;
}

.nav-link:hover {
    background-color: var(--aether-nav-hover) !important;
    color: var(--aether-text) !important;
}

/* The selected row is a tint plus an accent rail, never a full accent fill
   (§161.8 D1). The fill failed WCAG 1.4.3 in eight of the nine themes — 3.53:1
   in the default theme, 1.43:1 in high-contrast — so the heaviest element in
   the panel was also its least readable one. A tint keeps the selection's
   perceived weight independent of the user's drag: the old treatment painted a
   36x447px saturated slab at the 448px clamp, 3.5x the ink of the same
   indicator at 176px. The contrast is NOT repaired by changing
   `--aether-control-text` — that token is the on-accent foreground for buttons
   and other controls product-wide. */
.nav-link.active {
    background-color: var(--aether-nav-active-bg) !important;
    color: var(--aether-text) !important;
    font-weight: 600;
    box-shadow: inset 3px 0 0 0 var(--aether-nav-rail);
}

/* One focus ring for every control in the nav (§161.8, closes USA-20260916-004:
   the rows carried Chromium's default ring and `--aether-focus` was unused
   here). The offset must be negative — rows are full-bleed inside the sidebar's
   own scroll container, so an outset ring is clipped at the left edge. */
.nav-link:focus-visible,
.nav-accordion-header:focus-visible,
.nav-sub-accordion-header:focus-visible,
.nav-sub-toggle:focus-visible,
.nav-item-link:focus-visible,
.nav-item-twisty:focus-visible,
.nav-item-menu-btn:focus-visible {
    outline: 2px solid var(--aether-nav-focus);
    outline-offset: -2px;
}

/* Sub-heading inside the sidebar nav (§161.8, supersedes §161.1).

   The same shelf as .nav-section, subordinated by geometry rather than by a
   second, weaker fill: glyph at x=24 against x=16, fill from x=14 against x=6.
   A lighter fill was what made the sub-heading band separate from the panel by
   1.07:1 — invisible. */
.nav-subsection {
    display: block;
    margin: 12px 0 2px 14px;
    padding: 4px 12px 4px 10px;
    font-size: 14px;
    font-weight: 600;
    line-height: 18px;
    letter-spacing: 0;
    color: var(--aether-text);
    background-color: var(--aether-nav-band);
    border-radius: 4px 0 0 4px;
}

label,
summary,
th {
    color: var(--aether-muted);
}

/* Single-expand accordion shell for the normal-user sidebar nav. */
.nav-accordion {
    border-bottom: 1px solid var(--aether-border);
}
.nav-accordion-header {
    display: flex;
    align-items: center;
    gap: 0;
    width: 100%;
    /* No right padding and no vertical padding: the heading band inside is what
       reaches the sidebar's content edge and what carries the row height. The
       left padding plus the 16px chevron box is the whole inset before the
       band; the chevron stays outside the band (§161.8). */
    padding: 0 0 0 6px;
    background: transparent;
    border: none;
    cursor: pointer;
    text-align: left;
}
/* The hover highlight paints the band rather than the full button width, so the
   highlight and the band share one edge. */
.nav-accordion-header:hover .nav-section {
    background-color: var(--aether-nav-band-hover);
}
.nav-accordion-header .nav-section {
    flex: 1;
    margin: 0;
    padding: 6px 12px 6px 10px;
}

/* One caret for every expandable control in the nav (§161.8): a 12x12 stroked
   SVG in a 16x16 box, rotated by the owning control's own `aria-expanded`. The
   glyph is not swapped in script — the ARIA state was already the single source
   of truth and a second one would be a second thing to keep in sync. */
.nav-chevron {
    flex: 0 0 16px;
    width: 16px;
    height: 16px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--aether-nav-quiet);
}
.nav-chevron svg {
    width: 12px;
    height: 12px;
    transition: transform 150ms ease;
}
:where(.nav-accordion-header, .nav-sub-accordion-header, .nav-sub-toggle,
       .nav-item-twisty, .nav-item-group-toggle)[aria-expanded="true"] .nav-chevron svg {
    transform: rotate(90deg);
}
@media (prefers-reduced-motion: reduce) {
    .nav-chevron svg { transition: none; }
}
.nav-accordion-body {
    display: none;
    padding-bottom: 0.25rem;
}
.nav-accordion-header[aria-expanded="true"] + .nav-accordion-body {
    display: block;
}

/* Sub-level nav-links inside any accordion body sit deeper than the section
   headers (General / Activities / Configuration) for a visible hierarchy. The
   inset is 24px rather than 28px: at the 176px clamp every pixel of indent came
   out of the label, which had room for about ten characters (§161.8.4). */
.nav-accordion-body .nav-link {
    padding-left: 24px;
}

/* Nested sub-accordion (Activities → Chat, Endpoints → LLM/Image). */
.nav-sub-accordion {
    display: block;
    position: relative;
}

/* A sub-section heading that is itself the control (§168.3).

   The Activities sub-accordions head a link, so their caret is a separate 16px
   button pinned in the row's padding. An Endpoint sub-section heads no link, so
   the whole heading is the button — the same construction as
   `.nav-accordion-header` one level up, and the same geometry rule: the caret
   box stays outside the band, so the button carries the inset `.nav-subsection`
   would otherwise take as its own margin (14px) and the band starts after the
   caret at x=30 with its first glyph at x=40. */
.nav-sub-accordion-header {
    display: flex;
    align-items: center;
    gap: 0;
    width: 100%;
    padding: 0 0 0 14px;
    margin: 12px 0 2px;
    background: transparent;
    border: none;
    cursor: pointer;
    text-align: left;
}
.nav-sub-accordion-header:hover .nav-subsection {
    background-color: var(--aether-nav-band-hover);
}
.nav-sub-accordion-header .nav-subsection {
    flex: 1;
    margin: 0;
}
.nav-sub-row {
    position: relative;
    display: flex;
    align-items: center;
}
.nav-sub-row .nav-sub-link {
    flex: 1;
    /* Inherits .nav-link padding-left from the body rule above so Chat text
       aligns with Image text. The chevron is absolutely positioned in the
       padding area and does not shift this text. */
}
/* When the link itself hosts the + control on the right, lay out its
   children as a flex row so the text sits on the left and the + sits on
   the right edge inside the same hover-highlighted area. */
.nav-sub-link-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}
.nav-sub-link-row .nav-sub-link-text {
    flex: 1;
    min-width: 0;
}
.nav-sub-toggle {
    position: absolute;
    left: 4px;
    top: 50%;
    transform: translateY(-50%);
    width: 16px;
    height: 16px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: none;
    cursor: pointer;
    color: var(--aether-muted);
    padding: 0;
    z-index: 1;
}
/* Ticket 01M51NYGW7N9D4DPRWTFPXXEC6: the same 24x24 hit area as
   `.nav-item-twisty::before`, for the same reason and with the caret's own 16px
   box untouched. Its row is 32px, so the enlarged area stays inside it; the
   sibling link's text starts at x=24 and the area spans x in [0, 24], so it
   covers padding only, and the existing `z-index: 1` already settles stacking. */
.nav-sub-toggle::before {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 24px;
    height: 24px;
}
.nav-sub-toggle:hover {
    color: var(--aether-text);
}
.nav-sub-newchat {
    width: 1.25rem;
    height: 1.25rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background-color: var(--aether-accent);
    color: var(--aether-control-text);
    border: none;
    border-radius: 0.25rem;
    font-size: 0.9rem;
    line-height: 1;
    cursor: pointer;
    padding: 0;
    flex: 0 0 auto;
    user-select: none;
}
.nav-sub-newchat:hover {
    background-color: var(--aether-accent-hover);
}
/* The 20px left padding is the tree's gutter: it holds the twisty of a
   depth-0 row, which is positioned out of flow so that the 16px it used to
   occupy in the row goes to the label instead (§161.8.4). */
.nav-sub-body {
    display: none;
    padding: 2px 4px 4px 20px;
}
#sessions-sub-body {
    padding-left: 32px;
}
#projects-sub-body {
    padding-left: 32px;
}
.nav-sub-accordion.is-open .nav-sub-body {
    display: block;
}
/* Where the heading is the control it is also the body's previous sibling, so
   the open state comes straight off its own `aria-expanded` and needs no second
   class to keep in sync (§161.8, §168.3). */
.nav-sub-accordion-header[aria-expanded="true"] + .nav-sub-body {
    display: block;
}

/* Pinned utility rows (Search, Ticket) sit below the nav, separated by the one
   hairline the sidebar needs. They are ordinary item rows, at the item rows'
   own size and ink: position plus the rule carries the separation, and dimming
   an interactive target does not (§161.8.3). */
.sidebar-pinned {
    border-top: 1px solid var(--aether-border);
    padding: 8px 0 4px;
}
.nav-link-compact {
    padding: 6px 12px 6px 16px;
    font-size: 13px;
    line-height: 20px;
}

/* The identity footer: one 44px row, the panel's quietest. */
.sidebar-footer {
    display: flex;
    align-items: center;
    gap: 8px;
    height: 44px;
    box-sizing: border-box;
    padding: 10px 12px;
    border-top: 1px solid var(--aether-border);
}
.sidebar-footer .sidebar-avatar {
    width: 24px;
    height: 24px;
    flex: 0 0 24px;
    border-radius: 50%;
    object-fit: cover;
    background-color: var(--aether-nav-band);
}
.sidebar-footer .sidebar-username {
    flex: 1;
    min-width: 0;
    font-size: 13px;
    font-weight: 500;
    color: var(--aether-text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* §207.22.11  The three controls as one flex item, so the row can wrap whole.
   `flex: 0 0 auto` keeps the group at the 88px its three 24px boxes and two 8px gaps
   need; those gaps are the same 8px the row itself uses, so a row that still fits is
   laid out to the pixel as it was before the group existed. */
.sidebar-footer .sidebar-footer-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 0 0 auto;
}
.sidebar-footer .sidebar-footer-icon {
    flex: 0 0 24px;
    width: 24px;
    height: 24px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 4px;
    color: var(--aether-nav-quiet);
}
.sidebar-footer .sidebar-footer-icon svg {
    width: 16px;
    height: 16px;
}
.sidebar-footer .sidebar-footer-icon:focus-visible {
    outline: 2px solid var(--aether-nav-focus);
    outline-offset: -2px;
}
.sidebar-footer .sidebar-footer-icon:hover {
    color: var(--aether-text);
}

/* §207.10.2  The cart's badge, painted outside the hit box it belongs to.

   `.sidebar-footer-icon` is 24x24, which is exactly the WCAG 2.2 SC 2.5.8 floor —
   there is no slack to spend. So the badge is not a child of the link: the wrapper
   is a zero-size positioning context beside it and the badge hangs off that, with
   `pointer-events: none` so the part that overlaps the link still activates the
   link. A badge painted inside would have taken a corner out of a target that is
   already at the minimum, which is the one thing the footer row cannot afford.

   Ink: `--aether-text` on `--aether-nav-band-hover`, the pair the hovered section
   heading already uses. Measured across all nine themes it ranges 4.68:1 (nord) to
   11.06:1 (daylight), so it clears 1.4.3 everywhere. Deliberately *not* text on
   `--aether-accent`: that pairing measures 1.43:1 in `high-contrast` and fails in
   eight of the nine, which is the §206.0 finding and the reason §206.3 adds an
   on-accent token this file does not have yet.

   The badge is `aria-hidden` in the markup and its count is folded into the link's
   accessible name, so nothing here carries meaning by colour: it is a second
   rendering of a number a screen reader already heard. Nothing animates, which is
   how `prefers-reduced-motion` is honoured — by having no motion to reduce. */
.sidebar-footer .sidebar-footer-cart {
    position: relative;
    flex: 0 0 24px;
    width: 24px;
    height: 24px;
    display: inline-flex;
}
.sidebar-footer .sidebar-footer-badge {
    position: absolute;
    top: -5px;
    right: -6px;
    min-width: 14px;
    height: 14px;
    box-sizing: border-box;
    padding: 0 3px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 10px;
    font-weight: 700;
    line-height: 1;
    border-radius: 7px;
    color: var(--aether-text);
    background-color: var(--aether-nav-band-hover);
    border: 1px solid var(--aether-border);
    pointer-events: none;
}

/* The active-session row's unselect control. A box rather than a bare glyph: at
   the inherited font size the `x` is a 13.4px target, below the 24px SC 2.5.8
   floor (ticket 01M51NYGW7N9D4DPRWTFPXXEC6). Deliberately no `display`
   declaration — the button is shown and hidden through Tailwind's `.hidden`,
   which this file would outrank on source order, so a `display` here would be a
   second source of truth for its visibility. As a flex item it is blockified
   anyway, so `min-width`/`min-height` apply without one. */
.sidebar-session-clear {
    min-width: 24px;
    min-height: 24px;
    line-height: 24px;
    padding: 0;
    text-align: center;
    flex: 0 0 auto;
}

/* One item row inside a Chat/Sessions/Projects sub-body. Positioned because
   the twisty hangs out of flow to its left (§161.8.4). */
.nav-item-row {
    position: relative;
    display: flex;
    align-items: center;
    border-radius: 4px;
    /* Declared, not emergent: this min-height is what keeps the twisty's
       enlarged 24px hit area inside its own row (ticket
       01M51NYGW7N9D4DPRWTFPXXEC6). */
    min-height: 24px;
}

/* The expand control of a tree row, hung out of flow in the gutter its parent
   list reserves for it. In flow it cost every row 16px of label at every depth;
   outside the row's box it costs nothing and cannot overlap the label or the
   row's hit area (§161.8.4). A leaf renders nothing at all — the labels line up
   because the gutter is the container's, not the row's. */
.nav-item-twisty {
    position: absolute;
    left: -16px;
    top: 50%;
    transform: translateY(-50%);
    width: 16px;
    height: 16px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    background: none;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    color: var(--aether-nav-quiet);
}
/* Ticket 01M51NYGW7N9D4DPRWTFPXXEC6: 16x16 is under the SC 2.5.8 floor, so the
   hit area is grown to 24x24 by a pseudo-element and the caret's own box — its
   visual size and its focus ring — is left alone. Centred in a row whose
   `min-height` holds it, so an enlarged area cannot reach into the row above or
   below and toggle the wrong branch. Sideways it spans x in [-20, +4], which is
   the gutter plus the link's own left padding, never its text. */
.nav-item-twisty::before {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 24px;
    height: 24px;
}
.nav-item-twisty.is-leaf {
    display: none;
}
.nav-item-row:hover .nav-item-twisty {
    color: var(--aether-text);
}
/* Children are indented by the row's own padding, so this container adds no
   inset of its own and a deep branch stays readable in a 14rem sidebar. */
.nav-item-children {
    display: block;
}
.nav-item-row:hover {
    background-color: var(--aether-nav-hover);
}
/* A move is not instantaneous: the row reports that it is working rather than
   letting the user wonder whether the click registered. */
.nav-item-row.is-busy {
    opacity: 0.55;
    pointer-events: none;
}
.nav-item-row.is-busy .nav-item-link::after {
    content: " — moving…";
    font-style: italic;
    color: var(--aether-muted);
}
/* The same tint and rail as .nav-link.active, for the same reason: a full
   accent fill under control-text failed 1.4.3 in eight of nine themes, and the
   two selected-row treatments must not drift apart (§161.8.3). */
.nav-item-row.is-active {
    background-color: var(--aether-nav-active-bg);
    box-shadow: inset 3px 0 0 0 var(--aether-nav-rail);
}
.nav-item-row.is-active .nav-item-link {
    color: var(--aether-text);
    font-weight: 600;
}

/* §169 — direct manipulation of the tree.

   The row being dragged stays in place and says so; the tree is never rewritten
   before the server has confirmed the move, so the source row is the only thing
   that changes while a drag is under way. */
.nav-item-row.is-dragging {
    opacity: 0.5;
}
/* The drag image is the item's name text, not a ghost of the whole row (the
   ticket's own wording). It is attached off-screen for one frame because
   `setDragImage` needs a rendered node, then removed. */
.nav-drag-label {
    position: fixed;
    top: -1000px;
    left: -1000px;
    z-index: 90;
    padding: 2px 6px;
    border-radius: 4px;
    background-color: var(--aether-panel-strong);
    color: var(--aether-text);
    font-size: 13px;
    line-height: 18px;
    white-space: nowrap;
    pointer-events: none;
    opacity: 0.6;
}
/* The insertion line. The ticket asks for a black line; `--aether-text` is
   black or near-black in every light theme and stays legible on the dark
   panels, where a literal #000 line on #1e1e2e is invisible — an indicator that
   cannot be seen fails the behaviour it was asked for (§161.8 measured the same
   trap for the active row). It marks the drop target only: there is no
   persisted order for it to promise (operator decision, §169.2). */
.nav-drop-line {
    position: absolute;
    left: 0;
    right: 0;
    height: 2px;
    background-color: var(--aether-text);
    pointer-events: none;
    z-index: 5;
}
/* §184.2 — the "no session or project" destination's own surface.

   Absent at rest: the element does not exist until a drag that destination
   would accept, so it costs nothing for the users who never need it. `sticky`
   is for its primary host, `#chat-list`, which is the real scroller — the
   banner then stays at the top of the visible port however far the 8060px of
   rows are scrolled. In the fallback host, the chat sub-accordion's own row,
   there is nothing to stick to and the declaration is inert.

   Above `.nav-drop-line`'s z-index, because the line is a sibling cue and must
   not cover the target it is about. */
.nav-drop-banner {
    position: sticky;
    top: 0;
    z-index: 6;
    margin: 0 0 4px;
    padding: 6px 8px;
    border: 2px dashed var(--aether-nav-rail);
    border-radius: 4px;
    background-color: var(--aether-panel-strong);
    color: var(--aether-text);
    font-size: 12px;
    line-height: 16px;
    text-align: center;
    user-select: none;
}
/* The hover state this destination never had: it is the resolved target now. */
.nav-drop-banner--active {
    border-style: solid;
    border-color: var(--aether-nav-focus);
    background-color: var(--aether-panel);
}
/* The container a valid drop would land in, so the line is never the only cue. */
.nav-drop-into {
    outline: 2px dashed var(--aether-nav-rail);
    outline-offset: -2px;
    border-radius: 4px;
}
/* The drop zone right of the panel opens or activates instead of moving, so it
   is marked differently from a re-parent target (§169.4). */
.nav-drop-open {
    outline: 2px dashed var(--aether-nav-focus);
    outline-offset: -4px;
}
@media (prefers-reduced-motion: reduce) {
    /* The line and the label stay; only the movement goes (§169.6). */
    .nav-item-row.is-dragging { transition: none; }
    .nav-drag-label { transition: none; }
}
/* The keyboard drag's running commentary. Off screen, never hidden: a
   `display: none` region is not announced. */
.nav-drag-live {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}
/* The move question, anchored at the drop point rather than centred over the
   page: it answers a gesture the user just made there (§169.3). */
.nav-confirm-bubble {
    position: fixed;
    z-index: 85;
    max-width: 18rem;
    padding: 0.6rem 0.75rem;
    border-radius: 0.5rem;
    background-color: var(--aether-panel-strong);
    border: 1px solid var(--aether-border);
    color: var(--aether-text);
    font-size: 0.8rem;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
}
.nav-confirm-bubble .nav-confirm-actions {
    display: flex;
    justify-content: flex-end;
    gap: 0.5rem;
    margin-top: 0.6rem;
}
.nav-confirm-bubble button {
    padding: 0.25rem 0.7rem;
    border-radius: 0.375rem;
    border: 1px solid var(--aether-border);
    background: transparent;
    color: var(--aether-text);
    font-size: 0.75rem;
    cursor: pointer;
}
.nav-confirm-bubble button.nav-confirm-yes {
    background-color: var(--aether-accent);
    border-color: var(--aether-accent);
    color: var(--aether-control-text);
}
.nav-confirm-bubble button:focus-visible {
    outline: 2px solid var(--aether-nav-focus);
    outline-offset: 1px;
}
.nav-item-link {
    flex: 1 1 auto;
    min-width: 0;
    padding: 5px 4px;
    font-size: 13px;
    line-height: 18px;
    text-align: left;
    background: none;
    border: none;
    cursor: pointer;
    color: var(--aether-text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Always visible: a hover-only control is undiscoverable, and users concluded
   the item actions did not exist at all. 24x24 is an ergonomic improvement over
   the previous 30x16, NOT a conformance fix — the old target already passed
   2.5.8 through the spacing exception (§161.8.3). */
.nav-item-menu-btn {
    flex: 0 0 24px;
    margin-left: auto;          /* hold the right edge whatever the label length */
    margin-right: 0.5rem;       /* keep the control clear of overlay scrollbars */
    width: 24px;
    height: 24px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    background: none;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    color: var(--aether-nav-quiet);
    font-size: 15px;
    line-height: 1;
}
.nav-item-row:hover .nav-item-menu-btn,
.nav-item-menu-btn:focus,
.nav-item-menu-btn:hover,
.nav-item-row.menu-open .nav-item-menu-btn {
    color: var(--aether-text);
}

/* The per-item menu is positioned next to the row that opened it. */
.nav-item-menu {
    position: fixed;
    z-index: 60;
    min-width: 11rem;
    padding: 0.25rem;
    border-radius: 0.5rem;
    background-color: var(--aether-panel-strong);
    border: 1px solid var(--aether-border, #3a3a4e);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
}
.nav-item-menu button {
    display: block;
    width: 100%;
    padding: 0.35rem 0.6rem;
    font-size: 0.8rem;
    text-align: left;
    background: none;
    border: none;
    border-radius: 0.375rem;
    cursor: pointer;
    color: var(--aether-text);
}
.nav-item-menu button:hover {
    background-color: var(--aether-nav-active-bg);
    color: var(--aether-text);
}
.nav-item-menu button.is-danger {
    color: #f87171;
}
.nav-item-menu button.is-danger:hover {
    background-color: rgba(153, 27, 27, 0.35);
    color: #fca5a5;
}
.nav-item-empty {
    padding: 0.25rem 0.5rem;
    font-size: 0.75rem;
    font-style: italic;
    color: var(--aether-muted);
}
.nav-item-group {
    padding: 0.4rem 0.5rem 0.15rem;
    font-size: 0.6rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--aether-muted);
}

/* "CLI Sessions": the band that separates the sessions a command-line client
   bound to a working folder from the ones a person created in the portal.

   A rule above the caption rather than a plain heading, because the two kinds
   of session are otherwise indistinguishable in a flat list -- and the band is
   closed by default: these are made by a machine, one per folder, and a person
   looking for their own work should not have to scroll past them to find it. */
.nav-item-group-toggle {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    width: 100%;
    margin-top: 0.4rem;
    padding: 0.45rem 0.5rem 0.2rem;
    background: transparent;
    border: none;
    border-top: 1px solid var(--aether-border);
    font-size: 0.6rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--aether-muted);
    cursor: pointer;
    text-align: left;
    /* The SC 2.5.8 floor (ticket 01M51NYGW7N9D4DPRWTFPXXEC6). The height was
       otherwise set by the 12px `.nav-item-group-chevron` this flex row centres,
       not by the 0.6rem text line box: 7.2 + 12 + 3.2 + 1 is about 23.4px. */
    min-height: 24px;
}
.nav-item-group-toggle:hover {
    color: var(--aether-text);
}
/* The same caret as the rest of the nav, sized down to the caption it opens. */
.nav-item-group-chevron {
    flex: 0 0 12px;
    width: 12px;
    height: 12px;
    letter-spacing: normal;
}
.nav-item-group-chevron svg {
    width: 10px;
    height: 10px;
}
.nav-item-group-title {
    flex: 1;
    min-width: 0;
}
.nav-item-group-count {
    letter-spacing: normal;
    font-variant-numeric: tabular-nums;
}
.nav-item-group-body[hidden] {
    display: none;
}

th.font-medium {
    cursor: pointer;
    user-select: none;
}

th.font-medium:hover {
    color: var(--aether-text);
}

input,
textarea,
select {
    background-color: var(--aether-control) !important;
    color: var(--aether-control-text) !important;
    border: 1px solid var(--aether-border) !important;
    caret-color: var(--aether-control-text);
}

select option,
select optgroup {
    background-color: var(--aether-control) !important;
    color: var(--aether-control-text) !important;
}

input::placeholder,
textarea::placeholder {
    color: var(--aether-muted) !important;
    opacity: 1;
}

input:focus,
textarea:focus,
select:focus {
    border-color: var(--aether-focus) !important;
    outline: 2px solid transparent !important;
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--aether-focus) 55%, transparent) !important;
}

input[type="file"]::file-selector-button {
    background: var(--aether-panel-strong);
    color: var(--aether-text);
    border: 1px solid var(--aether-border);
    border-radius: 0.375rem;
    padding: 0.25rem 0.5rem;
}

button,
a {
    transition-property: background-color, color, border-color;
    transition-duration: 0.15s;
}

/* ============================================================
   Live-Turn Sub-Bubble (WebInterface.md §7.9 — threshold rule)
   ============================================================
   Used by the activity bubble renderer to wrap content blocks
   (tool arguments, tool result payloads, tool errors, reasoning)
   that exceed the inline-render threshold (>6 lines or >280
   chars). Click toggles `.expanded` to remove the height cap.
*/

.aether-subbubble {
    position: relative;
    margin-top: 0.25rem;
    padding: 0.5rem 2rem 0.5rem 0.75rem;
    background-color: var(--aether-code-bg);
    border: 1px solid var(--aether-border);
    border-radius: 0.375rem;
    color: var(--aether-text);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.75rem;
    line-height: 1.25rem;
    max-height: 7.5rem;          /* ~6 lines * 1.25rem */
    overflow: hidden;
    white-space: pre-wrap;
    word-break: break-word;
    cursor: pointer;
    transition: max-height 0.15s ease;
}

.aether-subbubble.expanded {
    max-height: none;
    cursor: zoom-out;
}

.aether-subbubble__fade {
    pointer-events: none;
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 1.5rem;
    background: linear-gradient(to bottom, transparent, var(--aether-code-bg));
}

.aether-subbubble.expanded .aether-subbubble__fade {
    display: none;
}

.aether-subbubble__chevron {
    position: absolute;
    top: 0.25rem;
    right: 0.5rem;
    color: var(--aether-muted);
    font-size: 0.7rem;
    user-select: none;
}

.aether-subbubble__copy {
    position: absolute;
    top: 0.25rem;
    right: 1.6rem;
    color: var(--aether-muted);
    font-size: 0.7rem;
    background: transparent;
    border: none;
    padding: 0 0.25rem;
    opacity: 0;
    transition: opacity 0.1s ease;
    cursor: pointer;
}

.aether-subbubble:hover .aether-subbubble__copy {
    opacity: 1;
}

.aether-subbubble__copy:focus,
.aether-subbubble__copy:hover {
    color: var(--aether-text);
    outline: none;
}

.aether-subbubble--error {
    background-color: var(--aether-panel);
    border-color: var(--aether-danger);
    color: var(--aether-danger);
}

.aether-subbubble--error .aether-subbubble__fade {
    background: linear-gradient(to bottom, transparent, var(--aether-panel));
}

.aether-subbubble--reasoning {
    color: var(--aether-muted);
    font-style: italic;
}

.aether-subbubble-label {
    font-size: 0.7rem;
    color: var(--aether-muted);
    margin-top: 0.25rem;
}

.aether-inline-content {
    margin-top: 0.125rem;
    padding: 0.125rem 0.375rem;
    background-color: var(--aether-code-bg);
    border-radius: 0.25rem;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.75rem;
    color: var(--aether-text);
    white-space: pre-wrap;
    word-break: break-word;
}

.aether-inline-content--error {
    background-color: transparent;
    color: var(--aether-danger);
}

.aether-inline-content--reasoning {
    background-color: transparent;
    color: var(--aether-muted);
    font-style: italic;
}

/* Single-line horizontal legend rendered above each dashboard resource chart. */
.chart-legend {
    display: flex;
    flex-wrap: nowrap;
    justify-content: center;
    gap: 1rem;
    margin-bottom: 0.25rem;
    font-size: 0.7rem;
    color: var(--aether-muted);
}

.chart-legend span {
    display: inline-flex;
    align-items: center;
    white-space: nowrap;
}

.chart-legend i {
    display: inline-block;
    width: 0.6rem;
    height: 0.6rem;
    margin-right: 0.3rem;
    border-radius: 0.15rem;
}

/* Transient confirmation for sidebar re-parenting, which can move an item out
   of every list currently on screen. */
.nav-item-toast {
    position: fixed;
    left: 1rem;
    bottom: 1rem;
    z-index: 70;
    max-width: 22rem;
    padding: 0.6rem 0.9rem;
    border-radius: 0.5rem;
    background-color: var(--aether-panel-strong);
    border: 1px solid var(--aether-accent);
    color: var(--aether-text);
    font-size: 0.8rem;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
}

/* Parent-selection dialog (§111). Replaces a numbered prompt() that could not
   be used once an account had many similarly-named sessions. */
.picker-overlay {
    position: fixed;
    inset: 0;
    z-index: 80;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.6);
}
.picker-dialog {
    width: 100%;
    max-width: 26rem;
    max-height: 70vh;
    display: flex;
    flex-direction: column;
    padding: 1rem;
    border-radius: 0.75rem;
    background-color: var(--aether-panel);
    border: 1px solid var(--aether-border, #3a3a4e);
    box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5);
}
.picker-title {
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--aether-text);
    margin-bottom: 0.75rem;
}
.picker-filter {
    width: 100%;
    padding: 0.45rem 0.7rem;
    margin-bottom: 0.5rem;
    border: none;
    border-radius: 0.5rem;
    background-color: var(--aether-surface, #1e1e2e);
    color: var(--aether-text);
    font-size: 0.85rem;
}
.picker-filter:focus {
    outline: 2px solid var(--aether-accent);
}
.picker-list {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
}
.picker-row {
    display: block;
    width: 100%;
    padding: 0.45rem 0.6rem;
    border: none;
    border-radius: 0.5rem;
    background: none;
    text-align: left;
    cursor: pointer;
    color: var(--aether-text);
}
.picker-row:hover,
.picker-row:focus {
    background-color: var(--aether-nav-active-bg);
    color: var(--aether-text);
}
.picker-row-label {
    display: block;
    font-size: 0.85rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.picker-row-detail {
    display: block;
    font-size: 0.7rem;
    color: var(--aether-muted);
}
.picker-row:hover .picker-row-detail,
.picker-row:focus .picker-row-detail {
    color: var(--aether-text);
}

/* §186.2 — the mirror's nesting. A session inside a project is indented one
   level, exactly as the sidebar indents it, so the picker re-reads a structure
   the user already knows instead of a flat list they must re-derive. */
.picker-row[data-depth="1"] {
    padding-left: 1.35rem;
}

/* §186.4 — a destination that is shown, reachable and refused.

   It keeps its focus ring and stays in the tab order, because the reason is
   only any use to someone who can arrive at the row and hear it. The cue is
   never colour alone: the reason is in the row's own text and in its
   accessible name, and the cursor says the row will not act. */
.picker-row[aria-disabled="true"] {
    cursor: not-allowed;
    opacity: 0.6;
}
.picker-row[aria-disabled="true"]:hover {
    background: none;
    color: var(--aether-text);
}
.picker-row[aria-disabled="true"]:focus {
    outline: 2px solid var(--aether-accent);
    background: none;
}
.picker-row--readonly .picker-row-detail,
.picker-row--current .picker-row-detail {
    font-style: italic;
}
/* The parent the item already has, marked by a rule in the gutter rather than
   by a colour, so it survives a high-contrast theme and colour-vision
   deficiency alike. */
.picker-row--current {
    /* An inset shadow rather than a border: a border would shift the label by
       its own width and break the indent the mirror depends on. */
    box-shadow: inset 2px 0 0 var(--aether-accent);
}
.picker-empty {
    padding: 1rem 0.5rem;
    font-size: 0.8rem;
    font-style: italic;
    color: var(--aether-muted);
}
.picker-actions {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    margin-top: 0.75rem;
}
.picker-detach,
.picker-cancel {
    padding: 0.4rem 0.8rem;
    border: 1px solid var(--aether-border);
    border-radius: 0.5rem;
    background-color: var(--aether-panel-strong);
    color: var(--aether-text);
    font-size: 0.8rem;
    cursor: pointer;
}
.picker-detach:hover,
.picker-cancel:hover {
    background-color: var(--aether-nav-active-bg);
    color: var(--aether-text);
}

/* Text-entry dialog (§111.4), sharing the picker's shell. */
.dialog-label {
    display: block;
    font-size: 0.75rem;
    color: var(--aether-muted);
    margin-bottom: 0.3rem;
}
.dialog-buttons {
    display: flex;
    gap: 0.5rem;
}
.dialog-confirm {
    padding: 0.4rem 0.8rem;
    border: none;
    border-radius: 0.5rem;
    background-color: var(--aether-accent);
    color: var(--aether-control-text);
    font-size: 0.8rem;
    cursor: pointer;
}
.dialog-confirm:disabled {
    opacity: 0.45;
    cursor: not-allowed;
}

/* Item menu: icon, label, optional submenu caret, shortcut key — with the
   destructive action separated at the bottom (operator-supplied reference). */
.nav-item-menu button {
    display: flex;
    align-items: center;
    gap: 0.6rem;
}
.nav-item-menu-icon {
    flex: 0 0 auto;
    width: 1rem;
    height: 1rem;
    opacity: 0.85;
}
.nav-item-menu-text {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.nav-item-menu-more {
    flex: 0 0 auto;
    font-size: 1rem;
    line-height: 1;
    opacity: 0.7;
}
.nav-item-menu-key {
    flex: 0 0 auto;
    min-width: 0.9rem;
    text-align: right;
    font-size: 0.7rem;
    opacity: 0.55;
}
.nav-item-menu-sep {
    height: 1px;
    margin: 0.25rem 0.15rem;
    background-color: var(--aether-border, #3a3a4e);
}

/* Rendered Markdown answers: block elements own the layout, so the pre-wrap
   used for literal user text would double the spacing here. */
.msg-content.is-markdown { white-space: normal; }
.msg-content.is-markdown ul,
.msg-content.is-markdown ol { margin: 0.35rem 0 0.35rem 1.25rem; }
.msg-content.is-markdown ul { list-style: disc; }
.msg-content.is-markdown ol { list-style: decimal; }
.msg-content.is-markdown li { margin: 0.15rem 0; }
.msg-content.is-markdown strong { font-weight: 600; }

/* §191.4 — a table an assistant answer wrote.

   The assistant bubble is capped at `min(80%,46rem)`, so a table wider than that
   scrolls inside its own wrapper instead of widening its ancestor: a table that
   grows the bubble gives the whole transcript a horizontal scrollbar and scrolls
   the page sideways at phone width. Same pattern as `.msg-content pre`.

   Colour comes only from the existing custom properties. This file defines nine
   themes, so a literal colour value would be correct in at most one of them. */
.msg-content.is-markdown .md-table-wrap {
    overflow-x: auto;
    margin: 0.5rem 0;
}
.msg-content.is-markdown table.md-table {
    border-collapse: collapse;
    font-size: 0.85em;
}
.msg-content.is-markdown table.md-table th,
.msg-content.is-markdown table.md-table td {
    border: 1px solid var(--aether-border);
    padding: 0.3rem 0.55rem;
    text-align: left;
    vertical-align: top;
    color: var(--aether-text);
}
/* Weight AND a background, so the header row stays distinguishable in
   `high-contrast` and for a reader who cannot resolve the background. */
.msg-content.is-markdown table.md-table th {
    font-weight: 600;
    background-color: var(--aether-code-bg);
}
.msg-content.is-markdown table.md-table .md-align-left { text-align: left; }
.msg-content.is-markdown table.md-table .md-align-center { text-align: center; }
.msg-content.is-markdown table.md-table .md-align-right { text-align: right; }

/* In-page help-icon tooltip for the endpoint/preset config form (replaces the
   native `title` attribute, which showed inconsistently and never responded
   to a click/tap). Positioned in JS by endpoint_config_form.js. */
.epf-tooltip {
    display: none;
    position: fixed;
    z-index: 90;
    max-width: 20rem;
    padding: 0.5rem 0.75rem;
    border-radius: 0.5rem;
    background-color: var(--aether-panel-strong);
    border: 1px solid var(--aether-accent);
    color: var(--aether-text);
    font-size: 0.75rem;
    line-height: 1.15rem;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
    pointer-events: none;
}
.msg-content.is-markdown a { text-decoration: underline; }

/* ---------------------------------------------------------------------------
   §161.3  Resizable sidebar edge.

   Desktop only: below 768px the sidebar is a slide-in drawer, which keeps its
   Tailwind width and carries no handle. The width itself lives in
   `--aether-sidebar-width`, set by each layout's <head> script before the first
   paint so a stored width never renders as the default for a frame and then
   jumps. `nav.sidebar` outranks Tailwind's `w-52`/`w-56` on specificity, which
   is why no inline width is needed, and the handle reads the same variable so
   it tracks the edge it moves.
   --------------------------------------------------------------------------- */
@media (min-width: 768px) {
    nav.sidebar {
        width: var(--aether-sidebar-width, 13rem);
    }

    .sidebar-resize-handle {
        position: fixed;
        top: 0;
        bottom: 0;
        left: var(--aether-sidebar-width, 13rem);
        width: 6px;
        margin-left: -3px;
        z-index: 20;
        padding: 0;
        border: none;
        background-color: transparent;
        cursor: col-resize;
        /* Fixed rather than absolute: the sidebar is its own scroll container,
           and an absolutely positioned child would scroll away from the edge
           it is supposed to be attached to. */
        touch-action: none;
    }

    /* The grip (ticket 01M51NYGW7N9D4DPRWTFPXXEC6). One box doing three jobs, and
       the third is what pays for the first two.

       * It is the SC 2.5.8 hit area. The handle's own box is a 6px line, and the
         criterion measures the pointer target, not the paint. 24 x 120 contains a
         24 x 24 square outright, so the floor is met on any reading -- including an
         auditor who declines a bounding-box argument.
       * It is the first affordance this edge has ever had. At rest the divider is
         the 1px `::after` line below, so a resizable edge was invisible: nothing
         distinguished it from a border. The bar is deliberately the grip's full
         120px rather than a short centred stub, because the 120px is also the
         band where the wheel is inert (below), and a cost the user can see is a
         different thing from one that is merely silent.
       * Its HEIGHT is the bound on both costs. The previous shape was this same
         24px width at full viewport height, and it blocked mouse-wheel scrolling
         of `main` across an 18px band for the whole height of the viewport --
         measured, 0px against +516px for the same sweep 200px to its right. The
         mechanism is `main`'s own padding: the strip covers it, so the wheel
         target is the handle and not the scroller. Bounding the height to 120px
         cuts that area by about 85% at an 800px viewport and leaves the latitude
         that makes the target easy to acquire with tremor or reduced motor
         precision -- which a 24 x 24 knob at the WCAG floor would not.

       Deliberately a real element rather than a pseudo-element: `::before` cannot
       be measured by anything that reads the box model, because a pseudo-element
       does not enlarge its host's rect, so `getBoundingClientRect()` on the
       separator reports 6px however well the hit area works. The grip has a rect
       of its own. `role="separator"` is Children Presentational, so the span adds
       nothing to the accessibility tree, and `aria-hidden` says so in the markup
       rather than relying on that; the drag is unaffected because a pointerdown on
       the grip bubbles to the handle, which is where the listener and the pointer
       capture live. The handle's own 6px box, its `:focus-visible` ring and its
       full-height drag region are all untouched -- widening the separator itself
       would have turned its focus ring into a 24px band, which is the reason the
       original fix used a pseudo-element at all.

       Growth stays asymmetric into the content side: the sidebar's right edge
       carries its own scrollbar, which must stay grabbable, while `main`'s left
       24px is `p-6` padding and holds no target. Cost still accepted, now bounded
       to the grip: with `touch-action: none` a touch drag begun on it resizes
       instead of scrolling `main`. */
    .sidebar-resize-grip {
        position: absolute;
        top: 50%;
        left: 0;
        width: 24px;
        height: 120px;
        transform: translateY(-50%);
    }

    /* The painted part. Centred on the resting line rather than on the grip's own
       box: the grip grows one way, so its centre is 9px clear of the edge it
       marks. The grip starts at `x = w - 3` and the line occupies `[w, w + 1]`, so
       5px at `left: 1px` spans `[w - 2, w + 3]` and the two share a centre exactly.

       No `z-index`, on purpose. `::after` is after the host's children in tree
       order, so the resting line paints over the middle of this bar and the hover
       and drag states stay visible on the grip itself -- raising the grip above it
       would have hidden that feedback exactly where the pointer is.

       `--aether-muted` is the quietest token that can actually be seen in all nine
       themes. Measured against both surfaces the bar straddles, the worst of the
       eighteen readings is 4.05:1 (`nord`, against the sidebar panel), so it
       clears SC 1.4.11's 3:1 everywhere and SC 1.4.3's 4.5:1 in eight of nine --
       whereas `--aether-border`, which the resting line uses, is 1.29:1 at worst.
       A token rather than a hand-picked colour so a tenth theme inherits it. */
    .sidebar-resize-grip::after {
        content: "";
        position: absolute;
        top: 0;
        bottom: 0;
        left: 1px;
        width: 5px;
        border-radius: 3px;
        background-color: var(--aether-muted);
    }

    /* A 1px line at rest so the edge is visible before anyone hovers it, and a
       2px accent line while it is being used. The suppressed outline is
       replaced by a themed ring: the handle is a keyboard-operable separator
       and had nothing to show for its own Tab stop (§161.8.3). */
    .sidebar-resize-handle::after {
        content: "";
        position: absolute;
        top: 0;
        bottom: 0;
        left: 3px;
        width: 1px;
        background-color: var(--aether-muted);
    }

    .sidebar-resize-handle:hover::after,
    .sidebar-resize-handle:focus-visible::after,
    .sidebar-resize-handle.is-dragging::after {
        left: 2px;
        width: 2px;
        background-color: var(--aether-nav-rail);
    }

    .sidebar-resize-handle:focus-visible {
        outline: 2px solid var(--aether-nav-focus);
        outline-offset: -1px;
    }
}

@media (max-width: 767px) {
    .sidebar-resize-handle {
        display: none;
    }

    /* §209.1  The drawer is never wider than the viewport it is shown in.

       Here the drawer's width comes from Tailwind (`w-52` admin, `w-56` portal)
       and `--aether-sidebar-width` does not apply, so it was a fixed 208/224px at
       every narrower width: at 180 CSS px — a 360px phone at 200 % browser zoom —
       Logout sat entirely outside the visual viewport and Settings was clipped,
       with no horizontal document scroll to reach either (WCAG 2.2 SC 1.4.4).

       A cap rather than a `width`, so each shell keeps its own Tailwind width
       wherever it fits and one rule serves both: nothing moves until the viewport
       is narrower than the drawer plus the peek, which is below 232px (admin) and
       248px (portal). The 24px peek keeps the backdrop a dismiss target of its
       own. The brand row's `nowrap` assumed 176px of content width, which this
       breaks; §209.2 relaxes it. */
    nav.sidebar {
        max-width: calc(100vw - 24px);
    }

    /* §207.22.11  The identity footer wraps rather than overhangs.

       §209.1's cap is what this pays for: the row's four 24px boxes and four 8px gaps
       need 128px of content width, and the capped drawer stops having it below a
       176 CSS px viewport. At 160 the row overhung the panel's content edge by 17px
       and Logout rendered 116→140 against a 135px panel, inside the region §209
       rescued (bug report `01M5AYYD6FTV8N7DB0239WJ5A3`).

       Wrapping and not shrinking: the icons are exactly on SC 2.5.8's 24px floor and
       three sidebar controls are already below it (ticket
       `01M51NYGW7N9D4DPRWTFPXXEC6`), so there is no slack to spend here. The
       `sidebar-footer-actions` group breaks to a line of its own, left-aligned —
       which also keeps the controls out of the right edge the amendments on
       `§207.16.2` and `§207.10.6` reserve.

       `height` relaxes to `auto` and is given **no** `min-height`, so only the width
       that cannot hold one line buys a second one. The absent `min-height` is
       load-bearing, and it was measured rather than reasoned: this row is a flex item
       of a `flex-col overflow-y-auto` nav, so its default `flex-shrink: 1` applies,
       and what normally stops it being compressed is flexbox's *automatic* minimum
       size — `min-height: auto`, which floors a flex item at its content. An explicit
       `min-height: 44px` replaces that floor, and with it in place a 160x320 viewport
       (a 320x640 phone at 200 % zoom — the reported shape) compressed the wrapped row
       from the 66px it needs back to 44px and clipped the identity line out of it.
       Leaving the automatic minimum alone floors the row at 44px on one line and 76px
       on two, which is what the 44px `height` was expressing anyway.

       The 9.5px padding is what keeps the relaxation inert, and it is arithmetic
       rather than a tweak:
       `height: 44px` is a border-box height, so the shipped row spends 1px on its
       top border and 20px on padding and leaves a **23px** content box — its 24px
       children already overflow it by 0.5px above and below. An auto height over the
       same padding would therefore measure 45px, not 44px. Taking 0.5px off each
       padding edge puts the content box exactly where the overflowing children
       already sat: the row measures 44.0px on one line and every control's top and
       bottom are unchanged to the digit, which is what `§207.10.2`'s 320px and 640px
       readings assert. Two lines measure 76px.

       The base rule keeps its own `height` declaration untouched, which is what
       `sidebar_resize.test.js` reads off the 2/4/6/8/12/16/20/24/32/44 scale. */
    .sidebar-footer {
        flex-wrap: wrap;
        height: auto;
        padding: 9.5px 12px;
    }
}

/* ---------------------------------------------------------------------------
   §205.2.2  A floor under the sidebar chat list's dynamic height.

   `user_chat.html` sizes `#chat-list` as `max-height: calc(100dvh - 360px)`,
   which `USA-20261001-008` already records as clamping to `0px` once the
   viewport is shorter than 360px. Since §205.2 asks the on-screen keyboard to
   shrink the layout viewport — that is the whole point of
   `interactive-widget=resizes-content` — `100dvh` now reaches roughly 360-410px
   of a 664px phone while the keyboard is open, and that formula would collapse
   the chat list to nothing. Fixing the composer must not close the navigation.

   This belongs beside the declaration it floors, in `user_chat.html`'s own
   block. It is here because that file belongs to another topic in this round
   and must not be edited from the viewport work; `ToDo.md §205.3` carries the
   leaf that folds it back and deletes this override. The descendant selector is
   what outranks the template's bare `#chat-list`, which is loaded after this
   sheet and would otherwise win on source order alone.
   --------------------------------------------------------------------------- */
nav.sidebar #chat-list {
    max-height: max(calc(100dvh - 360px), 8rem);
}

/* ---------------------------------------------------------------------------
   §207.9.1, §207.10.5, §207.10.6  Component sections, and the empty states the
   nav needs now that a section can legitimately have nothing in it.

   A component's label is the one nav label this platform does not author: it
   comes from a manifest, so it can be any length. It is therefore left-aligned
   and truncates, and the row carries no right-aligned affordance at all —
   below 768px the drawer's right edge is clipped out of the visual viewport
   (bug report `01M51DQT0GDQJVBNAXBX75ZJJB`, accepted as pre-existing by
   §207.16.2), so a count, a status dot or an overflow menu placed there would
   be unreachable at 200 % zoom on a phone. The full name reaches assistive
   technology through the element's `title`, not through a wider row.

   **Widened to every top-level section label, 2026-10-19 (§207.22.11).** The
   scoping below used to read "a literal in a template and short by construction"
   — which was measured at the pre-§209 drawer width and is false at the capped
   one: "Components" is a template literal and still has a 127.63px min-content
   band against a 135px panel at a 160 CSS px viewport, so it rendered 22→149.63,
   14.63px past the content edge (bug report `01M5AYYD6FTV8N7DB0239WJ5A3`). The
   amendments on `§207.10.6` and `§207.16.2` ask for truncation from section
   labels as a class, not from manifest-fed ones only, so the rule now matches
   what those sentences say. `.nav-section-component` is a subset of
   `.nav-section` and keeps its behaviour unchanged.

   The sub-level rule stays on the component rows, and that exclusion is a
   reading rather than an omission: with every accordion in both shells expanded
   at 160 CSS px, "Components" is the **only** nav label past the content edge —
   `.nav-subsection` "LLM-Endpoint" and "Image-Endpoint" sit inside it, because a
   sub-level band starts further in at a smaller type size.
   --------------------------------------------------------------------------- */
.nav-accordion-header .nav-section,
.nav-sub-accordion-header .nav-subsection-component {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* A named normal state, at the quiet ink the footer and the version already
   use — `--aether-nav-quiet` rather than `--aether-muted`, which measures
   4.05:1 on the panel in `nord` and is a 1.4.3 failure there (§206.5). */
.nav-empty-state {
    margin: 2px 4px 4px 20px;
    font-size: 12px;
    line-height: 18px;
    color: var(--aether-nav-quiet);
}

/* ---------------------------------------------------------------------------
   §207.9.2, §207.9.6, §207.10.4  The marketplace surfaces' own states.

   **These pills carry no colour semantics, deliberately.** The five item states
   of §207.9.2 and the disabled/stopped state of §207.10.4 are carried by their
   own words; the pill is one neutral shape for all of them. Two measurements
   decided that, over all nine themes:

   * The five existing `.badge-*` classes tint their own hue 30 % into the panel
     and then print that hue on it. Measured, the result is 1.91:1 (badge-red in
     `nord`) to 6.35:1 (badge-green in `high-contrast`) and it clears 4.5:1 in
     only 12 theme-and-class combinations out of 45 — nine of them `.badge-gray`,
     which is not a hue-on-its-own-tint pairing at all, so of the four hue
     classes 33 of 36 fail. Those counts score a combination as passing only
     when it clears 4.5:1 against **both** surfaces a pill sits on, which is
     not pedantry: `badge-green` in `graphite` reads 4.52:1 over
     `--aether-panel` and 3.95:1 over `--aether-panel-strong`. Reusing them for
     a state would have been a 1.4.3 failure in eight themes — recorded as a
     finding against §206, which is where that family is owned; nothing here
     changes it.
     §215.1.1 has since re-paired the four hue classes, so the range and the
     counts above describe the pre-§215 family.
   * As a *boundary* at 3:1 (WCAG 1.4.11) the semantic tokens fail too against
     `--aether-panel-strong`: `--aether-danger` measures 2.11:1 in `nord`,
     `--aether-warning` 2.90:1 in `sepia`, `--aether-accent` 2.11:1 in
     `aether-dark`. Only `--aether-nav-quiet` (min 4.38:1) and `--aether-text`
     (min 7.09:1) hold in all nine.

   So: `--aether-text` on `--aether-panel-strong` (7.09:1 in `nord` to 16.86:1 in
   `high-contrast`) with a `--aether-nav-quiet` boundary. An actionable state is
   distinguished by weight, which no theme and no colour-vision deficiency can
   take away.
   --------------------------------------------------------------------------- */
.component-state {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 8px;
    border-radius: 10px;
    font-size: 12px;
    line-height: 18px;
    font-weight: 500;
    color: var(--aether-text);
    background-color: var(--aether-panel-strong);
    border: 1px solid var(--aether-nav-quiet);
}
.component-state-actionable {
    font-weight: 700;
}

/* A performance-critical setting, and a setting that needs a restart. Both are
   facts about the field, so both are words in the label and not a colour on it:
   the mark below only groups them visually for a sighted scan. */
.setting-mark {
    display: inline-block;
    margin-left: 6px;
    padding: 1px 6px;
    border-radius: 8px;
    font-size: 11px;
    line-height: 16px;
    font-weight: 600;
    color: var(--aether-text);
    background-color: var(--aether-panel-strong);
    border: 1px solid var(--aether-nav-quiet);
}

/* A row whose component the administrator stopped. The reason is text in the row
   and in its accessible name (§207.10.4), so this rule carries no information at
   all — it must not be the only signal, and it is not: `opacity` is deliberately
   absent, because dimming is exactly the cue that leaves a screen-reader user
   with "dimmed, unavailable" and nothing else. */
.component-row-disabled {
    border-left: 3px solid var(--aether-nav-quiet);
    padding-left: 9px;
}

/* ---------------------------------------------------------------------------
   §207.11.4  The project page's Components panel.

   Every colour here is `--aether-text`, on `--aether-panel-strong` for a row
   and a control and on `--aether-panel` for the panel's own status line. That
   is not conservatism: `--aether-muted`, which `.text-gray-400` resolves to and
   the two sibling panels use, measures 4.05:1 on `--aether-panel` in one theme
   (§206.5's own note above), and `--aether-accent` with white text has no
   measured on-fill foreground at all until `--aether-accent-on` (§206.3)
   exists. So hierarchy in this panel is size and weight, which no theme and no
   colour-vision deficiency can take away, and the row's boundary is
   `--aether-nav-quiet` — the one token that clears 3:1 as a boundary in all
   nine themes.

   Measured in Chromium per theme
   (`tests/web_interface/test_project_components_browser_207_11_4.py`):
   `--aether-text` on `--aether-panel-strong` — the row and the state pill —
   7.09:1 (nord) to 16.86:1 (high-contrast); on `--aether-panel` — the status
   line and the control — 8.26:1 (nord) to 18.88:1 (high-contrast); and the
   `--aether-nav-quiet` boundary against the panel 5.11:1 (nord) to 16.29:1
   (high-contrast), against the 3:1 of WCAG 1.4.11. The row wraps at 320px
   rather than scrolling, which is why it is a `flex-wrap` and not a grid.
   --------------------------------------------------------------------------- */
.project-component-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    padding: 8px 10px;
    border-radius: 10px;
    color: var(--aether-text);
    background-color: var(--aether-panel-strong);
    border: 1px solid var(--aether-nav-quiet);
}
.project-component-name {
    flex: 1 1 8rem;
    min-width: 0;
    font-weight: 600;
    overflow-wrap: anywhere;
}
.project-component-note {
    flex: 1 1 100%;
    font-size: 12px;
    line-height: 18px;
    color: var(--aether-text);
}
.project-component-action {
    padding: 6px 10px;
    border-radius: 8px;
    font-size: 12px;
    line-height: 18px;
    font-weight: 600;
    color: var(--aether-text);
    background-color: var(--aether-panel);
    border: 1px solid var(--aether-nav-quiet);
    min-height: 32px;
}
.project-component-status {
    color: var(--aether-text);
}

/* The install sequence, step by step. `ok` and `failed` are words in the line. */
.component-steps {
    margin: 0;
    padding-left: 18px;
    font-size: 12px;
    line-height: 18px;
    color: var(--aether-text);
}

/* A failed start's captured output, shown beside the step that failed (§207.22.1). It is a
   component's own stderr, so it is unbounded in width and in length: it wraps rather than
   widening the toast, and scrolls rather than pushing the page's rows off screen. Carried
   on the same neutral tokens as `.component-state`, because no state here is a colour. */
.component-stderr {
    margin: 6px 0 0 -18px;
    padding: 6px 8px;
    max-height: 12em;
    overflow: auto;
    font-size: 12px;
    line-height: 18px;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    color: var(--aether-text);
    background-color: var(--aether-panel-strong);
    border: 1px solid var(--aether-nav-quiet);
    border-radius: 6px;
}
