/* CYPHER Web — "Atmos, the Living Cockpit" design system.
   A private near-black cockpit lit by periwinkle, with a living orb at its heart.
   One accent (periwinkle #5b8cff) with a violet secondary (#8b8ff5), floating glass
   modules over a soft-lit ground, hairline strokes, monochrome glyphs, ease-out-only
   motion. Light theme is available via [data-theme="light"] but the app ships dark. */

:root {
  --bg: #0a0e16;
  --bg-2: #10151f;
  --panel: rgba(26, 33, 48, 0.72);
  --panel-solid: #1a2130;
  --panel-2: rgba(34, 42, 58, 0.6);
  --stroke: rgba(255, 255, 255, 0.07);
  --stroke-strong: rgba(255, 255, 255, 0.14);
  --text: #e7ebf3;
  --text-dim: #9aa3b6;
  --text-faint: #7c8aa0;
  --accent: #5b8cff;
  --accent-2: #8b8ff5;
  --accent-bright: #7aa2ff;
  --ease-out-quint: cubic-bezier(.22, 1, .36, 1);
  --ease-out-expo: cubic-bezier(.16, 1, .3, 1);
  /* Duration ramp — three steps instead of the old .1/.12/.15/.16/.18/.2/.22/.3
     spread. --dur-fast = press/tap feedback, --dur = the default hover/state
     change, --dur-slow = entrance-sized moves. The two easings above stay the
     only curves. */
  --dur-fast: .12s;
  --dur: .15s;
  --dur-slow: .3s;
  /* Type scale — body is the DESIGN.md 14px/1.6 reading size, --fs-sm the
     secondary/meta size, --fs-label the tracked-uppercase caption size. */
  --fs-body: 14px;
  --fs-sm: 12.5px;
  --fs-label: 11px;
  --accent-ink: #0a1226;
  --good: #3fbf87;
  --warn: #f0b429;
  --bad: #f2687a;
  /* Rounded scale — DESIGN.md ships exactly three steps (sm 9 / md 14 / pill 999);
     --radius-lg is the module/composer corner (the big floating glass panels).
     Everything else collapses onto these instead of the old 5/6/7/8/10/11/12/17/20
     literal sprawl. */
  --radius: 14px;
  --radius-sm: 9px;
  --radius-lg: 16px;
  --radius-pill: 999px;
  /* Spacing scale — the DESIGN.md 6/8/12/16/20/28 steps. Padding/gap/margin drive
     from these; the off-scale one-offs (9/11/13/14/18/22/34) snap to the nearest step. */
  --space-xs: 6px;
  --space-sm: 8px;
  --space-md: 12px;
  --space-lg: 16px;
  --space-xl: 20px;
  --space-2xl: 28px;
  /* One rail height for the composer's control strip so five different controls
     stop computing five different heights off their own padding. */
  --ctl-h: 30px;
  --shadow: 0 20px 60px -20px rgba(0, 0, 0, 0.7);
  --mono: ui-monospace, "SF Mono", "JetBrains Mono", "Cascadia Code", Menlo, Consolas, monospace;
  --sans: system-ui, -apple-system, "Segoe UI", Roboto, Inter, sans-serif;
  /* z-index layer scale — semantic names for the page-level overlay stack. Values
     are unchanged from the ad-hoc numbers they replace, so the current stacking
     order is preserved exactly; this is a token-isation, not a re-layering. The
     small intra-component z-indexes (0–9) stay literal — they order children
     inside their own stacking context, not this global overlay scale. */
  --z-dropdown: 20;        /* composer slash-command menu */
  --z-scrim: 35;           /* mobile off-canvas backdrop */
  --z-sticky: 40;          /* header dropdown panel, off-canvas sidebar/rail sheets */
  --z-drawer: 60;          /* artifact drawer, full-screen auth/landing screens */
  --z-modal-backdrop: 90;  /* modal scrim */
  --z-toast: 100;          /* toast stack */
  --z-overlay: 120;        /* code-word unlock flash */
  --z-popover: 200;        /* floating popovers (attach / user-card / agent menus) */
  --z-tooltip: 320;        /* context-usage popover */
  --z-menu: 330;           /* chess chaos side-menu */
}
:root[data-theme="light"] {
  --bg: #eef1f6; --bg-2: #e6eaf1; --panel: rgba(255,255,255,0.8); --panel-solid:#fff;
  --panel-2: rgba(255,255,255,0.6); --stroke: rgba(10,20,40,0.1); --stroke-strong: rgba(10,20,40,0.2);
  /* --text-faint was #8a94a8 — only ~2.7:1 on the light body, failing the 4.5:1
     body/caption floor. Darkened to ~4.6:1; still clearly the quietest ink, and
     the dark theme's faint (which already passes) is untouched. */
  --text:#101828; --text-dim:#475069; --text-faint:#636b81; --accent:#2f5fd0; --accent-bright:#2953c2; --accent-ink:#eef3ff; --shadow:0 20px 50px -24px rgba(20,30,60,.35);
  /* Semantic colors used as TEXT on the near-white light panels failed 4.5:1 (warn ~1.9,
     good ~2.3, bad ~3.0). Darkened for light mode only — the dark theme keeps its brighter
     hues (which already pass on the near-black ground). --accent also darkened above so the
     many `color: var(--accent)` text uses (links, author names) clear the floor on white,
     while still reading as accent for fills (accent-ink text sits on it fine). */
  --good:#1f8f5f; --warn:#8a6100; --bad:#c8384c;
}

* { box-sizing: border-box; }
/* #app MUST carry a definite height: the shell/views use height:100% and
   overflow:auto, which only constrain (and therefore scroll) when their
   ancestor chain has a real height. Without this the inner regions grow past
   the viewport and, because body is overflow:hidden, nothing scrolls. */
html, body, #app { height: 100%; }
#app { display: flex; flex-direction: column; }
body {
  margin: 0; font-family: var(--sans); color: var(--text);
  background:
    radial-gradient(1200px 700px at 82% -8%, rgba(91,140,255,0.10), transparent 60%),
    radial-gradient(1000px 620px at 8% 108%, rgba(139,143,245,0.10), transparent 60%),
    var(--bg);
  -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility;
  overflow: hidden;
}
a { color: var(--accent); text-decoration: none; }
button { font-family: inherit; }
::selection { background: rgba(91,140,255,0.3); }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb { background: var(--stroke-strong); border-radius: 20px; border: 2px solid transparent; background-clip: content-box; }
::-webkit-scrollbar-thumb:hover { background: var(--text-faint); background-clip: content-box; }

/* --- boot --- */
.app-loading { display: grid; place-items: center; height: 100%; }
.boot { text-align: center; }
.boot-mark { font-size: 54px; color: var(--accent); filter: drop-shadow(0 0 24px rgba(91,140,255,0.5)); animation: pulse 2s ease-in-out infinite; }
.boot-name { letter-spacing: 0.42em; font-weight: 700; margin: 14px 0 22px; font-size: 15px; color: var(--text-dim); padding-left: 0.42em; }
.boot-spin { width: 26px; height: 26px; border: 2px solid var(--stroke-strong); border-top-color: var(--accent); border-radius: 50%; margin: 0 auto; animation: spin 0.8s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
/* NEW DEFAULT loader: the animated CYPHER logomark (trace + pulse + breathe from
   .cmark). Sized up for a view/section loading state; centred in its host. */
.loader-mark { display: grid; place-items: center; margin: 26px auto; }
.loader-mark .cmark-loader { width: 46px; height: 46px; }
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.55; } }
/* LDRS-style loaders (uiball-inspired, self-contained, CSP-clean) — periwinkle-blue ring = the TOOL working,
   violet = an AGENT working (the Violet-Is-The-Agent rule). Reduced-motion → a calm pulse, no spin. */
.ldr { display: inline-block; vertical-align: middle; width: 18px; height: 18px; box-sizing: border-box; }
.ldr-ring { border-radius: 50%; border: 2.5px solid rgba(91,140,255,0.16); border-top-color: var(--accent);
  animation: ldrspin 0.7s cubic-bezier(0.45,0.15,0.35,0.9) infinite; filter: drop-shadow(0 0 5px rgba(91,140,255,0.35)); }
.ldr-ring.agent { border-color: rgba(139,143,245,0.16); border-top-color: var(--accent-2);
  filter: drop-shadow(0 0 6px rgba(139,143,245,0.45)); }
@keyframes ldrspin { to { transform: rotate(1turn); } }
@media (prefers-reduced-motion: reduce) { .ldr-ring { animation: pulse 1.4s ease-in-out infinite; } }
/* Live agent-activity pill + popover (Claude-Code-style). Periwinkle-blue, mono, tabular digits. */
.agent-activity { border: 1px solid var(--stroke-strong); background: rgba(91,140,255,0.08);
  color: var(--accent); border-radius: var(--radius-pill); padding: 5px 10px; font-size: 12px; font-weight: 600; cursor: pointer;
  font-family: var(--mono); letter-spacing: 0.3px; white-space: nowrap; font-variant-numeric: tabular-nums; }
.agent-activity.hide { display: none; }
.agent-activity:hover { border-color: var(--accent); background: rgba(91,140,255,0.15); }
.agent-pop { position: fixed; z-index: var(--z-popover); background: var(--panel-solid); border: 1px solid var(--stroke-strong);
  border-radius: 12px; padding: 12px 14px; box-shadow: var(--shadow); min-width: 250px; font-size: 12.5px; }
.ap-head { font-family: var(--mono); font-size: 10px; letter-spacing: 1.5px; color: var(--text-faint);
  margin-bottom: 9px; text-transform: uppercase; }
.ap-row { display: flex; align-items: center; gap: 9px; padding: 6px 0; border-top: 1px solid var(--stroke); }
.ap-row:first-of-type { border-top: none; }
.ap-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--accent); box-shadow: 0 0 8px var(--accent);
  flex: 0 0 auto; animation: pulse 1.2s ease-in-out infinite; }
.ap-name { flex: 1; color: var(--text); }
.ap-tok { font-family: var(--mono); color: var(--accent); font-variant-numeric: tabular-nums; }
.ap-el { font-family: var(--mono); color: var(--text-faint); font-size: 11px; min-width: 36px; text-align: right; }
.ap-empty { color: var(--text-dim); padding: 6px 0; }
.ap-foot { margin-top: 9px; font-size: 10.5px; color: var(--text-faint); line-height: 1.45; }
.material-bar { text-align: center; margin-top: 8px; font-family: var(--mono); font-size: 12px;
  letter-spacing: 0.5px; color: var(--text-dim); font-variant-numeric: tabular-nums; }
/* /distill: one attach popover replacing the 3 composer file buttons */
.attach-menu { position: fixed; z-index: var(--z-popover); background: var(--panel-solid); border: 1px solid var(--stroke-strong);
  border-radius: var(--radius); padding: 6px; box-shadow: var(--shadow); min-width: 244px; display: flex; flex-direction: column; gap: 2px;
  transform-origin: bottom left; animation: popMenuIn .16s var(--ease, cubic-bezier(.22,1,.36,1)); }
.am-item { display: flex; align-items: center; gap: 11px; padding: 9px 10px; border-radius: var(--radius-sm); border: none;
  background: transparent; color: var(--text); cursor: pointer; text-align: left; width: 100%;
  transition: background 0.12s var(--ease-out-quint, ease); }
.am-item:hover { background: rgba(91,140,255,0.09); }
.am-sect { font-size: 10px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase; color: var(--text-faint);
  padding: 8px 10px 3px; margin-top: 2px; border-top: 1px solid var(--stroke); }
.am-sect:first-child { border-top: none; margin-top: 0; }
.am-empty { padding: 4px 10px 8px; font-size: var(--fs-label); color: var(--text-faint); }
.am-ic { font-size: 17px; flex: 0 0 auto; }
.am-txt { display: flex; flex-direction: column; gap: 1px; }
.am-lbl { font-size: 13.5px; font-weight: 600; }
.am-sub { font-size: 11px; color: var(--text-dim); }
/* Texture output-mode picker (Albedo vs Full PBR) — segmented, with a live summary */
.texmode-card { border: 1px solid var(--stroke); border-radius: var(--radius); background: var(--bg-2); padding: 10px 12px; margin-top: 10px; }
.texmode-lbl { font-family: var(--mono); font-size: 10px; letter-spacing: 1.5px; text-transform: uppercase; color: var(--text-faint); margin-bottom: 8px; }
.texmode-seg { display: flex; gap: 6px; }
.texmode { flex: 1; display: flex; flex-direction: column; gap: 2px; align-items: flex-start; padding: 9px 11px;
  border-radius: var(--radius-sm); border: 1px solid var(--stroke); background: var(--panel-solid); color: var(--text-dim);
  cursor: pointer; transition: border-color var(--dur) var(--ease-out-quint), background var(--dur) var(--ease-out-quint); }
.texmode:hover { border-color: var(--accent); }
.texmode.on { border-color: var(--accent); background: rgba(91,140,255,0.08); color: var(--text); }
.texmode-t { font-size: 13px; font-weight: 700; }
.texmode-s { font-size: 10.5px; color: var(--text-faint); }
.texmode-sum { font-size: 11.5px; color: var(--text-dim); margin-top: 8px; line-height: 1.4; }

/* --- generic --- */
.btn {
  display: inline-flex; align-items: center; gap: 8px; justify-content: center;
  padding: 9px 15px; border-radius: var(--radius-sm); border: 1px solid var(--stroke-strong);
  background: var(--panel-2); color: var(--text); cursor: pointer; font-size: 13px; font-weight: 550;
  transition: border-color .15s, background .15s, transform .05s; white-space: nowrap;
}
.btn:hover { border-color: var(--accent); background: rgba(91,140,255,0.08); }
.btn:active { transform: translateY(1px); }
.btn.primary { background: var(--accent); color: var(--accent-ink); border-color: transparent; font-weight: 650; }
.btn.primary:hover { filter: brightness(1.08); }
.btn.ghost { background: transparent; border-color: transparent; color: var(--text-dim); }
.btn.ghost:hover { color: var(--text); background: var(--panel-2); }
.btn.danger:hover { border-color: var(--bad); background: rgba(242,104,122,0.1); color: var(--bad); }
/* Filled rose primary — the confirm button on destructive confirmModal() dialogs. */
.btn.danger-solid { background: var(--bad); color: #2a0810; border-color: transparent; font-weight: 650; }
.btn.danger-solid:hover { filter: brightness(1.06); }
.btn.sm { padding: 5px 10px; font-size: 12px; border-radius: 7px; }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
.btn.icon { padding: 8px; width: 34px; height: 34px; }

input, textarea, select {
  width: 100%; padding: 10px 12px; border-radius: var(--radius-sm);
  border: 1px solid var(--stroke-strong); background: rgba(0,0,0,0.28); color: var(--text);
  font-family: inherit; font-size: 14px; outline: none; transition: border-color .15s, box-shadow .15s;
}
input:focus, textarea:focus, select:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(91,140,255,0.14); }
textarea { resize: vertical; }
label.field { display: block; margin-bottom: 14px; }
label.field > span { display: block; font-size: 12px; color: var(--text-dim); margin-bottom: 6px; letter-spacing: .02em; }

.pill { display: inline-flex; align-items: center; gap: 6px; padding: 3px 9px; border-radius: var(--radius-pill); font-size: 11px; font-weight: 600; border: 1px solid var(--stroke); background: var(--panel-2); color: var(--text-dim); }
.pill.good { color: var(--good); border-color: rgba(63,191,135,0.3); }
.pill.bad { color: var(--bad); border-color: rgba(242,104,122,0.3); }
.pill.warn { color: var(--warn); border-color: rgba(240,180,41,0.3); }
.dot { width: 7px; height: 7px; border-radius: 50%; background: var(--text-faint); }
.dot.good { background: var(--good); box-shadow: 0 0 8px var(--good); }
.dot.bad { background: var(--bad); }
.dot.warn { background: var(--warn); }

/* PERF: .card is used across many views; its backdrop-filter was doing almost nothing (most cards
   sit over the solid app bg, not the ground) but still forced a blur layer. Dropped — the inner
   top-highlight in the polish block gives it the same lit-glass read for free. */
.card { background: var(--panel-solid); border: 1px solid var(--stroke); border-radius: var(--radius); }
.muted { color: var(--text-dim); }
.faint { color: var(--text-faint); }
.mono { font-family: var(--mono); }
.spacer { flex: 1; }
.row { display: flex; align-items: center; gap: 10px; }
.hide { display: none !important; }

/* --- toast --- */
#toasts { position: fixed; bottom: 20px; right: 20px; display: flex; flex-direction: column; gap: 10px; z-index: var(--z-toast); }
.toast { display: flex; align-items: flex-start; gap: 9px; padding: var(--space-md) var(--space-lg); border-radius: var(--radius); border: 1px solid var(--stroke-strong); background: var(--panel-solid); box-shadow: var(--shadow); font-size: 13px; max-width: 340px; animation: toastin .2s ease; }
.toast.bad { border-color: var(--bad); }
.toast.good { border-color: var(--good); }
.toast.warn { border-color: var(--warn); }
.toast .toast-ic { flex: 0 0 auto; margin-top: 1px; }
.toast.good .toast-ic { color: var(--good); }
.toast.bad .toast-ic { color: var(--bad); }
.toast.warn .toast-ic { color: var(--warn); }
.toast .toast-msg { min-width: 0; }

/* Atmos inline icon glyphs — one monochrome SVG set, always inherits currentColor
   (so it greys/tints with its text; emoji couldn't). Base ~16px; .i-sm for chips/meta. */
.i-svg { width: 16px; height: 16px; flex: 0 0 auto; vertical-align: -0.18em; }
.i-svg.i-sm { width: 13px; height: 13px; vertical-align: -0.15em; }
@keyframes toastin { from { opacity: 0; transform: translateY(8px); } }

/* --- auth screen --- */
.auth-wrap { display: grid; place-items: center; height: 100%; padding: 20px; overflow-y: auto; }
.auth-card { width: 100%; max-width: 400px; padding: 34px 30px; }
.auth-brand { display: flex; align-items: center; gap: 12px; margin-bottom: 6px; }
.auth-brand .mark { font-size: 30px; color: var(--accent); filter: drop-shadow(0 0 16px rgba(91,140,255,0.45)); }
.auth-brand .name { font-weight: 700; letter-spacing: 0.34em; font-size: 20px; padding-left: 0.34em; }
.auth-status { text-align: center; font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.24em;
  color: var(--text-faint); margin: 2px 0 12px; opacity: 0; transform: translateY(-2px);
  transition: opacity 0.5s ease, transform 0.5s ease; }
.auth-status.in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) { .auth-status { transition: none; } }
.auth-sub { color: var(--text-dim); font-size: 13px; margin: 0 0 24px; }
.auth-tabs { display: flex; gap: 4px; margin-bottom: var(--space-xl); background: rgba(0,0,0,0.25); padding: 4px; border-radius: var(--radius); }
.auth-tabs button { flex: 1; padding: 8px; border: none; background: transparent; color: var(--text-dim); border-radius: var(--radius-sm); cursor: pointer; font-size: 13px; font-weight: 600; }
.auth-tabs button:hover:not(.on) { color: var(--text); }
/* Selected state on the one control that decides which request the form fires: panel-on-panel
   was ~1.2:1. One accent, state only — no gradient fill. */
.auth-tabs button.on { background: rgba(91,140,255,.16); color: var(--text); box-shadow: inset 0 0 0 1px rgba(91,140,255,.34); }
.auth-note { font-size: 12px; color: var(--text-faint); margin-top: 16px; line-height: 1.5; }
.auth-err { color: var(--bad); font-size: 13px; margin: -6px 0 var(--space-md); min-height: 18px; }
/* Not colour alone — a glyph rides along whenever there's actually a message. */
.auth-err:not(:empty)::before { content: "⚠"; margin-right: 6px; }
.secure-badge { display: inline-flex; align-items: center; gap: 6px; font-size: 11px; color: var(--text-faint); margin-top: 18px; }

/* --- app shell --- */
.shell { display: grid; grid-template-columns: 250px 1fr; height: 100%; }
.sidebar { background: linear-gradient(180deg, var(--bg-2), var(--bg)); border-right: 1px solid var(--stroke); display: flex; flex-direction: column; min-height: 0; }
.side-brand { display: flex; align-items: center; gap: 12px; padding: 16px; border-bottom: 1px solid var(--stroke); }
.side-brand .mark { font-size: 20px; color: var(--accent); }
.side-brand .name { font-weight: 700; letter-spacing: 0.28em; font-size: 14px; padding-left: 0.28em; }
.side-nav { padding: 10px; display: flex; flex-direction: column; gap: 2px; }
.nav-item { display: flex; align-items: center; gap: var(--space-md); padding: var(--space-sm) var(--space-md); border-radius: var(--radius-sm); color: var(--text-dim); cursor: pointer; font-size: 13.5px; font-weight: 500; border: 1px solid transparent; }
.nav-item:hover { background: var(--panel-2); color: var(--text); }
.nav-item.on { background: rgba(91,140,255,0.10); color: var(--text); border-color: rgba(91,140,255,0.22); }
.nav-item .ic { width: 18px; text-align: center; opacity: .9; }
.side-section { padding: var(--space-lg) var(--space-lg) var(--space-sm); font-size: 11px; letter-spacing: .14em; text-transform: uppercase; color: var(--text-faint); display: flex; align-items: center; justify-content: space-between; }
.proj-list { flex: 1; overflow-y: auto; padding: 0 10px 10px; display: flex; flex-direction: column; gap: 2px; min-height: 0; }
.proj-item { padding: 8px 12px; border-radius: var(--radius-sm); cursor: pointer; border: 1px solid transparent; }
.proj-item:hover { background: var(--panel-2); }
.proj-item.on { background: var(--panel-2); border-color: var(--stroke-strong); }
.proj-item .pname { font-size: 13.5px; font-weight: 600; display: flex; align-items: center; gap: 7px; }
.proj-item .pmeta { font-size: 11px; color: var(--text-faint); margin-top: 3px; display: flex; gap: 8px; }
.proj-del { border: none; background: transparent; color: var(--text-faint); cursor: pointer; font-size: 12px; padding: 2px 5px; border-radius: 6px; opacity: 0; transition: opacity .15s, background .15s, color .15s; flex: none; }
.proj-item:hover .proj-del { opacity: 1; }
.proj-del:hover { background: rgba(242,104,122,.15); color: var(--bad); }
.msg-del { border: none; background: transparent; color: var(--text-faint); cursor: pointer; font-size: 11px; padding: 0 5px; border-radius: 5px; opacity: 0; transition: opacity .15s, color .15s; }
.msg-del { margin-left: 0; }
.msg:hover .msg-del, .msg:focus-within .msg-del { opacity: .8; }
.msg-del:hover, .msg-del:focus-visible { color: var(--bad); opacity: 1; }
.msg-act { border: none; background: transparent; color: var(--text-faint); cursor: pointer; font-size: 12px; padding: 0 5px; border-radius: 5px; opacity: 0; transition: opacity .15s, color .15s; }
.msg-act:first-of-type { margin-left: auto; }
.msg:hover .msg-act, .msg:focus-within .msg-act { opacity: .7; }
.msg-act:hover, .msg-act:focus-visible { color: var(--accent); opacity: 1; }
/* Touch has no :hover — gating Regenerate / Edit / Delete / "Resume" on it hid
   them entirely on a phone. On coarse pointers they rest visible-but-quiet. */
@media (hover: none) {
  .msg-act, .msg-del { opacity: .55; }
}
.msg .meta { width: 100%; }
.side-foot { border-top: 1px solid var(--stroke); padding: 10px; display: flex; align-items: center; gap: 10px; }
.avatar { width: 30px; height: 30px; border-radius: 8px; background: var(--accent); color: var(--accent-ink); display: grid; place-items: center; font-weight: 700; font-size: 13px; flex: none; }

.main { min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
.view { flex: 1; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }

/* --- chat view --- */
.chat-cols { display: grid; grid-template-columns: 1fr 300px; height: 100%; min-height: 0; }
.chat-main { display: flex; flex-direction: column; min-height: 0; }
.chat-head { padding: 12px 20px; border-bottom: 1px solid var(--stroke); display: flex; align-items: center; gap: 12px; background: var(--panel); }
.chat-head-left, .chat-head-right { flex: 1 1 0; display: flex; align-items: center; gap: 8px; min-width: 0; }
.chat-head-right { justify-content: flex-end; }
.chat-head-center { flex: 0 1 auto; text-align: center; min-width: 0; }
.chat-title { font-weight: 650; font-size: 15px; display: flex; align-items: center; gap: 10px; min-width: 0; }
.chat-title-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chat-sub { font-size: 12px; color: var(--text-faint); margin-top: 1px; }

/* Model dropdown (header, top-right) */
.model-dd { position: relative; }
.model-dd-btn { display: inline-flex; align-items: center; gap: 6px; max-width: 240px; padding: 6px 10px; border: 1px solid var(--stroke); border-radius: var(--radius-sm); background: var(--panel-2); color: var(--text); font-size: var(--fs-sm); cursor: pointer; transition: border-color var(--dur), background var(--dur); }
.model-dd-btn:hover { border-color: var(--accent); }
.model-dd-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 190px; }
.model-dd-caret { color: var(--text-faint); font-size: 10px; }
.model-dd-panel { position: absolute; top: calc(100% + 6px); right: 0; width: 300px; max-width: 78vw; background: var(--panel); border: 1px solid var(--stroke); border-radius: 12px; box-shadow: 0 18px 44px -14px rgba(0,0,0,.6); z-index: var(--z-sticky); padding: 8px; }
.model-dd-search { width: 100%; box-sizing: border-box; padding: 8px 10px; border: 1px solid var(--stroke); border-radius: 8px; background: var(--panel-2); color: var(--text); font-size: 13px; margin-bottom: 6px; }
.model-dd-list { max-height: 320px; overflow-y: auto; scrollbar-width: thin; }
.model-dd-row { display: flex; flex-direction: column; gap: 2px; padding: 8px 10px; border-radius: 8px; cursor: pointer; }
.model-dd-row:hover { background: var(--panel-2); }
.model-dd-row.on { background: color-mix(in srgb, var(--accent) 16%, transparent); }
.mdr-nm { font-size: 13px; font-weight: 550; }
.mdr-meta { font-size: 10.5px; color: var(--text-faint); font-family: var(--mono); }
.tps-tag { font-size: 10px; color: var(--text-faint); font-family: var(--mono); border: 1px solid var(--stroke); padding: 0 6px; border-radius: 5px; }

/* ── Chat-header control cluster ──────────────────────────────────────────────
   One shape / size / state for the header's secondary controls (effort, learning)
   so the top bar reads as a single cockpit rather than a row of mismatched boxes.
   They mirror the model picker's shape (radius-sm rect); a hairline divider sets
   the primary model picker apart from these quieter secondary toggles. */
.hdr-div { flex: none; align-self: stretch; width: 1px; margin: 5px 2px; background: var(--stroke); }
.hdr-ctl {
  margin: 0; padding: 6px 10px; border-radius: var(--radius-sm); border: 1px solid var(--stroke);
  background: var(--panel-2); color: var(--text-dim); font-size: 12px; font-weight: 600; cursor: pointer;
  white-space: nowrap; transition: border-color .15s var(--ease-out-quint), background .15s, color .15s, box-shadow .15s;
}
.hdr-ctl:hover { border-color: var(--accent); color: var(--text); }
.hdr-ctl.on { color: var(--text); border-color: var(--accent); background: rgba(91,140,255,.12); box-shadow: 0 0 0 1px rgba(91,140,255,.14); }
.hdr-ctl.on.learn { color: var(--text); border-color: var(--good); background: rgba(63,191,135,.12); box-shadow: 0 0 0 1px rgba(63,191,135,.16); }

