/* GENERATED by website/sync-app.mjs from the app's own stylesheets. Do not edit. */

/* ---- styles/tokens.css ---- */
/* Project Poiesis — design tokens.
   "Daylight" (light) and "Backlit" (dark): one monochrome liquid-crystal
   panel, read by ambient light or lit from behind. Names match the theme
   switch in Settings.
   The panels are flat — they share the surface colour and are separated by
   hairlines, so --paper-edge-2 is load-bearing structure, not decoration. It
   must stay clearly visible against --paper or the layout dissolves. Only
   things that float over the panels (composer, menus, dialogs) take a soft
   shadow. Ground and ink both carry a faint green cast (the glass tint). */

:root {
  /* ---- Light: "Daylight" ----
     Recolored toward the palette used in the Poiesis Harness Shakedown
     artifact: the same green-cast neutrals, but cooler and lighter — a
     near-white panel instead of an off-white one, and less of the old khaki
     saturation in the borders and muted text. Contrast ratios below are
     recalculated against the new #ffffff paper, not carried over from the
     old one. */
  --canvas: #f3f5f3; /* recessed ground the panels sit on */
  --paper: #ffffff; /* panel surface — was off-white, now a clean white */
  --paper-edge: #e6eae6; /* inner dividers, timeline rail tint */
  /* Not a straight lift of the artifact's `--rule` (#d8ddd8): against white
     that measures 1.38:1, well under the old 1.72:1 this token held against
     the old off-white paper. The artifact can afford that because it leans
     on `box-shadow` for card definition; this UI has no shadow layer and is
     flat by design (see file header), so the border *is* the only
     separation and has to stay a real line, not a suggestion of one. This
     value keeps the cooler, less-khaki cast the artifact has while holding
     ~1.87:1 against the new white paper — slightly stronger than before. */
  --paper-edge-2: #b8c0b8; /* panel outlines — the structural grid */
  --ink: #17201c; /* primary text — dark green-black, 16.7:1 on paper */
  --ink-muted: #5a6560; /* metadata, labels, step verbs — 6.06:1 (AA body) */
  --ink-faint: #8a938e; /* secondary labels, hairlines, idle dots — 3.16:1 (AA large/UI) */

  /* functional color — used sparingly, only where it serves a purpose */
  --local: #3d4fa0; /* indigo — local model */
  --cloud: #b5642e; /* oxidized copper — cloud model */
  --ok: #4a7a5e; /* completed timeline step dot */
  --danger: #a8453a; /* errors only */

  /* code highlighting (Shiki's css-variables theme, `lib/highlight.ts`).
     Drawn from the functional hues above, darkened where needed so every
     token holds AA (4.5:1) on paper. Comments and punctuation recede into the
     existing muted ink rather than taking a colour of their own. */
  --hl-foreground: var(--ink);
  --hl-background: transparent;
  --hl-token-keyword: #3d4fa0;
  --hl-token-string: #3b6a4e;
  --hl-token-string-expression: #3b6a4e;
  --hl-token-constant: #95501f;
  --hl-token-function: #2a5d6b;
  --hl-token-parameter: var(--ink);
  --hl-token-comment: var(--ink-muted);
  --hl-token-punctuation: var(--ink-muted);
  --hl-token-link: #3d4fa0;

  /* type roles */
  --font-serif: "Newsreader Variable", "Newsreader", Georgia, "Times New Roman",
    serif;
  --font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-mono: "JetBrains Mono", "SF Mono", Consolas, monospace;

  /* editorial type scale (PRD §5.2) */
  --fs-timeline: 12px;
  --fs-ui: 13px;
  --fs-ui-lg: 15px;
  --fs-reading: 17.5px; /* long-form documents (Workbench markdown) */
  --fs-display: 21px;
  --lh-reading: 1.68;
  /* The chat answer: sans, a step smaller and a touch more open than a
     document page — it sits among UI, and reads best in the UI's own face. */
  --font-reading: var(--font-sans);
  /* User-chosen face for chat messages, yours and the agent's (Settings →
     Message font). Set from the store; defaults to the UI's own sans. */
  --font-message: var(--font-sans);
  --fs-answer: 15.5px;
  --lh-answer: 1.72;
  --measure: 64ch;
  /* User-adjustable reading size (Settings, §5.5). Scales the serif reading
     column; content reflows rather than truncating. */
  --reading-scale: 1;

  /* ---- type ramp ----
     One step per role, whole pixels only. The UI had drifted to thirteen
     sizes between 9px and 15px, most of them half-pixel neighbours (11.5,
     12.5, 13.5, 14.5) that read as "slightly off" rather than as hierarchy.
     Every literal size in the sheets now maps onto one of these. */
  --fs-micro: 10px; /* badges, counters inside chips */
  --fs-caption: 11px; /* timestamps, kbd, section labels */
  --fs-footnote: 12px; /* secondary lines, hints, metadata */
  /* --fs-ui (13px) above is body; --fs-ui-lg (15px) is emphasised body */
  --fs-callout: 14px; /* field text, list titles that lead a row */
  --fs-title: 17px; /* card and dialog titles */
  --fs-page: 28px; /* a page's own title */
  /* Display sizes track tighter, the way optical sizes do. */
  --tracking-tight: -0.011em;
  --tracking-display: -0.02em;

  /* ---- spacing: a 4pt grid ---- */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 20px;
  --sp-6: 24px;
  --sp-8: 32px;
  --sp-10: 40px;
  --sp-12: 48px;

  /* ---- shape ----
     Nested radii, macOS proportions: a control inside a card is one step
     smaller than the card, so the two curves stay concentric. Still crisp —
     softened from the old 4/6px, never pill-round. */
  --radius-xs: 3px; /* code spans, keycaps, tiny chips */
  --radius-sm: 5px; /* segments inside a control, small icon buttons */
  --radius: 7px; /* buttons, fields, rows */
  --radius-lg: 10px; /* cards, panels, menus */
  --radius-xl: 14px; /* the composer, dialogs */
  --radius-pill: 999px;

  /* Control heights: everything that sits on one line with something else is
     one of these, so a button beside a field beside a select line up. */
  --control-sm: 26px;
  --control: 32px;
  --control-lg: 36px;

  /* ---- interaction fills ----
     Derived from ink rather than added as colours, so they sit correctly on
     both the canvas and white paper and invert themselves in Backlit. */
  --fill-hover: color-mix(in srgb, var(--ink) 5%, transparent);
  --fill-press: color-mix(in srgb, var(--ink) 9%, transparent);
  --fill-selected: color-mix(in srgb, var(--ink) 8%, transparent);
  --fill-quiet: color-mix(in srgb, var(--ink) 3.5%, transparent);
  --hairline: color-mix(in srgb, var(--paper-edge-2) 70%, transparent);

  /* ---- elevation ----
     The panels stay flat and hairline-separated; elevation is reserved for
     things that genuinely float above them — the composer, menus, popovers,
     dialogs. Neutral shadows only, so the palette is untouched. */
  --shadow-xs: 0 1px 2px rgba(16, 24, 20, 0.05);
  --shadow-sm: 0 1px 2px rgba(16, 24, 20, 0.04), 0 2px 10px rgba(16, 24, 20, 0.05);
  --shadow-float: 0 0 0 0.5px rgba(16, 24, 20, 0.06), 0 10px 30px rgba(16, 24, 20, 0.12),
    0 2px 8px rgba(16, 24, 20, 0.06);
  --shadow-modal: 0 0 0 0.5px rgba(16, 24, 20, 0.08), 0 24px 70px rgba(16, 24, 20, 0.2),
    0 6px 16px rgba(16, 24, 20, 0.08);
  /* Focus halo: the indigo already used for the focus ring, softened. */
  --ring: 0 0 0 3px color-mix(in srgb, var(--local) 22%, transparent);

  /* ---- motion ----
     Ease-out with a long tail for anything arriving or responding to the
     pointer; a quicker, sharper deceleration (no overshoot) for small things that pop; symmetric for
     layout that moves both ways. */
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  --ease-pop: cubic-bezier(0.16, 1, 0.3, 1);
  --dur-fast: 140ms;
  --dur: 220ms;
  --dur-slow: 360ms;
  --t-fast: var(--dur-fast) var(--ease-out);
  --t-mode: var(--dur-slow) var(--ease-in-out);
}

[data-mode="dark"] {
  /* ---- Dark: "Backlit" — the same panel lit from behind, same green cast ---- */
  --canvas: #0d100e;
  --paper: #141814; /* panel lifted off the canvas, mirroring the light mode */
  --paper-edge: #232a24;
  --paper-edge-2: #333c34; /* outlines stay readable against the panel */
  --ink: #e3e9e0;
  --ink-muted: #8f9a90; /* 6.15:1 on panel (AA body) */
  --ink-faint: #6a756c; /* 3.74:1 on panel (AA large/UI) */

  --local: #8c97e8;
  --cloud: #d6884a;
  --ok: #7fb596;
  --danger: #d6776b;

  --hl-token-keyword: #9ca6ee;
  --hl-token-string: #8fc0a1;
  --hl-token-string-expression: #8fc0a1;
  --hl-token-constant: #dc9d68;
  --hl-token-function: #82bcc7;
  --hl-token-link: #9ca6ee;

  /* Fills mix toward ink, which is light here — re-declared so the mix
     resolves against this mode's ink rather than inheriting Daylight's. */
  --fill-hover: color-mix(in srgb, var(--ink) 6%, transparent);
  --fill-press: color-mix(in srgb, var(--ink) 10%, transparent);
  --fill-selected: color-mix(in srgb, var(--ink) 9%, transparent);
  --fill-quiet: color-mix(in srgb, var(--ink) 4%, transparent);
  --hairline: color-mix(in srgb, var(--paper-edge-2) 80%, transparent);

  /* On a dark ground a shadow has to be deeper to read at all, and a faint
     light rim does the edge-defining work the shadow does in Daylight. */
  --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.3);
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3), 0 2px 10px rgba(0, 0, 0, 0.25);
  --shadow-float: 0 0 0 0.5px rgba(255, 255, 255, 0.06), 0 12px 34px rgba(0, 0, 0, 0.5),
    0 2px 8px rgba(0, 0, 0, 0.3);
  --shadow-modal: 0 0 0 0.5px rgba(255, 255, 255, 0.07), 0 28px 80px rgba(0, 0, 0, 0.6),
    0 6px 16px rgba(0, 0, 0, 0.35);
  --ring: 0 0 0 3px color-mix(in srgb, var(--local) 30%, transparent);
}

/* Respect OS reduced-motion: no pulsing dot, no blinking cursor, instant mode swap. */
@media (prefers-reduced-motion: reduce) {
  :root {
    --t-fast: 0s;
    --t-mode: 0s;
    --dur-fast: 0s;
    --dur: 0s;
    --dur-slow: 0s;
  }
  * {
    animation-duration: 0s !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0s !important;
  }
}


/* ---- styles/global.css ---- */
/* Global resets and base element styles. Component styles live alongside
   their components as CSS modules / scoped files. */

* {
  box-sizing: border-box;
}

html,
body,
#root {
  margin: 0;
  padding: 0;
  height: 100vh;
}

/* Desktop-app feel: nothing is highlightable by default, the way a native
   app's chrome and copy never smear under a drag-select. Actual text-entry
   surfaces opt back in below so typing, cut/copy/paste still work there. */
* {
  user-select: none;
  -webkit-user-select: none;
}

input,
textarea,
[contenteditable="true"],
[data-selectable="true"],
[data-selectable="true"] * {
  user-select: text;
  -webkit-user-select: text;
}

html {
  /* The shell sits on the recessed canvas; outlined panels use --paper on top
     of it, which is what gives the flat LCD layers their separation. */
  background: var(--canvas);
  color: var(--ink);
  font-family: var(--font-sans);
  transition: background var(--t-mode), color var(--t-mode);
  /* Grayscale smoothing and real kerning: type on a lit panel should look
     drawn, not rasterised. */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  font-kerning: normal;
}

body {
  overflow: hidden;
}

/* Visible focus ring on every interactive element (accessibility floor, §5.5).
   The outline follows the element's own radius; this rule used to force a
   2px corner radius as well, which squared off every rounded control the
   moment it took keyboard focus. */
:focus-visible {
  outline: 2px solid color-mix(in srgb, var(--local) 70%, transparent);
  outline-offset: 2px;
}

button {
  font-family: inherit;
  color: inherit;
}

/* One motion vocabulary for every interactive surface. Longhands, so a
   component that declares its own `transition` shorthand simply replaces this
   rather than fighting it — and every hover that used to snap now eases. */
button,
a,
input,
textarea,
select,
[role="button"],
[role="tab"],
[role="menuitem"],
[role="option"] {
  transition-property: background-color, border-color, color, box-shadow, opacity, transform;
  transition-duration: var(--dur-fast);
  transition-timing-function: var(--ease-out);
}

/* Arrivals. Menus and popovers grow out of where they were opened from;
   panels and cards rise a few pixels as they fade in. Short, and only ever
   on the way in — leaving is instant, so nothing lingers in the way. */
@keyframes pop-in {
  from {
    opacity: 0;
    transform: scale(0.96) translateY(-3px);
  }
}
@keyframes fade-in {
  from {
    opacity: 0;
  }
}
/* The same arrival for things that open upward (the composer's drop-ups). */
@keyframes pop-up {
  from {
    opacity: 0;
    transform: scale(0.96) translateY(4px);
  }
}
/* Dialogs settle into place from a touch smaller. */
@keyframes dialog-in {
  from {
    opacity: 0;
    transform: scale(0.96);
  }
}
@keyframes rise-in {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
}

/* Checkboxes and radios — the native controls ship a browser-blue fill that
   has nothing to do with the palette, so they are drawn from scratch as
   hairline-outlined boxes matching every other panel. Checked state uses ink,
   never an accent colour: black highlight is the whole highlight vocabulary.
   The glyph is drawn with borders rather than a data-URI SVG so it can inherit
   var(--paper) and stay correct in both modes. */
input[type="checkbox"],
input[type="radio"] {
  appearance: none;
  -webkit-appearance: none;
  width: 15px;
  height: 15px;
  margin: 0;
  flex: none;
  display: inline-grid;
  place-content: center;
  vertical-align: middle;
  border: 1px solid var(--paper-edge-2);
  border-radius: var(--radius-xs);
  background: var(--paper);
  cursor: pointer;
  transition:
    background var(--t-fast),
    border-color var(--t-fast),
    transform var(--dur) var(--ease-pop);
}
input[type="checkbox"]:active:not(:disabled),
input[type="radio"]:active:not(:disabled) {
  transform: scale(0.9);
}

input[type="radio"] {
  border-radius: 50%;
}

input[type="checkbox"]:hover:not(:disabled),
input[type="radio"]:hover:not(:disabled) {
  border-color: var(--ink-faint);
}

input[type="checkbox"]:checked,
input[type="checkbox"]:indeterminate,
input[type="radio"]:checked {
  background: var(--ink);
  border-color: var(--ink);
}

input[type="checkbox"]:checked::after {
  content: "";
  width: 4px;
  height: 8px;
  border: solid var(--paper);
  border-width: 0 1.5px 1.5px 0;
  transform: translateY(-1px) rotate(45deg);
  animation: check-in var(--dur) var(--ease-pop);
}
@keyframes check-in {
  from {
    opacity: 0;
    transform: translateY(-1px) rotate(45deg) scale(0.4);
  }
}

input[type="checkbox"]:indeterminate::after {
  content: "";
  width: 7px;
  border-top: 1.5px solid var(--paper);
}

input[type="radio"]:checked::after {
  content: "";
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--paper);
}

input[type="checkbox"]:disabled,
input[type="radio"]:disabled {
  opacity: 0.45;
  cursor: default;
}

/* ---- shared primitives ----
   Buttons and form fields used across routes and components alike. These lived
   in Settings.css and Apps.css, which meant every other consumer (Models,
   Skills, Tasks, PersonaEditor, MemoryPanel, SelfPanel, …) was relying on one
   of those route stylesheets happening to be in the bundle. They are the
   app's vocabulary, not any one route's, so they belong here. */

.btn-primary,
.btn-secondary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: var(--control);
  font-family: var(--font-sans);
  font-size: var(--fs-ui);
  font-weight: 500;
  line-height: 1;
  white-space: nowrap;
  border-radius: var(--radius);
  padding: 0 14px;
  cursor: pointer;
}
/* A press settles the button into the panel a fraction; the spring on the
   way back out is what makes it feel physical rather than toggled. */
.btn-primary:active:not(:disabled),
.btn-secondary:active:not(:disabled) {
  transform: scale(0.97);
  transition-duration: 60ms;
}

.btn-primary {
  background: var(--ink);
  color: var(--paper);
  border: 1px solid var(--ink);
  box-shadow: var(--shadow-xs);
}
.btn-primary:hover:not(:disabled) {
  background: color-mix(in srgb, var(--ink) 86%, var(--paper));
  border-color: color-mix(in srgb, var(--ink) 86%, var(--paper));
}
.btn-primary:disabled {
  opacity: 0.35;
  box-shadow: none;
  cursor: default;
}

.btn-secondary {
  background: var(--paper);
  color: var(--ink);
  border: 1px solid var(--paper-edge-2);
  box-shadow: var(--shadow-xs);
}
.btn-secondary:hover:not(:disabled) {
  border-color: var(--ink-faint);
  background: color-mix(in srgb, var(--paper) 94%, var(--ink));
}
.btn-secondary:disabled {
  opacity: 0.45;
  cursor: default;
}
.btn-secondary.selected {
  background: var(--fill-selected);
  border-color: var(--ink);
  color: var(--ink);
  box-shadow: none;
}

/* A link-style action. Colour-only feedback, no fill: it sits flush with the
   text column, and a hover pad would need side padding that pushes it off
   that edge. */
.btn-text {
  background: none;
  border: none;
  font-family: var(--font-sans);
  font-size: var(--fs-ui);
  cursor: pointer;
  color: var(--ink-muted);
  padding: 6px 2px;
}
.btn-text:hover {
  color: var(--ink);
}
.btn-text:active {
  opacity: 0.7;
}
.btn-text.danger:hover {
  color: var(--danger);
}

/* A key or chord, drawn as a hairline keycap with a hint of depth. */
.kbd {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-sans);
  font-size: var(--fs-caption);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  color: var(--ink-faint);
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-bottom-width: 1.5px;
  border-radius: var(--radius-xs);
  padding: 2px 5px;
  white-space: nowrap;
}

.field-label {
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  font-weight: 600;
  color: var(--ink-muted);
}

/* Two shapes, one name: an inline <span> qualifying a label ("(optional)"),
   and a standalone <p> under a field. The inline one must inherit the label's
   size and only step down in weight and colour; the block one sets its own
   type. Previously these were two competing base rules in two files and
   whichever landed last in the bundle styled both. */
.field-hint {
  font-weight: 400;
  color: var(--ink-faint);
}
p.field-hint {
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  line-height: 1.5;
  margin: 0;
}

/* Fields: hairline at rest, a soft halo on focus — the halo, not a heavier
   border, is what says "you are typing here". */
.field-input {
  font-family: var(--font-sans);
  font-size: var(--fs-ui);
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--paper-edge-2);
  border-radius: var(--radius);
  min-height: var(--control);
  padding: 7px 11px;
  outline: none;
}
.field-input:hover:not(:focus):not(:disabled) {
  border-color: color-mix(in srgb, var(--paper-edge-2) 60%, var(--ink-faint));
}
.field-input:focus {
  border-color: var(--ink-faint);
  box-shadow: var(--ring);
}
.field-input::placeholder,
textarea::placeholder,
input::placeholder {
  color: var(--ink-faint);
}

/* ---- segmented control ----
   The one way to pick one of a few: a recessed track with the chosen
   segment lifted out of it as a small paper tile. Seven different controls
   used to do this job — bordered tab strips, black-filled chips, outlined
   buttons, a bare text row — each a different height and a different idea of
   "selected". They keep their class names (the components still use them)
   but all of them are drawn here, and their own sheets only place them. */
