/* pokrok — thin orange outlines, no rounding, restrained colour. */

:root {
  --radius: 0;
  --border: 1px;

  --bg: #ffffff;
  --bg-sunken: #f6f5f3;
  --bg-raised: #ffffff;
  --bg-hover: #f0eeea;
  --fg: #141414;
  --fg-muted: #6a6a6a;
  --fg-faint: #9a9a9a;
  --line: #d9d5cf;
  --line-strong: #b4afa7;
  --accent: #c2410c;
  --accent-hover: #9a3412;
  --accent-bg: #fff4ec;
  --accent-fg: #ffffff;
  --danger: #b42318;
  --warn: #b45309;
  --ok: #15803d;

  --s1: 4px; --s2: 8px; --s3: 12px; --s4: 16px; --s5: 24px; --s6: 32px; --s7: 48px;
  --t-xs: 11px; --t-sm: 12.5px; --t-md: 14px; --t-lg: 16px; --t-xl: 20px; --t-2xl: 26px;
  --font: ui-sans-serif, system-ui, "Segoe UI", Helvetica, Arial, sans-serif;
  --mono: ui-monospace, "JetBrains Mono", "SF Mono", Menlo, Consolas, monospace;
  --rail: 208px;
  --list: 264px;
}

:root[data-theme="dark"] {
  --bg: #0d0d0d;
  --bg-sunken: #080808;
  --bg-raised: #151515;
  --bg-hover: #1e1e1e;
  --fg: #ededed;
  --fg-muted: #9c9c9c;
  --fg-faint: #6e6e6e;
  --line: #2c2c2c;
  --line-strong: #454545;
  --accent: #ff7a1a;
  --accent-hover: #ff9247;
  --accent-bg: #241505;
  --accent-fg: #0d0d0d;
  --danger: #f97066;
  --warn: #fdb022;
  --ok: #4ade80;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0d0d0d;
    --bg-sunken: #080808;
    --bg-raised: #151515;
    --bg-hover: #1e1e1e;
    --fg: #ededed;
    --fg-muted: #9c9c9c;
    --fg-faint: #6e6e6e;
    --line: #2c2c2c;
    --line-strong: #454545;
    --accent: #ff7a1a;
    --accent-hover: #ff9247;
    --accent-bg: #241505;
    --accent-fg: #0d0d0d;
    --danger: #f97066;
    --warn: #fdb022;
    --ok: #4ade80;
  }
}

*, *::before, *::after { box-sizing: border-box; border-radius: 0 !important; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font: var(--t-md)/1.5 var(--font);
  -webkit-font-smoothing: antialiased;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

h1, h2, h3, h4 { margin: 0; font-weight: 600; line-height: 1.25; }
h1 { font-size: var(--t-xl); }
h2 { font-size: var(--t-lg); }
h3 { font-size: var(--t-md); }
p { margin: 0 0 var(--s3); }

.boot { padding: var(--s7); color: var(--fg-faint); }

/* ---------------------------------------------------------------- shell */

/* Sized to what is visible, not to the layout viewport: an on-screen keyboard
   shortens the first and not the second, and the difference is a chat header
   scrolled off the top of the screen. --app-h is set from visualViewport;
   100dvh is the fallback where that is unavailable. */
.shell { display: flex; height: var(--app-h, 100dvh); overflow: hidden; }

.rail {
  width: var(--rail);
  flex: 0 0 var(--rail);
  border-right: var(--border) solid var(--line);
  background: var(--bg-sunken);
  display: flex;
  flex-direction: column;
  overflow-y: auto;
}

.brand {
  display: flex;
  align-items: baseline;
  gap: var(--s2);
  padding: var(--s4);
  border-bottom: var(--border) solid var(--line);
}
.brand b { font-size: var(--t-lg); letter-spacing: .02em; }
.brand span { font-size: var(--t-xs); color: var(--fg-faint); }

/* The workspace picker sits directly under the brand, because which instance
   you are looking at outranks anything you might do inside it. It is only ever
   rendered when there is more than one, so it never appears as a control with a
   single choice. */
.ws-row {
  padding: var(--s2) var(--s4);
  border-bottom: var(--border) solid var(--line);
}
.ws-pick {
  width: 100%;
  font-size: var(--t-sm);
  padding: 4px var(--s2);
  background: var(--bg);
  border: var(--border) solid var(--line);
  color: var(--fg);
}
.ws-pick:hover { border-color: var(--line-strong); }
.ws-pick:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.nav { padding: var(--s2) 0; flex: 1; }
.nav a {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s2) var(--s4);
  color: var(--fg-muted);
  border-left: 2px solid transparent;
  font-size: var(--t-md);
}
.nav a:hover { background: var(--bg-hover); color: var(--fg); text-decoration: none; }
.nav a.active { color: var(--accent); border-left-color: var(--accent); background: var(--accent-bg); }
.brand .mark {
  display: inline-flex; color: var(--accent); flex: none;
  /* The row is baseline-aligned, which sits a box on the text baseline rather
     than on its optical centre. Two pixels puts the mark level with the word. */
  position: relative; top: 2px;
}
.brand .mark svg { display: block; width: 17px; height: 17px; }

.nav a .badge { margin-left: auto; }
/* A fixed column for the icon, so the labels line up whatever the glyph's
   advance width happens to be — and so a drawn icon sits where a glyph would. */
.nav a .ic {
  display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px; flex: none; line-height: 1;
}
.nav a .ic svg { display: block; width: 15px; height: 15px; }

.rail-foot { border-top: var(--border) solid var(--line); padding: var(--s3) var(--s4); }
.rail-foot .who { font-size: var(--t-sm); color: var(--fg-muted); overflow: hidden; text-overflow: ellipsis; }
.rail-foot .row { display: flex; gap: var(--s2); margin-top: var(--s2); }

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

.view { flex: 1; overflow-y: auto; padding: var(--s5); }
.view.flush { padding: 0; }

.page-head {
  display: flex;
  align-items: flex-start;
  gap: var(--s4);
  margin-bottom: var(--s4);
  padding-bottom: var(--s3);
  border-bottom: var(--border) solid var(--line);
}
.page-head .grow { flex: 1; min-width: 0; }
.page-head p { margin: var(--s1) 0 0; color: var(--fg-muted); font-size: var(--t-sm); }

/* ---------------------------------------------------------------- controls */

/* An <a class="btn"> is a button that happens to navigate — a download link,
   an external raw view. Every modifier below therefore names both, or the
   anchor keeps the default size while the buttons beside it shrink. */
button, .btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font: inherit;
  font-size: var(--t-sm);
  padding: var(--s2) var(--s3);
  background: var(--bg);
  color: var(--fg);
  border: var(--border) solid var(--line-strong);
  cursor: pointer;
  white-space: nowrap;
}
button:hover, .btn:hover { border-color: var(--accent); color: var(--accent); text-decoration: none; }
button:disabled, .btn:disabled { opacity: .45; cursor: not-allowed; }
button:disabled:hover, .btn:disabled:hover { border-color: var(--line-strong); color: var(--fg); }

button.primary, .btn.primary {
  border-color: var(--accent);
  color: var(--accent);
  font-weight: 600;
}
button.primary:hover, .btn.primary:hover { background: var(--accent); color: var(--accent-fg); }
button.danger, .btn.danger { color: var(--danger); border-color: var(--danger); }
button.danger:hover, .btn.danger:hover { background: var(--danger); color: var(--bg); }
button.ghost, .btn.ghost { border-color: transparent; background: transparent; color: var(--fg-muted); }
button.ghost:hover, .btn.ghost:hover { border-color: var(--line); color: var(--accent); }
button.sm, .btn.sm { padding: 2px var(--s2); font-size: var(--t-xs); }
button.xs, .btn.xs { padding: 0 var(--s1); font-size: var(--t-xs); line-height: 1.7; }

