/* --- Dashboard --- */
#dashboard {
  flex: 1;
  overflow-y: auto;
  padding: 16px;
}
/* THE QUOTA PILLS, the phone's home for the header row (#global-limits, whose
   pill styles in 11-update-banner.css are the only ones — this is a mount, not
   a second look). renderGlobalLimits paints into exactly one of the two, so on
   a desktop this container is empty and the `:empty` rule below is why the
   screen is unchanged there; on a phone it is the header that stands empty.
   It sits above #dashboard-filter-wrap and inside this scrolling panel, so it
   goes up with the cards the way the filter does — neither one is sticky.
   WRAPS rather than scrolls: a second line of pills is readable and a row that
   runs off the side of a 320px screen is not, so the pills are allowed to break
   between themselves and, if one of them is wider than the screen on its own,
   inside their own text. */
#dashboard-limits {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  /* The air #dashboard-filter-wrap puts under itself, so the row keeps this
     screen's one gap rather than inventing a second. */
  padding-bottom: 10px;
  /* An untoned pill has no colour of its own — dimmed IS the colour it
     inherits, which the header's row gives it and this one has to as well. */
  color: var(--text-muted);
}
/* Nothing to say, no height at all — padding included, which is why this is
   display and not a margin trick. */
#dashboard-limits:empty { display: none; }
#dashboard-limits .global-limit-pill {
  /* Whole, or on the next line — never squeezed into a taller box while the
     line it is on still has room. flex-shrink is what decides that; the text
     may only break once the pill has a line to itself and is still too wide. */
  flex-shrink: 0;
  white-space: normal;
  max-width: 100%;
}
/* Cards and the rail. Stacked by default — which is exactly what this screen
   was — and side by side only where there is room for both (see the media query
   under .dash-fold). The narrow case is the default rather than the exception
   because it is also the phone's case, where the rail does not exist at all. */
/* The dispatcher strip: what docker is running for this relay, at a height that
   does not move. Fixed, not "usually short" — the whole reason it exists is the
   half hour when everything is on fire, and a panel that grows with the trouble
   pushes the screen it is reporting on out of the way. Anything per-container
   goes on the cards in the rail instead.
   Sideways when it does not fit, the same answer .status-right gives at the same
   question, and with the same invisible scrollbar. */
#dashboard-dispatch,
#containers-dispatch {
  display: flex;
  align-items: stretch;
  gap: 16px;
  height: 44px;
  flex: 0 0 44px;
  margin-bottom: 12px;
  padding: 0 2px;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  white-space: nowrap;
}
#dashboard-dispatch::-webkit-scrollbar,
#containers-dispatch::-webkit-scrollbar { display: none; }
.dispatch-cell {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2px;
  flex-shrink: 0;
}
.dispatch-label {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
}
.dispatch-value {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  color: var(--text);
  font-family: var(--mono);
}
/* The tail of a cell's value — beside the number, not under it — for the part
   that qualifies the number rather than being one of its own: which kinds make
   up a count. One cell wears it, `containers`, and that is the whole test for
   anything else that wants it — a caption on the figure above, never a second
   measurement sharing its cell. */
.dispatch-sub {
  font-size: 11px;
  color: var(--text-muted);
  font-family: var(--font);
  margin-left: 6px;
}
.dispatch-of { color: var(--text-muted); }
.dispatch-waiting { color: var(--text-muted); font-family: var(--font); font-size: 12px; }
.dispatch-idle { align-self: center; color: var(--text-muted); font-size: 12px; }
/* The roster is the last one agency could see rather than the current one —
   --orange, the same "look at this" the parked deliveries wear, because every
   number beside it may already be wrong. */
.dispatch-stale .dispatch-label,
.dispatch-stale .dispatch-value,
.dispatch-ooms .dispatch-label,
.dispatch-ooms .dispatch-value { color: var(--orange); }
/* WHICH OF THE TWO HOSTS IS ON SCREEN — the whole of the move, in two rules.
   The strip is one drawing (renderDispatch paints every [data-dispatch]); this
   says where it stands. A desktop keeps it on the dashboard, beside the rail's
   container blocks it belongs with. A phone has no blocks under 768px, so the
   dashboard would be holding it alone — and the poll it costs would then be
   keyed on the screen a phone is on most, which is how the always-on background
   polling got there in the first place. On the containers page it stands with
   the cards it describes, on a screen somebody chose to open. */
#containers-dispatch { display: none; }