.segmented,
.model-tabs,
.models-filters,
.filter-row,
.library-filter-row,
.usage-ranges,
.self-segmented,
.wb-segments,
.memory-scope,
.setting-actions[role="group"] {
  display: inline-flex;
  align-items: stretch;
  gap: 2px;
  padding: 2px;
  min-height: var(--control);
  background: var(--fill-press);
  border: none;
  border-radius: var(--radius);
  width: fit-content;
  max-width: 100%;
  flex-wrap: wrap;
  overflow: visible;
}
.segmented-item,
.model-tab,
.filter-chip,
.usage-range,
.self-segment,
.wb-segment,
.memory-scope-segment,
.setting-actions[role="group"] > .btn-secondary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 0 12px;
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  font-weight: 500;
  line-height: 1;
  color: var(--ink-muted);
  background: transparent;
  border: none;
  border-radius: var(--radius-sm);
  min-height: calc(var(--control) - 4px);
  box-shadow: none;
  cursor: pointer;
  white-space: nowrap;
}
.segmented-item:hover,
.model-tab:hover,
.filter-chip:hover,
.usage-range:hover,
.self-segment:hover,
.wb-segment:hover,
.memory-scope-segment:hover,
.setting-actions[role="group"] > .btn-secondary:hover {
  color: var(--ink);
  background: color-mix(in srgb, var(--paper) 45%, transparent);
}
.segmented-item.active,
.model-tab.active,
.filter-chip.active,
.usage-range.active,
.self-segment.active,
.wb-segment.active,
.memory-scope-segment.active,
.setting-actions[role="group"] > .btn-secondary.active,
.segmented-item.on,
.model-tab.on,
.filter-chip.on,
.usage-range.on,
.self-segment.on,
.wb-segment.on,
.memory-scope-segment.on,
.setting-actions[role="group"] > .btn-secondary.on,
.segmented-item.selected,
.model-tab.selected,
.filter-chip.selected,
.usage-range.selected,
.self-segment.selected,
.wb-segment.selected,
.memory-scope-segment.selected,
.setting-actions[role="group"] > .btn-secondary.selected,
.segmented-item[aria-pressed="true"],
.model-tab[aria-pressed="true"],
.filter-chip[aria-pressed="true"],
.usage-range[aria-pressed="true"],
.self-segment[aria-pressed="true"],
.wb-segment[aria-pressed="true"],
.memory-scope-segment[aria-pressed="true"],
.setting-actions[role="group"] > .btn-secondary[aria-pressed="true"],
.segmented-item[aria-selected="true"],
.model-tab[aria-selected="true"],
.filter-chip[aria-selected="true"],
.usage-range[aria-selected="true"],
.self-segment[aria-selected="true"],
.wb-segment[aria-selected="true"],
.memory-scope-segment[aria-selected="true"],
.setting-actions[role="group"] > .btn-secondary[aria-selected="true"] {
  color: var(--ink);
  background: var(--paper);
  box-shadow: var(--shadow-xs), 0 0 0 0.5px var(--hairline);
}

/* ---- empty state ----
   Nothing here yet is a calm moment, not an error: centred, quiet, in the
   UI's own face — never a dashed box of monospace. */
.empty-note {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: var(--sp-10) var(--sp-6);
  text-align: center;
  font-family: var(--font-sans);
  font-size: var(--fs-ui);
  line-height: 1.5;
  color: var(--ink-muted);
  background: var(--fill-quiet);
  border-radius: var(--radius-lg);
  animation: fade-in var(--dur) var(--ease-out);
}

/* Subtle scrollbars: a thin thumb inset from the edge by a transparent
   border, so it floats in the gutter the way an overlay scroller does. */
::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}
::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--ink) 18%, transparent);
  border: 3px solid transparent;
  background-clip: padding-box;
  border-radius: var(--radius-pill);
}
::-webkit-scrollbar-thumb:hover {
  background-color: color-mix(in srgb, var(--ink) 32%, transparent);
}
::-webkit-scrollbar-track,
::-webkit-scrollbar-corner {
  background: transparent;
}


/* ---- styles/menu.css ---- */
/* Shared row-level context menu: the small drop-down that hangs off a `⋯`
   button or a right-click. Used by the rail's chat rows and by the session
   menu in chat, so it lives here rather than in either component's sheet. */

.row-menu-backdrop {
  position: fixed;
  inset: 0;
  z-index: 39;
}

.row-menu {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  z-index: 40;
  min-width: 168px;
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-float);
  padding: 5px;
  display: flex;
  flex-direction: column;
  gap: 1px;
  /* Grows out of the ⋯ that opened it. The backdrop is a sibling, not an
     ancestor, so the transient transform cannot capture its fixed box. */
  transform-origin: top right;
  animation: pop-in var(--dur) var(--ease-out);
}

/* Buttons don't vertically centre their own text reliably once `overflow`
   is involved, so the item is an explicit flex box and anything that needs
   to truncate does so in an inner `.row-menu-label`, never on the button. */
.row-menu-item {
  display: flex;
  align-items: center;
  width: 100%;
  min-height: 30px;
  font-family: var(--font-sans);
  font-size: var(--fs-ui);
  line-height: 1.4;
  text-align: left;
  white-space: nowrap;
  background: none;
  border: none;
  border-radius: var(--radius-sm);
  color: var(--ink);
  padding: 6px 10px;
  cursor: pointer;
  transition-duration: 80ms;
}
.row-menu-label {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.row-menu-item:hover {
  background: var(--fill-hover);
}
.row-menu-item:active {
  background: var(--fill-press);
}
.row-menu-item.danger {
  color: var(--danger);
}
.row-menu-item.danger:hover {
  background: color-mix(in srgb, var(--danger) 9%, transparent);
}

/* An item that folds a list of destinations open under itself (the session
   menus' "Add to project…"). The list hangs off a hairline, the way a
   project's chats hang off theirs in the rail, and scrolls past a handful. */
.row-menu {
  max-width: 260px;
}
.row-menu-parent {
  gap: 8px;
}
.row-menu-caret {
  flex: none;
  color: var(--ink-faint);
  transition: transform var(--t-fast);
}
.row-menu-caret.open {
  transform: rotate(90deg);
}
.row-menu-sub {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 1px;
  max-height: 220px;
  overflow-y: auto;
  padding-left: 14px;
}
.row-menu-sub::before {
  content: "";
  position: absolute;
  left: 10px;
  top: 4px;
  bottom: 4px;
  width: 1px;
  background: var(--paper-edge-2);
}
.row-menu-sub .row-menu-item {
  min-height: 28px;
  padding: 6px 9px;
  color: var(--ink-muted);
}
.row-menu-sub .row-menu-item:hover {
  color: var(--ink);
}
/* A hairline between the kinds of thing a row menu does — organising the chat
   above, removing it below. */
.row-menu-sep {
  border: none;
  border-top: 1px solid var(--paper-edge);
  margin: 4px 6px;
}


/* ---- App.css ---- */
/* Both side columns are *registered* custom properties, so the browser knows
   they are lengths and can interpolate them. An unregistered custom property
   would snap between values, which is why show/hide has to animate through
   these and not through plain variables. The rail used to hard-code its width
   straight into `grid-template-columns` (52px vs 232px) instead of going
   through a registered property like the dock did — `grid-template-columns`
   itself doesn't interpolate between two different literal tracks, so the
   dock's open/close eased in while the rail's collapse/expand snapped. */
@property --dock-w {
  syntax: "<length>";
  inherits: true;
  initial-value: 340px;
}
@property --rail-w {
  syntax: "<length>";
  inherits: true;
  initial-value: 232px;
}
/* App shell grid — 232px rail + fluid main column + the Workbench dock, under
   a 42px topbar spanning the full width. The main column is `1fr`, so whatever
   the dock gives up it immediately takes.

   `SHL-27`: an open file, artifact or agent takes that main column whole — the
   widest thing the shell has. It had its own fourth column briefly (`SHL-26`),
   which meant a document and the conversation splitting one window between
   them and neither having room. What makes that affordable now is the session
   tab: the conversation is one click away in the strip, so the item can have
   everything. */
.app {
  display: grid;
  grid-template-columns: var(--rail-w) 1fr var(--dock-w);
  transition: --dock-w var(--t-mode), --rail-w var(--t-mode);
  /* Row 3 is explicit (not left implicit) so that "grid-row: 2 / -1" on the
     rail and the full-height routes actually reaches it — negative grid line
     indices only resolve against the *explicit* grid, not implicit tracks
     the composer would otherwise add on its own. Auto-collapses to 0 in any
     view that doesn't place something there. */
  /* 42px, matching the mockup. The header holds a wordmark, a location label,
     the sidebar's tabs and two toggles — none of which needed 48px, and six
     pixels off the chrome is six more of the reading column. */
  grid-template-rows: 42px 1fr auto;
  height: 100vh;
}

/* The chat/route surfaces occupy the second column, second row. The topbar
   and rail handle their own grid placement. */

/* Manual icon-only collapse (rail toggle). */
.app.rail-collapsed {
  --rail-w: 52px;
}

/* While dragging the divider the width is already following the pointer;
   easing it as well would make it lag behind the cursor. */
.app.dock-dragging {
  transition: none;
  cursor: col-resize;
  user-select: none;
}
/* `SHL-27`: an open item occupies the same cells the conversation does, so the
   conversation steps aside while one is showing — hidden rather than
   unmounted, which keeps the scroll position, the composer's draft and any
   running turn exactly as they were. Getting back is the session tab, and
   what you come back to is what you left.

   The composer goes with it. A text box addressed to the agent, sitting under
   a file that is not the conversation, invites a message into a stream nobody
   can see being written. */
.app.item-open .chat-body,
.app.item-open .composer-wrap,
.app.item-open .workspace-body {
  display: none;
}

/* Routes other than chat have no Workbench at all — nothing to animate. */
.app.no-dock {
  transition: none;
}

/* Responsive: narrow the rail gracefully on a smaller window (§5.5). */
@media (max-width: 900px) {
  .app:not(.rail-collapsed) {
    --rail-w: 188px;
  }
}
/* On a small window the dock gives way before the conversation does: capped
   at a third of the window, so the reading column never ends up the narrowest
   thing on screen. The dragged width still applies whenever it is smaller. */
@media (max-width: 1100px) {
  .app {
    grid-template-columns: var(--rail-w) minmax(0, 1fr) min(var(--dock-w), 32vw);
  }
}


/* ---- components/TopBar/TopBar.css ---- */
.topbar {
  position: relative;
  grid-column: 1 / -1;
  display: flex;
  align-items: stretch;
  background: var(--canvas);
}

/* No horizontal padding on `.topbar` itself — it used to have 20px on each
   side, which is exactly what was throwing every alignment in this file off:
   `.topbar-left` at `width: var(--rail-w)` sat 20px right of where the rail
   actually starts below it, and the doc zone's divider inherited the same
   20px error against the dock. Padding now lives *inside* the two fixed
   segments instead, where it can't move an edge that has to land exactly on
   a boundary drawn by a different component. */
.topbar-left {
  display: flex;
  align-items: center;
  gap: 10px;
  width: var(--rail-w);
  flex-shrink: 0;
  padding: 0 8px 0 11px;
  border-right: 1px solid var(--paper-edge-2);
  border-bottom: 1px solid var(--paper-edge-2);
  /* No transition of its own: `--rail-w` is already a registered, animated
     property on `.app` (see App.css), so this width follows its
     interpolation live rather than needing a second `width` transition. */
}

.sidebar-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  color: var(--ink-faint);
  cursor: pointer;
  width: 28px;
  height: 28px;
  padding: 0;
  border-radius: var(--radius-sm);
}
/* The rail toggle sits at the segment's right end, on the rail's own edge —
   the side the rail closes toward. */
.topbar-left .sidebar-toggle {
  flex-shrink: 0;
  margin-left: auto;
  margin-right: -2px;
}
.sidebar-toggle:hover {
  color: var(--ink);
  background: var(--fill-hover);
}
.sidebar-toggle:active {
  background: var(--fill-press);
}
/* A pressed toggle reads as "this panel is on", not just hovered. */
.sidebar-toggle.on {
  color: var(--ink);
}

.workbench-toggle {
  position: relative;
  margin-left: 0;
  margin-right: -5px;
}
.toggle-badge {
  position: absolute;
  top: 5px;
  right: 5px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--local);
  box-shadow: 0 0 0 2px var(--canvas);
  animation: pop-in var(--dur) var(--ease-pop);
}

.brand {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  overflow: hidden;
  font-family: var(--font-serif);
  /* The mockup's 15px wordmark in a 42px header. It was 17px in a 48px one;
     shrinking the row without shrinking what sits in it is how a "narrower"
     header ends up merely more cramped. */
  font-size: var(--fs-ui-lg);
  letter-spacing: 0.02em;
  font-weight: 500;
  white-space: nowrap;
}
/* Mirrors the rail's own collapsed state (icon only, label hidden) — the
   brand segment is pinned to `--rail-w` for the strip's alignment, and 52px
   only fits the mark. */
.app.rail-collapsed .brand span {
  display: none;
}
/* 52px holds one icon, not the mark and the toggle side by side: collapsed,
   the toggle takes the slot alone, centred over the rail's icon column, so
   the way back out is where the rail went. */
.app.rail-collapsed .topbar-left {
  justify-content: center;
  padding: 0;
}
.app.rail-collapsed .brand {
  display: none;
}
.app.rail-collapsed .topbar-left .sidebar-toggle {
  margin: 0;
}

/* `SHL-27`: the toggle, back in the flow at the row's right end.
   It was taken out of the flow while the header was cut into segments pinned
   to the shell's columns: 41px had to come from somewhere, and with the
   sidebar collapsed it came out of whichever segment sat to its left, sliding
   the strip off the pane it stood over. There are no pinned segments left, so
   the 41px simply comes off the strip's flexible width and nothing moves. */
.topbar-right {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding: 0 11px;
  border-bottom: 1px solid var(--paper-edge-2);
}

/* ---- the header as title bar (Windows, frameless window) ----
   Empty header space drags the window (`onTitleBarMouseDown`), so nothing in
   it should start a text selection on the way. */
.topbar.is-titlebar {
  user-select: none;
  -webkit-user-select: none;
}

/* Caption buttons: full header height and flush with the window's right
   edge, the shape Windows gives its own so they're found where the hand
   expects them — but in the app's ink and paper rather than system blue. */
.window-controls {
  flex-shrink: 0;
  display: flex;
  align-items: stretch;
  border-bottom: 1px solid var(--paper-edge-2);
}
.wc-btn {
  width: 46px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  padding: 0;
  color: var(--ink-muted);
  cursor: default;
}
.wc-btn:hover {
  background: var(--fill-hover);
  color: var(--ink);
}
.wc-btn:active {
  background: var(--fill-press);
}
.wc-close:hover,
.wc-close:active {
  background: var(--danger);
  color: var(--paper);
}
.wc-btn:focus-visible {
  outline: 1px solid var(--ink-muted);
  outline-offset: -3px;
}

/* `SHL-24`: where you are, stated once, in the space the session tabs used to
   fill. A label and not a control — no hover, no cursor, nothing to press —
   because everything it can name is reached from the Rail. Serif for a chat
   (its title is prose, and the Rail sets it in serif too), sans for a route,
   the same distinction the strip used to draw with two tab faces.

   `SHL-27`: shown only while the strip is not, so the two never name the same
   chat twice in one row. */
.topbar-where {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  padding: 0 16px;
  border-bottom: 1px solid var(--paper-edge-2);
}
/* One voice for "where you are", whatever it names: the window-title
   treatment — sans, semibold, a touch tight. It used to be serif for a chat
   and tiny tracked capitals for a route, so the same slot changed character
   every time you moved. */
.topbar-where-label {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-family: var(--font-sans);
  font-size: var(--fs-ui);
  font-weight: 600;
  letter-spacing: var(--tracking-tight);
  color: var(--ink);
  animation: fade-in var(--dur) var(--ease-out);
}


/* ---- components/TopBar/TabStrip.css ---- */
/* `SHL-27`: the strip is the header's flexible middle — everything between the
   brand and the toggles. It used to be pinned to the width of the pane its
   tabs drove, which was the narrowest box in the row; the tabs now drive the
   whole main column, so there is no edge to align to and the width is simply
   whatever the row has left. A path finally fits in a tab.

   Tabs sit on the recessed `--canvas` the header inherits from `html`
   (global.css) — the same ground the Rail's rows sit on. The *active* tab
   lifts onto `--paper`, the same surface as the column it fills, so a focused
   tab reads as a physical extension of what is under it rather than a
   separately coloured label pointing at it. */

/* `ScrollZone` (TabStrip.tsx): an outer flex row — `[left chevron?, the
   scrollable strip itself, right chevron?]` — sized by `.ts-zone-items`. The
   chevrons are the reachability answer: shown only when there's actually
   somewhere to scroll to, gone otherwise, so nothing decorates the common
   case where every open tab already fits. */
.ts-scrollzone {
  display: flex;
  align-items: stretch;
  min-width: 0;
}

.ts-zone {
  display: flex;
  align-items: stretch;
  flex: 1;
  min-width: 0;
  overflow-x: auto;
  overflow-y: hidden;
  /* No visible scrollbar — the chevrons are the affordance. Wheel scroll and
     scroll-into-view on focus (TabStrip.tsx) still work without one. */
  scrollbar-width: none;
  /* Continues the header's bottom edge across any empty space past the last
     tab — each tab then paints over its own share with its own border,
     erased when active, so the line reads as unbroken everywhere else. */
  border-bottom: 1px solid var(--paper-edge-2);
}
.ts-zone::-webkit-scrollbar {
  display: none;
}

.ts-nav {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  background: var(--canvas);
  border: none;
  border-bottom: 1px solid var(--paper-edge-2);
  color: var(--ink-faint);
  cursor: pointer;
}
.ts-nav:hover {
  color: var(--ink);
  background: var(--fill-hover);
}
.ts-nav-left {
  border-right: 1px solid var(--paper-edge-2);
}
.ts-nav-right {
  border-left: 1px solid var(--paper-edge-2);
}

/* Holds the conversation and its open items. Sans is the default; a file or a
   patch is mono, because its label is a path. */
.ts-scrollzone.ts-zone-items {
  flex: 1;
  min-width: 0;
  font-family: var(--font-sans);
}

.ts-tab {
  display: flex;
  align-items: stretch;
  flex-shrink: 0;
  /* Room for a real file name. It was 170px when the strip was confined to the
     pane's width and every tab was fighting the same shortage. */
  max-width: 220px;
  min-width: 0;
  border-right: 1px solid var(--paper-edge-2);
  color: var(--ink-faint);
  cursor: pointer;
  /* No border-bottom of its own — `.ts-zone` already draws that line the
     full width of the strip, and a second one here on top of it was what
     doubled/thickened the line under every ordinary tab. Only the active
     tab (below) needs to touch it at all: to erase its own share of it. */
}
.ts-tab.active {
  background: var(--paper);
  border-bottom: 1px solid var(--paper);
  color: var(--ink);
}
/* `SHL-19`: the tab is the focusable element now, so it needs the focus ring
   the label's button used to carry. The global `:focus-visible` (global.css)
   already draws it — only the offset is overridden, since an outline sitting
   2px *outside* a tab in a 42px-tall strip is clipped by the header's bounds
   and by the neighbouring tab. Drawn inside, it stays whole. */
.ts-tab:focus-visible {
  outline-offset: -2px;
}

/* The mockup's own tab sizes, mapped onto DESIGN.md's ramp: Timeline (12.5px)
   sans for an artifact or an agent, mono and a step smaller for a path. */
.ts-tab-main {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  flex: 1;
  padding: 0 4px 0 10px;
  font-size: var(--fs-footnote);
  color: inherit;
}
.ts-tab:hover .ts-tab-main {
  color: var(--ink-muted);
}
.ts-tab.active .ts-tab-main {
  color: var(--ink);
  font-weight: 500;
}
.ts-tab-label {
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The item's own glyph — a page corner, a sparkle, a branching agent. It says
   where the tab came from before the label is read, and it never shrinks away
   with the label. */
.ts-tab-icon {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 13px;
  color: var(--ink-faint);
}
.ts-tab.active .ts-tab-icon {
  color: var(--ink);
}
.ts-tab.ts-mono .ts-tab-main {
  font-family: var(--font-mono);
  font-size: var(--fs-footnote);
}

/* `SHL-27`: the session tab. Serif and a step larger, because it names a
   conversation and every other place a chat's title appears in this shell —
   the Rail's rows, the location label this replaces — sets it in serif. It is
   also the one tab with no ×, so it keeps the close button's 22px as padding
   and doesn't sit tighter than its neighbours. */
.ts-session {
  max-width: 260px;
  /* A firmer edge than the hairline between two item tabs: everything right of
     it is something taken *out* of the chat named to its left. */
  border-right: 1px solid var(--paper-edge-2);
  margin-right: 3px;
}
.ts-session .ts-tab-main {
  font-family: var(--font-serif);
  font-size: var(--fs-ui);
  padding-right: 12px;
}

/* A file changed this session — the same dot the tree's own rows carry. */
/* PRJ-UI-2: the dot sits where the close button sits, the way an unsaved
   editor buffer wears it. Hovering the tab trades the dot for the button, and
   pressing the dot shows the patch instead of closing anything. */
.ts-dirty {
  flex-shrink: 0;
  width: 22px;
  align-self: stretch;
  border: none;
  padding: 0;
  cursor: pointer;
  background: radial-gradient(circle, var(--ink) 3px, transparent 3.5px);
}
/* An unsaved buffer is the user's own work, not the agent's, so it takes the
   hollow ring the rest of the UI uses for "yours, in progress" rather than the
   solid dot that means "the agent touched this". It is a span, not a button:
   there is nowhere to go — the file is already open in front of you. */
.ts-dirty.unsaved {
  cursor: default;
  background: radial-gradient(
    circle,
    transparent 1.5px,
    var(--ink) 1.75px,
    var(--ink) 3px,
    transparent 3.5px
  );
}
.ts-tab.has-slot .ts-tab-close {
  display: none;
}
/* Hover only: on keyboard focus the dot must stay put to be pressed, and
   `Ctrl+W` still closes the tab. */
.ts-tab.has-slot:hover .ts-dirty {
  display: none;
}
.ts-tab.has-slot:hover .ts-tab-close {
  display: flex;
}
/* A live page is a state, not a quantity — one dot, breathing gently, stopped
   for anyone who asked motion to stop. */
.ts-live {
  flex-shrink: 0;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--ink-muted);
  animation: ts-breathe 2.6s ease-in-out infinite;
}
@keyframes ts-breathe {
  0%,
  100% {
    opacity: 0.35;
  }
  50% {
    opacity: 1;
  }
}
@media (prefers-reduced-motion: reduce) {
  .ts-live {
    animation: none;
    opacity: 0.9;
  }
}
.ts-count {
  flex-shrink: 0;
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-caption);
  color: var(--ink-muted);
  background: var(--paper-edge);
  /* The system's chip radius. This was a hand-picked 3px, which is not a step
     on the scale and not a shape anything else in the app has — DESIGN.md
     gives chips `--radius` (4px) and caps everything at 6px. */
  border-radius: var(--radius);
  padding: 0 4px;
}
.ts-tab.active .ts-count {
  background: var(--ink);
  color: var(--paper);
}