input, select, textarea {
  font: inherit;
  font-size: var(--t-sm);
  padding: var(--s2);
  background: var(--bg);
  color: var(--fg);
  border: var(--border) solid var(--line);
  width: 100%;
}
input:focus, select:focus, textarea:focus { border-color: var(--accent); outline: none; }
textarea { resize: vertical; min-height: 72px; font-family: var(--font); }
textarea.mono { font-family: var(--mono); font-size: var(--t-sm); }
select { cursor: pointer; }
/* Radios were missing here, and nothing in the app had used one until a
   decision offered a choice. They inherited width: 100% from the rule above,
   so each button stretched across its row and squeezed its own label into a
   column one character wide. */
input[type="checkbox"], input[type="radio"] { width: auto; accent-color: var(--accent); }
input[type="range"] { width: 100%; accent-color: var(--accent); padding: 0; border: none; }
input[type="file"] { border: none; padding: 0; }

label { display: block; font-size: var(--t-xs); text-transform: uppercase; letter-spacing: .06em;
  color: var(--fg-muted); margin-bottom: var(--s1); }

.field { margin-bottom: var(--s4); }
.field .help { font-size: var(--t-xs); color: var(--fg-faint); margin-top: var(--s1); }
.field .err { font-size: var(--t-xs); color: var(--danger); margin-top: var(--s1); }
.field.bad input, .field.bad select, .field.bad textarea { border-color: var(--danger); }

.inline { display: flex; align-items: center; gap: var(--s2); }
.inline label { margin: 0; text-transform: none; letter-spacing: 0; font-size: var(--t-sm); color: var(--fg); }
/* A row shares its width between the things that can use it — form fields,
   mostly. Anything sized by its own content is exempt: stretching a badge
   across a whole row is never what was meant, and shrinking a button below
   its label spills the text out of the border, because buttons never wrap. */
.row { display: flex; gap: var(--s3); align-items: flex-start; }
.row > * { flex: 1; min-width: 0; }
.row > .badge,
.row > button,
.row > .btn,
.row > .wf-kind { flex: none; }
.spacer { flex: 1; }
.wrap { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2); }

/* An icon is chosen from a grid rather than invented in a text box. The glyphs
   are monochrome by design: an agent's icon sits beside its name everywhere,
   and a colour emoji would be the loudest thing on the screen. */
.icon-pick-wrap { display: flex; flex-direction: column; gap: var(--s2); }
.icon-pick-preview {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; flex: none;
  border: var(--border) solid var(--line-strong);
  font-size: var(--t-lg); color: var(--accent);
}
.icon-pick-custom { flex: 1; }
.icon-pick {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(34px, 1fr));
  gap: 2px; max-height: 190px; overflow-y: auto;
  border: var(--border) solid var(--line); padding: var(--s1);
  background: var(--bg-sunken);
}
.icon-pick button {
  padding: 0; height: 32px; font-size: var(--t-lg); line-height: 1;
  border-color: transparent; background: transparent; color: var(--fg-muted);
}
.icon-pick button:hover { border-color: var(--line-strong); color: var(--fg); }
.icon-pick button.on {
  border-color: var(--accent); color: var(--accent); background: var(--accent-bg);
}
.icon-pick button.none { font-size: var(--t-sm); }

/* ---------------------------------------------------------------- surfaces */

.card {
  border: var(--border) solid var(--line);
  background: var(--bg-raised);
  padding: var(--s4);
  margin-bottom: var(--s3);
}
.card.pad0 { padding: 0; }
.card-head { display: flex; align-items: center; gap: var(--s3); margin-bottom: var(--s3); }
.card-head h3 { flex: 1; min-width: 0; }

.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: var(--s3); }
/* A card whose footer carries a row of actions needs to be wide enough for all
   of them. 300px fitted four; the fifth wrapped onto a line of its own, which
   reads as an afterthought rather than a peer. */
.grid.actions { grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); }

