/* --- Sidebar --- */
#sidebar {
  width: 260px;
  min-width: 260px;
  background: var(--bg-surface);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* Filter box sits above the scrollable list, so it stays put while the list
   scrolls. flex-shrink:0 keeps it from collapsing when the list is long. */
#session-filter-wrap {
  flex-shrink: 0;
  padding: 8px 8px 0;
}
/* The dashboard's filter box is the same control asking the same question, so
   it is the same rule rather than a second copy of it. Only where they sit
   differs, and that is the wrapper's business. */
/* The New Session dialog's name box. Same dressing as the two filter boxes —
   one look for "a place to type" — but it sits in an .agent-picker row, so the
   width comes from the row rather than from 100%. */
#session-name {
  flex: 1;
  min-width: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 6px 10px;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 13px;
  outline: none;
  transition: border-color 0.15s;
}
#session-name:focus { border-color: var(--accent); }
#session-name::placeholder { color: var(--text-muted); }

#session-filter, #dashboard-filter {
  width: 100%;
  box-sizing: border-box;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 6px 10px;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 13px;
  outline: none;
  transition: border-color 0.15s;
}
#session-filter:focus, #dashboard-filter:focus { border-color: var(--accent); }
#session-filter::placeholder, #dashboard-filter::placeholder { color: var(--text-muted); }
/* #dashboard already pads its own edges, so this only has to part the box from
   the cards under it. */
#dashboard-filter-wrap { padding-bottom: 10px; }
/* One question, one box. The sidebar's filter and this one drive the same
   state — typing in either fills both — so on a desktop, where the sidebar is
   always there, the dashboard's own box was the same question asked twice, in
   the same words, forty pixels apart. The sidebar's is the one that survives:
   it is on screen in every view, not only this one.
   Phones keep this one and only this one: there is no sidebar there. */
@media (min-width: 769px) {
  #dashboard-filter-wrap { display: none; }
}
/* Whatever the dashboard has to say when it has no cards to show. */
.dash-empty {
  color: var(--text-muted);
  text-align: center;
  padding: 40px;
}

#session-list {
  flex: 1;
  overflow-y: auto;
  padding: 8px;
}

/* Shown in place of the list when a filter matches nothing. */
.session-filter-empty {
  padding: 16px 10px;
  color: var(--text-muted);
  font-size: 13px;
  text-align: center;
}

/* 8px of padding around a 20px line — a 36px row, and the tallest thing in it is
   the name rather than the ⋯. That was not true a moment ago: the toggle
   inherited line-height 1.5 on 16px type and stood 28px, so it, not the text,
   had been setting the height. Dropping the second line saved 6.5px of the 14 it
   should have, and the button ate the rest.
   A row is also the tap target that opens the session, so a coarse pointer gets
   the padding back and a 44px row (see below). A mouse doesn't need it and a
   list is easier to scan when more of it fits. */
.session-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  border-radius: var(--radius);
  cursor: pointer;
  margin-bottom: 2px;
  transition: background 0.15s;
  position: relative;
}
@media (pointer: coarse) {
  .session-item { padding: 12px; }
  .session-item.subagent-item { padding: 10px; }
}
.session-item:active { background: var(--bg-hover); }
@media (hover: hover) {
  .session-item:hover { background: var(--bg-hover); }
}
/* Where you ARE, and it has to survive eight themes. The old mark was
   --bg-surface2, which measured 1.11-1.51:1 against the sidebar — and in five
   of the eight that is LESS than plain hover (1.27-1.80), so the row you were in
   was quieter than the row your pointer happened to be over.
   Two marks now, both in the accent, because the accent is the one colour every
   theme guarantees against its own surface. A band for bulk (1.37-2.09:1, still
   modest on its own — a tint can only ever be a tint) and a ring around the row,
   which is the part that does not depend on the theme's luminance at all.
   A RING, and the row has no other edge mark: unread is not said on the row at
   all, only by the size of its dot (see THE UNREAD RULE below).
   Not tried again: the name in the accent. Measured on the band it comes out at
   2.19-4.80:1 where the plain name is 2.65-11.10, and in the two solarized
   themes accent-vs-text is 1.16-1.21 — it would cost legibility everywhere to
   say nothing in two of the eight. */
