/* team.helloprimercherang.com — Primer Cherang: brand black surfaces (#1e1f1c), brand red (#ca2127), gold from the logo frame. */

:root {
  --bg:        #111210;
  --bg-1:      #171816;
  --bg-2:      #1e1f1c;
  --bg-3:      #272825;
  --border:    #2c2d2a;
  --border-2:  #3a3b37;
  --fg:        #ececed;
  --fg-dim:    #9a9aa4;
  --fg-faint:  #64646e;
  /* Priority: red, yellow, blue, low to high in hue distance. */
  --prio-high: #f87171;
  --prio-med:  #facc15;
  --prio-low:  #60a5fa;
  --accent:    #ec5157;   /* brand red lifted for text: 5.2:1 on --bg */
  --accent-dim:#ca2127;
  --accent-fill:#ca2127;   /* the brand red itself, for anything filled: white on it is 5.6:1 */
  --gold:      #d4a843;
  --green:     #4ade80;
  --red:       #f87171;
  --amber:     #fbbf24;
  --blue:      #7aa2f7;
  --violet:    #b48ce8;
  /* Named for the job rather than the shade, so a second theme is a second
     list of values and not a second copy of the stylesheet. Everything below
     here was a literal somewhere in the rules. */
  --sel:        #2b1516;   /* accent-tinted surface: a chosen row, a live tab */
  --sel-soft:   #1d1314;   /* the same idea, quieter */
  --sel-strong: #3b1719;   /* and louder — a toggle that is on */
  --on-accent:  #ffffff;   /* text sitting on an accent fill */
  --shadow:     #000;      /* what a raised surface casts */
  --hair:       #1b1c19;   /* a divider lighter than --border */
  --scroll:     #2b2b32;
  --scroll-hi:  #3a3a44;
  --mark:       #5d4a12;   /* find-in-file */
  --selection:  #3b5b8c88;
  --danger-bg:  #2a1414;
  --danger-br:  #5a2626;
  --danger-fg:  #fca5a5;
  --danger-solid: #7f1d1d;
  /* Diff: the band behind a line, and the text on it. */
  --add-bg: #16351f;  --add-cell: #12331f;  --add-line: #0e2417;  --add-fg: #b7f4cf;
  --del-bg: #3a1d1d;  --del-cell: #3a1a1a;  --del-line: #2a1416;  --del-fg: #f8c4c4;
  --chg-bg: #2c2a16;
  --gap-a:  #1b1b1b;  --gap-b: #141414;     /* the hatch where a side has nothing */
  /* Syntax. The one place a light theme cannot simply reuse the dark values:
     these are all tuned to sit on near-black. */
  --hl-comment: #63707f;  --hl-keyword: var(--violet); --hl-type:   #d98cf0;
  --hl-string:  #7fd6a2;  --hl-number:  #f0a56a;       --hl-title:  #7aa2f7;
  --hl-class:   #f0c674;  --hl-attr:    #9fd0e0;       --hl-meta:   #8b98ad;
  --hl-del:     #f28b8b;  --hl-add:     #7fd6a2;

  --mono: "JetBrains Mono", "SF Mono", ui-monospace, Menlo, Consolas, monospace;
  --sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, sans-serif;
}

/* The light theme is the same list of jobs with different values, never a
   second set of rules. Written on [data-theme] rather than only in a media
   query so a deliberate choice can override what the system asks for; the
   script sets that attribute before the first paint.

   Not an inversion. Dark backgrounds get lighter and dark text gets darker,
   but the accent stays put — it is the brand — and the tinted surfaces are
   re-picked by eye against white rather than computed, because #2a1d12
   lightened is mud. */
:root[data-theme="light"] {
  --bg:        #fbfbfa;
  --bg-1:      #f4f4f2;
  --bg-2:      #ededea;
  --bg-3:      #e3e3df;
  --border:    #dcdcd6;
  --border-2:  #c4c4bc;
  --fg:        #1c1c1e;
  --fg-dim:    #57575f;
  --fg-faint:  #85858f;
  /* The yellow is darker than a true yellow: on a near-white card the real
     thing measures 1.85:1 and this is the lightest that clears 3:1. */
  --prio-high: #b91c1c;
  --prio-med:  #b07d00;
  --prio-low:  #2563eb;
  /* The same orange, darkened until it clears 4.5:1 both as text on the page
     (4.81) and as a fill under white (4.98). #c25a1d looked right and measured
     4.25 and 4.40 — close enough to pass by eye and not by number. */
  --accent:    #ca2127;
  --accent-dim:#a81a20;
  --accent-fill:#ca2127;
  --gold:      #a8842a;
  --green:     #15803d;
  --red:       #b91c1c;
  --amber:     #a16207;
  --blue:      #1d4ed8;
  --violet:    #7c3aed;

  --sel:        #fbe3e3;
  --sel-soft:   #f9eeee;
  --sel-strong: #f7d3d4;
  --on-accent:  #ffffff;   /* plain white on the accent fill: 5.6 */
  /* A shadow on white is a grey, not a black: black at any opacity reads as
     dirt rather than depth. */
  --shadow:     #6b6b74;
  --hair:       #e8e8e4;
  --scroll:     #cacac4;
  --scroll-hi:  #b0b0a8;
  --mark:       #fde68a;
  --selection:  #bcd4f5;
  --danger-bg:  #fdeaea;
  --danger-br:  #e9b4b4;
  --danger-fg:  #a01818;
  --danger-solid: #b91c1c;

  --add-bg: #d8f2e0;  --add-cell: #ddf3e4;  --add-line: #e6f7ec;  --add-fg: #14532d;
  --del-bg: #fbdede;  --del-cell: #fce1e1;  --del-line: #fdeaea;  --del-fg: #7f1d1d;
  --chg-bg: #faf0cc;
  --gap-a:  #ebebe6;  --gap-b: #f3f3ef;

  --hl-comment: #6b7280;  --hl-keyword: #7c3aed;  --hl-type:   #a21caf;
  --hl-string:  #15803d;  --hl-number:  #b45309;  --hl-title:  #1d4ed8;
  --hl-class:   #a16207;  --hl-attr:    #0e7490;  --hl-meta:   #52525b;
  --hl-del:     #b91c1c;  --hl-add:     #15803d;
}

/* Following the system is resolved in script rather than repeated here: the
   attribute is set to light or dark before the first paint, and again if the
   system changes while the tab is open. One list of values, one place to edit
   it — a media-query copy would be a second list to keep in step. */
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"] { color-scheme: dark; }

* { box-sizing: border-box; }
/* [hidden] is display:none at low specificity, so any class that sets an
   explicit display (.btn, iframe, flex panes) would otherwise beat it. */
[hidden] { display: none !important; }
html, body { height: 100%; margin: 0; }
/* No double-tap zoom, and taps register without the 300ms wait that came with
   it. Pinch is handled in the client, since Safari ignores user-scalable=no. */
html { touch-action: manipulation; -webkit-text-size-adjust: 100%; }
body {
  background: var(--bg); color: var(--fg);
  font: 14px/1.5 var(--sans);
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
}
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
button { font: inherit; }
.mono { font-family: var(--mono); }

::-webkit-scrollbar { width: 9px; height: 9px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--scroll); border-radius: 5px; border: 2px solid var(--bg); }
::-webkit-scrollbar-thumb:hover { background: var(--scroll-hi); }

/* ------------------------------------------------------------- wordmark */
.wordmark { display: inline-flex; align-items: center; gap: 9px; font-family: var(--mono); font-size: 13.5px; font-weight: 600; color: var(--fg); }
.wordmark .mark { width: 15px; height: 15px; flex: 0 0 auto; fill: currentColor; color: var(--accent); }
.wordmark .logo { display: block; height: 24px; width: auto; max-width: 100%; object-fit: contain; object-position: left; border-radius: 2px; }
.card .wordmark .logo { height: 46px; }
.card.signin { max-width: 360px; display: flex; flex-direction: column; align-items: center; gap: 28px; }
.card.signin .wordmark { max-width: 100%; }
.card.signin .logo { height: auto; width: 100%; max-width: 280px; }
.wordmark .dim { color: var(--fg-faint); font-weight: 400; }

/* ---------------------------------------------------------------- login */
.center { min-height: 100%; display: grid; place-items: center; padding: 32px;
  background: radial-gradient(800px 460px at 50% -10%, rgba(240,133,74,.10), transparent 65%); }
.card { width: 100%; max-width: 400px; background: linear-gradient(180deg, var(--bg-2), var(--bg-1));
  border: 1px solid var(--border); border-radius: 14px; padding: 32px; box-shadow: 0 24px 60px -20px var(--shadow); }
.card h1 { margin: 20px 0 6px; font-size: 19px; font-weight: 600; letter-spacing: -.015em; }
.card p.sub { margin: 0 0 22px; color: var(--fg-dim); font-size: 13.5px; }
.card .legal { margin: 20px 0 0; padding-top: 16px; border-top: 1px solid var(--border); color: var(--fg-faint); font-size: 12px; line-height: 1.6; }

/* --------------------------------------------------------------- shared */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 8px 14px; border-radius: 8px; border: 1px solid var(--border-2);
  background: var(--bg-3); color: var(--fg); font-size: 13px; font-weight: 500; cursor: pointer;
  transition: border-color .14s, background .14s;
}
.btn:hover:not(:disabled) { border-color: var(--accent); }
.btn:disabled { opacity: .45; cursor: default; }
.btn.primary { background: var(--accent-fill); border-color: var(--accent); color: var(--on-accent); font-weight: 600; }
.btn.primary:hover:not(:disabled) { background: #f5955f; }
.btn.wide { width: 100%; }
.btn.sm { padding: 5px 10px; font-size: 12px; border-radius: 7px; }
.btn.ghost { background: transparent; border-color: transparent; color: var(--fg-dim); }
.btn.ghost:hover { background: var(--bg-2); color: var(--fg); border-color: transparent; }
.btn.danger:hover { border-color: var(--red); color: var(--red); }

.pill { display: inline-flex; align-items: center; gap: 5px; padding: 2px 8px; border-radius: 999px;
  font-size: 11px; font-family: var(--mono); border: 1px solid var(--border-2); color: var(--fg-dim); white-space: nowrap; }
.pill .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--fg-faint); }
.pill.ok { color: var(--green); border-color: #4ade8044; } .pill.ok .dot { background: var(--green); }
.pill.run { color: var(--accent); border-color: #ec515755; } .pill.run .dot { background: var(--accent-fill); box-shadow: 0 0 7px var(--accent); }
.pill.bad { color: var(--red); border-color: #f8717144; } .pill.bad .dot { background: var(--red); }
.pill.warn { color: var(--amber); border-color: #fbbf2444; } .pill.warn .dot { background: var(--amber); }

.face { width: 22px; height: 22px; border-radius: 50%; overflow: hidden; flex: 0 0 auto;
  display: grid; place-items: center; font-family: var(--mono); font-size: 10px; font-weight: 600;
  color: var(--on-accent); background: linear-gradient(135deg, #ca2127, #d4a843); }
.face img { width: 100%; height: 100%; object-fit: cover; }

input, textarea, select {
  background: var(--bg); color: var(--fg); border: 1px solid var(--border-2);
  border-radius: 8px; padding: 8px 10px; font: inherit; font-size: 13px; outline: none; width: 100%;
}
input:focus, textarea:focus, select:focus { border-color: var(--accent); }
textarea { resize: vertical; }
label.fld { display: block; margin-bottom: 5px; color: var(--fg-dim); font-size: 12px; }
.field { margin-bottom: 13px; }

/* ============================================================== layout */
/* The shell was a hard 100% tall; with a banner above it that would overflow
   the viewport by the banner's height, so the body owns the column instead. */
body { display: flex; flex-direction: column; }
#shell { display: flex; flex: 1 1 auto; min-height: 0; }

/* Not dismissible: it disappears the moment GitLab is connected, and every
   git-facing action in the app fails without it. */
/* The first thing on the page, so it is what sits under the status bar when the
   app is installed to a home screen — the clock and the battery were printed
   over the message. The tab bar already keeps clear of the inset at the bottom;
   this is the same idea at the top, and it costs nothing in a browser tab where
   the inset is zero. */
/* Two hairlines across the top: the five-hour allowance above, the week below.
 *
 * Every agent here spends from one account, so these are the whole team's and
 * somebody starting a long job at 90% is about to stop everybody. Fixed rather
 * than in the flow — they must not move the panes when they appear, and a
 * measurement that costs layout is a measurement worth less than the pixels.
 *
 * Two of the thinnest thing that can still be seen, stacked. The track is dark
 * so an empty bar reads as a seam rather than as a thing that failed to load.
 */
/* In the flow rather than fixed, as the first thing in the column: fixed would
   overlay the topbar, and body cannot take padding — it carries height: 100%,
   so padding there makes the column taller than the screen. Being in the flow
   it also takes the status-bar inset, and the panes below stop claiming it, the
   same arrangement the GitLab banner already uses. */
/* The strip is also what you point at, so it has to be bigger than what it
   draws: three hairlines and their gaps come to 8px, which is a hard target
   with a mouse and an unreasonable one with a thumb. It used to buy that room
   with padding underneath, but padding is space, and a band of empty between
   the bars and the header is what it looked like. The ::after below takes the
   room without taking the space: out of flow, so nothing moves for it.
   1px above the first line, which is the gap the other two already have between
   them — without it the top bar sits against the edge and the three do not read
   as one set. */
#usebars { position: relative; flex: 0 0 auto; padding-bottom: 0;
  padding-top: calc(1px + env(safe-area-inset-top, 0px));
  background: var(--bg); cursor: default; }
/* Kept short: it lies over whatever is beneath, and the header under it has
   buttons near its top edge. 6px is enough to aim at and not enough to reach
   them — checked by hit-testing the header's own controls. */
#usebars::after { content: ''; position: absolute; left: 0; right: 0;
  top: 100%; height: 6px; }
/* In the middle column on a desktop, where it is the width of the conversation
   rather than of the whole window. No status bar to clear in there. */
#center > #usebars { padding-top: 1px; background: transparent; }
#usebars[hidden] { display: none !important; }
#usebars .ubar { position: relative; height: 2px; background: #ffffff0f; }
#usebars .ubar + .ubar { margin-top: 1px; }
#usebars .ubar > s, #usebars .ubar > i, #usebars .ubar > u {
  position: absolute; top: 0; bottom: 0; left: 0; }
/* How far through the window, under everything: while spending is behind the
   clock this is the grey the colour stops short of, which is the whole point —
   colour shorter than grey means there is room. */
#usebars .ubar > s { width: 0; background: #ffffff26; transition: width 1s linear; }
#usebars .ubar > i { width: 0; background: var(--green);
  transition: width .6s ease, background-color .6s ease; }
/* And the mark for the same instant, over the fill rather than under it. Once
   spending passes the clock the grey is painted over and this is the only thing
   left saying where the window had got to — a dark notch cut through the
   colour, which is where it is easiest to see and the only place it matters. */
#usebars .ubar > u { width: 2px; margin-left: -1px; background: var(--bg);
  opacity: .85; transition: left 1s linear; }
#usebars .ubar > u[hidden] { display: none; }
#usebars .ubar[hidden] { display: none; }
#usebars .ubar.warn > i { background: var(--amber); }
#usebars .ubar.hot  > i { background: var(--red); }

/* The numbers behind the bars. Hangs off the strip rather than following the
   pointer: there are only three rows and they are always the same three, so a
   fixed place to look beats one that moves. */
#usepop { position: absolute; top: 100%; left: 10px; z-index: 320;
  background: var(--bg-2); border: 1px solid var(--border-2); border-radius: 9px;
  padding: 8px 11px; box-shadow: 0 16px 40px -12px var(--shadow);
  font-size: 11.5px; line-height: 1.7; color: var(--fg-dim); white-space: nowrap; }
#usepop[hidden] { display: none; }
#usepop .row { display: flex; align-items: baseline; gap: 10px; }
#usepop .k { color: var(--fg); font-family: var(--mono); font-size: 11px;
  min-width: 66px; }
#usepop .v { flex: 1 1 auto; }
#usepop .dot { width: 6px; height: 6px; border-radius: 50%; flex: 0 0 auto;
  background: var(--green); }
#usepop .dot.warn { background: var(--amber); }
#usepop .dot.hot { background: var(--red); }
#usepop .foot { margin-top: 5px; padding-top: 5px; border-top: 1px solid var(--border);
  color: var(--fg-faint); font-size: 10.5px; white-space: normal; max-width: 260px; }
/* On a phone it would run off the right edge from a left anchor. */
@media (max-width: 820px) {
  #usepop { left: 8px; right: 8px; white-space: normal; }
}
/* Green until it matters, amber when a long job would be a gamble, red when the
   next one probably will not finish. */

#glBanner { flex: 0 0 auto; display: flex; align-items: center; gap: 10px;
  padding: calc(8px + env(safe-area-inset-top, 0px)) 14px 8px;
  padding-left: calc(14px + env(safe-area-inset-left, 0px));
  padding-right: calc(14px + env(safe-area-inset-right, 0px));
  background: var(--sel-strong); border-bottom: 1px solid #ec515755;
  font-size: 12.5px; color: var(--fg); }
#authBanner { flex: 0 0 auto; display: flex; align-items: center; gap: 10px;
  padding: calc(8px + env(safe-area-inset-top, 0px)) 14px 8px;
  padding-left: calc(14px + env(safe-area-inset-left, 0px));
  padding-right: calc(14px + env(safe-area-inset-right, 0px));
  background: var(--sel-strong); border-bottom: 1px solid #ec515755;
  font-size: 12.5px; color: var(--fg); }
#authBanner { background: var(--danger-bg); border-bottom: 1px solid var(--danger-br); }
#authBanner[hidden] { display: none; }
#authBanner .msg { font-size: 12.5px; }
/* The command to type, selectable: the person who can run it is at a terminal,
   and retyping a path from a screenshot is how typos get made. */
#authBanner .cmd { flex: 0 0 auto; padding: 3px 8px; border-radius: 6px;
  background: #00000055; border: 1px solid var(--danger-br); color: var(--fg);
  font: 11px var(--mono); user-select: all; }
@media (max-width: 820px) { #authBanner .cmd { display: none; } }

/* The bars above already cleared the status bar; claiming the inset twice would
   open a band of empty above the banner. */
body.has-usebars #glBanner { padding-top: 8px; }
#glBanner[hidden] { display: none !important; }
#glBanner .msg { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#glBanner .grow { flex: 1 1 auto; }
#glBanner .btn { flex: 0 0 auto; }
.gldot.warn { background: var(--accent-fill); box-shadow: 0 0 6px var(--accent); }

/* ---------------------------------------------------------- 1. sidebar */
/* `flex-shrink: 1` with a floor, rather than refusing to move.
   Both side panes used to hold their full width however narrow the window
   got, and the chat took whatever was left — which at 860px was 116px of
   wrapped fragments. They give way together now, each down to a width that is
   still worth having, and the chat keeps a floor of its own.

   The three floors come to 820, which is the widest they can be and still fit
   the narrowest window that shows three panes — one pixel above the phone
   breakpoint. Any larger and there is a band just above 820 where the panes
   overflow the window rather than fitting it. */
#side { width: 264px; flex: 0 1 auto; min-width: 170px; display: flex; flex-direction: column;
  background: var(--bg-1); border-right: 1px solid var(--border); min-height: 0; position: relative; }
#lgrip { position: absolute; right: -3px; top: 0; bottom: 0; width: 6px; cursor: col-resize; z-index: 3;
  touch-action: none; }
#lgrip:hover, #lgrip.dragging { background: #ec515755; }
#side.dragging { user-select: none; cursor: row-resize; }
/* 264px is only the starting width now that the sidebar is draggable, so this
   row matters more, not less: the wordmark shrinks and truncates to keep the
   controls inside the edge at any width. */
#side .top { display: flex; align-items: center; gap: 8px; height: 48px;
  padding: 0 12px 0 14px; flex: 0 0 auto; overflow: hidden; }
#side .top .grow { flex: 1 1 auto; min-width: 0; }
#side .top .wordmark { flex: 0 1 auto; min-width: 0; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
#side .top .btn { flex: 0 0 auto; }
/* The full plate, on the one row. Tighter buttons are what buy it the width. */
#side .top { gap: 2px; padding-left: 12px; padding-right: 10px; }
#side .top .wordmark { margin-right: 6px; }
#side .top .wordmark .logo { height: 18px; width: auto; max-width: 100%; border-radius: 2px; }
/* Icon-only button: square, so it does not read as a truncated label. */
.btn.icon { width: 28px; padding: 0; display: grid; place-items: center; }
.btn.icon svg { width: 15px; height: 15px; }
/* An <a> styled as a button needs the centring too. */
a.btn.icon { display: grid; place-items: center; }

.gldot { width: 6px; height: 6px; border-radius: 50%; flex: 0 0 auto;
  background: var(--green); box-shadow: 0 0 6px var(--green); }
#side .newbtn { margin: 4px 10px 10px; }
#side .scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden; padding: 0 8px 10px; }
/* A pinned row can be dragged into an order. Half-there while it is carried,
   so the gap it will leave is visible. */
.wsitem.pinned[draggable] { cursor: grab; }
.wsitem.pinned[draggable]:active { cursor: grabbing; }
.wsitem.pindrag { opacity: .4; }

/* The team roster keeps its own space at the foot of the sidebar. It never
   shrinks below a couple of rows, never takes more than a third of the height,
   and scrolls internally once the team outgrows that. */
/* The grip owns the division now, so no fixed ceiling — but a floor and a
   ceiling remain as a guard: if script never runs, the team list must not eat
   the workspace list, and dragging must not hide it entirely. The border moves
   to the grip, which is the line between them. */
#side .teampane { flex: 0 0 auto; min-height: 44px; max-height: 80%;
  overflow-y: auto; overflow-x: hidden; padding: 0 8px 8px; }
#side .teampane .grouphd { position: sticky; top: 0; z-index: 1; background: var(--bg-1); }
#side .grouphd { display: flex; align-items: center; gap: 6px; padding: 10px 8px 6px;
  color: var(--fg-faint); font-size: 10.5px; text-transform: uppercase; letter-spacing: .08em; }
#side .grouphd .grow { flex: 1 1 auto; }
/* This is the way into the archive, and on a phone a bare 10px word is not
   something you can reliably hit. Padding rather than a bigger font, so it
   stays a quiet header control. */
#side .grouphd button { background: none; border: none; color: var(--fg-faint); cursor: pointer;
  font-size: 10.5px; padding: 8px 6px; margin: -8px -6px; border-radius: 5px; }
#side .grouphd button:hover { color: var(--accent); }

.wsitem { display: flex; gap: 9px; width: 100%; max-width: 100%; padding: 8px 9px; border-radius: 8px;
  background: none; border: none; border-left: 2px solid transparent; color: var(--fg-dim);
  text-align: left; cursor: pointer; align-items: flex-start; }
.wsitem:hover { background: var(--bg-2); color: var(--fg); }
/* Under another workspace: indented, with a rail down the side. A plain line
   rather than an elbow per row — an elbow's foot lands against the row's own
   left border, which is the orange bar when that workspace is selected, and the
   two crossing looked like a mistake. Consecutive children share one unbroken
   rail, which also reads better than a stack of brackets. */
.wsitem.child { margin-left: 16px; position: relative; }
.wsitem.child::before { content: ''; position: absolute; left: -9px; top: 0; bottom: 0;
  width: 1px; background: var(--border-2); }
/* Rounded at the ends of a run, so the rail starts and stops rather than being
   cut off by whatever follows. */
.wsitem.child:not(.child + .wsitem.child)::before { top: 6px; border-radius: 1px; }
.wsitem.child:not(:has(+ .wsitem.child))::before { bottom: 6px; border-radius: 1px; }
.wsitem.active { background: var(--bg-3); color: var(--fg); border-left-color: var(--accent); }
.wsitem:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.wsitem .body { flex: 1 1 auto; min-width: 0; overflow: hidden; }

/* creator avatar: small enough not to compete with the status dot */
.face.tiny { width: 15px; height: 15px; font-size: 8px; flex: 0 0 auto; }
.pill.by { display: inline-flex; align-items: center; gap: 5px; padding-left: 4px; }
.pill.by img { width: 14px; height: 14px; border-radius: 50%; object-fit: cover; }

/* Pin: hidden until the row is hovered, but always shown once pinned —
   otherwise a pinned row gives no clue why it is sitting at the top. */
.wsitem .pin { flex: 0 0 auto; width: 20px; height: 20px; padding: 0; border: none;
  border-radius: 5px; background: none; color: var(--fg-faint); cursor: pointer;
  display: grid; place-items: center; opacity: 0; transition: opacity .12s; }
.wsitem .pin svg { width: 11px; height: 11px; display: block; fill: currentColor; }
.wsitem:hover .pin, .wsitem .pin:focus-visible { opacity: 1; }
.wsitem .pin:hover { background: var(--bg-3); color: var(--fg); }
.wsitem .pin.on { opacity: 1; color: var(--accent); }
/* An archived row: dimmed text, but its actions stay at full strength —
   greyed-out buttons read as disabled ones. */
/* The archive toggle now sits in the one list header. Lit when it is on, so
   the state is visible without reading the word; disabled while filtering,
   because a filter reaches the archive whatever the toggle says and a button
   claiming otherwise would be lying. */
#side .grouphd #btnArchToggle.on { color: var(--accent); }
#side .grouphd #btnArchToggle:disabled { opacity: .4; cursor: default; }
#side .grouphd #btnArchToggle:disabled:hover { color: var(--fg-faint); }

/* Filtering the list. Full width and quiet: it is used often enough to want no
   ceremony and rarely enough that it should not shout. */