/* The phone keeps the strip — it is the only place these numbers exist there,
   since the rail with the per-container cards does not — and keeps it at the
   same height, so it scrolls sideways like the section row above the composer
   does. What it changes is the ORDER: at 320px only the first cell is fully on
   screen, and sideways scrolling has no visible affordance, so the near edge
   must hold what the strip is for. Memory (the one number with a denominator)
   comes first, and anything shouting — a dead event stream, an OOM in the last
   hour — comes before even that. The counts, which are the cheapest to guess
   from the screen behind, go last. */
@media (max-width: 768px) {
  /* Hosts swap: the dashboard is the session list and nothing else, and the
     strip stands on the page. */
  #dashboard-dispatch { display: none; }
  #containers-dispatch { display: flex; margin-bottom: 8px; }
  /* Everything below is written against the ATTRIBUTE rather than either id,
     because it is the strip's phone FORM and has to travel with it — a rule
     naming the host it used to stand in would have been left behind by the
     move. */
  [data-dispatch] { gap: 12px; }
  [data-dispatch] .dispatch-mem { order: -1; }
  [data-dispatch] .dispatch-ooms { order: -2; }
  [data-dispatch] .dispatch-stale { order: -3; }
  /* ORDER IS NOW ALL THIS BLOCK DOES. Two rules used to stand here taking
     readings OUT, because ordering was only ever half an answer — the order says
     which cell the near edge holds, not whether the rest can be reached, and
     there is no visible scrollbar to say there is a rest. Measured with the live
     relay's own shape (7 services, 1 job, 2 turns), the strip's content was
     477px: at 390 it overflowed its 358 by 119 and `turns` and `cpu` sat past
     the edge. Hiding, not wrapping and not a taller strip, because the strip's
     own first sentence is a height that does not move.
     Both of those readings have since left the strip on every screen — the meter
     was a second drawing of "1.9 of 7.8GB" standing right beside it, with the
     band's colour already saying the same thing; the turns rate was the one
     figure here that was not a count of what is there now. So a phone has
     nothing left to drop, and what it keeps is the near edge holding the reading
     somebody opened the page for. */
}

/* Memory and CPU on the card of the container they belong to, on a line of their
   own. Muted and mono: it is a measurement under a name, not the name. */
.card-stats {
  /* Its own line on the card, the same way .svc-foot takes one. */
  flex: 0 0 100%;
  min-width: 0;
  display: flex;
  align-items: center;
  /* Wide enough that the two readings are two things. They used to be one
     string joined on " · " and the separator did this work; the marks do it now,
     and a middot between an icon and an icon is a third mark saying nothing. */
  gap: 12px;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-muted);
}
/* One reading: its mark and its number, which belong to each other more closely
   than either belongs to the pair next door — hence 4 against the 12 above. */
.card-stat {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
}

/* --- How much is too much -------------------------------------------------
   The two bands a resource figure can be in when it is worth saying anything,
   worn identically by the per-container cards and by the dashboard strip. One
   rule for both surfaces: see resourceBand in app.js for the thresholds and for
   why there is no band BELOW ordinary.

   --orange is this app's "look at this" — a parked delivery, a warn chip, a dead
   event stream, an OOM in the last hour — and a container over half of what it
   is allowed is exactly that register.

   --red is the harder call, because the rule beside .mode-chip reserves it for
   something having GONE WRONG, and a container at 85% of its cap has not gone
   wrong yet. That rule's actual argument is about FREQUENCY: yolo wore red, most
   sessions run yolo, so the screen carried an alarm that was always on and
   nothing else red could be heard over it. This does not have that shape.
   Measured against the live relay, the busiest figure on a working machine was
   24.9% — a job holding 267MB of a 1GB cap — with every other container under 7%
   and the whole VM at 18.5%. Red here is off essentially always, and on only
   when the kernel is about to kill something, which is one step before the
   failed job that this app already paints red. It is a warning about the same
   event, not a second colour for a different one.

   The LABEL is left alone, unlike .dispatch-stale and .dispatch-ooms which take
   both. Those two are about the cell itself being untrustworthy or being an
   alarm; this is one reading inside an ordinary cell running hot, and colouring
   its label too would make the strip's cells shout at each other. */