/* Tool-produced file: a download chip under the reply */
.msg-files { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
.msg-files:empty { display: none; }
.file-chip { display: inline-flex; align-items: center; gap: var(--space-sm); max-width: 380px; padding: var(--space-sm) var(--space-md); border: 1px solid var(--stroke); border-radius: var(--radius-sm); background: var(--panel-2); color: var(--text); text-decoration: none; font-size: 13px; transition: border-color var(--dur), background var(--dur); }
.file-chip:hover { border-color: var(--accent); background: var(--panel); }
.file-chip .fc-ic { font-size: 15px; }
.file-chip .fc-name { font-family: var(--mono); font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; min-width: 0; }
.file-chip .fc-size { color: var(--text-faint); font-size: 11px; }
.file-chip .fc-dl { color: var(--accent); font-size: 11.5px; font-weight: 600; white-space: nowrap; }
/* "Download all N files" chip under a reply that produced several files */
.file-chip.zip-all { cursor: pointer; border-style: dashed; background: transparent; width: 100%; text-align: left; }
.file-chip.zip-all:hover { border-style: solid; border-color: var(--accent); background: var(--panel-2); }

/* Messages typed while Cypher is mid-reply — queued, not fired concurrently */
.queue-strip { display: flex; flex-direction: column; gap: 4px; margin: 6px 0 0; }
.queued-msg { display: flex; align-items: center; gap: 8px; padding: 5px 10px; border: 1px dashed var(--stroke); border-radius: 8px; background: rgba(139,143,245,.06); font-size: 12px; color: var(--text-dim); }
.queued-msg .qm-ic { font-size: 11px; opacity: .8; }
.queued-msg .qm-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.queued-msg .qm-x { border: none; background: transparent; color: var(--text-faint); cursor: pointer; font-size: 11px; padding: 0 4px; border-radius: 5px; }
.queued-msg .qm-x:hover { color: var(--bad); }

/* Artifact chip (under a reply) */
.msg-artifacts { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
.msg-artifacts:empty { display: none; }
.artifact-chip { display: flex; align-items: center; gap: 10px; max-width: 420px; padding: 10px 12px; border: 1px solid var(--stroke); border-radius: var(--radius); background: linear-gradient(135deg, var(--panel-2), var(--panel)); color: var(--text); cursor: pointer; text-align: left; transition: border-color .15s, transform .1s; }
.artifact-chip:hover { border-color: var(--accent); transform: translateY(-1px); }
.artifact-chip .ac-ic { font-size: 18px; }
.artifact-chip .ac-body { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.artifact-chip .ac-title { font-size: 13px; font-weight: 650; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.artifact-chip .ac-kind { font-size: 11px; color: var(--text-faint); font-family: var(--mono); }
.artifact-chip .ac-open { color: var(--accent); font-size: 12px; font-weight: 600; white-space: nowrap; }

/* Artifact drawer (right side) */
.artifact-drawer { position: fixed; top: 0; right: 0; height: 100vh; width: min(680px, 94vw); background: var(--panel); border-left: 1px solid var(--stroke); box-shadow: -20px 0 60px -20px rgba(0,0,0,.6); z-index: var(--z-drawer); display: flex; flex-direction: column; animation: adIn .22s cubic-bezier(.2,.8,.3,1); }
/* Drag-to-resize gutter on the drawer's left edge. */
.artifact-drawer .ad-resize { position: absolute; left: 0; top: 0; bottom: 0; width: 7px; cursor: ew-resize; z-index: 2; transition: background .12s; }
.artifact-drawer .ad-resize:hover, .artifact-drawer .ad-resize:active { background: var(--accent); opacity: .55; }
@keyframes adIn { from { transform: translateX(30px); opacity: .3; } to { transform: none; opacity: 1; } }
.ad-head { display: flex; align-items: center; gap: 10px; padding: 12px 14px; border-bottom: 1px solid var(--stroke); }
.ad-head .ad-ic { font-size: 20px; }
.ad-head .ad-title { font-size: 14.5px; font-weight: 650; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ad-head .ad-sub { font-size: 11px; color: var(--text-faint); font-family: var(--mono); }
.ad-ver { padding: 5px 8px; border: 1px solid var(--stroke); border-radius: 7px; background: var(--panel-2); color: var(--text); font-size: 12px; }
.ad-tabs { display: flex; gap: 6px; padding: 8px 14px; border-bottom: 1px solid var(--stroke); }
.ad-tab { padding: 5px 14px; border: 1px solid var(--stroke); border-radius: 8px; background: transparent; color: var(--text-dim); cursor: pointer; font-size: 12.5px; }
.ad-tab.on { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); font-weight: 600; }
.ad-content { flex: 1; overflow: auto; min-height: 0; position: relative; }
/* Firefox resolves height:100% inside a flex item to 0 (Chrome doesn't) -> the iframe
   collapsed to nothing = blank preview in Firefox. Absolute inset:0 sizes reliably in
   both browsers because it resolves against the positioned parent's real (flex) height. */
.ad-frame { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; background: #fff; }
.ad-code { margin: 0; padding: 18px 20px; font-size: 13px; line-height: 1.7; overflow: auto; height: 100%; box-sizing: border-box; white-space: pre; background: linear-gradient(180deg, #0d1117, #0a0d13); color: #e6edf3; font-family: var(--mono); tab-size: 2; }
.ad-code code { font-family: inherit; }
/* Syntax tokens (works on the #0d1117 editor background) */
.tok-com { color: #7a8699; font-style: italic; }
.tok-str { color: #7ee787; }
.tok-num { color: #f0a868; }
.tok-kw  { color: #ff7b9c; font-weight: 600; }
.code-copy { z-index: 2; }
.ad-md { padding: 16px 20px; }
.ad-foot { display: flex; gap: 8px; padding: 10px 14px; border-top: 1px solid var(--stroke); }
@media (max-width: 640px) { .artifact-drawer { width: 100vw; } }
.proj-edit { border: none; background: transparent; color: var(--text-faint); cursor: pointer; font-size: 11px; padding: 2px 5px; border-radius: 6px; opacity: 0; transition: opacity .15s, background .15s, color .15s; flex: none; }
.proj-item:hover .proj-edit { opacity: 1; }
.proj-edit:hover { background: rgba(255,255,255,.08); color: var(--accent); }
.presence { display: flex; align-items: center; }
.presence .pdot { width: 26px; height: 26px; border-radius: 50%; border: 2px solid var(--bg-2); background: var(--accent); color:var(--accent-ink); display: grid; place-items: center; font-size: 11px; font-weight: 700; margin-left: -6px; }

.messages { flex: 1; overflow-y: auto; padding: 20px 20px 8px; display: flex; flex-direction: column; gap: 20px; }
.msg { display: flex; gap: 12px; max-width: 860px; animation: msgin .18s ease; }
@keyframes msgin { from { opacity: 0; transform: translateY(6px); } }
.msg .who { flex: none; }
.msg .who .av { width: 32px; height: 32px; border-radius: var(--radius-sm); display: grid; place-items: center; font-weight: 700; font-size: 14px; }
.msg.user .av { background: var(--accent); color: var(--accent-ink); }
.msg.agent .av { background: var(--accent-2); color: var(--accent-ink); box-shadow: 0 0 18px -4px rgba(139,143,245,.5); }
.msg.system .av { background: var(--panel-2); color: var(--text-faint); }
.msg .body { min-width: 0; }
/* Speaker separation without inventing right-aligned bubbles: your own turn gets a
   barely-there recessed tint and a periwinkle edge marker, so a long back-and-forth
   splits at a glance instead of relying on the 32px avatar column. The negative
   margin cancels the padding, so text stays aligned with agent turns. */
.msg.user .body { padding: 6px 11px; margin: -6px -11px; border-radius: var(--radius-sm);
  background: rgba(255,255,255,.022); box-shadow: inset 2px 0 0 color-mix(in srgb, var(--accent) 45%, transparent); }
.msg .meta { display: flex; align-items: baseline; gap: 9px; margin-bottom: 4px; }
.msg .author { font-weight: 650; font-size: 13.5px; }
.msg.agent .author { color: var(--accent); }
.msg .time { font-size: 11px; color: var(--text-faint); }
.msg .model-tag { font-size: 10px; color: var(--text-faint); font-family: var(--mono); border: 1px solid var(--stroke); padding: 0 6px; border-radius: 5px; }
.msg .skill-tag { font-size: 10px; color: var(--accent); font-family: var(--mono); border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--stroke)); background: color-mix(in srgb, var(--accent) 10%, transparent); padding: 0 6px; border-radius: 5px; white-space: nowrap; }
.bubble { font-size: var(--fs-body); line-height: 1.62; color: var(--text); word-wrap: break-word; }
.bubble p { margin: 0 0 10px; } .bubble p:last-child { margin-bottom: 0; }
.bubble pre { background: linear-gradient(180deg, #0d1117, #0a0d13); color: #e6edf3; border: 1px solid var(--stroke-strong, var(--stroke)); border-radius: 12px; padding: 14px 16px; overflow-x: auto; font-family: var(--mono); font-size: 12.5px; line-height: 1.6; margin: 10px 0; box-shadow: inset 0 1px 0 rgba(255,255,255,.03), 0 6px 18px -12px rgba(0,0,0,.7); tab-size: 2; }
.bubble pre code { color: inherit; }
:root[data-theme="light"] .bubble pre, :root[data-theme="light"] .ad-code { background: #0d1117; color: #e6edf3; }
.bubble code { font-family: var(--mono); font-size: 0.9em; background: rgba(255,255,255,0.07); padding: 1px 5px; border-radius: 5px; }
.bubble pre code { background: none; padding: 0; }
.bubble ul, .bubble ol { margin: 6px 0; padding-left: 22px; }
.bubble a { text-decoration: underline; }
.bubble img.media { max-width: 340px; border-radius: 10px; margin: 8px 0; border: 1px solid var(--stroke); display: block; }
.bubble pre.has-copy { position: relative; }
.code-copy { position: absolute; top: 6px; right: 6px; font-size: 11px; padding: 3px 8px; border-radius: 6px; border: 1px solid var(--stroke-strong); background: rgba(0,0,0,0.55); color: var(--text-dim); cursor: pointer; opacity: 0; transition: opacity .15s, color .15s, border-color .15s; }
.bubble pre:hover .code-copy { opacity: 1; }
.code-copy:hover { color: var(--text); border-color: var(--accent); }
/* Inline mermaid diagrams (rendered once from a ```mermaid fence). The Atmos-glass
   card frames the periwinkle SVG; wide graphs scroll inside their own box so the
   message column never overflows the viewport. */
.mmd-render { margin: 12px 0; padding: 12px; display: flex; justify-content: center; overflow-x: auto;
  border: 1px solid var(--stroke); border-radius: 12px; background: rgba(91,140,255,.04);
  animation: atmosModIn .5s var(--ease, cubic-bezier(.22,1,.36,1)) backwards; }
.mmd-render svg { max-width: 100%; height: auto; }
.mmd-render.mmd-err { display: block; padding: 0; border: 0; background: none; }
.mmd-fallback { margin: 0; background: linear-gradient(180deg, #0d1117, #0a0d13); color: #e6edf3;
  border: 1px solid var(--stroke); border-radius: 12px; padding: 14px 16px; overflow-x: auto;
  font-family: var(--mono); font-size: 12.5px; line-height: 1.6; white-space: pre; }
@media (prefers-reduced-motion: reduce) { .mmd-render { animation: none; } }
.think { margin: 4px 0 8px; border: 1px solid var(--stroke); border-radius: var(--radius-sm); background: rgba(139,143,245,.04); overflow: hidden; max-width: 640px; }
.think-head { display: flex; align-items: center; gap: 8px; padding: 7px 11px; cursor: pointer; font-size: 12px; color: var(--text-dim); user-select: none; }
.think-head:hover { color: var(--text); }
.think-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--accent-2); box-shadow: 0 0 8px var(--accent-2); animation: pulse 1.6s ease-in-out infinite; }
.think-caret { margin-left: auto; transition: transform .15s var(--ease-out-quint); }
.think.open .think-caret { transform: rotate(90deg); }
.think-body { display: none; padding: 0 12px 10px; font-size: 12px; line-height: 1.55; color: var(--text-dim); white-space: pre-wrap; font-family: var(--mono); max-height: 280px; overflow-y: auto; }
.think.open .think-body { display: block; }
.think-body:empty { display: none; }
.think-write { display: none; margin: 0 10px 10px; padding: 12px 14px; font-size: 12px; line-height: 1.55; font-family: var(--mono); color: #e6edf3; background: linear-gradient(180deg,#0d1117,#0a0d13); border: 1px solid var(--stroke); border-radius: 8px; white-space: pre; overflow: auto; max-height: 340px; }
.think.open .think-write { display: block; }
.think.done .think-dot { animation: none; opacity: .5; }
.think-label { font-style: italic; }
.msg-steps { margin: 2px 0 8px; }
.msg-steps.hide { display: none; }
.steps-wrap { border: 1px solid var(--stroke); border-radius: 10px; background: var(--panel-2, rgba(255,255,255,.02)); overflow: hidden; }
.steps-sum { cursor: pointer; padding: 7px 11px; font-size: 12px; font-weight: 650; color: var(--accent); list-style: none; user-select: none; }
.steps-sum::-webkit-details-marker { display: none; }
.steps-sum::before { content: '▸ '; color: var(--text-faint); }
.steps-wrap[open] .steps-sum::before { content: '▾ '; }
.steps-body { padding: 4px 10px 8px; display: flex; flex-direction: column; gap: 5px; }
.stp { font-size: 12px; }
.stp-head { display: flex; align-items: baseline; gap: 8px; }
.stp-ic { flex: 0 0 auto; }
.stp-label { color: var(--text); font-family: var(--mono); word-break: break-word; }
.stp-status { margin-left: auto; flex: 0 0 auto; font-size: 11px; color: var(--text-faint); font-family: var(--mono); }
.stp-status.bad { color: var(--bad); }
.stp-det { margin: 3px 0 2px 24px; }
.stp-det > summary { cursor: pointer; font-size: 11px; color: var(--text-dim); }
.stp-det pre { margin: 5px 0 0; padding: 8px 10px; background: #0e1219; border: 1px solid var(--stroke); border-radius: 8px; font-family: var(--mono); font-size: 11.5px; color: #d3ddef; white-space: pre-wrap; word-break: break-word; max-height: 260px; overflow: auto; }
.activity { margin: 2px 0 6px; font-size: 12px; color: var(--accent); display: flex; align-items: center; gap: 7px; }
.activity::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: var(--accent); box-shadow: 0 0 8px var(--accent); animation: pulse 1.2s ease-in-out infinite; }
.msg-media { display: flex; flex-wrap: wrap; gap: 8px; }
.msg-media:not(:empty) { margin-top: 8px; }
.msg-media .media { max-width: 340px; }
.cursor { display: inline-block; width: 8px; height: 15px; background: var(--accent); margin-left: 2px; vertical-align: text-bottom; animation: blink 1s steps(2) infinite; border-radius: 1px; }
/* The streaming "typing" indicator: the animated CYPHER hexagon (same mark as the top-left logo),
   inline at the tail of the text, replacing the old terminal-style blinking bar. */
.cmark-stream { width: 1.15em; height: 1.15em; margin-left: 4px; vertical-align: -0.24em; }
/* Word-reveal typing caret: a slim periwinkle bar at the leading edge, glow not shadow. */
.cursor-type { width: 2px; height: 1.05em; margin-left: 1px; border-radius: 2px; box-shadow: 0 0 6px var(--accent); animation: caretBlink 1.05s ease-out infinite; }
@keyframes blink { 50% { opacity: 0; } }
@keyframes caretBlink { 0%, 45% { opacity: 1; } 55%, 100% { opacity: .15; } }
/* Reduced motion: reveal is instant and the caret sits still (no blink). */
@media (prefers-reduced-motion: reduce) { .cursor, .cursor-type { animation: none; } }
.typing-line { padding: 0 20px 6px; font-size: 12px; color: var(--text-faint); height: 18px; }

.composer { border-top: 1px solid var(--stroke); padding: var(--space-md) var(--space-lg); background: var(--panel); }
/* N7 — password eye-reveal + generator. */
.pw-eye-wrap { position: relative; display: block; }
.pw-eye-wrap input { width: 100%; padding-right: 42px; box-sizing: border-box; }
.eye-btn { position: absolute; right: 4px; top: 50%; transform: translateY(-50%); display: inline-flex;
  align-items: center; justify-content: center; width: 34px; height: 32px; border: 0; background: transparent;
  color: var(--dim, #9aa3b6); cursor: pointer; border-radius: 8px; }
.eye-btn:hover { color: var(--text); background: color-mix(in oklab, var(--accent) 12%, transparent); }
.eye-btn.on { color: var(--accent); }
.pw-gen { border: 1px solid var(--stroke); border-radius: var(--radius, 12px); padding: 12px 13px; margin: 4px 0 12px;
  background: var(--bg-2, rgba(255,255,255,.02)); display: flex; flex-direction: column; gap: 10px; }
.pw-gen .pg-row { display: flex; align-items: center; gap: 10px; }
.pw-gen .pg-lbl { font-size: 12px; color: var(--dim); min-width: 46px; }
.pw-gen .pg-len { font: 12.5px ui-monospace, monospace; color: var(--text); min-width: 22px; text-align: right; }
.pw-gen .pg-opts { display: flex; flex-wrap: wrap; gap: 6px 14px; }
.pw-gen .pg-opt { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; color: var(--dim); cursor: pointer; }
.pw-gen .pg-meter { height: 6px; border-radius: 4px; background: color-mix(in oklab, var(--stroke) 60%, transparent); overflow: hidden; }
.pw-gen .pg-meter i { display: block; height: 100%; width: 0; transition: width .2s, background .2s; }
.pw-gen .pg-meter.s0 i { width: 12%; background: #ff6b6b; }
.pw-gen .pg-meter.s1 i { width: 33%; background: #ff6b6b; }
.pw-gen .pg-meter.s2 i { width: 55%; background: #f0b429; }
.pw-gen .pg-meter.s3 i { width: 78%; background: #7aa2ff; }
.pw-gen .pg-meter.s4 i { width: 100%; background: #3fbf87; }
.pw-gen .pg-actions { display: flex; gap: 8px; }
/* N6/N1 — chat-lock banner shown above the composer when a user loses chat / gated-model access. */
.chat-lock-banner { display: flex; align-items: center; gap: 10px; margin: 0 0 10px; padding: 10px 13px;
  border: 1px solid color-mix(in oklab, #ff6b6b 45%, var(--stroke)); border-radius: 11px;
  background: color-mix(in oklab, #ff6b6b 12%, var(--panel)); color: var(--text); font-size: 13px; }
.chat-lock-banner .clb-msg { flex: 1; line-height: 1.4; }
.chat-lock-banner svg { color: #ff6b6b; flex: none; }
.chat-lock-banner .btn { flex: none; }
.composer-bar { display: flex; gap: 8px; margin: 2px 0 11px; align-items: center; flex-wrap: wrap; }
.composer-row { display: flex; gap: 10px; align-items: flex-end; }
.composer textarea { min-height: 48px; max-height: 200px; border-radius: 12px; }
.mini-select { display: inline-flex; align-items: center; gap: 6px; padding: 5px 10px; border-radius: 8px; border: 1px solid var(--stroke-strong); background: var(--panel-2); font-size: 12px; color: var(--text-dim); cursor: pointer; }
.mini-select:hover { border-color: var(--accent); color: var(--text); }
.mini-select select { width: auto; border: none; background: none; padding: 0; font-size: 12px; color: var(--text); cursor: pointer; }
.cbar-label { font-size: 10px; text-transform: uppercase; letter-spacing: .12em; color: var(--text-faint); padding-right: 2px; flex: none; }
.model-chips { display: flex; gap: 6px; overflow-x: auto; padding-bottom: 3px; flex: 1; scrollbar-width: thin; }
.model-chips::-webkit-scrollbar { height: 5px; }
.model-chip { display: inline-flex; align-items: center; gap: 6px; padding: 5px 11px; border-radius: var(--radius-pill); border: 1px solid var(--stroke-strong); background: var(--panel-2); font-size: 12px; color: var(--text-dim); cursor: pointer; white-space: nowrap; transition: border-color .15s, color .15s, background .15s; }
.model-chip:hover { color: var(--text); }
.model-chip.on { border-color: var(--accent); background: rgba(91,140,255,.12); color: var(--text); box-shadow: 0 0 0 1px rgba(91,140,255,.14); }
.model-chip.vis.on { border-color: var(--accent-2); background: rgba(139,143,245,.14); box-shadow: 0 0 0 1px rgba(139,143,245,.16); }
.model-chip .mc-nm { font-family: var(--mono); font-size: 11.5px; }
.model-chip .mc-eye { font-size: 11px; }
.model-chip .mc-tier { font-size: 10px; opacity: .7; }

/* ── Composer control strip: model picker + effort pill (both drop UP) ─────── */
/* A group separator, not a full-bleed rule — centred and fixed so it can't stretch
   to the tallest neighbour and overhang the gauge at both ends. */
.cbar-div { width: 1px; align-self: center; height: 18px; margin: 0 2px; background: var(--stroke); flex: none; }
.model-picker, .effort-pill { flex: none; }
/* Every control on the strip sits on the same rail: one height, vertical padding
   surrendered to it, horizontal padding left to each control. */
.composer-bar .mp-btn, .composer-bar .ep-btn, .composer-bar .plan-toggle,
.composer-bar .agent-activity, .composer-bar .tok-gauge {
  height: var(--ctl-h); box-sizing: border-box;
  display: inline-flex; align-items: center;
  padding-top: 0; padding-bottom: 0;
}
.composer-bar .agent-activity.hide, .composer-bar .tok-gauge.hide, .composer-bar .plan-toggle.hide { display: none; }
.mp-btn, .ep-btn {
  display: inline-flex; align-items: center; gap: 7px; padding: 6px 11px;
  border-radius: var(--radius-pill); border: 1px solid var(--stroke-strong);
  background: var(--panel-2); color: var(--text); font: inherit; font-size: 12.5px;
  cursor: pointer; white-space: nowrap; transition: border-color var(--dur), background var(--dur), box-shadow var(--dur);
}
.mp-btn:hover, .ep-btn:hover { border-color: var(--accent); background: rgba(91,140,255,.10); }
.mp-btn[aria-expanded="true"], .ep-btn[aria-expanded="true"] {
  border-color: var(--accent); background: rgba(91,140,255,.14); box-shadow: 0 0 0 1px rgba(91,140,255,.16);
}
.mp-btn:focus-visible, .ep-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.mp-glyph { display: inline-flex; color: var(--accent); filter: drop-shadow(0 0 6px rgba(91,140,255,.35)); }
.mp-name { font-weight: 560; }
.ep-lead { font-size: 10px; text-transform: uppercase; letter-spacing: .1em; color: var(--text-faint); font-weight: 600; }
.ep-name { color: var(--text); font-weight: 560; }
/* Composer "Add" button — labeled so its purpose reads on the dock. */
.composer-add { display: inline-flex; align-items: center; gap: 6px; }
.composer-add .ca-lbl { font-size: 12.5px; }
.composer-add .mp-caret { font-size: 10px; color: var(--text-faint); }
.mp-caret { font-size: 10px; color: var(--text-faint); transition: transform var(--dur), color var(--dur); }
/* Open = the caret points back at the collapse action, not still up at a panel
   that's already raised. */
.mp-btn[aria-expanded="true"] .mp-caret, .ep-btn[aria-expanded="true"] .mp-caret { transform: rotate(180deg); color: var(--accent); }

/* The drop-up panel — body-mounted, fixed, above the pill. z above modals'
   backdrop-free surfaces; below toast. */
.mp-panel {
  position: fixed; z-index: var(--z-popover); width: min(340px, calc(100vw - 16px));
  padding: 6px; border-radius: var(--radius); border: 1px solid var(--stroke-strong);
  background: var(--panel-solid);
  background-image: linear-gradient(150deg, rgba(52,64,94,.45), rgba(18,24,38,.15) 55%);
  box-shadow: 0 -18px 50px -22px rgba(0,0,0,.7), 0 0 0 1px rgba(91,140,255,.10);
  transform-origin: bottom left;
  animation: mpRise .16s var(--ease-out-expo) both;
  /* Height is bounded by the real gap above the trigger (set inline by place());
     this is the ceiling so a 7-row effort panel can never run off the top. */
  max-height: min(60vh, 420px); overflow-y: auto; overscroll-behavior: contain;
}
.ep-panel { width: min(280px, calc(100vw - 16px)); }
@keyframes mpRise { from { opacity: 0; transform: translateY(6px) scale(.985); } to { opacity: 1; transform: none; } }
.mp-head {
  font-size: 10px; text-transform: uppercase; letter-spacing: .14em; font-weight: 600;
  color: var(--text-dim); padding: 6px 9px 7px;
}
.mp-row {
  display: flex; gap: 10px; align-items: flex-start; padding: 9px 10px; border-radius: var(--radius-sm);
  cursor: pointer; transition: background var(--dur); border: 1px solid transparent;
}
.mp-row:hover { background: rgba(255,255,255,.045); }
/* Focus is a real ring, not the hover wash — arrow keys ARE the primary navigation
   here. Inset offset so it doesn't clip against the panel's 6px padding. */
.mp-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.mp-row.on { background: rgba(91,140,255,.12); border-color: rgba(91,140,255,.28); }
.mp-row-glyph { display: inline-flex; color: var(--accent); margin-top: 1px; flex: none; }
.mp-row.on .mp-row-glyph { filter: drop-shadow(0 0 7px rgba(91,140,255,.5)); }
.mp-row-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.mp-row-top { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; min-width: 0; }
.mp-row-name { font-size: 13.5px; font-weight: 600; color: var(--text); min-width: 0; }
.mp-row-line { font-size: 10.5px; color: var(--text-dim); }
.mp-row-line.act { color: var(--accent); }
.mp-row-sum { font-size: 11.5px; line-height: 1.45; color: var(--text-dim); }
/* The reason a row can't be taken (sealed / gated / opens a new room). */
.mp-row-sum.why { color: var(--accent); }
.mp-row.rogue .mp-row-sum.why { color: #f0a35b; }
/* SEALED = clearly not-yet-available. The old treatment kept the name fully bright, which read as
   "pick me" and confused people (LO: "currently confusing"). Now the whole row is visibly SHADED —
   a flat grey wash, desaturated, dimmed name/summary — so at a glance it's plainly a locked tier.
   The date/"Restricted" pill is the ONE thing held at full contrast, because it's the reason. */
.mp-row.locked, .mp-row.locked:hover {
  cursor: default;
  border: 1px dashed var(--stroke);
  background: rgba(255,255,255,.02);
  filter: grayscale(0.7);
}
.mp-row.locked .mp-row-glyph { opacity: .4; }
.mp-row.locked .mp-row-name { color: var(--text-dim); font-weight: 500; }
.mp-row.locked .mp-row-sum { color: var(--text-faint); }
/* Hold the seal pill OUT of the row's grey wash so the "why" stays readable. */
.mp-row.locked .mp-lock { filter: none; }
.mp-lock { display: inline-flex; align-items: center; gap: 5px; color: var(--accent); }
.mp-lock .i-sm { width: 12px; height: 12px; }
.mp-date {
  font-size: 9.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--accent); padding: 1px 6px; border-radius: var(--radius-pill);
  background: rgba(91,140,255,.14); border: 1px solid rgba(91,140,255,.26);
  white-space: nowrap;   /* the seal never breaks mid-pill; the row wraps instead */
}
/* Rogue — the gated uncensored tier. Amber = restricted, distinct from the
   periwinkle "coming soon" date seal. */
.mp-date.restricted { color: #f0a35b; background: rgba(240,163,91,.14); border-color: rgba(240,163,91,.30); }
.mp-lock:has(.restricted) { color: #f0a35b; }
.mp-row-line.unc { color: #f0a35b; font-weight: 600; letter-spacing: .02em; }
.mp-row.rogue .mp-row-glyph { color: #f0a35b; }
.mp-row.rogue.on { background: rgba(240,163,91,.12); border-color: rgba(240,163,91,.30); }
.mp-row.rogue.on .mp-row-glyph { filter: drop-shadow(0 0 7px rgba(240,163,91,.5)); }
/* Rogue sealed = amber "coming, and gated"; Omega sealed = periwinkle "coming". */
.mp-row.rogue.locked, .mp-row.rogue.locked:hover {
  border-color: rgba(240,163,91,.26);
  background: linear-gradient(150deg, rgba(240,163,91,.05), transparent 60%);
}
/* Bandit = GREEN accent (its own colour, distinct from Rogue's orange). */
.mp-row.bandit .mp-row-glyph { color: #4fd18b; }
.mp-row.bandit .mp-row-sum.why { color: #4fd18b; }
.mp-row.bandit.on { background: rgba(79,209,139,.12); border-color: rgba(79,209,139,.30); }
.mp-row.bandit.on .mp-row-glyph { filter: drop-shadow(0 0 7px rgba(79,209,139,.5)); }
.mp-row.bandit.locked, .mp-row.bandit.locked:hover {
  border-color: rgba(79,209,139,.26);
  background: linear-gradient(150deg, rgba(79,209,139,.05), transparent 60%);
}
/* "best" crown pill on Omega — top of the intelligence ladder. A quiet gold so it reads as
   premium without shouting over the periwinkle scheme. */
.mp-best { display: inline-flex; align-items: center; font-size: 9px; font-weight: 800; letter-spacing: .1em;
  text-transform: uppercase; padding: 2px 8px; border-radius: 999px; margin-left: 8px; line-height: 1;
  color: #3a2c05; background: linear-gradient(135deg, #f5d789, #e6ac35); box-shadow: 0 0 10px rgba(240,190,70,.35); }
/* An access lock with no date IS a closed door — flat, grey, no seal. That's the
   vocabulary sealed tiers must not borrow. */
.mp-row.locked.denied, .mp-row.locked.denied:hover { border: 1px solid transparent; background: transparent; }
.mp-row.locked.denied .mp-row-name { color: var(--text-dim); }
.mp-row.locked.denied .mp-row-glyph { color: var(--text-dim); }
@media (prefers-reduced-motion: reduce) {
  .mp-panel { animation: none; }
  .mp-caret { transition: none; }
}
@media (max-width: 560px) {
  .mp-panel { left: 8px !important; right: 8px; width: auto; }
  /* Wrapping already groups the strip down here; the divider would land first on
     line two with nothing to its left. */
  .cbar-div { display: none; }
}

/* Audit fixes ─────────────────────────────────────────────────────────────── */
/* Token gauge: the percent reused .tok-num (the amber code-syntax token) so it was
   always amber. Follow the button ink; only the ring stroke carries state color. */
.tok-gauge .tok-num { color: inherit; }
/* Dashboard with no agents: collapse to one column so there's no dead right gutter. */
.dash-cols--solo { grid-template-columns: 1fr; }
.dash-cols--solo .dash-spine { max-width: 760px; }
/* Texture / PBR results are taller than the square preview — let the box grow to fit
   the tiled preview, the map grid, notes and the .zip actions instead of clipping them. */
.gen-preview.tall { aspect-ratio: auto; height: auto; min-height: 220px; overflow: visible; place-items: stretch; }

/* Agent editor — roomier: two-up rows, clear checkboxes, breathing room. */
.agent-editor { padding: 22px 24px; }
.agent-editor h3 { margin: 0 0 14px; font-size: 16px; }
.agent-editor .agent-presets { margin-bottom: 12px; }
.agent-grid .field { margin-bottom: 0; }
.agent-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin: 10px 0; }
.agent-editor input[type="text"], .agent-editor input:not([type]), .agent-editor select { width: 100%; }
.agent-check { display: flex; gap: 9px; align-items: flex-start; margin: 12px 0 4px; cursor: pointer; font-size: 13px; line-height: 1.45; color: var(--text-dim); }
.agent-check b { font-weight: 640; color: var(--text); }
.agent-check input[type="checkbox"] { width: 16px; height: 16px; margin: 2px 0 0; flex: none; accent-color: var(--accent); }
.agent-note { display: flex; gap: 8px; align-items: center; margin: 12px 0 4px; font-size: 12.5px; color: var(--text-faint); }
.agent-note .i-sm { color: var(--accent); }
@media (max-width: 520px) { .agent-grid { grid-template-columns: 1fr; } }

/* ── Documents workspace ──────────────────────────────────────────────────── */
/* No viewport arithmetic: `.view` is already flex:1/min-height:0, so make the body a
   non-scrolling flex parent for THIS view and let the shell take what's left. The old
   `calc(100vh - 168px)` was a guess at a header that shrinks with its own clamp(). */
.doc-shell { display: grid; grid-template-columns: 250px 1fr; gap: var(--space-lg); height: 100%; min-height: 0; }
.vbody:has(.doc-shell) { overflow: hidden; display: flex; flex-direction: column; }
.doc-side { display: flex; flex-direction: column; min-height: 0; }
.doc-new { width: 100%; margin-bottom: var(--space-sm); display: inline-flex; align-items: center; justify-content: center; gap: var(--space-xs); }
.doc-filter { margin-bottom: var(--space-sm); padding: var(--space-sm) var(--space-md); font-size: var(--fs-sm); flex: none; }
.doc-list { display: flex; flex-direction: column; gap: var(--space-xs); overflow-y: auto; min-height: 0; scrollbar-width: thin; }
.doc-item { position: relative; border-radius: var(--radius-sm); border: 1px solid transparent;
  transition: background var(--dur), border-color var(--dur), box-shadow var(--dur); }
/* The open control is a real <button> SIBLING of the two actions — a role=button row with
   nested buttons has presentational children, so AT dropped rename/delete entirely. */
.doc-item-open { display: flex; flex-direction: column; align-items: flex-start; gap: 3px; width: 100%; min-width: 0;
  /* 54px right is the action-button gutter, not a spacing choice; everything else is on the ramp. */
  padding: var(--space-sm) 54px var(--space-sm) var(--space-md); border: 0; background: none; border-radius: inherit; color: inherit; font: inherit;
  text-align: left; cursor: pointer; }
.doc-item-act { position: absolute; top: 50%; transform: translateY(-50%); z-index: 1; width: 22px; height: 22px; display: grid; place-items: center;
  border: 0; background: none; color: var(--text-faint); border-radius: var(--radius-sm); cursor: pointer; opacity: 0;
  transition: opacity var(--dur), background var(--dur), color var(--dur); }
.doc-item-ren { right: 29px; }
.doc-item-del { right: 5px; }
.doc-item:hover .doc-item-act, .doc-item.on .doc-item-act, .doc-item-act:focus-visible { opacity: 1; }
.doc-item-ren:hover { background: rgba(91,140,255,.18); color: var(--text); }
.doc-item-del:hover { background: rgba(242,104,122,.16); color: var(--bad); }
.doc-item-act:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* Touch: both actions are always visible AND the row itself opens the document, so a 22px
   target 7px from its neighbour turns a slightly-right thumb into a delete. */
@media (hover: none) {
  .doc-item-act { opacity: 1; width: 34px; height: 34px; }
  .doc-item-ren { right: 42px; }
  .doc-item-del { right: 6px; }
  .doc-item-open { padding-right: 84px; }
}
.doc-item:hover { background: rgba(255,255,255,.045); }
/* The open document gets a non-colour cue as well as the tint (inset, not a decorative stripe). */
.doc-item.on { background: rgba(91,140,255,.12); border-color: rgba(91,140,255,.28); box-shadow: inset 2px 0 0 var(--accent); }
.doc-item-t { max-width: 100%; font-size: 13px; font-weight: 560; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.doc-item-d { font-size: var(--fs-label); color: var(--text-faint); }

.doc-main { display: flex; flex-direction: column; min-width: 0; min-height: 0; background: var(--panel); border: 1px solid var(--stroke); border-radius: var(--radius); overflow: hidden; }
.doc-toolbar { display: flex; align-items: center; gap: var(--space-xs); row-gap: var(--space-sm); padding: var(--space-sm) var(--space-md); border-bottom: 1px solid var(--stroke); flex-wrap: wrap; }
/* The actions wrap as ONE unit — individually they shed into a ragged two/three-row block. */
.doc-acts { display: flex; align-items: center; gap: var(--space-xs); flex: none; }
.doc-title { flex: 0 1 240px; min-width: 130px; background: transparent; border: none; border-bottom: 1px solid transparent; color: var(--text); font-size: 15px; font-weight: 620; padding: 4px 2px; }
.doc-title:focus { outline: none; border-bottom-color: var(--accent); }
/* :focus suppressed the global ring for keyboard users too — give it back, scoped. */
.doc-title:focus-visible { outline: none; border-bottom-color: var(--accent); box-shadow: 0 2px 0 -1px var(--accent); }
/* Nothing said this heading-looking thing was an input until you clicked it. */
.doc-title:hover:not(:focus) { border-bottom-color: var(--stroke-strong); }
.doc-title:disabled { color: var(--text-faint); cursor: default; }
.doc-spacer { flex: 1; }
.doc-div { width: 1px; align-self: stretch; margin: 3px 1px; background: var(--stroke); }
.doc-btxt { margin-left: 5px; }
/* Capped so an "edited · 3 in thread" doesn't shove the whole action group onto a new row
   at the exact moment you're reaching for Save. */
.doc-status { display: inline-flex; align-items: center; gap: 6px; min-width: 0; max-width: 24ch;
  font-size: var(--fs-label); white-space: nowrap; color: var(--text-faint); }
.doc-status > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.doc-status .ldr { flex: none; width: 13px; height: 13px; border-width: 2px; }
.doc-status.warn { color: var(--warn); }
.doc-status.good { color: var(--good); }

/* Version-history panel: timestamped restore points (left) + read-only preview (right). */
.doc-hist .doc-hist-body { display: grid; grid-template-columns: 260px 1fr; gap: var(--space-md);
  height: min(56vh, 460px); min-height: 0; margin: 6px 0 18px; }
.doc-hist-list { overflow-y: auto; display: flex; flex-direction: column; gap: 4px;
  border: 1px solid var(--stroke); border-radius: var(--radius-sm); padding: 6px; background: var(--bg); }
.doc-hist-row { display: flex; flex-direction: column; gap: 2px; text-align: left; width: 100%;
  border: 1px solid transparent; background: none; color: var(--text); font: inherit;
  padding: 8px 10px; border-radius: calc(var(--radius-sm) - 2px); cursor: pointer;
  transition: background var(--dur), border-color var(--dur); }
.doc-hist-row:hover { background: var(--panel-2); }
.doc-hist-row.on { background: var(--panel-2); border-color: var(--accent); }
.doc-hist-when { font-size: 13px; font-weight: 600; }
.doc-hist-abs { font-size: 11px; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.doc-hist-size { font-size: 11px; color: var(--text-faint); }
.doc-hist-prev { overflow: auto; border: 1px solid var(--stroke); border-radius: var(--radius-sm);
  background: var(--bg); padding: 12px; min-width: 0; }
.doc-hist-src { margin: 0; white-space: pre-wrap; word-break: break-word; font-family: var(--mono, ui-monospace, monospace);
  font-size: 12px; line-height: 1.55; color: var(--text-dim); }
.doc-hist-muted { color: var(--text-faint); font-size: 13px; }
@media (max-width: 720px) {
  .doc-hist .doc-hist-body { grid-template-columns: 1fr; height: min(64vh, 520px); }
}

/* segmented controls (Markdown|HTML, Edit|Split|Preview) */
/* The well is the ground colour, not a raw black wash. The 3px inset / 2px chip gap are the
   mechanics of a segmented control (they set the nested radius below), not spacing rhythm. */
.doc-seg { display: inline-flex; background: var(--bg); padding: 3px; border-radius: var(--radius-sm); gap: 2px; }
/* Radius derived from the token so the chip nests true inside the 3px inset instead of bulging. */
.doc-segb { border: 0; background: none; color: var(--text-dim); font: inherit; font-size: var(--fs-label); padding: 4px var(--space-sm); border-radius: calc(var(--radius-sm) - 3px); cursor: pointer; transition: background var(--dur), color var(--dur); }
.doc-segb:hover { color: var(--text); }
/* Accent economy: these chips only REPORT the current mode. A raised neutral chip with a 2px
   accent underline reads as selected instantly without spending the accent twice more in a
   44px strip and leaving the Save button with no primacy. */
.doc-segb.on { background: var(--panel-2); color: var(--text); font-weight: 600;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.06), inset 0 -2px 0 var(--accent), 0 1px 2px rgba(0,0,0,.4); }
.doc-segb:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* editor / preview panes */
.doc-panes { flex: 1; display: grid; min-height: 0; }
.doc-panes.mode-edit { grid-template-columns: 1fr; }
.doc-panes.mode-preview { grid-template-columns: 1fr; }
.doc-panes.mode-split { grid-template-columns: 1fr 1fr; }
.doc-panes.mode-edit .doc-preview { display: none; }
.doc-panes.mode-preview .doc-body { display: none; }
.doc-body { width: 100%; height: 100%; resize: none; border: none; background: var(--bg); color: var(--text); padding: var(--space-lg) var(--space-xl); font-family: var(--mono); font-size: var(--fs-body); line-height: 1.7; min-height: 0; }
.doc-body:focus { outline: none; }
/* The primary writing surface had NO focus indicator at all — inset so it doesn't fight the pane seam. */
.doc-body:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--accent); }
.doc-panes.mode-split .doc-body { border-right: 1px solid var(--stroke); }
.doc-preview { width: 100%; height: 100%; border: 0; background: #f4f5f8; min-height: 0; }

/* Nothing open: no blank white sheet, no six live controls that all refuse. */
/* zeroEl carries BOTH `empty-state` and `doc-zero`. `.empty-state{display:flex}` has the same
   specificity as a bare `.doc-zero{display:none}` and comes LATER, so it won the tie and the
   "No document open" card showed even with a document open (a blocking visual glitch). Qualify the
   hide so it outranks `.empty-state`; the `.is-empty` show-rule below (higher still) wins when empty. */
.doc-main:not(.is-empty) .doc-zero { display: none; }
.doc-main.is-empty .doc-panes { grid-template-columns: 1fr; place-content: center; }
.doc-main.is-empty .doc-body, .doc-main.is-empty .doc-preview { display: none; }
.doc-main.is-empty .doc-zero { display: flex; }
.doc-main.is-empty .doc-acts, .doc-main.is-empty .doc-seg { pointer-events: none; opacity: .4; }
/* A model call is in flight — don't let a second Draft race the first into the same textarea. */
.doc-main.busy .doc-agentb { pointer-events: none; opacity: .55; }
/* Armed state for the Web toggle. A ghost button that is ON must not look like one that is OFF,
   and the only difference cannot be the icon — it reads as a hover artefact. Tinted ground plus a
   real border, so "Draft will search the web" is legible at a glance and after a look away. */
.doc-agentb.on {
  color: var(--accent);
  border-color: color-mix(in oklab, var(--accent) 55%, transparent);
  background: color-mix(in oklab, var(--accent) 12%, transparent);
}
.doc-agentb.on:hover { background: color-mix(in oklab, var(--accent) 18%, transparent); }

/* A built-but-locked action (mesh Texture, until its launch date). Reads as "coming, not broken":
   dashed edge + dimmed, the lock and date at full legibility. Same language as a sealed model tier. */
.btn.m3d-locked, .btn.m3d-locked:hover {
  cursor: default;
  color: var(--text-dim);
  border: 1px dashed var(--stroke);
  background: rgba(255,255,255,.02);
}
.btn.m3d-locked .i-svg { opacity: .8; }

/* export menu */
.doc-exp-menu { position: fixed; z-index: var(--z-popover); width: 236px; padding: 6px; border-radius: var(--radius);
  max-height: min(320px, 70vh); overflow-y: auto;
  border: 1px solid var(--stroke-strong); background: var(--panel-solid);
  background-image: linear-gradient(150deg, rgba(52,64,94,.45), rgba(18,24,38,.15) 55%);
  box-shadow: 0 18px 44px -14px rgba(0,0,0,.7); }
.doc-exp-item { display: flex; flex-direction: column; gap: 1px; padding: 8px 10px; border-radius: var(--radius-sm); cursor: pointer; transition: background var(--dur); }
.doc-exp-item:hover, .doc-exp-item:focus-visible { background: rgba(91,140,255,.12); outline: none; }
.doc-exp-item > span:first-child { font-size: 13px; font-weight: 600; color: var(--text); }
.doc-exp-sub { font-size: 11px; color: var(--text-faint); }

/* Below 900px there IS no preview pane, so don't offer Split as a mode — pressing it used to
   give you a screen identical to Edit, plus a hairline seam against nothing. */
@media (max-width: 900px) {
  .doc-panes.mode-split { grid-template-columns: 1fr; }
  .doc-panes.mode-split .doc-preview { display: none; }
  .doc-panes.mode-split .doc-body { border-right: 0; }
  .doc-seg [data-m="split"] { display: none; }
}
/* Labels go at 1180px, NOT 720 — the crunch is 760–1150px where the sidebar still holds a
   column. Icon-only + the title attributes carry these, as the delete button already proves. */
@media (max-width: 1180px) { .doc-btxt { display: none; } }
@media (max-width: 720px) {
  .doc-shell { grid-template-columns: 1fr; height: auto; }
  .vbody:has(.doc-shell) { overflow-y: auto; display: block; }
  .doc-side { max-height: 34vh; }
  /* dvh so collapsing mobile browser chrome doesn't clip the editor. */
  .doc-main { height: calc(100dvh - 220px); min-height: 320px; }
}

/* ── Landing polish ───────────────────────────────────────────────────────── */
/* House SVG icons on the capability cards (was raw dingbats — one rendered as a colour emoji). */
.lcard .li .i-svg { width: 22px; height: 22px; }
.feat-mark .i-svg { width: 28px; height: 28px; }
/* The featured Oracle card is a TIER ABOVE the grid, not row zero — it is separated by a full
   --space-2xl step (double the 14px grid gutter) in the base .lcard-feat rule, because a 16px
   gap against a 14px gutter filed it straight back into the grid as row zero. That rule, and
   the phone stack that goes with it, now live together further down this file: an override up
   here loses the cascade to the equal-specificity base rule declared after it. */

/* GPU memory panel (Settings → System, owner) */
.gpu-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; flex-wrap: wrap; margin-bottom: var(--space-sm); }
/* The device name is CONTEXT; the VRAM figure is the payload. Sizes say so. */
.gpu-name { font-size: 11.5px; font-weight: 500; color: var(--text-dim); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gpu-nums { font-size: 18px; font-weight: 650; color: var(--text); font-variant-numeric: tabular-nums; letter-spacing: -.01em; }
.gpu-free { font-size: 11.5px; color: var(--text-dim); font-variant-numeric: tabular-nums; margin-top: var(--space-xs); }
/* Empty track must read as a container in BOTH themes — a white-on-white wash didn't. */
.gpu-bar { position: relative; height: 10px; border-radius: var(--radius-pill); background: var(--stroke-strong); border: 1px solid var(--stroke); overflow: hidden; }
.gpu-fill { display: block; height: 100%; border-radius: var(--radius-pill); transition: width .4s var(--ease-out-quint), background .3s; }
.gpu-fill.good { background: var(--accent); }
.gpu-fill.warn { background: var(--warn); }
.gpu-fill.bad { background: var(--bad); }
/* Where the tone flips — otherwise the colour change reads as a mood swing. */
.gpu-mark { position: absolute; top: 0; bottom: 0; width: 1px; background: var(--stroke-strong); pointer-events: none; }
.gpu-rows { display: flex; flex-direction: column; gap: var(--space-xs); margin-top: var(--space-md); }
.gpu-row { display: flex; align-items: center; gap: 10px; padding: var(--space-sm) var(--space-md); border-radius: var(--radius-sm); background: var(--panel-2); border: 1px solid var(--stroke); }
.gpu-row-txt { flex: 1; min-width: 0; }
.gpu-row-n { display: block; flex: 1; min-width: 0; font-size: 12.5px; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gpu-row-id { display: block; font-family: var(--mono); font-size: 11px; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-top: 1px; }
.gpu-row-m { font-size: 12px; color: var(--text-dim); font-variant-numeric: tabular-nums; white-space: nowrap; }
.gpu-sub { font-size: 11px; text-transform: uppercase; letter-spacing: .12em; font-weight: 600; color: var(--text-faint); margin-top: var(--space-lg); }
.gpu-stamp { font-size: 11px; color: var(--text-faint); font-variant-numeric: tabular-nums; align-self: center; }
@media (prefers-reduced-motion: reduce) { .gpu-fill { transition: none; } }

/* Code-word presets (Admin) */
.cw-list { display: flex; flex-direction: column; gap: var(--space-xs); margin-bottom: var(--space-xs); }
.cw-row { display: flex; align-items: center; gap: var(--space-sm); padding: var(--space-sm) var(--space-md); border-radius: var(--radius-sm); border: 1px solid var(--stroke); background: var(--panel-2); transition: border-color var(--dur), background var(--dur); }
.cw-row:hover { border-color: var(--stroke-strong); background: rgba(91,140,255,.05); }
.cw-row.on { border-color: var(--accent); background: rgba(91,140,255,.10); }
.cw-row.on:hover { background: rgba(91,140,255,.16); }
.cw-row:focus-within { border-color: var(--accent); }
/* One pick target for every row (None included) so the cursor and hit area never change. */
.cw-pick { display: flex; align-items: center; gap: var(--space-sm); flex: 1; min-width: 0; cursor: pointer; }
.cw-meta { display: flex; align-items: center; gap: var(--space-sm); flex: 1; min-width: 0; }
.cw-name { font-weight: 600; font-size: 13px; white-space: nowrap; flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.cw-prev { font-size: 12px; color: var(--text-dim); flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.cw-add { display: flex; flex-direction: column; gap: var(--space-sm); }
.cw-add .field { margin-bottom: 0; }
.cw-add .btn { align-self: flex-start; }
.cw-add input, .cw-add textarea { width: 100%; }
/* Phone: a 60-char name gets the full row width, preview drops beneath it. */
@media (max-width: 720px) {
  .cw-meta { flex-direction: column; align-items: flex-start; gap: 2px; }
  .cw-name, .cw-prev { max-width: 100%; }
}

/* Draggable gallery cards → droppable studio source area. */
.gcard.draggable-img { cursor: grab; }
.gcard.draggable-img:active { cursor: grabbing; }
.gcard.dragging { opacity: .5; }
/* Per-card GLB download (mesh gallery) — mirrors .gdel, top-left. */
.gcard .gdl { position: absolute; top: 6px; left: 6px; width: 26px; height: 26px; display: grid; place-items: center; border-radius: 8px; border: 1px solid var(--stroke); background: rgba(12,12,16,.72); color: var(--text); cursor: pointer; opacity: 0; transition: opacity .15s, background .15s; z-index: 3; text-decoration: none; }
.gcard:hover .gdl, .gcard:focus-within .gdl, .gcard .gdl:focus-visible { opacity: 1; }
.gcard .gdl:hover { background: rgba(91,140,255,.3); }
.gcard .gdl .i-sm { width: 14px; height: 14px; }
@media (hover: none) { .gcard .gdl { opacity: 1; } }
.src-thumb { transition: border-color var(--dur), box-shadow var(--dur), background var(--dur); }
.src-thumb.drag {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px rgba(91,140,255,.28), 0 0 22px -6px rgba(91,140,255,.5);
  background: rgba(91,140,255,.08);
}
.src-thumb.drag * { pointer-events: none; }   /* let the drop land on the thumb, not a child */

/* A locked nav item (e.g. Oracle without the grant) — dimmed, with a trailing lock. */
.nav-item.locked { color: var(--text-faint); }
.nav-item.locked .ic { opacity: .5; }
.nav-item.locked:hover { color: var(--text); background: rgba(255,255,255,.03); }
.nav-item.locked .nav-lock { margin-left: auto; color: var(--text-faint); display: inline-flex; }
.nav-item.locked .nav-lock .i-sm { width: 13px; height: 13px; }

.gen-bar { position: absolute; left: 0; right: 0; bottom: 0; height: 3px; background: rgba(255,255,255,.08); z-index: 4; }
.gen-bar > i { display: block; height: 100%; width: 0; background: var(--accent); box-shadow: 0 0 10px var(--accent); transition: width .35s ease; }
/* Past the estimate the bar caps at ~94-96% and the label reads "finishing up…" —
   a motionless fill there reads as a HANG. .indeterminate adds a travelling sheen so
   it stays legibly alive without faking progress it doesn't have. */
.gen-bar.indeterminate > i { background-image: linear-gradient(90deg, transparent, rgba(255,255,255,.5), transparent);
  background-size: 55% 100%; background-repeat: no-repeat;
  animation: genBarSheen 1.5s var(--ease-out-quint) infinite; }
@keyframes genBarSheen { from { background-position: -60% 0; } to { background-position: 160% 0; } }
@media (prefers-reduced-motion: reduce) { .gen-bar.indeterminate > i { animation: none; } }
.plan-toggle { padding: 6px 12px; border-radius: var(--radius-pill); border: 1px solid var(--stroke-strong); background: var(--panel-2); color: var(--text-dim); font-size: 12.5px; font-weight: 600; cursor: pointer; white-space: nowrap; transition: border-color .15s, color .15s, background .15s, box-shadow .15s; }
.plan-toggle:hover { color: var(--text); }
.plan-toggle.on { border-color: var(--accent-2); background: rgba(139,143,245,.15); color: var(--text); box-shadow: 0 0 0 1px rgba(139,143,245,.2); }
/* Swarm shares the plan-toggle pill so the composer bar reads consistent; its
   active state stays periwinkle-blue (the tool), where Plan's is violet (the agent). */
.plan-toggle.swarm { display: inline-flex; align-items: center; gap: 6px; }
.plan-toggle.swarm.on { border-color: var(--accent); background: rgba(91,140,255,.14); box-shadow: 0 0 0 1px rgba(91,140,255,.2); }
.voice-toggle { opacity: .55; }
.voice-toggle.on { opacity: 1; color: var(--good); box-shadow: 0 0 0 1px rgba(63,191,135,.35); }
.cmd-menu { position: absolute; bottom: 100%; left: 16px; right: 16px; margin-bottom: 8px; background: var(--panel-solid); border: 1px solid var(--stroke-strong); border-radius: 12px; box-shadow: var(--shadow); overflow: hidden; max-height: 260px; overflow-y: auto; z-index: var(--z-dropdown); }
.composer { position: relative; }
.cmd-item { display: flex; align-items: baseline; gap: 10px; padding: 9px 14px; cursor: pointer; font-size: 13px; }
.cmd-item.on, .cmd-item:hover { background: rgba(91,140,255,.1); }
.cmd-label { font-family: var(--mono); font-weight: 600; color: var(--accent); }
.cmd-hint { font-family: var(--mono); font-size: 11px; color: var(--text-faint); }
.cmd-desc { margin-left: auto; color: var(--text-dim); font-size: 12px; }
.attach-strip { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 8px; }
.thumb { position: relative; width: 56px; height: 56px; border-radius: 8px; overflow: hidden; border: 1px solid var(--stroke-strong); }
.thumb img { width: 100%; height: 100%; object-fit: cover; }
.thumb-x { position: absolute; top: 2px; right: 2px; width: 18px; height: 18px; border-radius: 50%; border: none; background: rgba(0,0,0,0.7); color: var(--text); font-size: 10px; cursor: pointer; display: grid; place-items: center; }
.attachments { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 8px; }
.attachments .media { max-width: 220px; max-height: 220px; border-radius: 10px; border: 1px solid var(--stroke); margin: 0; }
.agent-chips { display: flex; gap: 6px; flex-wrap: wrap; }
.agent-chip { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border-radius: var(--radius-pill); border: 1px solid var(--stroke-strong); background: var(--panel-2); font-size: 12px; cursor: pointer; color: var(--text-dim); }
.agent-chip.on { border-color: var(--accent); background: rgba(91,140,255,0.1); color: var(--text); }
.agent-chip .a-av { font-size: 13px; }

/* --- right rail --- */
.rail { border-left: 1px solid var(--stroke); background: var(--bg-2); display: flex; flex-direction: column; min-height: 0; }
.rail-tabs { display: flex; border-bottom: 1px solid var(--stroke); }
.rail-tabs button { flex: 1; padding: 11px 4px; border: none; background: none; color: var(--text-faint); font-size: 12px; font-weight: 600; cursor: pointer; border-bottom: 2px solid transparent; }
.rail-tabs button:hover:not(.on) { color: var(--text-dim); }
.rail-tabs button.on { color: var(--text); border-bottom-color: var(--accent); }
.rail-body { flex: 1; overflow-y: auto; padding: var(--space-lg); min-height: 0; }
/* ── one empty-state device for every "nothing here yet" surface ──
   A greyed monochrome glyph, a title line, an optional hint. The four legacy
   one-liner empties route through the same ink/size/fade so they read as
   designed zero states rather than missing content. */
.empty-state { display: flex; flex-direction: column; align-items: center; gap: 7px;
  text-align: center; padding: 30px 14px; color: var(--text-faint);
  animation: fadeIn .32s var(--ease-out-quint) backwards; }
.empty-state .es-ic { color: var(--text-faint); font-size: 22px; line-height: 1; opacity: .85; }
.empty-state .es-ic svg { width: 22px; height: 22px; display: block; }
.empty-state .es-title { font-size: 13px; color: var(--text-dim); }
.empty-state .es-hint { font-size: var(--fs-label); color: var(--text-faint); line-height: 1.5; max-width: 30ch; }
.empty-state .btn { margin-top: 4px; }
.rail-empty, .ctx-empty, .dash-empty, .am-empty {
  color: var(--text-faint); animation: fadeIn .32s var(--ease-out-quint) backwards; }
.rail-empty { font-size: var(--fs-sm); text-align: center; padding: 30px 10px; }
@media (prefers-reduced-motion: reduce) { .empty-state, .rail-empty, .ctx-empty, .dash-empty, .am-empty { animation: none; } }

.drop { border: 1.5px dashed var(--stroke-strong); border-radius: 12px; padding: 20px; text-align: center; color: var(--text-dim); font-size: 12.5px; cursor: pointer; transition: border-color .15s, background .15s; }
.drop:hover, .drop.over { border-color: var(--accent); background: rgba(91,140,255,0.06); color: var(--text); }
.file-row { display: flex; align-items: center; gap: 9px; padding: 8px 10px; border-radius: 8px; font-size: 12.5px; }
.file-row:hover { background: var(--panel-2); }
.file-row .fic { color: var(--accent); flex: none; }
.file-row .fname { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: var(--mono); font-size: 12px; }
.file-row .fsz { color: var(--text-faint); font-size: 11px; }
.file-row .facts { display: none; gap: 4px; } .file-row:hover .facts { display: flex; }

.mem-item { padding: var(--space-md); border-radius: var(--radius-sm); border: 1px solid var(--stroke); margin-bottom: var(--space-sm); background: var(--panel-2); }
.mem-item .mtxt { font-size: 12.5px; line-height: 1.5; }
.mem-item .mmeta { font-size: 10.5px; color: var(--text-faint); margin-top: 6px; display: flex; justify-content: space-between; }

/* --- generic view header --- */
.vhead { padding: var(--space-xl) 24px; border-bottom: 1px solid var(--stroke); display: flex; align-items: center; gap: var(--space-lg); }
/* DESIGN.md Display: view titles are the page's anchor, not another card header. */
.vhead h1 { font-size: clamp(1.6rem, 3.5vw, 2.4rem); line-height: 1.1; margin: 0; font-weight: 700; letter-spacing: -0.01em; }
/* 12.5px under a 38px h1 was a 3:1 jump with nothing between — and the subtitle is often the only
   orientation copy on the screen, so it read smaller than the rows beneath it. */
.vhead p { margin: 3px 0 0; font-size: var(--fs-body); line-height: 1.5; max-width: 68ch; color: var(--text-dim); }
/* Title + live state (e.g. "4 documents · last edited 2h ago") on one baseline. */
.vhead-title { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--space-xs) var(--space-md); }
.vhead-count { font-size: 12px; font-weight: 550; color: var(--text-faint); }
.vhead-count:empty { display: none; }
.vbody { flex: 1; overflow-y: auto; padding: 22px 24px; min-height: 0; }

/* --- studio --- */
/* Unified Studio shell: one header with a segmented Image/Video/3D switcher, then
   the active sub-studio's panel+gallery grid fills the rest. */
.studio-head { flex-wrap: wrap; row-gap: 12px; }
.studio-head-titles { min-width: 0; margin-right: auto; }
.studio-body { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.studio-body > .studio { flex: 1; min-height: 0; }
/* ONE segmented-control spec. The sub-studio switch, the panel mode switch and the
   Settings seg-toggle were three near-but-not-equal geometries (radius 11/10/10,
   track .28/.25/panel-2, inner 8/7/7). They now share: track = --radius-sm, 4px
   pad, rgba(0,0,0,.28); inner tab = track radius minus the pad (correct nesting). */
.studio-switch { display: inline-flex; gap: 4px; padding: 4px; background: rgba(0,0,0,0.28); border: 1px solid var(--stroke); border-radius: var(--radius-sm); flex-shrink: 0; }
.studio-tab { display: inline-flex; align-items: center; gap: var(--space-sm); padding: var(--space-sm) 14px; border: none; background: transparent; color: var(--text-dim); border-radius: calc(var(--radius-sm) - 4px); cursor: pointer; font: 600 12.5px/1 inherit; font-family: inherit; }
.studio-tab .ic { display: inline-flex; align-items: center; }
.studio-tab .ic svg { width: 16px; height: 16px; display: block; }
.studio-tab:hover:not(.on) { color: var(--text); background: rgba(255,255,255,0.04); }
.studio-tab.on { background: var(--panel-2); color: var(--text); box-shadow: 0 0 0 1px var(--stroke-strong); }
.studio-tab.on .ic { color: var(--accent); }
.studio-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.studio { display: grid; grid-template-columns: 340px 1fr; gap: 0; height: 100%; min-height: 0; }
.studio-panel { border-right: 1px solid var(--stroke); padding: 20px; overflow-y: auto; }
.studio-gallery { padding: 20px; overflow-y: auto; }
/* Repair — a collapsible "seal any mesh" section under the generate controls. */
.m3d-repair { margin-top: 18px; border: 1px solid var(--stroke); border-radius: var(--radius-sm); background: rgba(255,255,255,.015); }
.m3d-repair > summary { display: flex; align-items: center; gap: 9px; padding: 12px 14px; cursor: pointer; font-size: 13.5px; font-weight: 600; list-style: none; user-select: none; }
.m3d-repair > summary::-webkit-details-marker { display: none; }
.m3d-repair > summary .i-svg { color: var(--accent); }
.m3d-repair > summary:hover { color: var(--accent); }
.m3d-repair[open] > summary { border-bottom: 1px solid var(--stroke); }
.m3d-repair > .m3d-drop { margin: 14px; }
.m3d-repair > #meshRepairStatus { margin: 0 14px 14px; }
.gallery-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(170px,1fr)); gap: 12px; }
.gcard { border-radius: 12px; overflow: hidden; border: 1px solid var(--stroke); background: var(--panel-2); cursor: pointer; position: relative; }
.gcard img { width: 100%; aspect-ratio: 1; object-fit: cover; display: block; }
.gcard .gcap { padding: 8px 10px; font-size: 11px; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gcard .gdel { position: absolute; top: 6px; right: 6px; width: 26px; height: 26px; border-radius: 8px; border: 1px solid var(--stroke); background: rgba(10,14,22,.88); color: var(--text); font-size: 12px; line-height: 1; cursor: pointer; opacity: 0; transition: opacity .15s, background .15s, transform .15s; z-index: 3; }
.gcard:hover .gdel, .gcard:focus-within .gdel, .gcard .gdel:focus-visible { opacity: 1; }
@media (hover: none) { .gcard .gdel { opacity: 1; } }
.gcard .gdel:hover { background: var(--bad, #c0392b); transform: scale(1.08); }

/* Sandbox runner output */
.sandbox-out { background: var(--panel-2, #14141a); border: 1px solid var(--stroke); border-radius: 10px; padding: 12px; overflow-x: auto; white-space: pre-wrap; word-break: break-word; font-family: var(--mono, ui-monospace, monospace); font-size: 12.5px; line-height: 1.5; }
.sandbox-out pre { white-space: pre-wrap; word-break: break-word; margin: 0; font-family: inherit; }
/* The preview adopts the SELECTED aspect ratio (--gen-ar, set per panel when the
   aspect/resolution changes) so 16:9 and 9:16 results fill the frame instead of
   letterboxing inside a square. Falls back to 1:1 until a ratio is chosen. */
.gen-preview { position: relative; aspect-ratio: var(--gen-ar, 1); border-radius: var(--radius); border: 1px solid var(--stroke); display: grid; place-items: center; background: rgba(0,0,0,0.3); overflow: hidden; margin-bottom: 14px; }
.gen-preview img { width: 100%; height: 100%; object-fit: contain; }
.gen-preview .ph { color: var(--text-faint); font-size: 13px; text-align: center; padding: 20px; }
/* Download the finished result — floats top-right over the image/video, revealed on hover. */
.gen-preview .gen-dl { position: absolute; top: 10px; right: 10px; width: 34px; height: 34px; display: grid; place-items: center;
  border-radius: 9px; border: 1px solid var(--stroke); background: rgba(12,12,16,.74); color: var(--text); cursor: pointer;
  opacity: 0; transition: opacity .15s, background .15s; z-index: 4; text-decoration: none; backdrop-filter: blur(4px); }
.gen-preview:hover .gen-dl, .gen-preview .gen-dl:focus-visible { opacity: 1; }
.gen-preview .gen-dl:hover { background: rgba(91,140,255,.32); }
.gen-preview .gen-dl .i-sm { width: 16px; height: 16px; }
@media (hover: none) { .gen-preview .gen-dl { opacity: 1; } }
/* PBR maps show/hide row — a quiet label + the modern .cap-switch, sitting above the map grid. */
.pbr-toggle { display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 9px 12px; border-radius: 10px; border: 1px solid var(--stroke); background: var(--panel-2);
  font-size: 12.5px; color: var(--text-dim); }
.studio-modes { display: flex; gap: 4px; margin-bottom: var(--space-lg); background: rgba(0,0,0,0.28); padding: 4px; border-radius: var(--radius-sm); }
.studio-modes .smode { flex: 1; padding: var(--space-sm) 4px; border: none; background: transparent; color: var(--text-dim); border-radius: calc(var(--radius-sm) - 4px); cursor: pointer; font-size: 12.5px; font-weight: 600; }
.studio-modes .smode:hover:not(.on) { color: var(--text); }
.studio-modes .smode.on { background: var(--panel-2); color: var(--text); box-shadow: 0 0 0 1px var(--stroke-strong); }
/* Top-level category switch (Images ↔ Textures): a hair tighter to the sub-tabs below it, and the
   active category reads with the accent so it's clearly the parent choice, not another sub-mode. */
.studio-modes.studio-cats { margin-bottom: var(--space-sm); }
.studio-modes.studio-cats .smode.on { background: color-mix(in oklab, var(--accent) 20%, var(--panel-2)); box-shadow: 0 0 0 1px color-mix(in oklab, var(--accent) 45%, transparent); }
/* Match .studio-tab:focus-visible — every segmented/selectable control in the
   Studio keeps a visible keyboard focus indicator, not just the sub-studio switch. */
.studio-modes .smode:focus-visible, .texmode:focus-visible, .engine-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.src-thumb { width: 100%; aspect-ratio: 16/10; border-radius: 10px; border: 1px dashed var(--stroke-strong); display: grid; place-items: center; overflow: hidden; background: rgba(0,0,0,0.25); }
.src-thumb img { width: 100%; height: 100%; object-fit: contain; }

/* image "developing" animation — aurora wash + mesh + scanning sweep, then the
   finished image reveals from a blurred, scaled state into focus. */
/* The generation animation is FULL-SCREEN by design — it takes over the whole
   viewport while image / texture / 3D generation runs (not confined to the little
   preview box). position:fixed makes that deterministic regardless of ancestors;
   z below toast/modal so notifications and dialogs still surface over it. */
.gen-working { position: fixed; inset: 0; z-index: 80; overflow: hidden; display: grid; place-items: center;
  background: radial-gradient(120% 120% at 50% 0%, #141d2e, #0b1120 72%);
  animation: genFadeIn .2s var(--ease-out-quint, ease-out) both; }
@keyframes genFadeIn { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .gen-working { animation: none; } }
/* PERF: a 34px blur RE-RASTERISED every frame as it spun was cooking the CPU. Lower the blur AND
   promote to a cached layer (will-change/translateZ) so the spin just transforms the cached bitmap. */
.gen-working .aurora { position: absolute; inset: -45%; filter: blur(16px); opacity: .85;
  background: conic-gradient(from 0deg, transparent 0deg, rgba(91,140,255,.38) 60deg, transparent 150deg, rgba(139,143,245,.38) 240deg, transparent 330deg);
  animation: genSpin 6s linear infinite; will-change: transform; }
.gen-working .mesh { position: absolute; inset: 0; opacity: .5;
  background-image: linear-gradient(rgba(255,255,255,.06) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,.06) 1px, transparent 1px);
  background-size: 26px 26px;
  -webkit-mask-image: radial-gradient(circle at 50% 50%, #000, transparent 76%);
  mask-image: radial-gradient(circle at 50% 50%, #000, transparent 76%); }
.gen-working .scan { position: absolute; left: 0; right: 0; top: -20%; height: 20%; mix-blend-mode: screen;
  background: linear-gradient(180deg, transparent, rgba(91,140,255,.55), transparent); animation: genScan 2.2s ease-in-out infinite; }
.gen-working .glabel { position: relative; z-index: 3; text-align: center; padding: 11px 18px; border-radius: var(--radius-pill);
  background: rgba(7,9,13,.82); border: 1px solid var(--stroke-strong); font-size: 13px; color: var(--text); }
.gen-working .gtimer { margin-top: 5px; font-size: 11px; color: var(--text-dim); font-variant-numeric: tabular-nums; }
/* Cancel sits STATICALLY inside the centred label block, beneath the timer — not absolutely positioned
   over the spinning aurora (that read as floating / glitching as the animation moved). */
.gen-working .glabel { display: flex; flex-direction: column; align-items: center; }
.gen-working .gen-cancel { position: static; margin-top: 14px; z-index: 4;
  background: rgba(7,9,13,.82); border: 1px solid var(--stroke-strong); }
.gen-working .gen-cancel:hover:not(:disabled) { color: var(--bad); border-color: color-mix(in oklab, var(--bad) 55%, transparent); }
.gen-working .gen-cancel:disabled { opacity: .6; }
.gen-dot { display: inline-block; width: 4px; height: 4px; border-radius: 50%; background: var(--accent); margin-left: 4px; vertical-align: middle; animation: genBlink 1.2s infinite; }
.gen-dot:nth-child(2) { animation-delay: .2s; }
.gen-dot:nth-child(3) { animation-delay: .4s; }
.gen-reveal { animation: genReveal .9s cubic-bezier(.2,.7,.2,1) both; }
@keyframes genSpin { to { transform: rotate(1turn); } }
@keyframes genScan { 0% { top: -20%; } 100% { top: 100%; } }
@keyframes genBlink { 0%,100% { opacity: .25; } 50% { opacity: 1; } }
@keyframes genReveal { from { filter: blur(22px) saturate(1.5); transform: scale(1.05); opacity: 0; } to { filter: none; transform: none; opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .gen-working .aurora, .gen-working .scan, .gen-dot { animation: none; }
  .gen-reveal { animation: none; }
}

/* --- code-word "unlocked" room ---
   LO's call: NO whole-UI recolor. Activating the code word leaves the Atmos blue
   completely untouched and only reveals a small "Unlocked" badge at the top. The
   toggle logic, the `body.room-unlocked` class and the badge element all stay —
   only the pink/magenta recolor (accent overrides + chat-head/avatar/composer/brand
   tints) was removed. The reveal flash is retained but recoloured to the Atmos
   palette so nothing reads as red. */
.unlock-badge { display: none; }
body.room-unlocked .unlock-badge { display: inline-flex; flex: none; align-items: center; gap: 6px; padding: 3px 10px;
  border-radius: var(--radius-pill); font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--accent); border: 1px solid rgba(91,140,255,.4); background: rgba(91,140,255,.10); }

.unlock-flash { position: fixed; inset: 0; z-index: var(--z-overlay); display: grid; place-items: center; pointer-events: none;
  background: radial-gradient(circle at 50% 50%, rgba(91,140,255,.20), rgba(2,4,8,.74)); animation: ufIn .35s ease; }
.unlock-flash.out { animation: ufOut .7s ease forwards; }
.unlock-flash::before { content: ""; position: absolute; inset: 0; mix-blend-mode: overlay; opacity: .5;
  background: repeating-linear-gradient(0deg, transparent 0 2px, rgba(255,255,255,.07) 2px 3px); animation: ufScan 2.1s linear; }
.uf-inner { text-align: center; position: relative; }
.uf-title { font-family: var(--mono); font-weight: 700; letter-spacing: .32em; font-size: clamp(22px, 5vw, 46px); color: #fff;
  padding-left: .32em; text-shadow: 0 0 26px rgba(91,140,255,.85); position: relative; animation: ufGlitch 2s steps(2, end) 1; }
.uf-title::after { content: attr(data-t); position: absolute; left: .32em; top: 0; color: var(--accent-2); mix-blend-mode: screen; opacity: .8;
  clip-path: inset(0 0 55% 0); animation: ufShift 2s steps(2, end) 1; }
.uf-sub { margin-top: 14px; letter-spacing: .5em; color: var(--accent-bright); font-size: 12px; text-transform: uppercase; padding-left: .5em; }
.uf-by { margin-top: 8px; color: var(--text-faint); font-size: 12px; }
@keyframes ufIn { from { opacity: 0; } }
@keyframes ufOut { to { opacity: 0; } }
@keyframes ufScan { from { transform: translateY(-100%); } to { transform: translateY(100%); } }
@keyframes ufGlitch { 0% { transform: translateX(-3px); } 15% { transform: translateX(3px); } 30% { transform: translateX(-2px); } 45% { transform: translateX(2px); } 100% { transform: none; } }
@keyframes ufShift { 0% { transform: translateX(4px); } 25% { transform: translateX(-4px); } 50% { transform: translateX(3px); } 100% { transform: none; } }
@media (prefers-reduced-motion: reduce) { .unlock-flash::before, .uf-title, .uf-title::after { animation: none; } }

/* ============================================================
   BADASS — ambient energy on the auth panel + app shell
   ============================================================ */
/* Static cyber grid behind the auth screen — present as texture, no longer
   drifting. The instrument is at rest; the one live ambient is the halo below. */
.auth-wrap::before {
  content: ""; position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background-image:
    linear-gradient(rgba(91,140,255,.06) 1px, transparent 1px),
    linear-gradient(90deg, rgba(91,140,255,.06) 1px, transparent 1px);
  background-size: 42px 42px;
  -webkit-mask-image: radial-gradient(circle at 50% 38%, #000, transparent 68%);
  mask-image: radial-gradient(circle at 50% 38%, #000, transparent 68%);
}
.auth-card { position: relative; z-index: 1; overflow: hidden; }
/* PERF: a soft conic halo bleeding from behind the card. It used to spin forever while blurred
   (blur(30px) + continuous rotate = an always-on GPU repaint on a big layer — the exact
   "animating a blurred element" we're eliminating). Now STATIC: same lit halo, zero cost. */
.auth-card::before {
  content: ""; position: absolute; inset: -40%; z-index: -1;
  background: conic-gradient(from 210deg, transparent 0deg, rgba(91,140,255,.5) 40deg, transparent 130deg, rgba(139,143,245,.5) 220deg, transparent 320deg);
  filter: blur(30px); opacity: .5;
}
/* Top edge accent — single-hue periwinkle, sweeps ONCE on entry then rests
   (no more infinite loop, no purple->blue two-hue). */
.auth-card::after {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 1.5px;
  background: linear-gradient(90deg, transparent, var(--accent), transparent);
  background-size: 200% 100%; animation: edgeSweep 2.4s var(--ease-out-quint) 1 both;
}
@keyframes edgeSweep { to { background-position: 200% 0; } }
/* The wordmark holds a steady glow — no float loop (state, not fidget). */
.auth-brand .mark { font-size: 34px; filter: drop-shadow(0 0 18px rgba(91,140,255,.6)); }

/* sidebar mark holds a steady glow (was an always-on drop-shadow pulse = a continuous filter
   repaint); the animated CYPHER logomark trace is the sidebar's living signature, not this). */
.side-brand .mark { filter: drop-shadow(0 0 12px rgba(91,140,255,.7)); animation: markBreathe 4.6s var(--ease) infinite; transform-origin: center; }
@keyframes markBreathe { 0%,100% { transform: scale(1); filter: drop-shadow(0 0 10px rgba(91,140,255,.55)); } 50% { transform: scale(1.07); filter: drop-shadow(0 0 18px rgba(122,162,255,.9)); } }
.btn.primary { box-shadow: 0 4px 18px -8px rgba(91,140,255,.5); }
.btn.primary:hover { box-shadow: 0 8px 28px -6px rgba(91,140,255,.7); }

/* static accent hairline across the top of the working area (was an always-on background-position
   sweep = a continuous repaint of the strip; the static line reads the same at rest). */
.main { position: relative; }
.main::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 1px; z-index: 5; pointer-events: none;
  background: linear-gradient(90deg, transparent, var(--accent), transparent); opacity: .5;
}
/* selected model chip holds a steady glow (was an always-on box-shadow pulse). */
.model-chip.on { box-shadow: 0 0 14px -3px rgba(91,140,255,.4), 0 0 0 1px rgba(91,140,255,.3); }
@media (prefers-reduced-motion: reduce) {
  .auth-wrap::before, .auth-card::after, .auth-brand .mark { animation: none; }
}

/* ============================================================
   MOTION — entrance + interaction animations across the app
   ============================================================ */
@keyframes viewIn { from { opacity: 0; transform: translateY(7px); } to { opacity: 1; transform: none; } }
@keyframes cardIn { from { opacity: 0; transform: translateY(12px) scale(.99); } to { opacity: 1; transform: none; } }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes popIn { from { opacity: 0; transform: scale(.95); } to { opacity: 1; transform: none; } }
@keyframes slideRight { from { opacity: 0; transform: translateX(-9px); } to { opacity: 1; transform: none; } }

.view { animation: viewIn .3s cubic-bezier(.2,.75,.25,1); }
.vhead { animation: slideRight .38s ease backwards; }
.vbody { animation: fadeIn .35s ease .05s backwards; }
.section-card { animation: cardIn .42s cubic-bezier(.2,.75,.25,1) backwards; }
.admin-grid > *:nth-child(1) { animation-delay: .02s; }
.admin-grid > *:nth-child(2) { animation-delay: .07s; }
.admin-grid > *:nth-child(3) { animation-delay: .12s; }
.admin-grid > *:nth-child(4) { animation-delay: .17s; }
.admin-grid > *:nth-child(5) { animation-delay: .22s; }
.admin-grid > *:nth-child(6) { animation-delay: .27s; }
.admin-grid > *:nth-child(n+7) { animation-delay: .32s; }

.proj-item { animation: slideRight .32s ease backwards; }
.proj-list .proj-item:nth-child(1) { animation-delay: .03s; }
.proj-list .proj-item:nth-child(2) { animation-delay: .06s; }
.proj-list .proj-item:nth-child(3) { animation-delay: .09s; }
.proj-list .proj-item:nth-child(4) { animation-delay: .12s; }
.proj-list .proj-item:nth-child(n+5) { animation-delay: .15s; }

.gcard { animation: popIn .32s ease backwards; transition: transform .18s var(--ease-out-quint), box-shadow .18s; }
.gcard:hover { transform: translateY(-4px) scale(1.025); box-shadow: 0 16px 34px -14px rgba(0,0,0,.65); z-index: 2; }
.model-card { animation: cardIn .3s ease backwards; transition: transform .15s, border-color .15s; }
.model-card:hover { transform: translateY(-2px); border-color: var(--stroke-strong); }
.file-row, .mem-item { animation: fadeIn .28s ease backwards; }
.modal { animation: popIn .22s var(--ease-out-quint); }

/* interaction polish — durations run through the --dur ramp, not a near-continuous
   spread of .1/.12/.15/.16/.18/.2s. Press feedback = --dur-fast, everything
   hover/state = --dur. */
.btn { transition: border-color var(--dur), background var(--dur), transform var(--dur-fast), box-shadow var(--dur), color var(--dur); }
.btn:active { transform: translateY(1px) scale(.99); }
.nav-item { transition: background var(--dur), color var(--dur), border-color var(--dur), transform var(--dur-fast); }
.nav-item:active { transform: scale(.98); }
.nav-item .ic { transition: transform var(--dur); display: inline-block; }
.nav-item:hover .ic { transform: scale(1.15) rotate(-4deg); }
.model-chip { transition: border-color var(--dur), color var(--dur), background var(--dur), transform var(--dur-fast), box-shadow var(--dur); }
.model-chip:active { transform: scale(.95); }
.agent-chip, .smode, .rtabs button, .auth-tabs button, .rail-tabs button, .studio-tab { transition: background var(--dur), color var(--dur), border-color var(--dur), box-shadow var(--dur); }
.pill { transition: border-color var(--dur), color var(--dur), background var(--dur); }
.avatar, .msg .av, .pdot { transition: transform var(--dur); }
.avatar:hover, .msg .av:hover { transform: scale(1.08); }
.tbl tr td { transition: background var(--dur-fast); }
.field input, .field textarea, .field select, input, textarea, select { transition: border-color var(--dur), box-shadow var(--dur), background var(--dur); }
.thumb, .src-thumb { transition: border-color var(--dur), transform var(--dur-fast); }
.thumb:hover { transform: scale(1.05); }

@media (prefers-reduced-motion: reduce) {
  .view, .vhead, .vbody, .section-card, .admin-grid > *, .proj-item, .gcard, .model-card,
  .file-row, .mem-item, .modal { animation: none !important; }
  .gcard:hover, .model-card:hover, .avatar:hover, .msg .av:hover, .nav-item:hover .ic, .thumb:hover { transform: none; }
}

/* --- models --- */
.provider-group { margin-bottom: 22px; }
.provider-head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.provider-head h3 { margin: 0; font-size: 14px; }
/* auto-FIT, not auto-fill: a provider with one or two models shouldn't leave
   phantom empty tracks and a dead gutter — the cards stretch to fill the row. */
.model-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px,1fr)); gap: 10px; }
.model-card { border: 1px solid var(--stroke); border-radius: var(--radius); padding: var(--space-md) 14px; background: var(--panel-2); }
/* break-all shattered ids mid-token ("qwen2.5-cod / er:32b"); anywhere only breaks
   when the word genuinely cannot fit. */
.model-card .mc-name { font-family: var(--mono); font-size: 12.5px; font-weight: 600; overflow-wrap: anywhere; }
.model-card .mc-tags { display: flex; gap: 5px; flex-wrap: wrap; margin-top: 8px; }

/* --- admin tables --- */
.tbl-wrap { overflow-x: auto; max-width: 100%; }
.tbl { width: 100%; border-collapse: collapse; font-size: 13px; }
.tbl-wrap .tbl { min-width: 560px; }
.tbl th { text-align: left; padding: 9px 12px; color: var(--text-faint); font-weight: 600; font-size: var(--fs-label); text-transform: uppercase; letter-spacing: .14em; border-bottom: 1px solid var(--stroke); }
.tbl td { padding: 10px 12px; border-bottom: 1px solid var(--stroke); }
.tbl tr:hover td { background: var(--panel-2); }
.tbl .mono { font-size: 12px; }
.admin-grid { display: grid; grid-template-columns: 1fr; gap: 22px; max-width: 1000px; }
.section-card { padding: var(--space-xl); }
/* DESIGN.md Title — 15px/600/1.3. The ONE card-header size (modal h2 matches). */
.section-card h2 { font-size: 15px; font-weight: 600; line-height: 1.3; margin: 0 0 4px; }
.section-card .sc-sub { font-size: 12.5px; color: var(--text-dim); margin-bottom: 16px; }

/* --- Settings: categorised, Atmos-polished layout --- */
.settings-cats { display: flex; flex-direction: column; gap: var(--space-2xl); max-width: 1120px; }
.settings-cat { display: flex; flex-direction: column; gap: 14px; animation: cardIn .42s cubic-bezier(.2,.75,.25,1) backwards; }
.settings-cats > .settings-cat:nth-child(2) { animation-delay: .05s; }
.settings-cats > .settings-cat:nth-child(3) { animation-delay: .10s; }
.settings-cats > .settings-cat:nth-child(4) { animation-delay: .15s; }
.settings-cats > .settings-cat:nth-child(5) { animation-delay: .20s; }
.settings-cat-head { display: flex; flex-direction: column; gap: 3px; padding: 0 2px 2px;
  border-bottom: 1px solid var(--stroke); }
.settings-cat-head h2 { margin: 0; font-size: 12px; font-weight: 700; letter-spacing: .14em;
  text-transform: uppercase; color: var(--accent-bright); }
.settings-cat-head p { margin: 0 0 8px; font-size: 12.5px; color: var(--text-faint); line-height: 1.5; }
/* two-up on wide, single column on small — each card keeps its own height */
.settings-grid { grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); gap: 16px; max-width: none; align-items: start; }
@media (max-width: 720px) { .settings-grid { grid-template-columns: 1fr; } }

/* --- Pronunciation editor (owner) --- */
.pron-list { display: flex; flex-direction: column; gap: 8px; margin: 6px 0 2px; }
.pron-row { display: grid; grid-template-columns: minmax(0,1fr) auto minmax(0,1fr) auto; align-items: center; gap: 8px; }
.pron-row input { width: 100%; margin: 0; }
.pron-arrow { color: var(--text-faint); font-size: 13px; text-align: center; }
.pron-del { flex: none; color: var(--bad); }
@media (max-width: 520px) {
  .pron-row { grid-template-columns: 1fr 1fr; }
  .pron-row .pron-arrow { display: none; }
  .pron-row .pron-del { grid-column: 1 / -1; justify-self: end; }
}

/* --- modal --- */
/* No backdrop blur: the THERMAL OVERRIDE reset near the end of this file nulls
   every backdrop-filter, so declaring one here only lied about what ships. */
.modal-back { position: fixed; inset: 0; background: rgba(2,4,8,0.72); display: grid; place-items: center; z-index: var(--z-modal-backdrop); padding: 20px; animation: fade .15s; }
@keyframes fade { from { opacity: 0; } }
.modal { width: 100%; max-width: 460px; padding: 24px; }
.modal h2 { margin: 0 0 4px; font-size: 15px; font-weight: 600; line-height: 1.3; }
.modal .m-sub { color: var(--text-dim); font-size: 13px; margin-bottom: 18px; }
.modal-actions { display: flex; gap: 10px; justify-content: flex-end; margin-top: 8px; }
/* .modal-card — dialogs (skill view, room-skills editor, game challenge/leave/draw) mount
   straight into .modal-back WITHOUT the .modal class, so they need the card chrome AND the
   modal sizing spelled out here, or they render as bare text on the scrim. */
.modal-card { width: 100%; max-width: 460px; padding: 24px; background: var(--panel-solid);
  border: 1px solid var(--stroke); border-radius: var(--radius);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.04), var(--shadow); }

.status-strip { display: flex; gap: 8px; flex-wrap: wrap; }

/* --- responsive --- */
@media (max-width: 1000px) {
  .chat-cols { grid-template-columns: 1fr; }
  /* .rail is not hidden here any more — it becomes an off-canvas sheet further
     down (see the phones block) so Files/Artifacts/Team stay reachable. */
  .studio { grid-template-columns: 1fr; }
  .studio-panel { border-right: none; border-bottom: 1px solid var(--stroke); }
}
@media (max-width: 720px) {
  .shell { grid-template-columns: 1fr; }
  .sidebar { position: fixed; z-index: var(--z-sticky); width: 260px; height: 100%; transform: translateX(-100%); transition: transform .2s; box-shadow: var(--shadow); }
  .sidebar.open { transform: none; }
  .mobile-only { display: inline-flex !important; }
}
.mobile-only { display: none; }

/* ==========================================================================
   Polish pass — additive refinements only. Nothing here changes layout or the
   colour system; it sharpens type, adds the missing keyboard-focus affordance,
   and gives surfaces a bit more depth. Safe to delete as a block.
   ========================================================================== */

/* Crisper text on dark backgrounds — the single biggest perceived-quality win. */
html { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-rendering: optimizeLegibility; }

/* Headings were at default tracking; tightening reads as "designed". */
h1 { letter-spacing: -0.022em; }
h2, h3 { letter-spacing: -0.015em; }
.chat-title, .side-brand .name { letter-spacing: -0.01em; }

/* Keyboard focus was invisible on buttons/links/tabs — inputs had a ring, nothing
   else did. This is an accessibility fix as much as a visual one. Mouse users
   never see it (:focus-visible), keyboard users always do. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
.btn:focus-visible, .nav-item:focus-visible, .model-chip:focus-visible,
.rail-tabs button:focus-visible, .ad-tab:focus-visible { outline-offset: 3px; }

/* Buttons: a touch of lift on hover, and an honest press. */
.btn:not(:disabled):hover { transform: translateY(-1px); }
.btn:not(:disabled):active { transform: translateY(0) scale(0.985); }
.btn:disabled { opacity: .55; cursor: not-allowed; transform: none; }

/* Panels/cards: a hairline top highlight so glass surfaces read as lit from above
   instead of flat fills. */
.card, .artifact-drawer, .cmd-menu, .model-dd-panel {
  box-shadow: inset 0 1px 0 rgba(255,255,255,.04), var(--shadow);
}

/* Thin, unobtrusive scrollbars everywhere (was only set on a couple of elements). */
* { scrollbar-width: thin; scrollbar-color: var(--stroke-strong) transparent; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background: var(--stroke-strong); border-radius: var(--radius-pill);
  border: 3px solid transparent; background-clip: content-box;
}
*::-webkit-scrollbar-thumb:hover { background: var(--text-faint); background-clip: content-box; }

/* Selection in the brand accent rather than the browser default blue. */
::selection { background: color-mix(in srgb, var(--accent) 30%, transparent); color: var(--text); }

/* Links inside replies: underline on hover only, so prose stays calm. */
.bubble a { color: var(--accent); text-decoration-color: color-mix(in srgb, var(--accent) 40%, transparent); text-underline-offset: 2px; }
.bubble a:hover { text-decoration-color: var(--accent); }

/* Slightly more air between messages — density was fine, breathing is better. */
.msg { padding-block: 2px; }
.msg .bubble { line-height: 1.62; }

/* Respect reduced-motion for the new movement. */
@media (prefers-reduced-motion: reduce) {
  .btn:not(:disabled):hover, .btn:not(:disabled):active { transform: none; }
}

/* ==========================================================================
   Phones. The layout already collapsed its columns, but collapsing is not the
   same as being usable: the rail (Files/Artifacts/Team) was display:none below
   1000px, so those panels were simply gone on a phone; 14px inputs made iOS
   zoom the whole page on every composer tap; and nothing respected the notch or
   the home indicator. This block fixes those specifically.
   ========================================================================== */

/* Backdrop shared by the sidebar and rail sheets. */
.scrim {
  position: fixed; inset: 0; z-index: var(--z-scrim); background: rgba(0,0,0,0.55);
  opacity: 0; pointer-events: none; transition: opacity .2s;
}
.scrim.on { opacity: 1; pointer-events: auto; }
.rail-only { display: none; }

@media (max-width: 1000px) {
  /* The rail returns as a right-hand sheet instead of vanishing. */
  .rail-only { display: inline-flex; }
  .rail {
    display: flex; position: fixed; top: 0; right: 0; z-index: var(--z-sticky);
    width: min(340px, 88vw); height: 100%;
    transform: translateX(101%); transition: transform .22s ease;
    box-shadow: var(--shadow); padding-right: env(safe-area-inset-right);
  }
  .rail.open { transform: none; }
}

@media (max-width: 720px) {
  /* Any input under 16px makes iOS Safari zoom the page on focus and never zoom
     back — the single worst mobile papercut here. 16px is the threshold. */
  input, textarea, select, #composer { font-size: 16px; }

  /* 100%/100vh both count the URL bar that isn't there; dvh tracks it. */
  .shell { height: 100dvh; }

  /* Keep the composer clear of the home indicator, and the sidebar off the notch. */
  .composer { padding: 10px 12px calc(12px + env(safe-area-inset-bottom)); }
  .sidebar { padding-top: env(safe-area-inset-top); padding-left: env(safe-area-inset-left); }
  .chat-head { padding-top: calc(6px + env(safe-area-inset-top)); }

  /* Thumb-sized targets. 44px is the Apple/Android minimum; the icon buttons
     were ~28px, which is a coin-flip to hit while walking. */
  .btn.icon, .rail-tabs button, .chat-head .btn { min-height: 44px; min-width: 44px; }

  /* The centred room title has no room next to two icon buttons on a 390px
     screen — let it truncate rather than shove the controls off-screen. */
  /* The header packed a title + UNLOCK badge + presence dots + a wide model
     dropdown into 390px, and the right cluster ran clean off the edge. Tighten
     spacing, drop presence (a nicety, not load-bearing on a phone), and cap the
     model dropdown so it can't shove the rest out of view. */
  .chat-head { padding-left: 12px; padding-right: 12px; gap: 8px; }
  .chat-head-left, .chat-head-right { gap: 6px; }
  .chat-head-center { min-width: 0; flex: 1 1 auto; padding: 0 4px; }
  .chat-title { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 15px; }
  .chat-sub { display: none; }
  .chat-head .presence { display: none; }
  .chat-head-right .model-dd-btn { max-width: 150px; }
  .chat-head-right .model-dd-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

  /* Give the transcript the screen. The old rules here targeted `.msg-body`,
     which is not a class that exists — the message text is `.bubble` inside
     `.msg .body`, so the mobile sizing never applied and long lines clipped at
     the right edge. Tighter gutters + real shrink fixes it. */
  .messages { padding: 16px 12px 8px; gap: 14px; }
  .msg { max-width: 100%; gap: 9px; }
  .msg .body, .bubble { min-width: 0; max-width: 100%; }
  .modal { width: 100%; max-width: 100%; border-radius: var(--radius) var(--radius) 0 0; }

  /* Wide content (code, tables, mermaid, the artifact code view) scrolls inside
     its own box instead of making the whole page scroll sideways. */
  .bubble pre, .bubble table, .ad-code, .think-write { max-width: 100%; overflow-x: auto; }

  /* The Oracle ask row packs an input + attach + TALK + VOICE + SEND on one
     no-wrap line — off a ~390px phone it overflowed the glass panel and gave
     the page a sideways scroll. Let the input own its own full-width line and
     the buttons wrap under it, each at a 44px thumb target. */
  .oracle-inputrow { flex-wrap: wrap; }
  .oracle-inputrow > input, .oracle-inputrow > textarea { flex: 1 1 100%; }
  .oracle-inputrow .oracle-talkbtn, .oracle-inputrow .oracle-voicebtn, .oracle-inputrow .oracle-send { flex: 1 1 auto; min-height: 44px; }
  .oracle-inputrow .btn.icon { min-height: 44px; min-width: 44px; }
}

/* A phone in landscape is short, not narrow — reclaim the vertical chrome. */
@media (max-height: 480px) and (orientation: landscape) {
  .chat-head { padding-top: 4px; padding-bottom: 4px; }
  .composer-bar { margin-bottom: 4px; }
}

/* Search-index status + rebuild, at the top of the Files rail. */
.idx-row {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 7px 9px; margin-bottom: 8px; border-radius: var(--radius-sm);
  background: rgba(0,0,0,0.22); border: 1px solid var(--stroke);
}
.idx-stat { font-size: 11.5px; color: var(--text-faint); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Scheduled tasks in the rail. */
.sched-row {
  padding: 9px 10px; margin-bottom: 8px; border-radius: var(--radius-sm);
  background: rgba(0,0,0,0.22); border: 1px solid var(--stroke);
}
.sched-row.off { opacity: 0.55; }
.sr-top { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.sr-label { font-size: 13px; font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sr-when { font-size: 11px; color: var(--accent); flex: none; }
.sr-sub { font-size: 11.5px; color: var(--text-faint); margin-top: 2px; }
.sr-err { color: var(--bad); }
.sr-acts { display: flex; gap: 4px; margin-top: 7px; }

/* Context-window meter (Settings -> Context & memory). On-brand glass panel with a
   tidy segmented bar: persona=accent, memory=warn, summary=bad, history=violet. */
.ctx-meter { background: linear-gradient(180deg, rgba(255,255,255,.035), transparent), var(--glass);
  border: 1px solid var(--stroke); border-radius: var(--radius-sm); padding: 14px 15px;
  box-shadow: 0 1px 0 0 var(--glass-hi) inset; }
.ctx-empty { color: var(--text-faint); font-size: var(--fs-sm); }
.ctx-head { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; font-size: 12px; margin-bottom: 9px; }
.ctx-head > span:first-child { font-weight: 600; color: var(--text); }
.ctx-model { font-weight: 600; color: var(--text); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The fullness read leads — colour IS the message (roomy / filling / about to drop). */
.ctx-pct { font-weight: 700; font-family: var(--mono); font-size: 11.5px; white-space: nowrap; }
.ctx-pct.good { color: var(--good); }
.ctx-pct.warn { color: var(--warn); }
.ctx-pct.bad { color: var(--bad); }
.ctx-note { font-size: 10.5px; line-height: 1.45; margin-top: 8px; padding: 6px 8px; border-radius: var(--radius-sm); }
.ctx-note.warn { color: var(--warn); background: color-mix(in oklab, var(--warn) 12%, transparent); }
.ctx-note.bad { color: var(--bad); background: color-mix(in oklab, var(--bad) 12%, transparent); }
.ctx-bar { display: flex; height: 9px; border-radius: var(--radius-pill); overflow: hidden; background: rgba(255,255,255,0.06);
  box-shadow: inset 0 0 0 1px var(--stroke); }
.ctx-seg { height: 100%; min-width: 2px; transition: width .5s var(--ease); }
.ctx-persona { background: var(--accent); }
.ctx-memory  { background: var(--warn); }
.ctx-summary { background: var(--bad); }
.ctx-history { background: var(--accent-2); }
.ctx-nums { font-size: 11px; color: var(--text-faint); margin-top: 8px; line-height: 1.5; }
.ctx-legend { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 10px; font-size: 11px; color: var(--text-dim); }
.ctx-key { display: inline-flex; align-items: center; gap: 6px; }
.ctx-key i { width: 9px; height: 9px; border-radius: 3px; display: inline-block; }

/* Settings rows: a label paired with a control (toggle / preview) on one line, and
   the small helper paragraphs used inside section cards. Previously unstyled after
   the settings reorg — which is what left the Context & memory card looking broken. */
.setting-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.setting-label { font-size: 13px; font-weight: 500; color: var(--text); }
.section-card .m-sub { color: var(--text-dim); font-size: 12px; line-height: 1.55; }
/* Segmented pick (Appearance → Loader style, and reusable elsewhere). */
.seg-toggle { display: inline-flex; gap: 4px; padding: 4px; border: 1px solid var(--stroke); border-radius: var(--radius-sm); background: var(--panel-2); }
.seg-btn { padding: var(--space-xs) 14px; border-radius: calc(var(--radius-sm) - 4px); border: none; background: none; color: var(--text-dim);
  font-size: 12.5px; font-weight: 600; cursor: pointer; transition: background .18s var(--ease), color .18s; }
.seg-btn:hover { color: var(--text); }
.seg-btn.on { background: rgba(91,140,255,.16); color: var(--text); box-shadow: 0 0 0 1px rgba(91,140,255,.25) inset; }
/* Settings reuses the .plan-toggle pill for plain on/off switches (Performance mode,
   Compaction). Violet is THE AGENT — reserved for Plan mode — so a generic "this
   setting is on" lights periwinkle, matching .seg-btn.on right above it. */
.setting-row .plan-toggle.on { border-color: var(--accent); background: rgba(91,140,255,.14);
  box-shadow: 0 0 0 1px rgba(91,140,255,.2); }
.loader-preview { display: inline-flex; min-width: 64px; min-height: 48px; align-items: center; justify-content: center; }

/* Checkbox rows: the base .field is a stacked label+control with the control at
   full width, which flung the box to the far edge of the row. Shrink-wrap it and
   keep it next to its text. */
.field.check { display: flex; flex-direction: row; align-items: center; gap: 8px; }
.field.check input[type="checkbox"] { width: 16px; height: 16px; flex: none; margin: 0; accent-color: var(--accent); }
.field.check span { flex: 1; }

/* Video model picker (Studio) — two comparison cards, pick one. */
.engine-pick { display: flex; flex-direction: column; gap: 8px; }
.engine-card { text-align: left; width: 100%; padding: 10px 12px; border-radius: var(--radius-sm); border: 1px solid var(--stroke); background: rgba(0,0,0,0.22); cursor: pointer; transition: border-color .15s, background .15s; }
.engine-card:hover:not(.off) { border-color: var(--stroke-strong); }
.engine-card.on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent); }
.engine-card.off { opacity: 0.55; cursor: not-allowed; }
.ec-top { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.ec-name { font-weight: 600; font-size: 13px; color: var(--text); }
.ec-tag { font-size: 10.5px; font-weight: 600; color: var(--accent); text-transform: uppercase; letter-spacing: .03em; }
.ec-blurb { font-size: 11.5px; color: var(--text-faint); line-height: 1.45; margin-top: 3px; }
.ec-state { font-size: 10.5px; color: var(--text-dim); margin-top: 6px; }
.engine-card.on .ec-state { color: var(--accent); }

/* ---- Oracle HUD (JARVIS-style neural voice console) ---------------------- */
.oracle-hud { display:flex; flex-direction:column; height:100%; background:#0e1219; color:var(--accent-bright); font-family:'Courier New',ui-monospace,monospace; overflow:hidden; }
/* ── Dashboard (the owner's cockpit — NOT a SaaS panel) ──────────────────────
   Atmos: a lit greeting header, a spine of activity, and a side rail to jump
   back into rooms. Varied weight on purpose — no hero-metric tiles, no grid of
   identical cards. Warmth comes from the lit time-of-day glyph + periwinkle. */
.dash { display: flex; flex-direction: column; gap: 20px; padding-bottom: 24px; }

/* greeting header — the glyph is lit like the orb (warmth by light, not a metric) */
.dash-vhead { align-items: flex-start; }
.dash-greet { display: flex; align-items: center; gap: 14px; min-width: 0; }
.dash-greet-orb { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; border-radius: 50%; color: var(--accent-bright);
  background: radial-gradient(circle at 50% 42%, rgba(91,140,255,.28), rgba(91,140,255,.05) 70%, transparent);
  box-shadow: inset 0 0 0 1px var(--stroke-strong), 0 0 22px -6px rgba(91,140,255,.5); }
.dash-greet-orb .i-svg { width: 20px; height: 20px; vertical-align: 0; }
.dash-greet-txt { min-width: 0; }
.dash-greet-line { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
/* The dashboard IS the landing view and this greeting is its title — it carries the
   DESIGN.md Display treatment explicitly (rather than inheriting a card-header size)
   so the page has a clear top note. */
.dash-greet-line h1 { font-size: clamp(1.6rem, 3.5vw, 2.4rem); font-weight: 700;
  letter-spacing: -0.01em; line-height: 1.1; margin: 0; }
.dash-greet-sub { margin: 3px 0 0; font-size: 13px; color: var(--text-dim); }
/* live status chip — subtle, state-only (appears when Oracle has work running) */
/* text uses --text (not the accent) so it clears 4.5:1 on the tinted chip in BOTH themes;
   the periwinkle lives in the dot + border, which carry the "live" signal. */
.dash-live { display: inline-flex; align-items: center; gap: 6px; font-size: 11px; font-weight: 600;
  letter-spacing: .02em; color: var(--text); padding: 3px 10px; border-radius: var(--radius-pill);
  border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
  background: color-mix(in srgb, var(--accent) 12%, transparent); }
.dash-live.hide { display: none; }
.dash-live-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--accent-bright);
  box-shadow: 0 0 8px var(--accent-bright); animation: pulse 1.4s ease-in-out infinite; }

/* quick actions — real buttons, icon + label */
.dash-quick { display: flex; flex-wrap: wrap; gap: 8px; }
.dash-quick .btn { display: inline-flex; align-items: center; gap: 7px; }

.dash-cols { display: grid; grid-template-columns: minmax(0, 1.7fr) minmax(0, 1fr); gap: 22px; align-items: start; }
.dash-side { display: flex; flex-direction: column; gap: 20px; min-width: 0; }
.dash-group { min-width: 0; }
.dash-empty { color: var(--text-faint); font-size: var(--fs-sm); padding: 16px 0; text-align: center; }

/* activity spine — the primary column, framed by a leading accent rule, not boxed as a card */
.dash-spine { min-width: 0; }
.dash-spine-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 8px; color: var(--accent); }
.dash-spine-head h2 { margin: 0; font-size: 15px; font-weight: 600; color: var(--text); }
.dash-spine-head .i-svg { align-self: center; }
.dash-spine-sub { font-size: 12px; color: var(--text-faint); }

/* activity feed — rows, not cards (cards would be the lazy answer here) */
.dash-feed { display: flex; flex-direction: column; }
.dash-ev { padding: 11px 10px; margin: 0 -10px; border-top: 1px solid var(--stroke); cursor: pointer;
  transition: background .18s var(--ease-out-quint), transform .18s var(--ease-out-quint); border-radius: var(--radius-sm); }
.dash-feed .dash-ev:first-child { border-top: none; }
.dash-ev:hover { background: var(--panel-2); }
.dash-ev:active { transform: translateX(1px); }
.dash-ev:focus-visible { outline: none; background: var(--panel-2); box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 55%, transparent) inset; }
.de-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; margin-bottom: 3px; }
.de-who { font-size: 12.5px; font-weight: 600; color: var(--text); }
.de-who.de-agent { color: var(--accent-2); }
.de-who.de-sys { color: var(--text-faint); }
.de-room { font-size: 11px; color: var(--accent); }
.de-at { margin-left: auto; font-size: 11px; color: var(--text-faint); font-variant-numeric: tabular-nums; }
.de-body { font-size: 13px; line-height: 1.5; color: var(--text-dim); overflow-wrap: anywhere; }

/* rooms list */
.dash-rooms { display: flex; flex-direction: column; gap: 6px; }
.dash-roomlist { display: flex; flex-direction: column; gap: 6px; }
.dash-search { width: 100%; margin-bottom: 8px; padding: 8px 11px; border-radius: var(--radius-sm);
  border: 1px solid var(--stroke); background: var(--bg-2); color: var(--text); font: inherit; font-size: 13px; }
.dash-search::placeholder { color: var(--text-faint); }
.dash-search:focus-visible { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px rgba(91,140,255,.14); }
.dash-subhead { display: flex; align-items: center; gap: 7px; margin: 14px 0 7px; color: var(--text-dim);
  font-size: var(--fs-label); font-weight: 600; letter-spacing: .14em; text-transform: uppercase; }
.dash-group > .dash-subhead:first-child { margin-top: 0; }
.dash-room { padding: 9px 11px; border-radius: var(--radius-sm); border: 1px solid transparent;
  cursor: pointer; transition: background .16s var(--ease-out-quint), border-color .16s var(--ease-out-quint); }
.dash-room:hover { background: var(--panel-2); border-color: var(--stroke-strong); }
.dash-room:active { background: color-mix(in srgb, var(--accent) 8%, var(--panel-2)); }
.dash-room:focus-visible { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px rgba(91,140,255,.14); }
.dash-oracle-room { border-color: color-mix(in srgb, var(--accent-2) 26%, transparent); background: color-mix(in srgb, var(--accent-2) 7%, transparent); }
.dash-oracle-room:hover { border-color: color-mix(in srgb, var(--accent-2) 44%, transparent); }
.dr-top { display: flex; align-items: center; gap: 8px; }
.dr-name { font-size: 13.5px; font-weight: 600; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dr-tag { font-size: 10px; letter-spacing: .08em; text-transform: uppercase; color: var(--text-faint);
  border: 1px solid var(--stroke); border-radius: var(--radius-pill); padding: 1px 7px; }
.dr-meta { display: flex; align-items: center; gap: 6px; font-size: 11.5px; color: var(--text-faint); margin-top: 2px; }
.dr-dot { opacity: .6; }

/* agents */
.dash-agents { display: flex; flex-wrap: wrap; gap: 6px; }
.dash-agent { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-dim);
  background: var(--panel-2); border: 1px solid var(--stroke); border-radius: var(--radius-pill); padding: 4px 11px; }
.dash-agent .da-av { color: var(--accent); }
@media (max-width: 900px) { .dash-cols { grid-template-columns: 1fr; } .dash-quick .btn { flex: 1 1 auto; justify-content: center; } }
@media (prefers-reduced-motion: reduce) { .dash-ev, .dash-room { transition: none; } .dash-ev:active { transform: none; } .dash-live-dot { animation: none; } }

.oracle-stage { position:relative; flex:1; min-height:0; }
.oracle-canvas { position:absolute; inset:0; width:100%; height:100%; display:block; }
.oracle-title { position:absolute; top:6%; left:0; right:0; text-align:center; font-size:30px; letter-spacing:14px; font-weight:bold; color:var(--accent-bright); text-shadow:0 0 22px rgba(122,162,255,.65); pointer-events:none; }
.oracle-sub { position:absolute; top:calc(6% + 44px); left:0; right:0; text-align:center; font-size:11px; letter-spacing:6px; color:#8fa5d6; text-transform:uppercase; pointer-events:none; }
.oracle-status { position:absolute; bottom:6%; left:0; right:0; text-align:center; font-size:14px; letter-spacing:.24em; font-weight:bold; color:var(--accent-bright); pointer-events:none; }
.oracle-status.speaking { color:#8b8ff5; text-shadow:0 0 14px rgba(139,143,245,.6); }
.oracle-status.thinking { color:#f0b429; text-shadow:0 0 12px rgba(240,180,41,.5); }
.oracle-console { border-top:1px solid #26365e; background:#141a24; padding:12px 16px; display:flex; flex-direction:column; gap:10px; }
/* 150px (~6 lines) was too short: it auto-scrolls to the bottom, so opening the transcript pinned it
   to Oracle's latest reply and YOUR message had scrolled out of view — reading as "it only shows what
   it said back". Give it real height so the whole recent exchange (your line + the reply) is visible. */
.oracle-log { overflow:auto; max-height:min(46vh,460px); min-height:44px; font-size:13px; line-height:1.55; display:flex; flex-direction:column; gap:6px; }
.oracle-log .ol-you { color: var(--accent-bright); }   /* make YOUR lines pop so they're unmistakable */
.oracle-log .ol-who { color:var(--text-faint); font-weight:bold; margin-right:10px; letter-spacing:1px; }
.oracle-log .ol-you span:last-child { color:#c3cef0; }
.oracle-log .ol-oracle span:last-child { color:#dfe6ff; }
.oracle-inputrow { display:flex; gap:8px; }
/* Tokenised: these base Oracle rules predate the Atmos palette and still shipped
   hardcoded blue-greys (#26365e / #33436e / #0e1219 / #8fa5d6) wherever the Atmos
   override block below didn't happen to cover them. */
.oracle-input { flex:1; background:var(--bg-2); border:1px solid var(--stroke); border-radius:var(--radius-sm); color:var(--text); font-family:inherit; font-size:14px; padding:11px 12px; outline:none; }
.oracle-input:focus { border-color:var(--accent-bright); box-shadow:0 0 0 1px rgba(122,162,255,.3); }
.oracle-send, .oracle-voicebtn { background:transparent; border:1px solid var(--stroke-strong); color:var(--accent-bright); font-family:inherit; font-weight:bold; font-size:12px; letter-spacing:1px; border-radius:var(--radius-sm); padding:0 16px; cursor:pointer; white-space:nowrap; }
.oracle-send:hover, .oracle-voicebtn:hover { background:rgba(122,162,255,.12); }
.oracle-voicebtn.on { border-color:#3fbf87; color:#3fbf87; }
.oracle-controls { display:flex; align-items:center; gap:8px; flex-wrap:wrap; padding-top:4px; }
.oracle-controls .ocl { color:var(--text-faint); font-size:var(--fs-label); font-weight:600; letter-spacing:.14em; }
.oracle-speed { accent-color:var(--accent-bright); width:120px; }
.oracle-vsel { background:var(--bg-2); border:1px solid var(--stroke); color:var(--accent-bright); font-family:inherit; font-size:12px; border-radius:var(--radius-sm); padding:3px 6px; }
.oracle-texttoggle { background:transparent; border:1px solid #33436e; color:#8fa5d6; font-family:inherit; font-weight:bold; font-size:11px; letter-spacing:1px; border-radius:var(--radius-sm); padding:4px 10px; cursor:pointer; margin-left:auto; }
.oracle-texttoggle.on { border-color:var(--accent-bright); color:var(--accent-bright); }
.oracle-canvas { cursor:pointer; }
.usercard-menu { position:fixed; z-index:var(--z-popover); background:var(--panel,#161b22); border:1px solid var(--stroke,#30363d); border-radius:10px; padding:6px; width:250px; box-shadow:0 12px 40px -8px rgba(0,0,0,.7);
  transform-origin: bottom left; animation: popMenuIn .16s var(--ease, cubic-bezier(.22,1,.36,1)); }
/* P5 — the downloads popout scales + fades in from its anchor corner */
@keyframes popMenuIn { from { opacity: 0; transform: scale(.96) translateY(4px); } to { opacity: 1; transform: none; } }
.usercard-menu.closing { animation: popMenuOut .16s ease forwards; }
@keyframes popMenuOut { to { opacity: 0; transform: scale(.96) translateY(4px); } }
/* IA reorg — the items "pop out" one after another when the card menu opens. */
.usercard-menu.opening > * { animation: ucmItemIn .26s var(--ease, cubic-bezier(.22,1,.36,1)) both; }
.usercard-menu.opening > *:nth-child(1){animation-delay:.02s} .usercard-menu.opening > *:nth-child(2){animation-delay:.05s}
.usercard-menu.opening > *:nth-child(3){animation-delay:.08s} .usercard-menu.opening > *:nth-child(4){animation-delay:.11s}
.usercard-menu.opening > *:nth-child(5){animation-delay:.14s} .usercard-menu.opening > *:nth-child(6){animation-delay:.17s}
.usercard-menu.opening > *:nth-child(7){animation-delay:.20s} .usercard-menu.opening > *:nth-child(n+8){animation-delay:.23s}
@keyframes ucmItemIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
.usercard-menu .ucm-sep { height:1px; background:var(--stroke,#30363d); margin:6px 4px; opacity:.7; }
.usercard-menu .ucm-admin { width:100%; text-align:left; border:0; background:transparent; cursor:pointer; font:inherit; }
.usercard-menu .ucm-admin span:first-child { color:var(--accent,#7aa2ff); font-weight:600; }
@media (prefers-reduced-motion: reduce) { .usercard-menu, .attach-menu, .usercard-menu.opening > * { animation: none !important; } }
.usercard-menu .ucm-head { color:var(--text-faint,#8b949e); font-size:10px; font-weight:700; letter-spacing:1px; padding:6px 8px 4px; }
.usercard-menu .ucm-item { display:flex; justify-content:space-between; align-items:center; padding:9px 10px; border-radius:7px; color:var(--text,#e6edf3); font-size:13px; text-decoration:none; }
.usercard-menu .ucm-item:hover { background:var(--panel-2,#1c2230); }
.usercard-menu .ucm-sub { color:var(--text-faint,#8b949e); font-size:11px; }
.usercard-menu .ucm-note { color:var(--text-faint,#8b949e); font-size:11px; line-height:1.4; padding:6px 10px 4px; }
.side-foot { cursor:pointer; }
/* The power button is a real target ABOVE the row's click-to-open-downloads handler, so clicking
   the visible glyph reliably signs out (its own handler stops propagation to the row). */
.side-foot .side-power { position: relative; z-index: 2; flex: 0 0 auto; }
.oracle-talkbtn { background:transparent; border:1px solid #33436e; color:var(--accent-bright); font-family:inherit; font-weight:bold; font-size:12px; letter-spacing:1px; border-radius:var(--radius-sm); padding:0 14px; cursor:pointer; white-space:nowrap; }
.oracle-talkbtn:hover { background:rgba(122,162,255,.12); }
.oracle-talkbtn.rec { border-color:#f2687a; color:#f2687a; background:rgba(242,104,122,.12); animation:oraclePulse 1s ease-in-out infinite; }
@keyframes oraclePulse { 0%,100%{ box-shadow:0 0 0 0 rgba(242,104,122,.5); } 50%{ box-shadow:0 0 0 5px rgba(242,104,122,0); } }

/* ============================ GAME CENTER (chess) ============================ */
.game-layout{display:flex;gap:22px;align-items:flex-start;flex-wrap:wrap}
/* The board and its material readout stack: the "up by how much · vs Stockfish"
   line is a CAPTION under the board, not a sibling column beside it. */
.board-col{display:flex;flex-direction:column;min-width:0}
.game-board-wrap{display:flex;gap:10px;align-items:stretch}
/* (the always-hidden eval bar and its CSS were removed — the advantage is
   intentionally not shown, so it was dead DOM + a stray empty flex slot) */
.chessboard{position:relative;display:grid;grid-template-columns:repeat(8,1fr);grid-template-rows:repeat(8,1fr);
  width:min(72vh,560px);height:min(72vh,560px);border-radius:var(--radius);overflow:hidden;box-shadow:0 18px 50px -20px #000;border:1px solid var(--stroke)}
/* Board squares are tokenised and carry a faint periwinkle cast, so the board sits
   inside the Atmos identity instead of a disconnected neutral slate — and themes
   with the accent rather than staying frozen at two hex literals. */
.chessboard{--sq-light:color-mix(in srgb, var(--accent) 10%, #2a3341);
  --sq-dark:color-mix(in srgb, var(--accent) 8%, #1b2430)}
.sqr{position:relative;display:flex;align-items:center;justify-content:center;user-select:none}
.sqr.light{background:var(--sq-light,#2a3341)}
.sqr.dark{background:var(--sq-dark,#1b2430)}
.sqr.lastmove{box-shadow:inset 0 0 0 100px rgba(91,140,255,.14)}
.sqr.sel{box-shadow:inset 0 0 0 3px var(--accent)}
.sqr.premove{box-shadow:inset 0 0 0 100px rgba(242,104,122,.28)}
/* King-in-check pulse — a soft rose glow that settles, not a strobe (on-brand: state, not gaud). */
.sqr.incheck{box-shadow:inset 0 0 0 3px rgba(242,104,122,.85);animation:sqCheck 1.6s var(--ease-out-quint) infinite}
@keyframes sqCheck{0%,100%{box-shadow:inset 0 0 0 3px rgba(242,104,122,.85)}50%{box-shadow:inset 0 0 0 3px rgba(242,104,122,.85),inset 0 0 26px rgba(242,104,122,.45)}}
.sqr .piece{width:88%;height:88%;pointer-events:none;filter:drop-shadow(0 2px 3px rgba(0,0,0,.4))}
/* Move slide — transform-only (no layout thrash); the mover rides from origin to home.
   .cap-ghost is the taken piece, layered above and fading/scaling out as the mover lands. */
.sqr .piece.sliding{transition:transform .22s var(--ease-out-quint);will-change:transform;z-index:3}
.sqr .piece.cap-ghost{position:absolute;top:6%;left:6%;width:88%;height:88%;z-index:2;pointer-events:none;
  transform-origin:center;animation:capGhost .24s var(--ease-out-quint) forwards}
@keyframes capGhost{from{opacity:.95;transform:scale(1)}to{opacity:0;transform:scale(.55)}}
@media (prefers-reduced-motion:reduce){
  .sqr.incheck{animation:none;box-shadow:inset 0 0 0 3px rgba(242,104,122,.85)}
  .sqr .piece.sliding{transition:none}
  .sqr .piece.cap-ghost{animation:none;display:none}
}
.sqr.target::after{content:'';width:30%;height:30%;border-radius:50%;background:rgba(91,140,255,.45)}
/* %-based capture ring: a fixed 4px border was heavy on a phone-sized board and
   hairline on a big one. A radial-gradient ring scales with the square. */
/* Capture ring is RED (not the periwinkle of a quiet move) so "you can take here" —
   including en passant — reads at a glance. */
.sqr.capture::before{content:'';position:absolute;inset:6%;border-radius:50%;
  background:radial-gradient(circle, transparent 0 76%, rgba(242,104,122,.62) 76% 100%)}
.sqr .coord{position:absolute;font-size:10px;font-weight:700;color:rgba(232,238,245,.5)}
.sqr .coord.rank{top:2px;left:3px}
.sqr .coord.file{bottom:1px;right:4px}
/* The hint arrow eases in on the app's curve like every other board affordance
   (slide, capture ghost, check pulse) instead of hard-popping the SVG. */
.best-arrow{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;
  transform-origin:center;animation:bestArrowIn .26s var(--ease-out-quint) both}
@keyframes bestArrowIn{from{opacity:0;transform:scale(.94)}to{opacity:1;transform:none}}
@media (prefers-reduced-motion:reduce){.best-arrow{animation:none}}
.game-side{flex:1;min-width:280px;max-width:420px;display:flex;flex-direction:column;gap:12px}
.game-tabs{display:flex;gap:6px}
.game-tab{flex:1;padding:9px;border-radius: var(--radius-sm);border:1px solid var(--stroke);background:var(--panel-solid);color:var(--text-dim);font-weight:600;cursor:pointer;transition:color .15s var(--ease-out-quint),border-color .15s var(--ease-out-quint)}
.game-tab:hover:not(.on){color:var(--text);border-color:var(--stroke-strong)}
.game-tab.on{background:var(--accent);color:var(--accent-ink);border-color:var(--accent)}
.game-status{padding:9px 12px;border-radius: var(--radius-sm);background:var(--panel-solid);border:1px solid var(--stroke);font-weight:600;font-size:13px}
.game-row{display:flex;align-items:center;gap:var(--space-sm);margin-top:var(--space-sm);flex-wrap:wrap}
.game-num{width:74px;background:var(--bg-2);color:var(--text);border:1px solid var(--stroke-strong);border-radius:var(--radius-sm);padding:6px 8px;font-family:var(--mono)}
.game-slider{flex:1;min-width:120px;accent-color:var(--accent)}
.game-check{display:flex;align-items:center;gap:var(--space-xs);font-size:13px;color:var(--text)}
.game-fen{flex:1;min-width:140px;background:var(--bg-2);color:var(--text);border:1px solid var(--stroke-strong);border-radius:var(--radius-sm);padding:7px 9px;font-family:var(--mono);font-size:12px}
/* Keyboard focus was invisible on the game inputs — give them the standard
   periwinkle border + 3px ring the rest of the app's fields use. */
.game-num:focus,.game-num:focus-visible,.game-fen:focus,.game-fen:focus-visible,.game-slider:focus-visible{
  outline:none;border-color:var(--accent);box-shadow:0 0 0 3px rgba(91,140,255,.14)}
.game-moves{max-height:200px;overflow:auto;font-family:var(--mono);font-size:12.5px;line-height:1.9;color:var(--text-dim)}
.game-moves .mv{display:inline-block;margin-right:10px}
.chaos-menu{position:fixed;top:0;left:0;height:100vh;width:314px;background:linear-gradient(180deg,#10141b,#0a0e13);
  border-right:1px solid rgba(242,104,122,.5);box-shadow:14px 0 44px -12px #000,inset -1px 0 0 rgba(242,104,122,.14);
  z-index:var(--z-menu);padding:18px 16px;display:flex;flex-direction:column;gap:9px;overflow-y:auto;overflow-x:hidden;animation:chaosIn .2s cubic-bezier(.2,.8,.2,1)}
.chaos-menu::-webkit-scrollbar{width:8px}
.chaos-menu::-webkit-scrollbar-thumb{background:#2a3542;border-radius:4px}
@keyframes chaosIn{from{transform:translateX(-100%);opacity:.5}to{transform:translateX(0);opacity:1}}
.chaos-head{font-size:16px;font-weight:800;color:var(--bad);letter-spacing:2.5px;display:flex;align-items:center;gap:8px}
.chaos-sub{font-size:10px;color:var(--text-faint);letter-spacing:1px;text-transform:uppercase;margin:-5px 0 3px;font-family:ui-monospace,"SF Mono",Menlo,monospace}
.chaos-sec{font-size:11.5px;font-weight:700;color:var(--accent);letter-spacing:1.3px;text-transform:uppercase}
.chaos-menu .game-check{padding:3px 0}
.chaos-menu .game-row{gap:8px;align-items:center}
.game-lobby{display:flex;flex-direction:column;gap:6px;max-height:220px;overflow:auto}
.lobby-row{display:flex;align-items:center;justify-content:space-between;gap:var(--space-sm);padding:var(--space-sm) 10px;border-radius:var(--radius-sm);background:var(--bg-2);border:1px solid var(--stroke)}
.lobby-row .btn.sm{padding:4px 10px;font-size:12px}

/* ---- context-usage gauge (Claude-Code-style ring) ---- */
.tok-gauge{display:inline-flex;align-items:center;gap:var(--space-xs);background:transparent;border:1px solid var(--stroke);border-radius:var(--radius-pill);padding:3px 10px 3px 4px;cursor:pointer;color:var(--text-dim);font-size:11px;font-weight:600}
.tok-gauge:hover{border-color:var(--stroke-strong);color:var(--text)}
.tok-gauge svg{width:20px;height:20px;transform:rotate(-90deg)}
.tok-gauge .tok-bg{fill:none;stroke:var(--stroke-strong);stroke-width:4}
.tok-gauge .tok-fg{fill:none;stroke:var(--accent);stroke-width:4;stroke-linecap:round;transition:stroke-dashoffset .3s ease,stroke .3s ease}
.tok-pop{position:fixed;z-index:var(--z-tooltip);background:var(--panel-solid);border:1px solid var(--stroke-strong);border-radius:var(--radius);padding:14px var(--space-lg);box-shadow:0 18px 50px -18px #000;min-width:200px}
.tok-pop-title{font-size:12px;font-weight:700;letter-spacing:.14em;color:var(--text-faint);text-transform:uppercase}

/* ==========================================================================
   MOTION LAYER — product-register interaction feedback. Motion conveys STATE
   (hover intent, press, focus, live activity), never decoration for its own
   sake. All timing runs on the shared easing tokens; the reduced-motion block
   at the very end neutralises every bit of it. Additive — no layout changes.
   ========================================================================== */

/* One easing voice for interaction. These elements already define transitions;
   we only swap their default `ease` for the brand's ease-out-quint so every
   hover/press decelerates the same considered way. */
.btn, .nav-item, .model-chip, .agent-chip, .pill, .file-chip, .artifact-chip,
.model-dd-btn, .mini-select, .plan-toggle, .ad-tab, .rail-tabs button,
.auth-tabs button, .studio-modes .smode, .engine-card, .tok-gauge,
input, textarea, select, .drop, .thumb, .src-thumb, .gcard, .model-card,
.proj-item, .side-foot, .think-head, .model-dd-row, .file-row {
  transition-timing-function: var(--ease-out-quint);
}

/* The clickable divs promoted to keyboard controls had no transition of their
   own, so their hover state snapped. Give them the same calm border/bg/transform
   decel as the rest of the bench (press + focus handled in the motion layer). */
.proj-item, .side-foot, .think-head, .model-dd-row, .file-row[role="button"] {
  transition: background .15s var(--ease-out-quint), color .15s var(--ease-out-quint),
    border-color .15s var(--ease-out-quint), box-shadow .15s var(--ease-out-quint), transform .09s var(--ease-out-quint);
}

/* Consistent hover intent: a periwinkle-blue hairline + a soft periwinkle-blue glow (the accent's
   own "elevation"), unifying the many one-off hover states. */
.model-dd-btn:hover, .mini-select:hover, .file-chip:hover, .artifact-chip:hover,
.engine-card:hover:not(.off), .drop:hover, .drop.over {
  box-shadow: 0 0 0 1px rgba(91,140,255, 0.14), 0 0 16px -6px rgba(91,140,255, 0.5);
}

/* Honest press — a quick, uniform scale-in on click for every tappable control,
   including the now-keyboard-reachable clickable divs (proj/side-foot/think/etc). */
.btn:not(:disabled):active, .nav-item:active, .model-chip:active, .agent-chip:active,
.pill:active, .plan-toggle:active, .ad-tab:active, .rail-tabs button:active,
.auth-tabs button:active, .studio-modes .smode:active, .studio-tab:active, .mini-select:active,
.model-dd-btn:active, .tok-gauge:active, .proj-item:active, .side-foot:active,
.think-head:active, .model-dd-row:active, .file-row[role="button"]:active, .gcard:active {
  transform: scale(0.98);
}

/* Keyboard focus keeps its outline (a11y) and gains the same periwinkle glow the
   mouse gets, so focus reads as "live" not just "outlined". Now that the clickable
   divs are real tab stops, they light up the same way. */
.btn:focus-visible, .model-dd-btn:focus-visible, .mini-select:focus-visible,
input:focus-visible, textarea:focus-visible, select:focus-visible,
.plan-toggle:focus-visible, .model-chip:focus-visible, .nav-item:focus-visible,
.proj-item:focus-visible, .side-foot:focus-visible, .think-head:focus-visible,
.model-dd-row:focus-visible, .agent-chip:focus-visible, .file-row[role="button"]:focus-visible,
.gcard:focus-visible, .gcard .gdel:focus-visible {
  box-shadow: 0 0 0 3px rgba(91,140,255, 0.16), 0 0 18px -6px rgba(91,140,255, 0.45);
}

/* Placeholder legibility — pull it off the faint gray so it clears the
   readable-contrast floor (DESIGN: placeholders never faint-gray). */
input::placeholder, textarea::placeholder { color: var(--text-dim); opacity: 1; }

/* Live agent activity reads as alive: the pill's rest state breathes a soft
   periwinkle-blue glow while an agent is working, reinforcing the "something is happening"
   signal already carried by its token counter. */
.agent-activity:not(.hide) { animation: liveGlow 2.4s ease-in-out infinite; }
@keyframes liveGlow {
  0%, 100% { box-shadow: 0 0 0 1px rgba(91,140,255, 0.10); }
  50% { box-shadow: 0 0 16px -3px rgba(91,140,255, 0.5), 0 0 0 1px rgba(91,140,255, 0.3); }
}

/* Active nav item is positioned so the static HUD marker (::before, defined below) can anchor to it. */
.nav-item.on { position: relative; }

/* ==========================================================================
   REDUCED MOTION — the global escape hatch. Anyone who asks for less motion
   gets a near-instant, still interface (the login globe, decrypt reveals and
   loaders already opt out individually; this covers everything else too).
   Kept LAST so it wins the cascade.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* ==========================================================================
   ATMOS HUD RE-SKIN — floating glass modules over an animated soft-lit ground.
   This block is a VISUAL LAYER: it re-skins the existing live DOM classes
   (.shell/.sidebar/.main/.rail/.chat-head/.composer/.nav-item/.proj-item/…)
   into the approved "Atmos HUD" look. It is appended last so it wins the
   cascade at equal specificity; nothing here changes app logic or markup
   structure beyond the small class hooks added in app.js.
   ========================================================================== */
:root {
  --ground: #080b12; --ground-2: #0b0f18;
  --glass: rgba(22,28,42,0.55); --glass-2: rgba(28,35,52,0.62); --glass-hi: rgba(255,255,255,0.07);
  /* PERF: a near-opaque panel fill that reads as lit glass WITHOUT a backdrop-filter blur.
     Used on the always-visible modules so the ground shows through only as tone, not a live blur. */
  --glass-solid: rgba(19,25,38,0.90);
  --orb-a: #cdd9ff; --orb-b: #2c4bb0;
  --glow: 0 0 0 1px rgba(255,255,255,0.06), 0 1px 0 0 rgba(255,255,255,0.07) inset,
          0 24px 60px -28px rgba(0,0,0,0.9), 0 0 40px -30px rgba(91,140,255,0.6);
  --ease: cubic-bezier(.22,1,.36,1);
}
body { background: var(--ground); }

/* ── animated ground + masked grid + cursor spotlight (injected in app.js) ── */
.ground { position: fixed; inset: 0; z-index: 0; overflow: hidden; pointer-events: none; }
/* PERF: the ground is now a STATIC soft-lit gradient. The two big blurred glows used to drift
   forever (two large blurred layers recomposited every frame) — the single biggest always-on GPU
   cost after the module blurs. A static radial-glow ground looks ~identical and costs ZERO
   continuous GPU. No animation, no will-change layer promotion. */
.ground::before, .ground::after { content: ""; position: absolute; border-radius: 50%; filter: blur(46px); opacity: .34; }
/* Pause ALL animation when the tab is backgrounded — stops the logomark/orb-css/spins burning CPU on idle tabs. */
body.tab-idle *, body.tab-idle *::before, body.tab-idle *::after { animation-play-state: paused !important; }
.ground::before { width: 52vw; height: 52vw; left: -10vw; top: -20vw;
  background: radial-gradient(circle, rgba(91,140,255,.5), transparent 62%); }
.ground::after { width: 48vw; height: 48vw; right: -12vw; bottom: -18vw;
  background: radial-gradient(circle, rgba(139,143,245,.42), transparent 62%); }
.grid { position: fixed; inset: 0; z-index: 0; pointer-events: none; opacity: .35;
  background-image: linear-gradient(rgba(255,255,255,.03) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,.03) 1px, transparent 1px);
  background-size: 44px 44px; -webkit-mask-image: radial-gradient(ellipse 80% 70% at 50% 40%, #000 40%, transparent 85%);
  mask-image: radial-gradient(ellipse 80% 70% at 50% 40%, #000 40%, transparent 85%); }
.cglow { position: fixed; width: 440px; height: 440px; border-radius: 50%; z-index: 0; pointer-events: none;
  left: 50%; top: 40%; transform: translate(-50%,-50%); opacity: 0; transition: opacity .5s var(--ease);
  background: radial-gradient(circle, rgba(122,162,255,.18), transparent 62%); }
.spark { position: fixed; width: 8px; height: 8px; border-radius: 50%; background: var(--accent-bright);
  pointer-events: none; z-index: 9; transform: translate(-50%,-50%); animation: atmosSpark .5s var(--ease) forwards; }
@keyframes atmosSpark { from { opacity: 1; transform: translate(-50%,-50%) scale(1); } to { opacity: 0; transform: translate(-50%,-50%) scale(3.6); } }

/* ── shell: padded canvas, floating glass modules ── */
.shell { position: relative; z-index: 1; gap: 14px; padding: 14px; background: transparent; }
@keyframes atmosModIn { from { opacity: 0; transform: translateY(16px) scale(.992); filter: blur(5px); } to { opacity: 1; transform: none; filter: blur(0); } }
@keyframes atmosSlideRight { from { opacity: 0; transform: translateX(-10px); filter: blur(4px); } to { opacity: 1; transform: none; filter: blur(0); } }
@keyframes atmosRise { from { opacity: 0; transform: translateY(10px); filter: blur(4px); } to { opacity: 1; transform: none; filter: blur(0); } }
.sidebar, .main, .chat-main, .rail {
  /* PERF: these four modules are always on screen. A backdrop-filter blur on each is the single
     biggest continuous GPU cost on the page (four blurred layers recomposited every frame). We
     drop the blur entirely and fake glass with a near-opaque fill + 1px stroke + the inner
     top-highlight (::after) + the --glow shadow. Reads as lit glass, costs ~nothing at rest. */
  background: linear-gradient(180deg, rgba(255,255,255,.04), transparent 120px), var(--glass-solid);
  border: 1px solid var(--stroke); border-radius: var(--radius-lg); box-shadow: var(--glow);
  overflow: hidden; position: relative;
}
/* soft inner top-highlight so glass reads as lit from above */
.sidebar::after, .main::after, .chat-main::after {
  content: ""; position: absolute; inset: 0; border-radius: var(--radius-lg); pointer-events: none; z-index: 6;
  background: linear-gradient(180deg, rgba(255,255,255,.05), transparent 22%);
}
.sidebar { animation: atmosModIn .6s var(--ease) .02s backwards; }
.main { animation: atmosModIn .6s var(--ease) .10s backwards; }
/* In chat, .main wraps BOTH the center and the rail — dissolve it so chat-main and
   rail float as separate modules over the ground (their gap shows the ground). */
.main:has(.chat-cols) { background: transparent; -webkit-backdrop-filter: none; backdrop-filter: none;
  border: none; box-shadow: none; overflow: visible; animation: none; }
.main:has(.chat-cols)::before, .main:has(.chat-cols)::after { display: none; }
.chat-cols { gap: 14px; }
.chat-main { animation: atmosModIn .6s var(--ease) .10s backwards; }
.rail { animation: atmosModIn .6s var(--ease) .18s backwards; }

/* ── sidebar: brand logomark, nav icon-tiles, room cards ── */
.side-brand { border-bottom: 1px solid var(--stroke); }
.side-nav .nav-item { animation: atmosSlideRight .5s var(--ease) backwards; }
.side-nav .nav-item:nth-child(1) { animation-delay: .20s; } .side-nav .nav-item:nth-child(2) { animation-delay: .25s; }
.side-nav .nav-item:nth-child(3) { animation-delay: .30s; } .side-nav .nav-item:nth-child(4) { animation-delay: .35s; }
.side-nav .nav-item:nth-child(5) { animation-delay: .40s; } .side-nav .nav-item:nth-child(6) { animation-delay: .45s; }
.side-nav .nav-item:nth-child(n+7) { animation-delay: .50s; }
.nav-item .ic { width: 27px; height: 27px; text-align: center; border-radius: 8px; display: grid; place-items: center;
  font-size: 14px; opacity: .95; background: rgba(255,255,255,.035); border: 1px solid var(--stroke);
  transition: transform .28s var(--ease), background .2s, border-color .2s, color .2s; }
.nav-item:hover .ic { transform: scale(1.09) rotate(-4deg); background: var(--glass-2); }
.nav-item.on { color: var(--text); background: linear-gradient(90deg, rgba(91,140,255,.20), rgba(91,140,255,.06));
  border-color: rgba(91,140,255,.32); box-shadow: 0 0 20px -6px rgba(91,140,255,.5); }
.nav-item.on .ic { background: rgba(91,140,255,.16); border-color: rgba(91,140,255,.3); color: var(--accent-bright); }
/* static accent glow-bar on the active nav item (was an always-on opacity pulse; the selected
   state doesn't need to fidget to read as selected). */
.nav-item.on::before { content: ""; position: absolute; left: 0; top: 24%; bottom: 24%; width: 3px; height: auto;
  border-radius: 3px; transform: none; background: linear-gradient(var(--accent), var(--accent-2));
  box-shadow: 0 0 10px var(--accent); }

.proj-item { margin: 3px 0; padding: var(--space-md); border-radius: var(--radius); border: 1px solid transparent; }
.proj-item:hover { background: var(--glass-2); transform: translateX(3px); }
.proj-item.on { background: linear-gradient(90deg, rgba(91,140,255,.12), var(--glass-2));
  border-color: rgba(91,140,255,.24); box-shadow: 0 0 20px -9px rgba(91,140,255,.7); }
.proj-top { display: flex; align-items: center; gap: 8px; }
.proj-item .rmark { color: var(--accent); font-size: 12px; flex: 0 0 auto; filter: drop-shadow(0 0 5px rgba(91,140,255,.55)); }
/* active room mark holds a steady glow (was an always-on drop-shadow pulse = continuous filter repaint). */
.proj-item.on .rmark { filter: drop-shadow(0 0 9px rgba(91,140,255,.85)); }
.proj-item .pname { font-size: 13px; font-weight: 600; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; }
/* ONE accent carries every on/live/selected signal — good/warn/bad stay strictly for
   health state, so the active-room dot is periwinkle, not green. */
.proj-item .rlive { flex: 0 0 auto; width: 7px; height: 7px; border-radius: 50%; background: var(--accent);
  box-shadow: 0 0 7px var(--accent); animation: atmosBlink 2.4s ease infinite; }
@keyframes atmosBlink { 50% { opacity: .35; } }
@media (prefers-reduced-motion: reduce) { .proj-item .rlive { animation: none !important; } }
.proj-item .pmeta { display: flex; gap: 5px; margin-top: 9px; flex-wrap: wrap; }
.pmeta .tag { display: inline-flex; align-items: center; gap: 4px; font-size: 10.5px; color: var(--text-faint);
  background: rgba(255,255,255,.04); border: 1px solid var(--stroke); border-radius: 6px; padding: 2px 7px; transition: .18s var(--ease); }
.pmeta .tag svg { width: 11px; height: 11px; opacity: .85; }
.pmeta .tag.owner { color: var(--accent-bright); border-color: rgba(91,140,255,.24); background: rgba(91,140,255,.08); }
.proj-item:hover .pmeta .tag { border-color: var(--stroke-strong); color: var(--text-dim); }

/* ── crafted CYPHER hex logomark ──
   THERMAL: the resting brand mark used to run ~5 infinite animations forever — a spin, a shimmer,
   a core pulse, a whole-mark breathe, AND a stroke-trace that re-rasterised a drop-shadow filter
   every frame (expensive). At rest it now holds STILL except for ONE cheap, very slow opacity
   pulse on the core. The lively version is scoped to the TRANSIENT loader variant (.cmark-loader:
   boot splash, "thinking", panel loads) only, and everything is reduced-motion-guarded below. */
.cmark { width: 28px; height: 28px; flex: 0 0 auto; overflow: visible;
  filter: drop-shadow(0 0 8px rgba(91,140,255,.5)); }
.cmark .hex-o { opacity: .5; }
.cmark .hex-trace { stroke-dasharray: 20 80; stroke-dashoffset: 0; }
.cmark .hex-i { transform-origin: 16px 16px; opacity: .72; }
.cmark .core { transform-origin: 16px 17px; animation: atmosCcorePulse 7s ease-in-out infinite; }
@keyframes atmosCcorePulse { 0%,100% { opacity: .68; } 50% { opacity: 1; } }

/* LOADER variant only (transient states) — the full lively mark: breathe, tracing light, inner
   spin/shimmer, core pulse. Later source order → these win over the calm resting rules above. */
.cmark-loader { animation: atmosCbreathe 6s ease-in-out infinite; }
@keyframes atmosCbreathe { 0%,100% { transform: scale(1); } 50% { transform: scale(1.045); } }
.cmark-loader .hex-trace { filter: drop-shadow(0 0 3px var(--accent-bright)); animation: atmosTrace 2.9s linear infinite; }
@keyframes atmosTrace { to { stroke-dashoffset: -100; } }
.cmark-loader .hex-i { animation: atmosCspin 17s linear infinite reverse, atmosIshimmer 4.2s ease-in-out infinite; }
@keyframes atmosCspin { to { transform: rotate(360deg); } }
@keyframes atmosIshimmer { 0%,100% { opacity: .55; } 50% { opacity: 1; } }
.cmark-loader .core { animation: atmosCpulse 2.9s var(--ease) infinite; }
/* Make the STATIC brand (top-left) + big home/login logomarks visibly ALIVE — tracing light + inner
   shimmer/spin — not only the transient loader variant. */
.side-brand .cmark .hex-trace, .cmark-lg .hex-trace, .login-card .cmark .hex-trace { filter: drop-shadow(0 0 3px var(--accent-bright)); animation: atmosTrace 3.6s linear infinite; }
.side-brand .cmark .hex-i, .cmark-lg .hex-i, .login-card .cmark .hex-i { animation: atmosCspin 22s linear infinite reverse, atmosIshimmer 4.8s ease-in-out infinite; }
@keyframes atmosCpulse { 0%,100% { opacity: .7; transform: scale(.78); } 50% { opacity: 1; transform: scale(1.18); } }

/* ── chat head: glass topbar + Oracle presence orb ── */
.chat-head { background: linear-gradient(180deg, rgba(255,255,255,.03), transparent);
  -webkit-backdrop-filter: none; backdrop-filter: none; border-bottom: 1px solid var(--stroke); }
.chat-head > * { animation: atmosRise .55s var(--ease) backwards; }
.chat-head-left { animation-delay: .16s; } .chat-head-center { animation-delay: .22s; } .chat-head-right { animation-delay: .30s; }
.orb { position: relative; width: 30px; height: 30px; flex: 0 0 auto; }
.orb i { position: absolute; inset: 0; border-radius: 50%; display: block; }
.orb .core { inset: 8px; background: radial-gradient(circle at 38% 34%, var(--orb-a), var(--accent) 58%, var(--orb-b));
  box-shadow: 0 0 16px -2px rgba(91,140,255,.9); animation: atmosBreathe 4.6s var(--ease) infinite; }
.orb .ring { border: 1.5px solid rgba(91,140,255,.5); animation: atmosOrbPulse 4.6s var(--ease) infinite; }
.orb .ring2 { border: 1px solid rgba(139,143,245,.35); animation: atmosOrbPulse 4.6s var(--ease) infinite .6s; }
@keyframes atmosBreathe { 0%,100% { transform: scale(.9); filter: brightness(.9); } 50% { transform: scale(1.06); filter: brightness(1.15); } }
@keyframes atmosOrbPulse { 0% { transform: scale(.7); opacity: .8; } 70% { opacity: 0; } 100% { transform: scale(1.5); opacity: 0; } }
.orb.think .core { animation-duration: 1.4s; background: radial-gradient(circle at 38% 34%, #fff, var(--accent-bright) 55%, #3a5ed0); }
.orb.think .ring, .orb.think .ring2 { animation-duration: 1.4s; }
.orb.speak .core { animation: atmosSpeak .5s var(--ease) infinite; }
@keyframes atmosSpeak { 0%,100% { transform: scale(.94); } 50% { transform: scale(1.12); box-shadow: 0 0 26px 0 rgba(91,140,255,1); } }
.room-glyph { display: none; width: 48px; height: 48px; border-radius: 13px; place-items: center; flex: 0 0 auto;
  color: var(--accent); font-size: 22px; background: var(--glass-2); border: 1px solid var(--stroke); }
/* Keep the header a steady height whether Cypher's 56px orb or the away glyph is showing. */
.chat-head { min-height: 56px; }
body.away .orb { display: none; }
body.away .room-glyph { display: grid; }

/* ==========================================================================
   P2 — SERVER-RENDERED presence sprite. The 3-D orb's rotation is baked into
   one sheet server-side (/assets/orb-sprite.png); the client plays it as a
   pure-CSS steps() background-position loop — GPU-composited, ~zero CPU, no
   per-frame JS. States only change the spin duration / add a scale pulse.
   --orb-frames + --orb-endx are set inline per element (they depend on the box
   size); the sheet is a single horizontal strip of `frames` frames.
   ========================================================================== */
.sprite-orb { display: block; background-repeat: no-repeat; background-position: 0 0;
  border-radius: 50%; filter: drop-shadow(0 0 10px rgba(91,140,255,.45));
  animation: spriteSpin 9s steps(var(--orb-frames, 120)) infinite; will-change: background-position; }
.sprite-orb:not(.ready) { background: radial-gradient(circle at 40% 36%, var(--orb-a, #cdd9ff), var(--accent) 58%, var(--orb-b, #2c4bb0)); animation: none; }
@keyframes spriteSpin { to { background-position-x: var(--orb-endx, -100%); } }
/* thinking = faster spin; speaking = faster spin + a soft scale pulse (two simultaneous animations) */
.sprite-orb.think { animation-duration: 2.6s; filter: drop-shadow(0 0 13px rgba(122,162,255,.7)); }
.sprite-orb.speak { animation: spriteSpin 3.4s steps(var(--orb-frames, 120)) infinite, spriteOrbPulse .5s var(--ease) infinite;
  filter: drop-shadow(0 0 18px rgba(139,143,245,.9)); }
@keyframes spriteOrbPulse { 0%,100% { transform: scale(.97); } 50% { transform: scale(1.08); } }
/* interactive (the big Oracle sphere): hover speeds it up + brightens */
.sprite-orb.interactive { cursor: pointer; transition: filter .3s var(--ease); }
.sprite-orb.interactive:hover { animation-duration: 4s; filter: drop-shadow(0 0 22px rgba(139,143,245,.95)) brightness(1.15); }
.sprite-orb.interactive.speak:hover { animation-duration: 2.4s; }
/* pause when the tab is hidden OR Performance mode is on (P4) — the freeze escape hatch */
body.tab-idle .sprite-orb, body.perf-mode .sprite-orb { animation-play-state: paused !important; }
body.perf-mode .sprite-orb.speak { transform: none !important; }
@media (prefers-reduced-motion: reduce) { .sprite-orb { animation: none !important; background-position-x: 0 !important; } }

/* ==========================================================================
   v2 — HARDWARE-DECODED presence VIDEO. The sphere's 360° spin is pre-baked on
   the server into a transparent looping WebM (/assets/orb.webm, VP9 + alpha) and
   played in a <video>. GPU-decoded → ~zero CPU, no per-frame compositing (unlike
   the sprite sheet it replaces). NO always-on CSS animation lives here: idle/think
   only change the video's playbackRate (JS); .speak adds a cheap, TRANSIENT scale
   pulse; hover brightens the big one. This is the thermal fix.
   ========================================================================== */
/* TRANSPARENT orbs (v6): the video has a real alpha channel, so the particles float on whatever is
   behind — the stage's local periwinkle lighting shows around AND between the dots (no black square).
   The drop-shadow follows the actual sphere shape (alpha), so it's a soft CIRCULAR periwinkle glow —
   the "local purple lighting", never a box. */
.video-orb { display: block; border-radius: 50%; object-fit: contain; background: transparent;
  filter: drop-shadow(0 0 12px rgba(91,140,255,.38)); pointer-events: none; transition: opacity .5s ease; }
/* speaking = a SMOOTH, gentle scale pulse (compositor-only), shown ONLY while .speak is set.
   ease-OUT only (--ease = cubic-bezier(.22,1,.36,1), y never exceeds 1) → NO bounce/overshoot. */
.video-orb.speak { animation: videoOrbPulse 1.1s var(--ease) infinite; }
@keyframes videoOrbPulse { 0%,100% { transform: scale(1); } 50% { transform: scale(1.045); } }
/* the big interactive sphere reacts to hover (brightness + a small scale bump) */
.video-orb.interactive { cursor: pointer; pointer-events: auto; transition: filter .3s var(--ease), transform .3s var(--ease); }
.video-orb.interactive:hover { filter: drop-shadow(0 0 24px rgba(123,162,255,.6)) brightness(1.16); transform: scale(1.04); }
.video-orb.interactive.speak:hover { transform: scale(1.1); }
/* Performance mode / reduced-motion: kill the (transient) speak pulse. Video decode itself is
   paused from JS (video.pause()) under perf-mode and while the tab is hidden — not a CSS cost. */
body.perf-mode .video-orb.speak { animation: none !important; transform: none !important; }
@media (prefers-reduced-motion: reduce) { .video-orb.speak { animation: none !important; transform: none !important; } }

/* P1/v2 — the big central Oracle presence sphere, centered in the stage (P7: eases in on view load) */
.oracle-orb-big { position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%); z-index: 3;
  display: grid; place-items: center; overflow: visible;
  animation: orbBigIn .7s var(--ease) .1s backwards; }
@keyframes orbBigIn { from { opacity: 0; transform: translate(-50%,-50%) scale(.86); } to { opacity: 1; transform: translate(-50%,-50%) scale(1); } }
@media (prefers-reduced-motion: reduce) { .oracle-orb-big { animation: none; } }
/* GLOW REMOVED: the big presence is a bare <canvas> (no .sprite-orb/.video-orb class), so these
   drop-shadow halos never reached it anyway — dropped so nothing here can re-introduce a glow. */
/* Spawned SUB-AGENT figures: small coloured orbs that POP OUT and orbit AROUND the sphere (in front,
   never stacked behind it). Centering rides the individual `translate` property so the pop `scale`
   and the idle-float `transform` compose cleanly instead of clobbering each other. */
.oracle-agent-bg { position: absolute; inset: 0; z-index: 4; pointer-events: none; overflow: visible; }
.agent-orb-host { position: absolute; translate: -50% -50%; scale: 0; opacity: 0;
  animation: agentOrbPop .62s var(--ease-out-quint) forwards, agentFloat 5s ease-in-out .62s infinite; }
/* Ease-out only, no bounce (house motion rule): the agent scales up and SETTLES —
   it never overshoots past its resting size. */
@keyframes agentOrbPop { 0% { opacity: 0; scale: .4; } 100% { opacity: .92; scale: 1; } }
@keyframes agentFloat { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-11px); } }
@media (prefers-reduced-motion: reduce) { .agent-orb-host { animation: none; opacity: .85; scale: 1; } }
/* v2 — LARGE full-width frequency visualizer strip anchored to the BOTTOM of the Oracle stage.
   Taller + more bars than the old 220px chip; reacts to the real analyser while Oracle speaks,
   hidden (flat) when silent. Reduced-motion → static (the JS draw loop no-ops). */
.oracle-stage-wave { position: absolute; left: 0; right: 0; bottom: 0; z-index: 3;
  width: 100%; max-width: 100%; height: 92px; display: none;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent); }
body.butler-speaking .oracle-stage-wave { display: block; }
/* lift the STANDBY/SPEAKING status label clear of the tall bottom visualizer strip
   (92px), while leaving a real gap under the transcript at bottom:118px — at 108px a
   two-line status could collide with the newest bubble. */
.oracle-status { bottom: 96px; }

/* ── docked, floating glass composer with a slow rotating star-border glow ── */
@property --atmos-a { syntax: '<angle>'; inherits: false; initial-value: 0deg; }
/* Lit glass WITHOUT a live backdrop-filter: the composer is on screen for the whole
   session, so a real-time blur here is the perpetual GPU cost DESIGN.md bans. The
   fixed diagonal sheen over a near-opaque fill gives the same frosted read for free.
   The conic star-border and the periwinkle glow are unchanged. */
.composer { border-top: none; margin: var(--space-md) var(--space-lg) var(--space-lg); padding: var(--space-md);
  border: 1px solid var(--stroke-strong); border-radius: var(--radius-lg);
  background: linear-gradient(150deg, rgba(52,64,94,.45), rgba(18,24,38,.15) 55%), var(--glass-2);
  box-shadow: 0 18px 44px -22px rgba(0,0,0,.9), 0 0 34px -26px rgba(91,140,255,.7); }
.composer::before { content: ""; position: absolute; inset: -1px; border-radius: calc(var(--radius-lg) + 1px); padding: 1px; pointer-events: none; z-index: 1;
  background: conic-gradient(from var(--atmos-a), transparent 0 60%, rgba(91,140,255,.5) 78%, rgba(139,143,245,.7) 89%, transparent 100%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude; }
/* PERF: the star-border was a redundant always-on conic repaint every frame. Now it holds a
   static glow at rest and only SPINS while you're actually using the composer (hover/focus). */
.composer:hover::before, .composer:focus-within::before { animation: atmosStarSpin 16s linear infinite; }
@keyframes atmosStarSpin { to { --atmos-a: 360deg; } }
/* The composer's controls are load-bearing, not decoration: a .5–.68s stagger meant
   the Plan/Swarm toggles and the token gauge were visibly ABSENT for half a second on
   every room open. Near-simultaneous now, and only for people who want motion. */
@media (prefers-reduced-motion: no-preference) {
  .composer-bar > * { animation: atmosRise .5s var(--ease) backwards; }
  .composer-bar > *:nth-child(1) { animation-delay: 0s; } .composer-bar > *:nth-child(2) { animation-delay: .04s; }
  .composer-bar > *:nth-child(3) { animation-delay: .08s; } .composer-bar > *:nth-child(n+4) { animation-delay: .12s; }
}

/* ── right rail: glass module + tabs ── */
.rail { border-left: 1px solid var(--stroke); }
.rail-body > * { animation: atmosRise .5s var(--ease) backwards; }

/* Agents dock: Oracle presence block, the live "Running" list, section labels. */
.rail-lbl { font-size: 10.5px; letter-spacing: .12em; text-transform: uppercase; color: var(--text-faint);
  font-weight: 700; margin: 18px 2px 9px; }
.pres { display: flex; align-items: center; gap: var(--space-md); padding: var(--space-md); border-radius: var(--radius);
  background: var(--glass-2); border: 1px solid var(--stroke); box-shadow: 0 1px 0 0 var(--glass-hi) inset; }
.pres .pn { font-size: 13px; font-weight: 650; }
.pres .ps { font-size: 11px; color: var(--good); margin-top: 3px; display: flex; align-items: center; gap: 6px; }
.pres .ps.off { color: var(--text-faint); }
.pres .ps-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--good); box-shadow: 0 0 8px var(--good); }
.pres.away { opacity: .78; }
.agent-list { display: flex; flex-direction: column; gap: 8px; }
.agent { display: flex; align-items: center; gap: 11px; padding: var(--space-md); border-radius: var(--radius);
  border: 1px solid var(--stroke); background: var(--glass); transition: border-color .2s var(--ease); }
.agent:hover { border-color: var(--stroke-strong); }
.agent .ad { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; background: var(--accent);
  box-shadow: 0 0 8px var(--accent); animation: atmosBlink 1.6s ease infinite; }
.agent .an { font-size: 12.5px; font-weight: 600; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.agent .at { font-size: 11px; color: var(--text-faint); font-family: var(--mono); white-space: nowrap; }
.rail-idle { color: var(--text-faint); font-size: 12px; padding: 4px 2px; }

/* ── rich chat content: scene chip + italic thoughts + prettier code ── */
/* The [date · location · time] stamp opens a scene — it sits on its OWN line above
   the prose (block + shrink-to-fit), never inline with the first sentence. */
.bubble .scene { display: block; width: max-content; max-width: 100%;
  font-family: var(--mono); font-size: 11px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--accent-bright); padding: 5px 11px; border: 1px solid rgba(91,140,255,.28); border-radius: var(--radius-sm);
  background: rgba(91,140,255,.08); margin: 0 0 10px; }
.bubble em { color: #c3ccde; font-style: italic; }
.bubble strong { color: var(--text, #fff); }   /* was hard-coded #fff — invisible on a light-theme bubble; track the theme's text color instead */
.bubble code { color: var(--accent-bright); }
.bubble pre { background: rgba(6,9,14,.72); border-radius: 12px; transition: border-color .2s var(--ease); }
.bubble pre:hover { border-color: var(--stroke-strong); }
/* compact tool card (available for tool-result rows) */
.tool-card { align-self: flex-start; max-width: 82%; display: flex; align-items: center; gap: 9px; padding: 9px 14px;
  border-radius: 11px; border: 1px solid var(--stroke); background: var(--glass); font-size: 12.5px; color: var(--text-dim); }
.tool-card .tok { margin-left: auto; color: var(--good); font-family: var(--mono); font-size: 11px; }

/* ── LANDING + LOGIN screens (over the same animated ground) ──
   SECURITY: .shell is NOT in the DOM before sign-in, so these frosted screens
   blur ONLY the abstract ground — never account data. Decorative backdrop only. */
/* DECORATIVE ghost-UI behind the frosted glass — abstract panels, NO data — so the
   glass reads as "an app behind glass" while nothing real is in the DOM pre-auth.
   SECURITY: purely cosmetic shapes; the live console (.shell) is never rendered until
   sign-in, so even the blur exposes only these empty panels + the animated ground. It
   lives inside #app, so it's removed the instant the console mounts (and body.entered
   hides it as a belt-and-suspenders guard). */
/* Richer version (ported from the Atmos preview): abstract panels PLUS a bright
   accent orb, two message bubbles (one accent), an accent send button and a card —
   so the frosted glass reads clearly as a colourful app behind it. Still purely
   decorative: no real data is ever in these shapes, and body.entered removes it. */
.auth-backdrop { position: fixed; inset: 20px; z-index: 5; pointer-events: none; }
body.entered .auth-backdrop { display: none; }
.auth-backdrop span { position: absolute; border-radius: 16px; border: 1px solid var(--stroke); }
.ab-rail { left: 0; top: 0; bottom: 0; width: 210px; background: linear-gradient(180deg, rgba(91,140,255,.20), rgba(28,35,52,.72)); }
.ab-main { left: 232px; right: 300px; top: 0; bottom: 112px; background: linear-gradient(160deg, rgba(30,38,58,.78), rgba(20,26,40,.72)); }
.ab-side { right: 0; top: 0; bottom: 0; width: 284px; background: linear-gradient(180deg, rgba(139,143,245,.20), rgba(28,35,52,.72)); }
.ab-bar { left: 232px; right: 300px; bottom: 0; height: 94px; background: rgba(32,40,60,.8); border-color: var(--stroke-strong); }
.ab-orb { left: 37%; top: 15%; width: 130px; height: 130px; border-radius: 50%; border: none; background: radial-gradient(circle, rgba(160,192,255,.95), rgba(91,140,255,.55) 52%, transparent 72%); }
.ab-msg1 { left: 270px; top: 45%; width: 290px; height: 56px; border: none; background: rgba(44,54,80,.9); }
.ab-msg2 { right: 346px; top: 62%; width: 240px; height: 46px; border: none; background: linear-gradient(135deg, rgba(91,140,255,.92), rgba(139,143,245,.85)); }
.ab-send { left: 250px; bottom: 22px; width: 158px; height: 54px; border: none; background: linear-gradient(135deg, var(--accent), var(--accent-2)); }
.ab-card { right: 26px; top: 15%; width: 236px; height: 130px; border: none; background: rgba(44,54,80,.85); }
/* 900, not 820: between ~821 and ~900 the fixed-px bubbles jutted out of the percentage-anchored
   main panel and the "app behind glass" composition visibly came apart. */
/* Not a blackout: dropping EVERY shape left the phone hero as flat type on a plain scrim. The
   panels/bubbles go (they're px-anchored and fall apart), the single soft orb stays for presence. */
@media (max-width: 900px) { .auth-backdrop > span { display: none; } .auth-backdrop .ab-orb { display: block; left: 50%; top: 8%; translate: -50% 0; } }

/* Translucent frosted glass: see-through enough that the blurred ground drifts
   faintly behind. SECURITY unchanged — .shell isn't in the DOM pre-auth, so the
   blur reveals only the abstract ground, never account data. */
.screen { position: fixed; inset: 0; z-index: var(--z-drawer); overflow-y: auto;
  /* No blur (global thermal override): a near-opaque dark scrim frosts the ground instead. */
  background: radial-gradient(1100px 640px at 50% -14%, rgba(91,140,255,.17), transparent 56%), radial-gradient(760px 520px at 84% 118%, rgba(139,143,245,.13), transparent 60%), rgba(9,12,19,.86);
  /* `visibility` rides the transition too, otherwise the outgoing screen blanks in a single
     frame while the incoming one takes .55s — half a crossfade, read as a flicker. */
  transition: opacity .55s var(--ease), transform .55s var(--ease), visibility 0s; }
.lsec-lead { max-width: 60ch; margin: 0 auto 40px; color: var(--text-dim); font-size: clamp(14.5px,1.7vw,16px); line-height: 1.62; text-wrap: pretty; }
.screen.hide { opacity: 0; pointer-events: none; transform: scale(1.03); display: block !important; }
/* Held until the fade FINISHES (0s delayed by the full duration) so the outgoing screen
   dissolves instead of vanishing, and is still non-focusable once it's gone. */
.screen.hide[id] { visibility: hidden;
  transition: opacity .55s var(--ease), transform .55s var(--ease), visibility 0s linear .55s; }
#landing { padding: 12vh 24px 76px; }
#login { display: grid; place-items: center; padding: 24px; }
.land { max-width: 940px; margin: 0 auto; text-align: center; position: relative; animation: atmosRise .8s var(--ease) backwards; }
/* Hero column reads at a comfortable measure; the wider .land only frees the capability grid below. */
.lhero { max-width: 720px; margin: 0 auto; }
/* PERF: static landing aura (was an always-on blurred element breathing — blur(24px) + animated
   transform/opacity. On the landing this is the only always-on cost; a static glow reads the same). */
/* The wash scales with the column: at 380px fixed it was wider than a 375px phone and cropped by it. */
.land::before { content: ""; position: absolute; left: 50%; top: -40px; width: min(380px, 92vw); height: min(380px, 92vw); transform: translateX(-50%);
  background: radial-gradient(circle, rgba(91,140,255,.20), transparent 62%); filter: blur(24px); pointer-events: none; z-index: -1; opacity: .8; }
/* Hero lockup: the glyph carries the brand alongside the NAME, in real text. */
.lmark { display: flex; align-items: center; justify-content: center; gap: var(--space-md); margin: 0 auto var(--space-2xl); }
.cmark-lg { width: 48px; height: 48px; margin: 0; display: block; }
.lwordmark { font-size: 15px; font-weight: 650; letter-spacing: .22em; color: var(--text-dim); }
/* Vertical rhythm on the declared 6/8/12/16/20/28 ramp — the intervals climb (28 → 20 → 28 → 42+28)
   instead of the old 30/20/34/44+26 one-offs with no ratio between them. */
.land h1 { font-size: clamp(38px,6vw,64px); font-weight: 700; letter-spacing: -.035em; line-height: 1.03; margin: 0 0 var(--space-xl); text-wrap: balance; }
.land h1 .g { color: var(--accent-bright); }
/* The one sentence that explains the product gets its own step between the 64px h1 and the
   dim card body — bright leading clause, quiet qualifier after the em dash. */
.land .lead { font-size: clamp(17px,2.3vw,21px); color: var(--text); max-width: 56ch; margin: 0 auto var(--space-2xl); line-height: 1.55; text-wrap: pretty; }
.land .lead .dim { color: var(--text-dim); }
.cta { display: inline-flex; gap: 12px; justify-content: center; flex-wrap: wrap; }
/* Differentiator strip — real claims, not decoration. A thin rule sets it off from the CTAs. */
.ltrust { list-style: none; margin: calc(var(--space-2xl) * 1.5) auto 0; padding: var(--space-2xl) 0 0; max-width: 640px; border-top: 1px solid var(--stroke);
  display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-md) var(--space-2xl); }
.ltrust li { display: inline-flex; align-items: center; gap: 9px; font-size: 13.5px; font-weight: 550; color: var(--text); }
.ltrust li::before { content: ""; flex: 0 0 auto; width: 5px; height: 5px; border-radius: 50%;
  background: var(--accent-bright); box-shadow: 0 0 8px rgba(122,162,255,.55); }
.btn-lg { font: inherit; font-size: 15px; font-weight: 600; padding: 13px var(--space-2xl); border-radius: var(--radius); cursor: pointer;
  border: 1px solid transparent; color: var(--text); transition: transform .2s var(--ease), box-shadow .25s, border-color .2s, background .2s; }
.btn-lg.pri { background: var(--accent); color: var(--accent-ink);
  box-shadow: 0 10px 30px -8px rgba(91,140,255,.7); }
.btn-lg.pri:hover { transform: translateY(-2px); box-shadow: 0 18px 44px -8px rgba(91,140,255,.95); }
.btn-lg.gho { background: var(--glass-2); border-color: var(--stroke); }
.btn-lg.gho:hover { border-color: var(--stroke-strong); transform: translateY(-2px); }
/* The biggest buttons on the site were the only ones with no press feedback — and on touch,
   where magnetize() is inert, the press was the ONLY state they had. */
/* !important because magnetize() parks an inline transform on every mouse move, and an inline
   style outranks the sheet — without it the press is invisible on exactly the devices that magnetize. */
.btn-lg:not(:disabled):active { transform: translateY(0) scale(.985) !important; }
/* An accent ring on an accent fill inside a periwinkle bloom is invisible: give the primary
   CTA an ink ring instead, so keyboard focus actually reads. */
.btn-lg.pri:focus-visible { outline-color: var(--text); outline-offset: 3px; }
/* A pending submit must LOOK pending. Sits after .pri/.gho (same specificity) so it actually
   wins, and !important on transform beats magnetize()'s inline parked translate. */
.btn-lg:disabled, .btn-lg:disabled:hover { opacity: .6; cursor: not-allowed; transform: none !important; box-shadow: none; }
.lsections { max-width: 980px; margin: 92px auto 0; }
.lsec { text-align: center; margin-bottom: 44px; }
.lsec h2 { font-size: clamp(25px,3.4vw,34px); font-weight: 700; letter-spacing: -.025em; margin: 0 0 14px; text-wrap: balance; }
/* Break the TRACK, not out the decoration: six dimensionally identical cards read as a
   uniform 3×2 wall and say nothing about which capabilities carry weight. Twelve columns,
   the two load-bearing cards at half a row each, the four supporting ones 4-up beneath —
   so the hierarchy is in the composition instead of in more chrome. */
.lcards { display: grid; grid-template-columns: repeat(12, 1fr); gap: 14px; }
.lcards .lcard:nth-child(-n+2) { grid-column: span 6; }
.lcards .lcard:nth-child(n+3) { grid-column: span 3; padding: 20px 18px; }
.lcards .lcard:nth-child(n+3) .li { width: 40px; height: 40px; margin-bottom: 13px; }
/* PERF: cards carry a backdrop blur nowhere — a near-opaque fill reads the same behind the frosted
   .screen and costs zero continuous GPU. The ::after glare (hover only) keeps them lively. */
.lcard { background: var(--glass-solid); border: 1px solid var(--stroke); border-radius: 16px; padding: 24px 22px; text-align: left;
  min-width: 0;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.05); transition: transform .25s var(--ease), border-color .2s, box-shadow .25s; }
.lcard:hover { transform: translateY(-4px); border-color: var(--stroke-strong);
  box-shadow: 0 20px 44px -22px rgba(0,0,0,.8), 0 0 34px -26px rgba(91,140,255,.6); }
.lcard .li { display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px; margin-bottom: 15px;
  font-size: 21px; color: var(--accent-bright); border-radius: 12px; background: rgba(91,140,255,.10);
  border: 1px solid rgba(91,140,255,.20); filter: drop-shadow(0 0 6px rgba(91,140,255,.4)); }
/* 16px/650 over 13.5px body was a 1.19× step — the capability NAME barely out-ranked its own
   paragraph, so scanning the grid meant reading it. Wider step, lighter weight, and the two
   wide cards carry a visibly bigger title so the span reads as intent rather than reflow. */
.lcard h4 { margin: 0 0 10px; font-size: 17px; font-weight: 600; letter-spacing: -.01em; }
.lcards .lcard:nth-child(-n+2) h4 { font-size: 19px; }
.lcard p { margin: 0; font-size: 13.5px; color: var(--text-dim); line-height: 1.56; text-wrap: pretty; }

/* Featured "butler" card — a wider, accent-lit row so the six peers below don't read as an
   identical card wall. Its own layout: mark on the left, heading + prose stacked on the right. */
.lcard-feat { display: grid; grid-template-columns: auto 1fr; grid-template-rows: auto auto; gap: 4px 24px;
  align-items: start; padding: 28px 30px; margin-bottom: var(--space-2xl); border-radius: 18px;
  /* Its prose is capped at 64ch (~470px) inside a 980px section, which left ~350px of dead
     card to the right of the most important block on the page. Narrow the CARD to its measure
     and centre it, so it sits deliberately tighter than the grid beneath it. */
  max-width: 760px; margin-inline: auto;
  background: linear-gradient(180deg, rgba(91,140,255,.09), rgba(91,140,255,.03)), var(--glass-solid);
  border: 1px solid rgba(91,140,255,.24); box-shadow: inset 0 1px 0 rgba(255,255,255,.05), 0 0 46px -30px rgba(91,140,255,.8); }
.feat-mark { grid-column: 1; grid-row: 1 / 3; display: grid; place-items: center; width: 60px; height: 60px; border-radius: 15px;
  font-size: 30px; color: var(--accent-bright); background: rgba(91,140,255,.12); border: 1px solid rgba(91,140,255,.28);
  filter: drop-shadow(0 0 10px rgba(91,140,255,.5)); }
.feat-head { grid-column: 2; grid-row: 1; display: flex; align-items: baseline; flex-wrap: wrap; gap: 6px 12px; margin-bottom: 6px; }
.feat-head h3 { margin: 0; font-size: 24px; font-weight: 700; letter-spacing: -.01em; }
.feat-kicker { font-size: 12.5px; font-weight: 600; color: var(--accent-bright); }
.lcard-feat > p { grid-column: 2; grid-row: 2; margin: 0; max-width: 64ch; color: var(--text-dim); font-size: 14.5px; line-height: 1.62; text-wrap: pretty; }

/* The spanned track collapses in two steps, and every fallback uses minmax(0,1fr): a fixed
   240px floor could not shrink below 288px of viewport, so the Fold's 280px cover display
   pushed the whole page into horizontal scroll. */
@media (max-width: 900px) {
  .lcards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .lcards .lcard, .lcards .lcard:nth-child(-n+2), .lcards .lcard:nth-child(n+3) { grid-column: auto; padding: 22px 20px; }
  .lcards .lcard:nth-child(n+3) .li { width: 44px; height: 44px; margin-bottom: 15px; }
  .lcards .lcard:nth-child(-n+2) h4 { font-size: 17px; }
}
@media (max-width: 560px) {
  .lcards { grid-template-columns: minmax(0, 1fr); }
}
/* The featured card must STACK on a phone: at 390px the two-column layout left a ~198px prose
   column — a 25-character ribbon beside the glyph on the most important card of the page. */
@media (max-width: 640px) {
  .lcard-feat { grid-template-columns: 1fr; grid-template-rows: auto auto auto; padding: var(--space-xl); gap: var(--space-md); }
  .feat-mark { grid-column: 1; grid-row: 1; width: 48px; height: 48px; }
  .feat-head { grid-column: 1; grid-row: 2; }
  .lcard-feat > p { grid-column: 1; grid-row: 3; }
}

/* PHONE: the hero had zero small-screen styling. At 360–390px the two CTAs (~332px + gap) wrapped
   into two ragged rows instead of stacking as equal full-width targets, and 12vh + a 76px mark burned
   ~185px before the headline. Stack, tighten, and pull the primary CTA back above the fold. */
@media (max-width: 560px) {
  #landing { padding: 7vh 18px 56px; }
  .lmark { margin-bottom: var(--space-xl); }
  .cmark-lg { width: 42px; height: 42px; }
  .cta { display: flex; width: 100%; }
  .cta .btn-lg { flex: 1 1 100%; padding: 15px var(--space-lg); }
  .ltrust { margin-top: var(--space-2xl); padding-top: var(--space-xl); gap: var(--space-md) var(--space-xl); }
}

.lfoot { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 6px 16px;
  margin-top: 8px; padding: 26px 0 8px; border-top: 1px solid var(--stroke);
  color: var(--text-dim); font-size: 12px; letter-spacing: .1em; text-transform: uppercase; }
/* The host is a literal, not a caption — don't upper-case someone's hostname or port. */
.lfoot > span { text-transform: none; letter-spacing: .04em; }
/* This is the ONLY log-in affordance once the hero has scrolled away — 12px of bare text was a
   ~14px-tall tap target. Padded to the 44px minimum without changing how it looks. */
.linline { background: none; border: none; color: var(--accent-bright); cursor: pointer; font: inherit; font-size: 12px; letter-spacing: .1em; text-transform: uppercase;
  padding: var(--space-md) var(--space-sm); min-height: 44px; display: inline-flex; align-items: center; }
.linline:hover { text-decoration: underline; }
/* one-shot staggered reveal for the capability cards (load-in only; nothing runs at rest) */
.lcards .lcard { animation: atmosRise .5s var(--ease) backwards; }
.lcards .lcard:nth-child(1) { animation-delay: .04s; } .lcards .lcard:nth-child(2) { animation-delay: .10s; }
.lcards .lcard:nth-child(3) { animation-delay: .16s; } .lcards .lcard:nth-child(4) { animation-delay: .22s; }
.lcards .lcard:nth-child(5) { animation-delay: .28s; } .lcards .lcard:nth-child(6) { animation-delay: .34s; }
.lcard-feat { animation: atmosRise .55s var(--ease) backwards; }
/* The most-seen card in the product, so it obeys the system: three radii, the 6/8/12/16/20/28 ramp. */
.login-card { width: 100%; max-width: 384px; background: var(--glass-solid);
  border: 1px solid var(--stroke); border-radius: var(--radius-lg); box-shadow: var(--glow); padding: var(--space-2xl) var(--space-xl); animation: atmosRise .55s var(--ease) backwards; }
.login-card .cmark { width: 46px; height: 46px; margin: 0 auto var(--space-lg); display: block; }
.login-card h2 { text-align: center; font-size: 22px; margin: 0 0 4px; }
.login-card .lsub { text-align: center; font-size: 13px; color: var(--text-faint); margin-bottom: var(--space-xl); }
.login-card .auth-tabs { margin-bottom: var(--space-lg); }
.login-card .fld { margin-bottom: var(--space-lg); }
.login-card .fld label { display: block; font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--text-faint); margin-bottom: 6px; }
.login-card .fld input { width: 100%; padding: 12px 14px; border-radius: var(--radius-sm); border: 1px solid var(--stroke);
  background: rgba(6,9,14,.5); color: var(--text); font: inherit; font-size: 14px; transition: border-color .18s, box-shadow .18s; }
/* These are the ONLY interactive inputs that exist before sign-in, so the ring has to be real:
   .16 alpha on a near-black field measured under 1.6:1. Keyboard focus additionally gets an outline. */
.login-card .fld input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px rgba(91,140,255,.30); }
.login-card .fld input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.login-card .fld-hint { font-size: var(--fs-sm); color: var(--text-faint); margin-top: 6px; }
/* Full-width button never needed the landing CTA's 30px flanks — on a 320px phone the label wrapped. */
.login-card .btn-lg { width: 100%; margin-top: 6px; padding-left: var(--space-lg); padding-right: var(--space-lg); }
.login-card .auth-err { text-align: center; }
.astat { display: flex; align-items: center; justify-content: center; gap: 8px; margin-top: var(--space-xl);
  font-size: 11px; letter-spacing: .14em; text-transform: uppercase; color: var(--text-faint); }
/* The login card's ONLY exit was 12px of bare text — a ~15px hit box. Padded to a real
   target (flex + width:fit-content so the auto margins still centre it under the form). */
.lback { display: flex; align-items: center; justify-content: center; width: fit-content;
  margin: var(--space-lg) auto 0; padding: 10px var(--space-lg); min-height: 40px; border-radius: var(--radius-pill);
  font-size: 12px; color: var(--text-dim); cursor: pointer; background: none; border: none; }
.lback:hover { color: var(--text); }
@media (max-width: 400px) { .login-card { padding: var(--space-xl) var(--space-lg); } }

/* keep the modules from sliding around under off-canvas mobile transforms */
@media (max-width: 1000px) {
  .rail { animation: none; border-radius: 0; }
}
@media (max-width: 720px) {
  .shell { padding: 8px; gap: 8px; }
  .sidebar { animation: none; border-radius: 0; }
}

/* reactbits: 3-D card tilt (JS-driven) + a pointer-tracked glare sweep, and
   magnetic CTAs (JS sets transform). All motion is reduced-motion-guarded in JS. */
/* NO static `will-change` here: it permanently promoted seven compositor layers for an effect
   that only exists under a mouse. tilt() sets and clears it on pointerenter/pointerleave. */
.lcard { position: relative; transform-style: preserve-3d; }
.lcard::after { content: ""; position: absolute; inset: 0; border-radius: inherit; pointer-events: none; opacity: 0;
  transition: opacity .3s var(--ease);
  background: radial-gradient(220px circle at var(--gx,50%) var(--gy,50%), rgba(180,205,255,.16), transparent 60%); }
.lcard:hover::after { opacity: 1; }
.btn-lg, .send { will-change: transform; }
/* room glyph matches the 34px 3-D orb host */
.room-glyph { width: 34px; height: 34px; border-radius: 11px; }

@media (prefers-reduced-motion: reduce) {
  .cglow, .spark { display: none !important; }
  /* keep the new decorative motion calm under reduced-motion: the logomark loader
     and the running-agent status dots hold still rather than tracing/blinking. */
  .loader-mark .cmark-loader, .loader-mark .cmark-loader * { animation: none !important; }
  /* the logomark (brand + loader) holds completely still under reduced-motion */
  .cmark, .cmark * { animation: none !important; }
  .agent .ad { animation: none !important; }
  /* P4: no live waveform / pulse under reduced-motion — the speaking indicator stays a static chip. */
  .speak-wave { display: none !important; }
  body.butler-speaking .cmark, body.cypher-writing .cmark, .speak-dot, .pres.speaking .orb .core { animation: none !important; }
  /* Landing entrances hold still — content is already visible at rest, so just skip the reveal. */
  .land, .lcards .lcard, .lcard-feat { animation: none !important; }
}

/* ==========================================================================
   P2 — ORACLE VIEW re-skinned to the Atmos glass standard (matches chat).
   The JARVIS console keeps every control + data hook; only the surface changes:
   a transparent stage over the glass .main, a floating glass console, periwinkle
   controls, and staged entrance animations. No markup/wiring touched in app.js.
   ========================================================================== */
.oracle-hud { background: transparent; font-family: var(--sans); }
.oracle-stage { animation: atmosModIn .6s var(--ease) .06s backwards; }
/* GLOW REMOVED: the periwinkle vignette pool behind the orb is gone — it was a second halo source. */
.oracle-canvas { position: absolute; z-index: 1; }
.oracle-title, .oracle-sub, .oracle-status { z-index: 2; font-family: var(--sans); }
.oracle-title { animation: atmosRise .6s var(--ease) .2s backwards; }
.oracle-sub { animation: atmosRise .6s var(--ease) .3s backwards; }
.oracle-status { animation: atmosRise .5s var(--ease) .36s backwards; }
/* Same story as .composer: static sheen over a near-opaque fill, no live blur. */
.oracle-console { border-top: none; margin: var(--space-md) var(--space-lg) var(--space-lg); padding: var(--space-md) 14px; border-radius: var(--radius-lg);
  border: 1px solid var(--stroke-strong);
  background: linear-gradient(150deg, rgba(52,64,94,.45), rgba(18,24,38,.15) 55%), var(--glass-2);
  box-shadow: 0 18px 44px -22px rgba(0,0,0,.9), 0 0 34px -26px rgba(91,140,255,.7);
  position: relative; animation: atmosModIn .55s var(--ease) .16s backwards; }
.oracle-console > * { animation: atmosRise .5s var(--ease) backwards; }
.oracle-console > *:nth-child(1) { animation-delay: .24s; } .oracle-console > *:nth-child(2) { animation-delay: .30s; }
.oracle-console > *:nth-child(3) { animation-delay: .36s; } .oracle-console > *:nth-child(n+4) { animation-delay: .42s; }
/* House policy: no blur-in cascade for reduced-motion users — content appears at rest. */
@media (prefers-reduced-motion: reduce) {
  .oracle-stage, .oracle-title, .oracle-sub, .oracle-status,
  .oracle-console, .oracle-console > *, .oracle-rail {
    animation: none !important;
  }
}
.oracle-log .ol-who { color: var(--accent-bright); }
.oracle-input { background: rgba(6,9,14,.5); border: 1px solid var(--stroke); border-radius: var(--radius-sm); color: var(--text); }
.oracle-input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(91,140,255,.16); }
.oracle-send, .oracle-voicebtn, .oracle-texttoggle, .oracle-vsel { border-radius: var(--radius-sm); border-color: var(--stroke);
  background: var(--glass); color: var(--text); transition: border-color .18s var(--ease), background .18s, color .18s, box-shadow .2s; }
.oracle-send { background: var(--accent); color: var(--accent-ink); border: none; }
.oracle-send:hover { box-shadow: 0 8px 22px -8px rgba(91,140,255,.9); }
.oracle-voicebtn:hover, .oracle-texttoggle:hover, .oracle-vsel:hover { border-color: var(--stroke-strong); color: var(--text); }
.oracle-voicebtn.on { border-color: rgba(63,191,135,.5); color: var(--good); }
.oracle-speed { accent-color: var(--accent); }
/* button polish — consistent sizing + honest press states (transform only; no always-on motion). */
.oracle-send, .oracle-voicebtn, .oracle-talkbtn, .oracle-texttoggle, .oracle-vsel {
  transition: border-color .16s var(--ease), background .16s, color .16s, box-shadow .18s, transform .1s; }
.oracle-send:active, .oracle-voicebtn:active, .oracle-talkbtn:active, .oracle-texttoggle:active { transform: translateY(1px) scale(.98); }
.oracle-send { padding: 0 22px; height: 42px; font-weight: 700; }
.oracle-input { padding: 12px 14px; }

/* ==========================================================================
   P2 — Oracle two-column layout: stage + console on the left, a right context
   dock (rail) that mirrors Cypher's chat rail — a rotating presence orb on top,
   then Oracle's Display (blueprints/media) and live Swarm panels. NO blur (global
   thermal override is on); solid translucent glass fills only.
   ========================================================================== */
.oracle-hud { display: flex; flex-direction: column; }
.oracle-cols { flex: 1; min-height: 0; display: grid; grid-template-columns: 1fr 300px; gap: 14px; }
.oracle-main { display: flex; flex-direction: column; min-width: 0; min-height: 0; position: relative; }
.oracle-rail { display: flex; flex-direction: column; gap: 12px; min-height: 0; overflow-y: auto;
  background: var(--glass-solid); border: 1px solid var(--stroke); border-radius: var(--radius-lg);
  box-shadow: var(--glow); padding: var(--space-lg); position: relative;
  animation: atmosModIn .6s var(--ease) .18s backwards; }
.oracle-rail::after { content: ""; position: absolute; inset: 0; border-radius: var(--radius-lg); pointer-events: none;
  background: linear-gradient(180deg, rgba(255,255,255,.05), transparent 22%); }
.oracle-rail > * { position: relative; z-index: 1; }
.oracle-pres { display: flex; align-items: center; gap: var(--space-md); padding: var(--space-md); border-radius: var(--radius); flex: 0 0 auto;
  background: var(--glass-2); border: 1px solid var(--stroke); box-shadow: 0 1px 0 0 var(--glass-hi) inset; }
.oracle-pres .orb-pres { width: 54px; height: 54px; flex: 0 0 auto; }
.oracle-pres-nm { font-size: 14px; font-weight: 650; color: var(--text); }
.oracle-pres-st { font-size: 11px; color: var(--good); margin-top: 3px; display: flex; align-items: center; gap: 6px; }
.oracle-pres-st .ps-dot { width: 6px; height: 6px; border-radius: 50%; flex: 0 0 auto; background: var(--good); box-shadow: 0 0 8px var(--good); }
.oracle-pres-st.thinking { color: var(--warn); } .oracle-pres-st.thinking .ps-dot { background: var(--warn); box-shadow: 0 0 8px var(--warn); }
.oracle-pres-st.speaking { color: var(--accent-bright); } .oracle-pres-st.speaking .ps-dot { background: var(--accent); box-shadow: 0 0 8px var(--accent); }
/* Display + Swarm sit STATICALLY in the rail (they float:absolute over the stage on the JS-injected
   base rules; the higher-specificity .oracle-rail selector re-flows them into the column). */
.oracle-rail .oracle-blueprint, .oracle-rail .oracle-swarm {
  position: static; inset: auto; left: auto; right: auto; top: auto; bottom: auto;
  width: auto; max-width: none; z-index: auto;
  background: var(--glass-2); border: 1px solid var(--stroke); border-radius: var(--radius);
  box-shadow: 0 1px 0 0 var(--glass-hi) inset; }
.oracle-rail .oracle-blueprint { flex: 1 1 auto; max-height: 54%; }
.oracle-rail .oracle-swarm { flex: 0 1 auto; max-height: 46%; }
.oracle-rail .obp-title, .oracle-rail .osw-title { color: var(--accent-bright); }
.oracle-rail .obp-close { background: transparent; border: 1px solid var(--stroke); color: var(--text-dim); }
.oracle-rail .obp-close:hover { border-color: var(--accent); color: var(--text); }
.oracle-rail .osw-stop { background: var(--bad); }
@media (max-width: 1000px) {
  .oracle-cols { grid-template-columns: 1fr; }
  .oracle-rail { display: none; }
}

/* ==========================================================================
   P3 — chat "thinking" loader: the animated CYPHER logomark shows in the
   typing-line while a reply is awaited (before the first token).
   ========================================================================== */
.chat-thinking { display: inline-flex; align-items: center; gap: 8px; color: var(--text-dim); }
.chat-thinking .cmark-think { width: 19px; height: 19px; margin: 0; }
.chat-thinking > span { animation: thinkPulse 1.8s ease-in-out infinite; }
@keyframes thinkPulse { 0%, 100% { opacity: .55; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .chat-thinking > span { animation: none; opacity: .8; } }
/* a smaller, inline logomark loader variant (base .loader-mark is a big centred block) */
.loader-mark.inline { display: inline-flex; margin: 0; }
.loader-mark.inline .cmark-loader { width: 18px; height: 18px; }

/* ==========================================================================
   P4 — SPEAKING pulse + audio-wave indicator (tied to state._butlerSpeaking).
   The brand logomark & presence orb pulse; a periwinkle waveform reacts; a
   tasteful right-side chip in the chat header shows both while Oracle talks.
   ========================================================================== */
/* logomark pulses harder while speaking (overrides the calm resting breathe) */
body.butler-speaking .cmark { animation: atmosSpeakMark 0.62s var(--ease) infinite; }
/* The mark also LIVES while Cypher is writing — a slower, calmer pulse than the speaking one, so
   the two states read differently: quick beat = speaking aloud, slow breath = composing. */
body.cypher-writing .cmark { animation: atmosSpeakMark 1.6s var(--ease) infinite; }
body.cypher-writing.butler-speaking .cmark { animation: atmosSpeakMark 0.62s var(--ease) infinite; }
@keyframes atmosSpeakMark { 0%,100% { transform: scale(1); filter: drop-shadow(0 0 8px rgba(91,140,255,.5)); }
  50% { transform: scale(1.14); filter: drop-shadow(0 0 15px rgba(139,143,245,.95)); } }
/* right-side chat-header speaking chip — hidden (and out of layout) when silent */
.speak-indicator { display: none; align-items: center; gap: 8px; padding: 5px 10px; border-radius: var(--radius-pill);
  border: 1px solid rgba(91,140,255,.32); background: rgba(91,140,255,.10);
  box-shadow: 0 0 18px -6px rgba(91,140,255,.6); }
.speak-indicator.on { display: inline-flex; animation: atmosRise .3s var(--ease); }
.speak-dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto;
  background: radial-gradient(circle at 38% 34%, #cdd9ff, var(--accent) 60%, #2c4bb0);
  box-shadow: 0 0 10px 0 rgba(91,140,255,.9); animation: atmosSpeak .5s var(--ease) infinite; }
.speak-wave { display: block; width: 54px; height: 20px; }
.speak-label { font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--accent-bright); font-weight: 600; }
/* presence orb (right rail) gets a speaking ring too, when present */
.pres.speaking .orb .core { animation: atmosSpeak .5s var(--ease) infinite; }

/* ==========================================================================
   ADMIN — per-user capability grid + iMessage (Atmos glass)
   The "⚙ Manage" panel: identity header, grouped capability switches
   (Features / Sensitive / Messaging), and the iMessage handle block. Sensitive
   grants are visually flagged as higher-trust. Everything is theme-aware and
   collapses cleanly on mobile.
   ========================================================================== */
.cap-modal { max-width: 580px; max-height: min(86vh, 900px); display: flex; flex-direction: column;
  overflow-y: auto; overscroll-behavior: contain; }
.cap-modal > .modal-actions { position: sticky; bottom: -24px; margin-top: 16px; padding-top: 12px;
  background: linear-gradient(transparent, var(--glass-2) 42%); }

.avatar.lg { width: 46px; height: 46px; border-radius: 12px; font-size: 20px;
  box-shadow: 0 0 22px -6px rgba(91,140,255,.7); }

.cap-identity { display: flex; align-items: center; gap: 14px; padding: 4px 2px 16px;
  margin-bottom: 6px; border-bottom: 1px solid var(--stroke); }
.cap-name { font-size: 15px; font-weight: 650; color: var(--text); display: flex; align-items: center;
  flex-wrap: wrap; gap: 4px; }

.cap-groups { display: flex; flex-direction: column; gap: 18px; }
.cap-group { border: 1px solid var(--stroke); border-radius: 14px; overflow: hidden;
  background: linear-gradient(180deg, rgba(255,255,255,.02), transparent); }
.cap-group.sensitive { border-color: rgba(240,180,41,.28); }
.cap-group-head { font-size: 11px; font-weight: 700; letter-spacing: .14em; text-transform: uppercase;
  color: var(--text-faint); padding: 11px 15px; background: rgba(255,255,255,.03);
  border-bottom: 1px solid var(--stroke); }
.cap-group.sensitive .cap-group-head { color: var(--warn); background: rgba(240,180,41,.06);
  border-bottom-color: rgba(240,180,41,.22); }
.cap-list { display: flex; flex-direction: column; }
.cap-row { display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: 13px 15px; border-top: 1px solid var(--stroke); transition: background .16s var(--ease); }
.cap-row:first-child { border-top: none; }
.cap-row:hover { background: rgba(91,140,255,.05); }
.cap-row.sensitive:hover { background: rgba(240,180,41,.06); }
.cap-meta { min-width: 0; }
.cap-label { font-size: 13.5px; font-weight: 600; color: var(--text); display: flex; align-items: center;
  gap: 8px; flex-wrap: wrap; }
.cap-desc { font-size: 11.5px; color: var(--text-faint); line-height: 1.45; margin-top: 3px; }
.cap-trust { font-size: 9.5px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--warn); border: 1px solid rgba(240,180,41,.4); background: rgba(240,180,41,.1);
  padding: 1px 6px; border-radius: var(--radius-pill); white-space: nowrap; }

/* the on/off switch (role=switch) */
.cap-switch { position: relative; flex: 0 0 auto; width: 42px; height: 24px; border-radius: var(--radius-pill);
  border: 1px solid var(--stroke-strong); background: var(--panel-2); cursor: pointer; padding: 0;
  transition: background .2s var(--ease), border-color .2s var(--ease), box-shadow .2s var(--ease); }
.cap-switch:hover { border-color: var(--accent); }
.cap-switch .cap-knob { position: absolute; top: 2px; left: 2px; width: 18px; height: 18px; border-radius: 50%;
  background: var(--text-dim); transition: transform .2s var(--ease), background .2s var(--ease); }
.cap-switch.on { background: rgba(91,140,255,.9); border-color: transparent;
  box-shadow: 0 0 0 1px rgba(91,140,255,.3), 0 0 14px -4px var(--accent); }
.cap-switch.on .cap-knob { transform: translateX(18px); background: var(--accent-ink); }
.cap-row.sensitive .cap-switch.on { background: rgba(240,180,41,.9);
  box-shadow: 0 0 0 1px rgba(240,180,41,.35), 0 0 14px -4px var(--warn); }
.cap-switch:disabled { cursor: not-allowed; opacity: .55; }
.cap-switch:disabled:hover { border-color: var(--stroke-strong); }
.cap-switch:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(91,140,255,.3); }

.cap-imessage { margin-top: 18px; padding: 15px; border-radius: 14px; border: 1px solid var(--stroke);
  background: linear-gradient(180deg, rgba(91,140,255,.05), transparent); }
.cap-im-head { display: flex; align-items: flex-start; gap: 11px; }
.cap-im-ic { flex: 0 0 auto; width: 30px; height: 30px; border-radius: 8px; display: grid; place-items: center;
  font-size: 15px; background: rgba(91,140,255,.14); border: 1px solid rgba(91,140,255,.28);
  color: var(--accent-bright); }
.cap-im-title { font-size: 13.5px; font-weight: 600; color: var(--text); }
.cap-imessage .field { margin-bottom: 0; }

.cap-note { margin-top: 14px; font-size: 12px; color: var(--text-dim); line-height: 1.5;
  padding: 10px 13px; border-radius: 10px; border: 1px solid var(--stroke);
  background: rgba(255,255,255,.02); }

@media (max-width: 560px) {
  .cap-modal { max-width: 100%; max-height: 92vh; padding: 18px; }
  .cap-row { flex-wrap: wrap; }
  .cap-identity { gap: 11px; }
}
@media (prefers-reduced-motion: reduce) {
  .cap-row, .cap-switch, .cap-switch .cap-knob { transition: none; }
}

/* ════ THERMAL OVERRIDE — kill the always-on GPU load (real-time blur + the animating blurred
   ground) that was cooking the GPU. Panels keep a translucent solid fill so they still read as
   glass, just without the expensive continuous backdrop blur. Single biggest cooling win. ════ */
*, *::before, *::after { -webkit-backdrop-filter: none !important; backdrop-filter: none !important; }
.ground, .ground::before, .ground::after { animation: none !important; }
.auth-backdrop { animation: none !important; }
.main::before, .side-brand .mark { animation: none !important; }
/* FROSTED GLASS, but NOT live: a STATIC frosted sheen (a soft diagonal gradient over a mostly-
   opaque fill) gives the glassy look with ZERO real-time backdrop-blur cost and no see-through mess. */
:root { --glass: rgba(24,31,48,.93); --glass-2: rgba(30,38,57,.93); --panel: rgba(24,31,48,.93); --panel-2: rgba(30,38,57,.9); }
/* THEME GUARD: the fill below is a DARK glass and it carries !important, so without a
   guard [data-theme="light"] rendered dark panels on a light body — the light theme was
   simply broken. Dark keeps exactly what it had; light gets the same device in its own ink. */
:root[data-theme="light"] {
  --glass: rgba(255,255,255,.92); --glass-2: rgba(246,248,252,.94);
  --glass-solid: rgba(255,255,255,.94); --glass-hi: rgba(255,255,255,.9);
  --panel: rgba(255,255,255,.92); --panel-2: rgba(240,243,249,.9);
}
:root:not([data-theme="light"]) :is(.sidebar, .main, .chat-main, .rail, .card, .section-card, .mod, .composer, .chat-head, .usercard-menu, .cmd-menu, .modal, .cap-modal) {
  background-image: linear-gradient(150deg, rgba(52,64,94,.45), rgba(18,24,38,.15) 55%) !important;
  background-color: rgba(19,25,39,.94) !important;
}
:root[data-theme="light"] :is(.sidebar, .main, .chat-main, .rail, .card, .section-card, .mod, .composer, .chat-head, .usercard-menu, .cmd-menu, .modal, .cap-modal) {
  background-image: linear-gradient(150deg, rgba(255,255,255,.75), rgba(226,232,244,.35) 55%) !important;
  background-color: rgba(255,255,255,.94) !important;
}
/* In chat, .main is intentionally dissolved (line 1534) so chat-main and rail float as
   separate modules over the animated ground. The THERMAL fill above uses !important and would
   otherwise re-opaque it, so restore transparency at higher specificity in BOTH themes. */
:root:not([data-theme="light"]) .main:has(.chat-cols),
:root[data-theme="light"] .main:has(.chat-cols) { background: transparent !important; }


/* ════ IP LOCATOR ════ owner/admin surface: an IP field + results panel on the left, the dot-globe
   stage on the right. Atmos glass via the SOLID --glass-solid fill (no backdrop-filter). ════ */
.iploc { display: grid; grid-template-columns: minmax(320px, 420px) 1fr; gap: 18px; height: 100%;
  min-height: 0; padding: 22px; overflow: auto; }
.iploc-panel { display: flex; flex-direction: column; gap: 12px; align-content: start;
  background: linear-gradient(150deg, rgba(52,64,94,.28), rgba(18,24,38,.08) 60%), var(--glass-solid);
  border: 1px solid var(--stroke); border-radius: var(--radius); box-shadow: var(--glow);
  padding: 18px; height: fit-content; }
.iploc-title { font-size: 17px; font-weight: 700; letter-spacing: .3px; color: var(--text); }
.iploc-sub { font-size: 12.5px; color: var(--text-dim); margin-top: -6px; }
.iploc-inrow { display: flex; gap: 8px; margin-top: 4px; }
.iploc-input { flex: 1; min-width: 0; background: rgba(0,0,0,.28); border: 1px solid var(--stroke-strong);
  color: var(--text); border-radius: var(--radius-sm); padding: 10px 12px; font-size: 13.5px;
  font-family: var(--mono); transition: border-color .14s, box-shadow .14s; }
.iploc-input::placeholder { color: var(--text-faint); }
.iploc-input:focus { outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(91,140,255,.18); }
.iploc-go { flex: 0 0 auto; padding: 10px 18px; }
.iploc-go.loading { opacity: .7; cursor: default; }
.iploc-status { font-size: 12.5px; min-height: 16px; color: var(--text-dim); }
.iploc-status.err { color: #ff8fa3; }
.iploc-results:empty { display: none; }
.iploc-head { display: flex; align-items: center; gap: 8px; font-size: 15px; font-weight: 650;
  color: var(--text); padding-bottom: 4px; }
.iploc-pin { filter: drop-shadow(0 0 6px rgba(91,140,255,.6)); }
.iploc-grid { display: grid; grid-template-columns: 1fr; gap: 1px; background: var(--stroke);
  border: 1px solid var(--stroke); border-radius: var(--radius-sm); overflow: hidden; }
.iploc-row { display: grid; grid-template-columns: 96px 1fr; gap: 10px; padding: 9px 12px;
  background: rgba(19,25,38,.6); font-size: 13px; }
.iploc-k { color: var(--text-faint); font-weight: 600; text-transform: uppercase; letter-spacing: .5px;
  font-size: 10.5px; align-self: center; }
.iploc-v { color: var(--text); font-family: var(--mono); word-break: break-word; }
.iploc-note { font-size: 11.5px; color: var(--text-faint); line-height: 1.5; }
.iploc-stage { display: flex; align-items: center; justify-content: center; min-height: 0;
  background: radial-gradient(ellipse 70% 60% at 50% 45%, rgba(91,140,255,.10), transparent 70%); }
/* The globe is a defined CSS SQUARE. The server-baked spinning globe.webm sits in flow and gives the
   wrapper its size; the lazily-booted live WebGL canvas overlays it (absolute, centred) and cross-fades
   in on .live only during a locate. Idle = cheap video; locate = interactive canvas. */
.iploc-globe-wrap { position: relative; display: block; width: clamp(280px, 60vh, 560px); max-width: 100%;
  aspect-ratio: 1 / 1; margin: 0 auto; filter: drop-shadow(0 10px 40px rgba(20,30,80,.5)); }
/* Bigger globe for bigger desktops — the 560px cap held it small on wide monitors. Scale the ceiling up in
   tiers (still bounded by 68vh so it never taller than the viewport, and max-width:100% so it never overflows). */
@media (min-width: 1500px) { .iploc-globe-wrap { width: clamp(280px, 68vh, 680px); } }
@media (min-width: 1900px) { .iploc-globe-wrap { width: clamp(280px, 68vh, 820px); } }
@media (min-width: 2500px) { .iploc-globe-wrap { width: clamp(280px, 68vh, 960px); } }
.iploc-globe-video { display: block; width: 100%; height: 100%; object-fit: contain;
  transition: opacity .5s ease; will-change: opacity; }
.iploc-globe { position: absolute; inset: 0; margin: auto; display: block; opacity: 0;
  transition: opacity .3s ease; pointer-events: none; }
/* No black flash on locate: the live canvas fades IN fast (.3s), and the baked video only fades OUT
   AFTER a .35s delay — so the interactive globe is fully painted and lit before the video leaves.
   The two never dip simultaneously, which was the "blacks out a portion" moment. */
.iploc-globe-wrap.live .iploc-globe-video { opacity: 0; transition: opacity .5s ease .35s; }
.iploc-globe-wrap.live .iploc-globe { opacity: 1; pointer-events: auto; }
@media (max-width: 860px) {
  .iploc { grid-template-columns: 1fr; }
  .iploc-stage { order: -1; }
}

/* ── Chess clock (live PvP) ─────────────────────────────────────────────── */
.chess-clocks { display: flex; flex-direction: column; gap: 6px; margin: 10px 0 4px; }
.chess-clock { display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 7px 12px; border-radius: var(--radius-pill, 10px);
  border: 1px solid var(--hair, rgba(255,255,255,.08)); background: var(--surface-2, rgba(255,255,255,.03));
  transition: border-color .15s ease, background .15s ease; }
.chess-clock .cc-name { font-size: 12.5px; color: var(--text-dim, #9aa6bd); letter-spacing: .01em; }
.chess-clock .cc-time { font-family: var(--mono, ui-monospace, monospace); font-variant-numeric: tabular-nums;
  font-size: 19px; font-weight: 700; color: var(--text, #eaf0ff); }
/* the side to move is lit */
.chess-clock.running { border-color: color-mix(in oklab, var(--accent, #5b8cff) 55%, transparent);
  background: color-mix(in oklab, var(--accent, #5b8cff) 12%, transparent); }
.chess-clock.running .cc-time { color: var(--accent-bright, #7aa2ff); }
/* under 30s */
.chess-clock.low .cc-time { color: var(--bad, #f2687a); }
.chess-clock.running.low { border-color: color-mix(in oklab, var(--bad, #f2687a) 60%, transparent);
  background: color-mix(in oklab, var(--bad, #f2687a) 12%, transparent); }
.chess-clock.flag .cc-time { color: var(--bad, #f2687a); }
@media (prefers-reduced-motion: reduce) { .chess-clock { transition: none; } }

/* ── Markdown blocks in chat: tables, headers, lists, quotes ─────────────── */
.md-table-wrap { overflow-x: auto; margin: 10px 0; border-radius: 10px; border: 1px solid var(--hair, rgba(255,255,255,.08)); }
.md-table { border-collapse: collapse; width: 100%; font-size: 13.5px; }
.md-table th, .md-table td { text-align: left; padding: 8px 12px; border-bottom: 1px solid var(--hair, rgba(255,255,255,.07)); white-space: nowrap; }
.md-table th { font-weight: 650; color: var(--text, #eaf0ff); background: color-mix(in oklab, var(--accent, #5b8cff) 9%, transparent); font-variant-numeric: tabular-nums; letter-spacing: .01em; }
.md-table td { color: var(--text-dim, #c3cbe0); font-variant-numeric: tabular-nums; }
.md-table tbody tr:last-child td { border-bottom: 0; }
.md-table tbody tr:hover td { background: rgba(255,255,255,.025); }
.bubble .md-h { font-weight: 700; letter-spacing: -.01em; line-height: 1.25; margin: 14px 0 6px; }
.bubble h1.md-h { font-size: 20px; } .bubble h2.md-h { font-size: 17px; } .bubble h3.md-h { font-size: 15px; }
.bubble h4.md-h, .bubble h5.md-h, .bubble h6.md-h { font-size: 13.5px; color: var(--text-dim, #c3cbe0); }
.bubble ul, .bubble ol { margin: 6px 0; padding-left: 22px; }
.bubble li { margin: 3px 0; }
.bubble .md-quote { margin: 8px 0; padding: 6px 12px; border-left: 2px solid color-mix(in oklab, var(--accent, #5b8cff) 50%, transparent); color: var(--text-dim, #c3cbe0); background: rgba(255,255,255,.02); border-radius: 0 6px 6px 0; }
.bubble .md-hr { border: 0; border-top: 1px solid var(--hair, rgba(255,255,255,.1)); margin: 14px 0; }
