Per mara's review: instead of measuring the banner height in JS and offsetting the chrome's sticky top, put the warning banner and the page chrome in the same sticky div so they stack naturally. common.js builds the wrapper: ensureStickyTop() wraps the page's chrome (.dashboard-chrome / .page-header) in a single .sticky-top container and injects the warning banner as its first child. The banner and the chrome are no longer individually sticky — .sticky-top owns the stickiness, so they pin together in one context instead of two top:0 stickies colliding (the banner used to overlay the tab bar). Pages without a chrome (the H0M3 hub) get a banner-only sticky region. No per-page markup needed; no JS height measurement. Build green.
573 lines
20 KiB
CSS
573 lines
20 KiB
CSS
/* 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-reminder {
|
||
color: var(--cyan); border-color: var(--cyan);
|
||
text-shadow: 0 0 6px color-mix(in srgb, var(--cyan) 40%, transparent);
|
||
}
|
||
.badge-loose-ends {
|
||
color: var(--purple); border-color: var(--purple);
|
||
text-shadow: 0 0 6px color-mix(in srgb, var(--purple) 40%, transparent);
|
||
}
|
||
/* 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;
|
||
}
|