.ts-tab-close {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  background: none;
  border: none;
  color: var(--ink-faint);
  cursor: pointer;
}
.ts-tab-close:hover {
  color: var(--ink);
}



/* ---- components/Rail/Rail.css ---- */
.rail {
  grid-column: 1;
  grid-row: 2 / -1;
  border-right: 1px solid var(--paper-edge-2);
  padding: 14px 0 14px 12px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* The scroll container itself carries no horizontal padding, so its
   scrollbar sits flush against the panel's right edge instead of floating
   inset from it; children that need clearance from that edge give themselves
   a right margin (`.rail-group`, `.rail-job-row` below) rather than relying
   on a blanket `.rail-scroll > *` rule, which loses the cascade to any child
   that sets its own. */
.rail-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
}

/* SCH-UI-4: a scheduled job running unattended — quiet, but always visible,
   and always stoppable. */
.rail-job-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 12px 12px 0;
  padding: 7px 8px;
  background: var(--fill-quiet);
  border-radius: var(--radius);
  font-size: var(--fs-footnote);
  color: var(--ink-muted);
}
.rail-job-dot {
  flex: none;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--ink);
}
@media (prefers-reduced-motion: no-preference) {
  .rail-job-dot {
    animation: mark-breathe 2.4s ease-in-out infinite;
  }
}
.rail-job-label {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rail-job-stop {
  flex: none;
  font-size: var(--fs-micro);
  line-height: 1;
  background: none;
  border: none;
  color: var(--ink-faint);
  cursor: pointer;
  padding: 2px;
}
.rail-job-stop:hover {
  color: var(--ink);
}
.rail.collapsed .rail-job-row {
  justify-content: center;
  padding: 7px 0;
}
.rail.collapsed .rail-job-label {
  display: none;
}

.rail-top-actions {
  display: flex;
  flex-direction: column;
  gap: 1px;
  margin: 0 12px 18px 0;
}
/* The top actions are sidebar rows, not outlined buttons: the same height,
   inset, hover fill and selected fill as every chat row below them. Four
   hairline boxes stacked over a borderless list read as two different
   sidebars glued together. */
.rail-top-btn {
  display: flex;
  align-items: center;
  min-height: 30px;
  font-family: var(--font-sans);
  font-size: var(--fs-ui);
  color: var(--ink);
  background: none;
  border: none;
  border-radius: var(--radius);
  padding: 0 8px;
  text-align: left;
  cursor: pointer;
}
.rail-top-btn:hover {
  background: var(--fill-hover);
}
.rail-top-btn:active {
  background: var(--fill-press);
}
.rail-top-btn.active {
  background: var(--fill-selected);
  font-weight: 500;
}
.rail-top-btn .nav-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  margin-right: 10px;
  color: var(--ink-muted);
  flex-shrink: 0;
}
.rail-top-btn.active .nav-icon,
.rail-top-btn:hover .nav-icon {
  color: var(--ink);
}
/* New chat leads: the one action the sidebar exists for gets the ink. */
.rail-top-btn.new-chat {
  font-weight: 500;
  background: var(--fill-quiet);
}
.rail-top-btn.new-chat:hover {
  background: var(--fill-hover);
}
.rail-top-btn.new-chat:active {
  background: var(--fill-press);
}
.rail-top-btn.new-chat .nav-icon {
  color: var(--ink);
}
.rail-top-btn .rail-kbd {
  margin-left: auto;
}
.rail.collapsed .rail-kbd {
  display: none;
}

/* `PRJ-3`: "Projects" opens the overview; the `+` beside it is the same quick
   create the button used to be — a small icon button that surfaces on hover,
   inside the same row, rather than a second bordered cell. */
.rail-top-btn-row {
  position: relative;
  display: flex;
  align-items: stretch;
}
.rail-top-btn-row .rail-top-btn {
  flex: 1;
  min-width: 0;
}
.rail-top-btn-add {
  position: absolute;
  top: 50%;
  right: 4px;
  width: 22px;
  height: 22px;
  margin-top: -11px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  color: var(--ink-muted);
  background: none;
  border: none;
  border-radius: var(--radius-sm);
  cursor: pointer;
  opacity: 0;
}
.rail-top-btn-row:is(:hover, :focus-within) .rail-top-btn-add {
  opacity: 1;
}
.rail-top-btn-add:hover {
  color: var(--ink);
  background: var(--fill-press);
}
.rail.collapsed .rail-top-btn-add {
  display: none;
}

/* Wraps one labeled run of rows — gives the group its own right clearance
 * from the panel edge, since `.rail-scroll` itself carries none. */
.rail-group {
  margin-right: 12px;
}
/* Sticky within its own group, so the next group's label pushes it out: you
   always know which stretch of time you are scrolled into. */
.rail-label {
  position: sticky;
  top: 0;
  z-index: 2;
  margin: 0;
  padding: 6px 0 6px 8px;
  background: var(--canvas);
  font-size: var(--fs-caption);
  font-weight: 600;
  color: var(--ink-faint);
}

.nav-label {
  display: inline;
}

.chat-list {
  list-style: none;
  margin: 0 0 16px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

/* One row, chat or project. The title takes whatever the trailing slot
   leaves; nothing else sits in the row's flow at rest, so a title runs as
   far as it can before it clips. The row itself must not clip, or the row
   menu would be cut off at the rail's edge. */
.chat-row,
.project-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: 30px;
  padding: 0 8px;
  border-radius: var(--radius);
  font-size: var(--fs-ui);
  color: var(--ink-muted);
  white-space: nowrap;
  cursor: pointer;
  transition:
    background-color var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out);
}
.chat-row:focus-visible,
.project-row:focus-visible {
  outline-offset: -2px;
}
.chat-row:hover,
.project-row:hover {
  background: var(--fill-hover);
  color: var(--ink);
}
.chat-row:active,
.project-row:active {
  background: var(--fill-press);
}

/* Where you are: one step past hover's fill, not an inverted block. Hover
   fills flat with `--paper-edge`; active tints that same fill a little
   further toward ink — clearly a firmer, different state, but still the
   flat, hairline-family surface the rest of the panel is built from, rather
   than a block of ink dropped into the middle of the list. `--paper-edge-2`
   was tried here first and read muddy: it's a saturated structural token
   meant for 1px borders, not a fill covering a whole row. Mixing toward ink
   instead keeps the same neutral cast as hover, and in dark mode it
   lightens rather than darkens — ink is the one highlight in both lighting
   conditions, so "more of it" means the same thing either way. */
.chat-row.active,
.project-row.current {
  background: var(--fill-selected);
  color: var(--ink);
  font-weight: 500;
}
.chat-row.active :is(.row-stamp, .chat-digest, .chat-workspace, .chat-more),
.project-row.current :is(.row-stamp, .project-twisty, .chat-more) {
  color: var(--ink-muted);
}
.chat-row.active .chat-more:hover,
.project-row.current :is(.chat-more, .project-twisty):hover {
  color: var(--ink);
}

.chat-title {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Witnessed digestion (PRES-2): a pulse while I'm reading the conversation
   back, and a quiet mark once it taught me something. It follows the title
   rather than holding a slot, so rows without it spend nothing on it. The
   offer to reflect lives in the row menu. */
.chat-digest {
  flex: none;
  font-size: var(--fs-caption);
  line-height: 1;
  color: var(--ink-faint);
}
@media (prefers-reduced-motion: no-preference) {
  .chat-digest.reflecting {
    animation: mark-breathe 2.4s ease-in-out infinite;
  }
}
.chat-workspace {
  flex: none;
  color: var(--ink-faint);
  font-size: var(--fs-caption);
}

/* The trailing slot: a reading at rest (when, or how many), the ⋯ on hover or
   focus, in the same place. The actions never cost the title width, and the
   list gains a column of information instead of two blank gaps. */
.row-slot {
  position: relative;
  flex: none;
  align-self: stretch;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  min-width: 22px;
  margin-left: auto;
}
.row-stamp {
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-caption);
  font-weight: 400;
  color: var(--ink-faint);
  transition: opacity var(--t-fast);
}
/* No transform here: a transformed ancestor would turn the menu's fixed
   backdrop into a box the size of this slot. */
.chat-menu-wrap {
  position: absolute;
  top: 0;
  right: -4px;
  bottom: 0;
  display: flex;
  align-items: center;
}
.chat-more {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  background: none;
  border: none;
  border-radius: var(--radius);
  color: var(--ink-faint);
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--t-fast);
}
.chat-more:hover {
  color: var(--ink);
  background: var(--fill-press);
}
:is(.chat-row, .project-row):is(:hover, :focus-within) .chat-more,
.chat-more[aria-expanded="true"] {
  opacity: 1;
}
:is(.chat-row, .project-row):is(:hover, :focus-within) .row-stamp,
.row-slot:has(.chat-more[aria-expanded="true"]) .row-stamp {
  opacity: 0;
}

/* The menu itself (.row-menu) is shared with the session menu in chat — see
   styles/menu.css. */

.rail-divider {
  border: none;
  border-top: 1px solid var(--hairline);
  margin: 4px 12px 8px 0;
  width: auto;
}

/* The footer (Settings, and whatever else ever joins it): the same row
   language as `.chat-row`/`.project-row` above — flat fill on hover, a
   tinted fill for the current one, same height, same left inset. It used to
   be plain text with a different padding and no hover feedback at all, which
   is why it read as a leftover strip below the divider instead of the last
   row in the same list. */
.rail-nav {
  list-style: none;
  margin: 0 12px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.rail-nav li {
  display: flex;
  align-items: center;
  min-height: 30px;
  padding: 0 8px;
  transition:
    background-color var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out);
  border-radius: var(--radius);
  font-size: var(--fs-ui);
  color: var(--ink-muted);
  cursor: pointer;
}
.rail-nav li:hover {
  background: var(--fill-hover);
  color: var(--ink);
}
.rail-nav li:active {
  background: var(--fill-press);
}
.rail-nav li.active {
  background: var(--fill-selected);
  color: var(--ink);
  font-weight: 500;
}
.rail-nav li .nav-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  margin-right: 10px;
  color: var(--ink-muted);
  flex-shrink: 0;
}
.rail-nav li.active .nav-icon,
.rail-nav li:hover .nav-icon {
  color: var(--ink);
}
/* Settings is the foot of the rail and its one standing destination, so it
   rests on the same quiet fill as New chat at the top. */
.rail-nav-footer li {
  background: var(--fill-quiet);
}

/* Something about the agent's self is waiting on the user (SOUL-UI-3). A dot,
   not a count — this is an invitation to look, not an alarm. */
.nav-badge {
  display: inline-block;
  width: 6px;
  height: 6px;
  margin-left: 7px;
  border-radius: 50%;
  background: var(--ink);
  vertical-align: middle;
}
.rail.collapsed .nav-badge {
  margin-left: 4px;
}

/* Collapsed (icon-only) rail — manual toggle or auto at very narrow widths. */
.rail.collapsed {
  padding: 12px 5px;
  align-items: stretch;
  /* The session flyout is positioned relative to a button inside this
     scroll container; auto overflow would clip it at the rail's edge. */
  overflow: visible;
}
.rail.collapsed .chat-list,
.rail.collapsed .rail-label,
.rail.collapsed .rail-divider {
  display: none;
}
/* The 16px right clearance the expanded rail's rows need (so they don't sit
   flush against the panel edge) is dead weight in the collapsed, icon-only
   rail — it's already only 52px wide, padded 6px each side. */
.rail.collapsed .rail-top-actions,
.rail.collapsed .rail-job-row,
.rail.collapsed .rail-nav {
  margin-right: 0;
}
.rail.collapsed .rail-top-actions {
  margin-bottom: 10px;
}
.rail.collapsed .rail-top-btn {
  justify-content: center;
  padding: 0;
  min-height: 34px;
}
.rail.collapsed .rail-top-btn .nav-icon {
  margin-right: 0;
}
/* The label does *not* flex. Giving it the slack pushed the "changes waiting"
   badge to the far end of the row, where it sat against the engine readout and
   read as belonging to it — the badge is about Settings, so it stays touching
   the word Settings. The slack goes to the readout's own left margin instead,
   so only the readout is pushed right. */
.rail-nav-footer li .nav-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rail-nav-footer li .engine-status {
  margin-left: auto;
}

/* Collapsed, the row is the cog with the dot beside it. Both centred as one
   pair, so the icon does not shift sideways when the engine starts. */
.rail.collapsed .rail-nav li {
  justify-content: center;
  padding: 9px 0;
  gap: 5px;
}
.rail.collapsed .rail-nav li .nav-icon {
  margin-right: 0;
}
.rail.collapsed .nav-label {
  display: none;
}

/* Collapsed rail: the chat list collapses to a single "sessions" button that
   opens a flyout menu, rather than disappearing outright. */
.rail-session-collapsed {
  position: relative;
  display: flex;
  justify-content: center;
  margin-bottom: 14px;
}
.rail-session-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 34px;
  padding: 0;
  background: none;
  border: none;
  border-radius: var(--radius);
  color: var(--ink-muted);
  cursor: pointer;
}
.rail-session-btn:hover,
.rail-session-btn[aria-expanded="true"] {
  background: var(--fill-hover);
  color: var(--ink);
}
.rail-session-menu.row-menu {
  transform-origin: top left;
  top: 0;
  left: calc(100% + 8px);
  right: auto;
  width: 224px;
  max-width: 224px;
  max-height: 70vh;
  overflow-y: auto;
  padding: 6px;
  gap: 2px;
}
.rail-session-menu .row-menu-item {
  font-size: var(--fs-footnote);
}
.rail-session-menu .row-menu-item.active {
  color: var(--ink);
  font-weight: 500;
  background: var(--fill-selected);
}
.row-menu-empty {
  display: flex;
  align-items: center;
  min-height: 34px;
  font-size: var(--fs-footnote);
  color: var(--ink-faint);
  padding: 8px 11px;
}

/* `PRJ-UI-1`: projects, listed above the date-grouped chats, in the same rows
   (`.chat-row` / `.project-row` above) — one more group in the Rail rather
   than a second navigation model. A project is a container, so its row reads
   one step heavier than the chats inside it. */
.project-row {
  gap: 5px;
  padding-left: 3px;
  color: var(--ink);
  font-weight: 500;
}
/* The active chat is inside this project, but the project is folded: its row
   takes the lighter fill, so "you are in here" survives the collapse. */
.project-row-wrap:not(.expanded) > .project-row.contains-active:not(:hover) {
  background: var(--fill-hover);
}

/* The twisty and the row do different things: the row opens the project, the
   twisty only shows what is inside it. Drawn, and turned rather than swapped. */
.project-twisty {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  padding: 0;
  background: none;
  border: none;
  border-radius: var(--radius);
  color: var(--ink-faint);
  cursor: pointer;
}
.project-twisty:hover {
  color: var(--ink);
}
.project-twisty svg {
  transition: transform var(--dur) var(--ease-out);
}
.project-row-wrap.expanded .project-twisty svg {
  transform: rotate(90deg);
}

/* `PRJ-UI-1a`: hollow for a project with no working folder, filled for one
   with. Two kinds of project, one mark, no extra row of chrome. It becomes the
   language dot once there is a project card to read a language from
   (`COD-1`). */
.project-dot {
  flex-shrink: 0;
  width: 5px;
  height: 5px;
  margin-right: 4px;
  border-radius: 50%;
  border: 1px solid var(--ink-faint);
}
.project-dot.has-folder {
  background: var(--ink-faint);
}
.project-row.contains-active .project-dot,
.project-row.current .project-dot {
  border-color: var(--ink);
}
.project-row.contains-active .project-dot.has-folder,
.project-row.current .project-dot.has-folder {
  background: var(--ink);
}

.project-rename {
  flex: 1;
  min-width: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-ui);
  font-weight: 400;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--ink-faint);
  border-radius: var(--radius-sm);
  padding: 2px 6px;
  outline: none;
  box-shadow: var(--ring);
}

/* An expanded project's sessions sit in place of the flat list, hung off a
   hairline that drops from under the chevron — nesting drawn as structure,
   not implied by a margin. */
.project-sessions {
  position: relative;
  margin: 1px 0 4px;
  padding-left: 16px;
}
.project-sessions::before {
  content: "";
  position: absolute;
  left: 11px;
  top: 2px;
  bottom: 2px;
  width: 1px;
  background: var(--hairline);
}
.project-sessions {
  animation: rise-in var(--dur) var(--ease-out);
}
.project-empty {
  padding: 6px 7px;
  font-size: var(--fs-timeline);
  color: var(--ink-faint);
}


/* ---- components/EngineStatus/EngineStatus.css ---- */
/* The local engine's state, beside the Settings row at the foot of the Rail.
   Quieter than it was in the header: no box of its own, because it now stands
   inside a row that already has edges, and a bordered chip nested in a nav row
   reads as a control you can press. It is a readout. */
.engine-status {
  display: flex;
  align-items: center;
  gap: 5px;
  flex-shrink: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-caption);
  color: var(--ink-faint);
  max-width: 132px;
  white-space: nowrap;
  overflow: hidden;
}
.engine-status .engine-label {
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Dot alone in the collapsed rail: the readout shrinks to the dot rather than
   keeping a label-shaped gap where the words used to be. */
.engine-status.dot-only {
  gap: 0;
}

.engine-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--ink-faint);
  flex-shrink: 0;
}
.engine-orb {
  display: flex;
  flex-shrink: 0;
}
.engine-status.ready .engine-dot {
  background: var(--ok);
}
.engine-status.starting .engine-dot {
  background: var(--local);
  animation: engine-pulse 1.2s ease-in-out infinite;
}
@keyframes engine-pulse {
  0%,
  100% {
    opacity: 1;
    transform: scale(1);
  }
  50% {
    opacity: 0.4;
    transform: scale(0.7);
  }
}
@media (prefers-reduced-motion: reduce) {
  .engine-status.starting .engine-dot {
    animation: none;
  }
}


/* ---- components/Mark/Mark.css ---- */
/* The living mark (PRES-1). Ink tokens and one slow animation at a time —
   quiet biology, not a status light. Every state it can be in is also written
   into its aria-label and title, so nothing here is load-bearing. */

.poiesis-mark {
  flex: none;
  color: var(--ink);
}
.poiesis-mark .mark-membrane {
  stroke: currentColor;
  opacity: 0.55;
}
.poiesis-mark .mark-nucleus,
.poiesis-mark .mark-orbit {
  fill: currentColor;
}
.poiesis-mark .mark-orbit {
  opacity: 0.7;
}
/* Not yet grown: quieter still, so the first earned dot is a visible change. */
.poiesis-mark.stage-0 .mark-membrane {
  opacity: 0.35;
}

@media (prefers-reduced-motion: no-preference) {
  /* Working: the nucleus breathes. */
  .poiesis-mark.state-active .mark-nucleus {
    animation: mark-breathe 2.4s ease-in-out infinite;
  }
  /* Reflecting: the orbits go round. */
  .poiesis-mark.state-reflecting .mark-orbits {
    transform-origin: 12px 12px;
    animation: mark-orbit 1.8s linear infinite;
  }
  /* Recovering: the membrane flickers twice, then settles. */
  .poiesis-mark.state-healing .mark-membrane {
    animation: mark-flicker 1.5s ease-in-out 2;
  }
  /* `CPX-2`: tending to myself shapes the membrane once; waiting for an answer is
     the slow breath, quieter than working, because nothing is being done. */
  .poiesis-mark.state-tending .mark-membrane {
    animation: mark-flicker 1.5s ease-in-out 1;
  }
  .poiesis-mark.state-listening .mark-nucleus {
    animation: mark-breathe-slow 5s ease-in-out infinite;
  }
  /* SCH-UI-2: an unread digest, while otherwise resting — the same breathe as
     "working", just slower and shallower, so it reads as quiet rather than
     busy. Clears itself the moment the Self view is opened. */
  .poiesis-mark.digest-pending .mark-nucleus {
    animation: mark-breathe-slow 5s ease-in-out infinite;
  }
}

