/*
  Application styles.

  Rules here resolve colour, spacing and radius through the variables in
  tokens.css and never hard-code a value. Layout is flexbox and grid only --
  no framework, no preprocessor, no build step.
*/

/* --- Reset ---------------------------------------------------------------- */
*,
*::before,
*::after {
  box-sizing: border-box;
}

html,
body {
  height: 100%;
}

/*
  The `hidden` attribute has to win.

  The browser's own rule is `[hidden] { display: none }`, which any component
  rule setting `display` outranks -- `.btn` is `display: inline-flex`, so a
  button hidden from JavaScript stayed visible. That is not a styling nit: it
  is how the Stop button came to sit permanently beside Send. Anything toggled
  with `hidden` anywhere in the application depends on this line.
*/
[hidden] {
  display: none !important;
}

body {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  color: var(--ink);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4, h5, h6 {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 600;
  line-height: var(--leading-tight);
  letter-spacing: 0.01em;
}

p { margin: 0 0 var(--sp-4); }
p:last-child { margin-bottom: 0; }

a {
  color: var(--accent);
  text-decoration-color: color-mix(in srgb, var(--accent) 40%, transparent);
  text-underline-offset: 2px;
}
a:hover { color: var(--accent-hover); }

button, input, textarea, select {
  font: inherit;
  color: inherit;
}

/* A single, consistent focus ring. Never remove it without a replacement. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
:focus:not(:focus-visible) { outline: none; }

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* --- Icons ---------------------------------------------------------------- */
.icon {
  width: 1.25em;
  height: 1.25em;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.icon--sm { width: 1em; height: 1em; }
.icon--lg { width: 1.5em; height: 1.5em; }
/* The leaf is a filled silhouette, not a stroked pictogram. */
.icon--leaf { fill: currentColor; stroke: none; }

/* --- Buttons ---------------------------------------------------------------
   Every variant is the same height and vertically centres its contents, so a
   row of mixed buttons lines up without per-instance nudging. Icon buttons are
   square at that height rather than a different shape.
*/
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  height: var(--control-h);
  padding: 0 var(--control-px);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-raised);
  color: var(--ink);
  font-size: var(--text-sm);
  font-weight: 500;
  line-height: 1;
  cursor: pointer;
  white-space: nowrap;
  text-decoration: none;
  transition: background var(--transition-fast), border-color var(--transition-fast),
    color var(--transition-fast);
}
.btn:hover:not(:disabled) {
  background: var(--surface-hover);
  border-color: var(--border-strong);
  color: var(--ink);
}
.btn:disabled { opacity: 0.45; cursor: not-allowed; }

.btn--primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}
.btn--primary:hover:not(:disabled) {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
  color: var(--accent-ink);
}

.btn--danger { color: var(--danger); }
.btn--danger:hover:not(:disabled) {
  background: var(--danger-soft);
  border-color: var(--danger);
  color: var(--danger);
}

.btn--ghost { background: transparent; border-color: transparent; }
.btn--ghost:hover:not(:disabled) { background: var(--surface-hover); border-color: transparent; }

/* Square, and the same height as everything beside it. */
.btn--icon {
  width: var(--control-h);
  padding: 0;
  background: transparent;
  border-color: transparent;
  color: var(--ink-muted);
}
.btn--icon:hover:not(:disabled) { background: var(--surface-hover); color: var(--ink); }
.btn--icon.btn--primary {
  color: var(--accent-ink);
  background: var(--accent);
  border-color: var(--accent);
}

/*
  A toggle whose panel is open.

  `syncToggles` in app.js has been setting this class on every toggle pointing
  at a panel since the terminal landed, and nothing has ever styled it -- so an
  open inspector and a closed one gave their topbar buttons an identical
  appearance, and the only signal was the panel itself, which is off-screen at
  narrow widths. Written against `.btn` rather than `.btn--icon` so anything
  that becomes a toggle later is covered.
*/
.btn.is-active {
  background: var(--accent-soft);
  border-color: transparent;
  color: var(--accent);
}
.btn.is-active:hover:not(:disabled) {
  background: var(--accent-soft);
  color: var(--accent-hover);
}

.btn--sm { height: var(--control-h-sm); padding: 0 var(--control-px-sm); font-size: var(--text-xs); }
.btn--sm.btn--icon { width: var(--control-h-sm); padding: 0; }
.btn--lg { height: var(--control-h-lg); padding: 0 var(--sp-5); font-size: var(--text-base); }

.btn--block { width: 100%; }
.btn--grow { flex: 1; }

