/* Qubis OS — application shell.
   Design tokens, orb and ambient-background technique ported from the
   design_handoff_qubis_os_redesign spec: OKLCH palette, Bricolage Grotesque /
   IBM Plex type system, dark as default with [data-theme="light"] override.

   ── One stylesheet in four files (2026-09-10) ──
   This was one 1325-line file, well past the 800-line rule, and Section 2's
   composer would have pushed it further. Split along the section banners it
   already had, by which part of the product a rule dresses:

     style.css     tokens, resets, the base control and button look, the orb,
                   the shell chrome (sidebar, org switcher, page frame)
     chat.css      the chat page — messages, execution details, composer,
                   attachments, lightbox
     pages.css     dashboard, memory, settings, the shared modal, onboarding
                   and auth
     features.css  the live panel, Qubis (the figure), the Studio, and the per-module panels
                   that arrived after them

   Nothing moved *within* the cascade: the four files concatenate back to the
   original byte for byte, and index.html links them in that order. So the
   order of the <link> tags is load-bearing — a rule in chat.css still beats an
   equally specific one in style.css, exactly as it did when they were
   neighbours in one file. Tokens live here because the other three read them. */

:root {
  /* Tells the browser which theme its own widgets should draw in — the
     <select> popup, the time picker, scrollbars. Without it every native
     dropdown opened white over the dark interface (Automations "What", the
     org switcher). The light theme flips it below. */
  color-scheme: dark;
  /* The website's own palette, verbatim, so the product and the page that
     sells it are one place. Void ground, one blue, one warm sand signal,
     green only for "done". Source of truth: qubis-os-web design/v7/site.css. */
  --color-bg: #07080B;
  --color-surface: #0D0F14;
  --color-surface-2: #141821;
  /* The sign-in starfield, matched to the marketing site's own --star so the
     two ends of the same journey are the same colour. */
  --star: #C9D6F2;
  --color-text: #F7F8FA;
  --color-accent: #3B6BF5;
  --color-divider: #1E232E;
  --signal: #5FD09A;
  --sand: #E7A16A;
  --warn: #E0685C;

  --color-neutral-100: #F4F5F8;
  --color-neutral-200: #E2E5EC;
  --color-neutral-300: #C6CBD7;
  --color-neutral-400: #9BA3B4;
  --color-neutral-500: #7D8598;
  --color-neutral-600: #646D7E;
  --color-neutral-700: #4A5160;
  --color-neutral-800: #2A3140;
  --color-neutral-900: #1E232E;

  --color-accent-100: #111D40;
  --color-accent-200: #1A2E66;
  --color-accent-300: #244093;
  --color-accent-400: #2F55C4;
  --color-accent-500: #3B6BF5;
  --color-accent-600: #6E92F8;
  --color-accent-700: #9FB8FF;
  --color-accent-800: #D3E0FD;
  --color-accent-900: #EAF0FE;

  --font-heading: "Bricolage Grotesque", "IBM Plex Sans", sans-serif;
  --font-body: "IBM Plex Sans", system-ui, sans-serif;
  --font-mono: "IBM Plex Mono", monospace;

  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 14px;

  --shadow-sm: 0 0 0 1px #1E232E;
  --shadow-md: 0 0 0 1px #2A3140, 0 6px 18px rgba(0, 0, 0, 0.55);
  --shadow-lg: 0 0 0 1px #4A5160, 0 16px 40px rgba(0, 0, 0, 0.65);
}