@keyframes mark-breathe {
  0%,
  100% {
    opacity: 0.5;
  }
  50% {
    opacity: 1;
  }
}
@keyframes mark-orbit {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}
@keyframes mark-flicker {
  0%,
  100% {
    opacity: 0.55;
  }
  50% {
    opacity: 0.15;
  }
}
@keyframes mark-breathe-slow {
  0%,
  100% {
    opacity: 0.55;
  }
  50% {
    opacity: 0.85;
  }
}

/* The same mark, standing in as the Self destination's rail icon (PRES-3). */
.rail .nav-icon .poiesis-mark {
  vertical-align: middle;
}


/* ---- routes/Chat.css ---- */
/* The chat body occupies the main grid cell (col 2, row 2): just the reading
   column now — files and artifacts live in the Workbench dock in column 3. The
   composer sits in row 3 of the same column. */
.chat-body {
  grid-column: 2;
  grid-row: 2;
  display: flex;
  min-height: 0;
  overflow: hidden;
  position: relative;
  container-type: inline-size;
}
/* Once the column is too narrow for the reading measure plus a gutter, the
   session menu would sit on top of the pinned question; give it its own lane. */
@container (max-width: 800px) {
  .chat-body .main {
    padding-right: 56px;
  }
}

/* Session actions, pinned to the top-right of the reading area. Outside the
   scroll container so it stays put as the conversation moves under it. */
.session-menu-wrap {
  position: absolute;
  top: 8px;
  right: 14px;
  z-index: 15;
}
.session-more {
  display: flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--canvas) 85%, transparent);
  backdrop-filter: blur(8px);
  border: 1px solid transparent;
  border-radius: var(--radius);
  color: var(--ink-muted);
  width: 30px;
  height: 30px;
  line-height: 1;
  font-size: var(--fs-ui);
  cursor: pointer;
}
.session-more:hover,
.session-more[aria-expanded="true"] {
  background: var(--fill-hover);
  border-color: var(--hairline);
  color: var(--ink);
}
.main {
  flex: 1;
  position: relative;
  overflow-y: auto;
  display: flex;
  justify-content: center;
  padding: 28px 24px 0;
  min-width: 0;
}

.conversation {
  width: 100%;
  max-width: 700px;
  /* The gap to the composer lives here, on the scrolled child — not as
     padding-bottom on `.main`. A flex scroll container's end padding is not
     reliably part of the scrollable overflow region, so putting it there
     silently disappears once the stream is long enough to scroll. */
  padding-bottom: 32px;
}

/* The composer places itself in row 3 of this column — see Composer.css. */

/* empty state — invites action rather than apologizes (§5.6) */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 22px;
  min-height: 50vh;
}

/* PRES-6: the one-time introduction. Existing card tokens, no illustration,
   no modal — it reads as something said, not something popped up. */
.intro-card {
  max-width: 46ch;
  padding: 16px 20px;
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xs);
  animation: rise-in var(--dur-slow) var(--ease-out);
}
.intro-body {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-ui);
  line-height: 1.65;
  color: var(--ink);
}

/* PRES-5: ambient, not promotional — one muted line, no card, no border. */
.ambient-line {
  background: none;
  border: none;
  padding: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  color: var(--ink-muted);
  cursor: pointer;
}
.ambient-line:hover {
  color: var(--ink);
}
.empty-line {
  font-family: var(--font-serif);
  font-size: var(--fs-display);
  color: var(--ink-faint);
  text-align: center;
}

@media (max-width: 720px) {
  .main {
    grid-column: 2;
  }
}


/* ---- components/Conversation/Conversation.css ---- */
/* ---- reading size ----
   The Reading size setting (`--reading-scale`) scales the message stream as
   one unit — questions, prose, tool rows, plan cards, code, blocks, media and
   the spacing between them — rather than the prose alone. Scaling only the
   font left the chrome around it fixed, so "Larger" read as big text in
   small boxes and "Smaller" as small text in loose ones. `zoom` keeps every
   proportion the Standard size was designed at; the stream still takes the
   column's full width because a zoomed box lays out in its own units. */
.message-stream {
  zoom: var(--reading-scale);
}

/* ---- user turn ----
   A white panel with the hairline outline and radius every other panel in
   the app uses, the question set a little heavier than the answer, its time
   at the end of the line in the Rail's mono stamp.

   It also stays pinned to the top of the scroll area for as long as you're
   reading the answer underneath it: `.turn-group` (Chat.tsx) wraps a user
   turn together with every assistant turn that follows until the next user
   turn, and `.turn-user` is `position: sticky` within that wrapper — the
   classic sticky-list-header trick, so the question you're currently reading
   an answer to is always in view, and the next question naturally pushes it
   off once you scroll into its own group. */
.turn-user {
  position: sticky;
  top: 0;
  /* `z-index` isn't optional here: a positioned box only wins the paint order
     over later static-flow siblings (like the `PlanCard`/`Timeline` in the
     `AgentRun` right below it) when its stacking context actually says so —
     without this, content scrolling underneath showed through the sticky
     question instead of being covered by it. */
  z-index: 2;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  column-gap: 18px;
  align-items: baseline;
  margin-bottom: 20px;
  padding: 12px 16px;
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xs);
  animation: rise-in var(--dur-slow) var(--ease-out);
}
/* While pinned, the stream's top padding is a gap above the card that the
   answer scrolls through — text peeking over the question. A band of canvas
   closes it. Unpinned it covers only empty space (the stream's padding, or
   the previous turn's bottom margin). Offsets are measured from the padding
   box, so the extra 1px keeps the band clear of the card's top border. */
.turn-user::before {
  content: "";
  position: absolute;
  left: -1px;
  right: -1px;
  bottom: calc(100% + 1px);
  height: 28px;
  background: var(--canvas);
  pointer-events: none;
}
.turn-user > * {
  grid-column: 1;
}
.turn-user .role {
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  font-weight: 600;
  color: var(--ink-muted);
  margin-bottom: 4px;
}
.turn-user .body {
  font-family: var(--font-message);
  font-size: var(--fs-ui-lg);
  font-weight: 500;
  line-height: 1.55;
  letter-spacing: -0.005em;
  color: var(--ink);
  max-width: var(--measure);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.turn-stamp {
  grid-column: 2;
  grid-row: 1;
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-caption);
  color: var(--ink-faint);
}
.user-attachments {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 8px;
}
.user-attachment {
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  color: var(--ink-muted);
  background: var(--fill-quiet);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-sm);
  padding: 3px 10px;
}
/* `STR-3`: a dropped photo should look like the photo. */
.user-attachment-thumb {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  width: 64px;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
}
.user-attachment-img {
  width: 64px;
  height: 64px;
  object-fit: cover;
  border-radius: var(--radius);
  border: 1px solid var(--hairline);
}
.user-attachment-name {
  font-family: var(--font-sans);
  font-size: var(--fs-caption);
  color: var(--ink-faint);
  max-width: 64px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---- agent run: execution log + final prose, one continuous unit ---- */
.agent-run {
  margin-bottom: 36px;
  animation: fade-in var(--dur-slow) var(--ease-out);
}

/* inline generated media (Phase 13, `STR-2`) */
.chat-media {
  margin: 6px 0 0;
}
.chat-media-el {
  display: block;
  max-width: 100%;
  max-height: 512px;
  width: auto;
  height: auto;
  border-radius: var(--radius-lg);
  border: 1px solid var(--hairline);
  object-fit: contain;
  animation: fade-in var(--dur-slow) var(--ease-out);
}
.chat-media-note {
  font-family: var(--font-sans);
  font-size: var(--fs-ui);
  color: var(--ink-muted);
  padding: 12px 0;
}
/* Holds the final aspect ratio while the image is fetched, so the transcript
   never reflows when it lands — the detail that separates "feels like
   ChatGPT" from "feels like a web form" (`STR-2`). */
.chat-media-skeleton {
  max-width: min(512px, 100%);
  border-radius: var(--radius-lg);
  border: 1px solid var(--paper-edge-2);
  background: var(--paper-edge);
  overflow: hidden;
  position: relative;
}
.chat-media-shimmer {
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, transparent 40%, var(--paper-edge-2) 50%, transparent 60%);
  background-size: 200% 100%;
  opacity: 0.06;
  animation: chat-media-shimmer 2s ease-in-out infinite;
}
@keyframes chat-media-shimmer {
  from {
    background-position: 150% 0;
  }
  to {
    background-position: -50% 0;
  }
}
@media (prefers-reduced-motion: reduce) {
  .chat-media-shimmer {
    animation: none;
    background: none;
  }
}
/* Only appears once the wait is long enough to notice (3s) — before that a
   running clock would draw attention to a delay nobody felt. Cancel joins it
   at 10s, when waiting has become a decision (`STR-2`). */
/* `STR-4`: a partial fills the tile it will eventually become, so the swap to
   the finished image is a sharpening rather than a replacement. */
.chat-media-partial {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
}
.chat-media-waiting {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
}
/* The orb holds the middle of the tile; the clock and Cancel that join it after
   a few seconds drop to the foot so the two never sit on top of each other. */
.chat-media-orb {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.chat-media-orb ~ .chat-media-waiting {
  justify-content: flex-end;
  padding-bottom: 12px;
}
.chat-media-elapsed {
  font-family: var(--font-sans);
  font-size: var(--fs-timeline);
  font-variant-numeric: tabular-nums;
  color: var(--ink-faint);
}
.chat-media-cancel {
  font-family: var(--font-sans);
  font-size: var(--fs-timeline);
  color: var(--ink-muted);
  background: transparent;
  border: none;
  padding: 2px 6px;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.chat-media-cancel:hover {
  color: var(--ink);
}
.chat-media-actions {
  display: flex;
  gap: 6px;
  margin-top: 6px;
  opacity: 0.55;
  transition: opacity var(--t-fast);
}
.chat-media:hover .chat-media-actions,
.chat-media-actions:focus-within {
  opacity: 1;
}
/* There is no hover on a touch screen, so the quiet state would be the only
   state — the actions would read as permanently disabled. Show them outright
   (`STR-2`). */
@media (hover: none) {
  .chat-media-actions {
    opacity: 1;
  }
}
.chat-media-action {
  font-family: var(--font-sans);
  font-size: var(--fs-timeline);
  color: var(--ink-muted);
  background: transparent;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-sm);
  padding: 3px 8px;
  cursor: pointer;
}
.chat-media-action:hover {
  color: var(--ink);
  background: var(--fill-hover);
}
.chat-media-action:disabled {
  opacity: 0.5;
  cursor: default;
}
.chat-media-meta {
  font-family: var(--font-sans);
  font-size: var(--fs-timeline);
  color: var(--ink-faint);
  margin-top: 4px;
}
.chat-media-error {
  font-family: var(--font-sans);
  font-size: var(--fs-timeline);
  color: var(--danger);
  margin-top: 4px;
}

/* Who answered, set like a Rail group label — small caps-tracked sans for the
   role, the model's id in the mono the Rail uses for readings. A label over
   the answer, not a chip competing with it. */
.run-header {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-sans);
  font-size: var(--fs-caption);
  color: var(--ink-faint);
  margin-bottom: 10px;
  padding-left: 1px;
}
.run-header .agent-label {
  font-size: var(--fs-footnote);
  font-weight: 600;
  color: var(--ink-muted);
}
.run-header .model-tag {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums;
  color: var(--ink-faint);
}

/* ---- thinking state: shown before the first token/step/block lands ---- */
.thinking {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 3px 0 6px;
}
.thinking-label {
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  color: var(--ink-faint);
}
/* A turn that ended silently says so, rather than leaving a blank bubble that
   can't be told apart from one still running. The same for one that ran its
   steps and then wrote no answer. */
.run-empty,
.run-no-answer {
  margin: 2px 0 4px;
  font-family: var(--font-sans);
  font-size: var(--fs-ui);
  line-height: 1.55;
  color: var(--ink-faint);
}
/* `HRN-UI-3`: the live meter. Deliberately the quietest text in the turn —
   it is reassurance that work has a budget, not something to read. */
.run-meter {
  margin: 2px 0 4px;
  font-family: var(--font-sans);
  font-size: var(--fs-timeline);
  font-variant-numeric: tabular-nums;
  color: var(--ink-faint);
}
/* The orb's slot is always 20px wide, orb or not, so the line does not slide
   sideways each time a step starts or ends. */
.run-meter-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 2px 0 4px;
}
.run-meter-row .run-meter {
  margin: 0;
  min-width: 0;
}
.run-meter-orb {
  flex: none;
  width: 20px;
  height: 20px;
}
/* The model's thinking, while it is thinking. Deliberately quieter than the
   meter above it: this is evidence the run is alive, not something to read. */
.run-thinking {
  margin: 0 0 6px;
  font-family: var(--font-sans);
  font-size: var(--fs-timeline);
  color: var(--ink-faint);
}
.run-thinking > summary {
  cursor: pointer;
  font-variant-numeric: tabular-nums;
  list-style: none;
}
.run-thinking > summary::before {
  content: "▸ ";
}
.run-thinking[open] > summary::before {
  content: "▾ ";
}
.run-thinking > pre {
  max-height: 220px;
  overflow: auto;
  margin: 6px 0 0;
  padding: 8px 10px;
  border-left: 2px solid var(--paper-edge-2);
  background: var(--paper-sunken, transparent);
  font-family: var(--font-mono);
  font-size: var(--fs-timeline);
  line-height: 1.5;
  white-space: pre-wrap;
  color: var(--ink-faint);
}

/* `HRN-3`: why a turn stopped short. Sits directly under the prose it
   qualifies, because its whole job is to change how that prose is read. */
.run-stopped {
  margin: 4px 0 4px;
  font-family: var(--font-sans);
  font-size: var(--fs-ui);
  line-height: 1.55;
  color: var(--ink-muted);
}
/* `HRN-UI-1`: a turn typed into a run that was already working. Same step as
   the "You" label it sits beside; weight and colour carry the hierarchy. */
.mid-run-mark {
  margin-left: 8px;
  font-weight: 400;
  font-size: var(--fs-timeline);
  letter-spacing: 0.02em;
  color: var(--ink-faint);
}
.mid-run-mark.pending {
  opacity: 0.7;
}

/* ---- inline artifact chips (CHT-6): open straight into the Canvas panel ---- */
.artifact-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 10px;
}

/* The way in, on an empty chat. Quiet enough not to compete with the composer,
   present enough that nobody has to find it in Settings. */
.folder-invite {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  margin-top: 14px;
  padding: 10px 13px;
  font-family: var(--font-sans);
  font-size: var(--fs-ui);
  text-align: left;
  color: var(--ink-muted);
  background: var(--fill-quiet);
  border: none;
  border-radius: var(--radius-lg);
  cursor: pointer;
}
.folder-invite:hover {
  background: var(--fill-hover);
  color: var(--ink);
}
.folder-invite:hover .folder-invite-arrow {
  transform: translateX(2px);
  color: var(--ink);
}
.folder-invite-icon {
  display: flex;
  flex-shrink: 0;
  color: var(--ink-faint);
}
.folder-invite-arrow {
  margin-left: auto;
  flex-shrink: 0;
  color: var(--ink-faint);
  transition: transform var(--dur) var(--ease-out), color var(--dur-fast) var(--ease-out);
}

/* Files this turn changed on disk. Deliberately quieter than an artifact chip —
   a record of what happened, with a way back out of it. */
.changed-files {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-top: 10px;
}
.changed-file {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  color: var(--ink-muted);
}
.changed-file.undone {
  opacity: 0.55;
}
.changed-file-name {
  font-family: var(--font-mono);
  font-size: var(--fs-footnote);
  color: var(--ink);
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
}
.changed-file-name:hover {
  text-decoration: underline;
}
.changed-file-undo {
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  color: var(--ink-muted);
  background: none;
  border: none;
  padding: 1px 6px;
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.changed-file-undo:hover {
  color: var(--ink);
  background: var(--fill-hover);
}
.changed-file-undone {
  font-size: var(--fs-footnote);
  color: var(--ink-faint);
}
.artifact-chip {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 6px 11px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  background: var(--paper);
  box-shadow: var(--shadow-xs);
  cursor: pointer;
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  color: var(--ink);
  transition:
    border-color var(--dur-fast) var(--ease-out),
    box-shadow var(--dur) var(--ease-out),
    transform var(--dur) var(--ease-out);
}
.artifact-chip:hover {
  border-color: var(--paper-edge-2);
  box-shadow: var(--shadow-sm);
  transform: translateY(-1px);
}
.artifact-chip:active {
  transform: translateY(0) scale(0.98);
  transition-duration: 60ms;
}
.artifact-chip-kind {
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-caption);
  font-weight: 500;
  color: var(--ink-faint);
}
.artifact-chip-title {
  font-weight: 500;
}
.artifact-chip-arrow {
  color: var(--ink-faint);
  transition: transform var(--dur) var(--ease-out);
}
.artifact-chip:hover .artifact-chip-arrow {
  transform: translateX(2px);
  color: var(--ink);
}

/* ---- timeline ----
   What the agent did, as one hairline-framed panel built from the Rail's
   parts: the same outline as the Rail's top buttons, the same turned chevron
   as a project's twisty, rows separated like chat rows, readings in the mono
   stamp. Open while the run is live, folded to its one-line head once it is
   done — the steps are how the answer was got, not the answer. */
.timeline {
  margin: 0 0 16px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  background: var(--paper);
  overflow: hidden;
}

.timeline-head {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-height: 34px;
  padding: 0 12px 0 10px;
  background: none;
  border: none;
  font-family: var(--font-sans);
  font-size: var(--fs-timeline);
  text-align: left;
  color: var(--ink-muted);
  cursor: pointer;
}
.timeline-head:hover {
  color: var(--ink);
  background: var(--fill-quiet);
}
.timeline.open .timeline-head {
  border-bottom: 1px solid var(--paper-edge);
}
.timeline-twisty {
  flex: none;
  display: inline-flex;
  color: var(--ink-faint);
}
.timeline-twisty svg {
  transition: transform var(--dur) var(--ease-out);
}
.timeline.open .timeline-rows {
  animation: fade-in var(--dur) var(--ease-out);
}
.timeline.open .timeline-twisty svg {
  transform: rotate(90deg);
}
.timeline-head:hover .timeline-twisty {
  color: var(--ink);
}
.timeline-title {
  flex: none;
  font-weight: 500;
  color: var(--ink);
}
.timeline.live .timeline-title {
  animation: step-running 1.6s ease-in-out infinite;
}
.timeline-summary {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-footnote);
  color: var(--ink-faint);
}
.timeline-failed {
  flex: none;
  margin-left: auto;
  font-size: var(--fs-footnote);
  color: var(--danger);
}

.timeline-rows {
  display: flex;
  flex-direction: column;
  max-height: 420px;
  overflow-y: auto;
}
.timeline-rows > .step + .step,
.timeline-rows > .step + .step-band,
.timeline-rows > .step-band + .step,
.timeline-rows > .step-band + .step-band {
  border-top: 1px solid var(--paper-edge);
}
.timeline-earlier {
  padding: 6px 12px;
  background: none;
  border: none;
  border-bottom: 1px solid var(--paper-edge);
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  text-align: left;
  color: var(--ink-faint);
  cursor: pointer;
}
.timeline-earlier:hover {
  color: var(--ink);
  background: var(--fill-hover);
}

/* One step is one grid row: dot · verb · target · readings. Nothing wraps —
   the target and the stamp clip, and anything longer opens *under* the row,
   full width. The old wrapping flex row let the readings fall onto a line of
   their own, pinned right, which is what broke the panel apart. */
.step {
  display: flex;
  flex-direction: column;
  font-family: var(--font-mono);
  font-size: var(--fs-timeline);
  line-height: 1.5;
}
.step-head {
  display: grid;
  grid-template-columns: 6px auto minmax(0, 1fr) auto;
  align-items: center;
  column-gap: 10px;
  min-height: 32px;
  padding: 0 10px 0 12px;
}
.step.expandable > .step-head {
  cursor: pointer;
}
.step.expandable > .step-head:hover {
  background: var(--fill-quiet);
}
.step-meta {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

/* Status as a dot — the Rail's own mark for "something is running here".
   Hollow when done, filled and breathing while live, danger when it failed. */
.step-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  border: 1px solid var(--ink-faint);
}
.step.running .step-dot {
  border-color: var(--ink);
  background: var(--ink);
}
.step.error .step-dot {
  border-color: var(--danger);
  background: var(--danger);
}
@media (prefers-reduced-motion: no-preference) {
  .step.running .step-dot {
    animation: mark-breathe 2.4s ease-in-out infinite;
  }
}

/* RPC-1: a step a script asked for, sitting under the step that ran the
   script. Indented and hung off a hairline, so a loop over forty items reads
   as one thing the agent did rather than forty. */
.step.nested > .step-head {
  margin-left: 15px;
  padding-left: 12px;
  border-left: 1px solid var(--paper-edge-2);
}
.step.nested .verb,
.step.nested .target {
  color: var(--ink-muted);
}

