/* --- Reset & Variables --- */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

:root {
  --bg: #1a1a2e;
  --bg-surface: #16213e;
  --bg-surface2: #0f3460;
  --bg-hover: #1a3a6a;
  --text: #e0e0e0;
  --text-muted: #8899aa;
  /* Max-contrast direction for the "bright" agent-text option: white on dark
     themes, near-black on light ones (overridden in the light theme blocks). */
  --text-strong: #ffffff;
  --accent: #4ea8de;
  --accent-hover: #5bb8ee;
  --green: #4caf50;
  --blue: #42a5f5;
  --red: #ef5350;
  --orange: #ffa726;
  /* The mark a working turn wears, kept apart from --orange because --orange has
     thirty other jobs (timers, warn chips, the auto mode) and this one is an 8px
     disc. A small patch of colour reads paler than the same colour spread over a
     chip, so the dot gets the deepest member of the theme's orange family rather
     than the one the text uses. Two values, because the dot drifts: see
     dot-breathe. --dot-run-peak is the far end of that drift, picked per theme so
     the swing is the same everywhere -- 0.065-0.072 in OKLab, which is what the
     effect was tuned to here and what every theme used to MISS: the peak was one
     borrowed amber (#ffc107) for all eight, so solarized and light swung 0.30 and
     spent half of every second at a hue 45 degrees off their own orange. That is
     the wash. Peaks are hue-neighbours of each theme's orange at the same chroma,
     never a paler yellow.
     Here the base is darkorange -- Material orange 600 against the 400 the text
     orange uses. Chroma 0.179 against 0.164, and 6.81 against the surface it sits on. */
  --dot-run: #ff8c00;
  --dot-run-peak: #ffab00;
  --border: #2a2a4a;
  --radius: 8px;
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --mono: "SF Mono", "Fira Code", "Cascadia Code", Consolas, monospace;
}

[data-theme="light"] {
  --bg: #f5f5f5;
  --bg-surface: #ffffff;
  --bg-surface2: #e8eef4;
  --bg-hover: #dde5ed;
  --text: #1a1a2e;
  --text-muted: #666;
  --text-strong: #000000;
  /* The only theme that has to state these, and the only one that was not.
     Inheriting a DARK theme's accent put #4ea8de on a #f5f5f5 page, where it is
     wrong in all three jobs the accent does at once: 2.41 under the near-white
     text that sits on a filled button, 2.63 as text itself, 2.63 as a focus ring
     — against 4.5 for text and 3 for a line. Every surface in this theme is
     light, so all three want the same thing and darkening fixes all three; the
     other six themes are dark and need the opposite, which is exactly why one
     inherited value cannot serve both.
     #1f6fa8 is about as light as this family gets while still clearing 4.5 on
     the filled button (4.94; the next shade up, #2578b5, is 4.35) — darkened by
     what the strictest job needs and no further, so the theme keeps its blue.
     The hover deepens rather than lightens, the reverse of the dark themes and
     for the same reason: on this page "more" is darker. */
  --accent: #1f6fa8;
  --accent-hover: #18567f;
  --orange: #a45500;
  /* Against the hand-darkened #a45500 the text orange uses: chroma 0.130 ->
     0.172, measuring 4.19 on the surface where a dot wants 3 -- which is the
     reason it is not deeper still. Redder oranges carry more chroma at this
     lightness but close on --red; this one keeps 0.11 in OKLab.
     The drift runs the OTHER WAY here than on the dark themes, and has to: a
     warmer end is a lighter end, and lighter on a pale surface is less mark,
     not more. Same size of hue step, walked down instead of up -- 0.061, and
     the mark STRENGTHENS across it, 4.19 -> 5.03. The old shared amber took it
     to 1.63, which is a dot that disappears for half of every second. */
  --dot-run: #d05600;
  --dot-run-peak: #b05600;
  --blue: #1868b0;
  --green: #306e32;
  --red: #bc1411;
  --border: #d0d0d0;
}

[data-theme="nord"] {
  --bg: #2e3440;
  --bg-surface: #3b4252;
  --bg-surface2: #434c5e;
  --bg-hover: #4c566a;
  --text: #eceff4;
  --text-muted: #d8dee9;
  --accent: #88c0d0;
  --accent-hover: #8fbcbb;
  --green: #a3be8c;
  --blue: #81a1c1;
  --red: #bf616a;
  --orange: #d08770;
  /* Nord ships one orange and it is a muted salmon by design, so the base
    stands. The peak is the same colour walked 21 degrees warmer -- swing
    0.061, and chroma actually rises, 0.096 -> 0.107. */
  --dot-run: var(--orange);
  --dot-run-peak: #dd9a63;
  --border: #4c566a;
}