/* Rows of buttons: gap and alignment in one place, not per instance. */
.btn-row { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.btn-row--end { justify-content: flex-end; }
.spacer { flex: 1; }

/* --- Forms -----------------------------------------------------------------
   Inputs share the button height, so a control row is flush by construction.
*/
.field { margin-bottom: var(--sp-4); }
.field:last-child { margin-bottom: 0; }

.field__label {
  display: block;
  margin-bottom: var(--sp-2);
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--ink);
}
.field__hint {
  margin-top: var(--sp-2);
  font-size: var(--text-xs);
  color: var(--ink-faint);
  line-height: var(--leading-normal);
}

.input,
.select {
  width: 100%;
  height: var(--control-h);
  padding: 0 var(--control-px);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-sunken);
  color: var(--ink);
  font-size: var(--text-sm);
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
.textarea {
  width: 100%;
  padding: var(--sp-2) var(--control-px);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-sunken);
  color: var(--ink);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
  resize: vertical;
  min-height: 5rem;
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.input:focus,
.textarea:focus,
.select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.input::placeholder, .textarea::placeholder { color: var(--ink-faint); }

/*
  File inputs.

  A file input is two things in one box -- a button the browser draws and the
  chosen filename beside it -- and neither inherits anything useful. Left alone
  with `.input`, the padding applies to the whole control so the button sits
  hard against the left edge while the text floats off its centre line.

  So: no horizontal padding on the control, the button styled to the same
  height as everything else and given the right border that separates it, and
  the filename centred with line-height rather than flexbox, which file inputs
  do not lay out reliably.
*/
.input[type="file"] {
  padding: 0 var(--control-px) 0 0;
  line-height: calc(var(--control-h) - 2px);
  cursor: pointer;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ink-muted);
}
.input[type="file"]::file-selector-button {
  height: calc(var(--control-h) - 2px);
  margin: 0 var(--sp-3) 0 0;
  padding: 0 var(--control-px);
  border: 0;
  border-right: 1px solid var(--border);
  background: var(--surface-hover);
  color: var(--ink);
  font: inherit;
  font-weight: 500;
  cursor: pointer;
  transition: background var(--transition-fast);
}
.input[type="file"]:hover::file-selector-button { background: var(--surface-active); }
.input--mono, .textarea--mono { font-family: var(--font-mono); font-size: var(--text-xs); }

.select {
  appearance: none;
  padding-right: var(--sp-8);
  /* Chevron drawn in CSS, so no icon font and no extra element. */
  background-image:
    linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: right 1.1rem center, right 0.85rem center;
  background-size: 0.3rem 0.3rem, 0.3rem 0.3rem;
  background-repeat: no-repeat;
  cursor: pointer;
}
/* Sits inside a composed control that draws its own frame (the model picker). */
.select--bare {
  border-color: transparent;
  background-color: transparent;
  width: auto;
  max-width: 14rem;
  padding-left: var(--sp-1);
}
.select--bare:focus { box-shadow: none; }

.checkbox {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--text-sm);
  cursor: pointer;
  line-height: var(--leading-normal);
}
.checkbox input {
  accent-color: var(--accent);
  width: 1rem;
  height: 1rem;
  flex: none;
  cursor: pointer;
}

/* Multi-column form layout, one definition. */
.grid { display: grid; gap: var(--sp-4); }
.grid--2 { grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); }
.grid--3 { grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); }

/* --- Alerts --------------------------------------------------------------- */
.alert {
  display: flex;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--border);
  border-left-width: 3px;
  border-radius: var(--radius);
  font-size: var(--text-sm);
  margin-bottom: var(--sp-4);
  background: var(--surface);
}
.alert--error { border-left-color: var(--danger); background: var(--danger-soft); color: var(--ink); }
.alert--success { border-left-color: var(--success); background: var(--success-soft); }
.alert--warning { border-left-color: var(--warning); background: var(--warning-soft); }
.alert__icon { color: var(--danger); flex: none; margin-top: 0.15rem; }

/* --- Badges --------------------------------------------------------------- */
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  padding: 0.1rem 0.45rem;
  border-radius: var(--radius-full);
  font-size: var(--text-xs);
  font-weight: 500;
  background: var(--surface-active);
  color: var(--ink-muted);
}
.badge--leaf { background: var(--leaf-soft); color: var(--leaf); }
.badge--success { background: var(--success-soft); color: var(--success); }
.badge--danger { background: var(--danger-soft); color: var(--danger); }
.badge--warning { background: var(--warning-soft); color: var(--warning); }

