
/* ---- dark Gruvbox (UI-10) -------------------------------------------------
   The official dark palette: bg0 282828, bg0_h 1d2021, bg1 3c3836, bg2 504945,
   bg3 665c54, bg4 7c6f64, fg0 fbf1c7, fg1 ebdbb2, fg4 a89984, red fb4934,
   green b8bb26, yellow fabd2f, blue 83a598, purple d3869b, aqua 8ec07c,
   orange fe8019 — and the neutral (grey) variants used for card fills.
   Card colours (UI-17) are the grey dim of the matching bright colour, so the
   hue reads at a glance but the text stays the same fg1 everywhere. */
:root{
  --bg:#282828; --bg0h:#1d2021; --bg1:#3c3836; --bg2:#504945; --bg3:#665c54;
  --bg4:#7c6f64; --line:#3c3836; --line2:#504945;
  --text:#ebdbb2; --bright:#fbf1c7; --dim:#a89984; --dim2:#bdae93;
  --red:#fb4934; --green:#b8bb26; --yellow:#fabd2f; --blue:#83a598;
  --purple:#d3869b; --aqua:#8ec07c; --orange:#fe8019;
  --accent:#fe8019;
  /* UI-17: one pair per card kind — fill (grey dim) and edge (bright). */
  --c-user:#403520;  --c-user-edge:#7c6426;
  --c-order:#403239; --c-order-edge:#7a4e5d;
  --c-final:#33351f; --c-final-edge:#6c6828;
  --c-agent:#333836; --c-agent-edge:#5c6f66;
  --read-size:14px; --read-family:inherit;
  /* UI-41: the tool trail's own size. Tool lines are metadata about the work, not
     the work itself — 11px suits them at default, but a reader who wants the calls
     bigger must not have to enlarge the answer to get them, so this is a third
     preference beside the reading size rather than a part of it. */
  --tool-size:11px;
  /* UI-25: what the Raw listing is drawn in. The family stays monospace until the
     bar's Mono/Sans button says otherwise — a listing with proportional characters
     loses its column alignment, which is the one thing code needs. */
  --code-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
}
*{box-sizing:border-box}
html,body{height:100%}
body{margin:0;background:var(--bg0h);color:var(--text);
  font:14px/1.55 ui-sans-serif,system-ui,-apple-system,"Segoe UI",
  "Noto Sans Thai","Leelawadee UI",Tahoma,sans-serif}
/* #app is a grid, not a free-flowing page: the composer is a row of its own
   track, so it cannot be pushed below a long stream (UI-12). The row is
   `minmax(0,1fr)`, not the implicit `auto`, or a tall stream would stretch the
   row past the viewport and take the composer with it.
   The third column is the file panel (UI-19). A file no longer covers the window:
   it opens as a tab in the middle of the screen beside the session tabs (UI-27). */
#app{position:relative;display:grid;grid-template-columns:var(--side-w,290px) minmax(0,1fr) auto;
  grid-template-rows:minmax(0,1fr);height:100vh;height:100dvh;overflow:hidden}
/* UI-44: the sidebar is text the reader reads too — the session list, the agent
   list, the runtime lines — so it wears the same two preferences the header and
   the file panel wear. `body` pins a 14px ui-sans-serif, so without this the
   sidebar alone ignored every move of the controls sitting inside it. */
#side{background:var(--bg0h);border-right:1px solid var(--line);padding:14px;overflow:auto;
  font-family:var(--read-family);font-size:var(--read-size)}
.brand{font-weight:700;font-size:1.14em;letter-spacing:.3px;margin-bottom:12px;color:var(--bright)}
.brand span{color:var(--accent)}
/* The version of the program serving the page, next to the name it belongs to —
   the question "which build am I looking at?" is asked most often in the sidebar,
   and answered from /api/state so it is the running binary's own version, not a
   string typed into the markup that could go stale. */
.brand .ver{color:var(--bg4);font-weight:400;font-size:.79em;letter-spacing:0;margin-left:7px;
  font-variant-numeric:tabular-nums}
.sectitle{font-size:.79em;text-transform:uppercase;letter-spacing:.09em;color:var(--dim);margin:18px 0 6px}
button{font:inherit;border-radius:6px;border:1px solid var(--line2);background:var(--bg1);
  color:var(--text);padding:7px 12px;cursor:pointer}
