hyperhive/frontend/packages/dashboard/src/common.css
iris d79df3883d frontend: shadow-DOM upgrade for hive-dialog/hive-toast, drop global modal.css
Follow-up to the light-DOM custom-elements pilot (mara: 'that one landed
and works. i dont really like the css still being shared - id like that
to be split by component, with common stuff via @include').

<hive-dialog> and <hive-toast> now attach a shadow root and adopt a
component-scoped CSSStyleSheet built from a template-string constant in
modal.js (DIALOG_CSS / TOAST_CSS), plus a new shared/src/component-
styles.js sheet (currently just .btn) adopted alongside it via
adoptedStyleSheets -- the native equivalent of a Sass @include, no
preprocessor added. Theme vars keep resolving through the shadow
boundary since CSS custom properties inherit across it; only plain
class rules needed the explicit move.

Deleted the global shared/src/modal.css entirely and dropped its
@import from both dashboard/common.css and agent/agent.css -- nothing
outside modal.js renders the old .tc-* classes any more. The <hive-
dialog> element is now the backdrop itself (:host carries the fixed-
position/centering rules that used to be .tc-backdrop on a light-DOM
div); box/title/message/content/actions all render inside its shadow
root. <hive-toast> similarly styles :host directly instead of a light-
DOM div, with the message text placed straight into the shadow root
(no <slot> needed since there's no external light-DOM content to
project).

Public API (openDialog/themedConfirm/themedPrompt/themedToast)
unchanged -- no call-site changes needed anywhere in dashboard/agent.

Verified with a full frontend build; nix fmt clean.
2026-07-29 12:55:54 +02:00

580 lines
20 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* Shared Catppuccin palette + body typography + terminal pane styles.
Bundled in front of the page-specific rules via esbuild. */
@import "@hive/shared/base.css";
@import "@hive/shared/terminal.css";
@import "@hive/shared/tabs.css";
@import "@hive/shared/chrome.css";
/* ─── global typography ─────────────────────────────────────────────
Element-level rules shared across all three pages (index, flow,
logs). These must not reference page-specific chrome classes. */
h1, h2 {
color: var(--purple);
text-transform: uppercase;
letter-spacing: 0.15em;
margin-top: 2em;
text-shadow: 0 0 8px color-mix(in srgb, var(--purple) 40%, transparent);
}
.divider {
color: var(--purple-dim);
overflow: hidden;
white-space: nowrap;
margin-bottom: 0.5em;
}
ul { list-style: none; padding-left: 0; }
li { padding: 0.5em 0; }
.glyph { color: var(--purple); margin-right: 0.5em; }
a {
color: var(--cyan);
text-decoration: none;
font-weight: bold;
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);
}
code {
color: var(--amber);
background: var(--bg-elev);
padding: 0.1em 0.4em;
border: 1px solid var(--border);
border-radius: 2px;
font-size: 0.9em;
}
/* ─── shared inline labels ──────────────────────────────────────────
.meta / .id / .agent / .empty appear in message rows, build-log
toolbars, and side-panel content rendered by common.js. */
.meta { color: var(--muted); font-size: 0.85em; margin-left: 0.4em; }
.id { color: var(--pink); font-weight: bold; margin-right: 0.4em; }
.agent { color: var(--amber); font-weight: bold; margin-right: 0.6em; }
.empty { color: var(--muted); font-style: italic; }
/* ─── status badges ─────────────────────────────────────────────────
.badge base + semantic colour variants. Used on container rows
(tabs.js) and build-log rows (logs.js). */
.badge {
display: inline-block;
padding: 0.05em 0.5em;
border: 1px solid;
border-radius: 2px;
font-size: 0.75em;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.badge-warn {
color: var(--amber); border-color: var(--amber);
text-shadow: 0 0 6px color-mix(in srgb, var(--amber) 50%, transparent);
}
.badge-rate-limited {
color: var(--red); border-color: var(--red);
text-shadow: 0 0 6px color-mix(in srgb, var(--red) 50%, transparent);
}
.badge-muted {
color: var(--muted); border-color: var(--purple-dim);
background: color-mix(in srgb, var(--muted) 8%, transparent);
}
.badge-loose-ends {
color: var(--purple); border-color: var(--purple);
text-shadow: 0 0 6px color-mix(in srgb, var(--purple) 40%, transparent);
}
/* Paused agent: turn loop parked, container still up. Clickable (btn-inline):
clicking sends POST /api/resume/{name} so the badge doubles as a resume button. */
.badge-paused {
color: var(--yellow); border-color: var(--yellow);
background: color-mix(in srgb, var(--yellow) 10%, transparent);
}
/* Active Claude model badge on dashboard container rows. */
.badge-model {
color: var(--blue); border-color: var(--blue);
opacity: 0.8;
}
/* Context-window usage badges on dashboard container rows. */
.badge-ctx-ok {
color: var(--green); border-color: var(--green);
opacity: 0.85;
}
.badge-ctx-caution {
color: var(--amber); border-color: var(--amber);
text-shadow: 0 0 6px color-mix(in srgb, var(--amber) 50%, transparent);
}
.badge-ctx-warn {
color: var(--red); border-color: var(--red);
text-shadow: 0 0 6px color-mix(in srgb, var(--red) 50%, transparent);
}
.badge-ok { background: color-mix(in srgb, var(--green) 12%, transparent); color: var(--green); border-color: var(--green); }
.badge-fail { background: color-mix(in srgb, var(--red) 12%, transparent); color: var(--red); border-color: var(--red); }
.badge-running { background: color-mix(in srgb, var(--amber) 12%, transparent); color: var(--amber); border-color: var(--amber); }
/* ─── buttons ───────────────────────────────────────────────────────
.btn base + semantic colour/size modifiers. Logs page uses
.btn-restart for the ↻ refresh buttons; dashboard uses all variants. */
.btn {
font-family: inherit;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 0.1em;
background: transparent;
/* Strip browser UA button chrome (solid fill, system text colour).
Without this, native <button> + <input type="submit"> elements
ignore our background:transparent and colour in some browsers. */
-webkit-appearance: none;
appearance: none;
border: 1px solid;
padding: 0.25em 0.8em;
cursor: pointer;
text-shadow: 0 0 4px currentColor;
box-shadow: 0 0 0 0 currentColor;
transition: box-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:disabled,
.btn[disabled] {
opacity: 0.32;
cursor: not-allowed;
text-shadow: none;
box-shadow: none;
}
.btn:disabled:hover,
.btn[disabled]:hover {
background: transparent;
text-shadow: none;
box-shadow: none;
}
.btn-approve { color: var(--green); border-color: var(--green); }
.btn-deny { color: var(--red); border-color: var(--red); }
.btn-destroy { color: var(--red); border-color: var(--red); font-size: 0.75em; padding: 0.15em 0.5em; margin-left: 0.6em; }
.btn-rebuild { color: var(--amber); border-color: var(--amber); font-size: 0.75em; padding: 0.15em 0.5em; margin-left: 0.6em; }
.btn-restart { color: var(--cyan); border-color: var(--cyan); font-size: 0.75em; padding: 0.15em 0.5em; margin-left: 0.6em; }
.btn-stop { color: var(--pink); border-color: var(--pink); font-size: 0.75em; padding: 0.15em 0.5em; margin-left: 0.6em; }
.btn-start { color: var(--green); border-color: var(--green); font-size: 0.75em; padding: 0.15em 0.5em; margin-left: 0.6em; }
/* M0V3 affordance (selection bar) — mauve reads as "structural
change" rather than the destructive red / amber chrome of
destroy / rebuild. See docs/web-ui.md::Selection bar. */
.btn-move { color: var(--purple); border-color: var(--purple); font-size: 0.75em; padding: 0.15em 0.5em; margin-left: 0.6em; }
.btn-talk { color: var(--cyan); border-color: var(--cyan); }
.btn-spawn { color: var(--amber); border-color: var(--amber); }
.btn-fire-now { color: var(--purple); border-color: var(--purple); }
/* Post-fire flash: report renders directly on the button for ~1.5s
so the operator sees ok/failed/missing/consumed counts inline
without a modal. Green when at least one ok; muted otherwise. */
.btn-fire-now-flashed {
color: var(--green);
border-color: var(--green);
text-shadow: 0 0 6px currentColor;
box-shadow: 0 0 8px -2px currentColor;
}
/* Inline edit button on each schedule row. Yellow reads as a
parallel destructive-adjacent action (edit changes state, but
isn't deletion). */
.btn-edit-schedule { color: var(--yellow); border-color: var(--yellow); }
/* ─── file-preview / diff panel (common.js Panel) ───────────────────
Side-panel content rendered by openFilePanel / buildTabbedPreview
in common.js; all three pages load the Panel singleton. */
.diff-panel { display: flex; flex-direction: column; gap: 0.6em; }
.diff-base-tabs { display: flex; flex-wrap: wrap; gap: 0.4em; }
.diff-base-tab {
background: transparent;
border: 1px solid var(--border);
color: var(--muted);
font: inherit;
font-size: 0.85em;
padding: 0.2em 0.7em;
cursor: pointer;
}
.diff-base-tab:hover { color: var(--fg); }
.diff-base-tab.active {
color: var(--purple);
border-color: var(--purple);
background: color-mix(in srgb, var(--purple) 8%, transparent);
}
/* Image / tabbed file preview */
.preview-host { margin-top: 0.5em; }
.img-preview {
display: block;
max-width: 100%;
height: auto;
margin: 0 auto;
border: 1px solid var(--border);
border-radius: 4px;
/* checkerboard so transparent regions of the image read clearly */
background: repeating-conic-gradient(var(--border) 0% 25%, var(--bg) 0% 50%) 50% / 18px 18px;
}
/* Path linkification — agents drop pointer strings into messages;
clicking the anchor opens the file in the side panel. */
.path-link {
color: var(--blue);
text-decoration: underline dotted;
cursor: pointer;
}
.path-link:hover { color: var(--amber); }
/* File-preview body — rendered inside the side panel. */
.path-preview-body {
background: var(--bg);
border: 1px solid var(--border);
padding: 0.5em 0.7em;
margin: 0;
white-space: pre-wrap;
word-break: break-word;
font-size: 0.85em;
color: var(--fg);
}
/* page chrome (.page-header / .page-back / .page-title — the standalone
pages' sticky back-link bar) moved to @hive/shared/chrome.css, imported
at the top of this file. */
/* ─── operator inbox + message rows ────────────────────────────────
.inbox and .msg-* are rendered by common.js (renderInbox) and
flow.js — both the dashboard side panel and the /flow.html page
display broker messages. */
.inbox {
background: var(--bg-elev);
border: 1px solid var(--border);
padding: 0.5em 0.8em;
/* No max-height cap — let the inbox grow to fill the
side-panel-body which already scrolls. */
}
.inbox li {
padding: 0.25em 0;
border-bottom: 1px solid var(--border);
display: grid;
grid-template-columns: auto auto auto 1fr;
gap: 0.5em;
align-items: baseline;
}
.inbox li:last-child { border-bottom: 0; }
.inbox .msg-ts { color: var(--muted); font-size: 0.85em; }
.inbox .msg-from { color: var(--amber); }
.inbox .msg-sep { color: var(--muted); }
.inbox .msg-body { color: var(--fg); white-space: pre-wrap; word-break: break-word; }
/* `#msgflow` is a shared `.live` pane inside `.terminal-wrap`. The
msgrow / msg-* rules below power both the dashboard CALL tab and
the /flow.html full-page terminal. */
.live .msgrow {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 0.5em;
padding: 0.1em 0;
text-indent: 0;
}
.live .msgrow .msg-body {
flex: 1 1 100%;
min-width: 0;
}
.live .msgrow.sent .msg-arrow { color: var(--cyan); }
.live .msgrow.delivered .msg-arrow { color: var(--green); }
/* Reply-thread rendering: indented border-left + muted reply tag. */
.live .msgrow.msg-reply {
padding-left: 1.2em;
border-left: 2px solid var(--border);
margin-left: 0.6em;
}
.msg-reply-tag {
color: var(--muted);
font-size: 0.8em;
white-space: nowrap;
order: -1;
}
.msg-reply-tag a {
color: var(--muted);
text-shadow: none;
font-weight: normal;
}
.msg-reply-tag a:hover { color: var(--fg); }
@keyframes msg-highlight-fade {
from { background: color-mix(in srgb, var(--purple) 18%, transparent); }
to { background: transparent; }
}
.msg-highlight { animation: msg-highlight-fade 1.5s ease-out forwards; }
.msg-ts { color: var(--muted); font-size: 0.85em; }
.msg-arrow { font-weight: bold; }
.msg-from { color: var(--amber); }
.msg-sep { color: var(--muted); }
.msg-to { color: var(--pink); }
.msg-body { color: var(--fg); white-space: pre-wrap; word-break: break-word; }
/* ─── operator compose box ──────────────────────────────────────────
Sits inside `.terminal-wrap` on both /flow.html and the dashboard
CALL tab. Dashed top separator mirrors the agent terminal prompt. */
.op-compose {
position: relative;
display: flex;
align-items: flex-start;
gap: 0.6em;
padding: 0.55em 0.8em;
border-top: 1px dashed var(--purple-dim);
}
.op-compose-prompt {
color: var(--purple);
text-shadow: 0 0 4px currentColor;
font-weight: bold;
white-space: nowrap;
user-select: none;
padding-top: 0.15em;
}
.op-compose-input {
flex: 1;
background: transparent;
color: var(--fg);
border: none;
font-family: inherit;
font-size: 1em;
line-height: 1.4;
resize: none;
overflow: hidden;
max-height: 8em;
padding: 0;
}
.op-compose-input:focus { outline: none; }
.op-compose-input::placeholder { color: var(--muted); }
.op-compose-suggest {
position: absolute;
left: 3em;
bottom: 100%;
z-index: 50;
background: var(--bg-elev);
border: 1px solid var(--purple-dim);
border-radius: 4px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.45);
list-style: none;
margin: 0;
padding: 0.3em 0;
min-width: 12em;
max-height: 14em;
overflow-y: auto;
}
.op-compose-suggest .item {
padding: 0.35em 0.9em;
cursor: pointer;
font-size: 0.9em;
color: var(--muted);
}
.op-compose-suggest .item.active,
.op-compose-suggest .item:hover {
background: var(--border);
color: var(--purple);
}
/* ─── side panel ─────────────────────────────────────────────────
Long content (file previews, diffs, journald, applied config)
opens in a drawer that swipes in from the right — used by both
/flow.html and the dashboard. */
.side-panel {
position: fixed;
inset: 0;
z-index: 200;
pointer-events: none;
}
.side-panel-backdrop {
position: absolute;
inset: 0;
background: color-mix(in srgb, var(--crust) 55%, transparent);
opacity: 0;
transition: opacity 200ms ease;
}
.side-panel-drawer {
position: absolute;
top: 0;
right: 0;
bottom: 0;
width: min(42em, 95vw);
background: var(--bg-elev);
border-left: 1px solid var(--purple-dim);
display: flex;
flex-direction: column;
transform: translateX(100%);
transition: transform 220ms ease;
overflow: hidden;
}
.side-panel.open { pointer-events: auto; }
.side-panel.open .side-panel-backdrop { opacity: 1; }
.side-panel.open .side-panel-drawer { transform: translateX(0); }
.side-panel-resize {
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 6px;
cursor: ew-resize;
opacity: 0;
transition: opacity 120ms;
background: var(--purple-dim);
}
.side-panel-resize:hover,
body.side-panel-resizing .side-panel-resize {
opacity: 1;
}
body.side-panel-resizing .side-panel-resize {
background: var(--purple);
}
body.side-panel-resizing {
user-select: none;
}
body.side-panel-resizing * { cursor: ew-resize !important; }
.side-panel-head {
display: flex;
align-items: center;
gap: 0.6em;
padding: 0.6em 1em;
border-bottom: 1px solid var(--border);
flex: none;
}
.side-panel-title {
flex: 1;
font-size: 0.88em;
color: var(--muted);
letter-spacing: 0.04em;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.side-panel-close {
display: flex;
align-items: center;
justify-content: center;
background: none;
border: 1px solid var(--border);
border-radius: 3px;
color: var(--muted);
font-size: 1em;
line-height: 1;
padding: 0.15em 0.4em;
cursor: pointer;
transition: border-color 0.15s ease, color 0.15s ease;
}
.side-panel-close:hover { border-color: var(--red); color: var(--red); }
.side-panel-body {
flex: 1;
overflow-y: auto;
padding: 0.8em 1em;
font-size: 0.88em;
}
.side-panel-body .md { color: var(--fg); line-height: 1.5; }
.side-panel-body .md > :first-child { margin-top: 0; }
.side-panel-body .md > :last-child { margin-bottom: 0; }
.side-panel-body .md p { margin: 0.5em 0; }
.side-panel-body .md h1,
.side-panel-body .md h2,
.side-panel-body .md h3,
.side-panel-body .md h4 { color: var(--purple); margin: 0.9em 0 0.4em; }
.side-panel-body .md code {
background: var(--bg);
border: 1px solid var(--border);
border-radius: 2px;
padding: 0.1em 0.3em;
font-size: 0.9em;
}
.side-panel-body .md pre {
background: var(--crust);
border: 1px solid var(--border);
padding: 0.5em 0.7em;
overflow-x: auto;
margin: 0.5em 0;
}
.side-panel-body .md pre code { background: none; border: none; padding: 0; }
.side-panel-body .md a { color: var(--cyan); }
.side-panel-body .md ul,
.side-panel-body .md ol { margin: 0.4em 0; padding-left: 1.5em; }
.side-panel-body .md blockquote {
border-left: 3px solid var(--purple-dim);
padding-left: 0.8em;
margin: 0.4em 0;
color: var(--muted);
}
.side-panel-body .md table { border-collapse: collapse; margin: 0.5em 0; }
.side-panel-body .md th,
.side-panel-body .md td {
border: 1px solid var(--border);
padding: 0.2em 0.5em;
}
/* ─── page-content gutter ─────────────────────────────────────────────
Inverted-gutter layout: the dashboard + H0M3 keep `<body>` full-bleed
and wrap their padded content in a single `.page-content` div that
carries the horizontal gutter. Full-width chrome (the tab strip, the
server-warnings banner, the footer) then spans edge-to-edge for free,
with no negative-margin breakout hacks. Only matches on pages that opt
in by adding the wrapper (dashboard.html, index.html); FL0W / L0GS /
ST4TS / S3TT1NGS are full-bleed with their own `.<page>-main` padding. */
.page-content { padding: 0 1.5em; }
/* ─── sticky top region ───────────────────────────────────────────────
One sticky container per page holding the warning banner above the
page chrome (tab bar / page header). common.js (ensureStickyTop)
wraps the page's chrome in this on first render and injects the banner
as its first child, so the banner + chrome stack in a single sticky
context — pinned together — instead of two separate `top:0` stickies
colliding (the banner used to overlay the tab bar). */
.sticky-top {
position: sticky;
top: 0;
z-index: 50;
}
/* ─── server warnings banner ──────────────────────────────────────────
Generic top-of-page strip, injected as the first child of `.sticky-top`
by common.js (renderServerWarnings) on every page. One row per warning;
colour comes from the per-warning `level` (warn = amber, crit = red).
Stickiness lives on the `.sticky-top` wrapper, not here. The bar spans
the full width edge-to-edge; the dashboard + H0M3 are full-bleed at the
body level (their 1.5em gutter lives on an inner `.page-content`
wrapper), so the banner is full-width for free. */
.server-warnings[hidden] { display: none; }
.server-warn {
text-align: center;
padding: 0.4em 1em;
font-size: 0.85em;
font-weight: bold;
letter-spacing: 0.02em;
}
/* Tint composited over the opaque elevated-surface grey (not transparent):
the bar is `position: sticky`, so a see-through background would let
page content scroll through behind it. */
.server-warn-warn {
color: var(--amber);
background: color-mix(in srgb, var(--amber) 16%, var(--bg-elev));
border-bottom: 1px solid var(--amber);
}
.server-warn-crit {
color: var(--red);
background: color-mix(in srgb, var(--red) 18%, var(--bg-elev));
border-bottom: 1px solid var(--red);
}
/* ─── stats table ─────────────────────────────────────────────────
Shared right-aligned numeric table. Used by /stats.html (busiest
agents) and the dashboard SYST3M C0NT41N3R L04D table — moved here
from dashboard.css when ST4TS became its own page. */
.hive-stats-table {
width: 100%;
border-collapse: collapse;
font-size: 0.85rem;
margin-bottom: 8px;
}
.hive-stats-table th,
.hive-stats-table td {
border-bottom: 1px solid var(--border);
padding: 5px 8px;
text-align: right;
}
.hive-stats-table th:first-child,
.hive-stats-table td:first-child {
text-align: left;
}
.hive-stats-table th {
color: var(--muted);
font-weight: normal;
}
.hive-stats-table td.num {
font-variant-numeric: tabular-nums;
}