.step .verb {
  font-family: var(--font-sans);
  color: var(--ink-muted);
  white-space: nowrap;
}
.step.running .verb {
  color: var(--ink);
  animation: step-running 1.2s ease-in-out infinite;
}
.step.error .verb {
  color: var(--danger);
}
@keyframes step-running {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.45;
  }
}
.step .target {
  color: var(--ink);
  /* One line per step, however long the query or path — the full text is
     still there in the title tooltip, and in `aria-label` for anyone who
     can't hover. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.step .result {
  font-family: var(--font-sans);
  max-width: 22ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--fs-caption);
  font-variant-numeric: tabular-nums;
  color: var(--ink-faint);
}

/* The row's own twisty — the same chevron, turned the same way. */
.step-expand {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border: none;
  background: none;
  padding: 0;
  border-radius: var(--radius-sm);
  cursor: pointer;
  color: var(--ink-faint);
}
.step-expand svg {
  transition: transform var(--dur) var(--ease-out);
}
.step.open .step-expand svg {
  transform: rotate(90deg);
}
.step-expand:hover {
  color: var(--ink);
}

/* Everything a step opens, set under its row at the row's text edge. */
.step > :is(.recall-matches, .code-disclosure, .task-disclosure, .untrusted-disclosure, .kept-disclosure) {
  margin: 0 12px 10px 28px;
}
.step > .task-live-line {
  padding: 0 12px 8px 28px;
}

/* ---- recall provenance (RCL-UI) ---- */
.recall-matches {
  flex-basis: 100%;
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin: 6px 0 2px;
  padding-left: 2px;
  border-left: 1px solid var(--paper-edge-2);
}
.recall-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 3px 0 3px 10px;
  border: none;
  background: none;
  text-align: left;
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-footnote);
  color: var(--ink-muted);
}
.recall-row.link {
  cursor: pointer;
}
.recall-row.link:hover .recall-title {
  text-decoration: underline;
}
.recall-chip {
  flex: none;
  color: var(--ink-faint);
  letter-spacing: 0.03em;
}
.recall-title {
  flex: none;
  color: var(--ink);
}
.recall-date {
  flex: none;
  color: var(--ink-faint);
}
.recall-snippet {
  color: var(--ink-faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---- code disclosure (DAT-UI-1) ---- */
.code-disclosure {
  flex-basis: 100%;
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin: 6px 0 2px;
  padding-left: 10px;
  border-left: 1px solid var(--paper-edge-2);
}
.code-disclosure-lang {
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-caption);
  color: var(--ink-faint);
}
.code-disclosure-source {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-footnote);
  line-height: 1.5;
  color: var(--ink-muted);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  max-height: 260px;
  overflow-y: auto;
}

/* ---- project task step (COD-UI-2) ---- */
/* The newest line a running build printed, under its step, so four minutes of
   compiling reads as work rather than as a hang. One line, never a log. */
.task-live-line {
  flex-basis: 100%;
  padding-left: 10px;
  font-size: var(--fs-footnote);
  color: var(--ink-faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.task-disclosure {
  flex-basis: 100%;
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 6px 0 2px;
  padding-left: 10px;
  border-left: 1px solid var(--paper-edge-2);
  font-family: var(--font-sans);
}
.task-command {
  font-family: var(--font-mono);
  font-size: var(--fs-caption);
  color: var(--ink-faint);
  overflow-wrap: anywhere;
}
.task-tail {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-caption);
  line-height: 1.5;
  color: var(--ink-muted);
  white-space: pre;
  overflow: auto;
  max-height: 220px;
}

/* ---- untrusted-content marker (TRU-UI-1) — provenance, not an alarm: ink
   tones only, no red, no triangle ---- */
.untrusted-chip {
  flex: none;
  border: 1px solid var(--paper-edge-2);
  border-radius: var(--radius-sm);
  background: none;
  padding: 1px 8px;
  cursor: pointer;
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-caption);
  letter-spacing: 0.02em;
  color: var(--ink-faint);
}
.untrusted-chip:hover {
  color: var(--ink-muted);
  border-color: var(--ink-faint);
}
.untrusted-chip.risk-2,
.untrusted-chip.risk-3 {
  color: var(--ink-muted);
  border-color: var(--ink-muted);
}
.untrusted-disclosure {
  flex-basis: 100%;
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 6px 0 2px;
  padding-left: 10px;
  border-left: 1px solid var(--paper-edge-2);
}
.untrusted-source-label {
  display: block;
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-caption);
  color: var(--ink-faint);
  margin-bottom: 2px;
}
.untrusted-source-text {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-footnote);
  line-height: 1.5;
  color: var(--ink-muted);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  max-height: 220px;
  overflow-y: auto;
}

/* ---- run prose ----
   Set in the working grotesque, not a book face: the answer is read the way
   the rest of the instrument is read — clean, even, a little tighter than a
   novel — at a size and leading tuned for long answers. The serif is kept for
   what it does best here: the headings inside an answer, so a long reply has
   an editorial spine you can scan down. */
.run-text {
  font-family: var(--font-message);
  font-size: calc(var(--fs-answer) * var(--reading-scale));
  line-height: var(--lh-answer);
  letter-spacing: -0.003em;
  color: var(--ink);
  overflow-wrap: break-word;
}
/* Inside the stream the whole stream is already zoomed (`.message-stream`
   below) — multiplying here as well would scale the prose twice. */
.message-stream .run-text {
  font-size: var(--fs-answer);
}
.run-text p {
  margin: 0 0 0.9em;
}
.run-text > :last-child {
  margin-bottom: 0;
}
.run-text > :first-child {
  margin-top: 0;
}
.run-text :is(h1, h2, h3, h4) {
  font-family: var(--font-serif);
  font-weight: 500;
  line-height: 1.3;
  letter-spacing: -0.005em;
  color: var(--ink);
  margin: 1.5em 0 0.5em;
}
.run-text h1 {
  font-size: 1.45em;
}
.run-text h2 {
  font-size: 1.28em;
}
.run-text h3 {
  font-size: 1.12em;
}
.run-text h4 {
  font-family: var(--font-sans);
  font-size: 0.78em;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-muted);
}
.run-text :is(ul, ol) {
  margin: 0 0 0.9em;
  padding-left: 1.3em;
}
.run-text li {
  margin: 0.25em 0;
  padding-left: 0.2em;
}
.run-text li::marker {
  color: var(--ink-faint);
}
.run-text ol > li::marker {
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums;
  font-size: 0.85em;
}
.run-text strong {
  font-weight: 600;
}
.run-text a {
  color: var(--ink);
  text-decoration: underline;
  text-decoration-color: var(--ink-faint);
  text-underline-offset: 2px;
}
.run-text a:hover {
  text-decoration-color: var(--ink);
}
.run-text blockquote {
  margin: 0 0 0.9em;
  padding: 0 0 0 14px;
  border-left: 2px solid var(--paper-edge-2);
  color: var(--ink-muted);
}
.run-text hr {
  border: none;
  border-top: 1px solid var(--paper-edge-2);
  margin: 1.6em 0;
}
.run-text table {
  display: block;
  max-width: 100%;
  overflow-x: auto;
  border-collapse: collapse;
  margin: 0 0 1em;
  font-size: 0.88em;
  line-height: 1.5;
}
.run-text :is(th, td) {
  padding: 6px 12px 6px 0;
  border-bottom: 1px solid var(--paper-edge);
  text-align: left;
  vertical-align: top;
}
.run-text th {
  font-family: var(--font-sans);
  font-size: 0.85em;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-muted);
  border-bottom-color: var(--paper-edge-2);
}
.run-text code {
  font-family: var(--font-mono);
  font-size: 0.84em;
  background: var(--fill-press);
  padding: 0.12em 0.4em;
  border-radius: var(--radius-xs);
}
.run-text pre {
  font-family: var(--font-mono);
  font-size: var(--fs-ui);
  line-height: 1.55;
  background: var(--fill-quiet);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  padding: 14px 16px;
  overflow-x: auto;
  margin: 1em 0;
}
.run-text pre code {
  background: none;
  padding: 0;
  font-size: 1em;
}
.run-text.streaming p:last-child::after {
  content: "";
  display: inline-block;
  width: 2px;
  height: 1.05em;
  margin-left: 3px;
  border-radius: 1px;
  background: var(--ink);
  vertical-align: text-bottom;
  animation: blink 1.1s var(--ease-in-out) infinite;
}
@keyframes blink {
  50% {
    opacity: 0.15;
  }
}

/* Compaction boundary (CTX-UI-2). Every message above it is still rendered —
   this only marks where the model's verbatim view gives way to the summary. */
.compact-divider-wrap {
  margin: 20px 0;
}
.compact-divider {
  display: block;
  width: 100%;
  padding: 4px 0;
  border: none;
  background: none;
  cursor: pointer;
  font-size: var(--fs-footnote);
  letter-spacing: 0.04em;
  color: var(--ink-faint);
  text-align: center;
}
.compact-divider:hover {
  color: var(--ink-muted);
}
/* CTX-5: what a compaction actually replaced, and the passes before it. */
.compact-detail {
  margin-top: 8px;
}
.compact-note {
  margin: 0 0 8px;
  font-size: var(--fs-footnote);
  line-height: 1.6;
  color: var(--ink-faint);
}
.compact-history {
  margin-top: 10px;
}
.compact-history > summary {
  font-size: var(--fs-footnote);
  color: var(--ink-faint);
  cursor: pointer;
}
.compact-history > summary:hover {
  color: var(--ink-muted);
}
.compact-past {
  margin-top: 10px;
}
.compact-when {
  display: block;
  font-size: var(--fs-footnote);
  letter-spacing: 0.02em;
  color: var(--ink-faint);
}
.compact-summary {
  margin: 8px 0 0;
  padding: 12px 14px;
  border-left: 2px solid var(--ink-faint);
  font-family: var(--font-mono);
  font-size: var(--fs-footnote);
  line-height: 1.6;
  color: var(--ink-muted);
  white-space: pre-wrap;
  overflow-x: auto;
}

/* ---- inline change proposal (SOUL-UI-2) ----
   Permission-panel register: bordered, calm, no colour alarm. */
.proposal-card {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 10px;
  padding: 12px 16px;
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xs);
  animation: rise-in var(--dur-slow) var(--ease-out);
}
.proposal-text {
  flex: 1;
  min-width: 200px;
  margin: 0;
  font-size: var(--fs-ui);
  line-height: 1.55;
  color: var(--ink-muted);
}
/* SKL-UI-2: provenance about a skill's contents, in the same ink tone as the
   rest of the card. No red, no warning triangle — per Part I §4.2 this is
   information, not an alarm, and it never blocks the install. */
.proposal-risk {
  width: 100%;
  margin: 6px 0 0;
  font-size: var(--fs-footnote);
  line-height: 1.5;
  color: var(--ink-faint, var(--ink-muted));
}
.proposal-actions {
  display: flex;
  gap: 12px;
}

/* Email / skill variants (MAIL-UI-2, SKL-UI-2): the same card, a body that
   shows what will actually go out instead of only a sentence about it. */
.proposal-card.wide {
  flex-direction: column;
  align-items: stretch;
}
.proposal-fields {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 4px 10px;
  font-size: var(--fs-ui);
  margin: 2px 0 8px;
}
.proposal-field-label {
  color: var(--ink-faint);
}
.proposal-field-value {
  color: var(--ink);
  word-break: break-word;
}
.proposal-body-preview {
  white-space: pre-wrap;
  font-size: var(--fs-ui);
  line-height: 1.55;
  color: var(--ink-muted);
  margin: 0 0 8px;
  max-height: 220px;
  overflow-y: auto;
}
.proposal-edit-body {
  width: 100%;
  min-height: 120px;
  resize: vertical;
  font: inherit;
  font-size: var(--fs-ui);
  line-height: 1.5;
  padding: 8px;
  border: 1px solid var(--paper-edge-2);
  border-radius: var(--radius);
  background: var(--paper);
  color: var(--ink);
  margin-bottom: 8px;
}

/* `SUB-UI-1`: the agents a turn handed work to, inside the turn itself. Sits
   between the timeline and the prose — what happened, then who did it, then
   what came of it. */
.fleet-card {
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  background: var(--paper);
  margin: 8px 0 12px;
  overflow: hidden;
}
.fleet-header {
  font-size: var(--fs-timeline);
  color: var(--ink-faint);
  padding: 6px 10px;
  border-bottom: 1px solid var(--paper-edge);
}
.fleet-row {
  display: grid;
  grid-template-columns: auto minmax(0, max-content) minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 8px;
  padding: 7px 10px;
  font-size: var(--fs-ui);
}
.fleet-row + .fleet-row {
  border-top: 1px solid var(--paper-edge);
}
/* One 20px cell for either mark, so a row that is running (orb) and one that
   has ended (dot) keep their text on the same line. */
.fleet-lead {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
}
.fleet-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
}
.fleet-agent {
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 12ch;
}
.fleet-doing {
  color: var(--ink-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.fleet-clock {
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-timeline);
}
.fleet-actions {
  display: flex;
  gap: 6px;
}
.fleet-action {
  background: none;
  border: none;
  padding: 2px 4px;
  cursor: pointer;
  color: var(--ink-faint);
  font: inherit;
  font-size: var(--fs-timeline);
  border-radius: var(--radius);
}
.fleet-action:hover {
  color: var(--ink);
  background: var(--fill-hover);
}
.fleet-steer {
  grid-column: 1 / -1;
}
.fleet-steer input {
  width: 100%;
  font: inherit;
  font-size: var(--fs-ui);
  padding: 5px 8px;
  border: 1px solid var(--paper-edge-2);
  border-radius: var(--radius);
  background: var(--canvas);
  color: var(--ink);
}
.fleet-note,
.fleet-report {
  grid-column: 1 / -1;
  margin: 4px 0 0;
  font-size: var(--fs-timeline);
  color: var(--ink-muted);
}
.fleet-report {
  white-space: pre-wrap;
  font-family: inherit;
  max-height: 220px;
  overflow-y: auto;
  padding: 6px 8px;
  background: var(--canvas);
  border-radius: var(--radius);
}

/* ---- `HRN-UI-2`: steps that ran at the same time ----
   Drawn as one band with a single header, because "3 things at once" is a
   different fact from three things one after another, and a plain stack shows
   the second when the first is what happened. */
.step-band {
  display: flex;
  flex-direction: column;
  box-shadow: inset 2px 0 0 var(--paper-edge-2);
}
.step-band.running {
  box-shadow: inset 2px 0 0 var(--ink);
}
.band-header {
  padding: 7px 12px 2px 30px;
  font-family: var(--font-sans);
  font-size: var(--fs-caption);
  font-weight: 600;
  color: var(--ink-faint);
}
/* Every dot in a live band pulses together: the whole band is the unit. */
.step-band.running .step.running .verb {
  animation: step-running 1.2s ease-in-out infinite;
}

/* ---- `HRN-UI-4`: a result kept on disk ---- */
.kept-disclosure {
  flex-basis: 100%;
  margin-top: 4px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  overflow: hidden;
}
.kept-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 6px 9px;
  background: var(--paper-edge);
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  color: var(--ink-muted);
}
.kept-ref {
  font-family: var(--font-mono);
  color: var(--ink);
}
.kept-size {
  flex: 1;
}
.kept-save {
  border: none;
  background: none;
  padding: 0;
  cursor: pointer;
  color: var(--local);
  font: inherit;
}
.kept-save:hover {
  text-decoration: underline;
}
.kept-saved {
  color: var(--ok);
}
.kept-text {
  margin: 0;
  padding: 8px 9px;
  max-height: 320px;
  overflow: auto;
  font-family: var(--font-mono);
  font-size: var(--fs-timeline);
  line-height: 1.5;
  white-space: pre-wrap;
  word-break: break-word;
  color: var(--ink-muted);
}

/* `OBS-3`: the meter turns when the window is nearly full, which is the one
   moment the number is worth looking at. */
.run-meter.tight {
  color: var(--ink-muted);
}

/* `HRN-UI-5`: what you can do with an answer once it exists. Quiet by design —
   these sit under every finished turn, so they have to be findable without
   competing with the answer they follow. */
.turn-actions {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 14px;
  margin-top: 6px;
  opacity: 0;
  transition: opacity var(--dur) var(--ease-out);
}
.agent-run:hover .turn-actions,
.turn-actions:focus-within {
  opacity: 1;
}
.turn-time {
  font-family: var(--font-sans);
  font-size: var(--fs-caption);
  color: var(--ink-faint);
  white-space: nowrap;
}

/* ---- the plan (`PLN-UI-1`) ----
   Above the timeline, in the same column, sharing its ground: the plan and the
   steps that carry it out are one story read top to bottom, and putting them
   on two surfaces would make you hold one in your head while looking at the
   other. Quieter than the answer, louder than a step — it is the frame the
   steps below it are read inside. */
.plan-card {
  margin: 0 0 8px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  background: var(--paper);
}
/* The same head as the timeline's, so the two panels read as one story told
   at two altitudes. */
.plan-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  min-height: 32px;
  padding: 0 12px;
  border-bottom: 1px solid var(--paper-edge);
  font-family: var(--font-sans);
  font-size: var(--fs-timeline);
}
.plan-title {
  font-weight: 500;
  color: var(--ink);
}
.plan-progress {
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-footnote);
  color: var(--ink-faint);
}
/* `PLN-UI-3`: the mark that says this list is not the first one. */
.plan-revised {
  color: var(--ink-faint);
  font-style: italic;
}
.plan-items {
  list-style: none;
  margin: 0;
  padding: 9px 12px 10px;
  display: flex;
  flex-direction: column;
  gap: 5px;
}
/* Two columns, not a wrapping row. A row would let a long item flow back under
   its own checkbox, which is what turned this list into a wall — the boxes have
   to hold a column of their own for the list to read as a list. */
.plan-item {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0 9px;
  align-items: start;
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-timeline);
  line-height: 1.5;
  color: var(--ink);
}
.plan-mark {
  color: var(--ink-faint);
  /* The glyphs differ in width between fonts; a fixed box keeps the text edge
     straight whatever renders. */
  width: 1.1em;
  text-align: center;
  /* Optical: the box sits a shade high against mono text at this size. */
  transform: translateY(0.05em);
}
.plan-item.done .plan-mark,
.plan-item.doing .plan-mark {
  color: var(--ink-muted);
}
.plan-item.doing .plan-mark {
  color: var(--ink);
}
/* The item being worked on is the one thing on this card worth finding at a
   glance, so it is the only one carrying weight. */
.plan-item.doing .plan-text {
  color: var(--ink);
  font-weight: 600;
}
.plan-item.todo .plan-text {
  color: var(--ink-muted);
}
.plan-item.done .plan-text {
  color: var(--ink-faint);
}
/* A dropped item stays legible: struck through, not hidden. Losing it would
   turn the plan into a record of whatever happened to succeed. */
.plan-item.dropped .plan-text {
  color: var(--ink-faint);
  text-decoration: line-through;
  text-decoration-thickness: 1px;
}
.plan-why,
.plan-added {
  font-family: var(--font-sans);
  color: var(--ink-faint);
}
.plan-added {
  font-style: italic;
}
.plan-history {
  margin: -2px 0 0;
  padding: 0 12px 10px;
  font-family: var(--font-sans);
  font-size: var(--fs-timeline);
  color: var(--ink-faint);
}
.plan-history > summary {
  cursor: pointer;
  list-style: none;
}
.plan-history > summary::before {
  content: "▸ ";
}
.plan-history[open] > summary::before {
  content: "▾ ";
}
.plan-past {
  margin: 6px 0 0;
  padding-left: 20px;
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums;
  line-height: 1.5;
  color: var(--ink-faint);
}

/* `REG-4`: a command that changed something, as one muted line among the turns. */
.command-note {
  margin: 14px 0;
  text-align: center;
  font-size: var(--fs-footnote);
  letter-spacing: 0.02em;
  color: var(--ink-faint);
}
.command-note-meta {
  color: var(--ink-faint);
  opacity: 0.8;
}
.command-note.failed {
  text-decoration: line-through;
}

/* `SKC-2`: a skill command's bubble — `/name` and what it was asked, with the
   instructions it loaded one click away. */
.command-chip {
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  color: var(--ink-muted);
}
.command-chip summary {
  cursor: pointer;
  list-style: none;
}
.command-chip summary::-webkit-details-marker {
  display: none;
}
.command-chip-name {
  font-family: var(--font-mono);
  color: var(--ink);
}
.command-chip-args {
  color: var(--ink);
}
.command-chip-note {
  margin: 6px 0 0;
  color: var(--ink-faint);
  line-height: 1.5;
}

/* `SKC-4`: the instructions `/init` proposes, shown whole before they are accepted. */
.proposal-preview {
  margin: 8px 0;
  padding: 8px 10px;
  max-height: 220px;
  overflow: auto;
  white-space: pre-wrap;
  font-family: var(--font-mono);
  font-size: var(--fs-footnote);
  line-height: 1.5;
  color: var(--ink-muted);
  background: var(--paper-edge);
  border-radius: var(--radius-sm);
}

/* `PLF-4`: the approval row under a plan I wrote while planning first. */
.plan-approval {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid var(--hairline);
  font-size: var(--fs-ui);
}
.plan-approval-text {
  flex: 1;
  min-width: 180px;
  color: var(--ink-muted);
}