#wsFindRow { position: relative; padding: 0 10px 7px; }
/* Room on the right for the clear button, always — reserving it only when the
   button is there would shuffle the text sideways as you type the first letter.
   Safari draws its own ✕ on a search input and it is neither styleable nor
   present anywhere else, so it goes and this replaces it. */
#wsFind::-webkit-search-cancel-button { display: none; }
#wsFindClear { position: absolute; right: 14px; top: 1px;
  border: none; background: none; cursor: pointer; color: var(--fg-faint);
  font-size: 15px; line-height: 1; padding: 3px 4px; }
#wsFindClear[hidden] { display: none; }
#wsFindClear:hover { color: var(--fg); }
#wsFind { width: 100%; padding: 5px 22px 5px 8px; border-radius: 7px;
  border: 1px solid var(--border); background: var(--bg); color: var(--fg);
  font: 11.5px var(--mono); }
#wsFind::placeholder { color: var(--fg-faint); }
#wsFind:focus { outline: none; border-color: var(--border-2); }

/* The workspace's number. Quiet and monospaced so it reads as a handle rather
   than as part of the name, and narrow enough that a title still gets the room. */
.wsnum { color: var(--fg-faint); font: 11px var(--mono); margin-right: 6px; }
.wsnum.head { border: 1px solid var(--border); background: var(--bg-2); cursor: pointer;
  border-radius: 6px; padding: 2px 7px; margin-right: 8px; flex: 0 0 auto; }
.wsnum.head:hover { border-color: var(--accent); color: var(--accent); }

/* A label on the workspace header. Violet so it does not read as a branch or an
   issue, which are the other two chips beside it. */
#wsMeta .pill.label { border-color: #7c5cff44; color: #b9a6ff; cursor: pointer;
  padding-right: 3px; }
#wsMeta .pill.label:hover { border-color: #7c5cff88; }
#wsMeta .pill.label .rm { border: none; background: none; cursor: pointer;
  color: inherit; opacity: .5; font-size: 12px; line-height: 1; padding: 0 3px; }
#wsMeta .pill.label .rm:hover { opacity: 1; color: var(--red); }
#wsMeta .pill.addlabel { border-style: dashed; color: var(--fg-faint); cursor: pointer;
  background: none; padding: 2px 7px; }
#wsMeta .pill.addlabel:hover { color: var(--fg); border-color: var(--border-2); }
/* The ones already in play, offered so the vocabulary converges instead of
   growing a synonym every time somebody labels something. */
/* Typing a secret: hidden by default, with a way to look at what you typed. */
.peekwrap { position: relative; display: block; }
.peekwrap input { width: 100%; padding-right: 46px; }
/* Keeps the lines it was given — a PEM has them and losing them breaks the key.
   `pre` rather than `pre-wrap`: a wrapped line looks like a line, and a key you
   are checking by eye should not appear to have breaks it does not have. */
.peekwrap .secval { width: 100%; padding-right: 46px; resize: vertical;
  white-space: pre; overflow-x: auto; line-height: 1.45; font-family: var(--mono); }
/* The same masking a password input does, applied to a box that can hold lines.
   Letter-spacing off, or the dots stack up wider than the text they stand for. */
.peekwrap .secval.hushed { -webkit-text-security: disc; text-security: disc; }
/* Top rather than centred: with several lines there is no middle to sit in. */
.peekwrap .secval ~ .peek { top: 8px; transform: none; }
.peekwrap .peek { position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  border: none; background: none; cursor: pointer; color: var(--fg-faint);
  font: 10.5px var(--mono); padding: 3px 4px; }
.peekwrap .peek:hover { color: var(--fg); }

.lblused { display: flex; flex-wrap: wrap; gap: 6px; margin: 9px 0 4px; }
.lblused .pill { cursor: pointer; }
.lblused .pill:hover { border-color: var(--accent); color: var(--fg); }
.wsitem.arch .t, .wsitem.arch .m, .wsitem.arch .age { opacity: .55; }
.wsitem .wsacts { display: flex; gap: 6px; margin-top: 8px; }
.wsitem .wsacts button { min-height: 30px; padding: 5px 11px; border-radius: 6px;
  border: 1px solid var(--border); background: var(--bg-1); color: var(--fg-dim);
  font: 11.5px var(--sans); cursor: pointer; }
.wsitem .wsacts button:hover { border-color: var(--border-2); color: var(--fg); }
.wsitem .wsacts .del:hover { border-color: var(--red); color: var(--red); }

.wsitem .t { font-size: 13px; line-height: 1.35; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wsitem .m { margin-top: 3px; display: flex; align-items: center; gap: 6px; color: var(--fg-faint); font-size: 10.5px; font-family: var(--mono); }
.wsitem .m .br { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wsitem .age { flex: 0 0 auto; color: var(--fg-faint); font-size: 10.5px; font-family: var(--mono); }
.wsitem .stat { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; margin-top: 5px; background: var(--fg-faint); }
.wsitem .stat.working { background: var(--accent-fill); box-shadow: 0 0 8px var(--accent); animation: pulse 1.4s ease-in-out infinite; }
/* A monitored job still running: the green counterpart of the amber above, and
   deliberately the same pulse at the same rate — one means the agent is
   working, the other means something it started still is. Only ever one of the
   two, because statusClass returns `working` first: when the agent itself is
   going, that is the more immediate fact and this would only dilute it. */
.wsitem .stat.job { background: var(--green); box-shadow: 0 0 8px var(--green); animation: pulse 1.4s ease-in-out infinite; }
.wsitem .stat.review  { background: var(--green); }
.wsitem .stat.mr      { background: var(--blue); }
/* Merged, but not yet on the branch that counts as live. Amber because it is
   waiting on a release rather than on a person. */
.wsitem .stat.staged  { background: var(--amber); }
.wsitem .stat.merged  { background: var(--violet); }
.wsitem .stat.error   { background: var(--red); }
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: .35; } }

#side .foot { flex: 0 0 auto; border-top: 1px solid var(--border); padding: 8px 10px;
  display: flex; align-items: center; gap: 8px; }
#side .foot .grow { flex: 1 1 auto; }
#side .foot .who { font-size: 12px; color: var(--fg-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.teamrow { display: flex; align-items: center; gap: 8px; padding: 6px 9px; border-radius: 7px; color: var(--fg-dim); font-size: 12.5px; }
.teamrow .face { width: 20px; height: 20px; font-size: 9px; }
/* presence dot: lit when connected, hollow when away */
.teamrow .pdot { width: 6px; height: 6px; border-radius: 50%; flex: 0 0 auto;
  background: var(--green); box-shadow: 0 0 6px var(--green); }
/* Three states, and the hue carries one meaning while the weight carries the
   other: green means present on the site, filled means in front of it. So here
   now is filled and lit, open-but-elsewhere is a green ring, and gone is a faint
   hollow one. Not amber — amber means "this wants you" everywhere else in the
   app, and somebody stepping away is not asking for anything. */
.teamrow .pdot.away { background: transparent; box-shadow: none;
  border: 1.5px solid var(--green); opacity: .75; }
.teamrow .pdot:not(.on):not(.away) { background: transparent; box-shadow: none;
  border: 1px solid var(--fg-faint); }
.teamrow.away { opacity: .55; }
/* The branch is the useful half of this row, so it gets the room: it may take
   most of the width, and the first name — short, and already legible from the
   avatar — is what gives way. 96px used to clip almost every branch name. */
.teamrow .nm { flex: 0 1 auto; min-width: 34px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.teamrow .where { margin-left: auto; padding-left: 4px; color: var(--fg-faint);
  font-size: 10.5px; font-family: var(--mono); flex: 0 1 auto;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 66%; }

/* ------------------------------------------------------------ 2. agent */
/* The floor that makes the shrinking mean something: without it the others
   would still be asked to give way last. */
#center { flex: 1 1 0; display: flex; flex-direction: column; min-width: 340px; min-height: 0;
  position: relative; }   /* anchors the file-drop overlay */
/* Two rows: the name and its actions on top, the chips beneath. The chips grew
   past what one line could hold — creator, every repo, the branch — and were
   squeezing the title. */
#wsbar { display: flex; flex-direction: column; gap: 6px; padding: 9px 14px 10px; flex: 0 0 auto;
  border-bottom: 1px solid var(--border); }
#wsbar .wsbar-top { display: flex; align-items: center; gap: 10px; min-height: 30px; }
#wsbar .title { flex: 0 1 auto; min-width: 60px; font-size: 14px; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#wsbar .meta { display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  color: var(--fg-faint); font-size: 11.5px; font-family: var(--mono); }
#wsbar .meta:empty { display: none; }
#wsbar .grow { flex: 1 1 auto; min-width: 8px; }
#wsActions { flex: 0 0 auto; display: flex; align-items: center; gap: 6px; white-space: nowrap; }

#tape { flex: 1 1 auto; overflow-y: auto; padding: 18px 18px 6px; display: flex; flex-direction: column; gap: 2px; }
#tape::before { content: ""; flex: 1 1 auto; }
#tape > * { flex: 0 0 auto; }

.ev { display: flex; gap: 10px; padding: 5px 6px; border-radius: 8px; }
.ev .gut { width: 22px; flex: 0 0 auto; display: flex; justify-content: center; padding-top: 2px; }
/* The agent's own messages have nothing in the gutter, so it was pure indent —
   it left the prose starting well right of the tool summaries between it. The
   text columns line up now, with the tool caret hanging in the margin.
       assistant:  6 padding + 8 gutter + 10 gap = 24px
       tool run :  6 padding + 10 caret + 8 gap = 24px            */
.ev.assistant .gut, .ev.thinking .gut { width: 8px; }
.ev .bd { flex: 1 1 auto; min-width: 0; }
.ev.user { background: var(--sel-soft); border: 1px solid var(--border); margin: 10px 0 6px; }
/* pre-wrap stays: the Ask modal reuses this bubble for a question it prints
   verbatim, and that is where its line breaks come from. */
.ev.user .bd { font-size: 13.5px; white-space: pre-wrap; overflow-wrap: anywhere; }
/* A chat message is rendered markdown, so its blank lines are already paragraph
   gaps — kept as pre-wrap they would be counted twice. */
.ev.user .utext { white-space: normal; }
.ev.user .who { color: var(--accent); font-size: 11px; font-family: var(--mono); margin-bottom: 3px; }
.ev .md { font-size: 13.5px; line-height: 1.6; overflow-wrap: anywhere; }
.ev .md p { margin: 0 0 9px; }
.ev .md p:last-child { margin-bottom: 0; }
.ev .md ul, .ev .md ol { margin: 0 0 9px; padding-left: 20px; }
.ev .md li { margin: 2px 0; }
.ev .md code { font-family: var(--mono); font-size: 12px; background: var(--bg-3);
  border: 1px solid var(--border); border-radius: 4px; padding: 1px 5px; }
.ev .md pre { background: var(--bg-1); border: 1px solid var(--border); border-radius: 8px;
  padding: 10px 12px; overflow-x: auto; margin: 0; }
.ev .md .codeblock { position: relative; margin: 0 0 10px; }
.ev .md pre code { background: none; border: none; padding: 0; font-size: 12px; line-height: 1.5; }
.ev.thinking .bd { color: var(--fg-faint); font-size: 12.5px; font-style: italic; white-space: pre-wrap; }

/* A collapsed tool call: a caret, the name, and the argument on one line. */
.tool { display: flex; align-items: baseline; gap: 8px; font-family: var(--mono); font-size: 12px;
  padding: 3px 0; color: var(--fg-dim); cursor: pointer; user-select: none; }
.tool .tw { color: var(--fg-faint); font-size: 9px; width: 10px; flex: 0 0 auto; }
.tool:hover .tw { color: var(--accent); }
/* A run of tool calls, standing as one line until opened. */
.toolrun { margin: 2px 0; }
.runhead { display: flex; align-items: baseline; gap: 8px; padding: 3px 0 3px 6px; cursor: pointer;
  user-select: none; font-family: var(--mono); font-size: 12px; color: var(--fg-faint); }
.runhead:hover { color: var(--fg-dim); }
/* Centred in its box, so the glyph's centre is a known number rather than
   whatever the font happens to do: 6px padding + half of 10px = 11px. */
.runhead .tw { width: 10px; flex: 0 0 auto; font-size: 9px; text-align: center; }
.runhead:hover .tw { color: var(--accent); }
.runhead .fail { color: var(--red); font-size: 11px; }
.runhead.err .sum { color: var(--danger-fg); }
/* Top level, not nested inside .runhead — the body is its sibling, not its child. */
.runbody.folded { display: none; }
/* The rule hangs from the caret above it: 10.5px + half the 1px border puts
   its centre at 11px, exactly under the caret. The content still needs room to
   breathe beside it, hence the padding. */
.runbody { border-left: 1px solid var(--border); margin-left: 10.5px; padding-left: 14px; }
.tool .nm { color: var(--violet); flex: 0 0 auto; }
.tool .arg { color: var(--fg-faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  cursor: pointer; }
.tool .arg:hover { color: var(--fg-dim); }
/* Opened: the whole command, wrapped. `anywhere` because a shell line has no
   spaces to break at where it matters — a long path or a URL would otherwise
   push the pane sideways. */
.tool.open { align-items: flex-start; }
.tool.open .arg { white-space: pre-wrap; overflow-wrap: anywhere; overflow: visible;
  text-overflow: clip; color: var(--fg-dim); }
.tool.err .nm { color: var(--red); }
.toolout { font-family: var(--mono); font-size: 11.5px; color: var(--fg-faint); white-space: pre-wrap;
  background: var(--bg-1); border: 1px solid var(--border); border-radius: 7px; padding: 7px 9px;
  margin: 3px 0 6px; max-height: 200px; overflow: auto; }
.toolout.err { color: var(--danger-fg); border-color: #7f1d1d55; }

.result { display: flex; align-items: center; gap: 10px; margin: 8px 0 14px;
  color: var(--fg-faint); font-size: 11px; font-family: var(--mono); }
.result::before, .result::after { content: ""; flex: 1 1 auto; height: 1px; background: var(--border); }
.evnote { color: var(--fg-faint); font-size: 11.5px; font-family: var(--mono); padding: 4px 8px; }
.everr { color: var(--danger-fg); font-size: 12px; padding: 6px 9px; background: var(--danger-bg); border: 1px solid #7f1d1d55; border-radius: 8px; margin: 6px 0; }

#composer { flex: 0 0 auto; padding: 8px 16px 16px; }
#composer .box { display: flex; align-items: flex-end; gap: 9px; background: var(--bg-2);
  border: 1px solid var(--border-2); border-radius: 11px; padding: 9px;
  position: relative; }   /* anchors the slash-command menu above it */
#composer .box:focus-within { border-color: var(--accent); }
/* The send button is 30px, so it set the row's height while a one-line
   textarea was only 22px and bottom-aligned — the placeholder sat low. Matching
   the button's height and padding the text to the middle centres it, and
   box-sizing: border-box keeps autosize's scrollHeight arithmetic correct. */
#composer textarea { border: none; background: transparent; max-height: 200px;
  min-height: 30px; padding: 5px 0 4px; font-size: 13.5px; }
/* Nothing to say to an agent that has been put away. Dimmed rather than hidden:
   the transcript is still worth reading, and a composer that vanishes reads as
   something broken. */
#composer.shelved { opacity: .55; }
#composer.shelved textarea { cursor: not-allowed; }
#composer .tools { display: flex; align-items: center; gap: 8px; padding: 7px 4px 0; color: var(--fg-faint); font-size: 11px; }
#composer .tools .grow { flex: 1 1 auto; }
#composer .send { flex: 0 0 auto; width: 30px; height: 30px; border-radius: 8px; display: grid; place-items: center;
  background: var(--bg-3); border: 1px solid var(--border-2); color: var(--fg-dim); cursor: pointer; }
#composer .send:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
#composer .send:disabled { opacity: .4; cursor: default; }
#composer .send svg { width: 14px; height: 14px; }
/* One button, two icons: whichever the current state calls for. */
#composer .send .ic-stop { display: none; }
#composer .send.stop .ic-send { display: none; }
#composer .send.stop .ic-stop { display: block; }
#composer .send.stop { background: var(--sel-strong); border-color: var(--accent); color: var(--accent); opacity: 1; }
#composer .send.stop:hover { background: var(--sel); }

/* ------------------------------------------------------------ 3. review */
#right { width: 480px; flex: 0 1 auto; min-width: 310px; display: flex; flex-direction: column;
  border-left: 1px solid var(--border); background: var(--bg-1); min-height: 0; position: relative;
  /* Whatever is inside stays inside: the panes do their own scrolling, and a
     stray wide row should not make the window scroll. */
  overflow: hidden; }
#rgrip { position: absolute; left: -3px; top: 0; bottom: 0; width: 6px; cursor: col-resize; z-index: 3;
  touch-action: none; }
/* Six pixels is a mouse target, not a finger one. The hit area grows either
   side on a touch screen; the visible line does not move. */
@media (pointer: coarse) {
  #lgrip::after, #rgrip::after { content: ''; position: absolute; top: 0; bottom: 0;
    left: -9px; right: -9px; }
}
#rgrip:hover, #rgrip.dragging { background: #ec515755; }
body.resizing { user-select: none; }
body.resizing iframe { pointer-events: none; }

/* Five tabs and a refresh button come to more than a narrow right pane, and a
   row that cannot shrink pushes the whole page sideways rather than being
   clipped. It scrolls instead; the buttons keep their size. */
#rtabs { display: flex; gap: 2px; padding: 8px 10px; flex: 0 0 auto;
  border-bottom: 1px solid var(--border); overflow-x: auto; scrollbar-width: none; }
#rtabs::-webkit-scrollbar { display: none; }
#rtabs button { flex: 0 0 auto; }
#rtabs button { padding: 5px 11px; border-radius: 7px; background: none; border: none;
  color: var(--fg-dim); font-size: 12.5px; cursor: pointer; }
#rtabs button:hover { background: var(--bg-2); color: var(--fg); }
#rtabs button.active { background: var(--bg-3); color: var(--fg); box-shadow: inset 0 0 0 1px var(--border-2); }
#rtabs .grow { flex: 1 1 auto; }

/* The button that opens files and the terminal. An icon, not a word: it sits
   with refresh at the far end because it opens something rather than switching
   what this pane is showing, and the four words to its left are the choice. */
#rtabs .btn.icon { display: inline-flex; align-items: center; justify-content: center;
  padding: 4px 7px; }
#rtabs .btn.icon svg { width: 15px; height: 15px; display: block; }

.rpane { flex: 1 1 auto; overflow: auto; min-height: 0; display: none; }
.rpane.on { display: block; }
.rpane.flex.on { display: flex; flex-direction: column; }

/* diff — the list on top, the patch below, same shape as the Files pane */
/* The list is the pane now; there is no second half to share with. */
#diffList { flex: 1 1 auto; overflow: auto; min-height: 0; }
/* Nothing picked yet: the grip and the empty half stay out of the way rather
   than reserving a blank bottom third. */
#paneDiff.nopatch #diffList { flex: 1 1 auto; }
#paneDiff.dragging { user-select: none; cursor: row-resize; }

.dfile { display: flex; align-items: center; gap: 8px; padding: 6px 12px; cursor: pointer;
  border-bottom: 1px solid var(--hair); font-family: var(--mono); font-size: 11.5px; color: var(--fg-dim); }
.dfile:hover { background: var(--bg-2); color: var(--fg); }
.dfile.active { background: var(--bg-3); color: var(--fg); }
/* The filename holds its ground and the directory gives way. Truncating the
   whole path from the left ate the name as well, which is the one part that
   identifies the row. */
.dfile .nm { flex: 1 1 auto; min-width: 0; display: flex; align-items: baseline; }
/* rtl on the box puts the ellipsis at the start, so what survives is the folder
   the file is actually in. The bdi inside restores the reading order, keeping
   the trailing slash where it belongs. */
.dfile .nm .dir { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; direction: rtl; text-align: left; color: var(--fg-faint); }
.dfile .nm .dir > bdi { direction: ltr; unicode-bidi: isolate; }
.dfile .nm .fn { flex: 0 0 auto; }
.dfile .add { color: var(--green); } .dfile .del { color: var(--red); }
.patch { font-family: var(--mono); font-size: 11.5px; line-height: 1.55; white-space: pre; overflow-x: auto; padding: 8px 0 20px; }
/* As wide as the widest line, and never narrower than the pane: that is what
   lets a line's background run the full width instead of stopping at the edge
   of what happens to be on screen. */
.plines { width: max-content; min-width: 100%; }
.patch .l { padding: 0 12px; }
.patch .l.h { color: var(--fg-faint); background: var(--bg-2); }
.patch .l.a { color: var(--add-fg); background: var(--add-line); }
.patch .l.d { color: var(--del-fg); background: var(--del-line); }
.patch .l.m { color: var(--fg-dim); }
.patch .l.f { color: var(--accent); background: var(--sel-soft); font-weight: 600; }

/* checks */
/* The switch above the diff. Sticky, because the list it filters is long and
   the answer to "where did the rest go" should not require scrolling back. */
.difffilter { position: sticky; top: 0; z-index: 2; display: flex; align-items: center; gap: 8px;
  padding: 9px 14px; background: var(--bg-1); border-bottom: 1px solid var(--line);
  color: var(--fg-dim); font-size: 12px; cursor: pointer; }
.difffilter input { width: auto; margin: 0; accent-color: var(--accent); }
.difffilter .grow { flex: 1 1 auto; }
.difffilter .cnt { color: var(--fg-faint); font-size: 11px; font-family: var(--mono); }

.chk { padding: 12px 14px; }
.chk h3 { margin: 0 0 10px; font-size: 11px; text-transform: uppercase; letter-spacing: .07em; color: var(--fg-faint); font-weight: 500; }
.chk .row { display: flex; align-items: center; gap: 9px; padding: 7px 0; border-bottom: 1px solid var(--hair); font-size: 12.5px; }
.chk .row .grow { flex: 1 1 auto; }
.chk .row .lbl { font-family: var(--mono); font-size: 11.5px; color: var(--fg-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* When the base branch was last looked at, and what has landed on it since:
   true but secondary to the number itself. */
.chk .row .lbl .dim, .chk .row .lbl.dim { color: var(--fg-faint); }
.chk .row .lbl code { font-family: inherit; color: var(--fg); }

/* The bell, and how many are waiting behind it. */
#btnBell { position: relative; }
#btnBell .ndot { position: absolute; top: 2px; right: 2px; min-width: 15px; height: 15px;
  padding: 0 3px; border-radius: 999px; background: var(--accent-fill); color: var(--on-accent);
  font-size: 9.5px; line-height: 15px; font-weight: 600; text-align: center; }
#btnBell .ndot[hidden] { display: none; }

/* Feedback waiting, as a dot rather than a count.
   The bell counts because each mention is a separate thing addressed to you.
   This is a queue you work through, where the only question the button has to
   answer is whether it is worth opening — a number on it invites reading the
   number instead. The ring is in the button's own background, so the dot stays
   separate from the icon stroke behind it. */
#btnInbox { position: relative; }
#btnInbox .ndot { position: absolute; top: -1px; right: -2px; min-width: 14px; height: 14px;
  padding: 0 3px; border-radius: 999px; background: var(--accent-fill); color: var(--on-accent);
  font-size: 9px; line-height: 14px; font-weight: 700; text-align: center;
  box-shadow: 0 0 0 1.5px var(--bg); font-variant-numeric: tabular-nums; }
#btnInbox .ndot[hidden] { display: none; }

.notes-list { display: flex; flex-direction: column; gap: 2px; margin: 4px 0 18px;
  max-height: 60vh; overflow: auto; }
.notif { display: flex; align-items: flex-start; gap: 10px; width: 100%;
  padding: 10px 12px; border: 1px solid transparent; border-radius: 8px;
  background: none; color: var(--fg-dim); cursor: pointer; text-align: left;
  font-family: inherit; font-size: 13px; line-height: 1.5; }
.notif:hover { background: var(--bg-2); border-color: var(--line); }
.notif.unread { background: var(--bg-2); border-color: var(--border); color: var(--fg); }
.notif .face { flex: 0 0 auto; width: 22px; height: 22px; border-radius: 50%; overflow: hidden;
  background: var(--bg-3); display: flex; align-items: center; justify-content: center;
  font-size: 10px; color: var(--fg-dim); }