.list-item {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s3);
  border-bottom: var(--border) solid var(--line);
  cursor: pointer;
}
.list-item:last-child { border-bottom: none; }
.list-item:hover { background: var(--bg-hover); }
.list-item.sel { border-left: 2px solid var(--accent); background: var(--accent-bg); }
.list-item .grow { flex: 1; min-width: 0; }
.list-item .title { font-size: var(--t-md); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.list-item .sub { font-size: var(--t-xs); color: var(--fg-faint); overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }

.empty {
  border: var(--border) dashed var(--line-strong);
  padding: var(--s6) var(--s4);
  text-align: center;
  color: var(--fg-muted);
}
.empty h3 { margin-bottom: var(--s2); }
.empty p { font-size: var(--t-sm); max-width: 46ch; margin: 0 auto var(--s3); }

table { width: 100%; border-collapse: collapse; font-size: var(--t-sm); }
th { text-align: left; font-size: var(--t-xs); text-transform: uppercase; letter-spacing: .06em;
  color: var(--fg-muted); font-weight: 400; padding: var(--s2); border-bottom: var(--border) solid var(--line); }
td { padding: var(--s2); border-bottom: var(--border) solid var(--line); vertical-align: top; }
tr:last-child td { border-bottom: none; }
.scroll-x { overflow-x: auto; }

.badge {
  display: inline-block;
  font-size: var(--t-xs);
  padding: 1px var(--s2);
  border: var(--border) solid var(--line-strong);
  color: var(--fg-muted);
  white-space: nowrap;
}
.badge.ok { color: var(--ok); border-color: var(--ok); }
.badge.err { color: var(--danger); border-color: var(--danger); }
.badge.warn { color: var(--warn); border-color: var(--warn); }
.badge.accent { color: var(--accent); border-color: var(--accent); }

.mono { font-family: var(--mono); font-size: var(--t-sm); }
.muted { color: var(--fg-muted); }
.faint { color: var(--fg-faint); }
.small { font-size: var(--t-xs); }
.nowrap { white-space: nowrap; }
.hidden { display: none !important; }

/* ---------------------------------------------------------------- chat */

.chat-layout { display: flex; height: 100%; overflow: hidden; }
.chat-list {
  width: var(--list);
  flex: 0 0 var(--list);
  border-right: var(--border) solid var(--line);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.chat-list .head { padding: var(--s3); border-bottom: var(--border) solid var(--line); display: flex; gap: var(--s2); }
.chat-list .items { overflow-y: auto; flex: 1; }

.chat-main { flex: 1; min-width: 0; display: flex; flex-direction: column; overflow: hidden; }
.chat-head {
  display: flex; align-items: center; gap: var(--s3);
  padding: var(--s3) var(--s4);
  border-bottom: var(--border) solid var(--line);
}
.chat-head .title { font-size: var(--t-md); font-weight: 600; }
.chat-head .sub { font-size: var(--t-xs); color: var(--fg-faint); }

.stream { flex: 1; overflow-y: auto; padding: var(--s5) var(--s4); }
.thread { max-width: 820px; margin: 0 auto; }

.msg { margin-bottom: var(--s5); }
.msg .who { font-size: var(--t-xs); text-transform: uppercase; letter-spacing: .06em;
  color: var(--fg-faint); margin-bottom: var(--s2); display: flex; gap: var(--s2); align-items: center; }
.msg .body { white-space: normal; overflow-wrap: anywhere; }
.msg.user .body {
  border-left: 2px solid var(--line-strong);
  padding-left: var(--s3);
  color: var(--fg);
  white-space: pre-wrap;
}
.msg.assistant .body { border-left: 2px solid transparent; padding-left: var(--s3); }
.msg.assistant.streaming .body { border-left-color: var(--accent); }
.msg .foot { margin-top: var(--s2); display: flex; gap: var(--s2); align-items: center;
  font-size: var(--t-xs); color: var(--fg-faint); opacity: 0; transition: opacity .12s; }
.msg:hover .foot { opacity: 1; }

.body pre {
  border: var(--border) solid var(--line);
  background: var(--bg-sunken);
  padding: var(--s3);
  overflow-x: auto;
  font-family: var(--mono);
  font-size: var(--t-sm);
  margin: 0 0 var(--s3);
}
.body code { font-family: var(--mono); font-size: .92em; background: var(--bg-sunken);
  padding: 0 3px; border: var(--border) solid var(--line); }
.body pre code { background: none; border: none; padding: 0; }
.body ul, .body ol { margin: 0 0 var(--s3); padding-left: var(--s5); }
.body li { margin-bottom: var(--s1); }
.body blockquote { margin: 0 0 var(--s3); padding-left: var(--s3);
  border-left: 2px solid var(--line-strong); color: var(--fg-muted); }
.body table { margin-bottom: var(--s3); }
.body h1, .body h2, .body h3 { margin: var(--s4) 0 var(--s2); }
.body hr { border: none; border-top: var(--border) solid var(--line); margin: var(--s4) 0; }

.tool {
  border: var(--border) solid var(--line);
  margin-bottom: var(--s3);
  font-size: var(--t-sm);
}
.tool > summary {
  padding: var(--s2) var(--s3);
  cursor: pointer;
  display: flex;
  gap: var(--s2);
  align-items: center;
  list-style: none;
  color: var(--fg-muted);
}
.tool > summary::-webkit-details-marker { display: none; }
.tool > summary:hover { background: var(--bg-hover); }
.tool.err { border-color: var(--danger); }
.tool.err > summary { color: var(--danger); }
.tool .dot { width: 6px; height: 6px; background: var(--fg-faint); flex: none; }
.tool.ok .dot { background: var(--ok); }
.tool.err .dot { background: var(--danger); }
.tool.run .dot { background: var(--accent); animation: pulse 1s infinite; }
@keyframes pulse { 50% { opacity: .25; } }
.tool .panes { border-top: var(--border) solid var(--line); padding: var(--s3); }
.tool pre { max-height: 340px; overflow: auto; margin: 0; white-space: pre-wrap;
  overflow-wrap: anywhere; font-family: var(--mono); font-size: var(--t-xs); }
.tool .pane-label { font-size: var(--t-xs); text-transform: uppercase; letter-spacing: .06em;
  color: var(--fg-faint); margin: var(--s2) 0 var(--s1); }

/* The model's reasoning, while it reasons. It is deliberately quiet — no
   border, faint text — because it is scaffolding, not the answer. What it must
   never be is silent: the clock ticks and the tail of the thought moves, so a
   two minute think reads as work rather than as a stall. */
.think { margin-bottom: var(--s2); font-size: var(--t-sm); }
.think > summary {
  display: flex; align-items: baseline; gap: 0; cursor: pointer; list-style: none;
  color: var(--fg-faint); padding: 2px 0;
}
.think > summary::-webkit-details-marker { display: none; }
.think > summary:hover { color: var(--fg-muted); }
.think .think-label { text-transform: uppercase; letter-spacing: .06em; font-size: var(--t-xs); }
.think[open] .think-label::after { content: ' ▾'; }
.think:not([open]) .think-label::after { content: ' ▸'; }
.think .think-clock { font-variant-numeric: tabular-nums; white-space: pre; }
/* The preview is one line and must never push the layout around. */
.think .think-preview {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-style: italic; opacity: .75;
}
.think[open] .think-preview { display: none; }
.think .think-body {
  margin-top: var(--s2); padding: var(--s2) var(--s3);
  border-left: 2px solid var(--line); color: var(--fg-muted);
  max-height: 300px; overflow: auto; white-space: pre-wrap; overflow-wrap: anywhere;
  font-family: var(--mono); font-size: var(--t-xs);
}
/* While the turn is still streaming the label pulses, matching a running tool. */
.msg.streaming .think .think-label { animation: pulse 1.4s infinite; color: var(--accent); }

.cite {
  display: inline-block; font-size: var(--t-xs); border: var(--border) solid var(--accent);
  color: var(--accent); padding: 0 4px; margin: 0 2px; cursor: pointer; line-height: 1.4;
}

.composer { border-top: var(--border) solid var(--line); padding: var(--s3) var(--s4); }
.composer .box { max-width: 820px; margin: 0 auto; display: flex; flex-direction: column; gap: var(--s2); }
.composer .box .row { display: flex; gap: var(--s2); align-items: flex-end; }
.composer textarea { min-height: 44px; max-height: 240px; }
.hidden-file { display: none; }

/* A file is over the conversation. The whole box outlines rather than an
   overlay appearing: an overlay covers what you were reading, and the point of
   the feedback is only "yes, let go here". */
.composer .box.dropping { outline: 2px dashed var(--accent); outline-offset: var(--s2); }

/* Icon buttons sit at the same height as Send and read as part of the field
   rather than as a toolbar above it. */
.icon-btn {
  flex: none;
  width: 34px; height: 34px; margin-bottom: 5px;
  display: inline-flex; align-items: center; justify-content: center;
  background: none; border: var(--border) solid var(--line);
  color: var(--fg-muted); cursor: pointer; padding: 0;
}
.icon-btn:hover { color: var(--accent); border-color: var(--accent); }
.icon-btn svg { display: block; }

/* ---------------------------------------------------------------- attachments */

.tray { display: flex; flex-wrap: wrap; gap: var(--s2); }
.tray.hidden { display: none; }

.chip {
  display: flex; align-items: flex-start; gap: var(--s2);
  border: var(--border) solid var(--line);
  background: var(--bg-sunken);
  padding: var(--s2);
  max-width: 300px; min-width: 0;
}
.chip.bad { border-color: var(--danger); }
.chip .thumb { width: 34px; height: 34px; object-fit: cover; flex: none; display: block; }
.chip .glyph { width: 34px; height: 34px; flex: none; display: flex;
  align-items: center; justify-content: center; color: var(--fg-faint); font-size: var(--t-lg); }
.chip .meta { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.chip .name { font-size: var(--t-sm); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chip .sub { font-size: var(--t-xs); color: var(--fg-faint); }
.chip .warn { font-size: var(--t-xs); color: var(--warn); white-space: normal; }
.chip.bad .sub { color: var(--danger); }
.chip .x {
  flex: none; background: none; border: 0; cursor: pointer; padding: 0 2px;
  color: var(--fg-faint); font-size: var(--t-lg); line-height: 1;
}
.chip .x:hover { color: var(--danger); }

/* What was actually sent, in the transcript. */
.atts { margin-top: var(--s2); padding-left: var(--s3); display: flex; flex-direction: column; gap: var(--s2); }
.msg.user .atts { border-left: 2px solid var(--line-strong); }
.att-img { margin: 0; }
.att-img img { max-width: min(420px, 100%); max-height: 340px; display: block;
  border: var(--border) solid var(--line); }
.att-file, .att-audio {
  display: flex; align-items: center; gap: var(--s3);
  border: var(--border) solid var(--line); padding: var(--s2) var(--s3);
  font-size: var(--t-sm);
}
.att-audio { flex-direction: column; align-items: stretch; gap: var(--s2); }
.att-audio .att-file { border: 0; padding: 0; }
.att-audio audio { width: 100%; height: 32px; }
.att-file .meta { min-width: 0; flex: 1; display: flex; flex-direction: column; }
.att-file .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.att-file .sub { font-size: var(--t-xs); color: var(--fg-faint); }
.att-file .glyph { color: var(--fg-faint); font-size: var(--t-lg); }

.att-text { font-size: var(--t-sm); }
.att-text summary { cursor: pointer; color: var(--fg-muted); font-size: var(--t-xs);
  text-transform: uppercase; letter-spacing: .06em; padding: var(--s1) 0; }
.att-text summary:hover { color: var(--accent); }
.att-text-body {
  white-space: pre-wrap; overflow-wrap: anywhere;
  border-left: 2px solid var(--line); padding: var(--s2) 0 var(--s2) var(--s3);
  color: var(--fg-muted); max-height: 320px; overflow-y: auto;
}
.composer .hint { max-width: 820px; margin: var(--s1) auto 0; font-size: var(--t-xs); color: var(--fg-faint);
  display: flex; gap: var(--s3); }

/* The default-model radio: a narrow column that does not compete with the
   model name beside it. */
th.pick, td.pick { width: 1%; white-space: nowrap; text-align: center; }
tr.is-default > td:first-child { box-shadow: inset 2px 0 0 var(--accent); }
td.pick input[type="radio"] { width: auto; margin: 0; cursor: pointer; }
td.pick input[type="radio"]:disabled { cursor: not-allowed; opacity: .4; }

.notice {
  border: var(--border) solid var(--warn);
  color: var(--warn);
  padding: var(--s2) var(--s3);
  font-size: var(--t-sm);
  margin-bottom: var(--s3);
}
.notice.err { border-color: var(--danger); color: var(--danger); }
.notice.info { border-color: var(--accent); color: var(--accent); }

/* ---------------------------------------------------------------- drawer, modal, toast */

.drawer {
  position: fixed; top: 0; right: 0; bottom: 0; width: 420px; max-width: 92vw;
  background: var(--bg-raised); border-left: var(--border) solid var(--accent);
  display: flex; flex-direction: column; z-index: 40;
}
.drawer .head { padding: var(--s3) var(--s4); border-bottom: var(--border) solid var(--line);
  display: flex; align-items: center; gap: var(--s3); }
.drawer .content { flex: 1; overflow-y: auto; padding: var(--s4); }

.modal-back {
  position: fixed; inset: 0; background: rgba(0,0,0,.55);
  display: flex; align-items: flex-start; justify-content: center; z-index: 50; overflow-y: auto;
  padding: var(--s6) var(--s4);
}
.modal {
  background: var(--bg-raised); border: var(--border) solid var(--accent);
  width: 100%; max-width: 640px;
}
.modal.wide { max-width: 940px; }
.modal .head { padding: var(--s3) var(--s4); border-bottom: var(--border) solid var(--line);
  display: flex; align-items: center; gap: var(--s3); }
.modal .head h2 { flex: 1; }
/* Sized against --app-h, not vh. The on-screen keyboard does not shrink a
   viewport unit, so a dialog with a text field in it grew taller than the space
   left above the keyboard and pushed its own buttons off the screen — the same
   failure --app-h was introduced to fix for the shell. */
.modal .content { padding: var(--s4); max-height: calc(var(--app-h, 100dvh) * 0.6); overflow-y: auto; }
.modal .foot { padding: var(--s3) var(--s4); border-top: var(--border) solid var(--line);
  display: flex; gap: var(--s2); justify-content: flex-end; align-items: center; }
/* .grow is defined per parent elsewhere; a dialog that wants a line of
   explanation beside its buttons needs its own. */
.modal .foot .grow { flex: 1; min-width: 0; text-align: left; }

.toasts { position: fixed; right: var(--s4); bottom: var(--s4); z-index: 60;
  display: flex; flex-direction: column; gap: var(--s2); max-width: 420px; }
.toast { border: var(--border) solid var(--accent); background: var(--bg-raised);
  padding: var(--s2) var(--s3); font-size: var(--t-sm); display: flex; gap: var(--s2); align-items: flex-start; }
.toast.err { border-color: var(--danger); }
.toast.ok { border-color: var(--ok); }
.toast .grow { flex: 1; }

/* ---------------------------------------------------------------- auth */

.auth { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: var(--s4); }
/* The first-run wizard asks for more than a password does, and squeezing a
   base URL and a model filter into a sign-in box makes both hard to read. */
.auth .panel.wide { max-width: 560px; }
.auth .panel { width: 100%; max-width: 380px; border: var(--border) solid var(--line); padding: var(--s5);
  background: var(--bg-raised); }
.auth h1 { margin-bottom: var(--s2); }
.auth p.lead { color: var(--fg-muted); font-size: var(--t-sm); margin-bottom: var(--s4); }
.auth .sep { display: flex; align-items: center; gap: var(--s2); margin: var(--s4) 0;
  color: var(--fg-faint); font-size: var(--t-xs); }
.auth .sep::before, .auth .sep::after { content: ""; flex: 1; height: 1px; background: var(--line); }

/* ---------------------------------------------------------------- misc */

.toolpick { border: var(--border) solid var(--line); max-height: 420px; overflow-y: auto; }
.toolpick .group { border-bottom: var(--border) solid var(--line); }
.toolpick .group:last-child { border-bottom: none; }
.toolpick .group > .ghead { display: flex; align-items: center; gap: var(--s2); padding: var(--s2) var(--s3);
  background: var(--bg-sunken); cursor: pointer; }
.toolpick .group > .ghead .name { flex: 1; font-size: var(--t-sm); }
.toolpick .child { padding: var(--s1) var(--s3) var(--s1) var(--s6); font-size: var(--t-sm);
  display: flex; gap: var(--s2); align-items: flex-start; }
.toolpick .child .d { color: var(--fg-faint); font-size: var(--t-xs); }

.stat { display: flex; gap: var(--s5); margin-bottom: var(--s4); flex-wrap: wrap; }
.stat > div { min-width: 90px; }
.stat .k { font-size: var(--t-xs); color: var(--fg-faint); text-transform: uppercase; letter-spacing: .06em; }
.stat .v { font-size: var(--t-xl); }

.drop { border: var(--border) dashed var(--line-strong); padding: var(--s5); text-align: center;
  color: var(--fg-muted); font-size: var(--t-sm); cursor: pointer; }
.drop.over { border-color: var(--accent); color: var(--accent); background: var(--accent-bg); }

.bar { height: 4px; background: var(--bg-hover); border: var(--border) solid var(--line); }
.bar > i { display: block; height: 100%; background: var(--accent); }

.wiz-steps { display: flex; gap: var(--s2); margin-bottom: var(--s4); font-size: var(--t-xs); }
.wiz-steps span { padding: 2px var(--s2); border: var(--border) solid var(--line); color: var(--fg-faint); }
.wiz-steps span.on { border-color: var(--accent); color: var(--accent); }
.wiz-steps span.done { color: var(--fg-muted); }

.kv { display: grid; grid-template-columns: auto 1fr; gap: var(--s2) var(--s3); font-size: var(--t-sm); }
.kv dt { color: var(--fg-muted); }
.kv dd { margin: 0; overflow-wrap: anywhere; }

/* ---------------------------------------------------------------- environments */

/* The two panes are the same height, always.
   The tree used to grow with its contents, so opening a file next to a short
   folder left an editor hanging below a box that stopped a third of the way
   down — and walking into a long folder moved everything on the page. A fixed
   height means the browser looks the same whichever folder is open, and the
   editor beside it ends where the tree does. */
.env-files {
  display: grid; grid-template-columns: 280px 1fr; gap: var(--s3);
  align-items: stretch;
  height: var(--env-pane, 420px);
}
.env-tree { border: var(--border) solid var(--line); overflow: auto; }
.env-view { min-width: 0; display: flex; flex-direction: column; overflow: hidden; }
/* The editor takes whatever is left after its own header, so its bottom edge
   is the tree's bottom edge rather than wherever the text happened to end. */
.env-view textarea { flex: 1; min-height: 0; resize: none; }

.tree-row { display: flex; align-items: center; gap: var(--s2); padding: 3px var(--s2);
  font-size: var(--t-sm); cursor: pointer; font-family: var(--mono); }
.tree-row:hover { background: var(--bg-hover); color: var(--accent); }
/* The name takes the slack and the size sits in a column of its own, so every
   delete button lands on the same line down the right-hand edge instead of
   following the width of whatever is beside it. */
.tree-row .name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tree-row .size { flex: 0 0 auto; }
.tree-row .ghost.xs { flex: 0 0 auto; margin-left: auto; }
/* Reserved even when there is no button, so a read-only row lines up with the
   rows above it rather than reaching further right. */
.env-tree .tree-row { padding-right: var(--s2); }

/* Dropping files anywhere on the card. The whole card is the target because
   somebody dragging a file at a page is aiming at the thing on it, and a drop
   zone you have to find is one you will miss. */
.env-card { position: relative; }
.env-drop {
  position: absolute; inset: 0; z-index: 3;
  display: none; align-items: center; justify-content: center;
  /* Translucent on purpose: the point is to say where the file will land, and
     a panel that blanks the card hides the folder you are aiming at. */
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border: 2px dashed var(--accent);
  /* The overlay must not swallow the events that keep it up. */
  pointer-events: none;
}
.env-card.dropping .env-drop { display: flex; }
.env-drop-label {
  font-size: var(--t-md); color: var(--accent); font-weight: 600;
  padding: var(--s2) var(--s4);
  /* Its own backing, so the label stays readable over a list of filenames. */
  background: var(--bg);
  border: var(--border) solid var(--accent);
}

.term-out { border: var(--border) solid var(--line); background: var(--bg-sunken);
  height: 320px; overflow: auto; padding: var(--s3); margin: 0 0 var(--s2);
  font-family: var(--mono); font-size: var(--t-xs); white-space: pre-wrap; overflow-wrap: anywhere; }
.term-cmd { color: var(--accent); }
.term-err { color: var(--danger); }
.term-exit { color: var(--fg-faint); border-top: var(--border) solid var(--line);
  margin: var(--s2) 0; padding-top: 2px; }
.term-exit.bad { color: var(--danger); }
.term-in { font-family: var(--mono); }

@media (max-width: 900px) {
  /* One column, and no fixed height: stacked, a pane pinned to 420px would
     leave the editor a slot the size of a paragraph. */
  .env-files { grid-template-columns: 1fr; height: auto; }
  .env-tree { max-height: 260px; }
  .env-view textarea { min-height: 280px; }
}

/* ---------------------------------------------------------------- tracing */

.trace-filters { display: flex; gap: var(--s2); margin-bottom: var(--s3); }
.trace-filters input { flex: 1; }

.trace-table td { vertical-align: middle; }
.trace-row:hover { background: var(--bg-hover); }
.trace-row.fail td:first-child { border-left: 2px solid var(--danger); }

.trace-split { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s3); align-items: start; }
.trace-pane { min-width: 0; margin-bottom: 0; }
.trace-raw { max-height: 70vh; overflow: auto; white-space: pre-wrap;
  overflow-wrap: anywhere; font-size: var(--t-xs); margin: 0; }

.trace-turn { border-top: var(--border) solid var(--line); padding: var(--s3) 0; }
.trace-turn:first-of-type { border-top: none; }
.trace-role { font-size: var(--t-xs); text-transform: uppercase; letter-spacing: .06em;
  color: var(--fg-faint); margin-bottom: var(--s2); }
.trace-turn pre { max-height: 300px; overflow: auto; }

@media (max-width: 1100px) { .trace-split { grid-template-columns: 1fr; } }

@media (max-width: 860px) {
  .rail { --rail: 60px; width: 60px; flex-basis: 60px; }
  .rail .brand span, .rail .nav a span.lbl, .rail-foot .who { display: none; }
  .chat-list { --list: 200px; width: 200px; flex-basis: 200px; }
}

/* ---------------------------------------------------------------- workflows */

/* The editor is a split view: a sequential lane on the left, the selected
   step's configuration on the right. Nothing here is a free-form canvas —
   position carries no meaning beyond order, so every wire runs downward. */

.wf-editor { display: flex; flex-direction: column; height: 100%; min-height: 0; }

.wf-bar {
  display: flex; align-items: center; gap: var(--s3);
  padding: var(--s3) var(--s4);
  border-bottom: var(--border) solid var(--line);
  flex: none;
}
.wf-bar .wf-name {
  flex: 1; min-width: 0; max-width: 340px;
  font-size: var(--t-lg); font-weight: 600;
  border-color: transparent; background: transparent; padding-left: var(--s2);
}
.wf-bar .wf-name:hover { border-color: var(--line); }
.wf-bar .wf-stats { display: flex; gap: var(--s3); flex: 1; }
.wf-bar .row { flex: none; gap: var(--s2); }
.wf-bar .row > * { flex: none; }

.wf-body { display: flex; flex: 1; min-height: 0; }

.wf-steps {
  flex: 0 0 420px; min-width: 0; position: relative;
  border-right: var(--border) solid var(--line);
  overflow: auto; padding: var(--s3) 0 var(--s6);
  background: var(--bg-sunken);
}
.wf-wires { position: absolute; inset: 0; pointer-events: none; overflow: visible; }
.wf-wire { fill: none; stroke: var(--line-strong); stroke-width: 1.5; }
.wf-wire.sel { stroke: var(--accent); }
.wf-wire.ghost { stroke: var(--accent); stroke-dasharray: 4 3; }
.wf-wire.err { stroke: var(--danger); }
.wf-node { fill: var(--bg-sunken); stroke: var(--line-strong); stroke-width: 1.5; }
.wf-node.sel { stroke: var(--accent); }

.wf-rows { position: relative; }
.wf-step { display: grid; grid-template-columns: 88px 1fr; align-items: start; }
.wf-rail { min-height: 8px; }

.wf-card {
  border: var(--border) solid var(--line);
  background: var(--bg-raised);
  padding: var(--s2) var(--s3);
  margin-right: var(--s3);
  cursor: pointer;
  border-left: 2px solid transparent;
}
.wf-card:hover { background: var(--bg-hover); }
.wf-card.sel { border-left-color: var(--accent); background: var(--accent-bg); }
.wf-card.off { opacity: 0.55; }
.wf-card-head { display: flex; align-items: center; gap: var(--s2); }
.wf-card-head .wf-name-txt {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-weight: 600;
}
.wf-idx { font-size: var(--t-xs); color: var(--fg-faint); min-width: 12px; }
.wf-card-sub {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-top: 2px;
}
.wf-menu { flex: none; opacity: 0; }
.wf-card:hover .wf-menu, .wf-card.sel .wf-menu { opacity: 1; }

.wf-dot { width: 7px; height: 7px; flex: none; background: var(--fg-faint); }
.wf-dot.ok { background: var(--ok); }
.wf-dot.err { background: var(--danger); }
.wf-dot.warn { background: var(--warn); }
.wf-dot.skip { background: transparent; border: var(--border) solid var(--line-strong); }
.wf-dot.run { background: var(--accent); animation: pulse 1s infinite; }

.wf-chips { display: flex; flex-wrap: wrap; gap: var(--s1); margin-top: var(--s2); }
.wf-chip {
  display: inline-flex; align-items: center; gap: var(--s1);
  border: var(--border) solid var(--line-strong);
  padding: 1px var(--s2); font-size: var(--t-xs);
  cursor: grab; touch-action: none; user-select: none;
  background: var(--bg);
}
.wf-chip:hover, .wf-chip:focus { border-color: var(--accent); color: var(--accent); outline: none; }
.wf-chip.has { border-color: var(--ok); }
.wf-chip .t { color: var(--fg-faint); }
.wf-chip.reused { border-style: dashed; }

.wf-steps.wiring .wf-card.ok-target { outline: 2px solid var(--accent); outline-offset: -2px; }
.wf-steps.wiring .wf-card.no-target { opacity: 0.4; }

.wf-gap { height: 10px; margin-left: 88px; position: relative; }
.wf-gap::before {
  content: ''; position: absolute; left: 0; right: var(--s3); top: 50%;
  border-top: var(--border) dashed transparent;
}
.wf-gap:hover::before { border-top-color: var(--line-strong); }
.wf-gap-btn {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  padding: 0 6px; font-size: var(--t-xs); line-height: 16px;
  opacity: 0; background: var(--bg); border: var(--border) solid var(--line-strong);
}
.wf-gap:hover .wf-gap-btn { opacity: 1; }
.wf-add { margin: var(--s3) var(--s3) 0 88px; }
.wf-add button { width: 100%; border-style: dashed; }

.wf-progress { display: flex; align-items: center; gap: var(--s2); margin-top: var(--s2); }
.wf-progress .bar { flex: 1; }

.wf-menu-pop {
  position: absolute; right: var(--s3); z-index: 20;
  background: var(--bg-raised); border: var(--border) solid var(--line-strong);
  display: flex; flex-direction: column; min-width: 160px;
}
.wf-menu-pop button { text-align: left; border: none; padding: var(--s2) var(--s3); }
.wf-menu-pop button:hover { background: var(--bg-hover); color: var(--accent); }

.wf-config { flex: 1; min-width: 0; overflow: auto; padding: var(--s4); }
.wf-config > * { max-width: 760px; }
.wf-section {
  border: var(--border) solid var(--line);
  padding: var(--s4); margin-bottom: var(--s4); background: var(--bg-raised);
}
.wf-section > h3, .wf-section > h4 { margin: 0 0 var(--s3); }
.wf-section-head { display: flex; align-items: center; gap: var(--s3); margin-bottom: var(--s2); }
.wf-section-head h3, .wf-section-head h4 { margin: 0; flex: 1; }
details.wf-section > summary {
  cursor: pointer; font-weight: 600; color: var(--fg-muted);
}
details.wf-section[open] > summary { margin-bottom: var(--s3); }
.wf-sub { margin: var(--s4) 0 var(--s2); color: var(--fg-muted); }

.wf-slots { display: flex; flex-direction: column; gap: var(--s2); margin-bottom: var(--s4); }
.wf-slot {
  border: var(--border) dashed var(--line-strong);
  padding: var(--s2) var(--s3);
}
.wf-slot:focus { outline: 2px solid var(--accent); outline-offset: -2px; }
.wf-slot.filled { border-style: solid; border-color: var(--line); }
.wf-slot.need { border-color: var(--danger); }
.wf-slot .row { align-items: center; }
.wf-slot .row > * { flex: none; }
.wf-slot .row > b { flex: 1; }
.wf-bound { margin-top: var(--s2); }
.wf-bound-row { align-items: center; border-top: var(--border) solid var(--line); padding-top: var(--s1); }
.wf-bound-row > span { flex: 1; }
.wf-bound-row > button { flex: none; }
.wf-kind {
  font-size: var(--t-xs); color: var(--fg-faint);
  border: var(--border) solid var(--line); padding: 0 var(--s1);
}

.wf-outs { display: flex; flex-direction: column; gap: var(--s2); }
.wf-outrow { border: var(--border) solid var(--line); padding: var(--s2) var(--s3); }
.wf-outrow .row { align-items: center; }
.wf-outrow .row > * { flex: none; }
.wf-outrow .row > b { flex: 1; }
.wf-outedit { border: var(--border) solid var(--line); padding: var(--s3); }
.wf-outedit .row > input { flex: 1; }
.wf-outedit .row > select { flex: 0 0 120px; }
.wf-outedit .row > button { flex: none; }
.wf-outedit .field { margin: var(--s2) 0 0; }
.wf-preview {
  margin-top: var(--s1); max-height: 6em; overflow: hidden;
  white-space: pre-wrap; word-break: break-word;
}
.wf-log, .wf-contract, .wf-artifact {
  background: var(--bg-sunken); border: var(--border) solid var(--line);
  padding: var(--s3); max-height: 340px; overflow: auto;
  white-space: pre-wrap; word-break: break-word; margin: var(--s2) 0 0;
}
.wf-artifact { max-height: 60vh; }
.wf-empty { padding: var(--s6) 0; }

.wf-pick-list { max-height: 62vh; overflow: auto; margin-top: var(--s3); }
.wf-pick-group h4 { margin: var(--s4) 0 var(--s2); color: var(--fg-muted); }
.wf-pick-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: var(--s2); }
.wf-pick {
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  text-align: left; padding: var(--s2) var(--s3); height: 100%;
  border: var(--border) solid var(--line); background: var(--bg-raised);
  /* These are buttons, and buttons are nowrap so a toolbar never breaks mid
     label. A step card is a paragraph, not a label: without this every
     description ran off the edge of its box, clipped mid-sentence. */
  white-space: normal;
}
/* A long unbroken token — a step id, a URL in a description — must break rather
   than push the card wider than its grid track. */
.wf-pick > * { max-width: 100%; overflow-wrap: anywhere; }
/* Grid tracks size to their content by default, so one wide card would stretch
   the whole column. */
.wf-pick-grid > * { min-width: 0; }
.wf-pick:hover { border-color: var(--accent); background: var(--accent-bg); }
.wf-pick .wf-cost { color: var(--accent); }

textarea.code { font-family: var(--mono); font-size: var(--t-sm); min-height: 90px; max-height: 420px; }

/* A conversation title you can click. Without a hint it looked like plain text,
   so the rename that was already there went unnoticed. */
.chat-head .title.editable { cursor: text; }
.chat-head .title.editable:hover { text-decoration: underline dotted var(--line-strong); }
.chat-head .title.editable:focus { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Row actions stay out of the way until the row is under the pointer. */
.chat-list .list-item { position: relative; }
.chat-list .list-item .row-act { opacity: 0; flex: none; }
.chat-list .list-item:hover .row-act,
.chat-list .list-item .row-act:focus-visible { opacity: 1; }

/* Choosing who answers a new conversation. */
.agent-pick { display: flex; flex-direction: column; gap: var(--s2); max-height: 60vh; overflow: auto; }
.agent-pick-row {
  display: flex; align-items: flex-start; gap: var(--s3); width: 100%;
  /* Buttons centre their content, which left every agent without an icon
     hanging in the middle of its row and out of line with the rest. */
  justify-content: flex-start;
  text-align: left; white-space: normal; padding: var(--s3);
  border: var(--border) solid var(--line); background: var(--bg-raised);
}
.agent-pick-row:hover { border-color: var(--accent); background: var(--accent-bg); }
.agent-pick-row .grow { display: flex; flex: 1; flex-direction: column; gap: 1px; min-width: 0; }
.agent-pick-row .grow > * { overflow-wrap: anywhere; }
.agent-pick-icon { font-size: var(--t-lg); line-height: 1.2; flex: none; width: 20px; text-align: center; }

.err-text { color: var(--danger); }
.warn-text { color: var(--warn); }

@media (max-width: 1000px) {
  .wf-body { flex-direction: column; }
  .wf-steps { flex: 0 0 auto; max-height: 46vh; border-right: none;
    border-bottom: var(--border) solid var(--line); }
}

/* ---------------------------------------------------------------- phones

   Two layouts, chosen by orientation rather than by width alone, because a
   phone's two orientations want opposite things.

   Portrait has width to spare in neither direction but plenty of height: the
   rail becomes a row of thumb-sized targets pinned to the bottom, and any view
   that is two panes on a desktop becomes one pane with a way back. Landscape
   is the reverse — 800-odd pixels across and barely 400 down — so the rail
   stays, shrunk to its icons, and every horizontal rule of chrome is squeezed
   to give the content the vertical room it is short of.

   Neither block is reachable on a desktop: one requires a portrait viewport
   under 760px, the other a viewport under 520px tall. */

.tabbar { display: none; }

@media (max-width: 760px) and (orientation: portrait) {
  .rail { display: none; }

  /* The bar is fixed rather than a flex row of the shell so that it survives
     the on-screen keyboard, which shortens the viewport without reflowing a
     fixed element out of reach. */
  .tabbar {
    display: flex;
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
    background: var(--bg-sunken);
    border-top: var(--border) solid var(--line);
    padding-bottom: env(safe-area-inset-bottom);
  }
  .tabbar a, .tabbar button {
    flex: 1 1 0; min-width: 0; min-height: 54px;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 3px; padding: var(--s2) 2px;
    background: none; border: none; border-radius: 0;
    color: var(--fg-muted); font-size: 10.5px; letter-spacing: .01em;
  }
  .tabbar a:hover, .tabbar button:hover { text-decoration: none; background: var(--bg-hover); }
  .tabbar .active { color: var(--accent); background: var(--accent-bg); }
  .tabbar .ic { display: inline-flex; align-items: center; justify-content: center;
    width: 22px; height: 22px; font-size: 17px; line-height: 1; }
  .tabbar .ic svg { display: block; width: 20px; height: 20px; }
  .tabbar .lbl { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }

  /* Content clears the bar. Views that manage their own scrolling get the
     allowance on the scrolling element instead, further down. */
  .view { padding: var(--s4) var(--s4) calc(54px + var(--s5) + env(safe-area-inset-bottom)); }
  .view.flush { padding: 0; }

  /* A page heading and its action stop competing for one line. */
  .page-head { flex-wrap: wrap; gap: var(--s2) var(--s3); }
  .page-head h1 { font-size: var(--t-xl); }
  .page-head .grow { flex: 1 1 100%; }

  /* Anything meant to be tapped is at least a finger wide. */
  button, .btn, select, input, textarea { min-height: 40px; }
  button.sm, .btn.sm { min-height: 36px; }

  /* Two panes become one. Which one is showing follows the address: the list
     until something in it is opened, that thing afterwards. */
  .chat-layout .chat-list {
    width: 100%; flex-basis: auto; border-right: none;
  }
  .chat-layout .chat-list .items { padding-bottom: calc(54px + env(safe-area-inset-bottom)); }
  .chat-layout .chat-main { display: none; }
  :root[data-detail] .chat-layout .chat-list { display: none; }
  :root[data-detail] .chat-layout .chat-main { display: flex; }

  /* Inside a conversation the bar gives way: the composer wants that edge, and
     the way out is the back control in the header. */
  :root[data-tab="chats"][data-detail] .tabbar { display: none; }

  .chat-head { flex-wrap: wrap; gap: var(--s2); padding: var(--s2) var(--s3); }
  .chat-head .title {
    font-size: var(--t-md); min-width: 0;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
    overflow: hidden;
  }
  .chat-head .chat-back { display: inline-flex; }
  .chat-head .usage { display: none; }

  .composer { padding: var(--s2) var(--s3) calc(var(--s2) + env(safe-area-inset-bottom)); }
  .composer .box { max-width: none; }
  .composer .hint .enter-hint { display: none; }

  /* Tables are the one thing that cannot be made narrow honestly, so they
     scroll inside their own box rather than stretching the page. */
  .scroll-x { overflow-x: auto; -webkit-overflow-scrolling: touch; }

  .modal { width: 100%; max-width: none; max-height: calc(var(--app-h, 100dvh) * 0.92); border-radius: 0; }
  .modal-back { align-items: flex-end; }
}

/* Controls that a mouse reveals by hovering have to be permanent where there
   is no pointer to hover with. Renaming a conversation was reachable only by
   hovering its row, which on a touch screen means not at all. */
@media (hover: none) {
  .chat-list .list-item .row-act { opacity: 1; }
}

/* The way back out of a conversation. Present in the markup on every viewport
   so the portrait rules have something to reveal; on a desktop both panes are
   on screen at once, so a back control would point at what you can already
   see. */
.chat-back {
  display: none;
  align-items: center; justify-content: center;
  flex: none; width: 34px; height: 34px; margin-left: calc(var(--s2) * -1);
  font-size: 26px; line-height: 1; color: var(--fg-muted);
}
.chat-back:hover { color: var(--fg); text-decoration: none; background: var(--bg-hover); }

/* The More sheet: every section the bottom row has no room for. */
.sheet-ws {
  margin-bottom: var(--s4); padding-bottom: var(--s3);
  border-bottom: var(--border) solid var(--line);
}
/* The picker is sized for a 208px rail; here it has the whole sheet. */
.sheet-ws .ws-row { padding: var(--s2) 0 0; border-bottom: none; }
.sheet-ws .ws-pick { font-size: var(--t-md); padding: var(--s2); min-height: 44px; }

.sheet-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--s2); }
.sheet-item {
  display: flex; align-items: center; gap: var(--s3);
  padding: var(--s3); min-height: 48px;
  border: var(--border) solid var(--line); color: var(--fg);
}
.sheet-item:hover { background: var(--bg-hover); text-decoration: none; }
.sheet-item .ic { display: inline-flex; width: 18px; justify-content: center; color: var(--fg-muted); }
.sheet-item .ic svg { width: 16px; height: 16px; }
.sheet-foot {
  margin-top: var(--s4); padding-top: var(--s3);
  border-top: var(--border) solid var(--line);
}
.sheet-foot .who { font-size: var(--t-sm); color: var(--fg-muted); }
.sheet-foot .row { display: flex; gap: var(--s2); margin-top: var(--s2); }

@media (orientation: landscape) and (max-height: 520px) {
  /* Width is not the problem here; height is. The rail stays, because a bottom
     bar would spend scarce vertical space on navigation that fits perfectly
     well down the side. */
  /* 52px fits the icons but strangles the workspace picker down to "by ⌄",
     which names nothing. A few more pixels buy it enough characters to be
     read, and still spend under a tenth of the width on navigation. */
  .rail { --rail: 74px; width: 74px; flex-basis: 74px; }
  .rail .brand b, .rail .brand span:not(.mark), .rail .nav a span.lbl, .rail-foot .who { display: none; }
  .brand { justify-content: center; padding: var(--s3) 0; }
  .ws-row { padding: var(--s2); }
  .ws-pick { font-size: 10.5px; padding: 5px 2px; }
  .nav a { justify-content: center; padding: var(--s3) 0; border-left-width: 2px; }
  .rail-foot { padding: var(--s2); }
  .rail-foot .row { flex-direction: column; gap: 4px; }
  .rail-foot .row button { font-size: 10px; padding: 4px 2px; }

  .tabbar { display: none; }

  /* Every horizontal band of chrome gives back what it can. */
  .view { padding: var(--s3) var(--s4); }
  .page-head { margin-bottom: var(--s3); padding-bottom: var(--s2); }
  .page-head h1 { font-size: var(--t-lg); }
  .page-head p { display: none; }

  .chat-list { --list: 190px; width: 190px; flex-basis: 190px; }
  .chat-head { padding: var(--s2) var(--s3); }
  .chat-head .sub { display: none; }
  .chat-head .title {
    font-size: var(--t-md);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }

  .composer { padding: var(--s2) var(--s3); }
  .composer textarea { min-height: 36px; max-height: 96px; }
  .composer .hint { display: none; }
}

/* Extraction notes on a document that indexed anyway. Folded shut, because
   they explain a detail of how a file was read, not a problem to act on. */
.doc-warn { margin-top: 4px; }
.doc-warn summary { cursor: pointer; }
.doc-warn summary:hover { color: var(--fg); }
.doc-warn ul { margin: 4px 0 0; padding-left: 18px; }
.doc-warn li { margin: 2px 0; overflow-wrap: anywhere; }

/* Scheduled tasks: the next month on a compressed axis.
   The scale is square-root rather than linear so the first day takes about a
   third of the width. On a linear month everything about to happen collapses
   into the first two pixels while three empty weeks fill the rest, which
   answers "is anything coming" and nothing about "what is about to". */
.tl-card { padding-bottom: var(--s3); }

.tl-track {
  position: relative;
  height: 68px;
  /* Bottom room for the day labels, which hang below the axis, and for the
     bubble that opens under them. */
  margin: var(--s2) 0 var(--s6);
  border-bottom: 1px solid var(--line);
}

.tl-grid {
  position: absolute; top: 0; bottom: 0; width: 1px;
  background: var(--line);
}

.tl-tick {
  position: absolute; bottom: -18px;
  transform: translateX(-50%);
  white-space: nowrap;
  pointer-events: none;
}
/* The labels at the two ends would hang half off the card, so they sit inside
   their own edge instead of being centred on it. */
.tl-tick.first { transform: none; }
.tl-tick.last { transform: translateX(-100%); }

.tl-mark {
  position: absolute; top: 30px;
  width: 12px; height: 12px; padding: 0;
  transform: translateX(-50%);
  border: 2px solid var(--bg);
  background: var(--accent);
  cursor: pointer;
}
/* No border-radius here on purpose: the reset at the top of this file squares
   every corner in the product with !important, and a mark that tried to be
   round would simply not be, leaving a rule that reads as a lie. */
/* A little larger than the dot, but only a little: the pad has to stay narrower
   than the gap that pushes a mark into the next lane, or a mark would swallow
   the hover meant for its neighbour and become unreachable without any sign
   that it is there. */
.tl-mark::after {
  content: ''; position: absolute;
  inset: -3px;
}
.tl-mark:hover, .tl-mark:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
  /* Whichever mark is under the cursor comes forward, so an outline is never
     clipped by the one stacked next to it. */
  z-index: 1;
}
.tl-mark.err { background: var(--danger); }
/* A task that tells somebody is a different kind of event from one that only
   writes a transcript, and that is worth seeing without hovering. */