/* ---- components/Context/Context.css ---- */
/* PRES-5-style ambient line, sitting just below the composer box — a
   non-crucial detail, not another control competing in the input row. */
.context-chip-wrap {
  position: relative;
  display: flex;
  justify-content: flex-start;
  min-width: 0;
  padding: 4px 4px 0;
}

.context-chip {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  background: none;
  border: none;
  border-radius: var(--radius-sm);
  padding: 3px 6px;
  font-family: var(--font-sans);
  font-size: var(--fs-caption);
  color: var(--ink-faint);
  cursor: pointer;
}
.context-chip:hover {
  color: var(--ink);
  background: var(--fill-hover);
}

/* Same chip convention as the model/reasoning pickers (`.model-picker.compact
   .model-picker-trigger`): a transparent border at rest, drawn in on hover, so
   these read as the same family of quiet controls instead of plain text
   links. */
.why-link {
  background: none;
  border: 1px solid transparent;
  border-radius: var(--radius);
  padding: 2px 6px;
  margin-left: -6px;
  font-family: var(--font-sans);
  font-size: var(--fs-caption);
  color: var(--ink-faint);
  cursor: pointer;
}
.why-link:hover {
  border-color: var(--paper-edge-2);
  color: var(--ink);
}

.context-panel-backdrop {
  position: fixed;
  inset: 0;
  z-index: 49;
  background: color-mix(in srgb, var(--ink) 18%, transparent);
  backdrop-filter: blur(3px);
  animation: fade-in var(--dur) var(--ease-out);
}

.context-panel {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 50;
  width: 480px;
  max-width: 90vw;
  max-height: 76vh;
  overflow-y: auto;
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-xl);
  padding: 16px 18px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  box-shadow: var(--shadow-modal);
  animation: context-in var(--dur) var(--ease-out);
}
/* Centred with a transform, so the arrival has to carry that transform. */
@keyframes context-in {
  from {
    opacity: 0;
    transform: translate(-50%, -50%) scale(0.96);
  }
}

.context-panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-family: var(--font-sans);
  font-size: var(--fs-ui);
  font-weight: 600;
  color: var(--ink);
}

.context-layer {
  border: 1px solid var(--paper-edge-2);
  border-radius: var(--radius);
  padding: 6px 10px;
}
.context-layer summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  font-weight: 600;
  color: var(--ink-muted);
  cursor: pointer;
}
.context-layer[open] summary {
  color: var(--ink);
  margin-bottom: 6px;
}
.context-layer-label {
  flex: 1;
}
.context-layer-badge {
  font-weight: 500;
  font-size: var(--fs-caption);
  color: var(--ink-faint);
  white-space: nowrap;
}
.context-layer-sources {
  margin: 0 0 6px;
  font-family: var(--font-sans);
  font-size: var(--fs-caption);
  color: var(--ink-faint);
}
.context-layer-text {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-footnote);
  line-height: 1.5;
  color: var(--ink-muted);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.context-layer-empty {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  font-style: italic;
  color: var(--ink-faint);
}
.context-layer-edit {
  margin-top: 6px;
  background: none;
  border: none;
  padding: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-caption);
  color: var(--ink-faint);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.context-layer-edit:hover {
  color: var(--ink-muted);
}


/* ---- components/Memory/Memory.css ---- */
/* The durable self, made visible (MEM-UI). Same ink tokens as everything else —
   memory should feel like part of the room, not an alert. */

.memory-panel {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.memory-head {
  display: flex;
  align-items: center;
  gap: 10px;
}
.memory-count {
  flex: 1;
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-ui);
  color: var(--ink-muted);
}
.memory-search {
  margin-bottom: 2px;
}
.memory-error {
  margin: 0;
  font-size: var(--fs-ui);
  color: var(--danger);
}

/* ---- the untitled synthesis (PRO-UI, SMP-5) ---- */
.memory-about-you {
  padding: 2px 0 10px;
  border-bottom: 1px solid var(--paper-edge-2);
}
.memory-about-you.busy .memory-about-you-text {
  opacity: 0.5;
}
.memory-about-you-text {
  margin: 0 0 4px;
  font-size: var(--fs-ui);
  line-height: 1.6;
  color: var(--ink);
}
.memory-about-you-empty {
  color: var(--ink-faint);
}
.memory-about-you-meta {
  margin: 0 0 6px;
  font-size: var(--fs-footnote);
  color: var(--ink-faint);
}
.memory-about-you-actions {
  display: flex;
  gap: 12px;
}

/* ---- fact cards ---- */
.memory-card {
  padding: 12px 14px;
  border: 1px solid var(--paper-edge-2);
  border-radius: var(--radius-lg);
}
.memory-card-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-footnote);
}
.memory-name {
  color: var(--ink);
}
.memory-kind {
  padding: 1px 6px;
  border: 1px solid var(--paper-edge-2);
  border-radius: var(--radius);
  color: var(--ink-faint);
  font-size: var(--fs-caption);
}
.memory-created {
  margin-left: auto;
  color: var(--ink-faint);
}
.memory-desc {
  margin: 6px 0 2px;
  font-size: var(--fs-ui);
  color: var(--ink-muted);
}
.memory-last-used {
  margin: 0 0 2px;
  font-size: var(--fs-caption);
  color: var(--ink-faint);
}

/* ---- scope (SCP-UI-1 / SMP-6a) ---- */
.memory-scope-plain {
  margin: 0 0 2px;
  font-size: var(--fs-caption);
  color: var(--ink-faint);
}
/* Drawn by the shared segmented control (global.css); a small one here. */
.memory-scope {
  margin: 2px 0 4px;
  min-height: var(--control-sm);
}
.memory-scope-segment {
  font-size: var(--fs-caption);
  padding: 0 9px;
  min-height: calc(var(--control-sm) - 4px);
}
.memory-body {
  margin: 6px 0 8px;
  font-size: var(--fs-ui);
  line-height: 1.6;
  color: var(--ink);
  white-space: pre-wrap;
}
.memory-card-actions {
  display: flex;
  gap: 12px;
}

/* ---- consolidation proposal ---- */
.memory-proposal {
  padding: 12px 14px;
  border: 1px solid var(--paper-edge-2);
  border-radius: var(--radius-lg);
}
.memory-proposal-head {
  margin: 0 0 8px;
  font-size: var(--fs-ui);
  color: var(--ink);
}
.memory-proposal-row {
  display: flex;
  gap: 10px;
  padding: 6px 0;
  border-top: 1px solid var(--paper-edge-2);
  font-size: var(--fs-footnote);
}
.memory-proposal-kind {
  flex: none;
  width: 46px;
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums;
  color: var(--ink-faint);
}
.memory-proposal-body {
  flex: 1;
  color: var(--ink-muted);
}
.memory-proposal-body pre {
  margin: 4px 0 0;
  font-family: var(--font-mono);
  font-size: var(--fs-footnote);
  color: var(--ink-faint);
  white-space: pre-wrap;
}

.memory-undo {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 12px;
  border: 1px solid var(--paper-edge-2);
  border-radius: var(--radius);
  font-size: var(--fs-ui);
  color: var(--ink-muted);
}

/* ---- live write toast (MEM-UI-3) ---- */
.memory-toast {
  position: fixed;
  left: 50%;
  bottom: 96px;
  transform: translateX(-50%);
  z-index: 40;
  max-width: min(520px, calc(100vw - 32px));
  padding: 10px 14px;
  background: var(--paper);
  border: 1px solid var(--paper-edge-2);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-float);
}
.memory-toast-line {
  display: flex;
  align-items: baseline;
  gap: 10px;
}
.memory-toast-mark {
  color: var(--ink-faint);
}
.memory-toast-text {
  flex: 1;
  font-size: var(--fs-ui);
  color: var(--ink);
}
/* PRO-UI-5: the rebuild toast opens the Self view on click. */
.memory-toast-clickable {
  cursor: pointer;
}
.memory-toast-clickable:hover {
  text-decoration: underline;
}
.memory-toast-undo {
  border: none;
  background: none;
  padding: 0;
  cursor: pointer;
  font-size: var(--fs-ui);
  color: var(--ink-muted);
  text-decoration: underline;
}
.memory-toast-undo:hover {
  color: var(--ink);
}
.memory-toast-explain {
  margin: 6px 0 0;
  font-size: var(--fs-footnote);
  line-height: 1.5;
  color: var(--ink-faint);
}

/* ---- recall first-need prompt (SMP-2) ---- */
.memory-toast-actions {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-top: 8px;
}
/* Stacks above the write toast — a memory write is one of this prompt's own
   triggers, so both can legitimately be on screen at once. */
.recall-offer {
  bottom: 160px;
}


/* ---- components/Composer/Composer.css ---- */
/* A fixed, self-contained element that floats above the message stream
   (rather than a full-width toolbar fused to the bottom edge) — closer to
   the composer in claude.ai. */
.composer-wrap {
  /* Placement in the app shell grid (row 3 of the reading column), which Chat
     used to declare by redeclaring this class in its own stylesheet — two base
     rules for one class, resolved by bundle order. It belongs to the composer.
     Workspace nests the composer inside `.ws-foot`, which is not a grid
     container, so these two are simply inert there. */
  grid-column: 2;
  grid-row: 3;
  position: sticky;
  bottom: 0;
  /* A sticky box is always its own stacking context, so the pickers' z-index
     only counts *inside* the composer — the composer itself has to sit above
     the stream, or its drop-ups open underneath the pinned user turn
     (`.turn-user`, z 2). Above the session menu (15), below every panel and
     overlay (35+). */
  z-index: 16;
  /* Top pad adds to the stream's own bottom padding so the gap survives even
     if the last turn ends flush; bottom stays tight to keep the box low. */
  padding: 10px 24px 14px;
}
.composer-wrap.drag-over .composer {
  border-color: var(--local);
}
.composer-wrap.drag-over::after {
  content: "Drop image to attach";
  position: absolute;
  inset: 8px 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-sans);
  font-size: var(--fs-ui);
  color: var(--local);
  background: color-mix(in srgb, var(--paper) 88%, var(--local));
  border: 1.5px dashed color-mix(in srgb, var(--local) 60%, transparent);
  border-radius: var(--radius-xl);
  pointer-events: none;
  animation: fade-in var(--dur-fast) var(--ease-out);
}

.composer-col {
  max-width: 700px;
  margin: 0 auto;
}
/* Keeps the composer's right edge on the stream's when the narrow-column
   rule in Chat.css gives the session menu its own lane (same threshold: this
   box is the chat column minus its 24px side padding). */
.composer-wrap {
  container-type: inline-size;
}
@container (max-width: 752px) {
  .composer-col {
    margin-right: 32px;
  }
}

.composer {
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-sm);
  padding: 8px 8px 8px 10px;
  display: flex;
  align-items: center;
  gap: 10px;
  transition:
    border-color var(--dur) var(--ease-out),
    box-shadow var(--dur) var(--ease-out);
}
/* Focus lifts the box a little further off the page rather than drawing a
   heavier line around it. */
.composer:focus-within {
  border-color: var(--paper-edge-2);
  box-shadow: var(--shadow-float);
}

.attachment-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding-bottom: 10px;
}
.attachment-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  color: var(--ink-muted);
  background: var(--fill-quiet);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  padding: 4px 6px 4px 10px;
  animation: pop-in var(--dur) var(--ease-out);
}
.attachment-kind {
  color: var(--ink-faint);
}
.attachment-remove {
  background: none;
  border: none;
  color: var(--ink-faint);
  cursor: pointer;
  font-size: var(--fs-callout);
  line-height: 1;
  padding: 0 2px;
}
.attachment-remove:hover {
  color: var(--ink);
}

/* `PIK-2`: the declared route's target bar — what sending does now. */
.media-target-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 28px;
  padding: 0 10px;
  margin-bottom: 6px;
  background: var(--paper-edge);
  border-radius: var(--radius);
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  color: var(--ink-muted);
}
.media-target-glyph {
  color: var(--cloud);
}
.media-target-label {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.media-target-ratio {
  font-family: var(--font-sans);
  font-size: var(--fs-timeline);
  color: var(--ink-muted);
  background: var(--paper);
  border: 1px solid var(--paper-edge-2);
  border-radius: var(--radius);
  padding: 2px 4px;
}
/* `PIK-4`: the disclosure toggle and the panel it opens. Everything mode
   only — in Simple mode neither renders at all, per SMP-1b. */
.media-target-more {
  font-family: var(--font-sans);
  font-size: var(--fs-timeline);
  color: var(--ink-muted);
  background: transparent;
  border: none;
  padding: 2px 4px;
  cursor: pointer;
}
.media-target-more:hover {
  color: var(--ink);
}
.media-advanced {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 14px;
  padding: 8px 10px;
  margin-bottom: 6px;
  background: var(--paper-edge);
  border-radius: var(--radius);
  font-family: var(--font-sans);
  font-size: var(--fs-timeline);
  color: var(--ink-muted);
}
.media-adv-field,
.media-adv-check {
  display: flex;
  align-items: center;
  gap: 6px;
}
.media-adv-field.wide {
  flex: 1;
  min-width: 180px;
}
.media-adv-field input,
.media-adv-field select {
  font-family: var(--font-sans);
  font-size: var(--fs-timeline);
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--paper-edge-2);
  border-radius: var(--radius);
  padding: 2px 4px;
}
.media-adv-field input[type="number"] {
  width: 72px;
}
.media-adv-field.wide input {
  flex: 1;
  min-width: 0;
}
.media-target-back {
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  color: var(--ink-muted);
  background: none;
  border: none;
  cursor: pointer;
  padding: 0;
  white-space: nowrap;
}
.media-target-back:hover {
  color: var(--ink);
}

/* `PIK-3`: the inferred route's suggestion — dim and hedged at low
   confidence, never a silent hijack. */
.media-intent-chip {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 28px;
  padding: 0 10px;
  margin-bottom: 6px;
  background: var(--paper-edge);
  border-radius: var(--radius);
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  color: var(--ink);
}
.media-intent-chip.low-confidence {
  color: var(--ink-faint);
  background: transparent;
  border: 1px dashed var(--paper-edge-2);
}
.mic-label {
  flex: 1;
}
.mic-model {
  font-family: var(--font-sans);
  font-size: var(--fs-timeline);
  color: var(--ink-muted);
  background: var(--paper);
  border: 1px solid var(--paper-edge-2);
  border-radius: var(--radius);
  padding: 2px 4px;
  max-width: 160px;
}
.mic-dismiss {
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  color: var(--ink-muted);
  background: none;
  border: none;
  cursor: pointer;
  padding: 0;
  white-space: nowrap;
}
.mic-dismiss:hover {
  color: var(--ink);
}

/* `EDT-2`: the implicit reference — always visible before send, never a
   silent attach. */
/* `MOD-3`: the one-time "your default isn't available" line. */
.composer-model-notice {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 8px;
  margin-bottom: 6px;
  background: var(--paper-edge);
  border-radius: var(--radius);
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  color: var(--ink-muted);
  width: fit-content;
}
.implicit-ref-chip {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 8px;
  margin-bottom: 6px;
  background: var(--paper-edge);
  border-radius: var(--radius);
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  color: var(--ink-muted);
  width: fit-content;
}
.implicit-ref-thumb {
  width: 32px;
  height: 32px;
  object-fit: cover;
  border-radius: var(--radius);
  border: 1px solid var(--paper-edge-2);
}
.implicit-ref-remove {
  background: none;
  border: none;
  color: var(--ink-faint);
  cursor: pointer;
  font-size: var(--fs-callout);
  line-height: 1;
  padding: 0 2px;
}
.implicit-ref-remove:hover {
  color: var(--ink);
}

/* SKL-UI-2: the `/` skill list anchors to the text field, not to a button, so
   it opens directly above what's being typed. */
.composer-input-wrap {
  position: relative;
  flex: 1;
  display: flex;
}
.composer-slash-menu {
  right: 0;
  max-height: 280px;
  overflow-y: auto;
}
.composer-menu-item.active {
  background: var(--fill-hover);
}

.composer input {
  flex: 1;
  background: none;
  border: none;
  outline: none;
  font-family: var(--font-sans);
  font-size: var(--fs-callout);
  color: var(--ink);
  padding: 6px 2px;
}
.composer input::placeholder {
  color: var(--ink-faint);
}

.composer .icon-btn {
  background: none;
  border: 1px solid transparent;
  color: var(--ink-muted);
  width: 32px;
  height: 32px;
  border-radius: 50%;
  cursor: pointer;
  font-size: var(--fs-ui);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition:
    background-color var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out),
    transform var(--dur) var(--ease-pop),
    opacity var(--dur-fast) var(--ease-out);
}
.composer .icon-btn:hover {
  background: var(--fill-hover);
  color: var(--ink);
}
.composer .icon-btn:active:not(:disabled) {
  transform: scale(0.92);
  transition-duration: 60ms;
}
.composer .plus-btn {
  font-size: 18px;
  font-weight: 300;
  line-height: 1;
}
/* The + turns into a × while its menu is open: one control, two states. */
.composer .plus-btn.on {
  background: var(--fill-press);
  color: var(--ink);
  transform: rotate(45deg);
}

/* Lit, not turned: something is waiting to go out with the message. */
.composer .plus-btn.lit {
  background: var(--fill-press);
  color: var(--ink);
}

/* `CMP-4`: `/` does, where `+` adds. Same size and weight as its neighbour. */
.composer .slash-btn {
  font-family: var(--font-mono);
  font-size: var(--fs-ui);
}
.composer .slash-btn.on {
  background: var(--fill-press);
  color: var(--ink);
}

.composer-menu-empty {
  margin: 0;
  padding: 10px;
  font-size: var(--fs-footnote);
  color: var(--ink-faint);
}

/* The row under the box: context chip left, model chooser right. */
.composer-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding-top: 2px;
  min-height: 26px;
}
.composer-footer .cf-left {
  min-width: 0;
  flex: 1;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px;
}
.composer-footer .cf-right {
  flex: none;
  display: flex;
  align-items: center;
  gap: 8px;
}
.composer-footer .context-chip-wrap {
  padding: 0 4px;
}

/* The image-model select in Create-image mode. Distinct from .model-picker in
   the footer row below, which chooses the model that answers in chat. */
.composer .image-model-picker {
  height: 30px;
  max-width: 150px;
  background: none;
  border: 1px solid var(--hairline);
  color: var(--ink-muted);
  border-radius: var(--radius);
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  padding: 0 6px;
  cursor: pointer;
  flex-shrink: 0;
}
.composer .image-model-picker:hover {
  border-color: var(--ink-faint);
  color: var(--ink);
}

.composer .send {
  background: var(--ink);
  color: var(--paper);
  border-color: var(--ink);
  box-shadow: var(--shadow-xs);
}
.composer .send:hover {
  color: var(--paper);
  background: color-mix(in srgb, var(--ink) 86%, var(--paper));
  transform: scale(1.04);
}
.composer .send:disabled {
  opacity: 0.25;
  box-shadow: none;
  transform: none;
  cursor: default;
}

/* ---- the `+` drop-up: attach / modes / skills / persona, with submenus ---- */
.composer-menu-wrap {
  position: relative;
  display: flex;
}
.composer-menu-backdrop {
  position: fixed;
  inset: 0;
  z-index: 9;
}
.composer-menu {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 0;
  z-index: 10;
  min-width: 300px;
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-float);
  padding: 5px;
  display: flex;
  flex-direction: column;
  transform-origin: bottom left;
  animation: pop-up var(--dur) var(--ease-out);
}
.composer-menu-item {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  width: 100%;
  padding: 7px 10px;
  border: none;
  border-radius: var(--radius-sm);
  background: none;
  cursor: pointer;
  text-align: left;
  color: var(--ink);
  font-size: var(--fs-ui);
  transition-duration: 80ms;
}
.composer-menu-item:hover {
  background: var(--fill-hover);
}
.composer-menu-item:active:not(:disabled) {
  background: var(--fill-press);
}
.composer-menu-item:disabled {
  cursor: default;
  opacity: 0.55;
}
.composer-menu-item:disabled:hover {
  background: none;
}
.composer-menu-item .mi-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 18px;
  height: 18px;
  color: var(--ink-muted);
}
.composer-menu-item:hover .mi-icon {
  color: var(--ink);
}
.composer-menu-item .mi-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.composer-menu-item .mi-hint {
  font-size: var(--fs-footnote);
  color: var(--ink-faint);
}
.composer-menu-item .mi-check {
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  width: 14px;
}
/* The affordance that says "this one opens another panel". */
.composer-menu-item .mi-more {
  color: var(--ink-faint);
  width: 14px;
  text-align: right;
}

.composer-menu-sep {
  height: 1px;
  background: var(--paper-edge);
  margin: 4px 6px;
}

/* Submenu panel: replaces the root list in place, headed by the way back. */
.composer-menu-back {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 7px 10px;
  margin-bottom: 2px;
  background: none;
  border: none;
  border-bottom: 1px solid var(--paper-edge);
  border-radius: 0;
  cursor: pointer;
  text-align: left;
  font-family: var(--font-sans);
  font-size: var(--fs-ui);
  font-weight: 500;
  color: var(--ink-muted);
}
.composer-menu-back:hover {
  color: var(--ink);
}
.composer-submenu {
  animation: fade-in var(--dur-fast) var(--ease-out);
  display: flex;
  flex-direction: column;
  max-height: 300px;
  overflow-y: auto;
}