.notif .face img { width: 100%; height: 100%; object-fit: cover; }
.notif .bd { flex: 1 1 auto; min-width: 0; }
.notif .hd { display: flex; gap: 8px; align-items: baseline; }
.notif .hd .who { color: var(--fg); font-weight: 500; }
.notif .hd .where { color: var(--fg-faint); font-size: 11px; font-family: var(--mono);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.notif .hd .when { margin-left: auto; flex: 0 0 auto; color: var(--fg-faint); font-size: 11px; }
.notif .msg { margin-top: 2px; overflow-wrap: anywhere; }
.notif .msg .mention { color: var(--accent); }

/* A name inside a message, and the note that the message was for people. */
.ev.user .mention { color: var(--accent); font-weight: 500; }
.ev.user .aside { margin-left: 8px; padding: 1px 6px; border-radius: 999px;
  border: 1px solid var(--line); color: var(--fg-faint);
  font-size: 10px; font-family: var(--mono); letter-spacing: .02em; }
.chk .block { margin-bottom: 32px; }
.chk .block:last-child { margin-bottom: 4px; }
/* The last row in a section keeps no rule beneath it: a trailing border sitting
   in the gap reads as a divider between nothing, and blunts the separation the
   whitespace is there to give. */
.chk .block .row:last-child { border-bottom: none; }
.chk .empty { color: var(--fg-faint); font-size: 12px; padding: 6px 0; }

/* terminal + run */
#termhost, #runhost { flex: 1 1 auto; min-height: 0; }
#termhost { padding: 6px 0 0 8px; }
/* ---------------------------------------------------------- run cards */

#runRepos { padding: 8px; display: flex; flex-direction: column; gap: 8px; }

.runcard { border: 1px solid var(--border); border-radius: 10px; padding: 9px 11px 10px;
  background: var(--bg-1); cursor: pointer; display: flex; flex-direction: column; gap: 3px; }
.runcard:hover { border-color: var(--border-2); }
/* The selected card is the one whose log and preview are showing below. */
.runcard.sel { border-color: var(--accent); background: var(--sel-soft); cursor: default; }

.runcard .rctop { display: flex; align-items: center; gap: 9px; }
.runcard .rctop .grow { flex: 1 1 auto; }
.runcard .nm { font-family: var(--mono); font-size: 12.5px; color: var(--fg);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* A button in this row keeps its own width. Left to shrink — the default for a
   flex child — a long job name squeezed "■ Stop" until the icon and the word
   sat on separate lines, which on a phone is most of them. The name is the
   thing that should give way here, and it already truncates. */
.runcard .rctop .btn { flex: 0 0 auto; white-space: nowrap; }
.runcard .rctop .nm { flex: 0 1 auto; min-width: 0; }
.runcard .rcnote { color: var(--fg-faint); font-size: 11.5px; }

.runcard .stat { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; }
.runcard .stat.up { background: var(--green); box-shadow: 0 0 7px var(--green); }
.runcard .stat.down { background: transparent; border: 1px solid var(--fg-faint); }

.runcard .rcurl { font-family: var(--mono); font-size: 11px; color: var(--fg-dim);
  text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  align-self: flex-start; max-width: 100%; }
.runcard a.rcurl:hover { color: var(--accent); }
.runcard .rcurl.off { color: var(--fg-faint); }

/* Subdomains an agent published, which this app routes but does not run. */
.expblock { margin-top: 14px; display: flex; flex-direction: column; gap: 8px; }
.exphd { color: var(--fg-faint); font-size: 10.5px;
  text-transform: uppercase; letter-spacing: .06em; }
.runcard.exp { cursor: default; }
.runcard.exp:hover { border-color: var(--border); }

/* A long job with the agent watching it. Two states matter more than the rest:
   still going, and stopped because it needs a person. The second one gets amber
   and a steady ring — it is a request, not a failure, and it should read as
   something waiting rather than something broken. */
.runcard .stat.bad  { background: var(--red); box-shadow: 0 0 7px var(--red); }
.runcard .stat.wait { background: var(--amber); box-shadow: 0 0 0 3px #fbbf2422; }
/* Finished well. Green says it went fine; hollow and unlit says it is over, so
   it never reads as a job still working. */
.runcard .stat.done { background: transparent; border: 1.5px solid var(--green); opacity: .6; }
/* And a running job breathes, so "still going" is legible without reading the
   line under it. Slow and shallow — it should be noticed, not watched. */
.runcard.mon .stat.up { --glow: var(--green); animation: dotpulse 2.4s ease-in-out infinite; }
/* Shared with a running helper, which breathes at the same rate in its own
   colour — the two panes say "still going" the same way. */
@keyframes dotpulse {
  0%, 100% { box-shadow: 0 0 7px var(--glow); }
  50%      { box-shadow: 0 0 3px var(--glow); }
}
@media (prefers-reduced-motion: reduce) {
  .runcard.mon .stat.up, .helper.running .hdot { animation: none; }
}
.runcard.mon .moncmd { font-family: var(--mono); font-size: 11px; color: var(--fg-faint);
  margin-top: 5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  max-width: 100%; }
.runcard.mon .monrow { display: flex; align-items: center; gap: 9px; }
/* .rcurl pins itself to the top for the URL under a repo card. In this row it
   sits beside a 34px button, so left as-is its text rides high and the two read
   as separate lines. The button keeps its height — it is a tap target. */
.runcard.mon .monrow .rcurl { align-self: center; }
.runcard.mon .monrow .grow { flex: 1 1 auto; }
.runcard.mon .monlog { flex: 0 0 auto; }
/* The log is the whole point of opening it, so let it have the room. */
pre.monout { font-family: var(--mono); font-size: 11.5px; line-height: 1.5;
  color: var(--fg-dim); background: var(--bg-1); border: 1px solid var(--border);
  border-radius: 6px; padding: 10px 12px; margin: 4px 0 12px;
  /* No wrapping. A job's output is columns — a counter, an id, a path, a status
     — and wrapping folds every long line into two or three, so the shape that
     makes it scannable is gone and twice as much scrolling buys the same
     content. Long lines scroll sideways instead, which is what a terminal does
     with them. */
  max-height: 60vh; overflow: auto; white-space: pre; }

.runcard .rcsets { display: flex; flex-direction: column; gap: 6px; margin-top: 7px; }
.runcard .rcset { display: flex; align-items: center; gap: 9px; }
/* Fixed label column, so the controls line up down the card. */
.runcard .rcset .k { width: 62px; flex: 0 0 auto; color: var(--fg-faint);
  font-size: 10.5px; text-transform: uppercase; letter-spacing: .06em; }
.runcard .rcicon { width: 24px; padding: 0; display: grid; place-items: center; flex: 0 0 auto; }

/* A setting with two states: the current one is filled, the other recedes. */
.seg2 { display: inline-flex; flex: 0 0 auto; border: 1px solid var(--border);
  border-radius: 7px; overflow: hidden; background: var(--bg); }
.seg2 button { padding: 3px 11px; border: none; background: none; cursor: pointer;
  color: var(--fg-faint); font-family: var(--mono); font-size: 11px; }
.seg2 button:hover:not(.on) { color: var(--fg); background: var(--bg-2); }
/* Three choices rather than two, and it sits under a heading rather than at
   the end of a row, so it gets its own line. */
.themepick { margin-bottom: 4px; }
.seg2 button.on { background: var(--accent-fill); color: var(--on-accent); }

.runlog { flex: 1 1 auto; overflow: auto; font-family: var(--mono); font-size: 11.5px; line-height: 1.5;
  white-space: pre-wrap; padding: 10px 12px; color: var(--fg-dim); min-height: 0; }
#preview { flex: 1 1 auto; border: 0; width: 100%; background: #fff; min-height: 0; }

/* Draggable divider between the preview and the run log. The grab area is
   taller than the visible line so it is not a 1px target. */
.hsplit { flex: 0 0 9px; position: relative; cursor: row-resize; background: var(--bg-2);
  border-top: 1px solid var(--border); border-bottom: 1px solid var(--border);
  /* The browser decides what a touch gesture means before any handler runs, and
     on a 9px strip inside a scrolling pane it decides "scroll". `none` hands the
     gesture to the drag instead — without it the pane scrolls under your finger
     and the split never moves. */
  touch-action: none; -webkit-user-select: none; user-select: none;
  -webkit-tap-highlight-color: transparent; }
.hsplit::after { content: ''; position: absolute; left: 50%; top: 50%; width: 34px; height: 3px;
  transform: translate(-50%, -50%); border-radius: 2px; background: var(--border); }
.hsplit:hover, .hsplit.active { background: var(--bg-3); }
.hsplit:hover::after, .hsplit.active::after { background: var(--accent-fill); }
.hsplit:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* An iframe swallows mousemove, so the drag would stall the moment the pointer
   crossed into the preview. Disable pointer events on the pane while dragging. */
#paneRun.dragging iframe { pointer-events: none; }
#paneRun.dragging { user-select: none; cursor: row-resize; }

/* ---------------------------------------------------------------- files */

.fsearch { flex: 0 0 auto; display: flex; align-items: center; gap: 6px;
           padding: 6px 8px; border-bottom: 1px solid var(--border); }
.fmode { display: flex; flex: 0 0 auto; border: 1px solid var(--border); border-radius: 7px;
         overflow: hidden; background: var(--bg-1); }
.fmode button { margin: 0; padding: 4px 8px; border: none; border-radius: 0; background: none;
                color: var(--fg-faint); font-size: 10px; line-height: 1.4; cursor: pointer; }
.fmode button + button { border-left: 1px solid var(--border); }
.fmode button:hover { color: var(--fg); }
.fmode button.on { background: var(--bg-3); color: var(--accent); }

/* git grep results: a file, then the lines under it. */
.gfile { display: flex; align-items: center; gap: 6px; padding: 5px 10px 3px 8px;
         cursor: pointer; font-size: 11px; color: var(--fg); }
.gfile:hover { color: var(--accent); }
.gfile.active { color: var(--accent); }
.gfile > span:first-child { min-width: 0; overflow: hidden;
                            white-space: nowrap; text-overflow: ellipsis; }
.gfile .dim { color: var(--fg-faint); }
.gfile .n { margin-left: auto; padding-left: 10px; color: var(--fg-faint); font-size: 10px; }
.ghit { display: flex; gap: 8px; padding: 2px 10px 2px 20px; cursor: pointer;
        font-family: var(--mono); font-size: 10.5px; color: var(--fg-dim); }
.ghit:hover { background: var(--bg-2); color: var(--fg); }
.ghit.active { background: var(--bg-3); }
.ghit .ln { flex: 0 0 auto; min-width: 30px; text-align: right; color: var(--fg-faint); }
.ghit .tx { min-width: 0; overflow: hidden; white-space: pre; text-overflow: ellipsis; }
.ghit mark { background: var(--mark); color: inherit;
             border-radius: 2px; padding: 0 1px; }
.fsearch input { width: 100%; margin: 0; padding: 5px 9px; border-radius: 7px;
  background: var(--bg-1); border: 1px solid var(--border); color: var(--fg);
  font-family: var(--mono); font-size: 11.5px; }
.fsearch input:focus { outline: none; border-color: var(--accent); }
/* ---- Explorer: the tree on the left, the file on the right ---- */
/* Beats `.rpane.flex.on`, which stacks everything in a column. */
#paneFiles { flex-direction: row; }
#fileSide { position: relative; flex: 0 0 auto; width: 260px; min-width: 150px;
  display: flex; flex-direction: column; min-height: 0;
  border-right: 1px solid var(--border); }
/* Wider than it looks, because a 1px target is a test of aim rather than a
   control — the same grip the diff editor's file list uses. */
#fileSide .grip { touch-action: none;
  position: absolute; top: 0; right: -3px; bottom: 0; width: 7px;
  cursor: col-resize; z-index: 2; }
#fileSide .grip:hover, #fileSide .grip.dragging { background: var(--accent-fill); opacity: .5; }
/* Fills its column now rather than being sized by a divider. */
#fileTree { flex: 1 1 auto; overflow: auto; min-height: 0; padding: 4px 0; }
.frow .nm .dim { color: var(--fg-faint); }
.frow { display: flex; align-items: center; gap: 6px; padding: 3px 10px 3px 0; cursor: pointer;
  font-family: var(--mono); font-size: 11.5px; color: var(--fg-dim); white-space: nowrap; }
.frow:hover { background: var(--bg-2); color: var(--fg); }
.frow.active { background: var(--bg-3); color: var(--accent); }
.frow .tw { width: 10px; flex: 0 0 auto; color: var(--fg-faint); font-size: 9px; }
.frow .nm { overflow: hidden; text-overflow: ellipsis; }
.frow.dir .nm { color: var(--fg); }
.frow .sz { margin-left: auto; padding-left: 10px; color: var(--fg-faint); font-size: 10px; }

#fileEdit { flex: 1 1 auto; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
#fileEdit .fbar { display: flex; align-items: center; gap: 8px; padding: 6px 10px; flex: 0 0 auto;
  border-bottom: 1px solid var(--border); }
#fileEdit .fbar .grow { flex: 1 1 auto; }
.fback { display: none; margin: 0; padding: 0 6px; border: none; background: none; flex: 0 0 auto;
         color: var(--fg-dim); font-size: 18px; line-height: 1; cursor: pointer; }

/* A phone has room for the list or the file, not both: side by side left the
   editor a hundred pixels wide. One at a time, full width, the bar switching
   between them — the same answer the attachment viewer got. */
@media (max-width: 820px) {
  #fileSide { width: auto !important; flex: 1 1 auto; min-width: 0; }
  #fileSide .grip { display: none; }
  #paneFiles.showedit #fileSide { display: none; }
  #paneFiles:not(.showedit) #fileEdit { display: none; }
  #paneFiles.showedit .fback { display: block; }
}
#fileEdit .fpath { font-family: var(--mono); font-size: 11px; color: var(--fg-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; direction: rtl; text-align: left; }
#fileEdit .dirty { font-size: 10px; color: var(--accent); flex: 0 0 auto; }
/* The highlighted copy sits underneath a transparent textarea. Both must use
   identical font, padding, wrapping and tab-size or the caret drifts away from
   the glyphs it is supposed to sit between. */
.edwrap { position: relative; flex: 1 1 auto; min-height: 0; overflow: hidden; background: var(--bg-1);
  --gut: 2ch; }
.edwrap .edhi, .edwrap textarea {
  position: absolute; inset: 0; margin: 0; border: 0; border-radius: 0;
  font-family: var(--mono); font-size: 12px; line-height: 1.55; padding: 10px 12px;
  white-space: pre; tab-size: 2; overflow: auto; }
/* Room for the numbers, taken from the left of both layers so the text they
   belong to starts where they end. */
.edwrap .edhi, .edwrap textarea { padding-left: calc(var(--gut) + 18px); }
/* The numbers themselves: same metrics as the text, or they drift a line every
   screenful. Fixed to the left and scrolled only up and down — a long line
   moves the code sideways underneath them, not them. */
.edwrap .edgut {
  position: absolute; top: 0; bottom: 0; left: 0; width: calc(var(--gut) + 14px);
  z-index: 3; pointer-events: none; overflow: hidden;
  margin: 0; padding: 10px 6px 10px 0; box-sizing: border-box;
  font-family: var(--mono); font-size: 12px; line-height: 1.55;
  white-space: pre; text-align: right;
  color: var(--fg-faint); background: var(--bg-1);
  border-right: 1px solid var(--border); }
.edwrap .edgut i { display: block; }
/* The sign travels with the number, and takes its colour from what happened to
   the line — the same green and red as the band it labels. */
.edwrap .edgut i.plus { color: var(--green); }
.edwrap .edgut i.minus { color: var(--red); }
.edwrap .edhi { pointer-events: none; background: none; color: var(--fg); z-index: 1; }
.edwrap .edhi code { font: inherit; background: none; padding: 0; display: block; }
.edwrap textarea {
  z-index: 2; background: transparent; resize: none;
  color: transparent; -webkit-text-fill-color: transparent; caret-color: var(--accent); }
.edwrap textarea::selection { background: var(--selection); }
/* Mid-keystroke: the textarea shows its own text and the highlighted layer
   steps aside, so a character appears the moment it is typed. The colours come
   back when the typing stops. */
.edwrap.raw textarea { color: var(--fg); -webkit-text-fill-color: var(--fg); }
/* display rather than visibility: a hidden layer is still laid out, and this
   one is a couple of hundred kilobytes of markup that the browser would go on
   reflowing after every keystroke for nothing. */
.edwrap.raw .edhi { display: none; }

/* One band per line, behind the code, saying what happened to that line. Right
   at the back so the syntax colours and the find marks both sit on top of it,
   and the full width of the scrolled content so a long line is banded to its
   end rather than to the edge of the box. */
.edwrap .edstripes {
  position: absolute; inset: 0; z-index: 0; pointer-events: none; overflow: hidden;
  padding: 10px 0; font-family: var(--mono); font-size: 12px; line-height: 1.55;
  white-space: pre; }
/* One line box per line, holding a space — laid out by the same code as the
   text, rather than by a pixel height guessed to match it. */
.edwrap .edstripes i { display: block; }
.edwrap .edstripes i.add { background: var(--add-bg); }
.edwrap .edstripes i.del { background: var(--del-bg); }
.edwrap .edstripes i.chg { background: var(--chg-bg); }
/* A row that is only here to line up with the other side. Hatched rather than
   coloured: nothing was added or removed here, there is simply nothing. */
.edwrap .edstripes i.gap { background:
  repeating-linear-gradient(135deg, var(--gap-a) 0 6px, var(--gap-b) 6px 12px); }

/* find-in-file matches, marked in the highlighted layer beneath the textarea.
   The text stays its syntax colour; only the background changes, so a match
   inside a string or keyword is still readable as what it is. */
.edwrap .edhi mark { background: var(--mark); color: inherit; border-radius: 2px; }
.edwrap .edhi mark.on { background: var(--accent-fill); color: var(--on-accent); }

/* A file the agent is handing over. A card rather than a line of text, because
   it is a thing to take away and not something to read past. */
.ev.assistant.filedrop .bd { padding: 0; }
.fcard { display: flex; align-items: center; gap: 10px; padding: 10px 12px;
  border: 1px solid var(--border-2); border-radius: 10px; background: var(--bg-2); }
.fcard .fic { flex: 0 0 auto; width: 30px; height: 30px; border-radius: 8px;
  display: flex; align-items: center; justify-content: center;
  background: var(--sel); color: var(--accent); font-size: 15px; }