/* --- Application shell ----------------------------------------------------- */
.shell { display: flex; height: 100dvh; overflow: hidden; }

.sidebar {
  width: var(--sidebar-width);
  flex: none;
  display: flex;
  flex-direction: column;
  background: var(--bg-sunken);
  border-right: 1px solid var(--border);
  min-height: 0;
}
.sidebar[hidden] { display: none; }

.sidebar__header {
  display: flex;
  align-items: center;
  height: var(--header-height);
  padding: 0 var(--sp-3);
  flex: none;
}
.sidebar__brand {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  color: var(--ink);
  text-decoration: none;
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 600;
  letter-spacing: 0.01em;
  min-width: 0;
}
.sidebar__brand:hover { color: var(--ink); }
.sidebar__brand .brand-mark { width: 1.65rem; height: 1.65rem; flex: none; }
.sidebar__brand .brand-llm { color: var(--leaf); }

.sidebar__actions {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 0 var(--sp-3) var(--sp-3);
  flex: none;
}

.sidebar__scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  /* A flick past the end of the list stops there rather than chaining to
     whatever is behind it. The shell is `overflow: hidden`, so what chaining
     produced was not a scrolled page but a rubber-band into blank background --
     which reads as the sidebar having come loose from the layout. */
  overscroll-behavior: contain;
  padding: 0 var(--sp-2) var(--sp-3);
  scrollbar-width: thin;
  scrollbar-color: var(--border-strong) transparent;
}

.sidebar__footer {
  flex: none;
  padding: var(--sp-2);
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  /* Ends level with the composer on the other side of the sidebar edge. See
     `--footer-height`; `justify-content` keeps the rows at the bottom when the
     reader's permissions leave fewer of them than the token allows for. No top
     border: the nav above scrolls, and a drawn edge over scrolling content is
     a line the content stops dead at rather than passes under. */
  min-height: var(--footer-height);
  justify-content: flex-end;
}
.sidebar__tools { display: flex; align-items: center; gap: var(--sp-1); }

.main {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  background: var(--bg);
}

/* The inspector, mirroring the sidebar on the other side of .shell. Hidden by
   the `hidden` attribute, which the rule at the top of this file forces to win. */
.inspector {
  width: var(--inspector-width);
  flex: none;
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: var(--bg-sunken);
  border-left: 1px solid var(--border);
}

/*
  The header a side panel wears. The inspector and the terminal had this
  written out twice, identically, and they must stay identical: they sit side
  by side in the same slot and one being a pixel off reads as a rendering bug.
  Also the same height as .topbar, so the three line up across the shell.
*/
.panel-head {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  height: var(--header-height);
  flex: none;
  padding: 0 var(--sp-3);
  border-bottom: 1px solid var(--border);
}
.panel-head__title {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex: 1;
  min-width: 0;
  margin: 0;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--ink-muted);
}

.inspector__body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: var(--sp-4);
  scrollbar-width: thin;
  scrollbar-color: var(--border-strong) transparent;
}

.inspector__heading {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--text-sm);
  margin: var(--sp-5) 0 var(--sp-2);
}
.inspector__note {
  font-size: var(--text-xs);
  color: var(--ink-muted);
  line-height: var(--leading-normal);
  margin: 0 0 var(--sp-3);
  overflow-wrap: anywhere;
}

.inspector__facts {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--sp-1) var(--sp-3);
  font-size: var(--text-xs);
  margin: 0;
}
.inspector__facts dt { color: var(--ink-faint); }
.inspector__facts dd { margin: 0; overflow-wrap: anywhere; }

.inspector__json {
  margin: 0 0 var(--sp-3);
  padding: var(--sp-2);
  max-height: 22rem;
  overflow: auto;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  background: var(--code-bg);
  border: 1px solid var(--code-border);
  border-radius: var(--radius);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: var(--leading-normal);
  color: var(--ink-muted);
}

@media (max-width: 64rem) {
  .inspector {
    position: fixed;
    inset: 0 0 0 auto;
    z-index: var(--z-panel);
    box-shadow: var(--shadow-lg);
  }
}

/* The terminal panel: a fourth child of .shell, to the left of the inspector.
   Built beside it rather than in chat.css because the shell layout lives here,
   and the two are the same shape -- a fixed-width column that hides with the
   `hidden` attribute. */
.terminal {
  width: var(--terminal-width);
  min-width: var(--terminal-width-min);
  max-width: 80vw;
  flex: none;
  display: flex;
  flex-direction: column;
  min-height: 0;
  /* So the resize handle can sit on the edge. */
  position: relative;
  background: var(--bg-sunken);
  border-left: 1px solid var(--border);
}