/* ---- components/Composer/EffortPicker.css ---- */
/* How hard to think, in the composer footer beside the model picker.
   Every measurement here is taken from `.model-picker.compact` so the two read
   as one pair of controls rather than two components that happen to be
   adjacent: same 11.5px, same muted ink, same transparent border that only
   appears on hover or when open, same drop-up. What differs is deliberate —
   this one is quieter, because the model is the decision and this qualifies
   it. */
.effort-picker {
  position: relative;
}

.effort-trigger {
  display: flex;
  align-items: center;
  gap: 6px;
  max-width: 150px;
  padding: 3px 7px 3px 6px;
  background: none;
  border: 1px solid transparent;
  border-radius: var(--radius);
  cursor: pointer;
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  color: var(--ink-muted);
}
.effort-trigger:hover,
.effort-trigger[aria-expanded="true"] {
  background: var(--fill-hover);
  color: var(--ink);
}
.effort-trigger .caret {
  display: inline-flex;
  color: var(--ink-faint);
  margin-left: 1px;
}
.effort-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Three rising bars. The same job the provenance dot does on the model
   trigger: a small glyph that carries the state without spending words. */
.effort-meter {
  display: inline-flex;
  align-items: flex-end;
  gap: 1.5px;
  height: 9px;
  flex-shrink: 0;
}
.effort-meter i {
  width: 2.5px;
  border-radius: 1px;
  background: var(--paper-edge-2);
}
.effort-meter i:nth-child(1) {
  height: 3px;
}
.effort-meter i:nth-child(2) {
  height: 6px;
}
.effort-meter i:nth-child(3) {
  height: 9px;
}
.effort-meter i.on {
  background: var(--ink-muted);
}
/* "Model's default" is not a point on this scale, so it is drawn as an outline
   rather than as some quantity of bars. */
.effort-meter i.unknown {
  background: none;
  box-shadow: inset 0 0 0 1px var(--paper-edge-2);
}
.effort-trigger:hover .effort-meter i.on,
.effort-trigger[aria-expanded="true"] .effort-meter i.on {
  background: var(--ink);
}

/* Drop-up, matching `.model-picker.up .model-dropdown`. */
.effort-dropdown {
  position: absolute;
  bottom: calc(100% + 6px);
  right: 0;
  width: 292px;
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-float);
  padding: 6px;
  z-index: 20;
  transform-origin: bottom right;
  animation: pop-up var(--dur) var(--ease-out);
}

.effort-option {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  column-gap: 9px;
  padding: 6px 8px;
  border-radius: var(--radius);
  cursor: pointer;
}
.effort-option:hover {
  background: var(--fill-hover);
}
.effort-option.selected {
  background: var(--fill-selected);
}
.effort-option .name {
  font-size: var(--fs-ui);
  color: var(--ink);
}
.effort-option.selected .name {
  font-weight: 500;
}
/* The hint sits under the label in the text column, not beside it: these run
   to a sentence, and a second column would either wrap raggedly or force the
   list wider than the model dropdown it sits under. */
.effort-option .hint {
  grid-column: 2;
  font-size: var(--fs-caption);
  line-height: 1.45;
  color: var(--ink-faint);
}

.effort-note {
  margin: 6px 0 0;
  padding: 8px 8px 2px;
  border-top: 1px solid var(--paper-edge-2);
  font-size: var(--fs-caption);
  line-height: 1.45;
  color: var(--ink-faint);
}

/* `DEF-1`: the value shown is for this message only; the dot says so. */
.effort-once {
  color: var(--ink-faint);
}


/* ---- components/Composer/ContextMeter.css ---- */
/* Context fullness (CTX-UI-1). Deliberately quiet: a hairline, no colour of
   its own, no number unless you hover. */
.context-meter {
  width: 64px;
  height: 3px;
  border-radius: var(--radius-xs);
  background: var(--ink-faint);
  overflow: hidden;
  flex: none;
  align-self: center;
}

.context-meter-fill {
  height: 100%;
  background: var(--ink-muted);
  transition: width 240ms ease;
}


/* ---- components/ModelPicker/ModelPicker.css ---- */
.model-picker {
  position: relative;
}

.model-picker-trigger {
  display: flex;
  align-items: center;
  gap: 8px;
  background: none;
  border: 1px solid var(--paper-edge-2);
  border-radius: var(--radius);
  padding: 5px 10px 5px 8px;
  cursor: pointer;
  font-family: var(--font-sans);
  font-size: var(--fs-ui);
  color: var(--ink);
}
.model-picker-trigger:hover {
  border-color: var(--ink-faint);
}
.model-picker-trigger .caret {
  display: inline-flex;
  color: var(--ink-faint);
  margin-left: 1px;
}
.model-picker-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---- compact variant: the picker's home under the composer ---- */
.model-picker.compact .model-picker-trigger {
  gap: 6px;
  padding: 3px 7px 3px 6px;
  font-size: var(--fs-footnote);
  color: var(--ink-muted);
  border-color: transparent;
  max-width: 210px;
}
.model-picker.compact .model-picker-trigger:hover,
.model-picker.compact .model-picker-trigger[aria-expanded="true"] {
  background: var(--fill-hover);
  color: var(--ink);
}
.model-picker.compact .provenance-dot {
  width: 6px;
  height: 6px;
}
.model-picker.compact .model-dropdown {
  width: 300px;
}

/* ---- drop-up: opens above the trigger ---- */
.model-picker.up .model-dropdown {
  top: auto;
  bottom: calc(100% + 6px);
  max-height: 60vh;
  transform-origin: bottom right;
  animation: pop-up var(--dur) var(--ease-out);
}

.provenance-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  flex-shrink: 0;
  display: inline-block;
}
.provenance-dot.local {
  background: var(--local);
}
.provenance-dot.cloud {
  background: var(--cloud);
}
/* A user's own connected server: same "runs on your machine" hue as the
   integrated engine, but a hollow ring so the two are never mistaken for
   one another in the picker. */
.provenance-dot.endpoint {
  background: transparent;
  border: 1.5px solid var(--local);
  box-sizing: border-box;
}

.model-dropdown {
  position: absolute;
  top: 34px;
  right: 0;
  width: 340px;
  max-height: 70vh;
  overflow-y: auto;
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-float);
  padding: 6px;
  z-index: 20;
  transform-origin: top right;
  animation: pop-in var(--dur) var(--ease-out);
}

.filter-row {
  margin: 2px 2px 8px;
}
/* `.filter-chip` is drawn by the shared segmented control (global.css). */

.model-group-label {
  font-size: var(--fs-caption);
  font-weight: 600;
  color: var(--ink-faint);
  padding: 8px 8px 4px;
}

.model-option {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 7px 8px;
  border-radius: var(--radius);
  cursor: pointer;
}
.model-option:hover {
  background: var(--fill-hover);
}
.model-option .name {
  font-size: var(--fs-ui);
  color: var(--ink);
  flex: 1;
}
.model-option .meta {
  font-size: var(--fs-caption);
  color: var(--ink-faint);
}
.model-option .price {
  font-size: var(--fs-caption);
  color: var(--ink-faint);
  flex-shrink: 0;
}
/* A model the provider won't accept tools for. Same weight as the other
   qualifiers on the row — it narrows what the model can do, it isn't a fault. */
.model-option .no-tools {
  flex-shrink: 0;
  font-style: italic;
}
.model-option.selected {
  background: var(--fill-selected);
}
.model-option.selected .name {
  font-weight: 500;
}

.cloud-search {
  width: 100%;
  box-sizing: border-box;
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--paper-edge-2);
  border-radius: var(--radius);
  padding: 6px 9px;
  margin: 2px 0 6px;
  outline: none;
}
.cloud-search:focus {
  border-color: var(--ink-faint);
}

.add-key-row {
  font-size: var(--fs-footnote);
  color: var(--ink-muted);
  padding: 8px 8px 4px;
  margin-top: 4px;
  border-top: 1px solid var(--paper-edge-2);
}
.add-key-row a {
  color: var(--local);
  text-decoration: none;
}

/* `MOD-4`: the favorite star, on picker rows and on the Models page. */
.star-btn {
  background: none;
  border: none;
  font-size: var(--fs-ui-lg);
  line-height: 1;
  color: var(--ink-faint);
  cursor: pointer;
  padding: 2px 4px;
}
.star-btn.on {
  color: var(--ink);
}
.star-btn:disabled {
  cursor: default;
  opacity: 0.55;
}
.model-option .star-btn {
  margin-left: auto;
  font-size: var(--fs-ui);
}


/* ---- components/Workbench/Workbench.css ---- */
/* The Workbench dock: mirrors the Rail on the opposite edge — same grid span,
   same padding rhythm, border on the inside edge. */

/* `SHL-27`: the open item takes the conversation's cells — the main column,
   both of its rows, since the composer's row goes with the conversation it
   belongs to. No border of its own: it is the main column, not a panel beside
   it, and the only edges here are the rail's and the sidebar's.

   Drawn inside the sidebar this was stuck at 340px and covered the tree it
   came from; given a column of its own (`SHL-26`) it split the window with the
   conversation and neither had room. */
.item-view {
  position: relative;
  grid-column: 2;
  grid-row: 2 / -1;
  min-width: 0;
  overflow: hidden;
  background: var(--paper);
}
.item-view .agents-detail {
  padding: 14px 18px;
}

.workbench {
  position: relative;
  grid-column: 3;
  grid-row: 2 / -1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  overflow: hidden;
  background: var(--paper);
  border-left: 1px solid var(--paper-edge-2);
}
/* Collapsed: the grid column is 0 wide, so let the content keep its own width
   and clip. Without this the panel reflows to nothing on the way out and the
   animation reads as a jumble rather than a slide. */
.app.dock-collapsed .workbench {
  border-left: none;
  pointer-events: none;
}
.app.dock-collapsed .workbench > * {
  min-width: 300px;
}

/* The drag handle sits *on* the dock's inner edge — a few pixels wide, with a
   generous hit area, invisible until you approach it. */
.wb-resizer {
  position: absolute;
  top: 0;
  bottom: 0;
  left: -3px;
  width: 7px;
  z-index: 5;
  cursor: col-resize;
  background: none;
  border: none;
  padding: 0;
}
.wb-resizer:hover,
.app.dock-dragging .workbench > .wb-resizer {
  background: var(--local);
  opacity: 0.5;
}
.wb-resizer:focus-visible {
  background: var(--local);
  opacity: 1;
  outline: none;
}
.app.dock-collapsed .workbench > .wb-resizer {
  display: none;
}

/* ---- context header: where this chat works, on one line ----
   The rail's row language — 30px, a flat fill on hover, radius 4 — on the
   Workbench's own paper. Everything you can set about the folder waits in the
   popover underneath; the line itself only has to answer "where, and how much
   may it change". */

.wb-head {
  position: relative;
  flex-shrink: 0;
  padding: 8px 8px 6px;
}
.wb-context {
  display: flex;
  align-items: center;
  gap: 2px;
  min-width: 0;
}
.wb-context-btn {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 7px;
  min-height: 30px;
  padding: 0 7px;
  background: none;
  border: none;
  border-radius: var(--radius);
  font-family: var(--font-sans);
  font-size: var(--fs-ui);
  color: var(--ink);
  text-align: left;
  cursor: pointer;
}
.wb-context-btn:hover,
.wb-context-btn[aria-expanded="true"] {
  background: var(--fill-hover);
}
.wb-folder-icon {
  display: flex;
  color: var(--ink-faint);
  flex-shrink: 0;
}
.wb-crumbs {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: baseline;
  gap: 5px;
  white-space: nowrap;
  overflow: hidden;
}
/* The project is the context, the folder is what this panel is about: the
   project yields its width first and reads a step quieter. */
.wb-crumb-project {
  flex: 0 1 auto;
  min-width: 2.5em;
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--ink-muted);
}
.wb-crumb-sep {
  flex: none;
  color: var(--ink-faint);
}
.wb-folder-name {
  flex: 0 0 auto;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  font-weight: 500;
  color: var(--ink);
}
.wb-crumb-none {
  color: var(--ink-faint);
}
/* How much it may change, as a word, not a control. Only "Full" is marked,
   since it is the one level where nothing waits for you. */
.wb-access {
  flex: none;
  font-size: var(--fs-caption);
  line-height: 1.6;
  color: var(--ink-faint);
  border: 1px solid var(--paper-edge-2);
  border-radius: var(--radius);
  padding: 0 5px;
}
.wb-access-auto {
  color: var(--ink-muted);
  border-color: var(--ink-faint);
}
.wb-context-caret {
  flex: none;
  color: var(--ink-faint);
  transform: rotate(90deg);
  transition: transform var(--t-fast);
}
.wb-context-caret.open {
  transform: rotate(-90deg);
}
.wb-context-choose {
  flex: none;
}

/* ---- the context popover ---- */

.wb-popover {
  position: absolute;
  top: calc(100% - 4px);
  left: 8px;
  right: 8px;
  z-index: 30;
  max-height: 70vh;
  overflow-y: auto;
  padding: 4px;
  background: var(--paper);
  border: 1px solid var(--paper-edge-2);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-float);
}
.wb-pop-section {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 6px 4px 8px;
}
.wb-pop-section + .wb-pop-section {
  border-top: 1px solid var(--paper-edge);
}
/* The rail's group label, so the popover's parts read like the rail's. */
.wb-pop-label {
  margin: 0;
  padding: 2px 7px 5px;
  font-family: var(--font-sans);
  font-size: var(--fs-caption);
  font-weight: 600;
  color: var(--ink-faint);
}
.wb-pop-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
  min-height: 30px;
  padding: 0 7px;
  background: none;
  border: none;
  border-radius: var(--radius);
  font-family: var(--font-sans);
  font-size: var(--fs-ui);
  color: var(--ink);
  text-align: left;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.wb-pop-item:hover {
  background: var(--fill-hover);
}
.wb-pop-quiet {
  color: var(--ink-muted);
}
.wb-pop-danger {
  color: var(--danger);
}
.wb-project-name {
  font-weight: 500;
}
.wb-pop-caret {
  flex: none;
  color: var(--ink-faint);
  transition: transform var(--t-fast);
}
.wb-pop-caret.open {
  transform: rotate(90deg);
}
/* Destinations hang off a hairline, the way a project's chats do in the rail. */
.wb-pop-sublist {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding-left: 14px;
}
.wb-pop-sublist::before {
  content: "";
  position: absolute;
  left: 9px;
  top: 2px;
  bottom: 2px;
  width: 1px;
  background: var(--paper-edge-2);
}
.wb-pop-note {
  margin: 6px 7px 0;
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  line-height: 1.45;
  color: var(--ink-muted);
}
.wb-pop-section > .wb-primary {
  align-self: flex-start;
  margin: 8px 7px 0;
}
.wb-pop-section > .wb-confirm {
  padding: 2px 7px 0;
}
.wb-pop-section > .wb-segments {
  margin: 0 7px;
}
/* Three equal parts across the popover rather than a cluster at its left. */
.wb-segments-fill {
  display: flex;
  width: auto;
}
.wb-segments-fill .wb-segment {
  flex: 1;
}

.wb-folder-path {
  margin: 0 7px 6px;
  font-family: var(--font-mono);
  font-size: var(--fs-caption);
  color: var(--ink-faint);
  word-break: break-all;
}

/* COD-UI-1: what is known about the project's code. */
.wb-code .wb-chips,
.wb-code .wb-tasks {
  margin-left: 7px;
  margin-right: 7px;
}
.wb-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
}
.wb-chip {
  font-family: var(--font-sans);
  font-size: var(--fs-caption);
  line-height: 1.5;
  color: var(--ink-muted);
  background: none;
  border: 1px solid var(--paper-edge-2);
  border-radius: var(--radius);
  padding: 0 6px;
  white-space: nowrap;
}
.wb-chip-mono {
  font-family: var(--font-mono);
}
.wb-chip-button {
  cursor: pointer;
}
.wb-chip-button:hover,
.wb-chip-button.on {
  color: var(--ink);
  background: var(--fill-hover);
}
.wb-chip-note {
  color: var(--ink-faint);
}
.wb-tasks {
  margin-top: 8px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.wb-tasks-note {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  line-height: 1.45;
  color: var(--ink-muted);
}
.wb-task-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.wb-task {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  min-width: 0;
}
.wb-task-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.wb-task-name {
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  color: var(--ink);
}
.wb-task-cmd {
  font-family: var(--font-mono);
  font-size: var(--fs-caption);
  color: var(--ink-faint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.wb-task-allow {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-shrink: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  color: var(--ink-muted);
  cursor: pointer;
}
.wb-commands {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.wb-tasks-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

/* One empty state for every sub-view: a title, one line on what will appear,
   and at most one way to make it appear. */
.wb-empty {
  padding: 18px 15px;
}
.wb-empty-title {
  margin: 0 0 4px;
  font-family: var(--font-sans);
  font-size: var(--fs-ui);
  font-weight: 500;
  color: var(--ink);
}
.wb-empty-blurb {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  line-height: 1.5;
  color: var(--ink-muted);
}
.wb-empty .wb-primary {
  margin-top: 12px;
}

.wb-primary {
  font-family: var(--font-sans);
  font-size: var(--fs-ui);
  color: var(--ink);
  background: var(--paper-edge);
  border: 1px solid var(--paper-edge-2);
  border-radius: var(--radius);
  padding: 6px 12px;
  cursor: pointer;
}
.wb-primary:hover {
  background: var(--paper-edge-2);
}

.wb-link {
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  color: var(--ink-muted);
  background: none;
  border: none;
  padding: 2px 4px;
  cursor: pointer;
  border-radius: var(--radius);
}
.wb-link:hover {
  color: var(--ink);
  background: var(--fill-hover);
}

.wb-error {
  margin: 8px 0 0;
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  line-height: 1.45;
  color: var(--danger);
}

/* ---- folder index status (IDX-UI-1/2/3) ---- */

/* The foot of the Files view: what I have read of this folder. */
.wb-files-foot {
  flex-shrink: 0;
  padding: 6px 12px 8px 15px;
  border-top: 1px solid var(--paper-edge);
}
.wb-index-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  min-width: 0;
}
.wb-index-status {
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  color: var(--ink-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* SMP-4c: the one-time "what reading is for" line, beneath the reading state. */
.wb-index-explain {
  margin: 4px 0 0;
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  line-height: 1.45;
  color: var(--ink-muted);
}
.wb-index-skipped {
  margin: 2px 0 0 -4px;
}
.wb-index-skipped-list {
  margin: 4px 0 0;
  padding: 0 0 0 14px;
  list-style: disc;
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  line-height: 1.6;
  color: var(--ink-muted);
}
.wb-index-skipped-path {
  font-family: var(--font-mono);
  color: var(--ink);
}

/* ---- trust selector: the per-session access level ---- */

.wb-trust {
  display: flex;
  align-items: center;
  gap: 8px;
}
.wb-trust-label {
  font-family: var(--font-sans);
  font-size: var(--fs-caption);
  color: var(--ink-faint);
  font-weight: 600;
}
/* `.wb-segments` / `.wb-segment` are drawn by the shared segmented control
   (global.css). */

.wb-confirm p {
  margin: 0 0 8px;
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  line-height: 1.45;
  color: var(--ink-muted);
}
.wb-confirm-actions {
  display: flex;
  align-items: center;
  gap: 6px;
}

/* ---- overflow menu ---- */

.wb-menu-wrap {
  position: relative;
  flex-shrink: 0;
}
.wb-menu {
  position: absolute;
  top: 100%;
  right: 0;
  z-index: 20;
  min-width: 178px;
  padding: 4px;
  background: var(--paper);
  border: 1px solid var(--paper-edge-2);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-float);
}
.wb-menu button {
  display: block;
  width: 100%;
  text-align: left;
  font-family: var(--font-sans);
  font-size: var(--fs-ui);
  color: var(--ink);
  background: none;
  border: none;
  padding: 6px 9px;
  border-radius: var(--radius);
  cursor: pointer;
}
.wb-menu button:hover {
  background: var(--fill-hover);
}
.wb-menu hr {
  border: none;
  border-top: 1px solid var(--paper-edge);
  margin: 4px 2px;
}
.wb-menu-danger {
  color: var(--danger) !important;
}

/* A right-click menu (Tree.tsx's directory rows) rather than one anchored to
   a toggle button — `.wb-menu`'s look, positioned at the pointer instead. */
.wb-context-menu {
  position: fixed;
}

/* ---- duplicate scan (Perception, PHS-UI-1) ---- */
.wb-duplicates {
  padding-bottom: 8px;
}
.wb-dup-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 6px 0 0;
  padding: 0 12px;
  list-style: none;
}
.wb-dup-group {
  padding: 8px 10px;
  border: 1px solid var(--paper-edge-2);
  border-radius: var(--radius);
}
.wb-dup-relation {
  margin: 0 0 6px;
  font-size: var(--fs-footnote);
  color: var(--ink-faint);
  text-transform: uppercase;
  letter-spacing: 0.02em;
}
.wb-dup-files {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.wb-dup-file {
  display: flex;
  align-items: center;
  gap: 8px;
}
.wb-dup-path {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--fs-footnote);
  color: var(--ink);
}

.wb-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  color: var(--ink-faint);
  cursor: pointer;
  padding: 4px;
  border-radius: var(--radius);
  flex-shrink: 0;
}
.wb-icon:hover {
  color: var(--ink);
  background: var(--fill-hover);
}

/* ---- stacked sections: Files, then Artifacts ---- */

/* ---- the sub-views: Files · Artifacts · Changes (· Agents · Browser) ----
   Tabs rather than a stack, because each of these wants the column's full
   height. Drawn as the rail draws its rows — sentence case, a flat fill on
   hover, the rail's tinted fill for the one you are on — so moving between
   the two sidebars is moving between one kind of control. The row never wraps:
   on a narrow panel it scrolls sideways instead. */
.wb-tabs {
  display: flex;
  gap: 2px;
  padding: 0 8px 8px;
  border-bottom: 1px solid var(--paper-edge-2);
  flex-shrink: 0;
  overflow-x: auto;
  scrollbar-width: none;
}
.wb-tabs::-webkit-scrollbar {
  display: none;
}
.wb-tab {
  flex: none;
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: 28px;
  padding: 0 9px;
  background: none;
  border: none;
  border-radius: var(--radius);
  cursor: pointer;
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  color: var(--ink-muted);
  white-space: nowrap;
}
.wb-tab:hover {
  background: var(--fill-hover);
  color: var(--ink);
}
.wb-tab.active {
  background: var(--fill-selected);
  color: var(--ink);
  font-weight: 500;
}
/* The rail's stamp: mono, faint, tabular. */
.wb-tab-count {
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-caption);
  font-weight: 400;
  color: var(--ink-faint);
}
.wb-tab.active .wb-tab-count {
  color: var(--ink-muted);
}
/* A live page is a state, not a quantity — one dot, ink-toned, and it stops
   moving for anyone who asked motion to stop. */
.wb-tab-live {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--ink-muted);
  animation: wb-tab-breathe 2.6s ease-in-out infinite;
}
@keyframes wb-tab-breathe {
  0%,
  100% {
    opacity: 0.35;
  }
  50% {
    opacity: 1;
  }
}
@media (prefers-reduced-motion: reduce) {
  .wb-tab-live {
    animation: none;
    opacity: 0.9;
  }
}