.fcard .fmeta { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.fcard .fnm { font-size: 13px; font-weight: 500; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.fcard .fsub { color: var(--fg-faint); font: 11px var(--mono); overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.fcard .grow { flex: 1 1 auto; }
.fcard .btn { flex: 0 0 auto; }
@media (max-width: 820px) {
  /* The name needs the width more than the two buttons need to sit beside it. */
  .fcard { flex-wrap: wrap; }
  .fcard .fmeta { flex: 1 1 100%; order: 1; }
  .fcard .fic { order: 0; }
  .fcard .btn { order: 2; }
}

/* A question the agent stopped to ask. Its own card so it reads as something
   waiting on you rather than as more of what the agent was saying. */
.ev.assistant.question .bd { border: 1px solid var(--border-2); border-radius: 10px;
  background: var(--bg-2); padding: 12px; }
/* One the agent wrote out instead of calling the tool for, which is all plan
   mode allows it. Inside an assistant bubble rather than being one, so it needs
   its own edges. */
.ev.assistant .bd .qset.written { border: 1px solid var(--border-2); border-radius: 10px;
  background: var(--bg-2); padding: 12px; margin: 8px 0; }
.qset:focus { outline: none; }
.qset:focus-visible { outline: 1px solid var(--border-2); outline-offset: 3px; }

/* One question at a time. Four stacked questions with four options each is a
   wall, and what is being asked gets lost in it. */
.qtabs { display: flex; gap: 4px; margin-bottom: 10px; overflow-x: auto; }
.qtab { display: flex; align-items: center; gap: 5px; flex: 0 0 auto;
  padding: 4px 10px; border-radius: 999px; border: 1px solid var(--border);
  background: var(--bg-1); color: var(--fg-dim); cursor: pointer;
  font: 11px var(--mono); white-space: nowrap; }
.qtab:hover { border-color: var(--border-2); color: var(--fg); }
.qtab.on { border-color: var(--accent); color: var(--fg); background: var(--sel); }
/* A tick against the ones already settled, so four tabs say how far you are. */
.qtab .qmark { color: var(--fg-faint); font-size: 10px; }
.qblock { display: none; }
.qblock.on { display: block; }
.qtag { display: inline-block; margin-bottom: 5px; padding: 1px 7px; border-radius: 999px;
  background: var(--bg-3); color: var(--fg-dim); font: 10.5px var(--mono);
  text-transform: uppercase; letter-spacing: .05em; }
.qq { font-size: 13.5px; font-weight: 600; margin-bottom: 8px; }
.qopts { display: flex; flex-direction: column; gap: 6px; }
.qopt { display: flex; align-items: flex-start; gap: 9px;
  padding: 8px 11px; border: 1px solid var(--border); border-radius: 8px;
  background: var(--bg-1); color: var(--fg); cursor: pointer; text-align: left; width: 100%; }
.qopt:hover:not(:disabled) { border-color: var(--border-2); background: var(--bg-3); }
.qopt.on { border-color: var(--accent); background: var(--sel); }
.qopt:disabled { cursor: default; }
.qset.done .qopt.on { border-color: var(--accent); background: var(--sel); }
.qset.done .qopt:not(.on) { opacity: .5; }
/* The number you can press instead of aiming at it. */
.qopt .qnum { flex: 0 0 auto; width: 17px; height: 17px; margin-top: 1px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 4px; background: var(--bg-3); border: 1px solid var(--border);
  color: var(--fg-faint); font: 10.5px var(--mono); }
.qopt.on .qnum { background: var(--accent-fill); border-color: var(--accent); color: var(--on-accent); }
.qopt .qbody { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.qopt .ql { font-size: 13px; }
.qopt .qd { font-size: 11.5px; color: var(--fg-dim); }
.qopt.other .ql { color: var(--fg-dim); }
.qtext { width: 100%; margin-top: 6px; padding: 7px 9px; border-radius: 8px;
  border: 1px solid var(--border); background: var(--bg-1); color: var(--fg); font-size: 13px; }
.qtext:focus { outline: none; border-color: var(--accent); }
.qtext[hidden] { display: none; }
.qfoot { display: flex; align-items: center; gap: 8px; margin-top: 10px; }
.qfoot[hidden] { display: none; }
.qfoot .grow { flex: 1 1 auto; }
.qstate { font-size: 11.5px; color: var(--fg-faint); }

/* Answered, and folded back to what was decided. */
.qsum { display: flex; flex-direction: column; gap: 3px; }
.qsum[hidden] { display: none; }
.qsum .srow { display: flex; gap: 8px; font-size: 12.5px; }
.qsum .sq { color: var(--fg-faint); min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; max-width: 45%; }
.qsum .sa { color: var(--accent); font-weight: 500; }

/* The answer, under the question. */
.ev.answer .gut { width: 8px; color: var(--fg-faint); }
.ev.answer .bd { font-size: 12.5px; }
.ev.answer .arow { display: flex; gap: 8px; margin: 1px 0; }
.ev.answer .aq { color: var(--fg-faint); min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; max-width: 45%; }
.ev.answer .aa { color: var(--accent); font-weight: 500; }

/* Pull to refresh. Sits above the panes and below the dialogs, following the
   finger down from under the header. */
#ptr { position: fixed; top: 6px; left: 50%; z-index: 90;
  width: 30px; height: 30px; margin-left: -15px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%; background: var(--bg-2); border: 1px solid var(--border-2);
  box-shadow: 0 6px 18px -8px var(--shadow);
  transform: translate(-50%, 0); opacity: 0; pointer-events: none; }
#ptr.easing { transition: transform .2s ease, opacity .2s ease; }
#ptr .ring { width: 14px; height: 14px; border-radius: 50%;
  border: 2px solid var(--border-2); border-top-color: var(--fg-faint); }
/* Far enough to count: the ring picks up the accent before you let go, so the
   gesture says whether it will do anything while you can still change your mind. */
#ptr.ready .ring { border-top-color: var(--accent); }
#ptr.spinning .ring { border-top-color: var(--accent); animation: ptrspin .7s linear infinite; }
@keyframes ptrspin { to { transform: rotate(360deg); } }
/* Only the panes need it; a dialog scrolls itself and is excluded in script. */
@media (max-width: 820px) {
  #tape, #sideList, .rpane { overscroll-behavior-y: contain; }
}

/* find-in-file, floating over the top-right of the editor */
.findbar { position: absolute; top: 8px; right: 16px; z-index: 5; display: flex; align-items: center;
  gap: 6px; padding: 5px 6px; border-radius: 8px;
  background: var(--bg-2); border: 1px solid var(--border-2); box-shadow: 0 8px 24px -10px var(--shadow); }
.findbar[hidden] { display: none !important; }
.findbar .fq { width: 170px; margin: 0; padding: 3px 8px; border-radius: 5px;
  background: var(--bg-1); border: 1px solid var(--border); color: var(--fg);
  font-family: var(--mono); font-size: 11.5px; }
.findbar .fq:focus { outline: none; border-color: var(--accent); }
.findbar .cnt { min-width: 62px; text-align: center; color: var(--fg-faint); font-size: 10.5px; }
.findbar .cnt.none { color: var(--red); }
.findbar .fnav { width: 20px; height: 20px; padding: 0; border: none; border-radius: 4px;
  background: none; color: var(--fg-dim); cursor: pointer; font-size: 11px; }
.findbar .fnav:hover { background: var(--bg-3); color: var(--fg); }
.edwrap textarea:focus { outline: none; }
/* No highlight layer for unreadable files, so show the placeholder text plainly. */
.edwrap textarea:disabled { color: var(--fg-faint); -webkit-text-fill-color: var(--fg-faint); }
#paneFiles.dragging { user-select: none; cursor: col-resize; }

/* What else changed, beside what you are reading — so moving to the next file
   is a click rather than closing this and finding it in the pane behind.
   Desktop only: at phone width it would be most of the screen. */
/* Width rather than flex-basis, because dragging sets width and a basis would
   quietly win over it. */
.dfiles { flex: 0 0 auto; width: 236px; min-width: 0; display: flex; flex-direction: column;
  border-right: 1px solid var(--border); background: var(--bg-1); overflow: hidden; }
.dfiles { position: relative; }
.dfiles[hidden] { display: none; }
.dfiles .hd { flex: 0 0 auto; display: flex; align-items: center; gap: 6px;
  padding: 7px 6px 7px 10px; border-bottom: 1px solid var(--border);
  color: var(--fg-faint); font: 10.5px var(--mono); text-transform: uppercase;
  letter-spacing: .06em; }
.dfiles .hd .grow { flex: 1 1 auto; }
.dfiles .fold, .dshow { border: none; background: none; cursor: pointer;
  color: var(--fg-faint); font-size: 15px; line-height: 1; padding: 1px 5px; border-radius: 5px; }
.dfiles .fold:hover, .dshow:hover { background: var(--bg-3); color: var(--fg); }
/* The way back, once it is hidden: a thin strip down the edge rather than a
   button floating over the code. */
.dshow { flex: 0 0 18px; border-right: 1px solid var(--border); background: var(--bg-1);
  border-radius: 0; padding: 7px 0 0;
  /* At the top, where the list's own header was, rather than floating in the
     middle of the file. */
  display: flex; align-items: flex-start; justify-content: center; }
.dshow[hidden] { display: none; }
/* The edge you drag. Wider than it looks, because a 1px target is a test of
   aim rather than a control. */
.dfiles .grip { position: absolute; top: 0; right: -3px; bottom: 0; width: 7px;
  cursor: col-resize; z-index: 2; }
.dfiles .grip:hover, .dfiles .grip.dragging { background: var(--accent-fill); opacity: .5; }
.dfiles .list { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 4px; }
.dfiles .f { display: block; width: 100%; text-align: left; padding: 5px 8px; border: none;
  border-radius: 6px; background: none; color: var(--fg-dim); cursor: pointer;
  font: 11.5px var(--mono); }
.dfiles .f:hover { background: var(--bg-3); color: var(--fg); }
.dfiles .f.on { background: var(--sel); color: var(--fg); }
.dfiles .f .nm { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  direction: rtl; text-align: left; }
.dfiles .f .ct { display: block; margin-top: 1px; font-size: 10px; }
.dfiles .f .ct .add { color: var(--green); }
.dfiles .f .ct .del { color: var(--red); margin-left: 5px; }
.dfiles .fband { padding: 7px 8px 3px; color: var(--fg-faint); font: 9.5px var(--mono);
  text-transform: uppercase; letter-spacing: .06em; }
.dfiles .fband:first-child { padding-top: 2px; }
/* Under 900px the two panes already fold to one; a file list as well would
   leave nothing for the file. */
@media (max-width: 900px) { .dfiles { display: none; } }

#deFind { padding: 2px 9px; font-size: 14px; line-height: 1.2; }

/* Walking the changes: back, where you are, forward. */
.dnav { display: flex; align-items: center; gap: 4px; }
.dnav[hidden] { display: none; }
.dnav .btn { padding: 2px 8px; font-size: 13px; line-height: 1.2; }
.dnav .at { min-width: 48px; text-align: center; color: var(--fg-faint); font: 11px var(--mono); }
#modal .sheet .mbody:focus { outline: none; }

/* The list and the editor side by side, filling the sheet. */
#modal .sheet.tall .mbody.dwrap { display: flex; padding: 0; position: relative; }
#modal .sheet.tall .mbody.dwrap .dedit { position: relative; inset: auto; flex: 1 1 auto; min-width: 0; }

/* The diff editor: base on the left, working tree on the right, one above the
   other's worth of the same thing when there is no room for two. */
.dedit { display: flex; flex-direction: row; gap: 0; position: absolute; inset: 0; }
.dedit.one { display: block; }
.depane { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column;
  border-left: 1px solid var(--border); position: relative; }
.dedit.one .depane { border-left: none; height: 100%; }
.depane:first-child { border-left: none; }
.delabel { flex: 0 0 auto; padding: 4px 10px; font: 10.5px var(--mono);
  color: var(--fg-faint); text-transform: uppercase; letter-spacing: .06em;
  background: var(--bg-2); border-bottom: 1px solid var(--border); }
/* The editor fills what is left under the label — `.edwrap` is flex:1 already,
   and the modal's absolute rule would otherwise pull it over the label. */
#modal .sheet.tall .mbody .dedit .edwrap { position: relative; inset: auto; }

/* side-by-side diff */
.sbs { font-family: var(--mono); font-size: 11.5px; line-height: 1.5; min-width: max-content; }
.sbsrow { display: grid; grid-template-columns: 46px 1fr 46px 1fr; }
.sbsrow .no { text-align: right; padding: 0 8px; color: var(--fg-faint); font-size: 10.5px;
  user-select: none; background: var(--bg-2); border-right: 1px solid var(--border); }
.sbsrow .cell { padding: 0 10px; white-space: pre; overflow: visible; }
.sbsrow .cell.del { background: var(--del-cell); }
.sbsrow .cell.add { background: var(--add-cell); }
.sbsrow.hunk { background: var(--bg-3); }
.sbsrow.hunk .cell.full { grid-column: 1 / -1; padding: 3px 10px; color: var(--accent); }

/* Two clicks open the editor, so the text between them must not be selected
   on the way — a row that highlights blue as it opens looks like a mistake. */
.dfile { user-select: none; }

/* Which repository the pane is showing. Scrolls sideways rather than wrapping:
   a workspace with five repositories should not push the files down the page
   to make room for the way of choosing between them. */
.repotabs { display: flex; gap: 4px; flex: 0 0 auto; padding: 6px 8px;
  border-bottom: 1px solid var(--border); background: var(--bg-2);
  overflow-x: auto; position: sticky; top: 0; z-index: 3; }
.rtab { flex: 0 0 auto; display: flex; align-items: center; gap: 5px;
  padding: 3px 9px; border-radius: 999px; border: 1px solid var(--border);
  background: var(--bg-1); color: var(--fg-dim); cursor: pointer;
  font: 11px var(--mono); white-space: nowrap; }
.rtab:hover { border-color: var(--border-2); color: var(--fg); }
.rtab.on { border-color: var(--accent); color: var(--fg); background: var(--sel); }
.rtab .n { color: var(--fg-faint); font-size: 10px; }

/* Staged, not staged, committed — each its own band, so which side of the
   index a file is on is read at a glance rather than worked out. */
.dgroup { margin-bottom: 2px; }
.dgrouphd { display: flex; align-items: center; gap: 7px; position: sticky; top: 0; z-index: 1;
  padding: 5px 10px; background: var(--bg-2); border-bottom: 1px solid var(--border);
  color: var(--fg-dim); font: 10.5px var(--mono); text-transform: uppercase; letter-spacing: .06em; }
.dgrouphd .n { color: var(--fg-faint); }
.dgrouphd .grow { flex: 1 1 auto; }
.dgrouphd .glink { border: none; background: none; cursor: pointer; color: var(--fg-faint);
  font: 10.5px var(--mono); text-transform: none; letter-spacing: 0; padding: 2px 5px;
  border-radius: 5px; }
.dgrouphd .glink:hover { background: var(--bg-3); color: var(--accent); }

#pmText { width: 100%; margin: 6px 0 4px; padding: 8px 10px; border-radius: 8px;
  border: 1px solid var(--border); background: var(--bg-1); color: var(--fg);
  font-size: 13px; resize: vertical; }
#pmText:focus { outline: none; border-color: var(--accent); }

/* per-file actions in the diff list */
.dfile .grow { flex: 1 1 auto; }
.dfile .dact { flex: 0 0 auto; width: 28px; height: 24px; padding: 0; border: none; border-radius: 5px;
  background: none; color: var(--fg-faint); cursor: pointer; font-size: 17px; line-height: 1; opacity: 0; }
.dfile:hover .dact { opacity: 1; }
.dfile .dact:hover { background: var(--bg-3); color: var(--accent); }

/* The patch preview under the list says which file it is showing, and offers
   the same way in as the row above it — you are already looking at the change
   by the time you decide to open it. */
.phead { display: flex; align-items: center; gap: 8px; padding: 5px 10px;
  border-bottom: 1px solid var(--border); background: var(--bg-2);
  position: sticky; top: 0; z-index: 2; }
.phead .nm { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font: 11.5px var(--mono); color: var(--fg-dim); direction: rtl; text-align: left; }
.phead .grow { flex: 1 1 auto; }
.phead .dact { flex: 0 0 auto; width: 28px; height: 24px; padding: 0; border: none; border-radius: 5px;
  background: none; color: var(--fg-faint); cursor: pointer; font-size: 17px; line-height: 1; }
.phead .dact:hover { background: var(--bg-3); color: var(--accent); }

/* an inline checkbox that reads as a label, not a form control */
.fld.chk-inline { display: flex; align-items: center; gap: 8px; cursor: pointer;
  text-transform: none; letter-spacing: 0; font-size: 12.5px; color: var(--fg-dim); }
.fld.chk-inline input { width: auto; margin: 0; accent-color: var(--accent); }

/* Choosing which of several things a confirm applies to. Rows are tap targets,
   so they get real height rather than just a checkbox to hit. */
.pickset { display: flex; flex-direction: column; gap: 2px; margin: 10px 0 4px;
  overflow-y: auto; min-height: 0; }
.pickset .fld.chk-inline[hidden] { display: none; }
.pickset .fld.chk-inline { padding: 8px 10px; border-radius: 8px; font-family: var(--mono);
  font-size: 12px; color: var(--fg); background: var(--bg-1); border: 1px solid var(--border); }
.pickset .fld.chk-inline:hover { border-color: var(--border-2); }
.pickset input { flex: 0 0 auto; width: 16px; height: 16px; }

/* chat tabs, when a workspace has one chat per repository */
#chatTabs { display: flex; gap: 2px; padding: 6px 10px 0; flex: 0 0 auto; }
#chatTabs[hidden] { display: none !important; }
#chatTabs button { display: flex; align-items: center; gap: 6px; padding: 5px 11px;
  border: none; border-radius: 8px 8px 0 0; background: none; cursor: pointer;
  color: var(--fg-dim); font-size: 12.5px; font-family: var(--mono); }
#chatTabs button:hover { background: var(--bg-2); color: var(--fg); }
#chatTabs button.active { background: var(--bg-2); color: var(--fg); box-shadow: inset 0 -2px 0 var(--accent); }
/* the status colours are defined for .wsitem; repeat them here rather than
   widening those selectors and changing the sidebar by accident */
#chatTabs .stat { width: 6px; height: 6px; border-radius: 50%; flex: 0 0 auto; background: var(--fg-faint); }
#chatTabs .stat.working { background: var(--accent-fill); box-shadow: 0 0 8px var(--accent); animation: pulse 1.4s ease-in-out infinite; }
#chatTabs .stat.error { background: var(--red); }
#chatTabs .stat.idle { background: var(--green); }

/* slash command hints above the composer */
.cmdmenu { position: absolute; left: 0; right: 0; bottom: calc(100% + 6px); z-index: 20;
  max-height: 260px; overflow-y: auto; padding: 4px;
  background: var(--bg-2); border: 1px solid var(--border-2); border-radius: 10px;
  box-shadow: 0 16px 40px -14px var(--shadow); }
.cmdmenu[hidden] { display: none !important; }
.cmdrow { display: flex; align-items: baseline; gap: 9px; padding: 6px 9px; border-radius: 6px;
  cursor: pointer; font-size: 12.5px; }
.cmdrow.active, .cmdrow:hover { background: var(--bg-3); }
.cmdrow .nm { font-family: var(--mono); font-size: 11.5px; color: var(--accent); flex: 0 0 auto; }
.cmdrow .sub { color: var(--fg-faint); font-size: 11.5px; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmdrow .scope { margin-left: auto; color: var(--fg-faint); font-size: 10px; flex: 0 0 auto;
  padding: 1px 6px; border-radius: 999px; border: 1px solid transparent; }
/* Your own skills stand out from the built-ins they sit beside. */
.cmdrow .scope:not(:empty) { border-color: var(--border); }
/* Exists, but cannot run here. Still listed, so typing it explains itself. */
.cmdrow.off { opacity: .5; }
.cmdrow.off .nm { color: var(--fg-faint); text-decoration: line-through; }

/* Shell scripts, so they keep their line breaks and read as code. These were
   <input>s, which silently flatten a multi-line script the moment you save. */
#settingsBody textarea[data-f="setup_script"],
#settingsBody textarea[data-f="run_script"] {
  font-family: var(--mono); font-size: 11.5px; line-height: 1.5; white-space: pre; overflow-x: auto; }

/* skills list in settings */
.skills .row { display: flex; align-items: center; gap: 9px; padding: 8px 10px;
  border-bottom: 1px solid var(--border); font-size: 12.5px; }
.skills .row:last-child { border-bottom: none; }
.skills .nm { font-family: var(--mono); font-size: 11.5px; color: var(--fg); flex: 0 0 auto; }
.skills .sub { color: var(--fg-faint); font-size: 11.5px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.skills .empty { padding: 12px 10px; color: var(--fg-faint); font-size: 12px; }

/* Stored credentials. Deliberately no field showing the value or a masked
   stand-in for one: a row of dots reads as "nearly shown" and invites checking
   a secret by eye, which is the habit this replaces. */
.secrets { border: 1px solid var(--border); border-radius: 10px; overflow: hidden; }
.secrets .secret { border-bottom: 1px solid var(--border); padding: 8px 10px; }
.secrets .secret:last-child { border-bottom: none; }
.secrets .hd { display: flex; align-items: center; gap: 9px; font-size: 12.5px; }
.secrets .nm { color: var(--fg); font-size: 11.5px; flex: 0 0 auto; }
.secrets .sub { color: var(--fg-faint); font-size: 11.5px; }
/* Not ".note": there is a generic .note card elsewhere with a background and
   a border, and a one-line caption picked both up. */
.secrets .secnote { margin-top: 3px; }
/* Hidden until Rotate is pressed, so the common case is a list you read. */
.secrets .rotbox { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
.secrets .rotbox input { flex: 1 1 auto; font-family: var(--mono); font-size: 12.5px; }
.secretadd { margin-top: 10px; }

/* ---------------------------------------------------------------- modal */
#modal { position: fixed; inset: 0; z-index: 200; background: rgba(4,4,5,.74);
  display: grid; place-items: center; padding: 24px; }
#modal[hidden] { display: none; }

/* "You are running an old page" — sits above the app, below dialogs. */
.stalebar { position: fixed; left: 50%; bottom: 18px; transform: translateX(-50%); z-index: 350;
  display: flex; align-items: center; gap: 10px; padding: 9px 12px; border-radius: 10px;
  background: var(--bg-2); border: 1px solid var(--accent);
  box-shadow: 0 16px 40px -14px var(--shadow); font-size: 12.5px; }
/* The sentence keeps its line; without this the buttons take the width they
   want and squeeze the text into a five-line column. */
.stalebar span { flex: 0 1 auto; }
.stalebar .btn { flex: 0 0 auto; white-space: nowrap; }

/* Confirmations sit above everything, including an open dialog. */
#confirm { position: fixed; inset: 0; z-index: 400; background: rgba(4,4,5,.6);
  display: grid; place-items: center; padding: 24px; }
#confirm[hidden] { display: none !important; }
/* Bounded, and a column, so a long list scrolls inside it. Without this the
   sheet grew past the viewport with nothing to scroll: the choices at the
   bottom, and the buttons under them, were simply unreachable. */
#confirm .sheet { width: 100%; max-width: 420px; background: var(--bg-2);
  border: 1px solid var(--border-2); border-radius: 14px; padding: 20px;
  box-shadow: 0 30px 80px -24px var(--shadow);
  max-height: calc(100vh - 48px); display: flex; flex-direction: column; min-height: 0; }
/* The question and the answer stay put; the list between them is what moves. */
#confirm .sheet > h2,
#confirm .sheet > .hint,
#confirm .sheet > #pickFindRow,
#confirm .sheet > .row { flex: 0 0 auto; }
/* The same box as the one over the workspace list, so it reads as the same
   thing: room kept on the right for the clear button whether or not it is
   showing, and Safari's own ✕ dropped in favour of one that matches. */
#confirm #pickFindRow { position: relative; margin: 8px 0 0; }
#confirm #pickFind { width: 100%; padding: 6px 24px 6px 9px; border-radius: 7px;
  border: 1px solid var(--border); background: var(--bg); color: var(--fg);
  font: 12px var(--mono); }
#confirm #pickFind::-webkit-search-cancel-button { display: none; }
#confirm #pickFind::placeholder { color: var(--fg-faint); }
#confirm #pickFind:focus { outline: none; border-color: var(--border-2); }
#confirm #pickFindClear { position: absolute; right: 5px; top: 50%; transform: translateY(-50%);
  border: none; background: none; cursor: pointer; color: var(--fg-faint);
  font-size: 15px; line-height: 1; padding: 3px 4px; }
#confirm #pickFindClear[hidden] { display: none; }
#confirm #pickFindClear:hover { color: var(--fg); }
#confirm .pickset .empty { padding: 10px; color: var(--fg-faint); font-size: 12px; }
#confirm .pickset .empty[hidden] { display: none; }
#confirm h2 { margin: 0 0 6px; font-size: 15px; font-weight: 600; }
#confirm .hint { margin: 0 0 4px; }
#confirm .row { display: flex; gap: 10px; justify-content: flex-end; margin-top: 18px; }
#confirm .row .grow { flex: 1 1 auto; }

/* For the ones that destroy something. */
.btn.danger { background: var(--danger-solid); border-color: var(--danger-br); color: #ffd9d9; }
.btn.danger:hover { background: var(--danger-solid); border-color: var(--red); }
#modal .sheet { width: 100%; max-width: 560px; max-height: 86vh; overflow-y: auto;
  background: var(--bg-2); border: 1px solid var(--border-2); border-radius: 14px; padding: 22px;
  box-shadow: 0 30px 80px -24px var(--shadow); }
/* A diff or an editor needs room; `tall` hands the height to an inner pane so
   the body scrolls rather than the whole sheet. */
#modal .sheet.wide { max-width: min(1500px, 94vw); }

/* Prose is read at a measure, not at whatever width the window happens to be:
   a note at full `wide` ran to 234 characters a line. This lands near 110 and
   still clears the ~86-column code block a note tends to carry, so the reading
   width costs no horizontal scrollbar. A genuinely wide table still scrolls,
   which is what a wide table should do. */
#modal .sheet.read { max-width: min(760px, 94vw); }
#modal .sheet.tall { height: 88vh; display: flex; flex-direction: column; overflow: hidden; }
/* The diff editor wants the room: two files side by side, plus a list of what
   else changed. Nearly the whole window, because that is what it is for.
   After `.tall` rather than before it — same specificity, so order decides,
   and a height set earlier would simply lose. */
#modal .sheet.diff { max-width: 98vw; height: 95vh; max-height: 95vh; }
/* And the padding the sheet keeps for a form is width the diff can use. */
#modal:has(.sheet.diff) { padding: 8px; }
#modal .sheet.tall .mbody { flex: 1 1 auto; min-height: 0; overflow: auto;
  border: 1px solid var(--border); border-radius: 8px; background: var(--bg-1); }
/* The log takes the height the sheet has rather than its own 60vh, which left
   a band of empty sheet under it — and the whole reason for opening this is to
   see as many lines as the screen will hold. */
#modal .sheet.tall > .monout { flex: 1 1 auto; min-height: 0; max-height: none; }
#modal .sheet.tall .mbody .edwrap { position: absolute; inset: 0; }
#modal .sheet.tall .mbody:has(.edwrap) { position: relative; }
#modal .mhead { display: flex; align-items: center; gap: 10px; }
#modal .mhead h2,
#modal .mhead h3 { margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The two switches over a log. Not a full field row — they sit beside a title,
   so they take the label's size and none of its stacking. */
#modal .mhead .fld.chk-inline { margin: 0; flex: 0 0 auto; font-size: 11.5px;
  color: var(--fg-dim); gap: 5px; }
#modal .mhead .grow { flex: 1 1 auto; }
#modal .mhead .dirty { font-size: 10px; color: var(--accent); }
#modal .sheet.tall .hint { margin: 4px 0 10px; font-family: var(--mono); font-size: 11px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* That rule exists for the note modal's filename, which is one line by nature.
   A sentence is not: clamped to one line it lost its second half. */
#modal .sheet.tall .hint.wrap { font-family: inherit; font-size: 12.5px; line-height: 1.55;
  white-space: normal; overflow: visible; }
#modal h2 { margin: 0 0 4px; font-size: 16px; font-weight: 600; }
#modal .hint { margin: 0 0 18px; color: var(--fg-dim); font-size: 12.5px; }
/* The button row at the foot of a dialog. It is right-aligned and pushed down,
   which is why a toggle line must NOT reuse it — see .switchrow. */
#modal .row { display: flex; gap: 10px; justify-content: flex-end; margin-top: 18px; }

/* A toggle and its label on one line, aligned with the rest of the form. The
   whole thing is a <label>, so the text is a hit target too. */
.switchrow { display: flex; align-items: center; gap: 10px; cursor: pointer;
  font-size: 12.5px; color: var(--fg-dim); }
.switchrow:hover { color: var(--fg); }
.switchrow > span:last-child { min-width: 0; }
#modal .row .grow { flex: 1 1 auto; }
#modal .err { color: var(--red); font-size: 12px; min-height: 16px; }
.split { display: flex; gap: 10px; }
.split > * { flex: 1 1 0; }

/* -------------------------------------------------------------- empties */
.empty-state { display: grid; place-items: center; height: 100%; padding: 40px; text-align: center; }
.empty-state .inner { max-width: 380px; }
.empty-state h2 { margin: 0 0 8px; font-size: 16px; font-weight: 600; }
.empty-state p { margin: 0 0 18px; color: var(--fg-dim); font-size: 13px; line-height: 1.6; }
.tundo { margin-left: 12px; padding: 2px 9px; border-radius: 7px; border: 1px solid currentColor;
  background: none; color: inherit; font: inherit; font-size: 12px; font-weight: 600;
  cursor: pointer; opacity: .9; }
.tundo:hover { opacity: 1; }
.toast { position: fixed; bottom: 18px; left: 50%; transform: translateX(-50%);
  background: var(--bg-3); border: 1px solid var(--border-2); border-radius: 9px;
  padding: 9px 14px; font-size: 12.5px; z-index: 300; box-shadow: 0 12px 30px -10px var(--shadow); }
.toast.err { border-color: var(--danger-solid); color: var(--danger-fg); }
.toast[hidden] { display: none; }

/* ------------------------------------------------- multi-repo additions */
/* The section is what bounds the sticky header: it stays while its own
   repository is on screen and is pushed out by the next, rather than every
   header piling up at the top together. */
.repogroup { position: relative; }
.repohd { display: flex; align-items: center; gap: 8px; padding: 9px 12px 7px;
  background: var(--bg-2); border-bottom: 1px solid var(--border);
  /* Below the filter pinned above it, not at zero — otherwise it slides under
     the filter and is covered. The height is set from script, since it depends
     on whether the uncommitted toggle is there. */
  position: sticky; top: var(--filter-h, 0px); z-index: 1;
  font-family: var(--mono); font-size: 11.5px; }
.repohd .nm { color: var(--fg); font-weight: 600; }
.repohd .grow { flex: 1 1 auto; }
.repohd .add { color: var(--green); } .repohd .del { color: var(--red); }

.repopick { border: 1px solid var(--border-2); border-radius: 8px; overflow: hidden; }
.repopick-row { display: flex; align-items: center; gap: 9px; padding: 8px 11px;
  border-bottom: 1px solid var(--border); cursor: pointer; font-size: 13px;
  user-select: none; }