.tl-mark.sends { box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 35%, transparent); }
/* Several runs sharing one mark. Bigger and hollow, so it reads as "more than
   one thing here" rather than as a single event drawn slightly wrong. */
.tl-mark.many {
  width: 16px; height: 16px; top: 28px;
  background: var(--bg);
  border: 3px solid var(--accent);
}
.tl-mark.many.err { border-color: var(--danger); }

/* The track and the bubble share a positioning context, so the bubble can be
   placed against a mark's pixel position rather than against the page. */
.tl-wrap { position: relative; }

/* Above the track, not below it: under the axis is where the day labels and
   the bubble go, and a line of context that gets covered the moment you reach
   for a mark is a line nobody reads. */
.tl-summary { margin-top: var(--s1); }

.tl-bubble {
  /* Clear of the day labels, which hang below the track rather than inside it.
     Starting at the track's own edge would put the bubble straight over "now"
     and "1d" — the two labels that say what the mark it describes means. */
  position: absolute; top: calc(100% + 26px); z-index: 5;
  min-width: 260px; max-width: min(420px, 90vw);
  padding: var(--s3);
  background: var(--bg);
  border: var(--border) solid var(--line-strong);
  box-shadow: 0 6px 24px rgb(0 0 0 / 12%);
}
.tl-bubble .tl-head { margin-bottom: var(--s2); }
.tl-bubble .tl-rows { max-height: 240px; overflow-y: auto; }
.tl-bubble .tl-row {
  display: flex; align-items: flex-start; gap: var(--s3);
  padding: var(--s2) 0;
  border-top: 1px solid var(--line);
}
.tl-bubble .tl-head + .tl-row,
.tl-bubble .tl-rows .tl-row:first-child { border-top: none; }
/* A fixed column for the time keeps the names aligned down the list, which is
   what makes a cluster of one repeating task readable at a glance. */
