Styled variant with zero HTML/JS reference anywhere in the frontend tree, found while surveying pill/chip/badge sites for the "extract pill component" issue. The extraction it was originally paired with (PR#3197) got closed as not worth the churn for 2 instances, but this one finding stands on its own — no reason to carry dead CSS forward. npm run build clean.
1005 lines
32 KiB
CSS
1005 lines
32 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 stacked / inline with the overflow
|
|
trigger. Vertically centred against the full-height icon, no
|
|
wrap; pills can drop to a row of their own under crowding via
|
|
the flex-wrap of `.agent-header-pills` itself. */
|
|
.agent-header-pills {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5em;
|
|
flex-shrink: 0;
|
|
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);
|
|
}
|
|
|
|
/* Overflow menu trigger — `⋯` round button on the right of the
|
|
pills row. Quiet by default, lights on hover / open. */
|
|
.overflow-btn {
|
|
background: transparent;
|
|
border: 1px solid var(--purple-dim);
|
|
color: var(--muted);
|
|
border-radius: 999px;
|
|
width: 2em;
|
|
height: 1.8em;
|
|
font-size: 1em;
|
|
line-height: 1;
|
|
cursor: pointer;
|
|
padding: 0;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
transition: color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
|
|
}
|
|
.overflow-btn:hover,
|
|
.overflow-btn[aria-expanded="true"] {
|
|
color: var(--purple);
|
|
border-color: var(--purple);
|
|
box-shadow: 0 0 10px -2px var(--purple);
|
|
}
|
|
|
|
/* Overflow popover — rebuild + new-session + logout (and the
|
|
dashboard back-link, prepended in app.js setHeader). Positioned
|
|
in JS so the menu's top-right corner anchors under the trigger
|
|
button. See docs/web-ui.md::Per-agent page (Overflow button)
|
|
for the `:not([hidden])` scoping rationale. */
|
|
.overflow-menu:not([hidden]) {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.15em;
|
|
}
|
|
.overflow-menu {
|
|
position: fixed;
|
|
background: var(--agent-frost-bg);
|
|
-webkit-backdrop-filter: var(--agent-frost-blur);
|
|
backdrop-filter: var(--agent-frost-blur);
|
|
border: 1px solid var(--purple-dim);
|
|
border-radius: 6px;
|
|
padding: 0.35em;
|
|
z-index: 40;
|
|
box-shadow: 0 10px 26px rgba(0, 0, 0, 0.45);
|
|
min-width: 14em;
|
|
}
|
|
.overflow-item {
|
|
background: transparent;
|
|
border: 1px solid transparent;
|
|
color: var(--fg);
|
|
font-family: inherit;
|
|
font-size: 0.9em;
|
|
text-align: left;
|
|
padding: 0.4em 0.7em;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.6em;
|
|
letter-spacing: 0.06em;
|
|
text-decoration: none;
|
|
text-shadow: 0 0 4px currentColor;
|
|
transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
|
|
}
|
|
.overflow-item:hover {
|
|
background: color-mix(in srgb, var(--purple) 8%, transparent);
|
|
border-color: var(--purple-dim);
|
|
}
|
|
.overflow-item-icon {
|
|
font-size: 1.05em;
|
|
width: 1.4em;
|
|
text-align: center;
|
|
flex-shrink: 0;
|
|
}
|
|
.overflow-item-rebuild { color: var(--amber); }
|
|
.overflow-item-new-session { color: var(--amber); }
|
|
.overflow-item-rebuild:hover,
|
|
.overflow-item-new-session:hover {
|
|
background: color-mix(in srgb, var(--amber) 10%, transparent);
|
|
border-color: var(--amber);
|
|
}
|
|
.overflow-item-dashboard { color: var(--cyan); }
|
|
.overflow-item-dashboard:hover {
|
|
background: color-mix(in srgb, var(--cyan) 10%, transparent);
|
|
border-color: var(--cyan);
|
|
}
|
|
.overflow-item:disabled {
|
|
opacity: 0.4;
|
|
cursor: progress;
|
|
}
|
|
.overflow-sep {
|
|
height: 1px;
|
|
background: var(--border);
|
|
margin: 0.3em 0.4em;
|
|
}
|
|
.overflow-section-label {
|
|
font-size: 0.72em;
|
|
letter-spacing: 0.1em;
|
|
color: var(--muted);
|
|
padding: 0 0.7em 0.1em;
|
|
text-transform: uppercase;
|
|
}
|
|
.overflow-item-model,
|
|
.overflow-item-effort {
|
|
color: var(--muted);
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
.overflow-item-model:hover,
|
|
.overflow-item-effort:hover {
|
|
color: var(--fg);
|
|
background: color-mix(in srgb, var(--purple) 8%, transparent);
|
|
border-color: var(--purple-dim);
|
|
}
|
|
.overflow-item-model.active,
|
|
.overflow-item-effort.active {
|
|
color: var(--purple);
|
|
border-color: var(--purple-dim);
|
|
background: color-mix(in srgb, var(--purple) 6%, transparent);
|
|
}
|
|
|
|
/* Header pill — inbox / loose-ends triggers. Compact, count-prominent.
|
|
Markup carries `hive-pill` alongside `header-pill` (shared pill.css
|
|
supplies border/radius/padding/font-size/letter-spacing; `display`
|
|
is the one thing overridden here, to `inline-flex`, for the icon+
|
|
label+count row layout — see pill.css's own note on that). */
|
|
.header-pill {
|
|
background: transparent;
|
|
border-color: var(--purple-dim);
|
|
color: var(--fg);
|
|
font-family: inherit;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.4em;
|
|
cursor: pointer;
|
|
transition: border-color 0.15s ease, box-shadow 0.15s ease, color 0.15s ease;
|
|
}
|
|
.header-pill:hover {
|
|
border-color: var(--purple);
|
|
color: var(--purple);
|
|
box-shadow: 0 0 10px -2px var(--purple);
|
|
}
|
|
.header-pill-icon { font-size: 1.05em; line-height: 1; }
|
|
.header-pill-label { color: var(--muted); }
|
|
.header-pill-count {
|
|
background: var(--purple-dim);
|
|
color: var(--purple);
|
|
border-radius: 999px;
|
|
padding: 0 0.5em;
|
|
min-width: 1.6em;
|
|
text-align: center;
|
|
font-weight: bold;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
.header-pill-inbox .header-pill-count {
|
|
background: color-mix(in srgb, var(--amber) 18%, transparent);
|
|
color: var(--amber);
|
|
}
|
|
.header-pill-todos .header-pill-count {
|
|
background: color-mix(in srgb, var(--green) 18%, transparent);
|
|
color: var(--green);
|
|
}
|
|
|
|
.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-status-overlay {
|
|
position: absolute;
|
|
top: calc(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 (covered now by
|
|
`.overflow-item-rebuild` in the overflow menu).
|
|
- `.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 was moved into the overflow menu (`.overflow-item-dashboard`
|
|
covers it now). */
|
|
.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 (`.overflow-item-new-session` covers it; the
|
|
`:disabled` opacity treatment lives on the shared
|
|
`.overflow-item:disabled` rule). */
|
|
/* 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-h) + 0.8em);
|
|
padding-bottom: calc(var(--agent-composer-h) + 0.8em);
|
|
scroll-padding-top: calc(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;
|
|
align-items: flex-start;
|
|
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, .term-input .submit-hint {
|
|
padding-top: 0.25em;
|
|
}
|
|
.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;
|
|
}
|