.repopick-row:last-child { border-bottom: none; }
.repopick-row:hover { background: var(--bg-3); }
.repopick-row.on { background: var(--sel-soft); }
.repopick-row input { width: auto; margin: 0; accent-color: var(--accent); pointer-events: none; }
.repopick-row .nm { font-family: var(--mono); font-size: 12px; flex: 1 1 auto;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.repopick-row .pill { flex: 0 0 auto; }

/* selected block */
.repopick-row.chosen { background: var(--sel-soft); box-shadow: inset 2px 0 0 var(--accent); }
.repopick-row.chosen .tick { color: var(--accent); font-size: 12px; width: 13px; text-align: center; }
.repopick-row .rm { flex: 0 0 auto; width: 20px; height: 20px; border-radius: 5px;
  background: none; border: none; color: var(--fg-faint); font-size: 15px; line-height: 1; cursor: pointer; }
.repopick-row .rm:hover { background: #7f1d1d40; color: var(--danger-fg); }

/* base-branch chooser on a selected repo — shaped like the pill it replaces,
   so the Selected rows keep the same rhythm as the rest of the list. */
.repopick-row .basesel { flex: 0 0 auto; max-width: 200px; display: inline-flex; align-items: center;
  gap: 5px; padding: 2px 7px 2px 9px; height: 22px; border-radius: 999px; cursor: pointer;
  background: var(--bg-2); border: 1px solid var(--border); color: var(--fg-dim);
  font-family: var(--mono); font-size: 11px; }
.repopick-row .basesel .bn { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.repopick-row .basesel .cv { color: var(--fg-faint); font-size: 9px; flex: 0 0 auto; }
.repopick-row .basesel:hover { border-color: var(--accent); color: var(--fg); }
.repopick-row .basesel:focus-visible { outline: none; border-color: var(--accent); }

/* the searchable dropdown it opens */
#btnSound { flex: 0 0 auto; padding: 0 7px; font-size: 13px; line-height: 1; }
#btnSound.off { color: var(--fg-faint); text-decoration: line-through; }

/* agent mode chip in the composer — colour-coded by how much it can do */
.modebtn { flex: 0 0 auto; padding: 2px 9px; border-radius: 999px; cursor: pointer;
  background: var(--bg-2); border: 1px solid var(--border);
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .04em;
  color: var(--fg-dim); text-transform: uppercase; }
.modebtn:hover { color: var(--fg); border-color: var(--accent); }
/* Colour tracks how much can happen without being asked. */
.modebtn[data-mode="default"], .modebtn[data-mode="dontAsk"] { color: var(--fg-dim); }
.modebtn[data-mode="plan"] { color: #7fb4ff; border-color: #7fb4ff55; }
.modebtn[data-mode="acceptEdits"] { color: var(--green); border-color: #4ade8055; }
.modebtn[data-mode="auto"] { color: var(--green); border-color: #4ade8055; }
.modebtn[data-mode="bypassPermissions"] { color: var(--accent); border-color: #ec515755; }
.modebtn[hidden] { display: none !important; }
/* the model chip is informational, so it stays neutral rather than colour-coded */
.modebtn.model { color: var(--fg-faint); text-transform: none; letter-spacing: 0; }
/* Effort reads as a setting rather than a state, so it follows the model chip
   rather than the mode one — neutral, and cased the way the words are written. */
.modebtn.effort { color: var(--fg-faint); text-transform: none; letter-spacing: 0; }
/* Quiet until somebody sets one: an effort nobody chose is the CLI's business,
   not a setting worth a lit chip. */
.modebtn.effort.unset { opacity: .55; }
.modebtn.model:hover { color: var(--fg); border-color: var(--accent); }

/* the workspace name is the rename control, so it has to look clickable */
#wsTitle.editable { cursor: text; border-radius: 6px; padding: 1px 5px; margin-left: -5px;
  border-bottom: 1px dashed transparent; }
#wsTitle.editable:hover { background: var(--bg-3); border-bottom-color: var(--fg-faint); }

.combo { position: fixed; z-index: 300; background: var(--bg-2); border: 1px solid var(--border-2);
  border-radius: 10px; box-shadow: 0 18px 44px -12px var(--shadow); overflow: hidden; }
.combo .cq { width: 100%; margin: 0; border: none; border-bottom: 1px solid var(--border);
  border-radius: 0; background: var(--bg-1); color: var(--fg); padding: 8px 11px; font-size: 12px; }
.combo .cq:focus { outline: none; }
.combo .clist { max-height: 232px; overflow-y: auto; padding: 4px; }
.combo .crow { padding: 6px 8px; border-radius: 6px; cursor: pointer; color: var(--fg-dim);
  font-family: var(--mono); font-size: 11.5px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.combo .crow:hover, .combo .crow.active { background: var(--bg-3); color: var(--fg); }
.combo .crow.on { color: var(--accent); }
.combo .cempty { padding: 12px 10px; color: var(--fg-faint); font-size: 11.5px; text-align: center; }

/* The same surface holding actions instead of a list to filter. */
.combo.menu { padding: 5px; }
.combo.menu .mrow { display: block; width: 100%; padding: 7px 9px; border: none; border-radius: 6px;
  background: none; color: var(--fg-dim); cursor: pointer; text-align: left;
  font-family: inherit; font-size: 12.5px; }
.combo.menu .mrow:hover { background: var(--bg-3); color: var(--fg); }
.combo.menu .mrow.key { color: var(--accent); }


#runRepos { flex: 0 0 auto; }
#runRepos .runbar:last-child { border-bottom: 1px solid var(--border); }

/* ----------------------------------------------------------- gitlab bits */
.glcard { display: flex; align-items: center; gap: 12px; padding: 12px;
  background: var(--bg-3); border: 1px solid var(--border-2); border-radius: 10px; }
.glcard img { width: 40px; height: 40px; border-radius: 50%; }
.glcard .nm { font-size: 14px; font-weight: 600; }
.glcard .sub { color: var(--fg-dim); font-size: 12px; font-family: var(--mono); }
/* The name card is the same card with an editable middle, so the box has to be
   allowed to take the width the connected-account name would have taken. */
.glcard .grow { flex: 1 1 auto; min-width: 0; }
.glcard #myName { width: 100%; margin-bottom: 5px; }
/* The face stands in for a missing avatar at the same size as one. */
.glcard .face { width: 40px; height: 40px; flex: 0 0 auto; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-1); color: var(--fg-dim); font: 13px var(--mono); }

.glrow { display: flex; align-items: center; gap: 9px; padding: 7px 9px;
  border-bottom: 1px solid var(--border); font-size: 12.5px; }
.glrow:last-child { border-bottom: none; }
.glrow .nm { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; font-family: var(--mono); font-size: 11.5px; }
.glrow .grow { flex: 1 1 auto; }
.glrow.pickable { cursor: pointer; border-radius: 7px; }
.glrow.pickable:hover { background: var(--bg-3); }
.glrow.pickable.sel { background: var(--sel-soft); box-shadow: inset 0 0 0 1px var(--accent); }
#glresults, #glPick { max-height: 220px; overflow-y: auto;
  border: 1px solid var(--border); border-radius: 8px; }

.seg { display: flex; gap: 2px; background: var(--bg); border: 1px solid var(--border-2);
  border-radius: 8px; padding: 3px; }
.seg button { flex: 1 1 0; padding: 6px 8px; border-radius: 6px; background: none;
  border: none; color: var(--fg-dim); font-size: 12px; cursor: pointer; }
.seg button:hover { color: var(--fg); }
.seg button.on { background: var(--bg-3); color: var(--fg); box-shadow: inset 0 0 0 1px var(--border-2); }

/* --------------------------------------------- gitlab-backed repo picker */
.repopick { max-height: 300px; overflow-y: auto; }
.repopick-empty { padding: 14px 12px; color: var(--fg-faint); font-size: 12px; text-align: center; }
.repopick-note { position: sticky; top: 0; z-index: 1; padding: 7px 11px 5px;
  background: var(--bg-2); color: var(--fg-faint); font-size: 10.5px;
  text-transform: uppercase; letter-spacing: .07em; border-bottom: 1px solid var(--border); }
.repopick-note:not(:first-child) { border-top: 1px solid var(--border); }
.repopick-connect { display: flex; align-items: center; gap: 12px; padding: 14px 12px; }
.repopick-connect .nm { font-size: 13px; font-weight: 600; color: var(--fg); }
.repopick-connect .sub { margin-top: 3px; color: var(--fg-dim); font-size: 12px; line-height: 1.45; }
.repopick-connect .btn { flex: 0 0 auto; margin-left: auto; }
.repopick-row .pill.warn { flex: 0 0 auto; }

/* ============================================================== settings */
/* Your board. A full sheet like Settings: it is a different way of looking at
   the same workspaces, not a panel beside them. */
#board { position: fixed; inset: 0; z-index: 150; background: var(--bg); display: flex; }
#board[hidden] { display: none; }

/* The feedback inbox borrows the board's shell — same overlay — because it is
   the same kind of thing: a whole screen you step into and back out of. */
#inbox { position: fixed; inset: 0; z-index: 150; background: var(--bg); display: flex; }
#inbox[hidden] { display: none; }
/* No padding above the header: the header carries the inset itself, below. */
#inbox .board-inner { padding-top: 0; }

/* The header is the surface, not a thing floating under a gap.
   On a phone the status bar area has to stay clear of content either way, so
   it is given to the header rather than left as a band of background above it
   — which is what made it read as dead space. Sticky, so the filters stay
   reachable however far down the list you are. */
#inbox .shead { position: sticky; top: 0; z-index: 2; margin-bottom: 0;
  padding: 14px 0 12px; background: var(--bg); border-bottom: 1px solid var(--border); }
#inbox .ffilters { display: flex; gap: 8px; min-width: 0; }
#inbox .shead .mine { flex: 0 0 auto; padding: 2px 9px; border-radius: 999px;
  background: var(--bg-3); color: var(--fg-dim); font-size: 11px; }
#inbox .shead .mine[hidden] { display: none; }

/* The board, and the report over it. */
.inbox-split { flex: 1 1 auto; min-height: 0; position: relative; display: block; }
#inboxBoard { height: 100%; display: flex; gap: 12px; overflow-x: auto; padding: 12px 0 8px;
  scroll-snap-type: x proximity; }

.fcol { flex: 0 0 320px; min-width: 0; display: flex; flex-direction: column; min-height: 0;
  border: 1px solid var(--border); border-radius: 12px; background: var(--bg-1);
  scroll-snap-align: start; }
.fcol.over { border-color: var(--accent); }
.fcol > .chead { flex: 0 0 auto; display: flex; align-items: center; gap: 8px;
  padding: 10px 12px; border-bottom: 1px solid var(--border); }
.fcol > .chead .nm { font-size: 12.5px; font-weight: 600; color: var(--fg); }
.fcol > .chead .n { padding: 0 7px; border-radius: 999px; background: var(--bg-3);
  color: var(--fg-dim); font-size: 10.5px; font-variant-numeric: tabular-nums; }
/* What the column is for, said once rather than guessed from its name. */
.fcol > .chead .hint { margin-left: auto; color: var(--fg-faint); font-size: 10.5px; }
.fcards { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 8px; display: flex;
  flex-direction: column; gap: 8px; }
.fcards .bempty { padding: 18px 10px; text-align: center; color: var(--fg-faint); font-size: 12px; }

/* One report, as a card. */
.fcard { position: relative; display: block; width: 100%; box-sizing: border-box;
  padding: 10px 11px 10px 13px; border: 1px solid var(--border); border-radius: 10px;
  background: var(--bg); color: inherit; font: inherit; text-align: left; cursor: pointer;
  touch-action: manipulation; }
/* Priority, told three ways at once.
 *
 * Red, yellow, blue — the three hues furthest apart from each other, which is
 * why traffic lights use them. Luminance contrast between the three is beside
 * the point; what each one has to clear is 3:1 against the card it sits on,
 * and the light-theme yellow is the only hard one. A true yellow manages 1.85
 * on a near-white card, so it is taken down to the lightest shade that passes.
 *
 * All three run the full height. They used to differ in height as well, back
 * when the colours were red, amber and grey and the first two measured 1.31:1
 * apart — the height was carrying what the hue could not. With these three it
 * earns nothing and reads as a bug, which is how it was first reported. The
 * word is on the card's accessible name, where a screen reader still gets it
 * and the card costs no space for it. */
.fcard::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 5px;
  border-radius: 0 4px 4px 0; background: transparent; }
.fcard.sev-high::before { background: var(--prio-high); }
.fcard.sev-medium::before { background: var(--prio-med); }
.fcard.sev-low::before { background: var(--prio-low); }

.fcard:hover { border-color: var(--fg-faint); }
.fcard:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.fcard.on { border-color: var(--accent); }
.fcard.dragging { opacity: .35; }
.fcard .toprow { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; }
.fcard .sum { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
  overflow: hidden; font-size: 13px; line-height: 1.38; color: var(--fg-dim); font-weight: 400; }
.fcard.unread .sum { color: var(--fg); font-weight: 600; }
/* The kind, as a word with its mark. These lived on the list row before the
   board replaced it, and a card is where they live now. */
.fcard .lab { display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px 2px 6px;
  border-radius: 6px; font-size: 11px; font-weight: 600;
  background: var(--bg-3); color: var(--fg-dim); }
.fcard .lab .ic { flex: 0 0 auto; opacity: .9; }
.fcard .lab.bug { background: color-mix(in srgb, var(--red) 16%, transparent); color: var(--red); }
.fcard .lab.idea { background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--accent); }
.fcard .lab.question { background: color-mix(in srgb, var(--green) 16%, transparent); color: var(--green); }
/* How many clinics said it. Impact, not bookkeeping. */
.fcard .impact { padding: 1px 7px; border-radius: 999px;
  background: color-mix(in srgb, var(--red) 14%, transparent); color: var(--red);
  font-size: 10.5px; font-weight: 600; }
.fcard .span { color: var(--fg-dim); font-size: 10.5px; }
.fcard .who { display: inline-flex; align-items: center; gap: 5px; }
.fcard .face-xs { width: 16px; height: 16px; border-radius: 50%; background: var(--accent-fill);
  color: var(--on-accent); font-size: 8.5px; font-weight: 700; display: inline-flex;
  align-items: center; justify-content: center; flex: 0 0 auto; }
.fcard .foot { display: flex; align-items: center; gap: 8px; margin-top: 8px;
  font-size: 10.5px; color: var(--fg-dim); }
.fcard .foot .age.stale { color: var(--amber); }
.fcard .foot .age.old { color: var(--red); font-weight: 600; }
.fcard .foot .grow { flex: 1 1 auto; }
.fcard .quick { padding: 3px 9px 3px 7px; border-radius: 7px; border: 1px solid var(--border);
  background: var(--bg-1); color: var(--fg-dim); font-size: 10.5px; cursor: pointer;
  display: inline-flex; align-items: center; gap: 4px; }
.fcard .quick:hover { color: var(--accent); border-color: var(--accent); }

/* The card under the pointer while it is being carried. */
.fcard.ghost { position: fixed; left: 0; top: 0; z-index: 400; pointer-events: none;
  box-shadow: 0 18px 40px -16px #000; transform: rotate(1.5deg); opacity: .96; }
body.fdragging { user-select: none; cursor: grabbing; }

/* The report, over the board rather than beside it. */
#inboxDetail { position: absolute; top: 0; right: 0; bottom: 0; width: min(560px, 100%);
  overflow-y: auto; background: var(--bg); border-left: 1px solid var(--border);
  box-shadow: -24px 0 48px -32px #000; z-index: 3; }
#inboxDetail[hidden] { display: none; }

/* The report itself. */
.fdetail { padding: 18px 22px; max-width: 760px; }
.fdetail .back { display: inline-flex; align-items: center; gap: 6px; margin-bottom: 12px;
  padding: 7px 12px 7px 9px; border: 1px solid var(--border); border-radius: 9px;
  background: var(--bg-1); color: var(--fg-dim); font-size: 12.5px; cursor: pointer; }
.fdetail .back:hover { color: var(--fg); border-color: var(--fg-faint); }
.fdetail h2 { margin: 0 0 6px; font-size: 17px; line-height: 1.35; font-weight: 600; color: var(--fg); }
.fdetail .dmeta { display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  margin-bottom: 16px; font-size: 11.5px; color: var(--fg-dim); }
.fdetail .dmeta a { display: inline-flex; align-items: center; gap: 4px; color: var(--fg-dim);
  text-decoration: none; border-bottom: 1px solid var(--border); }
.fdetail .dmeta a:hover { color: var(--accent); border-color: var(--accent); }
/* The exchange as the exchange. It came out of a chat and reads as one. */
.fmsgs { display: flex; flex-direction: column; gap: 10px; margin-bottom: 20px; }
/* One message. --spk is the speaker's hue, set per bubble; everything that
   carries identity derives from it so a person looks the same throughout. The
   tint is a tenth of it over the surface, which is enough to group three
   messages by eye and little enough that body text stays above 13:1. */
.fmsg { padding: 9px 13px 11px; border-radius: 12px;
  background: color-mix(in srgb, var(--spk, var(--fg)) 10%, var(--bg-2));
  border: 1px solid color-mix(in srgb, var(--spk, var(--fg)) 18%, var(--border)); }
.fmsg .head { display: flex; align-items: center; gap: 7px; margin-bottom: 5px; }
.fmsg .face-sm { flex: 0 0 auto; width: 20px; height: 20px; border-radius: 50%;
  background: var(--spk, var(--fg-dim)); color: #fff; font-size: 9.5px; font-weight: 700;
  display: inline-flex; align-items: center; justify-content: center; }
.fmsg .name { font-size: 11.5px; font-weight: 600; color: var(--fg); }
/* The clinic the words came from, which is the part worth noticing. */
.fmsg .fwd { padding: 1px 7px; border-radius: 999px; border: 1px solid var(--border);
  background: var(--bg-1); color: var(--fg-dim); font-size: 10.5px; }
.fmsg .when { margin-left: auto; flex: 0 0 auto; font-size: 10.5px; color: var(--fg-dim);
  font-variant-numeric: tabular-nums; cursor: help; }
.fmsg .body { white-space: pre-wrap; overflow-wrap: anywhere; font-size: 13px;
  line-height: 1.55; color: var(--fg); }
/* A customer speaking through a colleague, rather than the colleague. */
/* How long nobody said anything, on a rule between the two messages so it
   reads as a pause in the conversation rather than a label on either one. */
.fgap { display: flex; align-items: center; gap: 10px; margin: 2px 0;
  font-size: 10.5px; color: var(--fg-faint); }
.fgap::before, .fgap::after { content: ''; flex: 1 1 auto; height: 1px; background: var(--border); }
.fgap span { flex: 0 0 auto; }
/* Carrying on, rather than speaking again: no second name, no second face,
   and tucked up against the bubble it continues. */
.fmsg.cont { margin-top: -6px; border-top-left-radius: 4px; border-top-right-radius: 4px;
  padding-top: 8px; }
/* Waiting, at roughly the size of the thing being waited for, so the pane
   does not jump when the real messages arrive. */
.fmsg.skel { background: var(--bg-2); border-color: var(--border);
  animation: skelpulse 1.4s ease-in-out infinite; }
@keyframes skelpulse { 0%, 100% { opacity: .5; } 50% { opacity: .8; } }
@media (prefers-reduced-motion: reduce) { .fmsg.skel { animation: none; opacity: .6; } }

.fmsg.quoted { border-left: 3px solid var(--spk, var(--border)); border-radius: 4px 12px 12px 4px; }

@media (max-width: 820px) {
  .fmsg .head { flex-wrap: wrap; row-gap: 3px; }
  .fmsg .when { margin-left: 0; }
}
/* The language and the button that uses it, side by side with room between.
   It borrowed .ffilters, whose only rule is scoped to #inbox — so inside a
   dialog that class styled nothing and the two controls sat against each
   other with no gap and no alignment. */
#modal .rlangrow { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
#modal .rlangrow select { flex: 0 1 220px; min-width: 0; margin: 0; }
#modal .rlangrow .btn { flex: 0 0 auto; }

/* Picking the screenshots that go with an answer to the group.
   A grid of what the workspace has, dim until chosen — the dialog's rule is
   that nothing leaves without a decision, so none of them start on. */
.rshots { display: grid; gap: 8px; grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
  max-height: 260px; overflow-y: auto; padding: 2px; }
.rshot { position: relative; display: block; padding: 0; cursor: pointer;
  border: 1px solid var(--border); border-radius: 9px; overflow: hidden;
  background: var(--bg-1); opacity: .62; transition: opacity .12s, border-color .12s; }
.rshot:hover { opacity: .85; }
.rshot.on { opacity: 1; border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.rshot img { display: block; width: 100%; height: 72px; object-fit: cover;
  object-position: top; }
.rshot .tick { position: absolute; top: 5px; right: 5px; width: 17px; height: 17px;
  display: grid; place-items: center; border-radius: 50%; font-size: 11px; line-height: 1;
  background: var(--accent-fill); color: #fff; opacity: 0; transition: opacity .12s; }
.rshot.on .tick { opacity: 1; }
/* The filename, where it says anything — the MCP's own page-<timestamp> says
   nothing and is left off by the server rather than shown as noise. */
.rshot .cap { display: block; padding: 4px 6px 5px; font-size: 10.5px; line-height: 1.3;
  color: var(--fg-faint); text-align: left; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }
.rshot.on .cap { color: var(--fg-dim); }
@media (pointer: coarse) {
  .rshot img { height: 84px; }
}

/* A screenshot in the conversation. Capped rather than full width: these are
   phone screenshots of other people's phone screenshots, and at full width one
   of them pushes everything said around it off the screen. Clicking opens the
   real thing. */
.fshots { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 8px; }
/* 420, not 260: these are screenshots of somebody's phone, often with the
   text that matters rendered inside them, and at thumbnail size the whole
   point of showing it is lost. */
.fshot { margin: 0; max-width: min(100%, 420px); }
.fshot a { display: block; border-radius: 10px; overflow: hidden;
  border: 1px solid var(--border); background: var(--bg-1); }
.fshot img { display: block; width: 100%; height: auto; max-height: 420px;
  object-fit: cover; object-position: top; }
.fshot a:hover { border-color: var(--border-2); }
/* The description the reading pass went on, so it is possible to see why a
   report says what it says. Dimmer and smaller than the message: it is a
   machine's account of the picture, not something anybody said — and folded
   to three lines, because at full length it outweighed the conversation it
   was meant to support. */
.fshot figcaption { margin-top: 5px; font-size: 11.5px; line-height: 1.5;
  color: var(--fg-faint); }
.fshot figcaption .capbody { display: -webkit-box; -webkit-box-orient: vertical;
  -webkit-line-clamp: 3; line-clamp: 3; overflow: hidden; }
.fshot figcaption.open .capbody { -webkit-line-clamp: none; line-clamp: none; }
.fshot .capmore { margin-top: 2px; border: 0; background: none; padding: 0;
  cursor: pointer; color: var(--fg-faint); font: inherit; font-size: 11px;
  text-decoration: underline; text-underline-offset: 2px; }
.fshot .capmore:hover { color: var(--fg-dim); }
.fshot.nofile { max-width: 100%; }
.fshot.nofile figcaption { font-style: italic; }

/* Asking about a report, in its own window. `fask`, not `ask`: the workspace
   Ask pane owns `.askbox`/`.asklist` further down this file, and sharing the
   names meant its composer rules landed on this window's wrapper.

   The chain that makes this scroll runs from the sheet down: .tall gives the
   sheet a fixed height, #fasks takes whatever is left of it, and the list is
   the one part allowed to overflow. Every link needs min-height: 0, or a flex
   item refuses to shrink below its content and the overflow leaves the sheet
   instead of scrolling inside it. */
#modal .sheet.tall > #fasks { flex: 1 1 auto; min-height: 0; display: flex; }
.faskbox { display: flex; flex-direction: column; align-items: stretch;
  flex: 1 1 auto; min-height: 0; width: 100%; }
.fasklist { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch; }
.faskempty { padding: 26px 0; text-align: center; color: var(--fg-faint); font-size: 13px; }
.fasklist .ask { padding: 12px 0; border-bottom: 1px solid var(--border); }
.fasklist .ask:last-child { border-bottom: 0; }
.fasklist .q { font-size: 12.5px; font-weight: 600; color: var(--fg); margin-bottom: 6px; }
.fasklist .a { font-size: 13px; line-height: 1.55; color: var(--fg-dim); }
.fasklist .a.err { color: var(--red); }
.fasklist .a.waiting { display: flex; align-items: center; gap: 10px; color: var(--fg-faint);
  font-style: italic; }
/* An answer can be wider than the window — a path, a table, a line of code —
   and a list that scrolls sideways is worse than one that wraps. */
.fasklist .a.md pre { overflow-x: auto; }
.fasklist .a.md table { display: block; overflow-x: auto; }
/* `src/components/clinic/patient/filters/FilterNationality.vue` is one word to
   the line breaker, and at 462px it gave the whole list a horizontal scrollbar.
   A code block still scrolls; a path in a sentence wraps. */
.fasklist .a.md :not(pre) > code,
.fasklist .a > code { overflow-wrap: anywhere; }
/* Across the bottom, not tucked into a corner: the box you type in is the
   point of the window and should look like it. */
.faskform { flex: 0 0 auto; display: flex; gap: 8px; width: 100%; padding-top: 12px;
  border-top: 1px solid var(--border); }
.faskform .btn { flex: 0 0 auto; }
.faskform input { flex: 1 1 auto; min-width: 0; margin: 0; padding: 8px 11px; border-radius: 9px;
  border: 1px solid var(--border); background: var(--bg); color: var(--fg); font: inherit;
  font-size: 13px; }
.faskform input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
@media (pointer: coarse) {
  .faskform input, .faskform .btn { min-height: 44px; }
}

.fdetail .acts { display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
  padding-top: 16px; border-top: 1px solid var(--border); }
/* Pushes anything after it to the far end, so the action that takes a report
   off the list is nowhere near the one that takes it on. */
.fdetail .acts .spacer { flex: 1 1 auto; }
.fdetail .acts .btn.icon { padding: 7px 12px; font-size: 15px; line-height: 1; }
/* Classification sits with the facts it describes, not with the things you
   do: a kind and a priority are what the report *is*, and mixing them into a
   row of verbs made seven controls where there are really three. */
.fdetail .dmeta .grow { flex: 1 1 auto; }
/* The same chip the card shows, with a press on it. */
.tagchip { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 5px; margin: 0;
  padding: 3px 9px 3px 7px; border-radius: 6px; border: 1px solid transparent;
  background: var(--bg-3); color: var(--fg-dim); font: inherit; font-size: 11px;
  font-weight: 600; cursor: pointer; }
.tagchip:hover { border-color: var(--fg-faint); }
.tagchip:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.tagchip .ic { flex: 0 0 auto; opacity: .9; }
.tagchip.label-bug { background: color-mix(in srgb, var(--red) 16%, transparent); color: var(--red); }
.tagchip.label-idea { background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--accent); }
.tagchip.label-question { background: color-mix(in srgb, var(--green) 16%, transparent); color: var(--green); }
/* Priority keeps its own three colours, as a dot so the chip does not shout
   as loudly as the kind beside it. */
.tagchip .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--fg-faint); }
.tagchip.severity-high .dot { background: var(--prio-high); }
.tagchip.severity-medium .dot { background: var(--prio-med); }
.tagchip.severity-low .dot { background: var(--prio-low); }
.fdetail .acts .btn { display: inline-flex; align-items: center; gap: 6px; }
.fdetail .acts .btn .ic { flex: 0 0 auto; }
/* Quiet, because removing something should not look like the thing to do. */
.fdetail .acts .btn.quiet { background: none; border-color: var(--border); color: var(--fg-dim); }
.fdetail .acts .btn.quiet:hover { color: var(--fg); border-color: var(--fg-faint); }
.fdetail .acts .btn .n { padding: 0 6px; border-radius: 999px; background: var(--bg-3);
  font-size: 10.5px; font-variant-numeric: tabular-nums; }
.fdetail .acts select { flex: 0 0 auto; width: auto; min-width: 104px; min-height: 36px;
  margin: 0; padding: 6px 9px; border-radius: 9px; border: 1px solid var(--border);
  background: var(--bg-1); color: var(--fg); font: inherit; font-size: 12.5px;
  touch-action: manipulation; }