.session-item.active {
  background: color-mix(in srgb, var(--accent) 30%, var(--bg-surface));
  box-shadow: inset 0 0 0 1px var(--accent);
}
/* Unread is not a mark on the row. It used to be three — an accent rail on the
   left, a bold name, and the dot at full size — and before those a wash, which
   went when the ACTIVE row took the fill: two washes in the same hue family are
   one mark at two strengths (measured over the row, unread 1.09-1.22:1 against a
   plain row and active 1.37-2.09, in solarized-dark the same colour twice). The
   rail and the bold went too, so there is one vocabulary for it everywhere a
   session is listed: the dot's SIZE (see THE UNREAD RULE below). With the rail
   gone an unread row no longer starts its content 3px right of its neighbours. */

.session-item .dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
}
/* Four hues, and they say what the session is doing — the SIZE, below, says
   whether it has anything for you:
     green           idle, and it will not move until you write to it
     blue            idle, but a wake-up or a job will start its next turn
     orange, moving  the agent is working
     red             the turn failed
   The ring for idle is gone. It carried "not working" by SHAPE, which was the
   right answer to the problem of the day: green-idle against blue-running
   measured 1.05-1.32:1 with an L* gap of 1.5-8.9 — different hues at identical
   brightness, which on an 8px circle glanced at rather than looked at is the
   weakest encoding there is. That problem left with blue.
   Blue is back for a different question, and the weak pair came back with it:
   the same two tokens, measured again over the eight themes, 1.01-1.32:1 and
   ΔL* 0.3-8.9 — dracula 1.01, dark 1.05, monokai 1.06, light 1.07. What changed
   is the price of a misread. Blue-running beside green-idle confused "working"
   with "done", which is what the list is for; this confuses two kinds of done.
   The dear direction is green read as blue — waiting on a session that is
   waiting on you — and the session's own wake-up chips and job cards say which.
   The owner asked for it so the list tells "waiting for me" from "waiting for
   something automatic" at a glance; where the hues collapse, it takes a look.
   Unread is the dot's alone: nothing else on the row or the card says it.
   JOB dots are a different family and did NOT move: their vocabulary is an
   outcome (running / ok / failed / stopped) where green already means
   "succeeded". Two families, no collision inside either. */
/* THE UNREAD RULE, for every session dot in the app: colour says what the
   session is doing, SIZE says whether it has anything for you — 5px read, 8px
   unread, in every state. It sits on the base class so idle, waiting, running
   and error all inherit it; the dashboard card (.dash-card .dash-dot) and the
   subagent dots (.sub-dot) are written the same way. Size rather than colour
   because green-vs-grey collapsed in two themes — dark at 1.05:1 and light at
   1.08:1, hue at identical brightness — while 5px against 8px is the same
   difference in every theme, on every screen, for every eye. */
.session-item .dot {
  /* Scaled, not resized. The box stays 8px so the name beside it starts in the
     same place on every row — measured with real widths, the name moved 33/36/39
     px along depending on the dot, which is a ragged left edge down the whole
     list. A transform paints smaller and takes the same space. */
  transform: scale(0.625);
}
/* Something to read: the same dot at full size. One class deeper, so it beats
   the rule above.
   THE SUBAGENT EXCEPTION — "Unread on subagents" in Settings → Appearance
   (subUnreadDots, state.js), OFF by default: a subagent's dot never takes the
   unread size, so a lead's open list is a column of small dots and the sessions
   you hold yourself are the only ones that grow. A fan-out is nearly always
   holding a report nobody has taken — that is what it is for — so on those rows
   the size that means "this wants you" fired constantly and stopped saying
   anything. The exception is the DRAWING and nothing else: the row still carries
   `unread`, the dot still carries the count in its title, and the relay still
   counts it. Written as a <body> class rather than as a second class on the row
   so a toggle re-measures the dots already on screen and no row is rebuilt for
   it — and every other subagent dot in the app is written the same way (the
   lead's cluster, .sub-dot in 01-base.css; the roster card's and the phone
   header's in 04-dashboard.css). */