.tl-bubble .tl-when { flex: 0 0 auto; min-width: 108px; padding-top: 2px; }
/* The middle column takes the slack so every row's button lands in the same
   place; without it a row with a short subtitle pulls its button inward and
   the list reads as ragged. */
.tl-bubble .tl-row > .grow { flex: 1 1 auto; min-width: 0; }

/* A one-off is the only run it will ever have, and the one mark on the
   timeline that will not be there again tomorrow. It is drawn as an upright
   marker rather than a dot so it reads as an event rather than as another beat
   of something regular. */
/* On its own row, above the recurring runs. A one-off never merges with
   anything, so left where the others are it would sooner or later land under a
   cluster and become unreachable — and separating them also says the thing the
   colour is saying, twice. */
.tl-mark.once {
  width: 4px; height: 20px; top: 4px;
  background: var(--fg);
  border: none;
  box-shadow: 0 0 0 2px var(--bg);
  z-index: 2;
}
.tl-mark.once.err { background: var(--danger); }
.tl-mark.once.sends { background: var(--accent); }

@media (max-width: 760px) {
  /* A phone has no hover, so a mark has to be tappable: the dot grows and takes
     its pad with it. Merging the marks that would collide is what keeps two of
     them reachable at that size. */
  /* Exempt from the 40px touch-target minimum every other button gets on a
     phone: a mark is a graphic, not a control with a label, and stretching it
     to 40px turns a square into a bar and a bar into a column. The pad around
     it is what makes it hittable. */
  .tl-mark { width: 14px; height: 14px; min-height: 0; }
  .tl-mark::after { inset: -8px; }
  .tl-mark.many { width: 16px; height: 16px; min-height: 0; }
  .tl-mark.once { width: 5px; height: 22px; top: 3px; min-height: 0; }
  .tl-tick { font-size: var(--t-xs); }
  /* The aside explaining the scale goes, rather than squeezing "Next 30 days"
     into three lines to keep a decoration. The uneven tick spacing right below
     says the same thing without costing the heading anything. */
  .tl-card .card-head > .small { display: none; }
  /* There is no room to anchor a bubble under a mark on a phone, so it spans
     the card instead — the mark it came from is the one that was just tapped. */
  .tl-bubble { left: 0 !important; right: 0; min-width: 0; max-width: none; }
  .tl-bubble .tl-when { min-width: 92px; }
}