.fdetail .acts select:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* Why it is worth taking, right beside the button that takes it. */
/* Where an approval has got to, in the report's own header line. */
.fdetail .dmeta .appr { color: var(--amber); }
.fdetail .dmeta .appr.approved { color: var(--green); }
.fdetail .dmeta .appr.declined { color: var(--red); }
.fdetail .why { flex: 1 0 100%; margin: 0 0 4px; font-size: 11.5px; color: var(--fg-dim); }
.fdetail .why strong { color: var(--fg); font-weight: 600; }

/* Nothing selected, and nothing to show. */
.fblank, #inboxList .fempty { padding: 48px 20px; text-align: center; color: var(--fg-dim); }
.fblank > .ic, #inboxList .fempty > .ic { color: var(--fg-faint); opacity: .7; margin-bottom: 12px; }
.fblank h2, #inboxList .fempty h2 { margin: 0 0 6px; font-size: 15px; font-weight: 600; color: var(--fg); }
.fblank p, #inboxList .fempty p { margin: 0 auto; max-width: 44ch; font-size: 13px; line-height: 1.6; }

@media (pointer: coarse) {
  .fcard { padding: 12px 12px 12px 14px; }
  .fcard .quick { min-height: 36px; }
  .fdetail .acts select, .fdetail .acts .btn { min-height: 44px; }
}

@media (max-width: 820px) {
  /* Columns a thumb can reach one at a time, and the report over all of it. */
  #inboxBoard { gap: 10px; scroll-snap-type: x mandatory; }
  .fcol { flex: 0 0 86vw; }
  #inboxDetail { inset: 0; width: auto; border-left: 0; box-shadow: none; }
  .fdetail { padding: 14px 14px 24px; max-width: none; }
  .fdetail .back { min-height: 44px; }
  /* The status bar sits on the header's own surface rather than on a band of
     background above it, so there is no gap to look at and the heading still
     clears the clock. */
  #inbox .shead { padding-top: max(10px, env(safe-area-inset-top, 0px)); }
  /* Two rows rather than four wrapped fragments: the title keeps Done on its
     line, the filters share the next one, and the count sits under both. */
  #inbox .shead { flex-wrap: wrap; row-gap: 8px; column-gap: 8px; }
  #inbox .shead h1 { order: 1; }
  #inbox .shead .grow { order: 2; }
  #inbox .shead > .btn { order: 3; }
  #inbox .ffilters { order: 4; flex: 1 0 100%; }
  #inbox .ffilters select { flex: 1 1 0; min-width: 0; width: auto; }
  #inbox .shead .sub { order: 5; flex: 1 0 100%; }
}
.board-inner { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; padding: 20px 24px 18px; }
.board-inner .shead { display: flex; align-items: center; gap: 12px; flex: 0 0 auto; margin-bottom: 14px; }
.board-inner h1 { margin: 0; font-size: 20px; font-weight: 600; letter-spacing: -.02em; }
.board-inner .sub { color: var(--fg-faint); font-size: 12px; font-family: var(--mono); }
.board-inner .grow { flex: 1 1 auto; }
/* A native checkbox is a pale square the browser draws to its own taste, which
   on a dark header is the one bright thing on the row and matches nothing else
   in the app. Built from the same parts as the other controls instead: the
   pill shape of a chip, lit when it is on. */
.autolabel { display: inline-flex; align-items: center; gap: 7px; flex: 0 0 auto; cursor: pointer;
  padding: 4px 10px; border-radius: 999px;
  border: 1px solid var(--border); background: var(--bg-1);
  color: var(--fg-dim); font: 11px var(--mono); user-select: none;
  white-space: nowrap; }
.autolabel:hover { border-color: var(--border-2); color: var(--fg); }
/* The box itself is drawn here, so it is the same on every platform. */
.autolabel input { appearance: none; -webkit-appearance: none;
  /* padding: 0 explicitly — the shared input rule carries 8px, and with
     border-box that inflates a 13px box to 22 rather than being clipped. */
  width: 13px; height: 13px; margin: 0; padding: 0; flex: 0 0 auto;
  border: 1px solid var(--border-2); border-radius: 4px; background: var(--bg);
  position: relative; cursor: pointer; }
.autolabel input:checked { background: var(--accent-fill); border-color: var(--accent); }
.autolabel input:checked::after {
  content: ''; position: absolute; left: 3.5px; top: 0.5px; width: 3px; height: 7px;
  border: solid var(--on-accent); border-width: 0 2px 2px 0; transform: rotate(45deg); }
.autolabel input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Lit when it is on, so the state reads from across the header rather than
   from a 13px box. */
.autolabel:has(input:checked) { border-color: var(--accent); background: var(--sel); color: var(--fg); }
/* A colleague's board: the switch still shows whether they use auto-label,
   because that is a fact about their board, but it does not take a click. */
.autolabel:has(input:disabled) { cursor: default; opacity: 0.6; }
.autolabel:has(input:disabled):hover { border-color: var(--border); color: var(--fg-dim); }
.autolabel input:disabled { cursor: default; }

/* Whose board. A select rather than a row of names: the team grows and a
   header is not somewhere a list can grow sideways. */
.whosel { flex: 0 0 auto; width: auto; margin: 0; padding: 4px 10px; border-radius: 999px;
  border: 1px solid var(--border); background: var(--bg-1); color: var(--fg-dim);
  font: 11px var(--mono); cursor: pointer; }
.whosel:hover { border-color: var(--border-2); color: var(--fg); }
#board.visiting .whosel { border-color: var(--accent); background: var(--sel); color: var(--fg); }
/* The columns scroll sideways and each scrolls down on its own, so a long
   column never pushes the others off the bottom. */
#boardCols { flex: 1 1 auto; min-height: 0; display: flex; gap: 12px; overflow-x: auto; padding-bottom: 4px; }
/* `min-width: 0` or the basis is a suggestion. A flex item's automatic minimum
   is its content's, and a branch like chore/poliklinikdrhalimah-migration is
   505px of unbreakable text — which pushed a 290px column out to 558 and took
   the cards with it. With the minimum removed the basis holds and the long
   names ellipsise, which is what they were already set up to do. */
.bcol { flex: 0 0 340px; min-width: 0; display: flex; flex-direction: column; min-height: 0;
  background: var(--bg-1); border: 1px solid var(--border); border-radius: 12px; }
/* Width rather than flex-basis, so the drag can set it — a basis would win. */
.bcol.tray { flex: 0 0 auto; width: 340px; min-width: 0; background: var(--bg-2);
  border-style: dashed; position: relative; }
.bcol.tray[hidden] { display: none; }
.bcol.tray .fold { border: none; background: none; cursor: pointer; color: var(--fg-faint);
  font-size: 15px; line-height: 1; padding: 1px 5px; border-radius: 5px; }
.bcol.tray .fold:hover { background: var(--bg-3); color: var(--fg); }
/* Wider than it looks: a 1px target is a test of aim rather than a control. */
.bcol.tray .grip { position: absolute; top: 0; right: -4px; bottom: 0; width: 8px;
  cursor: col-resize; z-index: 2; }
.bcol.tray .grip:hover, .bcol.tray .grip.dragging { background: var(--accent-fill); opacity: .5; }
/* The way back, once it is folded. */
.trayshow { flex: 0 0 20px; align-self: stretch; border: 1px dashed var(--border);
  border-radius: 10px; background: var(--bg-2); color: var(--fg-faint); cursor: pointer;
  font-size: 14px; padding: 8px 0 0; display: flex; align-items: flex-start;
  justify-content: center; }
.trayshow[hidden] { display: none; }
.trayshow:hover { color: var(--fg); border-color: var(--border-2); }
.bcol.over { border-color: var(--accent); }
.bcolhd { display: flex; align-items: center; gap: 8px; flex: 0 0 auto;
  padding: 10px 12px; border-bottom: 1px solid var(--border); }
/* The header is the handle, so it says so under the pointer — but the name
   inside it is still a text field, and a caret there means editing. */
.bcolhd { cursor: grab; }
.bcolhd:active { cursor: grabbing; }
.bcolhd .cgrip { flex: 0 0 auto; color: var(--fg-faint); font-size: 11px; letter-spacing: -1px;
  opacity: 0; transition: opacity .12s; }
.bcol:hover .bcolhd .cgrip { opacity: 1; }
.bcol.coldrag { opacity: .45; }
.bcolhd .nm { font-size: 13px; font-weight: 600; outline: none; border-radius: 4px; padding: 1px 3px;
  cursor: text; }
.bcolhd .nm[contenteditable]:hover { background: var(--bg-3); }
.bcolhd .nm[contenteditable]:focus { background: var(--bg-3); box-shadow: 0 0 0 1px var(--accent); }
.bcolhd .cnt { color: var(--fg-faint); font: 11px var(--mono); }
.bcolhd .grow { flex: 1 1 auto; }
.bcolhd .bx { border: none; background: none; color: var(--fg-faint); cursor: pointer;
  font-size: 15px; line-height: 1; padding: 2px 4px; border-radius: 5px; opacity: 0; }
.bcol:hover .bcolhd .bx { opacity: 1; }
.bcolhd .bx:hover { background: var(--bg-3); color: var(--red); }
/* The filter sits under the name, full width: a column is 290px and a box
   beside the title would have room for about four characters. */
.bfind { position: relative; flex: 0 0 auto; padding: 7px 8px 0; }
.bfind input { width: 100%; padding: 4px 20px 4px 8px; border-radius: 6px;
  border: 1px solid var(--border); background: var(--bg); color: var(--fg);
  font: 11px var(--mono); }
.bfind input::-webkit-search-cancel-button { display: none; }
.bfind input::placeholder { color: var(--fg-faint); }
.bfind input:focus { outline: none; border-color: var(--border-2); }
.bfind [data-bclear] { position: absolute; right: 11px; top: 50%; transform: translateY(-25%);
  border: none; background: none; cursor: pointer; color: var(--fg-faint);
  font-size: 13px; line-height: 1; padding: 2px 3px; }
.bfind [data-bclear][hidden] { display: none; }
.bfind [data-bclear]:hover { color: var(--fg); }

.bcards { flex: 1 1 auto; min-height: 40px; overflow-y: auto; padding: 8px; display: flex;
  flex-direction: column; gap: 7px; }
.bempty { color: var(--fg-faint); font-size: 11.5px; text-align: center; padding: 14px 8px; }

/* The freshness reads as an edge rather than a tint: a card tinted green or
   brown fights the status dot and the labels for the same job, and on a light
   theme a washed background makes the text harder to read for no gain. An edge
   is visible down a column at a glance and competes with nothing. */
/* `flex: 0 0 auto` because the column is a flex container and a card would
   otherwise shrink to share the height: with thirty-three in the tray they
   compressed to 20px each and the text ran over itself. They scroll. */
.bcard { flex: 0 0 auto; min-width: 0;
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 9px;
  padding: 9px 10px 9px 15px; cursor: pointer; position: relative; overflow: hidden; }
.bcard::before {
  content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 6px;
  background: var(--fresh, var(--border-2));
  /* The oldest fade out rather than shouting in brown — "nothing here" should
     be quiet, and the eye should land on what is moving. */
  opacity: calc(1 - var(--fresh-t, 0) * 0.3);
}
.bcard .age { color: var(--fresh, var(--fg-faint)); }
.bcard:hover { border-color: var(--border-2); }
.bcard:focus-visible { outline: none; border-color: var(--accent); }
/* Half-there while it is being carried, so the gap it will leave is visible. */
.bcard.dragging { opacity: .35; }
/* The card actually under the finger. Follows the pointer rather than sitting
   in a column, so it is out of the flow and above everything. */
.bcard.ghost { position: fixed; top: 0; left: 0; z-index: 200; pointer-events: none;
  margin: 0; box-shadow: 0 12px 28px -10px var(--shadow); border-color: var(--accent);
  transform: translate(-9999px, -9999px); }
/* While a card is in hand the board stops interpreting touches as scrolling —
   the drag does its own scrolling, at the edges. */
body.bdragging { user-select: none; }
body.bdragging .bcards, body.bdragging #boardCols { scroll-behavior: auto; }
/* A press-and-hold on a phone must not also select the text under it.
   Both axes pan: the columns are 82vw on a phone and the board scrolls
   sideways between them, but a card fills nearly all of its column — so
   `pan-y` meant almost every sideways swipe landed on a card and was refused,
   and the board could only be scrolled by finding a gap between two of them.
   Holding still is what starts a drag; moving is a scroll either way. */
.bcard { -webkit-touch-callout: none; -webkit-user-select: none; user-select: none;
  touch-action: pan-x pan-y; }
.bcard .bhd { display: flex; align-items: center; gap: 7px; }
.bcard .bhd .t { font-size: 13px; font-weight: 500; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.bcard .bm { margin-top: 3px; color: var(--fg-faint); font: 11px var(--mono);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bcard .blabels { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; }
.bcard .bft { display: flex; align-items: center; gap: 6px; margin-top: 7px; }
.bcard .bft .grow { flex: 1 1 auto; }
.bcard .age { font: 10.5px var(--mono); }

@media (max-width: 820px) {
  /* Installed to the home screen the page owns the whole display, notch and
     all, so the heading has to be told where the screen actually begins —
     otherwise it sits under the clock. */
  .board-inner {
    padding: calc(10px + env(safe-area-inset-top, 0px)) 12px calc(10px + env(safe-area-inset-bottom, 0px));
    padding-left: calc(12px + env(safe-area-inset-left, 0px));
    padding-right: calc(12px + env(safe-area-inset-right, 0px)); }
  /* Four things on one line at 390px meant "Your board" wrapping underneath
     its own subtitle. The count drops to its own line and the buttons keep
     theirs, so nothing overlaps anything. */
  .board-inner .shead { flex-wrap: wrap; row-gap: 4px; column-gap: 8px; margin-bottom: 10px; }
  .board-inner h1 { font-size: 17px; }
  .board-inner .sub { order: 3; flex: 1 0 100%; }
  /* The tray carries a width for the desktop drag, and a class selector beats
     a media query — so it has to be named here or it keeps 340px on a 390px
     screen while every other column is 82vw. */
  .bcol, .bcol.tray { flex: 0 0 82vw; width: auto; }
}

/* The key that does the same thing, said on the button that does it. Hidden on
   a phone, where there is no key to press and it would only take up room. */
.btn kbd { margin-left: 6px; padding: 1px 5px; border-radius: 4px;
  background: var(--bg-1); border: 1px solid var(--border);
  color: var(--fg-faint); font: 10px var(--mono); }
@media (max-width: 820px) { .btn kbd { display: none; } }

#settings { position: fixed; inset: 0; z-index: 150; background: var(--bg); overflow-y: auto; }
#settings[hidden] { display: none; }
.settings-inner { max-width: 860px; margin: 0 auto; padding: 28px 24px 70px; }
/* The way out stays reachable. Settings is one long scroll, and Done was at the
   top of it — far enough down the GitLab section and there was no way back
   without scrolling to find one.
   Its own background and the negative margins so the content passing underneath
   is covered edge to edge rather than showing through the padding. */
.settings-inner .shead { display: flex; align-items: center; gap: 12px;
  position: sticky; top: 0; z-index: 5;
  margin: -28px -24px 6px; padding: 20px 24px 12px;
  background: var(--bg); border-bottom: 1px solid var(--border); }
.settings-inner h1 { margin: 0; font-size: 20px; font-weight: 600; letter-spacing: -.02em; }
.settings-inner .grow { flex: 1 1 auto; }
.settings-inner .lede { margin: 0 0 26px; color: var(--fg-dim); font-size: 13px; }
.settings-inner h2 { margin: 28px 0 4px; font-size: 12px; font-weight: 500; color: var(--fg-faint);
  text-transform: uppercase; letter-spacing: .07em; }
.settings-inner .h2sub { margin: 0 0 12px; color: var(--fg-faint); font-size: 12px; }

.repocard { background: var(--bg-1); border: 1px solid var(--border); border-radius: 12px;
  margin-bottom: 12px; overflow: hidden; }
.repocard > .hd { display: flex; align-items: center; gap: 10px; padding: 13px 15px; cursor: pointer; }
.repocard > .hd:hover { background: var(--bg-2); }
.repocard .nm { font-family: var(--mono); font-size: 13px; font-weight: 600; }
.repocard .sub { color: var(--fg-faint); font-size: 11.5px; font-family: var(--mono); }
.repocard .grow { flex: 1 1 auto; }
.repocard .caret { color: var(--fg-faint); font-size: 11px; transition: transform .15s; }
.repocard.open .caret { transform: rotate(90deg); }
.repocard .body { display: none; padding: 4px 15px 16px; border-top: 1px solid var(--border); }
.repocard.open .body { display: block; }
.repocard .body .field { margin-bottom: 14px; }
.repocard .body textarea { font-family: var(--mono); font-size: 12px; }
.repocard .body .hint { margin: 4px 0 0; color: var(--fg-faint); font-size: 11.5px; line-height: 1.5; }
.repocard .saved { color: var(--green); font-size: 11.5px; }

.toggle { position: relative; width: 34px; height: 19px; border-radius: 10px; flex: 0 0 auto;
  background: var(--bg-3); border: 1px solid var(--border-2); cursor: pointer; transition: background .15s; }
.toggle::after { content: ""; position: absolute; top: 2px; left: 2px; width: 13px; height: 13px;
  border-radius: 50%; background: var(--fg-faint); transition: transform .15s, background .15s; }
.toggle.on { background: var(--sel-strong); border-color: var(--accent); }
.toggle.on::after { transform: translateX(15px); background: var(--accent-fill); }

.btypes { display: flex; flex-wrap: wrap; gap: 6px; }
.btype { display: flex; align-items: center; gap: 6px; padding: 4px 8px; border-radius: 7px;
  background: var(--bg-3); border: 1px solid var(--border-2); font-family: var(--mono); font-size: 11.5px; }
.btype input { width: auto; background: none; border: none; padding: 0; font: inherit;
  color: var(--fg); width: 90px; }
.btype .x { color: var(--fg-faint); cursor: pointer; }
.btype .x:hover { color: var(--red); }

.avail { border: 1px solid var(--border); border-radius: 10px; max-height: 260px; overflow-y: auto; }
.avail .row { display: flex; align-items: center; gap: 9px; padding: 8px 12px;
  border-bottom: 1px solid var(--border); font-size: 12.5px; }
.avail .row:last-child { border-bottom: none; }
.avail .row .nm { flex: 1 1 auto; min-width: 0; font-family: var(--mono); font-size: 11.5px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.avail .empty { padding: 14px; color: var(--fg-faint); font-size: 12px; text-align: center; }

/* branch-type chooser in the new-workspace dialog */
.btsel { display: flex; gap: 4px; flex-wrap: wrap; }
.btsel button { padding: 5px 11px; border-radius: 7px; background: var(--bg); border: 1px solid var(--border-2);
  color: var(--fg-dim); font-size: 12px; font-family: var(--mono); cursor: pointer; }
.btsel button:hover { color: var(--fg); }
.btsel button.on { background: var(--bg-3); color: var(--accent); border-color: var(--accent); }
#nBranch { width: 100%; margin-top: 7px; padding: 6px 9px; border-radius: 7px;
  border: 1px solid var(--border); background: var(--bg-1); color: var(--fg);
  font: 12px var(--mono); }
#nBranch:focus { outline: none; border-color: var(--accent); }
#nBranch::placeholder { color: var(--fg-faint); font-family: var(--sans, inherit); }

.branchpreview { margin-top: 7px; color: var(--fg-faint); font-size: 11.5px; font-family: var(--mono); }
.branchpreview b { color: var(--accent); font-weight: 500; }

/* ------------------------------------------------- syntax highlighting */
/* Written against this app's own palette rather than importing a stock theme,
   so code sits in the transcript instead of shouting over it. Hues are kept
   few and far apart: identifiers stay plain, only meaning gets colour. */
.ev .md pre code.hljs { display: block; background: none; padding: 0; }
.ev .md .codeblock > .lang {
  position: absolute; top: 6px; right: 8px;
  font-family: var(--mono); font-size: 10px; letter-spacing: .06em;
  text-transform: uppercase; color: var(--fg-faint); pointer-events: none;
}
/* Copy sits where the language label is; on hover the label yields to it so the
   two never overlap in the corner. */
.ev .md .codeblock > .copy {
  position: absolute; top: 4px; right: 6px; z-index: 1;
  padding: 2px 7px; border-radius: 5px; cursor: pointer;
  background: var(--bg-3); border: 1px solid var(--border);
  color: var(--fg-dim); font-family: var(--mono); font-size: 10px;
  opacity: 0; transition: opacity .12s;
}
.ev .md .codeblock:hover > .copy,
.ev .md .codeblock > .copy:focus-visible { opacity: 1; }
.ev .md .codeblock:hover > .lang { opacity: 0; }
.ev .md .codeblock > .copy:hover { color: var(--fg); border-color: var(--accent); }
.ev .md .codeblock > .copy.ok { color: var(--green); border-color: var(--green); opacity: 1; }

.hljs-comment, .hljs-quote { color: var(--hl-comment); font-style: italic; }
.hljs-keyword, .hljs-selector-tag, .hljs-literal,
.hljs-section, .hljs-doctag, .hljs-type { color: var(--hl-type); }
.hljs-string, .hljs-regexp, .hljs-addition,
.hljs-attribute, .hljs-meta .hljs-string { color: var(--hl-string); }
.hljs-number, .hljs-symbol, .hljs-bullet, .hljs-link { color: var(--hl-number); }
.hljs-title, .hljs-title.function_, .hljs-name { color: var(--hl-title); }
.hljs-title.class_, .hljs-built_in, .hljs-class .hljs-title { color: var(--hl-class); }
.hljs-attr, .hljs-variable, .hljs-template-variable,
.hljs-selector-attr, .hljs-selector-pseudo { color: var(--hl-attr); }
.hljs-meta, .hljs-meta .hljs-keyword { color: var(--hl-meta); }
.hljs-deletion { color: var(--hl-del); }
.hljs-addition { color: var(--hl-add); }
.hljs-emphasis { font-style: italic; }
.hljs-strong { font-weight: 600; }

/* The brief a workspace was opened with. Set apart because nobody said it in
   this chat — it was written elsewhere, by whoever opened this — and reading it
   as a message would be reading it as something that happened here. */
.ev.brief .bd { border-left: 2px solid var(--border-2); padding-left: 12px;
  background: var(--bg-2); border-radius: 0 8px 8px 0; padding: 10px 12px 4px; }
.ev.brief .briefhd { color: var(--fg-faint); font-size: 10.5px; font-family: var(--mono);
  text-transform: uppercase; letter-spacing: .06em; margin-bottom: 8px; }

/* ------------------------------------- tables and prose from the markdown */
/* A helper's result is the same md() output as a transcript message, so it gets
   the same rules. These were scoped to the transcript alone, which left helper
   results on browser defaults: headings at h1 size, and a table that ran off
   the side of its card instead of scrolling inside it. */
/* Wide tables scroll inside their own box rather than stretching the pane. */
.ev .md .mdtable,
.helper .hbody .mdtable { overflow-x: auto; margin: 0 0 10px; }
.ev .md table,
.helper .hbody table { border-collapse: collapse; font-size: 12.5px; min-width: 100%; }
.ev .md th,
.helper .hbody th {
  text-align: left; font-weight: 600; color: var(--fg-dim);
  padding: 5px 12px 5px 0; border-bottom: 1px solid var(--border-2); white-space: nowrap;
}
.ev .md td,
.helper .hbody td {
  padding: 5px 12px 5px 0; border-bottom: 1px solid var(--hair);
  color: var(--fg); vertical-align: top;
}
.ev .md tr:last-child td,
.helper .hbody tr:last-child td { border-bottom: none; }
.ev .md th:last-child, .ev .md td:last-child,
.helper .hbody th:last-child, .helper .hbody td:last-child { padding-right: 0; }

.ev .md .mdh,
.helper .hbody .mdh { margin: 14px 0 6px; font-weight: 600; letter-spacing: -.01em; }
.ev .md h1.mdh, .helper .hbody h1.mdh { font-size: 16px; }
.ev .md h2.mdh, .helper .hbody h2.mdh { font-size: 14.5px; }
.ev .md h3.mdh, .ev .md h4.mdh,
.helper .hbody h3.mdh, .helper .hbody h4.mdh { font-size: 13.5px; color: var(--fg-dim); }
.ev .md .mdh:first-child,
.helper .hbody .mdh:first-child { margin-top: 0; }

/* Why the Checks tab is marked, before the per-repo detail. Amber-edged so it
   reads as the same signal as the dot rather than as another row. */
/* Asking about a workspace: the transcript's two shapes, a scroll, and a
   composer. Everything here uses the sizes the rest of the app uses — an
   answer is prose and belongs at reading size, not at label size. */
.asklist { flex: 1 1 auto; min-height: 0; overflow-y: auto;
  margin: 12px -6px 0; padding: 0 6px; }
/* Centred while empty, so the modal reads as waiting for a question rather
   than as a panel that failed to load. */
.asklist.bare { display: flex; align-items: center; justify-content: center; }
.askempty { max-width: 420px; text-align: center; }
.askempty p { margin: 0 0 14px; color: var(--fg-faint); font-size: 12.5px; line-height: 1.6; }
/* Pills that wrap, not a stack of full-width boxes pretending to be inputs.
   --border, not --line: --line is defined nowhere, and an undefined custom
   property invalidates the whole `border` shorthand — so these drew at 0px and
   the examples read as prose rather than as the buttons they are. */
.askempty .egs { display: flex; flex-wrap: wrap; gap: 6px; justify-content: center; }
.askempty .eg { padding: 6px 12px; border: 1px solid var(--border); border-radius: 999px;
  background: none; color: var(--fg-dim); cursor: pointer;
  font-family: inherit; font-size: 12.5px; line-height: 1.4; }
.askempty .eg:hover { background: var(--bg-2); border-color: var(--border-2); color: var(--fg); }

/* white-space: normal, because .who inherits pre-wrap from .ev.user .bd — the
   gap between the name and the time belongs to `gap`, not to the template's
   indentation. */
.asklist .ev.user .who { display: flex; align-items: baseline; gap: 8px;
  white-space: normal; }
.asklist .ev.user .who .when { color: var(--fg-faint); font-weight: 400; }
/* Only on the question it belongs to, and only under the pointer: a column of
   × down the side would read as the point of the list. */
.asklist .askx { margin-left: auto; border: none; background: none; cursor: pointer;
  color: var(--fg-faint); font-size: 14px; line-height: 1; padding: 0 2px; opacity: 0; }
.asklist .ev.user:hover .askx, .asklist .askx:focus { opacity: 1; }
.asklist .askx:hover { color: var(--red); }
/* Always visible, unlike the ×: it is only there while something is running,
   which is exactly when it should not need hunting for. */
.asklist .askstop { margin-left: auto; border: 1px solid var(--border); border-radius: 999px;
  background: none; cursor: pointer; color: var(--fg-faint);
  font-family: var(--mono); font-size: 10.5px; line-height: 1.6; padding: 0 8px; }
.asklist .askstop:hover:not(:disabled) { color: var(--fg); border-color: var(--border-2); }
.asklist .askstop:disabled { opacity: .6; cursor: default; }
.asklist .thinking { color: var(--fg-faint); font-style: italic; }
.asklist .askbad { color: var(--red); }
.asklist .ev .md pre { background: var(--bg-1); border: 1px solid var(--border);
  border-radius: 8px; padding: 8px 10px; overflow-x: auto; }

/* The composer, aligned on the bottom edge so the button sits level with the
   last line of a question however tall it has grown. */
.askbox { flex: 0 0 auto; display: flex; align-items: flex-end; gap: 8px;
  margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border); }
/* Scrolls past the 140px cap but never shows a bar for it: a composer this
   small is scrolled by typing, and the 9px track stole width from the text. */
.askbox textarea { flex: 1 1 auto; resize: none; overflow-y: auto; line-height: 1.5;
  scrollbar-width: none; }
.askbox textarea::-webkit-scrollbar { display: none; }
/* 38px, not 36: the field's resting height is its line, its 8px padding and
   its border, and the two are meant to read as one control side by side. */
.askbox .btn { flex: 0 0 auto; height: 38px; padding: 0 18px; }

/* The amber rule needs room beside it — without padding the text sat against
   the line, which read as a rendering fault rather than an accent. */
.chk .block.behindnote { border-left: 2px solid var(--amber); background: #fbbf240d;
  padding: 2px 12px; border-radius: 0 8px 8px 0; }
.chk .block.behindnote code { color: var(--fg); }

/* A message typed while the agent was still answering. Dimmed and dashed
   because it has not been said yet — it is waiting its turn, and showing it
   like an ordinary message would claim the agent had already seen it. */
/* The dashed outline is the whole treatment, so the solid card underneath goes
   — otherwise it reads as a box inside a box. And `white-space: normal`,
   because .ev.user .bd is pre-wrap for message text: with the markup below
   nested inside it, every newline and indent in the template was rendering as
   a blank line, which is where the empty band came from. The text itself keeps
   pre-wrap on .qtext, where it belongs. */
/* The outline goes on the row, exactly where a sent message puts its own — so
   the avatar sits inside it rather than stranded alongside. Dashed and dimmed
   because it has not been said yet. */
.ev.user.queued { background: none; border: 1px dashed var(--border-2); opacity: .72; }
.ev.user.queued .bd { border: none; padding: 0; background: none; white-space: normal; }
.ev.user.queued .qtag { margin-left: 7px; padding: 1px 6px; border-radius: 5px;
  background: var(--bg-3); color: var(--fg-faint); font-size: 9.5px; font-family: var(--mono);
  text-transform: uppercase; letter-spacing: .05em; }
.ev.user.queued .who { display: flex; align-items: center; }
.ev.user.queued .who .grow { flex: 1 1 auto; }
/* Big enough to hit on a phone, quiet enough not to invite it. */
.ev.user.queued .qdel { flex: 0 0 auto; width: 26px; height: 26px; margin: -5px -4px -5px 0;
  padding: 0; border: none; border-radius: 6px; background: none; color: var(--fg-faint);
  font-size: 12px; line-height: 1; cursor: pointer; }
.ev.user.queued .qdel:hover { background: var(--bg-3); color: var(--red); }
.ev.user.queued .qdel:disabled { opacity: .4; cursor: default; }
.ev.user.queued .qtext { white-space: pre-wrap; word-break: break-word; margin-top: 3px; }
.ev.user.queued .qfiles { margin-top: 5px; color: var(--fg-faint); font-size: 11px;
  font-family: var(--mono); }

/* the person's own messages carry their face, like the team list does */
.ev.user .gut { display: flex; justify-content: center; padding-top: 1px; }
.ev.user .gut .face { width: 22px; height: 22px; border-radius: 50%; font-size: 10px; }
.ev.user .who { display: flex; align-items: center; gap: 6px; }

/* Issue targets in the new-workspace dialog: one checkbox per chosen repo that
   has a GitLab project, because an issue belongs to a project. */
.issuepick { display: flex; flex-wrap: wrap; gap: 6px; }
.issuepick .chk-inline { display: flex; align-items: center; gap: 7px; cursor: pointer;
  padding: 5px 10px; border: 1px solid var(--line); border-radius: 8px;
  background: var(--bg-2); color: var(--fg-dim); font-size: 12.5px; }
.issuepick .chk-inline:hover { border-color: var(--line-2); color: var(--fg); }
.issuepick .chk-inline:has(input:checked) { border-color: var(--accent); color: var(--fg); }
.issuepick .chk-inline input { width: auto; margin: 0; accent-color: var(--accent); }

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

/* Same shape as the send button, quieter, so the composer reads as one control
   with the text between them. */
#composer .attach { flex: 0 0 auto; width: 30px; height: 30px; border-radius: 8px;
  display: grid; place-items: center; background: none; border: 1px solid transparent;
  color: var(--fg-faint); cursor: pointer; }