.session-item.unread:not(.subagent-item) .dot,
body.sub-unread-dots .session-item.subagent-item.unread .dot {
  transform: none;
}
.session-item .dot.idle { background: var(--green); }
/* Idle, and due to move on its own — a wake-up or a job (dotState). Colour
   only; the size rules above still say whether it has anything to read. */
.session-item .dot.idle.waiting { background: var(--blue); }
/* Not BLINKING — see dot-breathe below, which drifts the hue at full opacity
   instead. Movement is the loudest signal there is and an agent that is working
   does not need answering, so it never fades out the way a blinking alarm would,
   and the elapsed time is still what says how long. (A hung agent looks exactly like a busy one until you read a clock:
   the working bar's timer inside the session, or the elapsed time on the
   dashboard card. The sidebar row no longer carries its own — a filled dot per
   row is what this list is for, and a column of counters was the noise around
   it.) */
.session-item .dot.running { background: var(--dot-run); animation: dot-breathe 1s ease-in-out infinite; }
.session-item .dot.error { background: var(--red); }

/* Orange drifting to amber and back, and deliberately not an opacity blink: a
   blink fades toward nothing because it wants answering, and the note above is
   right that a working agent doesn't. This changes hue at
   full opacity instead — the mark never weakens and never disappears between
   beats, which is the whole difference from a blink.
   1s a cycle, written 0/50/100 like `pulse` so the two periods can be compared
   at a glance. That is the period the dot had before 982b031 removed the blink
   (`pulse 1s infinite`), and it is deliberately back: a slower drift read as
   nothing happening. It carries no information the static colour didn't; what it
   adds is that a wall of rows shows life at the edge of vision without anything
   having to be looked at.
   Both ends are theme tokens now. The far end used to be `var(--yellow, #ffc107)`
   -- one amber for all eight themes, on the reasoning that no theme declares a
   --yellow. What that missed is that the DISTANCE to it was never the same twice:
   0.065 in OKLab from the dark theme's orange, which is what this was tuned to,
   but 0.102 from monokai's, 0.152 from gruvbox's and 0.295 from solarized's. Past
   about 0.08 the drift stops reading as one colour breathing and starts reading
   as a dot that keeps turning yellow -- the thing that made the mark look washed
   out. --dot-run-peak is that end chosen per theme instead, always a hue-neighbour
   of that theme's own orange at the same chroma, all eight within 0.051-0.072. */
@keyframes dot-breathe {
  0%, 100% { background: var(--dot-run); }
  50%      { background: var(--dot-run-peak); }
}
/* Motion is a preference before it is a design. The dots keep their orange —
   the state was never carried by the animation, so nothing is lost by stopping
   it. */
@media (prefers-reduced-motion: reduce) {
  .session-item .dot.running,
  .dash-card .dash-dot.running,
  .svc-card .dash-dot.running,
  #header-dot .dash-dot.running { animation: none; }
}