.card-stat.res-notable,
.dispatch-cell.res-notable .dispatch-value { color: var(--orange); }
.card-stat.res-high,
.dispatch-cell.res-high .dispatch-value { color: var(--red); }
/* And the top band is not carried by hue ALONE, because in two of the eight
   themes it cannot be. Measured across all eight, --orange to --red in OKLab:
   dark 0.199, monokai 0.235, dracula 0.207, gruvbox 0.108, light 0.106, nord
   0.100 — and solarized-dark and solarized-light both 0.053, against the ~0.08
   this stylesheet already takes as the distance below which two colours stop
   reading as two (see dot-breathe, where 0.08 is where a drift stopped being one
   colour breathing). Solarized's orange is #cb4b16 and its red #dc322f; side by
   side at 11px they are one colour.
   Weight is the second channel, and here it is FREE: every one of these figures
   is set in var(--mono), so 600 and 400 have the same advance width. Measured on
   the widest of them, "7.2GB of 8.0GB", 105.9px at either weight — while the
   same string in var(--font) goes 91.8 -> 94.6px, which is what would have had
   to be paid for anywhere else. Nothing moves as a number crosses 80%.
   The same argument as the title text one level down: whatever the colour says,
   something that is not colour says too. */
.card-stat.res-high,
.dispatch-cell.res-high .dispatch-value { font-weight: 600; }