#composer .attach:hover { border-color: var(--border-2); color: var(--accent); background: var(--bg-3); }
#composer .attach svg { width: 15px; height: 15px; }

/* Staged files, above the box; the same chip is reused in the transcript. */
.attached { display: flex; flex-wrap: wrap; gap: 6px; padding: 0 2px 7px; }
.attached[hidden] { display: none; }
.attached .chip { display: inline-flex; align-items: center; gap: 7px; max-width: 240px;
  padding: 4px 7px 4px 5px; border: 1px solid var(--border-2); border-radius: 8px;
  background: var(--bg-2); color: var(--fg-dim); font-size: 12px; text-decoration: none; }
.attached .chip:hover { border-color: var(--accent); color: var(--fg); }
/* A chip that opens here rather than in a tab is a button, and a button brings
   its own font and alignment unless told otherwise. */
.attached button.chip { font: inherit; cursor: pointer; text-align: left; }
.attached .chip img { width: 22px; height: 22px; border-radius: 4px; object-fit: cover; flex: 0 0 auto; }
.attached .chip .ic { width: 22px; text-align: center; color: var(--fg-faint); }
.attached .chip .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.attached .chip .rm { border: none; background: none; color: var(--fg-faint); cursor: pointer;
  font-size: 14px; line-height: 1; padding: 0 2px; }
.attached .chip .rm:hover { color: var(--red); }
/* Filling left to right behind the name while the file goes up. */
.attached .chip.up { position: relative; overflow: hidden; }
.attached .chip.up::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0;
  width: var(--pct, 0%); background: var(--accent-fill); opacity: .16; transition: width .2s linear; }
.attached .chip.up > * { position: relative; }      /* above the fill */
.attached .chip.up .pc { flex: 0 0 auto; min-width: 30px; text-align: right;
  color: var(--fg-faint); font-size: 10.5px; font-variant-numeric: tabular-nums; }
.attached .chip.done .pc { color: var(--green); }
.attached .chip.queued { opacity: .55; }

/* In the transcript an image speaks for itself; a click opens the original. */
.attached.in-chat { padding: 8px 0 0; }
.attached.in-chat .shot { display: block; }
/* `width: auto` on purpose: the timeline's own `.shot img` rule sets width to
   100%, and it matches these too. With the width forced and only the height
   capped, a tall image was stretched to the full width and then squashed to
   260px — the two limits are meant to work together, which they only do while
   both dimensions are free. `min()` keeps it inside a narrow phone as well. */
.attached.in-chat .shot img { width: auto; height: auto;
  max-width: min(340px, 100%); max-height: 260px; border-radius: 8px;
  border: 1px solid var(--border); display: block; }
.attached.in-chat .shot:hover img { border-color: var(--accent); }

/* Covers the transcript while a file is over it, so the target is unmissable. */
#dropveil { position: absolute; inset: 0; z-index: 40; display: grid; place-items: center;
  background: color-mix(in srgb, var(--bg-1) 78%, transparent); pointer-events: none; }
#dropveil[hidden] { display: none; }
#dropveil .inner { padding: 18px 26px; border-radius: 12px; border: 2px dashed var(--accent);
  background: var(--bg-2); color: var(--fg); font-size: 13.5px; }

/* --------------------------------------------------------- mcp servers */

.mcplist { display: flex; flex-direction: column; gap: 6px; }
.mcpempty { color: var(--fg-faint); font-size: 12px; padding: 4px 2px; }
.mcprow { display: flex; align-items: center; gap: 8px; padding: 9px 10px;
  border: 1px solid var(--line); border-radius: 9px; background: var(--bg-2); }
.mcprow.off { opacity: .55; }
.mcprow .mcpmain { flex: 1 1 auto; min-width: 0; }
.mcprow .mcphead { display: flex; align-items: center; gap: 7px; }
.mcprow .nm { color: var(--fg); font-size: 13px; font-family: var(--mono); }
.mcprow .by { color: var(--fg-faint); font-size: 11px; }
/* The command is the part worth reading closely — it is what will run. */
.mcprow .mcpcmd { color: var(--fg-dim); font-size: 11.5px; margin-top: 3px;
  font-family: var(--mono); overflow-wrap: anywhere; }
.mcprow .mcpenv { color: var(--fg-faint); font-size: 11px; margin-top: 2px; font-family: var(--mono); }
.mcprow .btn { flex: 0 0 auto; }

.mcpadd { display: flex; flex-direction: column; gap: 6px; }
.mcpadd textarea { font-family: var(--mono); font-size: 12.5px; line-height: 1.5;
  white-space: pre; overflow-wrap: normal; overflow-x: auto; tab-size: 2; resize: vertical; }
.mcpadd .row { display: flex; align-items: center; gap: 8px; }
.mcpadd .row .grow { flex: 1 1 auto; }

/* A person's Figma token, in Settings. */
.figapp { display: flex; flex-direction: column; gap: 7px; max-width: 460px; }
.figapp input { font-family: var(--mono); font-size: 12.5px; }
/* The buttons sat flush against each other without this. */
.figapp .row { display: flex; align-items: center; gap: 8px; }
.figapp .row .grow { flex: 1 1 auto; }
.mcpnote { margin-top: 2px; color: var(--fg-faint); font-size: 11.5px; }
.mcpnote.bad { color: var(--red); }

/* ------------------------------------------------------- live browser */

/* Splits the chat column: the agent's browser on top, the transcript below.
   Fixed flex-basis with the transcript flexing, so dragging the grip moves the
   boundary rather than squeezing both. */
#liveview { flex: 0 0 auto; height: 300px; min-height: 120px; display: flex; flex-direction: column;
  border-bottom: 1px solid var(--border); background: var(--bg-2); position: relative; }
#liveview[hidden] { display: none; }
#liveview .lvbar { flex: 0 0 auto; display: flex; align-items: center; gap: 8px;
  padding: 6px 12px; color: var(--fg-faint); font-size: 11.5px; font-family: var(--mono); }
#liveview .lvbar .grow { flex: 1 1 auto; }
/* A lit dot, the same language the presence list uses for "live". */
#liveview .dot { width: 6px; height: 6px; border-radius: 50%; flex: 0 0 auto;
  background: var(--green); box-shadow: 0 0 6px var(--green); }
#liveview .lvurl { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#liveview .lvbtn { border: none; background: none; color: var(--fg-faint); cursor: pointer;
  font-size: 11.5px; font-family: var(--mono); padding: 2px 4px; }
#liveview .lvbtn:hover { color: var(--accent); }
/* The page keeps its aspect ratio and is centred on the dark stage, so a
   narrow chat column does not stretch the screenshot. */
#liveview .lvstage { flex: 1 1 auto; min-height: 0; display: grid; place-items: center;
  background: var(--bg-1); overflow: hidden; }
#liveview .lvstage img { max-width: 100%; max-height: 100%; object-fit: contain; display: block; }
#liveview .lvgrip { position: absolute; left: 0; right: 0; bottom: -3px; height: 7px;
  cursor: ns-resize; z-index: 5; }
#liveview .lvgrip:hover { background: color-mix(in srgb, var(--accent) 35%, transparent); }
/* Collapsed: the address bar alone, and clicking it brings the picture back. */
#liveview.collapsed { height: auto !important; min-height: 0; }
#liveview.collapsed .lvstage,
#liveview.collapsed .lvgrip { display: none; }
#liveview.collapsed .lvbar { cursor: pointer; }
#liveview.collapsed .lvbar:hover { color: var(--fg-dim); }

/* When a message was sent, beside the name — quiet enough to skip over. */
.ev.user .who .when { margin-left: 8px; color: var(--fg-faint); font-weight: 400;
  font-size: 11px; font-family: var(--mono); }

/* ------------------------------------------------------------- phone */

/* The same three panes as on a desktop, but only one fits at a time, so the
   tab bar chooses which. Nothing is removed — a phone gets the whole app,
   including the terminal and the run tab. */
/* Bottom tab bar. Fixed rather than in flow, so a focused composer on iOS
   cannot push it off screen. */
/* One source of truth for how tall the bar is, because the panes have to
   reserve exactly this much and the two had already drifted apart once.

   The home indicator wants clearance, but the full 34pt inset under a 54px bar
   left a visibly empty band. 48px of bar plus what is left of the inset after
   12px keeps the labels clear of the indicator and gives 18px back. */
:root {
  --tabbar-h: 48px;
  --tabbar-pad: max(env(safe-area-inset-bottom, 0px) - 12px, 0px);
  --tabbar-total: calc(var(--tabbar-h) + var(--tabbar-pad));
}
/* The keys a shell needs that a phone keyboard does not have. Sits above the
   tab bar when the keyboard is down and rides the top of the keyboard when it
   is up — the offset is applied in script, since only the visual viewport knows
   how much of the page the keyboard has taken. */
#termkeys { position: fixed; left: 0; right: 0; bottom: var(--tabbar-total); z-index: 59;
  display: flex; gap: 4px; padding: 6px 6px;
  background: var(--bg-2); border-top: 1px solid var(--border); }
#termkeys[hidden] { display: none; }
/* 34px, not 42: nine keys at 42 plus their gaps come to 442px, which is wider
   than the phone this bar exists for. The height is what makes a key hittable
   and that is untouched. */
#termkeys button {
  flex: 1 1 0; min-width: 32px; min-height: 40px; padding: 0 2px;
  border: 1px solid var(--border-2); border-radius: 7px; background: var(--bg-3);
  color: var(--fg-dim); font: 13px var(--mono); cursor: pointer;
  white-space: nowrap; overflow: hidden;
  -webkit-tap-highlight-color: transparent; touch-action: manipulation;
}
/* The word keys. An arrow is one glyph and keeps its size; these are three and
   five characters sharing a row with seven other keys on a 375px screen, and
   the height is what makes them hittable, not the type size. */
#termkeys button[data-key="esc"],
#termkeys button[data-key="tab"],
#termkeys button[data-key="ctrl"] { font-size: 11px; }
#termkeys button[data-key="copy"],
#termkeys button[data-key="paste"] { font-size: 10px; letter-spacing: -.2px; }
#termkeys button:active { background: var(--bg-1); color: var(--fg); }
/* Latched, and it stays lit until it is spent — otherwise there is no telling
   whether the next letter will be a letter or a control code. */
#termkeys button.on { border-color: var(--accent); color: var(--accent); background: #f0854a1a; }

#tabbar { position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
  display: none; align-items: stretch;
  background: var(--bg-1); border-top: 1px solid var(--border);
  padding-bottom: var(--tabbar-pad);
  padding-left: env(safe-area-inset-left, 0px);
  padding-right: env(safe-area-inset-right, 0px); }
#tabbar[hidden] { display: none; }
#tabbar button { flex: 1 1 0; display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 3px; height: var(--tabbar-h); position: relative;
  border: none; background: none; color: var(--fg-faint); cursor: pointer;
  font-size: 10.5px; font-family: inherit; }
#tabbar button svg { width: 20px; height: 20px; }
#tabbar button.on { color: var(--accent); }
/* Unread work in a pane you are not looking at. */
#tabbar .badge { position: absolute; top: 6px; right: 50%; margin-right: -20px;
  min-width: 8px; height: 8px; border-radius: 5px; background: var(--accent-fill); }
#tabbar .badge[hidden] { display: none; }

@media (max-width: 820px) {
  #shell { position: relative; }

  /* Each pane fills the shell, stopping above the tab bar. The inset is on the
     panes themselves, not padding on #shell: an absolutely positioned child
     resolves against its ancestor's padding box, so padding there would not
     have moved them and the composer sat under the bar. */
  #side, #center, #right {
    position: absolute; left: 0; right: 0;
    /* Installed to a home screen the web view runs under a translucent status
       bar, which was sitting on top of the workspace title. The inset is on the
       panes rather than on body: body carries `height: 100%`, and padding there
       made the whole column taller than the screen.
       The twenty on top of it clears iOS 26's blur ramp, which reaches a little
       past the inset and was smearing the top of the bar — the title and the
       chips start around seventy-five now, and nothing is drawn above them for
       the ramp to find. None of the three top bars paints its own background,
       so the band reads as the same surface either way. */
    top: calc(env(safe-area-inset-top, 0px) + 20px);
    bottom: var(--tabbar-total);
    width: auto; min-width: 0; border: none; display: none;
  }
  /* Unless the GitLab banner is up: it sits above these and already keeps clear
     of the status bar, so claiming the inset again just opens a gap between the
     two. */
  body.has-banner #side, body.has-banner #center, body.has-banner #right { top: 0; }
  /* The bars sit under the pane's top bar here, not above it, so the inset is
     the pane's to claim — see placeUsebars. Padded rather than extended by the
     ::after hit area: that overlay used to hang into the header's own bottom
     padding, where it covered nothing, and under the bar it would hang over
     the top of the list instead and swallow the first few pixels of whatever
     row sits there. Six real pixels, tappable, over nothing. */
  #usebars { padding: 3px 0; background: transparent; }
  #usebars::after { content: none; }
  body.pane-side #side,
  body.pane-center #center,
  body.pane-right #right { display: flex; }

  /* The drag handles belong to a mouse. */
  #lgrip, #rgrip, #liveview .lvgrip, .hsplit { display: none !important; }
  /* Except these: sizing the list against the patch is the whole reason the Diff
     pane is split, and on a phone the sidebar is the whole screen, so how it
     divides between workspaces and the team matters more there, not less. */
  #sideSplit { display: block !important; }

  /* A 9px target is a mouse target. On a phone the strip stays visually thin —
     the handle inside it does not change — but it grabs from a taller band. */
  .hsplit { flex-basis: 18px; }

  /* Nothing in the sidebar is text you would want to select, and on a touch
     screen trying to select it is actively in the way: a tap that lands on a
     workspace name starts a selection instead of choosing the workspace, so the
     first tap appears to do nothing and it takes two to switch. The composer
     and the transcript keep selection — this is the list only. */
  #side, #side * { -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
  #side .wsitem, #side .frow, #side button { -webkit-tap-highlight-color: transparent; }

  #tabbar { display: flex; }

  /* The top bar: a title you can read, one button for everything else, and
     the chips on a line that scrolls rather than wrapping onto three rows. */
  #wsActions { display: none; }
  #wsbar .title { flex: 1 1 auto; font-size: 15px; }
  #wsbar .meta { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
  #wsbar .meta::-webkit-scrollbar { display: none; }
  #wsbar .meta > * { flex: 0 0 auto; }
  #wsbar { padding-bottom: 8px; }

  /* Roomier touch targets, and no hover-only affordances. */
  .btn.sm { padding: 7px 11px; }
  #wsbar { padding: 10px 12px; }
  #tape { padding: 14px 12px 6px; }
  #composer { padding: 8px 12px 12px; }
  .dfile .dact { opacity: 1; }
  #side { width: auto; }
  /* A phone is not the place for a 480px review pane. */
  #right { width: auto; }
  #liveview { height: 42vh; }

  /* Full width above the tab bar rather than a centred pill, but still one
     row: the sentence takes what is left after the buttons, and ellipsises
     rather than wrapping if a narrow phone cannot fit it. */
  .stalebar { left: 10px; right: 10px; transform: none;
    bottom: calc(var(--tabbar-total) + 10px); }
  .stalebar span { flex: 1 1 auto; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* "Shift+Enter", "drop or paste files" — none of it applies on a phone, and
     it was wrapping onto a second line to say so. The mode and model chips stay. */
  #composer .tools span:not(.grow):not(#agentState) { display: none; }

  /* iOS Safari zooms the page whenever it focuses a field smaller than 16px,
     and then leaves you zoomed in. 16px everywhere it can be typed into is the
     fix; the alternative, user-scalable=no, takes pinch zoom away from everyone
     to solve it. */
  #composer textarea,
  input[type="text"], input[type="password"], input[type="search"],
  input:not([type]), textarea, select { font-size: 16px; }
}

/* Settings is a menu and a pane rather than one long scroll: nine sections in
   a column meant scrolling past everyone else's settings to reach your own. */
.slayout { display: flex; gap: 26px; align-items: flex-start; }
.snav { flex: 0 0 128px; position: sticky; top: 0; display: flex; flex-direction: column; gap: 1px; }
.snav button { text-align: left; padding: 7px 10px; border: none; border-radius: 8px;
  background: none; color: var(--fg-dim); cursor: pointer; font-size: 13px; font-family: inherit; }
.snav button:hover { background: var(--bg-2); color: var(--fg); }
.snav button.on { background: var(--bg-3); color: var(--fg); }
.sbody { flex: 1 1 auto; min-width: 0; }
/* The first heading in a pane leads it, so it needs no space above. */
.sbody .spane > h2:first-child { margin-top: 0; }
.snav .sgroup { margin: 14px 0 4px; padding: 0 10px;
  color: var(--fg-faint); font-size: 10.5px; font-family: var(--mono);
  text-transform: uppercase; letter-spacing: .12em; }
.snav .sgroup:first-child { margin-top: 0; }
.guidebox textarea { font-family: var(--mono); font-size: 12.5px; line-height: 1.55;
  resize: vertical; }
.guidebox { max-width: 720px; }

@media (max-width: 820px) {
  /* No room for a column beside the pane: the menu becomes a scrolling strip. */
  .slayout { flex-direction: column; gap: 14px; }
  .snav { flex: 0 0 auto; position: static; flex-direction: row; align-items: center;
    gap: 4px; overflow-x: auto; padding-bottom: 4px; }
  .snav button { flex: 0 0 auto; white-space: nowrap; }
  .snav .sgroup { flex: 0 0 auto; margin: 0 2px; padding: 0; }
  .sbody { width: 100%; }
}

/* ------------------------------------------------------------- recap */

/* What happened while you were away, above the transcript. Deliberately quiet:
   it is context, not a message, and it goes away when you dismiss it or move
   to another workspace. */
/* An aside in the transcript, not a card in it. A boxed, bordered, accented
   panel read as something to act on; this is a note about what you missed, and
   the things around it are the work. */
.recap { flex: 0 0 auto; display: flex; align-items: baseline; gap: 7px;
  margin: 12px 6px; padding: 0 6px;
  color: var(--fg-faint); font-size: 12.5px; line-height: 1.65; font-style: italic; }
.recap .rclabel { flex: 0 0 auto; font-style: normal; color: var(--accent); opacity: .75;
  font-family: var(--mono); font-size: 11px; }
.recap .rcbody { flex: 1 1 auto; min-width: 0; }

/* ------------------------------------------------------- screenshots */

/* A timeline rather than a gallery: one column, oldest at the top, so it reads
   as a record of the work going by. */
.shots { display: flex; flex-direction: column; gap: 2px; padding: 10px 12px 16px; }
/* A floor, so the column has real height before the lazy images arrive.
   Without it the list is a few hundred pixels tall, the foot of it is already
   in view, and the observer pulls every remaining page at once. */
.shot { position: relative; margin: 0; padding: 0 0 14px 18px; min-height: 190px;
  /* The line runs down the whole column and stops at the last shot. */
  border-left: 1px solid var(--border); cursor: pointer; }
.shot:last-child { border-left-color: transparent; }
/* A dot on the line, level with the time.
   `left` is measured from the padding box, which starts inside the 1px border
   the rail is drawn with — so an offset of half the dot's own width left it
   sitting one pixel to the right of the line it is meant to be on. The dot is
   9px across including its border: -5 puts its centre on the line's centre. */
.shot::before { content: ""; position: absolute; left: -5px; top: 5px;
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--bg-1); border: 1px solid var(--fg-faint); }
.shot:hover::before { border-color: var(--accent); background: var(--accent-fill); }
/* Title first, then when it was taken, then the picture — you read what it is
   before you look at it. */