/* The drag handle on a panel's left edge. Wider than it looks -- a one-pixel
   border is a target nobody can hit -- and it sits *outside* the panel's own
   padding so it never overlaps what is being resized. */
.panel-resize {
  position: absolute;
  top: 0;
  bottom: 0;
  left: -3px;
  width: 9px;
  z-index: var(--z-handle);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: col-resize;
  color: transparent;
  touch-action: none;
  transition: background var(--transition-fast), color var(--transition-fast);
}
.panel-resize:hover,
.panel-resize:focus-visible,
body.is-resizing .panel-resize {
  background: var(--accent-soft);
  color: var(--accent);
  outline: none;
}
/* While dragging, nothing else may take the pointer -- a text selection
   starting mid-drag makes the whole page flicker blue, and the iframe-shaped
   hazard is the terminal itself swallowing pointermove. */
body.is-resizing {
  cursor: col-resize;
  user-select: none;
}
body.is-resizing .terminal__screen,
body.is-resizing .canvas__body { pointer-events: none; }

@media (max-width: 64rem) {
  /* A full-height overlay has no edge to drag, and no room to spare. */
  .panel-resize { display: none; }
}

.terminal__where {
  font-weight: 400;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-faint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The element xterm renders into. It measures itself from this box, so it must
   have a size of its own -- min-height: 0 on a flex child is what stops the
   terminal growing the panel instead of scrolling inside it. */
.terminal__screen {
  flex: 1;
  min-height: 0;
  padding: var(--sp-2);
  background: var(--code-bg);
}
.terminal__screen .xterm { height: 100%; }

.terminal__status {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  border-top: 1px solid var(--border);
  font-size: var(--text-xs);
  color: var(--ink-faint);
  line-height: var(--leading-normal);
}
.terminal__status strong { color: var(--ink-muted); font-weight: 600; }
.terminal__message { flex: 1; min-width: 0; overflow-wrap: anywhere; }
/* What the last command was. Monospace and clipped: it is a command line, and
   one long enough to wrap would push the status bar into two rows. */
.terminal__last {
  flex: 0 1 auto;
  min-width: 0;
  max-width: 16rem;
  font-family: var(--font-mono);
  color: var(--ink-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.terminal__last:empty { display: none; }
.terminal__message--error { color: var(--danger); }

/* --- The canvas panel ------------------------------------------------------
   The same shape as the terminal beside it: a fixed-width column that hides
   with the `hidden` attribute, and shares .panel-head and .panel-resize. It
   sits nearest the conversation, being the widest and the one most likely to
   be read alongside it. */
.canvas {
  width: var(--canvas-width);
  min-width: var(--canvas-width-min);
  max-width: 80vw;
  flex: none;
  display: flex;
  flex-direction: column;
  min-height: 0;
  position: relative;
  background: var(--bg-sunken);
  border-left: 1px solid var(--border);
}
.canvas__inner {
  display: flex;
  flex-direction: column;
  min-height: 0;
  flex: 1;
}
/* Where the file came from, beside its name. Shrinks and truncates rather than
   pushing the close button off the end. */
.canvas__where {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ink-faint);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 400;
}

/* One row, always. It scrolls sideways rather than wrapping -- the same rule
   the composer's toolbar is built around, and for the same reason: a strip
   that wraps to three lines takes the file with it. */
/*
  A tab strip read as an editor's rather than as a row of pills.

  Three things do that and none of them is decoration. The strip has a sunken
  background so the tabs sit *on* something; the tabs square off at the bottom
  and meet it; and the active one takes the body's own background with its
  bottom border removed, so it joins the file below rather than floating above
  it. Without that last part a strip of equally-shaded pills says which tab is
  selected only by a slight tint, which is exactly the thing nobody can see in
  a theme they did not choose.
*/
.canvas__tabs {
  display: flex;
  flex: none;
  gap: 1px;
  padding: var(--sp-1) var(--sp-2) 0;
  overflow-x: auto;
  scrollbar-width: thin;
  background: var(--bg-sunken);
  border-bottom: 1px solid var(--border);
}
.canvas__tab {
  display: inline-flex;
  align-items: center;
  flex: none;
  max-width: 14rem;
  /* Square at the bottom: a tab is attached to what it opens. */
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  border: 1px solid transparent;
  border-bottom: 0;
  /* The strip's own bottom border is 1px; this covers it for the active tab
     without moving anything, so the row does not shift by a pixel on switch. */
  margin-bottom: -1px;
  padding-bottom: 1px;
  background: transparent;
  transition: background var(--transition-fast), color var(--transition-fast);
}
.canvas__tab:hover { background: var(--surface); }
.canvas__tab.is-active {
  background: var(--bg);
  border-color: var(--border);
}
.canvas__tab-open {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  min-width: 0;
  height: var(--control-h-sm);
  padding: 0 var(--sp-1) 0 var(--sp-2);
  border: 0;
  background: none;
  color: var(--ink-muted);
  font-size: var(--text-xs);
  cursor: pointer;
}
.canvas__tab.is-active .canvas__tab-open { color: var(--ink); }
.canvas__tab-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Unsaved. A dot rather than a colour alone, which nobody can see in a theme
   they did not choose. */
.canvas__tab-dot {
  flex: none;
  width: 6px;
  height: 6px;
  border-radius: var(--radius-full);
  background: var(--accent);
}
.canvas__tab-close {
  display: inline-flex;
  align-items: center;
  padding: 0 var(--sp-1);
  border: 0;
  background: none;
  color: var(--ink-faint);
  cursor: pointer;
}
.canvas__tab-close:hover { color: var(--ink); }

/* One row, and the path box is the only thing allowed to shrink -- the same
   arrangement the composer's toolbar is built around. */
.canvas__open {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex: none;
  padding: var(--sp-2) var(--sp-3);
  border-bottom: 1px solid var(--border);
}

.canvas__body {
  flex: 1;
  min-height: 0;
  overflow: auto;
  padding: var(--sp-3);
}
.canvas__doc { display: flex; flex-direction: column; gap: var(--sp-2); }
.canvas__actions { display: flex; align-items: center; gap: var(--sp-2); }
.canvas__hint, .canvas__empty, .canvas__note {
  color: var(--ink-faint);
  font-size: var(--text-xs);
}
.canvas__note { display: flex; align-items: center; gap: var(--sp-2); }
.canvas__code {
  margin: 0;
  padding: var(--sp-3);
  border-radius: var(--radius-sm);
  background: var(--code-bg);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: 1.6;
  white-space: pre;
  overflow-x: auto;
}
.canvas__form { display: flex; flex-direction: column; gap: var(--sp-2); }
/* No highlighting while typing, and the panel says so rather than pretending.
   A mirror behind this would be the composer's trick at two thousand lines,
   laying the buffer out twice on every keystroke. */
.canvas__editor {
  width: 100%;
  min-height: 24rem;
  padding: var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--code-bg);
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: 1.6;
  white-space: pre;
  overflow-wrap: normal;
  resize: vertical;
}
.canvas__conflict { display: flex; flex-direction: column; gap: var(--sp-3); }
.canvas__theirs summary { cursor: pointer; font-size: var(--text-xs); }

@media (max-width: 64rem) {
  .terminal,
  .canvas {
    position: fixed;
    inset: 0 0 0 auto;
    z-index: var(--z-panel);
    box-shadow: var(--shadow-lg);
  }
  .terminal { width: min(var(--terminal-width), 100vw); }
  .canvas { width: min(var(--canvas-width), 100vw); }
}

.topbar {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  height: var(--header-height);
  flex: none;
  padding: 0 var(--sp-4);
  border-bottom: 1px solid var(--border);
  background: var(--bg);
}
.topbar__title {
  font-family: var(--font-display);
  font-size: var(--text-md);
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
  flex: 1;
}
.topbar__actions { display: flex; align-items: center; gap: var(--sp-2); flex: none; }

/*
  Which machine an agent chat runs on, and where.

  Its own flex item between the title and the actions, and it shrinks before
  the title does: `flex: 0 1 auto` with `min-width: 0` means a long path gives
  up its characters first, which is right -- a truncated title is a chat you
  cannot identify, a truncated path is still a path you recognise. Below 64rem
  it goes entirely; the terminal button beside it already says which machine.
*/
.topbar__where {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  flex: 0 1 auto;
  min-width: 0;
  max-width: 24rem;
  padding: 0 var(--sp-2);
  height: var(--control-h-sm);
  border-radius: var(--radius-full);
  background: var(--surface-hover);
  color: var(--ink-muted);
  font-size: var(--text-xs);
  white-space: nowrap;
}
.topbar__where-name { flex: none; }
.topbar__where-dir {
  font-family: var(--font-mono);
  color: var(--ink-faint);
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

@media (max-width: 64rem) {
  .topbar__where { display: none; }
}

/* The model picker: an avatar and a select sharing one frame, so it reads as a
   single control rather than two things that happen to be adjacent. */
.model-select {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  height: var(--control-h);
  padding: 0 var(--sp-1) 0 var(--sp-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  cursor: pointer;
  transition: border-color var(--transition-fast);
}
.model-select:hover { border-color: var(--border-strong); }
.model-select:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.model-select__avatar { width: 1.4rem; height: 1.4rem; border-radius: var(--radius-sm); }

/* Collapsible settings panel, shared by chat settings and anything like it. */
.panel {
  flex: none;
  border-bottom: 1px solid var(--border);
  background: var(--bg-sunken);
  max-height: 60vh;
  overflow-y: auto;
}
.panel__inner {
  max-width: var(--thread-max-width);
  margin: 0 auto;
  padding: var(--sp-5);
}
.panel__note {
  color: var(--ink-muted);
  font-size: var(--text-sm);
  font-style: italic;
  margin-bottom: var(--sp-4);
}

/* --- Sidebar navigation ---------------------------------------------------- */
.nav-group { margin-bottom: var(--sp-4); }
.nav-group:last-child { margin-bottom: 0; }
/* The pinned group is rendered even with nothing in it, so the Chat/Agent
   switch has a target to swap out of band -- htmx says nothing when a target is
   missing. Empty, it must take no room. */
.nav-group--pinned:empty { display: none; margin: 0; }
.nav-group__label {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--sp-2) var(--sp-2) var(--sp-1);
  font-size: var(--text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--ink-faint);
}

.nav-item {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: var(--control-h);
  padding: 0 var(--sp-2);
  border-radius: var(--radius);
  color: var(--ink-muted);
  text-decoration: none;
  font-size: var(--text-sm);
  cursor: pointer;
  position: relative;
}
.nav-item:hover { background: var(--surface-hover); color: var(--ink); }
.nav-item.is-active {
  background: var(--surface-active);
  color: var(--ink);
  font-weight: 500;
}
.nav-item__label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1;
  min-width: 0;
}
.nav-item__avatar { width: 1.4rem; height: 1.4rem; border-radius: var(--radius-sm); flex: none; }
.nav-item--model .nav-item__label { font-weight: 500; }

/* Row actions stay hidden until hover or focus, so the list reads calmly while
   remaining keyboard reachable. */
.nav-item__actions {
  display: flex;
  align-items: center;
  gap: 0.1rem;
  flex: none;
  opacity: 0;
  transition: opacity var(--transition-fast);
}
.nav-item:hover .nav-item__actions,
.nav-item:focus-within .nav-item__actions { opacity: 1; }

.nav-empty {
  padding: var(--sp-2);
  margin: 0;
  font-size: var(--text-xs);
  color: var(--ink-faint);
  font-style: italic;
}

/* --- Auth screens --------------------------------------------------------- */
.auth {
  min-height: 100dvh;
  display: grid;
  place-items: center;
  padding: var(--sp-6);
  background:
    radial-gradient(ellipse 60% 50% at 50% 0%, var(--leaf-soft), transparent 70%),
    var(--bg);
}
.auth__card {
  width: 100%;
  max-width: 25rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  padding: var(--sp-8);
  box-shadow: var(--shadow-lg);
}
.auth__brand { display: grid; place-items: center; gap: var(--sp-3); margin-bottom: var(--sp-6); }
.auth__brand .brand-mark { width: 3.5rem; height: 3.5rem; }
.auth__title {
  font-size: var(--text-2xl);
  font-family: var(--font-display);
  text-align: center;
}
.auth__subtitle {
  text-align: center;
  color: var(--ink-muted);
  font-size: var(--text-sm);
  margin-top: var(--sp-2);
}
.auth__footer {
  margin-top: var(--sp-5);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--border);
  text-align: center;
  font-size: var(--text-sm);
  color: var(--ink-muted);
}

