hyperhive/frontend/packages/agent/src/agent.css
iris cd339af91b agent.css: delete dead .overflow-btn/.overflow-menu/.overflow-item rules
`OverflowMenu.tsx`/`.css` were deleted (rebuild button removed outright,
dashboard link moved into `MetaNav`), but `agent.css` still carried the
whole trigger+popover+item rule set — grepped the entire `agent`
package, none of these classnames appear in any current markup.

Also fixed three tombstone comments elsewhere in the file that pointed
at the now-also-deleted `.overflow-item-*` classes as "where it moved
to" — they'd have dangled once those rules were gone too. And two stale
doc claims caught in the same sweep: `docs/web-ui/agent.md` still listed
`OverflowMenu.tsx` in the header's component tree and described "the
overflow menu" as a live effort-picker location.
2026-08-29 10:16:00 +02:00

882 lines
30 KiB
CSS

/* Shared Catppuccin palette + body typography + terminal pane styles.
Bundled in front of the agent-only rules below via esbuild. */
@import "@hive/shared/base.css";
@import "@hive/shared/terminal.css";
/* Shared page-header chrome (page-header / page-back / page-title) — gives the
agent SUB-pages (stats, screen) the same back-link nav the dashboard's
standalone pages use. The live terminal page keeps its own header. */
@import "@hive/shared/chrome.css";
/* Shared pill/chip/badge shape (the "extract pill component" issue,
resolved as shared CSS rather than a JS component) — see pill.css
itself for the classnames it covers. */
@import "@hive/shared/pill.css";
/* Themed dialog/toast component CSS (modal.js) now lives in each
component's own shadow root (adoptedStyleSheets), not a global
stylesheet — nothing to @import here any more. */
/* ─── full-screen layout overrides ─────────────────────────────────
The agent page mounts a full-viewport terminal under a fixed
frosted-glass header + composer pair. See docs/web-ui.md::Per-agent
page for the layer / pill / side-panel structure; rules below
override the in-page defaults from @hive/shared. */
:root {
/* 6em min-height accommodates the full-height agent icon (5em +
padding) alongside the two-row main column (title + nav-links
on top, state strip below). */
--agent-header-h: 6em;
--agent-composer-h: 3.6em;
--agent-frost-bg: color-mix(in srgb, var(--bg) 72%, transparent);
--agent-frost-blur: blur(12px) saturate(140%);
}
html, body { height: 100%; margin: 0; }
/* Legacy in-page layout retained for the sibling stats page
(`stats.html`) which doesn't apply `body.agent-shell` and stays
on a normal-document scroll. screen-shell is also excluded — it
manages its own full-viewport layout below. */
body:not(.agent-shell):not(.screen-shell) {
max-width: 110em;
margin: 1.5em auto;
padding: 0 1.5em;
height: auto;
}
.banner {
text-align: center;
margin: 0 0 1em 0;
font-size: 0.95em;
overflow-x: auto;
background: linear-gradient(
90deg,
var(--purple-dim) 0%,
var(--purple) 50%,
var(--purple-dim) 100%
);
background-size: 200% 100%;
background-position: 50% 0;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
filter: drop-shadow(0 0 6px color-mix(in srgb, var(--purple) 45%, transparent));
}
body.agent-shell {
background: var(--bg);
color: var(--fg);
/* Body itself doesn't scroll; the terminal does inside .agent-main. */
overflow: hidden;
/* Subtle radial accent to give the otherwise-flat full-screen
surface some depth and reinforce the vibec0re mood. */
background:
radial-gradient(ellipse 80% 60% at 50% 0%,
color-mix(in srgb, var(--purple) 6%, transparent) 0%,
transparent 60%),
var(--bg);
}
.agent-header {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 30;
min-height: var(--agent-header-h);
display: flex;
/* align-items: stretch lets the icon take the full header height
(it sizes itself via aspect-ratio off the stretched height). The
main column + pills column self-centre via inner layout. */
align-items: stretch;
gap: 0.9em;
padding: 0.5em 1em;
background: var(--agent-frost-bg);
-webkit-backdrop-filter: var(--agent-frost-blur);
backdrop-filter: var(--agent-frost-blur);
border-bottom: 1px solid var(--purple-dim);
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35);
}
/* Main column: title row on top, state strip below. Centred
vertically against the full-height icon on the left. */
.agent-header-main {
display: flex;
flex-direction: column;
justify-content: center;
gap: 0.45em;
min-width: 0;
flex: 1 1 auto;
}
.agent-header-row {
display: flex;
align-items: center;
gap: 0.8em;
flex-wrap: wrap;
min-width: 0;
}
.agent-header-title-row h2 {
margin: 0;
line-height: 1;
}
.agent-nav {
display: flex;
flex-wrap: wrap;
gap: 0.4em 0.8em;
margin: 0;
}
.agent-state-row {
margin: 0;
gap: 0.5em;
}
/* Right cluster — flyout pills, and (new Preact page only) the status
badges (`StatusChips`) too — mara, live: "move badges to where the
dropdowns are". `flex-wrap: wrap` alone doesn't help if this
container can't itself shrink: the old `flex-shrink: 0` here (fine
for app.js's 2-3-small-pills case, which never needed to shrink)
told the flex layout "give this its full natural width no matter
what," so once badges made that natural width wider than the
viewport, it just overflowed past the edge instead of ever getting
narrow enough to trigger its own wrap — mara, live, on the 500px
screenshot: "does not overflow properly". Dropping `flex-shrink: 0`
(default is `1`) is the whole fix — deliberately NOT pairing it
with `min-width: 0` (tried that first, made it worse: an unset
`min-width` still floors shrinking at this container's own
min-content — the widest single wrapped child, e.g. one badge, not
the full unwrapped row — which is the correct floor; `min-width: 0`
removes that floor entirely, so at a narrow enough viewport the
container shrinks past what its own content needs and the content
overflows its box sideways, landing on top of `.agent-header-main`
instead of wrapping cleanly — confirmed live, reproducible at
320px, not a one-off capture glitch). */
.agent-header-pills {
display: flex;
align-items: center;
gap: 0.5em;
flex-wrap: wrap;
justify-content: flex-end;
align-self: center;
}
h2, h3 {
color: var(--purple);
text-transform: uppercase;
letter-spacing: 0.15em;
text-shadow: 0 0 8px color-mix(in srgb, var(--purple) 40%, transparent);
}
.agent-icon {
/* Square identity anchor — explicit 5em sizing + align-self.
See docs/web-ui.md::Per-agent page (Agent icon) for the
intrinsic-dim-pushes-parent-flex-open + sticks-to-top
rationale. */
width: 5em;
height: 5em;
flex-shrink: 0;
align-self: flex-start;
border-radius: 8px;
box-shadow: 0 0 18px -2px color-mix(in srgb, var(--purple) 40%, transparent);
object-fit: cover;
}
/* Meta-nav links (stats / screen / forge / dashboard / extras) —
no underline; hover lights with cyan glow + subtle background
tint. Reads as a row of soft tabs rather than default-styled
inline anchors. */
.agent-nav-link {
color: var(--cyan);
text-decoration: none;
font-size: 0.85em;
letter-spacing: 0.04em;
padding: 0.1em 0.35em;
border-radius: 3px;
text-shadow: 0 0 4px color-mix(in srgb, var(--cyan) 40%, transparent);
transition: color 0.15s ease, text-shadow 0.15s ease, background 0.15s ease;
}
.agent-nav-link:hover {
color: var(--fg);
background: color-mix(in srgb, var(--cyan) 8%, transparent);
text-shadow: 0 0 10px color-mix(in srgb, var(--cyan) 85%, transparent);
}
/* Header pill (inbox / loose-ends triggers) — no CSS of its own any
more. `HeaderPill.tsx` renders `@hive/shared`'s `Badge` directly now
(mara: "cant we reuse the badge component") instead of a hand-styled
pill matched to Badge's own values; nothing left to override here. */
.agent-main {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
}
/* Login flow overlay: only rendered when status != online. Sits
centred over the (likely-empty) terminal area; doesn't take chrome
space in the normal online flow. `--agent-header-real-h` (set by
the new Preact page's `Header.tsx` via ResizeObserver, falling back
to the static `--agent-header-h` guess before the first
measurement / on the old app.js page, which never sets it) is the
header's ACTUAL rendered height, not an assumed one — see
Header.tsx's file comment for why this matters (a static guess is
wrong the moment the header wraps an extra line on some viewport). */
.agent-status-overlay {
position: absolute;
top: calc(var(--agent-header-real-h, var(--agent-header-h)) + 1.5em);
left: 50%;
transform: translateX(-50%);
max-width: 44em;
width: calc(100% - 3em);
z-index: 10;
}
.agent-status-overlay:empty { display: none; }
.agent-status-overlay > * {
background: var(--bg-elev);
border: 1px solid var(--purple-dim);
border-radius: 6px;
padding: 1em 1.2em;
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.4);
}
.agent-composer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 30;
min-height: var(--agent-composer-h);
background: var(--agent-frost-bg);
-webkit-backdrop-filter: var(--agent-frost-blur);
backdrop-filter: var(--agent-frost-blur);
border-top: 1px solid var(--purple-dim);
box-shadow: 0 -6px 18px rgba(0, 0, 0, 0.35);
}
.agent-composer .term-input {
/* The composer is its own chrome now — drop the in-terminal-wrap
padding the legacy layout assumed. */
padding: 0.45em 1em;
}
.agent-composer .term-input .sendform-term {
/* No dashed top-border in the floating composer — the box-shadow
and frosted border already separate it from the terminal. */
border-top: 0;
padding-top: 0;
}
.meta { color: var(--muted); font-size: 0.85em; }
.status-online { color: var(--green); text-shadow: 0 0 6px color-mix(in srgb, var(--green) 55%, transparent); }
.status-needs-login { color: var(--amber); text-shadow: 0 0 6px color-mix(in srgb, var(--amber) 55%, transparent); }
code { background: color-mix(in srgb, var(--purple) 12%, transparent); padding: 0.05em 0.3em; border-radius: 2px; }
a {
color: var(--cyan);
text-shadow: 0 0 4px color-mix(in srgb, var(--cyan) 50%, transparent);
}
a:hover { color: var(--fg); text-shadow: 0 0 12px color-mix(in srgb, var(--cyan) 90%, transparent); }
.btn {
font-family: inherit;
font-size: 1em;
background: var(--bg);
border: 1px solid var(--purple);
color: var(--purple);
padding: 0.25em 0.8em;
cursor: pointer;
letter-spacing: 0.1em;
}
.btn {
text-shadow: 0 0 4px currentColor;
transition: box-shadow 0.15s ease, text-shadow 0.15s ease;
}
.btn:hover {
background: color-mix(in srgb, var(--fg) 6%, transparent);
text-shadow: 0 0 10px currentColor;
box-shadow: 0 0 10px -2px currentColor;
}
.btn-login { color: var(--amber); border-color: var(--amber); }
.btn-cancel { color: var(--red); border-color: var(--red); font-size: 0.85em; padding: 0.15em 0.6em; }
/* Orphaned rules — left here as a tombstone so a search for the
class name finds them. Live consumers gone:
- `.btn-rebuild` was a per-agent header chip, then briefly an
overflow-menu item (`.overflow-item-rebuild`, also since deleted,
see MetaNav.tsx's file comment) — the button itself is gone
outright now, no replacement.
- `.btn-send` was a green send-button variant — the dashboard's
compose form that used it is retired. */
.sendform { display: flex; gap: 0.6em; margin-top: 0.5em; }
.sendform input {
font-family: inherit; font-size: 1em;
background: rgba(255, 255, 255, 0.04);
color: var(--fg);
border: 1px solid var(--purple-dim);
padding: 0.4em 0.6em;
flex: 1;
}
.sendform input:focus { outline: 1px solid var(--purple); }
.loginform { display: flex; gap: 0.6em; margin-top: 0.5em; align-items: stretch; }
.loginform input {
font-family: inherit; font-size: 1em;
background: rgba(255, 255, 255, 0.04);
color: var(--fg);
border: 1px solid var(--purple-dim);
padding: 0.4em 0.6em;
flex: 1;
}
.loginform input:focus { outline: 1px solid var(--purple); }
/* Show / hide toggle for the masked OAuth-code input. Quiet by
default (muted border + transparent bg), lights amber on hover
/ when pressed (`aria-pressed="true"`) so the operator sees at
a glance whether the code is currently visible. */
.loginform-reveal {
font-family: inherit;
font-size: 1em;
background: transparent;
color: var(--muted);
border: 1px solid var(--purple-dim);
border-radius: 3px;
padding: 0 0.6em;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
transition: color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
.loginform-reveal:hover,
.loginform-reveal:focus-visible {
color: var(--amber);
border-color: var(--amber);
outline: none;
}
.loginform-reveal[aria-pressed="true"] {
color: var(--amber);
border-color: var(--amber);
box-shadow: 0 0 8px -2px var(--amber);
}
pre.diff {
background: rgba(255, 255, 255, 0.03);
border: 1px solid var(--purple-dim);
padding: 0.6em 0.8em;
overflow-x: auto;
white-space: pre-wrap;
word-break: break-all;
max-height: 30em;
}
/* Per-agent inbox section — collapsible, dim, lives between the
state row and the terminal so the operator can peek at what
landed without scrolling through the live tail. */
.agent-inbox {
margin: 0.4em 0;
font-size: 0.85em;
color: var(--muted);
}
.agent-inbox > summary {
cursor: pointer;
letter-spacing: 0.05em;
list-style: none;
}
.agent-inbox > summary::marker { content: ''; }
.agent-inbox[open] > summary > span::before { content: ''; }
.agent-inbox ul {
list-style: none;
padding: 0.4em 0.8em;
margin: 0.3em 0 0;
background: rgba(255, 255, 255, 0.02);
border-left: 2px solid var(--purple-dim);
max-height: 16em;
overflow-y: auto;
}
/* Inbox / loose-ends rows: header (from / sep / ts) on one line,
body on its own line below — gives the body the full panel width
instead of squeezing it into a fourth grid column that wrapped
long messages over many narrow lines. */
.agent-inbox li {
padding: 0.4em 0;
display: block;
}
.agent-inbox .inbox-ts { color: var(--muted); font-size: 0.9em; margin-left: 0.5em; }
.agent-inbox .inbox-from { color: var(--amber); }
.agent-inbox .inbox-sep { color: var(--muted); margin-left: 0.4em; }
/* Todos flyout per-row checkbox (bulk mark-done) — sits inline before the
existing `.inbox-from` label, same row. */
.agent-inbox .todo-cb {
vertical-align: middle;
margin-right: 0.2em;
}
.agent-inbox .inbox-body {
display: block;
color: var(--fg);
white-space: pre-wrap;
word-break: break-word;
margin-top: 0.3em;
padding-left: 0.8em;
border-left: 2px solid var(--purple-dim);
}
/* Running bash-tasks flyout rows (buildBashTasksList). */
.agent-inbox .bash-task-status { font-weight: bold; font-size: 0.9em; }
.agent-inbox .bash-task-running { color: var(--green); }
.agent-inbox .bash-task-pending { color: var(--blue); }
.agent-inbox .bash-task-cmd {
font-family: monospace;
font-size: 0.92em;
}
.agent-inbox li.inbox-reply {
padding-left: 1em;
border-left: 2px solid var(--border);
margin-left: 0.4em;
}
.agent-inbox .inbox-reply-tag { color: var(--muted); font-size: 0.85em; }
/* Bulk-action header row: "mark all read" above the recent-messages
list in the inbox flyout, and "select all / select none / mark
done" above the list in the todos flyout — same classes, shared
look (mauve hover, bg-elev background) so both read as part of
the same affordance family. */
.agent-inbox .inbox-mark-all-row {
display: flex;
gap: 0.6em;
align-items: center;
padding: 0.4em 0.2em 0.6em;
margin-bottom: 0.5em;
border-bottom: 1px solid var(--border);
}
.agent-inbox .inbox-mark-all-btn {
font-family: inherit;
font-size: inherit;
background: var(--bg-elev);
color: var(--fg);
border: 1px solid var(--border);
border-radius: 3px;
padding: 0.3em 0.7em;
cursor: pointer;
white-space: nowrap;
}
.agent-inbox .inbox-mark-all-btn:hover:not(:disabled) {
border-color: var(--purple);
color: var(--purple);
}
.agent-inbox .inbox-mark-all-btn:disabled {
opacity: 0.5;
cursor: default;
}
.agent-inbox .inbox-mark-status {
color: var(--muted);
font-size: 0.9em;
}
.last-turn {
color: var(--muted);
font-size: 0.8em;
letter-spacing: 0.05em;
}
/* Markup carries `hive-pill-sm` alongside each of these (shared
pill.css supplies the shape); rules below are colour only. */
.model-chip,
.effort-chip {
border-color: var(--purple-dim);
}
.model-chip { color: var(--cyan); }
.effort-chip { color: var(--amber); }
/* Context-window badge. Mirrors Claude Code's bottom-right "N tokens"
chip — single primary number (total prompt tokens in use), full
breakdown on hover. Sized/coloured like a peer of model-chip so
the state row reads as one row of chrome. */
.ctx-badge {
border-color: var(--purple-dim);
color: var(--green);
cursor: default;
white-space: pre-line;
}
/* Harness reachability badge. Markup carries `hive-pill` alongside
`status-badge` (shared pill.css supplies the shape); colour
communicates the actual reachability state. */
.status-badge.status-loading { color: var(--muted); border-color: var(--purple-dim); }
.status-badge.status-online { color: var(--green); border-color: var(--green);
text-shadow: 0 0 6px color-mix(in srgb, var(--green) 55%, transparent); }
.status-badge.status-rate-limited { color: var(--red); border-color: var(--red);
text-shadow: 0 0 6px color-mix(in srgb, var(--red) 55%, transparent); }
.status-badge.status-needs-login { color: var(--amber); border-color: var(--amber); }
.status-badge.status-offline { color: var(--muted); border-color: var(--muted); }
/* Orphaned tombstone — `.btn-dashlink` chip that lived beside the
title moved into the overflow menu, then into `MetaNav`'s `🔗`
popover (the `↑ dashboard` item) once the overflow menu itself was
deleted — see MetaNav.tsx's file comment. */
.btn-cancel-turn {
font-family: inherit;
font-size: 0.8em;
letter-spacing: 0.08em;
background: transparent;
color: var(--red);
border: 1px solid var(--red);
border-radius: 999px;
padding: 0.2em 0.8em;
cursor: pointer;
text-shadow: 0 0 4px currentColor;
transition: box-shadow 0.15s ease, background 0.15s ease;
}
.btn-cancel-turn:hover {
background: color-mix(in srgb, var(--red) 10%, transparent);
box-shadow: 0 0 10px -2px currentColor;
}
/* Orphaned tombstone — `.btn-new-session` round-pill moved into the
overflow menu, then out of the header entirely: it's the
`/new-session` slash command now (TermInput.tsx), no button/chip
left to style. */
/* Markup carries `hive-pill` alongside `state-badge` (shared pill.css
supplies the shape); this rule is only the transition + state-
specific colour below. */
.state-badge {
transition: color 280ms ease, border-color 280ms ease,
box-shadow 280ms ease, background 280ms ease;
}
.state-badge.state-loading {
color: var(--muted); border-color: var(--purple-dim);
}
.state-badge.state-offline {
color: var(--muted); border-color: var(--muted);
}
.state-badge.state-idle {
color: var(--cyan); border-color: var(--cyan);
text-shadow: 0 0 6px color-mix(in srgb, var(--cyan) 55%, transparent);
}
.state-badge.state-thinking {
color: var(--amber); border-color: var(--amber);
text-shadow: 0 0 6px color-mix(in srgb, var(--amber) 65%, transparent);
animation: badge-pulse 1.8s ease-in-out infinite;
}
.state-badge.state-compacting {
color: var(--purple); border-color: var(--purple);
text-shadow: 0 0 6px color-mix(in srgb, var(--purple) 65%, transparent);
animation: badge-pulse 1.8s ease-in-out infinite;
}
.state-badge.state-just-changed {
animation: state-flash 600ms ease-out;
}
@keyframes state-flash {
0% { box-shadow: 0 0 0 0 currentColor, 0 0 0 0 currentColor; }
60% { box-shadow: 0 0 18px -4px currentColor, 0 0 4px 0 currentColor; }
100% { box-shadow: 0 0 0 0 currentColor, 0 0 0 0 currentColor; }
}
/* Full-screen overrides for the shared terminal rules. The base
`.terminal-wrap` (in shared/src/terminal.css) ships a crust-on-
black frame for the in-page case; the agent page now owns the
whole viewport so the frame chrome would be redundant noise.
`.live.terminal` similarly drops the in-page max-height cap so
it can fill the main area top-to-bottom; the floating header +
composer overlay it via fixed positioning. */
.agent-main .terminal-wrap {
position: absolute;
inset: 0;
border: 0;
background: transparent;
box-shadow: none;
border-radius: 0;
margin: 0;
padding: 0;
}
.agent-main .live.terminal {
position: absolute;
inset: 0;
height: auto;
max-height: none;
/* Scroll behind the floating header/composer, but keep the first
and last rows reachable with extra padding inside the scroll
area. scroll-padding-* keeps anchor-jumps (the `↓ N new` pill,
focus restore) clear of the floats too. */
padding-top: calc(var(--agent-header-real-h, var(--agent-header-h)) + 0.8em);
padding-bottom: calc(var(--agent-composer-h) + 0.8em);
scroll-padding-top: calc(var(--agent-header-real-h, var(--agent-header-h)) + 0.8em);
scroll-padding-bottom: calc(var(--agent-composer-h) + 0.8em);
overflow: auto;
}
/* Tail pill (↓ N new): nudged up so it floats clear of the composer
rather than colliding with the frosted bar. z-index bumped above
the composer (z-30) so the pill sits on the top layer instead of
being clipped by the floating chrome. (Pill is anchored in
`.agent-main` rather than `.terminal-wrap` — see
docs/web-ui.md::Per-agent page Terminal-wrap paragraph for the
backdrop-filter stacking-context rationale.) */
.agent-main .tail-pill {
bottom: calc(var(--agent-composer-h) + 0.6em);
z-index: 35;
}
/* Composer chrome — used to live inside `.terminal-wrap`; now lives
inside the fixed `.agent-composer` defined further up. The base
rules below stay scoped to whichever ancestor owns it. */
.term-input .sendform-term {
display: flex;
/* mara, live: "bottom input row text not aligned / inconsistent
sizes." Real cause: `.prompt` (1em, no line-height override) and
`.submit-hint` (0.8em, no line-height override) each get a
browser-default line-box at a different em-relative size, so
`align-items: flex-start` (their old value) pinned each one's
line-box top edge to the row's top — same nominal `padding-top`
but a DIFFERENT absolute offset per element, since 0.25em means
something different at 1em vs 0.8em font-size. `center` sidesteps
the whole "line-box for text this size" mismatch by aligning each
item's own box middle instead of a top edge that was never
actually equal. Still correct once the textarea grows multi-line
(a common composer-bar convention — Slack/Discord centre their
send affordance against a grown box too, not pin it to the first
line). */
align-items: center;
gap: 0.5em;
/* The dashed in-frame separator is dropped — see the
.agent-composer .term-input override above for the floating-bar
variant. */
border-top: 1px dashed var(--purple-dim);
padding-top: 0.5em;
}
.term-input .prompt {
color: var(--green);
text-shadow: 0 0 6px color-mix(in srgb, var(--green) 60%, transparent);
user-select: none;
flex: 0 0 auto;
}
.term-input textarea {
flex: 1;
background: transparent;
border: 0;
outline: 0;
color: var(--fg);
font-family: inherit;
font-size: 1em;
padding: 0.2em 0;
caret-color: var(--green);
resize: none;
overflow-y: auto;
line-height: 1.4;
min-height: 1.4em;
}
.term-input textarea::placeholder { color: var(--muted); }
.term-input .submit-hint { color: var(--muted); font-size: 0.8em; flex: 0 0 auto; }
.term-input.disabled .prompt { color: var(--muted); text-shadow: none; }
.term-input.disabled textarea { color: var(--muted); }
/* Row + pill + details styling moved to hive-fr0nt::TERMINAL_CSS. */
/* ─── side panel (singleton drawer) ────────────────────────────────
Inbox + loose-ends details open here instead of expanding inline. The
drawer chrome itself (backdrop, drawer, resize handle, header, title,
close button) is now the shared `<hive-side-panel>` element
(@hive/shared/side-panel.js — this also picks up drag-to-resize,
which this page didn't have before); what's left here is the drag
cursor-override escape hatch (has to be a global, light-DOM rule — it
reaches every element on the page during a drag, not just the shared
element's own shadow tree) and the styling for the panel's own
content types, which the shared element's shadow tree can never
reach (see @hive/shared/side-panel/hive-side-panel.css's header
comment). The `hive-side-panel .agent-inbox …` rules below use the
element's own tag name as the selector root — no compatibility class
needed, the tag name already uniquely identifies the light-DOM
instance app.js's `sidePanel` creates. */
body.side-panel-resizing {
user-select: none;
}
body.side-panel-resizing * { cursor: ew-resize !important; }
/* Inbox / loose-ends lists rendered into the side-panel body. The
legacy <details>-collapsible variant of .agent-inbox is gone, so
here we strip the inbox-only chrome (background, border-left) and
let the panel body's own padding own the framing. */
hive-side-panel .agent-inbox {
margin: 0;
font-size: inherit;
color: var(--fg);
}
hive-side-panel .agent-inbox ul {
background: transparent;
border-left: 0;
padding: 0;
max-height: none;
overflow: visible;
}
/* Empty-state placeholders for the side panel (when count drops to 0
between the click and the render — rare, but possible). */
.side-panel-empty {
color: var(--muted);
font-style: italic;
padding: 1em 0;
}
/* Hive identity row — shows "swarm / hive" label beneath the title.
Hidden until JS populates from /api/state (hive_name + swarm_name).
Mirrors the `.banner-thin` style in dashboard.css but kept tight
(no letter-spacing, smaller text) so it reads as a sub-label. */
.agent-hive-row {
/* Omit the default row gap when the row is hidden to avoid a
blank gap between the title row and the state row. */
gap: 0;
}
.agent-hive-label {
font-size: 0.72em;
letter-spacing: 0.1em;
color: var(--purple-dim);
text-transform: uppercase;
}
/* ─── /stats page ────────────────────────────────────────────────────────── */
/* The stats page header (back link + title) uses the shared
`.page-header` chrome (chrome.css), same as /screen. */
.window-tabs {
display: flex;
gap: 0.4rem;
margin: 0.5rem 0 1rem;
}
.window-tabs button {
background: var(--bg-elev);
color: var(--fg);
border: 1px solid var(--border);
padding: 0.3rem 0.8rem;
font-family: inherit;
cursor: pointer;
}
/* Active window: createTabStrip (@hive/shared/tabs.js) toggles
`.hive-tab--active`. The window selector keeps its own pill look rather
than the shared `.hive-tab` text-tab base. */
.window-tabs button.hive-tab--active {
background: var(--purple-dim);
border-color: var(--purple);
color: var(--purple);
}
.summary {
display: flex;
gap: 0.75rem;
flex-wrap: wrap;
margin-bottom: 1rem;
}
.summary .chip {
display: flex;
flex-direction: column;
justify-content: center;
align-items: stretch;
background: var(--bg-elev);
border: 1px solid var(--border);
padding: 0.5rem 0.9rem;
border-radius: 4px;
box-sizing: border-box;
min-width: 9rem;
height: 3.4rem;
line-height: 1.2;
}
.summary .chip .label {
color: var(--muted);
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.summary .chip .value {
color: var(--cyan);
font-weight: bold;
font-size: 1.05rem;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(420px, 1fr));
gap: 1rem;
}
.stats-card {
background: var(--bg-elev);
border: 1px solid var(--border);
padding: 0.75rem 1rem 1rem;
border-radius: 4px;
}
.stats-card h3 {
margin: 0 0 0.5rem;
color: var(--purple);
font-size: 0.95rem;
font-weight: normal;
}
.stats-card .chart-wrap { position: relative; height: 220px; }
.stats-card.wide { grid-column: 1 / -1; }
.stats-card.wide .chart-wrap { height: 260px; }
/* ─── /screen page ─── */
/* Full-screen VNC canvas viewer (screen.html). All rules scoped to
body.screen-shell so they can coexist with the agent/stats rules. */
body.screen-shell {
font-size: 14px;
overflow: hidden;
}
/* The screen page header (back link + title + controls) uses the
shared `.page-header` chrome (chrome.css). The fit/match/debug
controls + status chip below sit inside that header. */
/* Toolbar toggle buttons (fit / match-size / debug). */
.tbtn {
padding: 0.15rem 0.5rem;
font-size: 0.72rem;
font-family: inherit;
background: var(--border);
color: var(--subtext0);
border: 1px solid var(--purple-dim);
border-radius: 4px;
cursor: pointer;
}
.tbtn.active { color: var(--green); border-color: var(--green); }
.tbtn:disabled { opacity: 0.4; cursor: default; }
/* Connection status chip at the right end of the screen page header. */
#status {
margin-left: auto;
font-size: 0.75rem;
color: var(--subtext0);
}
#status.connected { color: var(--green); }
#status.error { color: var(--red); }
/* RFB debug log — fixed panel at the bottom, hidden by default. */
#debug-log {
position: fixed;
bottom: 0; left: 0; right: 0;
max-height: 40vh;
overflow-y: auto;
background: color-mix(in srgb, var(--crust) 95%, transparent);
border-top: 1px solid var(--purple-dim);
font-size: 0.72rem;
font-family: ui-monospace, monospace;
padding: 0.4rem 0.6rem;
z-index: 100;
display: none; /* toggled by the debug button */
}
#debug-log .dbg-line { color: var(--subtext0); margin: 1px 0; white-space: pre; }
#debug-log .dbg-line.err { color: var(--red); }
#debug-log .dbg-line.ok { color: var(--green); }
#debug-log .dbg-line.send { color: var(--blue); }
/* Canvas container — centres the VNC framebuffer. */
#canvas-wrap {
display: flex;
justify-content: center;
align-items: flex-start;
width: 100%;
height: calc(100% - 36px);
overflow: auto;
background: var(--crust);
}
/* Fit mode: canvas scaled in JS to fill the container. */
#canvas-wrap.fit { align-items: center; overflow: hidden; }
#canvas-wrap canvas { display: block; cursor: default; }
/* Pin the canvas to the JS-calculated size; prevents flex-item
min-width:auto from silently expanding beyond the scaled size. */
#canvas-wrap.fit canvas { flex: none; min-width: 0; min-height: 0; }
/* Transient flash message (copy-text, disconnect, etc.). */
#msg {
position: fixed;
bottom: 1rem;
left: 50%;
transform: translateX(-50%);
background: var(--border);
color: var(--yellow);
border-radius: 6px;
padding: 0.4rem 0.9rem;
font-size: 0.8rem;
opacity: 0;
transition: opacity 0.3s;
pointer-events: none;
}