.session-item .info {
  flex: 1;
  min-width: 0;
}
.session-item .info .name-row {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}
.session-item .info .name {
  font-size: 13px;
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  /* Shrink-to-fit, not stretch. Stretching put the subagent block over by the
     date instead of beside the name it belongs to; the date is held at the right
     by its own `margin-left: auto` either way. `1` in the shrink slot with
     min-width 0 keeps a long name ellipsising exactly as before. */
  flex: 0 1 auto;
  min-width: 0;
}
/* The second line, holding the date alone. It used to hold the path too, but a
   column this narrow cannot give width to both a name and a folder, so the path
   arrived pre-truncated and stayed that way — and the session you are in prints
   its cwd in full in the status bar anyway.
   The date stays downstairs rather than moving up beside the name: it is the
   coldest thing in the row, and putting it on the line the name and the badges
   share would have it competing for width with them every time a name grew. */
/* Pinned to the right of the one-line row: the name takes what it needs, the
   date takes its four characters at the edge, and nothing between them moves as
   a name changes length. */
.session-item .info .ts {
  flex-shrink: 0;
  margin-left: auto;
  padding-left: 6px;
  transition: opacity 0.15s;
}
/* How long something has been running. Session rows and dashboard cards used to
   carry it for a TURN and no longer do; what is left wearing this class is the
   job card, where the number is the job's own age. The working bar's timer to
   the pixel — same size, same muting, same mono — because it is the same reading
   in a narrower place. `tabular-nums` so the row does not twitch as the digits
   change under it, which would put back the movement this whole thing removed.
   Never wraps and never shrinks: it is four characters, and a name that needs
   the room can ellipsis instead.
   `:empty` for a job with no times to print — the span is in the markup either
   way, so it has to take no room when there is nothing in it. */
.run-time {
  font-size: 10px;
  color: var(--text-muted);
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  opacity: 0.6;
  flex-shrink: 0;
  white-space: nowrap;
}
.run-time:empty { display: none; }
/* The quote-selection button. Fixed, because it is placed from a Range's
   viewport rect. Sized for a fingertip where the pointer IS one — a 32px target
   is comfortable with a mouse and a miss on a phone. */
/* One floating bar over a selection, holding what you can do with it. The
   quote button was here alone and did the positioning itself; the box moved out
   to the bar so a second action costs a button rather than a second thing to
   place. */
.selection-actions {
  position: fixed;
  z-index: 300;
  display: flex;
  gap: 4px;
}
.selection-actions.hidden { display: none; }
.selection-actions button {
  width: 32px;
  height: 32px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 16px;
  line-height: 1;
  background: var(--bg-surface);
  color: var(--text);
  border: 1px solid var(--accent);
  border-radius: 6px;
  box-shadow: 0 2px 10px rgba(0,0,0,0.45);
  cursor: pointer;
}
.selection-actions button:hover { background: var(--accent); color: var(--bg); }
/* Confirmed, and it must not read as pressed-and-waiting: green fill, and the
   hover rule above deliberately does not fight it — the pointer is still on the
   button when the tick appears. */
.copy-sel-btn.copied,
.copy-sel-btn.copied:hover { background: var(--green); color: var(--bg); border-color: var(--green); }
@media (pointer: coarse) {
  .selection-actions button { width: 44px; height: 44px; font-size: 20px; }
}

/* --- Session "..." menu --- */
/* line-height 1, not the inherited 1.5: three dots have no descenders and no
   second line, and the 8px this was spending on leading was setting the height
   of every row in the sidebar. 20px now, which is the name's own line box — so
   the row is as tall as its text and nothing else. */
.menu-toggle {
  background: none;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  font-size: 16px;
  line-height: 1;
  padding: 2px 6px;
  border-radius: 4px;
  flex-shrink: 0;
  position: relative;
  opacity: 0;
  transition: opacity 0.15s;
}
/* A fingertip's target without a fingertip's button: the box stays 20px, an
   invisible overlay does the catching. Same trick the lead badge used before it
   left the row — the pattern outlived the element it was written for. */