.shot figcaption { color: var(--fg); font-size: 12.5px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.shot .when { color: var(--fg-faint); font-size: 11px; font-family: var(--mono); margin-top: 1px; }
.shot img { display: block; width: 100%; margin: 7px 0 0; border-radius: 8px;
  border: 1px solid var(--line); background: var(--bg-2); }
.shot:hover img { border-color: var(--accent); }

/* Full size in the modal, capped so a tall page still fits on screen. */
.shotfull { max-height: 70vh; overflow: auto; border-radius: 10px;
  border: 1px solid var(--line); background: var(--bg-1); margin-bottom: 14px; }
.shotfull img { display: block; width: 100%; }

/* The foot of the screenshot list: both the "more is coming" note and the
   target that triggers fetching it. */
.shotmore { padding: 4px 12px 18px 30px; color: var(--fg-faint);
  font-size: 11px; font-family: var(--mono); min-height: 14px; }

/* The alternative to the signed-in preview link, for when you want to see what
   a signed-out visitor sees. */
/* A card is a flex column, so a link in it is full-width unless told otherwise
   — this one covered the card and was clicked by accident, and the 8px it was
   given to sit beside the URL only pushed it out of line underneath. */
.rcplain { align-self: flex-start; max-width: 100%; margin-top: 1px;
  color: var(--fg-faint); font-size: 11px; font-family: var(--mono);
  text-decoration: none; border-bottom: 1px dotted var(--line); }
.rcplain:hover { color: var(--accent); border-bottom-color: var(--accent); }

/* ------------------------------------------------------------- notes */

/* Documents rather than messages, so they get room to breathe and a heading
   scale of their own — the transcript's would be shouting in here. */
/* A list of what is there; one opens at a time. Stacking every body meant
   scrolling through three surveys to reach the fourth. */
.notes { display: flex; flex-direction: column; gap: 2px; padding: 10px 12px; }
.noterow { display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 9px 11px; border: 1px solid transparent; border-radius: 8px;
  background: none; color: var(--fg-dim); cursor: pointer; text-align: left;
  font-family: inherit; font-size: 13px; }
.noterow:hover { background: var(--bg-2); border-color: var(--line); color: var(--fg); }

/* Picking one repository to bring into a workspace. */
.picklist { display: flex; flex-direction: column; gap: 2px; margin: 4px 0 18px; }
.pickrow { display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 10px 12px; border: 1px solid var(--line); border-radius: 8px;
  background: var(--bg-1); color: var(--fg); cursor: pointer; text-align: left;
  font-family: inherit; font-size: 13px; }
.pickrow:hover:not(:disabled) { background: var(--bg-2); border-color: var(--border-2); }
.pickrow:disabled { opacity: .5; cursor: default; }
.avail .pill.forge { flex: 0 0 auto; min-width: 24px; text-align: center;
  color: var(--fg-faint); font-size: 9.5px; letter-spacing: .04em; }

.pickrow.static { cursor: default; background: none; }
.basepick { display: flex; align-items: center; gap: 6px; flex: 0 1 auto; min-width: 0;
  color: var(--fg-faint); font-size: 11px; font-family: var(--mono); }
.pickrow.static:hover { background: none; border-color: var(--line); }
#modal h2.sub2 { margin: 18px 0 6px; font-size: 11px; text-transform: uppercase;
  letter-spacing: .07em; color: var(--fg-faint); font-weight: 500; }
.pickrow .grow { flex: 1 1 auto; }
.pickrow .sub { flex: 0 0 auto; color: var(--fg-faint); font-size: 11px; font-family: var(--mono);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 55%; }
.noterow .nt { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.noterow .grow { flex: 1 1 auto; }
.noterow .meta { flex: 0 0 auto; color: var(--fg-faint); font-size: 11px; font-family: var(--mono); }
/* In the modal it has the width a document wants, and scrolls on its own so
   the Done button stays put.

   The sizing has to be flex, not a max-height: a tall sheet is a flex column,
   and a body that is merely tall competes with the title and the filename line
   for the height — which squashed that line to three pixels and cut it off. */
.notemodal { flex: 1 1 auto; min-height: 0; overflow: auto;
  padding: 0 2px 4px; margin-bottom: 14px; font-size: 13.5px; }
#modal .sheet.tall > h2,
#modal .sheet.tall > .mhead,
#modal .sheet.tall > .hint,
#modal .sheet.tall > .row { flex: 0 0 auto; }
/* Prose fills the width it is given; only a wide table scrolls. */
.notemodal > * { max-width: 100%; }
.note { border: 1px solid var(--line); border-radius: 10px; background: var(--bg-2); }
.note > header { display: flex; align-items: center; gap: 8px; padding: 9px 12px;
  border-bottom: 1px solid var(--line); }
.note > header .nt { color: var(--fg); font-size: 13px; }
.note > header .grow { flex: 1 1 auto; }
.note > header .when { color: var(--fg-faint); font-size: 11px; font-family: var(--mono); }
.nbody { padding: 4px 14px 12px; color: var(--fg-dim); font-size: 13px; line-height: 1.65; }
.nbody h3 { font-size: 14px; margin: 14px 0 6px; color: var(--fg); }
.nbody h4, .nbody h5, .nbody h6 { font-size: 13px; margin: 12px 0 4px; color: var(--fg); }
.nbody p { margin: 8px 0; }
.nbody ul, .nbody ol { margin: 8px 0; padding-left: 20px; }
.nbody li { margin: 3px 0; }
.nbody code { font-family: var(--mono); font-size: 12px;
  background: var(--bg-3); border-radius: 4px; padding: 1px 4px; }
.nbody pre.code { background: var(--bg-1); border: 1px solid var(--line); border-radius: 8px;
  padding: 10px 12px; overflow-x: auto; margin: 10px 0; }
.nbody pre.code code { background: none; padding: 0; font-size: 12px; line-height: 1.55; }
.nbody blockquote { margin: 10px 0; padding: 2px 0 2px 12px;
  border-left: 2px solid var(--border-2); color: var(--fg-faint); }
.nbody hr { border: none; border-top: 1px solid var(--line); margin: 14px 0; }
.nbody a { overflow-wrap: anywhere; }
/* A file path in inline code is one long word, and on a phone one long word is
   wider than the screen. Only in prose: a code *block* and a table both scroll
   instead — rewrapping code changes what it says, and a wrapped table cell came
   out as a column of single letters. */
.nbody :not(pre):not(td):not(th) > code { overflow-wrap: anywhere; }
/* Task lists carry their own box, so the marker would be a second bullet. */
.nbody ul.tasks { list-style: none; padding-left: 2px; }
.nbody ul.tasks .box { display: inline-block; width: 16px; color: var(--fg-faint); }
.nbody ul.tasks li.done { color: var(--fg-faint); }
.nbody ul.tasks li.done .box { color: var(--green); }
/* An agent's HTML note may bring a table; keep it inside the pane. */
.nbody table { border-collapse: collapse; margin: 10px 0; display: block; overflow-x: auto; }
.nbody th, .nbody td { border: 1px solid var(--line); padding: 5px 9px; text-align: left; font-size: 12.5px; }
.nbody th { color: var(--fg); background: var(--bg-3); }
.nbody img { max-width: 100%; border-radius: 6px; }

.notemore { padding: 2px 14px 18px; color: var(--fg-faint);
  font-size: 11px; font-family: var(--mono); min-height: 14px; }

/* ------------------------------------------------------------ helpers */

.helpers { display: flex; flex-direction: column; gap: 10px; padding: 12px; }
.helper { border: 1px solid var(--line); border-radius: 10px; background: var(--bg-2); }
.helper > header { display: flex; align-items: center; gap: 8px; padding: 9px 12px; }
.helper > header .grow { flex: 1 1 auto; }
.helper > header .nm { color: var(--fg); font-size: 13px; }
.helper > header .when { color: var(--fg-faint); font-size: 11px; font-family: var(--mono); }
/* The dot carries the state at a glance; the pill spells it out. */
.helper .hdot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto;
  background: var(--fg-faint); }
/* The same grammar a monitor's dot uses: filled and lit means ongoing, a hollow
   ring means over. A finished helper used to wear a solid green dot and still
   read as live. The hues stay as they were — accent is what "working" means
   everywhere else in this app, green is good, red is bad — it is the weight
   that now carries running against finished. */
.helper.running .hdot { background: var(--accent-fill); --glow: var(--accent);
  box-shadow: 0 0 7px var(--accent); animation: dotpulse 2.4s ease-in-out infinite; }
.helper.done .hdot { background: transparent; border: 1.5px solid var(--green); opacity: .6; }
.helper.failed .hdot { background: var(--red); box-shadow: 0 0 7px var(--red); }
.helper.stopped .hdot { background: transparent; border: 1px solid var(--fg-faint); }
/* `overflow: hidden` clips at the padding box, not the content box, so a
   bottom padding on a line-clamped element is a window the next line shows
   through — the third line of a long task was bleeding into it as a sliver of
   cut-off text. Margin is outside the clip, so the gap stays and the leak
   does not. line-height is pinned because the clamp height derives from it. */
.helper .htask { padding: 0 12px; margin-bottom: 6px; color: var(--fg-dim);
  font-size: 12px; line-height: 1.5;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.helper .hmeta { padding: 0 12px 8px; color: var(--fg-faint); font-size: 11px; font-family: var(--mono); }
.helper .hnote { padding: 0 12px 12px; color: var(--fg-dim); font-size: 12px; font-style: italic; }
/* --border, not --line: --line is used in 21 places in this file and defined in
   none of them, and an undefined custom property invalidates the whole `border`
   shorthand — so this separator has never drawn, leaving the gap it reserves
   with nothing in it. */
/* The disclosure carries the rule now, so the card keeps its line whether the
   result is open or shut. A full-width row rather than a small link: it is the
   main thing you do to a finished helper, and it has to be hittable. */
.helper .hshow { display: flex; align-items: center; gap: 6px; width: 100%;
  min-height: 34px; padding: 8px 12px; margin-top: 2px;
  border: none; border-top: 1px solid var(--border); background: none;
  color: var(--fg-faint); font: 11.5px var(--sans); text-align: left; cursor: pointer; }
.helper .hshow:hover { color: var(--fg-dim); background: var(--bg-2); }
.helper .hshow::after { content: '▾'; margin-left: auto; font-size: 10px; transition: transform .15s; }
.helper .hshow[aria-expanded="true"]::after { transform: rotate(180deg); }
.helper .hbody { padding: 2px 12px 12px; color: var(--fg-dim); font-size: 12.5px; line-height: 1.6; }
.helper .hbody[hidden] { display: none; }
.helper .hbody.err { color: var(--red); font-family: var(--mono); font-size: 12px; }
.helper .hbody pre { background: var(--bg-1); border-radius: 6px; padding: 8px 10px; overflow-x: auto; }

/* On the tab, while they are working somewhere you are not looking. */
#rtabs .tabdot { display: inline-block; width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent-fill); margin-left: 5px; vertical-align: middle; }
#rtabs .tabdot[hidden] { display: none; }
/* Green and lit, like the dot on a run card: this one is a standing state
   rather than something that just happened, so it stays while the server is up
   — including on the Run tab itself, where it agrees with the cards. */
#rtabs .tabdot.up { background: var(--green); box-shadow: 0 0 6px var(--green); }
/* A monitored job in the same pane. It breathes where a dev server sits still:
   a server being up is a state, a job is work in progress, and the workspace
   list already separates the two the same way and at the same rate. */
#rtabs .tabdot.job { animation: pulse 1.4s ease-in-out infinite; }

/* The actions, when they have moved behind one button. */
.actionsheet { display: flex; flex-direction: column; gap: 8px; margin: 6px 0 16px; }
.actionsheet .btn { width: 100%; justify-content: center; padding: 11px 14px; font-size: 14px; }

/* A note on a phone is the whole screen. Last in the file on purpose: these
   have to beat the note rules above, which are not in a media query.

   Reading width is a desktop problem — on a 390px phone the screen is already
   narrower than the measure, so every pixel spent on a frame is a pixel taken
   from the words. The overlay padding, the sheet's own padding and the body's
   were costing 76 of those 390. One 14px gutter is left. */
@media (max-width: 820px) {
  /* `.wide.tall` is the other three of these: a monitor's log, a file, a
     screenshot. Same bargain as a note — they exist to be looked at, the frame
     around them is what stops you looking, and a phone has none to spare. Forms
     are left alone: a dialog that asks a question should still read as one. */
  #modal:has(.sheet.read),
  #modal:has(.sheet.wide.tall) { padding: 0; place-items: stretch; }
  #modal .sheet.read,
  #modal .sheet.wide.tall { max-width: none; width: 100%; height: 100%; max-height: none;
    border: none; border-radius: 0; box-shadow: none;
    padding: calc(12px + env(safe-area-inset-top)) 14px calc(10px + env(safe-area-inset-bottom)); }
  #modal .sheet.read .notemodal { padding: 0; margin-bottom: 0; }
  #modal .sheet.read .nbody { padding-left: 0; padding-right: 0; }
  /* The footer was three paddings deep — its own margin, the body's bottom
     margin and the sheet's. It only holds one button; a rule and a little air
     is enough to separate it from the words. */
  #modal .sheet.read .row { margin-top: 0; padding-top: 13px;
    border-top: 1px solid var(--line); }
  /* auto, not 0: a screenshot does not fill the height, and without this the
     buttons sit wherever the image happens to end with the rest of the screen
     empty beneath them. A log fills it anyway, so this changes nothing there. */
  #modal .sheet.wide.tall .row { margin-top: auto; padding-top: 13px;
    border-top: 1px solid var(--line); }
  #modal .sheet.read .row .btn,
  #modal .sheet.wide.tall .row .btn { padding: 7px 16px; }

  /* The content itself. Each was sized for a desktop sheet — a fixed fraction
     of the viewport, and a border drawn around something already sitting in a
     bordered box. Full bleed, the height is whatever is left, and the second
     border is width bought for nothing. */
  #modal .sheet.wide.tall > .monout,
  #modal .sheet.wide.tall > .shotfull,
  #modal .sheet.wide.tall > .mbody {
    flex: 1 1 auto; min-height: 0; max-height: none;
    margin-left: -14px; margin-right: -14px;
    border-left: none; border-right: none; border-radius: 0; }
  #modal .sheet.wide.tall > .monout { margin-top: 4px; margin-bottom: 0; padding: 10px 14px; }
  /* A log and a file want every pixel of height; a screenshot does not. Letting
     it grow like the others put a landscape image in the middle of a screen of
     black. It takes the width and keeps its own height. */
  #modal .sheet.wide.tall > .shotfull { flex: 0 1 auto; }
  #modal .sheet.wide.tall > .shotfull img { width: 100%; height: auto; }
}

/* Reading width on a phone.
 *
 * The transcript kept a desktop's margins: 12px on the pane, 6 more on the row,
 * then an 8px gutter and a 10px gap that exist to line assistant text up with
 * the avatars beside a person's messages. On a 390px screen that is 36px of
 * left inset, half of it holding a column that has nothing in it.
 *
 * A person's messages keep their gutter, because the face is in it. Everything
 * else starts where the pane does.
 */
@media (max-width: 820px) {
  #tape { padding: 14px 10px 6px; }
  .ev { padding: 5px 4px; gap: 8px; }
  .ev.assistant, .ev.thinking, .ev.note, .evnote { gap: 0; }
  .ev.assistant .gut, .ev.thinking .gut { width: 0; }
  /* Bordered, so it needs a little room inside the border to breathe. */
  .ev.user { padding: 7px 9px; }
  .recap { margin: 12px 2px; padding: 0 2px; }

  /* A table gets the width it is given and no more, so its first column has to
     be defended. A cell holding something unbreakable — a path, a branch name,
     RightPanelReservationPreorder.vue:66 — sets a min-content width the auto
     layout has to honour, and it takes the room from whichever column can give:
     the first, which collapsed to two characters a line.

     So long tokens may break, and the first column has a floor. */
  .ev .md table { table-layout: auto; }
  .ev .md th, .ev .md td { overflow-wrap: anywhere; word-break: break-word; }
  .ev .md th:first-child, .ev .md td:first-child { min-width: 5.5em; }
  .ev .md table code { overflow-wrap: anywhere; }
}


/* On a phone there is no room for a path on one line and no hover to reveal it,
   so the name takes the first line and the directory sits under it. The row
   actions are always visible for the same reason: they were waiting for a
   hover that a finger never sends. */
@media (max-width: 820px) {
  .dfile { align-items: flex-start; padding: 8px 10px; }
  .dfile .nm { flex-direction: column; align-items: flex-start; gap: 1px; }
  .dfile .nm .fn { overflow-wrap: anywhere; }
  /* Keeps the rtl truncation the wide layout uses. Forcing ltr here cut the
     directory from the right, which throws away the trailing slash and the
     folder the file is actually in — leaving "src/components/clinic/a…" above a
     filename with nothing joining them. From the left you keep both:
     "…/consultation/antenatal/". */
  .dfile .nm .dir { max-width: 100%; font-size: 10.5px; }
  .dfile .add, .dfile .del { align-self: center; }
  /* A 22-by-20 target drawn for a mouse. A finger needs somewhere to land, and
     the glyph has to be readable rather than merely present. */
  .dfile .dact { opacity: 1; align-self: center; width: 42px; height: 42px; font-size: 20px;
    font-size: 17px; line-height: 1; border-radius: 9px;
    background: var(--bg-2); border: 1px solid var(--line); color: var(--fg-dim); }
  .dfile .dact:active { background: var(--bg-3); color: var(--accent); }
  /* The two of them now take real width, so the numbers keep out of their way. */
  .dfile .add, .dfile .del { font-size: 11px; }
}


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

   A feed, not a table. The columns a file manager shows — size, type — were
   noise here: nobody chooses an attachment by its byte count, and what kind a
   thing is was already said by its thumbnail and by its own extension. The
   width they were taking goes to the names instead, which is the part that is
   read. Time became the order rather than a column. */
.atbar { display: flex; align-items: center; gap: 8px; flex: 0 0 auto;
  padding: 8px 10px; border-bottom: 1px solid var(--border); }
.atbar input { flex: 1 1 auto; min-width: 0; margin: 0; padding: 5px 9px; font-size: 12px; }
.atcount { color: var(--fg-faint); font: 11px var(--mono); flex: 0 0 auto; }
#attachList { flex: 1 1 auto; overflow: auto; min-height: 0; }
.alist { padding: 4px 6px 10px; }

/* Quiet, and sticky so you always know which day you are looking at after
   scrolling into the middle of one. */
.aday { position: sticky; top: 0; z-index: 2; padding: 9px 6px 4px;
  background: var(--bg); color: var(--fg-faint);
  font: 10px var(--mono); text-transform: uppercase; letter-spacing: .06em; }

.arow { display: grid; grid-template-columns: 22px minmax(0, 1fr) 26px;
  align-items: center; gap: 10px; width: 100%; padding: 7px 6px;
  background: none; border: none; border-radius: 8px; cursor: pointer;
  text-align: left; color: inherit; }
/* A picture needs to be big enough to recognise, which is the whole reason it
   is there; a glyph does not. */
.arow.pic { grid-template-columns: 54px minmax(0, 1fr) 26px; }
.arow:hover { background: var(--bg-2); }
.arow:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

.arow .ic { display: flex; align-items: center; justify-content: center;
  color: var(--fg-faint); }
.arow .ic svg { width: 16px; height: 16px; display: block; }
.arow .ic .th { width: 54px; height: 38px; object-fit: cover; border-radius: 4px;
  display: block; background: var(--bg-3); box-shadow: 0 0 0 1px var(--hair) inset; }

.arow .txt { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.arow .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 12.5px; color: var(--fg); }
.arow .sub { overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font: 10.5px var(--mono); color: var(--fg-faint); }

/* Getting the file should not mean opening it first. Out of the way until the
   row is under the pointer — and always there on a touch screen, which has no
   hover to reveal it with. */
.arow .dl { display: flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 6px; color: var(--fg-faint);
  opacity: 0; transition: opacity .12s; }
.arow .dl svg { width: 14px; height: 14px; display: block; }
.arow:hover .dl, .arow .dl:focus-visible { opacity: 1; }
.arow .dl:hover { background: var(--bg-3); color: var(--fg); }
@media (hover: none) { .arow .dl { opacity: 1; } }

/* ------------------------------------------------- helpers, inside Run */
/* A labelled band rather than a heading: Run is a stack of cards and this is
   one more group of them, not a new page. */
.runsec { display: flex; align-items: center; gap: 8px; padding: 10px 12px 6px;
  color: var(--fg-faint); font: 11px var(--mono); text-transform: uppercase;
  letter-spacing: .04em; }
.runsec .n { color: var(--fg-dim); text-transform: none; letter-spacing: 0; }
#runHelpers[hidden] { display: none; }
#runHelpers { flex: 0 0 auto; }

/* ------------------------------------------------- files and terminal

   Its own window, over everything. The same furniture as the board sheet, so
   the two full-screen things in this app look like the same kind of thing. */
#shellwin { position: fixed; inset: 0; z-index: 150; background: var(--bg); display: flex; }
#shellwin[hidden] { display: none; }
.shellwin-inner { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column;
  padding: 14px 16px 12px; }
.shellwin-inner .shead { display: flex; align-items: center; gap: 12px; flex: 0 0 auto;
  margin-bottom: 10px; }
.shellwin-inner .sub { color: var(--fg-faint); font: 11.5px var(--mono);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.shellwin-inner .grow { flex: 1 1 auto; }
/* The body is what the panes size against: without a bounded, scroll-free
   parent the terminal measures itself against the document and grows. */
.shellwin-body { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column;
  border: 1px solid var(--border); border-radius: 10px; overflow: hidden;
  background: var(--bg-1); }

#shelltabs { display: flex; gap: 2px; flex: 0 0 auto; }
#shelltabs button { padding: 5px 13px; border-radius: 7px; background: none; border: none;
  color: var(--fg-dim); font-size: 12.5px; cursor: pointer; }
#shelltabs button:hover { background: var(--bg-2); color: var(--fg); }
#shelltabs button.active { background: var(--bg-3); color: var(--fg);
  box-shadow: inset 0 0 0 1px var(--border-2); }

@media (max-width: 820px) {
  .shellwin-inner {
    padding: calc(10px + env(safe-area-inset-top, 0px)) 10px calc(10px + env(safe-area-inset-bottom, 0px));
    padding-left: calc(10px + env(safe-area-inset-left, 0px));
    padding-right: calc(10px + env(safe-area-inset-right, 0px)); }
  /* The workspace name is the first thing to go: the tabs and the way out
     both have to fit on one line at 390px. */
  .shellwin-inner .sub { display: none; }
}

/* ---- the attachment viewer: the list stays beside what it opened ---- */
.viewer { display: flex; gap: 0; height: 100%; min-height: 0; }
.vlist { flex: 0 0 auto; width: 240px; min-width: 0; overflow-y: auto;
  border-right: 1px solid var(--border); padding: 4px; }
.vpane { flex: 1 1 auto; min-width: 0; min-height: 0; display: flex; flex-direction: column;
  padding: 0 0 0 14px; }
/* The heading and the buttons hold still; only the middle scrolls, or a long
   note takes the row of buttons off the bottom of the screen. */
.vpane .vhd { flex: 0 0 auto; }
.vpane .vhd h2 { margin: 0 0 2px; }
.vpane .vmain { flex: 1 1 auto; min-height: 0; overflow: auto; margin: 8px 0 10px; }
.vpane .row { flex: 0 0 auto; }

.vrow { display: grid; grid-template-columns: 24px minmax(0, 1fr) auto; align-items: center;
  gap: 7px; width: 100%; padding: 6px 8px; border: none; border-radius: 7px;
  background: none; color: var(--fg-dim); cursor: pointer; text-align: left; }
.vrow:hover { background: var(--bg-2); color: var(--fg); }
.vrow.on { background: var(--bg-3); color: var(--fg); box-shadow: inset 0 0 0 1px var(--border-2); }
.vrow .ic { width: 14px; height: 14px; color: var(--fg-faint); }
.vrow .ic svg { width: 14px; height: 14px; display: block; }
.vrow .ic.img { color: var(--accent); }
.vrow .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; }
.vrow .at { color: var(--fg-faint); font: 10px var(--mono); }

/* On a phone there is no room for both: the list goes, and the row you tapped
   is the one you wanted. Closing and opening another is two taps there anyway. */
@media (max-width: 820px) {
  .vlist { display: none; }
  .vpane { padding-left: 0; }
}

/* The viewer's own list keeps a small thumbnail: narrow, but enough to tell
   two screenshots apart while reading a third. */
.vrow .ic.img { width: auto; height: auto; }
.vrow .ic .th { width: 22px; height: 16px; object-fit: cover; border-radius: 2px;
  display: block; background: var(--bg-3); }

/* ---- is it running, or is it ready? ----
   Three states, because they mean different things to whoever is looking: the
   process exists, the port answers, or it never will. A dev server that has
   been told to start but has not bound yet used to read as "running", which
   sent people to a link that refused the connection. */
.runcard .rcurl .rcdot { display: inline-block; width: 7px; height: 7px; border-radius: 50%;
  margin-right: 6px; background: var(--fg-faint); vertical-align: 1px; }
.runcard .rcurl.ready .rcdot { background: var(--green); }
.runcard .rcurl.starting { color: var(--accent); }
.runcard .rcurl.starting .rcdot { background: var(--accent-fill); animation: rcpulse 1.1s ease-in-out infinite; }
.runcard .rcurl.stalled { color: var(--red); }
.runcard .rcurl.stalled .rcdot { background: var(--red); }
.runcard .rcurl.off .rcdot { background: var(--border-2); }
@keyframes rcpulse { 0%, 100% { opacity: 1; } 50% { opacity: .25; } }
/* The dot on the Run tab follows the same idea: lit once something answers,
   pulsing while it is still coming up. */
#runDot.waking { animation: rcpulse 1.1s ease-in-out infinite; }