/* The interactive console. xterm draws into this; it needs a real height,
   because a terminal that is as tall as its contents is a terminal that has no
   idea how many rows it has, and a full-screen program asks. */
.term-live {
  height: 420px;
  padding: var(--s2);
  background: var(--bg-sunken);
  border: var(--border) solid var(--line);
  overflow: hidden;
}
.term-live .xterm { height: 100%; }
.term-live .xterm-viewport { background: transparent !important; }

@media (max-width: 760px) {
  /* Shorter, because the on-screen keyboard takes half the screen and a
     terminal you cannot see the bottom of is worse than a short one. */
  .term-live { height: 300px; }
}

/* ---------------------------------------------------------------- decisions

   A question the agent stopped to ask. It sits in the thread like a tool card,
   but louder: a run parked on an unanswered question is doing nothing at all,
   and the reader has to be able to tell that from a run that is thinking. The
   accent edge is the only thing in the thread that draws the eye on purpose. */

.decide {
  border: var(--border) solid var(--line);
  margin-bottom: var(--s3);
  padding: var(--s3);
  font-size: var(--t-sm);
}
.decide.open {
  border-color: var(--warn);
  border-left-width: 3px;
}
.decide-head {
  display: flex;
  gap: var(--s2);
  align-items: center;
  margin-bottom: var(--s2);
}
.decide-title { font-weight: 600; }
.decide-note { margin: 0 0 var(--s2); color: var(--fg-muted); }
.decide-q {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  align-items: baseline;
  padding: var(--s1) 0;
}
.decide-qtext { flex: 1 1 12rem; }
.decide-said { color: var(--accent); }
.decide-foot {
  display: flex;
  gap: var(--s3);
  align-items: center;
  margin-top: var(--s2);
}