button:hover:not(:disabled){border-color:var(--accent);color:var(--bright)}
button:disabled{opacity:.4;cursor:not-allowed}
button.primary{background:var(--accent);border-color:var(--accent);color:#1d2021;font-weight:700}
button.primary:hover:not(:disabled){background:#ffb066;border-color:#ffb066;color:#1d2021}
button.danger{border-color:#7a2e1f;color:var(--red)}
button.danger:hover:not(:disabled){border-color:var(--red);background:#3c2a24}
button.ghost{background:transparent}
button.iconBtn{background:transparent;border:none;padding:3px 7px;color:var(--dim);font-size:14px}
button.iconBtn:hover{color:var(--bright);border:none;background:var(--bg1)}
button.active{border-color:var(--accent);color:var(--bright)}
input,select,textarea{font:inherit;color:var(--text)}
select{width:100%;background:var(--bg1);border:1px solid var(--line2);border-radius:6px;padding:6px 8px}
select:focus,input:focus,textarea:focus{outline:none;border-color:var(--accent)}
/* The controls are chrome, not text: pinned to 12px so that enlarging the
   listing to 32px does not enlarge the picker and steppers that enlarge it
   (`button`/`select` are `font:inherit`, so an unpinned control grows with the
   very size it sets). `.sizeRead` shows the number, so it stays fixed too. */
.fontBox{display:flex;flex-direction:column;gap:7px;font-size:12px}
.sizeRow{display:flex;align-items:center;gap:8px}
.stepBtn{padding:4px 10px;min-width:44px}
.sizeRead{font-size:12px;color:var(--dim);flex:1;text-align:center}
.sessions,.agents,.files{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:4px}
.sessions li{padding:7px 9px;border-radius:6px;cursor:pointer;border:1px solid transparent;
  display:flex;align-items:flex-start;gap:6px}
.sessions li:hover{background:var(--bg1)}
.sessions li.active{background:var(--bg1);border-color:var(--line2)}
.sessions li .grow{flex:1;min-width:0}
.sessions .t{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.sessions .m{font-size:.79em;color:var(--dim)}
/* UI-35: a session whose run is parked on an ask_user waits for the reader, and it
   will wait for ASK_TIMEOUT and then answer itself. The mark sits beside the row it
   belongs to (never on a single tab: the question exists whether or not one is open)
   and is yellow — the same warning colour the token bar uses as it nears the ceiling.
   The word is in the row's own status line (`waiting for you`); this is the mark that
   survives the row being scanned at a glance. */
.sessions .askMark{flex:0 0 auto;width:16px;height:16px;margin-top:1px;border-radius:50%;
  background:var(--yellow);color:#111;font-size:11px;font-weight:700;line-height:16px;
  text-align:center}
/* UI-21: the Group Chat size, the number that says how close a session is to
   compaction. The bar is the share of `context.max_size` — it turns yellow near
   the ceiling and red past it, where the next round will compact. */
.sessions .tok{display:flex;align-items:center;gap:6px;margin-top:2px;color:var(--dim2);
  font-variant-numeric:tabular-nums}
.sessions .tok .tokNum{color:var(--aqua);font-weight:600}
.sessions .tok.near .tokNum{color:var(--yellow)}
.sessions .tok.over .tokNum{color:var(--red)}
.sessions .tok.none{color:var(--bg4);font-style:italic}
.sessions .tok .bar{flex:1;height:3px;border-radius:2px;background:var(--bg2);overflow:hidden;min-width:26px}
.sessions .tok .bar i{display:block;height:100%;background:var(--aqua)}
.sessions .tok.near .bar i{background:var(--yellow)}
.sessions .tok.over .bar i{background:var(--red)}
.delBtn{border:none;background:transparent;color:var(--bg4);padding:0 4px;line-height:1.2;
  font-size:14px;flex:none}
.sessions li:hover .delBtn{color:var(--dim2)}
.delBtn:hover{color:var(--red)!important;border:none!important;background:transparent!important}
.agents li{display:flex;justify-content:space-between;font-size:.86em;color:var(--dim);padding:3px 2px}
.agents .v{color:var(--green)}
.runtime{margin:0;font-size:.79em;color:var(--dim);display:grid;grid-template-columns:auto 1fr;gap:2px 8px}
.runtime dd{margin:0;overflow-wrap:anywhere}
.files li{font-size:.79em;color:var(--dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.files .rej{color:var(--red)}
/* Five rows: the bar, the tab strip (UI-27), the pane area — the only row that
   gives — then the composer's grip and the composer itself. The strip must not be
   allowed to grow, and the pane area must be able to shrink to nothing: a long file
   name in a tab is what used to push the Download button out of the window. */
/* `grid-template-columns:minmax(0,1fr)` is not decoration (UI-27). Without it this
   grid gets one implicit `auto` track, and an auto track grows to its content's
   max-content width — a long file path and a toolbar row are wider than a phone,
   so #main came out 654px inside a 620px window and body{overflow:hidden} cut the
   viewer bar's Download button off, unreachable. minmax(0,1fr) caps the track to
   the window and lets the bar's own wrapping do its job; scripts/viewer-panels.mjs
   measures this at 620px and 420px. */
#main{display:grid;grid-template-columns:minmax(0,1fr);
  grid-template-rows:auto auto minmax(0,1fr) auto auto;min-width:0;height:100%;overflow:hidden}
#bar{display:flex;flex-wrap:wrap;align-items:center;gap:10px;padding:11px 16px;
  border-bottom:1px solid var(--line);background:var(--bg0h)}
/* Hidden above 820px, where the sidebar owns the controls; shown below it, where
   `#side{display:none}` would otherwise leave no way to change the text at all.
   One visible pair at every width, never two. */
.barTextCtl{display:none;align-items:center;gap:6px;flex:1 1 100%;min-width:0;font-size:12px}
.barTextCtl select{width:auto;flex:1 1 auto;min-width:0;padding:0 6px;font-size:12px;height:28px}
.barTextCtl .stepBtn{padding:3px 8px;min-width:34px;font-size:12px;flex:none}
.barTextCtl .sizeRead{flex:none;text-align:right}
#sessionTitle{font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:40%;color:var(--bright)}
.spacer{flex:1}
.dot{width:9px;height:9px;border-radius:50%;background:var(--dim)}
.dot.run{background:var(--green);animation:pulse 1.1s infinite}
.dot.err{background:var(--red)}
@keyframes pulse{50%{opacity:.35}}
#statusText{font-size:12px;color:var(--dim)}
/* The reading pane: the only place the user's font choice applies (UI-11). */
.stream{overflow:auto;padding:18px;display:flex;flex-direction:column;gap:12px;
  font-family:var(--read-family);font-size:var(--read-size)}
.card{border:1px solid var(--line);border-radius:8px;padding:12px 14px;min-width:0;background:var(--bg1);
  white-space:pre-wrap;overflow-wrap:anywhere;animation:in .18s ease-out}
@keyframes in{from{opacity:0;transform:translateY(5px)}}
.card .who{font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:var(--dim);
  margin-bottom:7px;display:flex;align-items:center;gap:8px;font-family:inherit}
/* UI-32: the id beside the name is a reference, so it is dim, in the code face (ids
   are code), and it must never make a card wider or push the name out — a long id
   wraps below it instead. */
.card .who{flex-wrap:wrap;overflow-wrap:anywhere}
/* UI-31: the switch to the card's own text. Quiet until hovered: it belongs to the
   card, not to the reader's attention. */
.card .rawBtn{margin-left:auto;font-size:10px;padding:1px 7px;border-radius:20px;
  border:1px solid var(--line2);background:none;color:var(--dim);text-transform:none;
  letter-spacing:0;font-family:inherit}
.card .rawBtn:hover{color:var(--bright);border-color:var(--dim2)}
/* UI-28: the context figures at the right end of the name row, after the time.
   Smaller and dimmer than the time: an instrument reading, not a headline, and it
   must not compete with the agent's own words below. */
.card .who .tok2{font-size:9.5px;letter-spacing:.04em;color:var(--bg4);text-transform:none;
  margin-left:8px;white-space:nowrap;cursor:help}
.card .who .tok2 .tok{color:var(--dim)}
.card .who .badge{font-size:10px;padding:1px 6px;border-radius:20px;border:1px solid var(--line2);
  color:var(--dim2);text-transform:none;letter-spacing:0}
/* UI-24: the minute the entry was written, after the name. Dim and tabular: it is
   a reference, not the content, and it must not shove the name around as digits
   change. */
.card .who .when{margin-left:auto;color:var(--bg4);text-transform:none;letter-spacing:0;
  font-size:11px;font-variant-numeric:tabular-nums;white-space:nowrap;font-family:inherit}
/* UI-17: the colour IS the author. Yellow = the user's prompt, purple = the
   conductor ordering an agent, green = the conductor's summary of the work,
   blue = an agent's answer. */
.card.user{background:var(--c-user);border-color:var(--c-user-edge)}
.card.user .who{color:var(--yellow)}
.card.conductor{background:var(--c-order);border-color:var(--c-order-edge)}
.card.conductor .who{color:var(--purple)}
.card.agent{background:var(--c-agent);border-color:var(--c-agent-edge)}
.card.agent .who{color:var(--blue)}
.card.final{background:var(--c-final);border-color:var(--c-final-edge)}
.card.final .who{color:var(--green)}
.card.summary{border-color:#4a5338;background:var(--bg0h);color:var(--dim2)}
.card.err{border-color:#7a2e1f;background:#32201c;color:#fdd0c5}
/* UI-7: an `ask_user` exchange in the Group Chat. It borrows the user's yellow for
   its border because the half that matters — the answer — is the user's own words,
   but it stays visibly its own kind: an agent spoke first inside that card. */
.card.ask{background:var(--bg0h);border-color:var(--c-user-edge)}
.card.ask .who{color:var(--orange)}
.askQ{margin-bottom:8px}
.askA{display:flex;flex-wrap:wrap;gap:8px;padding:8px 10px;border-radius:6px;
  background:var(--c-user);border-left:3px solid var(--yellow)}
.askA .askTag{font-size:10px;text-transform:uppercase;letter-spacing:.08em;color:var(--yellow);
  font-family:inherit;white-space:nowrap;align-self:flex-start;padding-top:2px}
/* UI-15: the folded tool trail inside an agent's card. Closed by default — the
   card shows the answer, the calls are a line away when the user wants them.
   UI-41: its size is its own preference — the summary line and every line under it
   are sized by `--tool-size`, so A−/A+ here moves the trail without touching the
   answer above it. The control is fixed at 11px so it does not grow itself. */
.tools{margin-top:10px;border-top:1px solid var(--line);padding-top:7px;font-family:inherit;
  min-width:0;overflow-wrap:anywhere}
.tools > summary{cursor:pointer;font-size:var(--tool-size);color:var(--dim);list-style:none;
  display:flex;align-items:center;flex-wrap:wrap}
.tools > summary::before{content:'▸ ';color:var(--bg4)}
.tools[open] > summary::before{content:'▾ '}
.tools > summary:hover{color:var(--dim2)}
.tools[open]{display:flex;flex-direction:column;gap:3px;margin-top:10px}
/* UI-41: the trail's own A−/A+. Fixed at 11px whatever it is set to — a control that
   resized itself would run out of the row it sits in, and the figure has to stay
   readable in order to say what it is set to. What keeps a press on them from also
   folding the trail up is the `preventDefault`/`stopPropagation` in the delegated
   handler in `initTextControls()` — a button inside a `<summary>` still receives its
   own click, and the summary would then toggle on the way out.

   The other way was tried and does not work: taking the click back with
   `.tools > summary{pointer-events:none}` and giving the buttons theirs again leaves
   the trail's own words unable to open it. `elementFromPoint` on the words answers
   DETAILS rather than SUMMARY, and `ui-41.mjs` fails at its first summary check with
   exactly that. The stepper needs no `pointer-events` of its own — nothing above it
   switches them off. */
.toolCtl{display:inline-flex;align-items:center;gap:4px;margin-left:8px;font-size:11px;
  color:var(--bg4);white-space:nowrap;flex:none}
/* The trail's own words get whatever the row has left, and give way before the
   control does (UI-41): the control is what the reader is aiming at, so it stays
   exactly where they left it. */
.tools > summary .toolWords{flex:1 1 auto;min-width:0;overflow-wrap:anywhere}
.tools > summary .toolCtl{margin-left:auto}
.toolCtl .toolStep{padding:0 6px;min-width:0;height:18px;font-size:11px;line-height:1;
  border-radius:4px}
.toolCtl .toolSizeRead{min-width:38px;text-align:center;font-variant-numeric:tabular-nums}
/* The bar above the calls while they are still arriving (UI-41): the same steppers,
   outside a <summary> so nothing has to be un-folded, and at its own fixed 11px so
   enlarging the lines does not enlarge the handle that enlarges them. */
.toolCtlBar{display:flex;align-items:center;gap:8px;font-size:11px;color:var(--bg4)}
.toolCtlBar .toolCtl{margin-left:0}
.toolLine{font-size:var(--tool-size);color:var(--dim);display:flex;gap:6px;flex-wrap:wrap;
  overflow-wrap:anywhere;padding-left:8px}
.toolLine code{color:var(--blue);font-family:ui-monospace,monospace;flex:none}
.toolLine.note{color:var(--bg4)}
.toolLine.stale{opacity:.65}
/* UI-33: the tool that never came back — dimmer than a note, because it says nothing
   happened: the run was cut short while this was going. */
.toolLine.stopped{color:var(--red)}
.card.stale{opacity:.5}
.card.compact{border-color:var(--bg3);background:var(--bg0h);color:var(--dim2)}
.card .body{font-family:inherit}
.card pre{margin:0;font-family:ui-monospace,SFMono-Regular,monospace;font-size:.92em;white-space:pre-wrap}
.card .files{margin-top:8px;display:flex;flex-wrap:wrap;gap:6px}
.chip{font-size:11px;border:1px solid var(--line2);border-radius:20px;padding:2px 9px;
  color:var(--dim);background:var(--bg0h)}
.chip img{max-height:34px;border-radius:4px;display:block}
/* An image attachment is a button, not a chip with an onclick: a chip you cannot
   reach with Tab is a thumbnail that does not exist for the keyboard. `font-family`
   only — the `font` shorthand would reset the 11px this chip's own rule sets. */
button.chip.thumb{font-family:inherit;font-size:11px;padding:2px 9px;cursor:pointer;
  display:inline-flex;align-items:center;gap:6px;max-width:100%}
button.chip.thumb:hover{border-color:var(--accent);color:var(--bright)}
button.chip.thumb:focus-visible{outline:2px solid var(--bright);outline-offset:2px}
.chip .cap{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:180px}
.chip .kind{font-size:10px;text-transform:uppercase;letter-spacing:.08em;color:var(--purple)}
/* The sidebar's rows are links now. Dense padding, as the rest of the sidebar is
   dense (`.delBtn` is 14px tall); the focus ring is inset so the list's own edge
   cannot clip it. */
.files li a.dl{display:block;padding:4px 6px;border-radius:6px;color:var(--dim);
  text-decoration:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.files li a.dl:hover{background:var(--bg0h);color:var(--bright)}
.files li a.dl:focus-visible{outline:2px solid var(--bright);outline-offset:-2px}
/* Above the ask_user popup: an image opened from a card stays readable while a
   question is waiting underneath it. */
.lightbox{z-index:60}
.lbFig{margin:0;display:flex;flex-direction:column;gap:8px;max-width:min(92vw,1100px)}
.lbImg{max-width:100%;max-height:78vh;object-fit:contain;border:1px solid var(--bg3);
  border-radius:6px;background:var(--bg0h)}
.lbCap{display:flex;align-items:center;gap:8px;font-size:11px;color:var(--dim2);
  background:var(--bg1);border:1px solid var(--bg3);border-radius:6px;padding:6px 10px}
.lbCap #lightboxCap{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.lbDl{color:var(--yellow);text-decoration:none;font-weight:700;padding:2px 6px;border-radius:6px}
.lbDl:hover{text-decoration:underline}
.lbDl:focus-visible,.lightbox .ghost:focus-visible{outline:2px solid var(--bright);outline-offset:2px}
.card .dots::after{content:'…';color:var(--dim)}
/* UI-14: the handle sits between the stream and the composer — the border the
   two share is the thing you drag. Row height is auto, so the strip is always
   the same size no matter how tall the box gets. */
.grip{height:13px;cursor:ns-resize;display:flex;align-items:center;justify-content:center;
  touch-action:none;background:var(--bg0h);border-top:1px solid var(--line);flex:none}
.grip::before{content:'';width:64px;height:3px;border-radius:3px;background:var(--bg3)}
.grip:hover::before,.grip.dragging::before{background:var(--accent)}
.grip.dragging{cursor:ns-resize}
/* The two column edges, dragged to resize the side and file panels. They are
   children of #app rather than of the panels they belong to: both panels are
   `overflow:auto`, which is a clipping context, so a handle straddling their
   border would be cut away and be nothing to catch. `touch-action:none` keeps a
   drag on a phone a drag and not a page scroll, as on the composer's handle. */
.colGrip{position:absolute;top:0;bottom:0;width:10px;z-index:20;cursor:col-resize;
  display:flex;align-items:center;justify-content:center;touch-action:none}
.colGrip::before{content:'';width:3px;height:26px;border-radius:3px;background:var(--bg3)}
.colGrip:hover::before,.colGrip.dragging::before{background:var(--accent)}
.colGrip:focus-visible{outline:2px solid var(--bright);outline-offset:-2px}
#sideGrip{left:calc(var(--side-w,290px) - 5px)}
#treeGrip{right:calc(var(--tree-w,330px) - 5px)}
/* The handle belongs to the panel it resizes: a hidden panel has no edge to pull.
   The grips are later siblings of #tree, which is what lets this reach it. */
#tree[hidden] ~ #treeGrip{display:none}
/* While a drag is on, the pointer is over text that would otherwise be selected
   and dragged along with it. */
body.resizing{user-select:none;cursor:col-resize}
/* UI-12: pinned to the bottom of the main track; `minmax(0,1fr)` on the stream
   above is what stops it sliding out of view when the stream grows. */
#composer{border-top:1px solid var(--line);background:var(--bg0h);padding:10px 16px 12px;flex:none}
.pending{display:flex;flex-direction:column;gap:4px}
.pending .p{font-size:12px;color:var(--yellow)}
/* UI-14: resizable by the user. The height is set by the grip above the box; the
   native corner handle stays too, for people who find it by reflex. */
#input{width:100%;background:var(--bg1);border:1px solid var(--line2);border-radius:8px;
  padding:10px 12px;resize:vertical;min-height:60px;max-height:70vh;font:inherit;overflow:auto;
  font-family:var(--read-family);font-size:var(--read-size)}
#input::-webkit-resizer{background:var(--bg3);border-radius:0 0 7px 0}
.composerRow{display:flex;align-items:center;gap:10px;margin-top:9px}
.fileBtn{border:1px dashed var(--bg4);border-radius:6px;padding:6px 12px;cursor:pointer;
  color:var(--dim);font-size:12px}
.fileBtn:hover{border-color:var(--accent);color:var(--text)}
.hint{font-size:11px;color:var(--dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.hint .bad{color:var(--red)}
.kbd{font-size:11px;color:var(--dim);border:1px solid var(--line2);border-radius:4px;padding:2px 6px}
/* ---- the file panel (UI-19) ----------------------------------------------
   A tree, not a list: folders expand in place, files open in the viewer. It is
   the working directory the agents' `run` tool works in, so it is a way to watch
   a run produce its output. */
/* UI-44: the panel reads in the reader's font and at the reader's size, moved by
   the page's one pair of controls. Every size below is an `em` of that one size,
   at the ratio the fixed pixels used to paint — the tree keeps its hierarchy
   while the whole panel scales together. */
#tree{background:var(--bg0h);border-left:1px solid var(--line);width:var(--tree-w,330px);min-width:240px;
  max-width:50vw;display:flex;flex-direction:column;overflow:hidden;
  font-family:var(--read-family);font-size:var(--read-size)}
#tree[hidden]{display:none}
/* The head wraps: at the panel's 240px minimum a long folder name beside the
   three icon buttons is wider than the panel, and the way out is a second line —
   never a button that sits where nothing can reach it. */
.treeHead{display:flex;flex-wrap:wrap;align-items:center;gap:4px 6px;padding:10px 12px;
  border-bottom:1px solid var(--line)}
.treeHead #treeTitle{font-size:.85em;text-transform:uppercase;letter-spacing:.09em;color:var(--dim)}
.treePath{font-size:.72em;color:var(--bg4);padding:6px 12px;border-bottom:1px solid var(--line);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--read-family)}
.treeList{overflow:auto;flex:1;padding:6px 0;font-size:.85em;user-select:none}
.treeList .row{display:flex;align-items:center;gap:6px;padding:3px 10px;cursor:pointer;
  white-space:nowrap;overflow:hidden}
.treeList .row:hover{background:var(--bg1)}
.treeList .row.sel{background:var(--bg1);color:var(--bright)}
.treeList .row .tw{width:12px;flex:none;color:var(--bg4);font-size:.75em}
.treeList .row .nm{overflow:hidden;text-overflow:ellipsis}
.treeList .row .sz{margin-left:auto;color:var(--bg4);font-size:.84em;flex:none;padding-left:8px}
.treeList .row.dir .nm{color:var(--dim2)}
.treeList .row.blocked{opacity:.5}
.treeList .msg{padding:6px 12px;color:var(--red);font-size:.92em}
.treeList .empty{padding:6px 12px;color:var(--bg4);font-size:.92em}
.treeList .ld{padding:4px 12px;color:var(--bg4);font-size:.92em}
.treeFoot{border-top:1px solid var(--line);padding:6px 12px;font-size:.72em;color:var(--bg4);
  display:flex;align-items:center;gap:8px}
.treeFoot .spacer{flex:1}
/* ---- the tab strip and its panes (UI-20, UI-27) --------------------------
   Sessions and files share one strip, because both are "something I opened and
   left open". A file no longer covers the chat: it sits in the same area, so you
   can read what the run wrote and watch the run in the same window. */
.tabs{display:flex;align-items:stretch;gap:2px;background:var(--bg0h);border-bottom:1px solid var(--line);
  padding:0 8px;overflow-x:auto;overflow-y:hidden;flex:none;min-height:38px}
.tab{display:flex;align-items:center;gap:8px;padding:0 10px;border:none;background:transparent;
  color:var(--dim);font-size:12px;border-radius:0;border-bottom:2px solid transparent;max-width:260px;
  min-width:0;cursor:pointer;flex:0 1 auto}
.tab:hover{background:var(--bg1);color:var(--text);border-color:var(--bg3)}
.tab.active{background:var(--bg);color:var(--bright);border-bottom-color:var(--accent)}
.tab .tname{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
.tab .x{color:var(--bg4);padding:0 2px;font-size:13px}
.tab .x:hover{color:var(--red)}
/* The kind is marked before the name, so a session and a file of the same name —
   `notes.md` and a session titled `notes.md` — are not the same-looking tab. */
.tab .kind{opacity:.6;font-size:11px;flex:none}
.tab.session .kind{color:var(--blue)}
.tab.file .kind{color:var(--green)}
/* Something arrived while this tab was not on screen. */
.tab .badge{color:var(--yellow);font-weight:700;flex:none}
.tab.file.active{border-bottom-color:var(--green)}

/* One pane is on screen at a time; the others keep their DOM (and their scroll
   position, and their running EventSource) hidden behind it. */
/* One pane is on screen at a time; the others keep their DOM (and their scroll
   position, and their running EventSource) hidden behind it.

   `min-width:0` on all three is what keeps the window's edges honest (checked by
   scripts/viewer-panels.mjs at 620px and 420px). A flex or grid item's default is
   `min-width:auto` — never smaller than its content's minimum width — and a file
   path and a row of toolbar buttons have a wide minimum. While the viewer was a
   `position:fixed` overlay it contributed nothing to the page's widths; as an
   in-flow pane it does, so without these the whole middle column grew past the
   window and body{overflow:hidden} cut the Download button off — the same bug
   this bar had once before, arriving by a different route. */
.panes{min-height:0;min-width:0;overflow:hidden;position:relative;display:flex;flex-direction:column}
.pane{min-height:0;min-width:0;display:flex;flex-direction:column;overflow:hidden;flex:1}
.pane[hidden]{display:none}
.sessionPane .stream{flex:1}
/* UI-34: the way back to the newest card. A stream that stopped following has
   unread work below its bottom edge, and the reader has to be able to see that
   and end it with one press — so the mark sits inside the pane, over the stream,
   not in the bar above where it reads as a property of the session. */
.sessionPane{position:relative}
.tailJump{position:absolute;left:50%;transform:translateX(-50%);bottom:14px;z-index:3;
  display:none;align-items:center;gap:7px;padding:5px 13px;border-radius:999px;
  border:1px solid var(--line2);background:var(--bg0h);color:var(--bright);
  font:inherit;font-size:12px;box-shadow:0 3px 10px rgba(0,0,0,.45);cursor:pointer}
.tailJump.show{display:inline-flex}
.tailJump .n{color:var(--accent);font-weight:600}
/* The bar is two groups that wrap onto a second line when the window is narrow.
   It has to wrap: adding the text controls (UI-23) made the row wider than a
   small window, and what spilled off the edge was the Download button — the one
   thing in the bar you cannot do without. `min-width:0` on the file group is what
   lets the path give way to its ellipsis instead of pushing everything right. */
.viewerBar{display:flex;flex-wrap:wrap;align-items:center;gap:6px 10px;padding:8px 14px;min-width:0;
  border-bottom:1px solid var(--line);background:var(--bg0h);flex:none;font-size:12px;color:var(--dim)}
.viewerBar .fileBits{display:flex;align-items:center;gap:10px;min-width:0;flex:1 1 auto}
/* The controls wrap among themselves as a last resort: at a very narrow window
   their own minimum width is wider than the pane, and the alternative is the
   Download button sitting where nothing can reach it. */
.viewerBar .barCtl{display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;
  gap:6px 8px;flex:0 1 auto;min-width:0;margin-left:auto}
.viewerBar .path{font-family:ui-monospace,monospace;color:var(--dim2);overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap;min-width:60px;flex:1 1 auto}
.viewerBar .warn{color:var(--yellow)}
/* UI-20: Download is a button, not a link. It used to be an <a class="primary">,
   which no CSS rule matched — `button.primary` only styles buttons — so it
   rendered as plain underlined text in the corner of the bar and nobody saw it.
   Solid Gruvbox yellow with the dark background as its text colour: the one
   element in that bar that is unmistakably pressable. */
/* One height for every control in this bar. Left to their own rules the plain
   buttons and the Download button came out at different heights — 53px against
   35px — which reads as a broken toolbar the moment the row wraps. */
.viewerBar .barCtl > button,.viewerBar .dlBtn{height:30px;padding:0 12px;font-size:12px;
  display:inline-flex;align-items:center;gap:6px;flex:none;white-space:nowrap}
.viewerBar .dlBtn{text-decoration:none;font:inherit;font-size:12px;font-weight:700;padding:0 14px;
  border-radius:6px;border:1px solid var(--yellow);background:var(--yellow);color:#1d2021;cursor:pointer}
.viewerBar .dlBtn:hover{background:#ffd257;border-color:#ffd257;color:#1d2021}
.viewerBar .dlBtn:focus-visible{outline:2px solid var(--bright);outline-offset:2px}
/* UI-23 → UI-44: the viewer wears the same reading font and the same reading size
   as the Group Chat, the file panel and the composer — one pair of custom
   properties, so the controls in the file panel move the text everywhere at once.
   The viewer's own size used to be a third preference beside these; it duplicated
   the page-wide one and made two bars disagree, so it is gone. */
.viewerBody{flex:1;overflow:auto;background:var(--bg);
  font-family:var(--read-family);font-size:var(--read-size)}
.viewerBody img{max-width:100%;display:block;margin:16px auto;border:1px solid var(--line);border-radius:6px}
.viewerBody .notice{padding:24px;color:var(--dim);font-size:13px;text-align:center}
/* UI-44: the text controls left this bar for the file panel, so the only controls
   left here are the family toggle and the Raw/Formatted switch — both sized by
   the `.barCtl > button` rule above, one height for the row. */
/* UI-25: which of the two family buttons is in force. The plain one is the default
   (mono), so only the switched-on state is marked — same idea as the Raw/Formatted
   button: mark the state that changes what you are looking at. */
.viewerBar .famToggle.on{background:var(--bg3);border-color:var(--bg4);color:var(--bright);font-weight:700}
/* UI-23: raw/rendered switch. Yellow while the formatted view is on, because
   that is the state you can be caught out by — what is on screen is no longer
   the file's own characters. */
.viewerBar .fmtBtn.on{background:var(--yellow);border-color:var(--yellow);color:#1d2021;font-weight:700}
.viewerBar .fmtBtn.on:hover{background:#ffd257;border-color:#ffd257}
/* Rendered markdown (UI-23). Prose, not code: no line numbers, and the measure
   is capped so a long document stays readable while the pane is full width. */
.md{padding:22px 28px 60px;max-width:820px;line-height:1.7;color:var(--text)}
.md > :first-child{margin-top:0}
.md h1,.md h2,.md h3,.md h4,.md h5,.md h6{color:var(--bright);line-height:1.3;margin:1.5em 0 .5em;font-weight:700}
.md h1{font-size:1.7em;border-bottom:1px solid var(--line);padding-bottom:.3em}
.md h2{font-size:1.35em;border-bottom:1px solid var(--line);padding-bottom:.25em}
.md h3{font-size:1.15em}
.md h4,.md h5,.md h6{font-size:1em}
/* UI-39: in the file viewer, headings at every level are bright green and bold text
   is bright yellow, so the shape of a markdown file reads at a glance instead of
   needing to be parsed line by line. Both use the palette's own tokens (`--green`
   #b8bb26, `--yellow` #fabd2f) — bright against `--bg` and already the colours of
   "structure" and "emphasis" elsewhere in the page (a file tab's kind, a user's name).

   Scoped to `.viewerBody`, which is only ever the file pane's body (UI-14): the chat
   cards render markdown through the same `md` class and keep the cream headings and
   bold they have always had. Ask was for the file content viewer, and a chat card
   recoloured by it would be a change to a surface nobody asked about.

   A heading's own bold words stay the heading's colour — `## **Note**` renders as
   `<h2><strong>Note</strong></h2>`, so an unscoped bold rule would split one heading
   into two colours. `inherit` on the nested case also catches bold deeper inside. */
.viewerBody .md :is(h1,h2,h3,h4,h5,h6){color:var(--green)}
.viewerBody .md :is(strong,b){color:var(--yellow)}
.viewerBody .md :is(h1,h2,h3,h4,h5,h6) :is(strong,b){color:inherit}
.md p,.md ul,.md ol,.md blockquote,.md table,.md pre{margin:0 0 1em}
.md ul,.md ol{padding-left:1.6em}
.md li{margin:.2em 0}
.md li.task{list-style:none;margin-left:-1.3em}
.md li.task input{margin-right:.5em}
.md a{color:var(--blue);text-underline-offset:2px}
.md code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.92em;
  background:var(--bg0h);border:1px solid var(--line);border-radius:4px;padding:1px 5px}
.md pre{background:var(--bg0h);border:1px solid var(--line);border-radius:8px;padding:12px 14px;
  overflow:auto;font-size:.92em}
.md pre code{background:none;border:none;padding:0;font-size:1em}
.md blockquote{border-left:3px solid var(--bg3);padding:2px 0 2px 14px;color:var(--dim2)}
.md hr{border:none;border-top:1px solid var(--line2);margin:1.6em 0}
.md table{border-collapse:collapse;display:block;overflow:auto;max-width:100%}
.md th,.md td{border:1px solid var(--line);padding:6px 11px;text-align:left}
.md th{background:var(--bg1);color:var(--bright);font-weight:600}
.md img{max-width:100%;border-radius:6px}
.md .mdErr{color:var(--yellow)}
/* UI-23: rendered is not raw — say so, and keep the raw text one click away. */
.md .mdNote{font-size:11px;color:var(--bg4);margin-bottom:16px;font-family:inherit}
/* Code with line numbers. The gutter is sticky so it survives horizontal scroll.
   UI-25: this is the Raw view, and it used to wear a fixed 12.5px monospace no
   matter what the Text controls said — the picker and A−/A+ moved the formatted
   document and left the file's own characters untouched. It now wears exactly what
   the controls say: one size, one reading, no hidden offset. (A `calc(-1.5px)` with
   a `max()` floor was tried first and failed in a real browser: at the small end of
   the slider three settings in a row painted the same size, so A+ looked broken.)
   The family follows `--code-family`, which stays monospace until the bar's
   Mono/Sans button says otherwise — proportional characters lose the column
   alignment a listing depends on. */
.code{display:block;font-size:var(--read-size);line-height:1.5;
  font-family:var(--code-family);padding:10px 0;min-height:100%}
.ln{display:flex;padding:0 14px 0 0}
.ln:hover{background:#32302f}
.ln .num{flex:none;min-width:52px;width:auto;padding-right:14px;text-align:right;color:#665c54;user-select:none;
  position:sticky;left:0;background:var(--bg)}
.ln .src{white-space:pre;tab-size:4}
.t-comment{color:#928374;font-style:italic}
.t-str{color:#b8bb26}
.t-chr{color:#b8bb26}
.t-num{color:#d3869b}
.t-kw{color:#fb4934}
.t-type{color:#fabd2f}
.t-fn{color:#83a598}
.t-macro{color:#8ec07c}
.t-prep{color:#d3869b}
.t-deco{color:#8ec07c}
.t-tag{color:#83a598}
.t-attr{color:#fabd2f}
.t-punct{color:#a89984}
.t-key{color:#83a598}
.t-op{color:#a89984}
.t-ident{color:#ebdbb2}
.overlay{position:fixed;inset:0;background:rgba(29,32,33,.72);display:flex;
  align-items:center;justify-content:center;z-index:50}
.overlay[hidden]{display:none}
/* The dialog is capped and the question gives way first: the four-line answer box
   made the dialog taller than a short window, and the overlay centres without
   scrolling, so the Answer button sat below the edge of the screen where nothing
   could reach it (measured in a real browser at a 300px-tall window: the dialog
   302px tall, the button at 242..280 with the window ending at 300 — and past that
   the button left the window entirely). The question scrolls in its own box; the
   dialog scrolling is the last resort. */
.dialog{background:var(--bg1);border:1px solid var(--bg3);border-radius:10px;padding:20px;
  width:min(560px,92vw);max-height:94vh;overflow:auto;display:flex;flex-direction:column;gap:12px}
.popupAgent{font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:var(--purple);flex:none}
.popupQuestion{white-space:pre-wrap;overflow-wrap:anywhere;font-family:var(--read-family);
  font-size:var(--read-size);flex:none;max-height:28vh;overflow:auto}
/* UI-7: the popup's answer box. A textarea, not an input: an answer is often
   several lines (a list of values, a paragraph, a command) and a single-line field
   forces it all onto one. Four lines are shown from the start (`rows="4"`), it reads
   at the reader's own font, and the corner handle is painted (the browser's own is
   invisible on a dark theme — the same reason the composer got a grip, UI-14).
   `min-height` is the give: in a window too short for four lines plus a long
   question, the box shortens rather than pushing the Answer button off the screen. */
.dialog input,.dialog textarea{background:var(--bg0h);border:1px solid var(--line2);border-radius:6px;
  padding:9px 11px;font:inherit;width:100%}
.dialog textarea{font-family:var(--read-family);font-size:var(--read-size);line-height:1.5;
  resize:vertical;min-height:2.5em;max-height:60vh;overflow:auto}
.dialog textarea::-webkit-resizer{background:var(--bg3);border-radius:0 0 5px 0}
.dialogRow{display:flex;align-items:center;gap:8px}
body.login{display:flex;align-items:center;justify-content:center;height:100vh;background:var(--bg0h)}
.loginCard{background:var(--bg1);border:1px solid var(--line2);border-radius:10px;padding:26px;
  width:320px;display:flex;flex-direction:column;gap:12px}
.loginCard .msg{color:var(--dim);margin:0;font-size:12px;min-height:16px}
.loginCard input{background:var(--bg0h);border:1px solid var(--line2);border-radius:6px;padding:9px 11px;font:inherit}
@media (max-width:1100px){
  #tree{position:fixed;right:0;top:0;bottom:0;z-index:30;box-shadow:-6px 0 18px rgba(0,0,0,.45)}
  /* The drawer is a floating overlay at its own width; a handle on the window's
     edge would resize nothing. */
  #treeGrip{display:none}
}
@media (max-width:820px){
  /* #side and #tree are position:fixed/none here, so #main is the only in-flow
     item left and takes this track — one shrunken row keeps the composer pinned
     (UI-12). */
  #app{grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0,1fr)}
  #side{display:none}
  #sideGrip{display:none}
  /* The header takes over the text controls the hidden sidebar used to own. */
  .barTextCtl{display:flex}
  #tree{width:min(330px,86vw)}
  #main{height:100dvh}
}

/* UI-30: maths. The viewer draws TeX with the palette's own colours rather than a
   library's defaults, so a formula inside a paragraph reads as the prose around it.
   Fractions stack, scripts sit smaller and lower, and a run keeps the text's line
   height so one formula cannot stretch the paragraph holding it (UI-10 rule). */
.tex{font-family:"Latin Modern Math","STIX Two Math",Cambria Math,"Times New Roman",serif;
  white-space:normal;line-height:1;font-size:1.03em}
.tex sup,.tex sub{font-size:.74em;line-height:0}
.tex sup{vertical-align:.52em}.tex sub{vertical-align:-.28em}
.tex-frac{display:inline-flex;flex-direction:column;text-align:center;
  vertical-align:-.45em;margin:0 .14em}
.tex-num{border-bottom:1px solid currentColor;padding:0 .2em .1em}
.tex-den{padding:.1em .2em 0}
.tex-sqrt{display:inline-flex;align-items:flex-start}
.tex-rad{font-size:1.08em}
.tex-under{border-top:1px solid currentColor;padding:.08em .12em 0;margin-left:-.06em}
.tex-index{font-size:.64em;align-self:flex-start;transform:translate(.12em,.5em)}
.tex-acc{position:relative;display:inline-block}
.tex-accmark{position:absolute;left:0;right:0;top:-.58em;text-align:center;font-size:.86em}
/* The environment markup the renderer actually emits is .tex-env > .tex-delim +
   .tex-grid > .tex-row > .tex-cell. Rows are flex rows and columns line up because
   every cell reports the same flex basis, so `&` alignment holds without a table. */
.tex-env{display:inline-flex;align-items:center;vertical-align:middle;margin:0 .1em}
.tex-delim{font-size:1.24em;line-height:1;font-style:normal}
/* Two or more rows: stretch the bracket rather than leave a small glyph beside a
   tall grid. A scale on the glyph is an approximation of TeX's stretch — the
   curvature is slightly off, the height is not. */
.tex-env-tall{align-items:stretch}
.tex-env-tall .tex-delim{display:flex;align-items:center;transform:scaleY(1.75)}
.tex-env-tall .tex-grid{justify-content:center}
.tex-grid{display:inline-flex;flex-direction:column;gap:.2em;padding:.14em .42em}
.tex-row{display:flex;gap:.62em;align-items:baseline}
.tex-cell{display:block;flex:0 1 auto;text-align:center;min-width:2.2em}
.tex-cellsep{display:inline-block;width:1.1em}
.tex-sp{display:inline-block;width:.28em}
.tex-thin{display:inline-block;width:.17em}
.tex-quad{display:inline-block;width:1em}
.tex-bounded{display:inline-block;width:.22em}
.tex-block{display:block;margin:.7em 0;text-align:center;overflow-x:auto;overflow-y:hidden}
.tex-block .tex{display:inline-block;font-size:1.08em}
.tex-op{font-style:normal;padding:0 .06em}
.tex-sym{font-style:normal}
.tex-var{font-style:italic}
.tex-unk{font-style:normal;text-decoration:underline dotted;text-underline-offset:.22em}
.tex-katex .katex{font-size:1em}
@media (prefers-reduced-motion:reduce){.tex *{transition:none}}