[data-theme="monokai"] {
  --bg: #272822;
  --bg-surface: #1e1f1c;
  --bg-surface2: #3e3d32;
  --bg-hover: #49483e;
  --text: #f8f8f2;
  --text-muted: #75715e;
  --accent: #66d9ef;
  --accent-hover: #76e9ff;
  --green: #a6e22e;
  --blue: #66d9ef;
  --red: #f92672;
  --orange: #fd971f;
  /* Monokai's orange is already the palette's most chromatic (0.168); nothing
    deeper exists there, so only the drift changes -- the old peak was 0.102
    away and 23 degrees yellower, which is why the dot kept going yellow. Now
    0.069. */
  --dot-run: var(--orange);
  --dot-run-peak: #ffb300;
  --border: #49483e;
}

[data-theme="solarized-dark"] {
  --bg: #002b36;
  --bg-surface: #073642;
  --bg-surface2: #0a4050;
  --bg-hover: #0d4f5f;
  --text: #839496;
  --text-muted: #586e75;
  --accent: #268bd2;
  --accent-hover: #2e9fe6;
  --green: #859900;
  --blue: #268bd2;
  --red: #dc322f;
  --orange: #cb4b16;
  /* Solarized orange is canonical and stays. The old peak was its own yellow
    #b58900 by way of #ffc107 -- 0.295 away. Now 0.051, and the peak lifts the
    dot to 3.43 on the surface where the base sits at 2.82. */
  --dot-run: var(--orange);
  --dot-run-peak: #d6600f;
  --border: #0a4050;
}

[data-theme="solarized-light"] {
  --bg: #fdf6e3;
  --bg-surface: #eee8d5;
  --bg-surface2: #e4ddc8;
  --bg-hover: #d6cfb7;
  --text: #657b83;
  --text-muted: #93a1a1;
  --text-strong: #002b36;
  --accent: #268bd2;
  --accent-hover: #2e9fe6;
  --green: #859900;
  --blue: #268bd2;
  --red: #dc322f;
  --orange: #cb4b16;
  /* Solarized's two halves share an orange but not a background, so this is the
     one theme whose peak differs from its twin's: the dark half drifts up to
     #d6600f, this one drifts down. Deeper, for the reason spelled out in the
     light theme -- 3.76 at the base and 4.98 at the peak, where the old shared
     amber fell to 1.33. Swing 0.071. */
  --dot-run: var(--orange);
  --dot-run-peak: #ac3e00;
  --border: #d6cfb7;
}

[data-theme="dracula"] {
  --bg: #282a36;
  --bg-surface: #343746;
  --bg-surface2: #44475a;
  --bg-hover: #515470;
  --text: #f8f8f2;
  --text-muted: #6272a4;
  --accent: #bd93f9;
  --accent-hover: #caa4ff;
  --green: #50fa7b;
  --blue: #8be9fd;
  --red: #ff5555;
  --orange: #ffb86c;
  /* Dracula's orange is pastel on purpose. Its peak was already 0.068 away,
    the number every other theme is now tuned to, so this one is unchanged and
    simply named. */
  --dot-run: var(--orange);
  --dot-run-peak: #ffc107;
  --border: #44475a;
}

[data-theme="gruvbox"] {
  --bg: #282828;
  --bg-surface: #3c3836;
  --bg-surface2: #504945;
  --bg-hover: #665c54;
  --text: #ebdbb2;
  --text-muted: #a89984;
  --accent: #83a598;
  --accent-hover: #8ec07c;
  --green: #b8bb26;
  --blue: #83a598;
  --red: #fb4934;
  --orange: #fe8019;
  /* Gruvbox bright orange is the palette's most chromatic (0.182). The peak
    was its yellow #fabd2f at 0.139; now 0.072. */
  --dot-run: var(--orange);
  --dot-run-peak: #fea019;
  --border: #504945;
}

html, body { height: 100%; overflow: hidden; }

body {
  font-family: var(--font);
  background: var(--bg);
  color: var(--text);
  font-size: 14px;
  line-height: 1.5;
}

/* --- App Layout --- */
#app {
  display: flex;
  flex-direction: column;
  /* Cascade: dvh is fine when supported and keyboard-aware (iOS 16+),
     but iOS Safari often leaves dvh frozen at full height when the
     soft keyboard opens, leaving the input row behind it. JS sets
     --vvh from window.visualViewport.height so the app layout shrinks
     to the *visible* viewport when the keyboard is up. */
  height: 100vh;
  height: 100dvh;
  height: var(--vvh, 100dvh);
}

/* A column of rows, not a row of controls: the update banner is a row of its
   own, so it never takes space from the title or the tabs. The header keeps the
   background and the bottom border, so the banner reads as part of it — the
   border sits under BOTH rows. */
#header {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  /* The containing block for #sync-banner, which hangs below the header instead
     of taking a row inside it. z-index 10 below then puts the pill over the
     chat as well, since it rides this element's stacking context. */
  position: relative;
  background: var(--bg-surface);
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
  z-index: 10;
  transition: background 0.2s;
}
/* The connection, said by the chrome rather than by an object in it — see
   setConnState, which writes data-conn. Only the two states worth reporting get
   a colour; `connected` is the absence of one, which is what makes a tint here
   readable at all. Mixed into the surface rather than laid on it, because this
   band holds the title, the tabs and the Stop button, and they have to stay the
   things you read. The urgency stays on the button: it is what pulses, and it
   is what you press. */