/* --- Empty states --------------------------------------------------------- */
.empty {
  flex: 1;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: var(--sp-3);
  padding: var(--sp-10);
  text-align: center;
}
.empty__mark { width: 4.5rem; height: 4.5rem; opacity: 0.85; }
.empty__title { font-size: var(--text-xl); font-family: var(--font-display); }
.empty__text {
  color: var(--ink-muted);
  font-size: var(--text-sm);
  max-width: 32rem;
  font-style: italic;
}

/* --- Utilities ------------------------------------------------------------ */
.stack > * + * { margin-top: var(--sp-4); }
.row { display: flex; align-items: center; gap: var(--sp-3); }
.row--between { justify-content: space-between; }
.muted { color: var(--ink-muted); }
.faint { color: var(--ink-faint); }
.text-sm { font-size: var(--text-sm); }
.text-xs { font-size: var(--text-xs); }
.mono { font-family: var(--font-mono); }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* --- Small screens -------------------------------------------------------- */
@media (max-width: 48rem) {
  .sidebar {
    position: fixed;
    inset: 0 auto 0 0;
    z-index: var(--z-panel);
    box-shadow: var(--shadow-lg);
  }
  /* Hiding it is the `hidden` attribute, forced to win at the top of this
     file. There used to be a `[data-collapsed="true"]` rule here that nothing
     ever set. */
}