@media (pointer: coarse) {
  .menu-toggle::before {
    content: "";
    position: absolute;
    inset: -12px -8px;
  }
}
.session-item.active .menu-toggle { opacity: 1; }
@media (hover: hover) {
  .session-item:hover .menu-toggle { opacity: 1; }
  .menu-toggle:hover { background: var(--bg-hover); color: var(--text); }

  /* Out of flow where it is invisible most of the time. In flow it reserved
     ~36px on every row — 16px of glyph, 12 of padding, the row's 8px gap —
     which is 15% of the 236px a 260px sidebar leaves for content, held open for
     an element that only appears on hover. Absolute costs nothing when hidden
     and reflows nothing when it arrives: the name never shifts under the
     pointer.
     It lands on the date, and that is the deliberate half of the trade. The two
     are never wanted at once — a date is read while scanning the list, the ⋯ is
     reached for after stopping on one row — so the date leaves as the button
     comes. (A dashboard card reserves its corner instead rather than overlaying:
     the comment there says why, and it is that git counts sit at that end. A
     date is spendable; those are not.)
     Only under `hover: hover`. A touch sidebar is the full width of the screen,
     where 36px is not a scarcity worth solving, and there is no hover to bring
     the button back with. */
  .session-item .menu-toggle {
    position: absolute;
    right: 6px;
    top: 50%;
    transform: translateY(-50%);
  }
  .session-item:hover .ts,
  .session-item.active .ts,
  .session-item.menu-open .ts { opacity: 0; }
  /* The menu can outlive the pointer leaving the row. While it is open the
     button stays, so the date can't come back out from under an open menu. */
  .session-item.menu-open .menu-toggle { opacity: 1; }
}
@media (hover: none) {
  .menu-toggle { opacity: 0.7; }
}
.session-item.menu-open {
  z-index: 101;
}
.session-menu {
  position: absolute;
  right: 8px;
  top: 100%;
  z-index: 100;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: 6px;
  box-shadow: 0 4px 12px rgba(0,0,0,0.3);
  padding: 4px 0;
  min-width: 140px;
}
.session-menu button {
  display: block;
  width: 100%;
  background: none;
  border: none;
  color: var(--text);
  cursor: pointer;
  font-size: 12px;
  padding: 6px 12px;
  text-align: left;
  white-space: nowrap;
}
@media (hover: hover) {
  .session-menu button:hover { background: var(--bg-hover); }
}
.session-menu .delete-btn { color: var(--red); }

/* --- Archive toggle --- */
.archive-toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  margin-top: 8px;
  font-size: 12px;
  color: var(--text-muted);
  cursor: pointer;
  user-select: none;
  border-top: 1px solid var(--border);
}
@media (hover: hover) {
  .archive-toggle:hover { color: var(--text); }
}
.archive-toggle .arrow {
  font-size: 10px;
  transition: transform 0.15s;
}
.archive-toggle .arrow.open { transform: rotate(90deg); }

.session-item.archived { opacity: 0.6; }
@media (hover: hover) {
  .session-item.archived:hover { opacity: 0.85; }
}

/* `tabular-nums`, not the mono face. The string is "14:32", "6 Sep 14:32" or
   "6 Sep 2025 14:32" — mostly digits, and in a proportional font a 1 is
   narrower than an 8, so no two rows put their colon in the same place and a
   column of times reads as ragged. Figures of one width fixes that; switching
   the whole span to var(--mono) would fix it too and restyle "Sep" to pay for
   it. .run-time next door made the same call and says so. */
.session-item .ts {
  color: var(--text-muted);
  font-size: 10px;
  font-variant-numeric: tabular-nums;
}

/* --- Subagents (nested under their lead) --- */
/* Indented container holding a lead's subagent rows, with a subtle accent
   rail so they read as children of the row directly above. */
.subagent-children {
  margin-left: 14px;
  padding-left: 6px;
  border-left: 2px solid var(--border);
}
/* Compact child row — slightly smaller/dimmer than a normal session item. */
.session-item.subagent-item {
  padding: 6px 10px;
}
.session-item.subagent-item .info .name {
  font-size: 12px;
}
.session-item.subagent-item .sub-nick {
  color: var(--accent);
}
/* The subagent count badge (`@3`) that used to sit here is gone: leadDots draws
   one dot per subagent instead, which says the same number and what each of them
   is doing. Its rules went with it — including the corner dot for unread, which
   is now the size of the dot belonging to the subagent that has the news. */