[data-theme="light"] {
  color-scheme: light;
  /* The website's one daylight room: warm paper, ink, the same blue held
     darker so it still reads on white. */
  --color-bg: #F4F2EE;
  --color-surface: #FFFFFF;
  --color-surface-2: #ECE9E3;
  /* Light theme inverts the problem: pale points vanish on a pale ground, so
     the stars go dark and thin instead of bright. */
  --star: #6B7386;
  --color-text: #0A0B0E;
  --color-accent: #2F55C4;
  --color-divider: #E4E1DA;
  --signal: #2E8F63;
  --sand: #B0793F;
  --warn: #C24A3E;

  --color-neutral-100: #1E232E;
  --color-neutral-200: #2A3140;
  --color-neutral-300: #4A5160;
  --color-neutral-400: #646D7E;
  --color-neutral-500: #7D8598;
  --color-neutral-600: #9BA3B4;
  --color-neutral-700: #C6CBD7;
  --color-neutral-800: #E2E5EC;
  --color-neutral-900: #F4F5F8;

  --color-accent-100: #EAF0FE;
  --color-accent-200: #D3E0FD;
  --color-accent-300: #9FB8FF;
  --color-accent-400: #6E92F8;
  --color-accent-500: #3B6BF5;
  --color-accent-600: #2F55C4;
  --color-accent-700: #244093;
  --color-accent-800: #1A2E66;
  --color-accent-900: #111D40;

  --shadow-sm: 0 0 0 1px #E4E1DA;
  --shadow-md: 0 0 0 1px #E4E1DA, 0 6px 18px rgba(10, 11, 14, 0.08);
  --shadow-lg: 0 0 0 1px #D6D2C9, 0 16px 40px rgba(10, 11, 14, 0.12);
}

* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
body {
  background: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.55;
  overflow: hidden;
  transition: background 0.35s, color 0.35s;
}
h1, h2, h3, h4 { font-family: var(--font-heading); font-weight: 500; letter-spacing: -0.015em; margin: 0; }
button { font-family: inherit; cursor: pointer; }
button:disabled { opacity: 0.45; cursor: not-allowed; }
input, select { font-family: var(--font-body); }
/* One look for every text control (Section 2, 2026-09-10). Thirteen inputs
   and fifteen selects across the pages carried no class and rendered as the
   browser's white defaults beside a themed composer. :where() keeps this at
   zero specificity, so .field-input, .provider-row select and every other
   rule that already styles a control still wins; this only catches the ones
   nothing else does. Checkboxes, radios and file pickers are excluded — they
   are not text controls and accent-color already themes them. */
:where(input:not([type="checkbox"], [type="radio"], [type="file"], [type="range"], [type="color"], [type="submit"], [type="button"]), select, textarea) {
  background: var(--color-surface); color: var(--color-text); border: 1px solid var(--color-divider);
  border-radius: var(--radius-md); padding: 8px 10px; font: inherit; font-size: 14px; outline: none;
  color-scheme: inherit;
}
:where(input, select, textarea):focus-visible { border-color: var(--color-accent); }
:where(input, textarea)::placeholder { color: var(--color-neutral-500); }
/* ---------- buttons ----------
   One visual language, app-wide: .btn is the control, .btn-sm the panel-header
   size (Sync now, Pause, Stop), .btn-primary the filled variant for the one
   action a form exists for (Start, Save, Send). These three classes sat on
   twelve buttons in five modules with no rule behind them once, and were
   deleted by a chat-page edit once more (#239) because they lived in the chat
   block — a design-system rule in a page file reads as that page's. So they
   live here, beside the tokens and the base control look, and
   tests/frontendStyles.test.ts refuses a class with no rule. Anchors carry
   .btn too (Export), hence inline-flex and the reset on text-decoration.
   chat.css's .send-btn only makes this compact; it wins by load order. */
.btn {
  background: color-mix(in srgb, var(--color-accent) 16%, transparent); border: 1px solid var(--color-accent);
  color: var(--color-accent-300); border-radius: 10px; padding: 9px 16px; font-size: 14px; font-weight: 500;
  line-height: 1.25; display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  text-decoration: none; white-space: nowrap; cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
}
.btn:hover:not(:disabled) { background: color-mix(in srgb, var(--color-accent) 28%, transparent); }
.btn:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }
.btn-sm { padding: 6px 12px; font-size: 13px; border-radius: 8px; }
.btn-primary { background: var(--color-accent); color: #fff; }
.btn-primary:hover:not(:disabled) { background: color-mix(in srgb, var(--color-accent) 85%, var(--color-text)); }
a { color: var(--color-accent); }
.hidden { display: none !important; }
.text-muted { color: color-mix(in srgb, var(--color-text) 55%, transparent); }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb { background: color-mix(in srgb, var(--color-text) 14%, transparent); border-radius: 5px; border: 3px solid transparent; background-clip: content-box; }
::-webkit-scrollbar-track { background: transparent; }
:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }

/* ══════════════════════════════════════════ the orb + ambient background ══ */
@property --angle { syntax: "<angle>"; inherits: false; initial-value: 0deg; }
@keyframes orbRotate { to { --angle: 360deg; } }
@keyframes orbBreathe { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.05); } }
@keyframes orbHalo { 0%, 100% { opacity: 0.5; transform: scale(1); } 50% { opacity: 0.85; transform: scale(1.07); } }
@keyframes orbRing { 0% { transform: scale(0.88); opacity: 0.5; } 100% { transform: scale(1.35); opacity: 0; } }
@keyframes sheenPulse { 0%, 100% { opacity: 0.8; } 50% { opacity: 1; } }
@keyframes fadeUp { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes msgIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }
@keyframes growBar { from { width: 0; } }
@keyframes driftA { 0%, 100% { transform: translate(0, 0) scale(1); } 50% { transform: translate(4%, 6%) scale(1.08); } }
@keyframes driftB { 0%, 100% { transform: translate(0, 0) scale(1); } 50% { transform: translate(-5%, 4%) scale(1.05); } }
@keyframes driftC { 0%, 100% { transform: translate(0, 0) scale(1); } 50% { transform: translate(3%, -5%) scale(1.1); } }

.nv-ambient { position: absolute; inset: 0; overflow: hidden; pointer-events: none; }
.nv-blob { position: absolute; border-radius: 9999px; filter: blur(90px); opacity: 0.5; }
.nv-blob-a { top: -10%; left: 8%; width: 42vw; height: 42vw; max-width: 620px; max-height: 620px; background: radial-gradient(circle, rgba(59, 107, 245, 0.55), transparent 70%); animation: driftA 46s ease-in-out infinite; }
.nv-blob-b { top: 8%; right: 4%; width: 34vw; height: 34vw; max-width: 520px; max-height: 520px; background: radial-gradient(circle, rgba(123, 95, 224, 0.5), transparent 70%); animation: driftB 58s ease-in-out infinite; }
.nv-blob-c { bottom: -14%; left: 30%; width: 38vw; height: 38vw; max-width: 560px; max-height: 560px; background: radial-gradient(circle, rgba(231, 161, 106, 0.35), transparent 70%); animation: driftC 52s ease-in-out infinite; }
[data-theme="light"] .nv-blob { opacity: 0.35; }