/* --- Toasts ----------------------------------------------------------------
   Bottom-right, stacked, above dialogs' backdrop but out of the way of the
   composer.
*/
.toasts {
  position: fixed;
  right: var(--sp-4);
  bottom: var(--sp-4);
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  max-width: min(24rem, calc(100vw - var(--sp-8)));
  pointer-events: none;
}

.toast {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-3) var(--sp-3) var(--sp-4);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius);
  background: var(--surface-raised);
  box-shadow: var(--shadow-lg);
  font-size: var(--text-sm);
  pointer-events: auto;
  opacity: 0;
  transform: translateY(0.5rem);
  transition: opacity var(--transition), transform var(--transition);
}
.toast.is-in { opacity: 1; transform: none; }
.toast--success { border-left-color: var(--success); }
.toast--error { border-left-color: var(--danger); }
.toast--warning { border-left-color: var(--warning); }
.toast__text { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.toast__close {
  flex: none;
  border: 0;
  background: none;
  color: var(--ink-faint);
  cursor: pointer;
  font-size: var(--text-lg);
  line-height: 1;
  padding: 0 0.15rem;
}
.toast__close:hover { color: var(--ink); }

/* --- Dialogs ----------------------------------------------------------------
   <dialog> gives focus trapping, Escape and page inertness for free.
*/
.dialog {
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  background: var(--surface);
  color: var(--ink);
  padding: 0;
  max-width: min(28rem, calc(100vw - var(--sp-8)));
  width: 100%;
  box-shadow: var(--shadow-lg);
}
.dialog::backdrop { background: var(--scrim); backdrop-filter: blur(2px); }

.dialog__form { padding: var(--sp-5); display: flex; flex-direction: column; gap: var(--sp-3); }
.dialog__title { font-size: var(--text-lg); }
.dialog__message {
  margin: 0;
  color: var(--ink-muted);
  font-size: var(--text-sm);
  line-height: var(--leading-relaxed);
  overflow-wrap: anywhere;
}
.dialog__actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--sp-2);
  margin-top: var(--sp-2);
}