/* The dialog. Options are labels wrapping their own control so the whole row
   is a target — a radio dot alone is about 13px across, which is half of what
   a thumb can reliably hit. */
.decide-block {
  border-top: var(--border) solid var(--line);
  padding-top: var(--s3);
  margin-top: var(--s3);
}
.decide-block:first-child { border-top: 0; margin-top: 0; padding-top: 0; }
.decide-qhead {
  display: flex;
  gap: var(--s2);
  align-items: baseline;
  margin-bottom: var(--s2);
  font-weight: 600;
}
/* A <label>, so the whole row is a target — but the app's label typography is
   for form captions: uppercase, letter-spaced and muted, which turned every
   option and its explanation into small-caps shouting. Reset here rather than
   giving up the label, because the click target is worth more than the tag. */
.decide-opt {
  text-transform: none;
  letter-spacing: normal;
  font-size: var(--t-md);
  color: var(--fg);
  display: flex;
  gap: var(--s2);
  align-items: flex-start;
  padding: var(--s2);
  border: var(--border) solid var(--line);
  margin-bottom: var(--s1);
  cursor: pointer;
  min-height: 40px;
}
.decide-opt:hover { border-color: var(--line-strong); }
.decide-opt.rec { border-color: var(--ok); }
.decide-opt.other { cursor: default; }
.decide-opt input[type="radio"],
.decide-opt input[type="checkbox"] { flex: none; margin-top: 3px; }
.decide-opt-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1;
  min-width: 0;
}
.decide-opt-label {
  display: flex;
  gap: var(--s2);
  align-items: center;
  flex-wrap: wrap;
}
.decide-opt-detail { color: var(--fg-muted); font-size: var(--t-sm); }
.decide-other { width: 100%; }

/* A delegated run's steps, nested under the tool call that started it. Indented
   rather than boxed: it is the same block, one level in, and another border
   inside a bordered block reads as a separate thing. */
.sub-steps {
  padding: var(--s2) var(--s3);
  border-top: var(--border) solid var(--line);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.sub-step {
  font-size: var(--t-xs);
  color: var(--fg-muted);
  padding-left: var(--s3);
  border-left: 2px solid var(--line);
}
.sub-step.done { color: var(--fg); }

/* The link into a delegate's own thread, at the top of the block that started
   it. Reading what a specialist actually did used to mean going to Tracing and
   working back from the provider call. */
.sub-open { padding: var(--s2) var(--s3) 0; }