/* The lead up there is working. The orange the running dot uses — which is --dot-run
   and not --orange, so that this and the dots it stands in for cannot drift
   apart — and the same word the subagent dots say it in; see .sub-dot.running,
   which reports the identical fact on a dashboard card and in the sidebar. It
   takes the steady end only: the drift is a property of a dot in a list of
   them, and one glyph in a bar has nothing to keep pace with. */
#mobile-bar-lead.running { color: var(--dot-run); }

/* The sheet the subagents mark opens: a popover over the composer row, placed by
   openBarPopover. Full width bar a margin, because it holds real rows rather
   than a short list of labels. (It hung over the bottom BAR while the mark that
   opened it lived there; the mark moved a row up and the sheet followed it, by
   the same placement rule and with no change to this.)

   `overflow: visible` on purpose. Each row's ⋯ menu is absolutely positioned
   against the row, so a scrolling box would clip the menu of whichever row sat
   at its edge. The list inside scrolls instead, and the menus draw over the bar
   when they have to. */
.subagent-sheet {
  position: fixed;
  z-index: 60;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 8px 28px rgba(0,0,0,0.35);
  padding: 4px;
  overflow: visible;
}
.subagent-sheet-list {
  max-height: 50vh;
  overflow-y: auto;
}
/* The last row's ⋯ opens UPWARD. A menu is absolutely positioned under its row,
   and the list above scrolls — which means it clips, so the bottom row's menu
   was drawn outside the box and the bar took the taps meant for it. Upward it
   stays inside the list, where it can be seen and hit. */
.subagent-sheet .session-item:last-child .session-menu {
  top: auto;
  bottom: 100%;
}

/* Brief highlight when a subagent reports back (subagent.result/failed).
   The flash is the theme's accent, not a copy of one theme's: at 35% the colour
   is plainly visible, and frozen as rgba(78,168,222,…) it flashed the DEFAULT
   theme's blue over dracula's purple accent and gruvbox's sage one. Measured
   against the frozen value, the painted tint moves 14-39/255 per channel — a
   real correction, unlike the 8-10% washes elsewhere in this file, which move
   6-11 and are left alone for that reason (see .queue-chip-remove:hover). */
.session-item.subagent-item.just-reported {
  animation: subagent-flash 2s ease-out;
}
@keyframes subagent-flash {
  0% { background: color-mix(in srgb, var(--accent) 35%, transparent); }
  100% { background: transparent; }
}

/* Text on an accent fill is `var(--bg)`, never a literal. The page background is
   the one colour in a theme guaranteed to sit at the far end from its accent, so
   this self-tunes: near-black on the light blue monokai calls an accent, white
   where the accent is dark. `color: white` was 1.65:1 there against a 4.5
   minimum — the same shape of failure in nord, gruvbox, dracula and the default.
   The idiom was already here (#selection-actions button:hover,
   .preset-event-status); these
   buttons had simply missed it.

   It does NOT extend to the RED fills (#abort-btn, .danger, .bg-kill-btn:hover),
   and they deliberately still say white: the reds sit at
   mid luminance, so neither end of the scale clears 4.5 on them, and var(--bg)
   is the WORSE of the two in nord and both solarized themes. That is a palette
   question, not a button one. */
#new-session-btn {
  padding: 10px;
  background: var(--accent);
  color: var(--bg);
  border: none;
  border-radius: var(--radius);
  cursor: pointer;
  font-size: 14px;
  font-weight: 500;
  transition: background 0.15s;
}
#new-session-btn:hover { background: var(--accent-hover); }