/* A destructive confirmation needs the weight of a filled button, not the
   outline treatment .btn--danger gives a row action. */
.btn--danger-solid {
  background: var(--danger);
  border-color: var(--danger);
  color: var(--ink-inverse);
}
.btn--danger-solid:hover:not(:disabled) {
  background: var(--danger-hover);
  border-color: var(--danger-hover);
  color: var(--ink-inverse);
}

/* --- Model picker ----------------------------------------------------------
   Built by hand because a <select> renders only text in an <option> -- no
   avatar, no description, no badges.
*/
.picker { position: relative; }

.picker__button {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  height: var(--control-h);
  max-width: 16rem;
  padding: 0 var(--sp-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  font-size: var(--text-sm);
  cursor: pointer;
  transition: border-color var(--transition-fast);
}
.picker__button:hover { border-color: var(--border-strong); }
.picker__button[aria-expanded="true"] {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.picker__avatar { width: 1.4rem; height: 1.4rem; border-radius: var(--radius-sm); flex: none; }
.picker__label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
  font-weight: 500;
}
.picker__chevron { flex: none; color: var(--ink-faint); }

.picker__menu {
  position: absolute;
  top: calc(100% + var(--sp-1));
  right: 0;
  z-index: var(--z-dropdown);
  width: min(24rem, calc(100vw - var(--sp-8)));
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface-raised);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}
/* The composer's attach menu opens upwards: it sits at the bottom of the
   window, so a menu dropping down would be off screen. */
.picker--up .picker__menu {
  top: auto;
  bottom: calc(100% + var(--sp-1));
  left: 0;
  right: auto;
}
.picker__menu--compact { width: min(18rem, calc(100vw - var(--sp-8))); padding: var(--sp-1); }