.nv-orb { position: relative; flex: none; }
.nv-orb .orb-halo { position: absolute; inset: -14%; border-radius: 50%; background: radial-gradient(circle, rgba(159, 184, 255, 0.6), transparent 64%); filter: blur(22px); animation: orbHalo 5s ease-in-out infinite; }
.nv-orb .orb-core { position: absolute; inset: 0; border-radius: 50%; overflow: hidden; isolation: isolate; background: #141821; }
.nv-orb .orb-core::before {
  content: "";
  position: absolute; inset: 0; border-radius: 50%;
  background:
    conic-gradient(from calc(var(--angle) * 2) at 25% 70%, #7B5FE0, transparent 20% 80%, #7B5FE0),
    conic-gradient(from calc(var(--angle) * 2) at 45% 75%, #9FB8FF, transparent 30% 60%, #9FB8FF),
    conic-gradient(from calc(var(--angle) * -3) at 80% 20%, #3B6BF5, transparent 40% 60%, #3B6BF5),
    conic-gradient(from calc(var(--angle) * 1.5) at 60% 35%, #E7A16A, transparent 25% 75%, #E7A16A),
    conic-gradient(from calc(var(--angle) * 2) at 15% 5%, #9FB8FF, transparent 10% 90%, #9FB8FF),
    conic-gradient(from calc(var(--angle) * 1) at 20% 80%, #3B6BF5, transparent 10% 90%, #3B6BF5),
    conic-gradient(from calc(var(--angle) * -2) at 85% 10%, #7B5FE0, transparent 20% 80%, #7B5FE0);
  filter: blur(3px) contrast(1.5) saturate(1.4);
  animation: orbRotate 26s linear infinite;
}
.nv-orb .orb-core::after { content: ""; position: absolute; inset: 0; border-radius: 50%; box-shadow: inset 0 0 0 1px hsl(0 0% 100% / 0.16), inset 0 10px 20px hsl(0 0% 100% / 0.22), inset 0 -12px 24px hsl(0 0% 0% / 0.4); }
.nv-orb .orb-swirl { position: absolute; inset: 0; border-radius: 50%; background: radial-gradient(circle at 30% 24%, hsl(0 0% 100% / 0.32), transparent 34%), radial-gradient(circle at 72% 80%, hsl(0 0% 100% / 0.07), transparent 48%); mix-blend-mode: screen; animation: sheenPulse 6s ease-in-out infinite; }
.nv-orb .orb-ring { position: absolute; inset: 0; border-radius: 50%; border: 1px solid color-mix(in srgb, var(--color-accent) 55%, transparent); animation: orbRing 3.4s ease-out infinite; }
.nv-orb[data-state="thinking"] .orb-core::before { animation-duration: 5s; }
.nv-orb[data-state="thinking"] .orb-halo { animation-duration: 1.6s; }
.nv-orb[data-state="thinking"] .orb-ring { animation-duration: 1.6s; }
.nv-orb[data-state="speaking"] { animation: orbBreathe 1.2s ease-in-out infinite; }
.nv-orb[data-state="speaking"] .orb-core::before { animation-duration: 9s; }
.nv-orb[data-state="speaking"] .orb-halo { animation-duration: 1.2s; }
.nv-orb[data-state="idle"] .orb-ring { animation-duration: 6s; }
@media (prefers-reduced-motion: reduce) { .nv-blob, .nv-orb .orb-core::before, .nv-orb .orb-swirl { animation: none; } }

.nv-navbtn:hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
.nv-chip { transition: border-color 0.2s, color 0.2s, transform 0.2s; }
.nv-chip:hover { border-color: var(--color-accent); color: var(--color-accent-300); transform: translateY(-1px); }
.nv-card { transition: transform 0.25s ease, box-shadow 0.25s ease; }
.nv-card:hover { transform: translateY(-3px); box-shadow: var(--shadow-md); }
.nv-bar { animation: growBar 0.9s cubic-bezier(0.2, 0.7, 0.2, 1) both; }

/* Small transparent icon buttons used throughout chrome (sidebar controls,
   memory delete, mic). */
/* Connector directory (Session 15): groups, search, show-more. */
.directory-toolbar { display:flex; align-items:center; gap:8px; margin:0 0 10px; }
.directory-toolbar .directory-search { flex:1; }
.directory-group { margin-top:12px; }
.directory-group-title { font-weight:600; margin:0 0 6px; cursor:default; }
details.directory-group > summary { cursor:pointer; list-style:revert; }
.directory-more { margin:4px 0 8px; }

/* Speech in (Session 13): the mic while recording. */
.icon-btn-flat.recording { color: var(--danger, #d33); animation: qubis-pulse 1s ease-in-out infinite; }
@keyframes qubis-pulse { 50% { opacity: 0.45; } }
.icon-btn-flat {
  background: transparent; border: none; color: var(--color-neutral-400);
  padding: 6px; border-radius: 6px; display: flex; align-items: center; justify-content: center;
  font-size: 16px;
}
.icon-btn-flat:hover { color: var(--color-text); }

/* ══════════════════════════════════════════════════════════ app shell ══ */
/* One row, explicitly capped. Without grid-template-rows the single implicit
   row is `auto` and grows to whatever the tallest column holds, so a sidebar
   longer than the viewport made the whole shell taller than 100vh — and since
   body is `overflow: hidden`, the bottom was simply cut off with nothing to
   scroll. minmax(0, 1fr) is the row saying it may not exceed the shell, which
   is what lets a column's own overflow-y do its job. */
.app-shell { display: grid; grid-template-columns: var(--left-col, 248px) 1fr var(--right-col, 268px); grid-template-rows: minmax(0, 1fr); height: 100vh; }

/* Minimized sidebars (shell.js toggles these classes and persists the choice).
   Visibility is done here with shell-level classes rather than .hidden because
   .hidden is !important and would fight the .peek override below. The 22px
   column is the rail; grid auto-placement puts whichever of rail/aside is
   visible into the side column, so the two never coexist in the layout. */
.app-shell.left-min { --left-col: 22px; }
.app-shell.right-min { --right-col: 22px; }
.app-shell.left-min .app-sidebar:not(.peek) { display: none; }
.app-shell.right-min .live-sidebar:not(.peek) { display: none; }
.app-shell:not(.left-min) .side-rail-left { display: none; }
.app-shell:not(.right-min) .side-rail-right { display: none; }

/* A rail's caret sits where the toggle that produced it sat, so minimizing
   never moves the control out from under the pointer: the right sidebar's
   toggle is its first child (top), the left sidebar's lives in .sidebar-bottom
   (bottom). Both used to say flex-end, which on a row flex box is the *bottom*
   — so the right-hand caret jumped the height of the screen on every click. */
.side-rail {
  display: flex; justify-content: center;
  padding: 16px 0; font-size: 13px;
  background: color-mix(in srgb, var(--color-surface) 55%, var(--color-bg));
  border: none; color: var(--color-neutral-500);
}
.side-rail:hover { color: var(--color-text); }
.side-rail-left { border-right: 1px solid var(--color-divider); align-items: flex-end; }
.side-rail-right { border-left: 1px solid var(--color-divider); align-items: flex-start; }

/* Hover-peek: the sidebar floats over the content instead of reflowing it,
   so a glance does not shove the page around. Below the auth/onboarding
   overlays (z-index 100) — nothing here outranks signing in. */
.app-sidebar.peek, .live-sidebar.peek {
  position: fixed; top: 0; bottom: 0; z-index: 80;
  display: flex; box-shadow: var(--shadow-md);
}
.app-sidebar.peek { left: 0; width: 248px; animation: peek-in-left 0.16s ease both; }
.live-sidebar.peek { right: 0; width: 268px; background: var(--color-bg); animation: peek-in-right 0.16s ease both; }
@keyframes peek-in-left { from { transform: translateX(-24px); opacity: 0.4; } to { transform: none; opacity: 1; } }
@keyframes peek-in-right { from { transform: translateX(24px); opacity: 0.4; } to { transform: none; opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .app-sidebar.peek, .live-sidebar.peek { animation: none; }
}

.sidebar-min-btn { align-self: flex-end; }

.app-sidebar {
  display: flex; flex-direction: column; gap: 4px;
  padding: 16px 12px; overflow-y: auto; min-height: 0;
  border-right: 1px solid var(--color-divider);
  background: color-mix(in srgb, var(--color-surface) 55%, var(--color-bg));
}
/* The sidebar is the one scroll container on this side, so nothing inside it
   may absorb the overflow by shrinking. #agentNav used to: it carried its own
   max-height and overflow-y, which makes a flex item a scroll container, whose
   automatic minimum size is 0 rather than its content — so it quietly squashed
   to nothing under a long nav instead of pushing the sidebar past its height,
   and the sidebar therefore never overflowed and never grew a scrollbar. The
   symptom was an agent list you could not see the bottom of and no way to
   scroll to it. The spacer keeps growing; it is the only child that may. */
.app-sidebar > *:not(.sidebar-spacer) { flex: none; }

.org-header {
  width: 100%; display: flex; align-items: center; gap: 10px;
  border: 1px solid var(--color-divider); border-radius: var(--radius-md);
  padding: 10px; background: transparent;
}
.org-avatar {
  width: 30px; height: 30px; border-radius: 8px; flex: none;
  background: color-mix(in srgb, var(--color-accent) 22%, transparent); color: var(--color-accent-300);
  display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 600;
}
.org-header-text { flex: 1; min-width: 0; text-align: left; }
.org-name { display: block; font-size: 13.5px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.org-role { display: block; font-size: 11.5px; color: var(--color-neutral-400); }
.org-switcher {
  width: 100%; margin-top: 8px; padding: 7px 10px;
  border: 1px solid var(--color-divider); border-radius: var(--radius-md);
  background: transparent; color: var(--color-text);
  font: inherit; font-size: 12.5px; cursor: pointer;
}
.org-switcher option { background: var(--color-surface); color: var(--color-text); }

.sidebar-section-label {
  font-size: 10.5px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.1em;
  font-family: var(--font-mono); color: var(--color-neutral-500);
  padding: 14px 8px 6px;
}

.page-nav, .agent-nav { display: flex; flex-direction: column; gap: 2px; }
.page-btn {
  display: flex; align-items: center; gap: 11px;
  background: transparent; border: none; border-left: 2px solid transparent; border-radius: 6px;
  padding: 9px 11px; color: var(--color-neutral-400); font-size: 14px; text-align: left;
}
.page-btn i { font-size: 17px; }
.page-btn:hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
.page-btn.active { background: color-mix(in srgb, var(--color-accent) 10%, transparent); border-left-color: var(--color-accent); color: var(--color-text); }

.agent-item {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 10px; border-radius: var(--radius-md); color: var(--color-neutral-400);
  border: 1px solid transparent; background: transparent;
}
.agent-item:hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); color: var(--color-text); }
.agent-item.active { background: color-mix(in srgb, var(--color-accent) 10%, transparent); color: var(--color-text); border-color: color-mix(in srgb, var(--color-accent) 25%, transparent); }
.agent-avatar {
  width: 22px; height: 22px; border-radius: 6px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 700;
  background: var(--color-surface-2); color: var(--color-neutral-400);
}
.agent-item.active .agent-avatar { background: var(--color-accent); color: var(--color-bg); }
.agent-name { font-size: 12.5px; }
.agent-nav-disabled .agent-item { opacity: 0.45; cursor: default; }
.agent-nav-disabled .agent-item:hover { background: none; color: var(--color-neutral-400); }
.coming-soon-badge {
  margin-left: auto; font-size: 9px; color: var(--color-neutral-500);
  border: 1px solid var(--color-divider); padding: 1px 5px; border-radius: 4px; font-family: var(--font-mono);
}

.sidebar-spacer { flex: 1; }

.demo-badge {
  display: flex; align-items: center; gap: 8px;
  border: 1px dashed color-mix(in srgb, var(--color-accent) 45%, transparent); border-radius: var(--radius-md);
  padding: 8px 10px; margin-bottom: 10px; font-size: 11.5px; color: var(--color-neutral-300); line-height: 1.4;
}
.demo-badge i { color: var(--color-accent-300); }

/* Sticky, so who you are signed in as and the way out of the app never scroll
   away on a short viewport — which is what #agentNav's own max-height used to
   buy, at the cost of the sidebar's scroll (features.css). The negative margin
   and matching padding let the row's own background cover the full column
   width as content passes under it, despite the sidebar's 12px padding. */
.sidebar-bottom {
  position: sticky; bottom: 0; z-index: 1;
  margin: 0 -12px -16px; padding: 12px 12px 16px;
  background: color-mix(in srgb, var(--color-surface) 55%, var(--color-bg));
  border-top: 1px solid var(--color-divider);
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
}
.sidebar-user { display: flex; align-items: center; gap: 9px; min-width: 0; }
.user-avatar {
  width: 28px; height: 28px; border-radius: 50%; flex: none;
  background: var(--color-neutral-800); color: var(--color-neutral-300);
  display: flex; align-items: center; justify-content: center; font-size: 11px;
}
.user-name { font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sidebar-bottom-actions { display: flex; gap: 2px; flex: none; }

/* The unverified-session nudge above the user row. Accent, not a danger red:
   it invites finishing something, and the screen it opens says the rest.
   Hidden by default; shell.js's loadUserHeader() reveals it when the session
   says the email is unverified. */
.verify-email-nudge {
  display: flex; align-items: center; gap: 7px; width: 100%; margin-bottom: 8px;
  padding: 7px 9px; border: none; border-radius: 6px; text-align: left;
  font-size: 12.5px; color: var(--color-accent);
  background: color-mix(in srgb, var(--color-accent) 12%, transparent);
}
.verify-email-nudge:hover { background: color-mix(in srgb, var(--color-accent) 20%, transparent); }

/* ══════════════════════════════════════════════════════════ main / pages ══ */
.main { min-width: 0; height: 100vh; overflow: hidden; display: flex; flex-direction: column; background: var(--color-bg); }
.page { display: none; flex: 1; min-height: 0; flex-direction: column; }
.page-active { display: flex; }
.page-scroll { overflow-y: auto; height: 100%; padding: 36px 44px; animation: fadeUp 0.3s ease both; }
.page-inner { max-width: 860px; }
.page-title { font-size: 26px; margin: 0 0 4px; }
.page-sub { margin: 0 0 28px; font-size: 14px; }