.wb-tabpanel {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}

.wb-section-block {
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.wb-files {
  flex: 1;
}
/* Every list in the Workbench — files, artifacts, agents — is the same thing:
   rows inset 8px from the panel edges, the rail's fills, no card around them.
   They used to be a flush tree in one tab and bordered cards in two others,
   for no reason tied to what any of them shows. */
.wb-artifacts-block {
  flex: 0 1 auto;
  padding: 8px 8px 12px;
}
.wb-artifacts {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-height: 0;
}

.wb-section-toggle {
  width: 100%;
  background: none;
  border: none;
  cursor: pointer;
  text-align: left;
}
.wb-section-toggle:hover {
  color: var(--ink-muted);
}
.wb-section-action {
  margin-left: auto;
  padding: 2px;
}

.wb-filter-wrap {
  padding: 8px 8px 6px;
  flex-shrink: 0;
}
.wb-filter {
  width: 100%;
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--paper-edge-2);
  border-radius: var(--radius);
  padding: 4px 8px;
}
.wb-filter::placeholder {
  color: var(--ink-faint);
}

.wb-tree {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 1px;
  overflow-y: auto;
  padding: 0 8px 8px;
  min-height: 0;
}
.wb-tree > .wb-hint {
  padding: 8px 7px;
}
/* Artifact rows sit in their own section, so they don't need the tree's
   indent machinery — just the kind chip and the Save action. */
.wb-artifact {
  min-height: 30px;
  padding-left: 7px;
}
.wb-artifact .wb-row-name {
  font-style: italic;
}
.wb-hint {
  margin: 0;
  padding: 14px 14px;
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  line-height: 1.5;
  color: var(--ink-faint);
}

.wb-section {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 9px 12px 5px;
  font-family: var(--font-sans);
  font-size: var(--fs-caption);
  color: var(--ink-faint);
  flex-shrink: 0;
  font-weight: 600;
}
.wb-section-count {
  margin-left: auto;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0;
}

.wb-row {
  display: flex;
  align-items: center;
  gap: 5px;
  padding-right: 7px;
  min-height: 26px;
  border-radius: var(--radius);
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  color: var(--ink-muted);
  cursor: pointer;
  user-select: none;
}
.wb-row:focus-visible {
  outline-offset: -2px;
}
.wb-row:hover {
  background: var(--fill-hover);
  color: var(--ink);
}
/* The rail's "where you are" fill — see `.chat-row.active` in Rail.css. */
.wb-row.active {
  background: var(--fill-selected);
  color: var(--ink);
  font-weight: 500;
}
.wb-row.is-dir {
  color: var(--ink);
}
.wb-row-caret {
  width: 9px;
  flex-shrink: 0;
  font-size: var(--fs-micro);
  color: var(--ink-faint);
  text-align: center;
}
.wb-row-icon {
  display: flex;
  flex-shrink: 0;
  color: var(--ink-faint);
}
.wb-row:hover .wb-row-icon,
.wb-row.active .wb-row-icon {
  color: var(--ink-muted);
}
.wb-row-name {
  flex: 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.wb-touched {
  flex-shrink: 0;
  font-size: var(--fs-caption);
  color: var(--local);
  font-variant-numeric: tabular-nums;
}

.wb-kind {
  flex-shrink: 0;
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink-faint);
  border: 1px solid var(--paper-edge-2);
  border-radius: var(--radius);
  padding: 0 5px;
}

/* `Save →` promotes an artifact into the folder. Revealed on hover/focus so the
   rows stay quiet, but always reachable by keyboard. */
.wb-save {
  flex-shrink: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-caption);
  color: var(--ink-muted);
  background: none;
  border: 1px solid transparent;
  border-radius: var(--radius);
  padding: 1px 5px;
  cursor: pointer;
  opacity: 0;
}
.wb-row:hover .wb-save,
.wb-row:focus-within .wb-save,
.wb-save:focus-visible {
  opacity: 1;
  border-color: var(--paper-edge-2);
}
.wb-save:hover {
  color: var(--ink);
  background: var(--paper);
}

.wb-save-row {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 4px 10px 6px 24px;
}
.wb-save-input {
  flex: 1;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-footnote);
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--paper-edge-2);
  border-radius: var(--radius);
  padding: 3px 6px;
}

/* ---- viewer: the head and body of an open item tab ---- */

.wb-viewer {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
}
.wb-viewer-head {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 6px 8px 6px 12px;
  border-bottom: 1px solid var(--paper-edge);
  flex-shrink: 0;
}
.wb-viewer-title {
  flex: 1;
  min-width: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.wb-viewer-actions {
  display: flex;
  align-items: center;
  gap: 1px;
  flex-shrink: 0;
}
.wb-viewer-body {
  flex: 1;
  overflow: auto;
  min-height: 0;
}
.viewer-binary {
  padding: 22px 16px;
  text-align: center;
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  color: var(--ink-muted);
}
.viewer-binary p {
  margin: 0 0 8px;
}

/* ---- `EDT-1`: the editable file tab ---- */

/* Monaco measures its host, so the column from the pane down to the editor has
   to have a real height at every step — one `min-height: 0` missing anywhere
   here and the editor collapses to nothing or grows without bound. */
.editor-pane,
.editor-wrap {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
  overflow: hidden;
}
.editor-host {
  flex: 1;
  min-height: 0;
}
/* Monaco draws its own everything; all this has to do is not fight it. The
   editor takes the panel's paper, so the seam between chrome and code is a
   hairline, as everywhere else in the app. */
.editor-host .monaco-editor,
.editor-host .monaco-editor .overflow-guard {
  border-radius: 0;
}

/* A bar above the code: the conflict question, a read-only reason, a failed
   save. Never more than one at a time — they are ordered by how much they
   stop you doing. */
.editor-bar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 12px;
  border-bottom: 1px solid var(--paper-edge-2);
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  line-height: 1.45;
  color: var(--ink-muted);
}
.editor-bar span {
  flex: 1;
  min-width: 0;
}
.editor-bar.conflict,
.editor-bar.error {
  color: var(--danger);
}
.editor-bar .wb-link {
  flex: 0 0 auto;
}

/* The quietest possible status line: one word, most of the time "Saved". It
   sits under the code rather than over it so it cannot cover a line. */
.editor-status {
  flex: 0 0 auto;
  padding: 4px 12px;
  border-top: 1px solid var(--paper-edge);
  font-family: var(--font-sans);
  font-size: var(--fs-caption);
  color: var(--ink-faint);
  text-align: right;
}

/* Preview / Source, for the kinds that have both. It rides in the pane's
   header row, left of the icons that act on the file, so the header reads
   left to right as "this file · shown how · do what". A pair of text buttons,
   not a pill or a segmented control — the panel is flat, and the only mark of
   the active one is ink weight, the same way the Workbench's own tabs work. */
.editor-modes {
  flex: 0 0 auto;
  display: flex;
  gap: 1px;
  margin-right: 4px;
}
.editor-mode {
  display: flex;
  align-items: center;
  gap: 5px;
  border: none;
  background: none;
  padding: 3px 7px;
  border-radius: var(--radius);
  cursor: pointer;
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  color: var(--ink-faint);
}
.editor-mode:hover {
  color: var(--ink-muted);
  background: var(--fill-hover);
}
/* Ink and weight only. The Workbench's own tabs mark the active one with a 2px
   underline as well, but this row is 6px tall and sits directly on the header's
   hairline — a second rule that close to the first reads as a rendering
   artefact rather than as a selection. */
.editor-mode.active {
  color: var(--ink);
  font-weight: 600;
}
.editor-mode-dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--ink);
}
/* With the toggle present the editor is the row below it, not the whole pane. */
.editor-pane > .editor-wrap {
  flex: 1;
  min-height: 0;
  height: auto;
}
/* HTML and SVG previews are iframes: they fill the row and scroll internally,
   so they need the height but never a scroller around them. */
.editor-pane > .canvas-html,
.editor-pane > .canvas-frame {
  flex: 1;
  min-height: 0;
}
/* Markdown needs a scroller of its own here. Everywhere else
   `.canvas-markdown` is a direct child of `.wb-viewer-body`, which scrolls it;
   under the toggle it is a flex row in a clipped column instead, so without
   this a file taller than the pane simply ends where the pane does — no
   scrollbar, no way down.
   The scroller is this wrapper and not `.canvas-markdown` itself because that
   is capped at `--measure`: scrolling it would hang the scrollbar at the end
   of the text column, floating in the middle of a wide pane, instead of at the
   pane's edge where every other scrollbar in the app sits. `auto` on both axes
   — a wide table or an unbroken URL overflows sideways, and the measure cap
   does not apply to either. */
.editor-preview {
  flex: 1;
  min-height: 0;
  overflow: auto;
}

/* ---- rendered content ---- */

.canvas-frame {
  width: 100%;
  height: 100%;
  border: none;
  background: #fff;
}

/* `ART-5`: the page and what it said, stacked. The console takes only the room
   it needs, so a page that prints nothing looks exactly as it did before. */
.canvas-html {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
}
.canvas-html > .canvas-frame {
  flex: 1;
  min-height: 0;
}
.preview-console {
  flex: 0 0 auto;
  border-top: 1px solid var(--paper-edge-2);
  background: var(--paper);
  font-family: var(--font-mono);
  font-size: var(--fs-timeline);
}
.pc-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 0 8px 0 4px;
}
.pc-toggle,
.pc-clear {
  background: none;
  border: none;
  color: var(--ink-muted);
  font: inherit;
  cursor: pointer;
  padding: 6px 4px;
}
.pc-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: 1;
  text-align: left;
}
.pc-toggle:hover,
.pc-clear:hover {
  color: var(--ink);
}
.pc-caret {
  display: inline-block;
  transition: transform var(--t-fast);
  color: var(--ink-faint);
}
.pc-caret.open {
  transform: rotate(90deg);
}
.pc-count {
  color: var(--ink-faint);
}
/* The one thing here worth a colour: the page broke. */
.pc-errors {
  color: var(--danger);
}
.pc-list {
  /* Tall enough to read a stack trace, short enough to leave the page the
     panel. Beyond that it scrolls. */
  max-height: 180px;
  overflow-y: auto;
  border-top: 1px solid var(--paper-edge);
  padding: 4px 0;
}
.pc-line {
  display: flex;
  gap: 8px;
  padding: 2px 8px;
  align-items: baseline;
  color: var(--ink-muted);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.pc-line + .pc-line {
  border-top: 1px solid var(--paper-edge);
}
.pc-level {
  flex: 0 0 auto;
  color: var(--ink-faint);
  min-width: 4.5em;
}
.pc-text {
  flex: 1;
}
.pc-source {
  flex: 0 0 auto;
  color: var(--ink-faint);
}
/* Sits quietly at the end of the line it belongs to and firms up on hover:
   it's an offer attached to one error, not a call to action competing with
   the error text itself. */
.pc-fix {
  flex: 0 0 auto;
  align-self: center;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  color: var(--ink-faint);
  background: none;
  border: none;
  border-radius: var(--radius);
  padding: 0;
  cursor: pointer;
  transition: color var(--t-fast), background var(--t-fast);
}
.pc-fix:hover:not(:disabled) {
  color: var(--ink);
  background: var(--fill-hover);
}
.pc-fix:disabled {
  opacity: 0.4;
  cursor: default;
}
.pc-error .pc-level,
.pc-error .pc-text,
.pc-uncaught .pc-level,
.pc-uncaught .pc-text {
  color: var(--danger);
}
.pc-warn .pc-level {
  color: var(--cloud);
}
.canvas-markdown {
  padding: 18px 20px;
  font-family: var(--font-serif);
  font-size: var(--fs-reading);
  line-height: var(--lh-reading);
  color: var(--ink);
  max-width: var(--measure);
}
.canvas-markdown img,
.canvas-markdown table {
  max-width: 100%;
}
.canvas-code {
  margin: 0;
  padding: 14px 16px;
  font-family: var(--font-mono);
  font-size: var(--fs-footnote);
  line-height: 1.6;
  color: var(--ink);
  white-space: pre-wrap;
  word-break: break-word;
}
/* COD-UI-5: a code artifact's source above, what running it printed below. */
.viewer-code-scroll {
  flex: 1;
  min-height: 0;
  overflow: auto;
}
.viewer-run {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.viewer-run-outcome {
  font-size: var(--fs-footnote);
  color: var(--ink-muted);
}
.viewer-run-outcome.failed {
  color: var(--danger);
}
.viewer-run-error {
  margin: 0;
  padding: 4px 10px 8px;
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  color: var(--danger);
}
.viewer-run-body {
  max-height: 240px;
  padding: 8px 10px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.viewer-run-output {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-footnote);
  line-height: 1.5;
  color: var(--ink-muted);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
/* `COD-UI-2`'s hand-numbered line view (`.viewer-lines` / `.viewer-line`) is
   gone: opening a file at a line is Monaco's `revealLineInCenter` now, and it
   brings its own gutter, its own current-line mark and horizontal scrolling
   that does not fight `pre-wrap`. */
.canvas-image {
  display: block;
  max-width: 100%;
  margin: 16px auto;
}
.canvas-loading {
  padding: 24px;
  font-family: var(--font-sans);
  font-size: var(--fs-ui);
  color: var(--ink-muted);
  text-align: center;
}

/* ---- recent changes ---- */

.wb-changes {
  flex-shrink: 0;
  border-top: 1px solid var(--paper-edge-2);
  max-height: 34%;
  overflow-y: auto;
}
.wb-changes-head {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  padding: 8px 12px;
  font-family: var(--font-sans);
  font-size: var(--fs-caption);
  color: var(--ink-faint);
  background: none;
  border: none;
  cursor: pointer;
  text-align: left;
  font-weight: 600;
}
.wb-changes-head:hover {
  color: var(--ink-muted);
}
.wb-changes-caret {
  font-size: var(--fs-micro);
}
.wb-changes-list {
  list-style: none;
  margin: 0;
  padding: 0 10px 8px;
}
.wb-changes-list li {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 2px 2px;
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  color: var(--ink-muted);
}
.wb-changes-list li.undone {
  opacity: 0.5;
}
.wb-change-op {
  flex-shrink: 0;
  color: var(--ink-faint);
}
.wb-change-name {
  flex: 1;
  min-width: 0;
  text-align: left;
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  color: var(--ink);
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.wb-change-name:hover {
  text-decoration: underline;
}
.wb-change-when,
.wb-change-undone {
  flex-shrink: 0;
  font-size: var(--fs-caption);
  color: var(--ink-faint);
}

/* `BRW-UI-1`: the browser as a felt presence — a live page, a screenshot,
   a plain action trail. */
.wb-browser {
  border-bottom: 1px solid var(--paper-edge-2);
  padding-bottom: 10px;
}
.wb-browser-head {
  display: flex;
  align-items: center;
  gap: 6px;
}
.wb-browser-glyph {
  color: var(--ink-faint);
}
.wb-browser-title {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.wb-browser-stop {
  flex-shrink: 0;
  background: none;
  border: none;
  font-family: var(--font-sans);
  font-size: var(--fs-caption);
  color: var(--ink-faint);
  cursor: pointer;
  padding: 3px 6px;
  border-radius: var(--radius);
}
.wb-browser-stop:hover {
  color: var(--ink);
  background: var(--fill-hover);
}
.wb-browser-page-title {
  margin: 2px 14px 0;
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  color: var(--ink-muted);
}
.wb-browser-shot {
  display: block;
  width: calc(100% - 28px);
  margin: 8px 14px 0;
  padding: 0;
  background: none;
  border: none;
  cursor: zoom-in;
}
.wb-browser-shot img {
  display: block;
  width: 100%;
  border-radius: var(--radius-lg);
  border: 1px solid var(--paper-edge-2);
  max-height: 140px;
  object-fit: cover;
  object-position: top;
}
.wb-browser-shot.expanded {
  cursor: zoom-out;
}
.wb-browser-shot.expanded img {
  max-height: none;
  object-fit: contain;
}
.wb-browser-trail {
  margin: 8px 14px 0;
  font-family: var(--font-sans);
  font-size: var(--fs-footnote);
  color: var(--ink-faint);
}

/* `SUB-UI-2`: the Agents tab. A short list of who is working, and the whole
   panel below it for what the selected one is actually doing — the difference
   between knowing an agent exists and being able to supervise it. */
/* `.wb-tabpanel` carries no padding of its own — each panel supplies what it
   needs, and this one supplied none, so its list and transcript sat flush
   against the panel edge. 14px matches `.wb-hint`, which is what this tab
   shows when no work has been handed out yet; the two must not shift. */
.agents-panel {
  display: flex;
  flex-direction: column;
  min-height: 0;
  padding: 8px 8px 12px;
}
.agents-list {
  display: flex;
  flex-direction: column;
  gap: 1px;
  flex: none;
}
.agents-row {
  display: flex;
  gap: 8px;
  align-items: center;
  min-height: 30px;
  text-align: left;
  background: none;
  border: none;
  border-radius: var(--radius);
  padding: 0 7px;
  cursor: pointer;
  font: inherit;
  font-size: var(--fs-ui);
  color: var(--ink-muted);
  min-width: 0;
}
.agents-row:hover {
  background: var(--fill-hover);
  color: var(--ink);
}
.agents-row.active {
  background: var(--fill-selected);
  color: var(--ink);
}
/* Working or done: the orb while it runs, a still faint dot once it has
   stopped. The dot sits in a 20px-wide cell so the names stay aligned. */
.agents-row-dot {
  flex: none;
  width: 6px;
  height: 6px;
  margin: 0 7px;
  border-radius: 50%;
  background: var(--ink-faint);
}
.agents-row-orb {
  flex: none;
  display: flex;
  width: 20px;
  height: 20px;
}
.agents-row-agent {
  flex: none;
  color: var(--ink);
  font-weight: 500;
  white-space: nowrap;
}
.agents-row-task {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.agents-detail {
  min-height: 0;
  overflow-y: auto;
}
.agents-detail-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.agents-detail-agent {
  font-size: var(--fs-ui-lg);
  color: var(--ink);
}
.agents-task {
  font-size: var(--fs-ui);
  color: var(--ink-muted);
  margin: 2px 0 10px;
}
.agents-artifacts {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 8px;
}
.agents-artifact {
  font-size: var(--fs-timeline);
  color: var(--ink-muted);
  border: 1px solid var(--paper-edge-2);
  border-radius: var(--radius);
  padding: 2px 6px;
}
.agents-steer {
  margin-top: 10px;
}
.agents-steer input {
  width: 100%;
  font: inherit;
  font-size: var(--fs-ui);
  padding: 6px 8px;
  border: 1px solid var(--paper-edge-2);
  border-radius: var(--radius);
  background: var(--paper);
  color: var(--ink);
}