/* "What this chat can use": a list of switches rather than a list of actions.
   Wider than the compact menu because a skill's description has to fit, and
   scrollable because a library of sixty skills would otherwise run off the top
   of the window -- this menu opens upward. */
.picker__menu--scope {
  width: min(22rem, calc(100vw - var(--sp-8)));
  max-height: min(26rem, 60vh);
  overflow-y: auto;
  padding: var(--sp-1);
}
.picker__lede {
  margin: 0;
  padding: var(--sp-2) var(--sp-3);
  font-size: var(--text-xs);
  color: var(--ink-faint);
}
/* A label, not a button, so several can be set without the menu closing --
   ui.js closes on `[role="menuitem"]`, and these deliberately have none. */
.picker__option--toggle { align-items: center; }
.picker__option--toggle input { flex: none; margin: 0; }
.picker__option-note {
  font-size: var(--text-xs);
  color: var(--ink-faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* A dialog that holds a searchable list rather than a question. */
.dialog--wide { width: min(34rem, calc(100vw - var(--sp-6))); }
.dialog__note { margin: 0; font-size: var(--text-sm); color: var(--ink-muted); }
/* Where the directory browser currently stands. Sticky-feeling rather than
   sticky: it is above the list, so walking deeper never scrolls it away. */
.dialog__where {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin: 0;
  min-width: 0;
  padding: var(--sp-2) var(--sp-3);
  border-bottom: 1px solid var(--border);
  background: var(--bg-sunken);
  font-size: var(--text-xs);
  color: var(--ink-muted);
}
.dialog__where .mono { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* A row that is shown but cannot be chosen -- a file in a directory picker.
   Listed rather than hidden, because a directory of only files would otherwise
   look empty. */
.picker__option.is-inert { cursor: default; color: var(--ink-faint); }
.picker__option.is-inert:hover { background: none; }
.dialog__results {
  max-height: min(24rem, 50vh);
  overflow-y: auto;
  scrollbar-width: thin;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.dialog__results .picker__list { max-height: none; }

.picker__search { padding: var(--sp-2); border-bottom: 1px solid var(--border); }
/*
  Small controls, declared here because this is the file every page loads.

  `.select--sm` was used in three places and defined in none: chat.css had a
  copy, it was deleted as a duplicate, and the replacement never landed. A
  select with only `.select` takes `width: 100%` and `--control-h` -- so in a
  flex row every one of them asked for the whole width, all of them shrank
  together until each was a few characters wide, and each stood half a rem
  taller than the buttons beside it. That is what "the connection switch needs
  to be wider" was.

  `width: auto` is the important half: a small select should be as wide as its
  longest option, not as wide as it can get away with.
*/
.input--sm { height: var(--control-h-sm); font-size: var(--text-xs); }
.select--sm {
  height: var(--control-h-sm);
  padding: 0 var(--sp-6) 0 var(--control-px-sm);
  font-size: var(--text-xs);
  width: auto;
  max-width: 14rem;
  background-position: right 0.75rem center, right 0.5rem center;
}
.picker__list { max-height: 22rem; overflow-y: auto; scrollbar-width: thin; padding: var(--sp-1); }

.picker__option {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  width: 100%;
  padding: var(--sp-2);
  border: 0;
  border-radius: var(--radius);
  background: none;
  color: var(--ink);
  text-align: left;
  cursor: pointer;
}
.picker__option:hover, .picker__option:focus-visible { background: var(--surface-hover); }
.picker__option.is-selected { background: var(--accent-soft); }
.picker__option .picker__avatar { width: 1.75rem; height: 1.75rem; margin-top: 0.1rem; }
.picker__option-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.15rem; }
.picker__option-name {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  font-size: var(--text-sm);
  font-weight: 500;
}
.picker__pin { color: var(--leaf); }
.picker__option-desc {
  font-size: var(--text-xs);
  color: var(--ink-faint);
  line-height: var(--leading-normal);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.picker__option-tags { display: flex; flex-wrap: wrap; gap: var(--sp-1); }
.picker__option-tags:empty { display: none; }
.tag {
  font-size: 0.6875rem;
  padding: 0 0.35rem;
  border-radius: var(--radius-sm);
  background: var(--leaf-soft);
  color: var(--leaf);
}
.picker__tick { color: var(--accent); flex: none; margin-top: 0.35rem; }
.picker__empty {
  padding: var(--sp-4);
  margin: 0;
  text-align: center;
  font-size: var(--text-sm);
  color: var(--ink-faint);
}