#header[data-conn="disconnected"] { background: color-mix(in srgb, var(--red) 16%, var(--bg-surface)); }
#header[data-conn="reconnecting"] { background: color-mix(in srgb, var(--orange) 16%, var(--bg-surface)); }
#header-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 16px;
}

/* The name and its dots, one group inside the row of controls. 8px, which is
   the dashboard card's own gap (.dash-card .dash-row) — these are the same two
   marks about the same session, so they stand the same distance apart in both
   places — where the row around it uses 12px between unrelated controls.
   `min-width: 0` on the group as well as on the h1: without it the group takes
   its max-content width and the ellipsis inside never comes on. */
#header-name {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
/* The hosts for the two dot marks. Flex, because .dash-dot is sized in px and a
   span would leave it inline and unsizeable; `:empty` rather than a hidden
   class, so a header with nothing to say (the dashboard, a session with no
   subagents) costs no gap either. */
#header-dot,
#header-subdots {
  display: flex;
  align-items: center;
  flex-shrink: 0;
}
#header-dot:empty,
#header-subdots:empty { display: none; }
/* A DESK KEEPS ITS SIDEBAR, where the row for the open session already wears
   this dot six inches to the left — and its dashboard card wears the cluster.
   Two drawings of one fact on one screen is the thing this is not. Written as
   the phone query negated, like the other pairs in these stylesheets. */
@media not all and (max-width: 768px) {
  #header-dot,
  #header-subdots { display: none; }
}

#header h1 {
  font-size: 16px;
  font-weight: 600;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* In a session the title opens that session's menu (openSessionMenu). */
#header h1.session-menu-trigger { cursor: pointer; border-radius: var(--radius); }
@media (hover: hover) {
  #header h1.session-menu-trigger:hover { background: var(--bg-hover); }
}
.header-spacer { flex: 1; }

#sidebar-toggle {
  background: none;
  border: none;
  color: var(--text);
  font-size: 20px;
  cursor: pointer;
  padding: 4px 8px;
  border-radius: var(--radius);
}
#sidebar-toggle:hover { background: var(--bg-hover); }
/* The dot below is positioned against its own button. This list had no braces
   of its own and ran straight into the comment before `.sub-dots` — so the two
   rules were one selector list, and every section carrying unread quietly took
   the subagent block's `display: grid`, `grid-template-columns: repeat(3, 8px)`,
   `justify-items: center` and `margin-left: 6px`. Measured before the fix: a
   section shifted 6px the moment it had something to report. A comment between
   two selectors is invisible to the parser; only the missing `{` was. */
#mobile-bar-dashboard.has-unread,
#mobile-bar-lead.has-unread,
#mobile-bar-view.has-unread,
#mobile-bar-chat.has-unread {
  position: relative;
}

/* A lead's subagents as a block of dots — three to a row, one per subagent.
   The cell is 8px and the dot inside it is scaled the same 0.625 the session
   dots use, so the two vocabularies are one: colour for what it is doing, full
   size for something waiting to be read. Three columns because a row of them
   grows into the name beside it, and a block does not.
   `align-content: center` so a single row sits on the name's centre line rather
   than at the top of whatever height the row happens to have. */
.sub-dots {
  /* Two rows at most, and one until there are four. The column count comes from
     leadDots as --sub-cols (n, or ceil(n/2) from four up), so four is a square,
     six is 3x2, nine is 5x2 — and a partial row is always the bottom one,
     filling left to right like everything else that is read.
     A fixed three columns was tried first: it grew downward without limit and
     left a ragged shape hanging off the top line, so five and seven read as two
     unrelated blocks rather than one with more in it. */
  display: grid;
  grid-template-columns: repeat(var(--sub-cols, 3), 8px);
  gap: 1px;
  align-content: center;
  justify-items: center;
  flex-shrink: 0;
  margin-left: 6px;
}
/* Beside the name on a card too, the same as in the sidebar: the block belongs
   to the session it counts, and one rule for both places beats two. */
.sub-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--green);
  transform: scale(0.625);
}
.sub-dot.waiting { background: var(--blue); }
/* Every dot in a cluster is a SUBAGENT's, so the whole rule sits behind the
   option (THE SUBAGENT EXCEPTION, 02-sidebar.css): off — the default — a
   cluster is one block of small dots whatever its members are holding, and the
   colours still say which of them is working. */
body.sub-unread-dots .sub-dot.unread { transform: none; }
.sub-dot.running { background: var(--dot-run); animation: dot-breathe 1s ease-in-out infinite; }
.sub-dot.error { background: var(--red); }
@media (prefers-reduced-motion: reduce) {
  .sub-dot.running { animation: none; }
}

#mobile-bar-dashboard.has-unread::after,
#mobile-bar-lead.has-unread::after,
#mobile-bar-view.has-unread::after,
#mobile-bar-chat.has-unread::after {
  content: "";
  position: absolute;
  top: 4px;
  right: 4px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
}
/* --- Main Layout --- */
#main {
  display: flex;
  flex: 1;
  overflow: hidden;
  position: relative;
}