#dashboard-body {
  display: flex;
  flex-direction: column;
}
#dashboard-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 12px;
}
.dash-card {
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 10px 12px;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.dash-card .dash-row {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.dash-card .dash-name {
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  /* Shrink-to-fit, for the same reason as the sidebar's name: the subagent
     block is a sibling now, and a stretching name would push it over to the
     date. It used to be INSIDE this span, where a grid inside a nowrap
     ellipsised box became a block of its own and dropped to a second line —
     measured, that made those cards 9px taller than the rest. */
  flex: 0 1 auto;
  min-width: 0;
}
/* The date holds the right end, whatever sits between it and the name. */
.dash-card .dash-ts { margin-left: auto; }
/* THE SAME DOT ON A CONTAINER CARD, AND IN THE PHONE'S HEADER. A roster card for
   a session's persistent container, or for a running turn, wears that session's
   dot (buildRosterCard); so does the title bar of the session a phone has open
   (syncHeaderDots, where the sidebar row that would say it is off screen). Every
   rule below names all three hosts rather than being written out per host. One
   selector list is what makes them pixel-identical: measured, a copy in
   06-services.css is a copy that drifts, and these dots are inches apart on the
   same screen. `.svc-card` here is only ever a roster card's — a job or a
   service card has no .dash-dot in it. */
.dash-card .dash-dot,
.svc-card .dash-dot,
#header-dot .dash-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
}
/* Same four states as the sidebar's, same reasons — a wall of cards is read by
   its dots, not by inspecting any one of them. And the same unread rule (see
   .session-item .dot): small when read, full size when unread, whatever the
   state. The card itself carries no unread mark — no border, no wash. The
   header's `unread` rides its own host for the same reason the card's rides the
   card: the class goes on the thing that knows, and the dot is built from the
   same string in both places. */
.dash-card .dash-dot,
.svc-card .dash-dot,
#header-dot .dash-dot { transform: scale(0.625); }
/* `.dash-card` is never a subagent's — the dashboard lists no card of its own
   for one (onDashboard, sessions.js) — but the other two hosts can be: a roster
   card follows whichever session owns the container, and the phone's title bar
   follows whatever is open. Those two wear `sub-session` when the session behind
   them is a subagent (buildRosterCard/paintRosterDot, syncHeaderDots), and there
   the size waits on the option, exactly as the sidebar row does (THE SUBAGENT
   EXCEPTION, 02-sidebar.css). One mark read twice does not get to disagree with
   itself between two screens a thumb is switching between. */
.dash-card.unread .dash-dot,
.svc-card.unread:not(.sub-session) .dash-dot,
#header-dot.unread:not(.sub-session) .dash-dot,
body.sub-unread-dots .svc-card.unread.sub-session .dash-dot,
body.sub-unread-dots #header-dot.unread.sub-session .dash-dot { transform: none; }
.dash-card .dash-dot.idle,
.svc-card .dash-dot.idle,
#header-dot .dash-dot.idle { background: var(--green); }
.dash-card .dash-dot.idle.waiting,
.svc-card .dash-dot.idle.waiting,
#header-dot .dash-dot.idle.waiting { background: var(--blue); }
/* Steady, for the reason at .session-item .dot.running — and most of all here,
   where there are several cards at once. */
.dash-card .dash-dot.running,
.svc-card .dash-dot.running,
#header-dot .dash-dot.running { background: var(--dot-run); animation: dot-breathe 1s ease-in-out infinite; }
.dash-card .dash-dot.error,
.svc-card .dash-dot.error,
#header-dot .dash-dot.error { background: var(--red); }
.dash-card .dash-cwd {
  font-size: 11px;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex: 1;
  min-width: 0;
}
/* The value the dashboard SORTS by, said out loud — the order was
   lastUserMessageAt || createdAt all along, and nothing on the card said so.
   At the right end of the FIRST row, in the place the branch name used to hold.
   The branch is gone because it is the same branch on nearly every card and so
   told you nothing; what stood beside it — the dirty/ahead/behind pills — is
   real state and stays.
   The ⋯ needs no dodging: .dash-card reserves 44px of right padding for it at
   ≤768px, and the reservation covers the row's whole right edge.
   Sized and coloured like .dash-cwd — a date is not the point of the row, only
   its scale. `flex-shrink: 0` so the name gives way first: a name can ellipsis
   and still be recognised, half a date cannot. */
/* Same figures as the sidebar's .ts, for the same reason and off the same
   shortDate. */
.dash-card .dash-ts {
  flex-shrink: 0;
  font-size: 11px;
  color: var(--text-muted);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
/* A session with neither a last message nor a creation date (shortDate answers
   "" to a missing or unparseable one) leaves no gap behind. */
.dash-card .dash-ts:empty { display: none; }
/* Third row of a card: what the working copy is, and what state it is in.
   The branch came back here after being cut from the first row — that row had
   no space for it, this one has nothing else to hold. */
.dash-card .dash-git-row {
  font-size: 11px;
}
.dash-card .dash-branch {
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex: 1;
  min-width: 0;
}
/* `.dash-git` — the counts themselves — is styled with the other git pills,
   further down beside #status-dirty and its siblings. */
/* Phones only: the ⋯ that opens a card's session menu. On a desktop the sidebar
   is right there with the same menu, so the card stays exactly as it was. */
.dash-menu-toggle { display: none; }
@media (max-width: 768px) {
  .dash-card { position: relative; }
  /* Reserved, not overlaid. The corner belongs to the button, so the branch and
     the git summary at the right of the first row stop before it instead of
     running underneath. */
  .dash-card { padding-right: 44px; }
  .dash-menu-toggle {
    display: flex;
    align-items: center;
    justify-content: center;
    position: absolute;
    top: 0;
    right: 0;
    /* A real 44px target. The glyph is small; the box a thumb lands on is not,
       and it has to be, because everything around it opens the session. */
    width: 44px;
    height: 44px;
    background: none;
    border: none;
    color: var(--text-muted);
    font-size: 18px;
    line-height: 1;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  .dash-menu-toggle:active { color: var(--text); }
}

/* The sheet it opens: the session menu, drawn as rows over the bar by
   openBarPopover. Dressed like the subagents sheet next to it. */
.card-menu {
  position: fixed;
  z-index: 60;
  min-width: 200px;
  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;
}
.card-menu-item {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  color: var(--text);
  cursor: pointer;
  font-size: 14px;
  padding: 10px 12px;
  border-radius: 6px;
  white-space: nowrap;
}
.card-menu-item:active { background: var(--bg-hover); }
@media (hover: hover) {
  .card-menu-item:hover { background: var(--bg-hover); }
}

@media (hover: hover) {
  .dash-card:hover { background: var(--bg-hover); border-color: var(--accent); }
}
.dash-card:active { background: var(--bg-hover); }

#dashboard-btn {
  background: none;
  border: none;
  color: var(--text);
  cursor: pointer;
  font-size: 18px;
  padding: 4px 8px;
  border-radius: var(--radius);
}
@media (hover: hover) {
  #dashboard-btn:hover { background: var(--bg-hover); }
}
/* On a phone the bottom bar's left section leads here, under the thumb rather
   than in the corner the other hand has to reach. The unread dot is written to
   both without either asking whether it is on screen (renderSessionList), so it
   survives the button going away. */
@media (max-width: 768px) {
  #dashboard-btn { display: none; }
}

#messages {
  flex: 1;
  overflow-y: auto;
  padding: 16px;
  scroll-behavior: smooth;
}

#scroll-bottom-btn {
  position: absolute;
  bottom: 8px;
  right: 16px;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--bg-surface2);
  border: 1px solid var(--border);
  color: var(--text);
  font-size: 18px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 5;
}
#scroll-bottom-btn:hover { background: var(--bg-hover); }

