Each agent row on the hive dashboard, and the agent page's link menu, linked to the agent's forge profile and its config repo. Both are gone, with what only they used: - hive-agent no longer builds the two links, the `Forge` link kind, or the `forge_public_url` field of its state, and no longer reads `HIVE_FORGE_PUBLIC_URL`. The agent page's link menu and the dashboard row's icon strip (which skips a forge link an agent not yet rebuilt still sends) drop their forge handling. - hive-c0re's dashboard state no longer carries `forge_public_url`. - `HIVE_FORGE_PUBLIC_URL` is no longer forwarded to agents through the meta flake (the forwarded-variable list, its test fixture, the controller's settings and the VM test), so the next meta flake render differs by that one line. hive-c0re still reads it for the forge URL a hive publishes, so `swarm.forge.publicUrl` stays, with its description corrected. - The docs stop describing the links.
869 lines
25 KiB
CSS
869 lines
25 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) lives in each
|
|
component's own shadow root (adoptedStyleSheets), not a global
|
|
stylesheet — nothing to @import here. */
|
|
|
|
/* ─── full-screen layout overrides ─────────────────────────────────
|
|
The agent page mounts a full-viewport terminal under a fixed
|
|
frosted-glass header. See docs/2-handbook/agents/web-page.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-frost-bg: color-mix(in srgb, var(--bg) 72%, transparent);
|
|
--agent-frost-blur: blur(12px) saturate(140%);
|
|
}
|
|
|
|
/* `?hide=header` (per-agent-page embed mode): `Root.tsx` toggles
|
|
this body class when it omits `<Header>`.
|
|
Zeroing the custom properties here — rather than overriding each
|
|
dependent rule — collapses every calc() that reads them (overlay
|
|
offset, terminal scroll padding) in one place. */
|
|
body.agent-hide-header {
|
|
--agent-header-h: 0px;
|
|
--agent-header-real-h: 0px;
|
|
}
|
|
|
|
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/2-handbook/agents/web-page.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 / 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;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
/* 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);
|
|
}
|
|
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. */
|
|
/* Orphaned tombstone — `.btn-cancel-turn` standalone button moved into
|
|
the consolidated status badge's dropdown as a danger-toned row
|
|
(StatusChips.tsx's `StatusMenu`, `@hive/shared`'s
|
|
`.ui-dropdown-item-danger`). */
|
|
/* Orphaned tombstone — `.btn-new-session` round-pill moved into the
|
|
overflow menu, then out of the header entirely: it's a row of the
|
|
state badge's menu now, 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
|
|
overlays 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, 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: 0.8em;
|
|
scroll-padding-top: calc(
|
|
var(--agent-header-real-h, var(--agent-header-h)) + 0.8em
|
|
);
|
|
scroll-padding-bottom: 0.8em;
|
|
overflow: auto;
|
|
}
|
|
/* Tail pill (↓ N new): z-index bumped above the floating chrome so the
|
|
pill sits on the top layer instead of being clipped by it. (Pill is anchored in
|
|
`.agent-main` rather than `.terminal-wrap` — see
|
|
docs/2-handbook/agents/web-page.md::Per-agent page Terminal-wrap paragraph for the
|
|
backdrop-filter stacking-context rationale.) */
|
|
.agent-main .tail-pill {
|
|
bottom: 0.6em;
|
|
z-index: 35;
|
|
}
|
|
|
|
/* ─── 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 the shared `<hive-side-panel>` element
|
|
(@hive/shared/side-panel.js, including drag-to-resize); 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: strip
|
|
the inbox-only chrome (background, border-left) here 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;
|
|
}
|