The frost variable was defined only in the removed flow stylesheet, so its fallback always applied. The messages page header no longer points at the flow page.
784 lines
20 KiB
CSS
784 lines
20 KiB
CSS
/* Dashboard-specific styles. common.css must be loaded first —
|
|
it defines the CSS custom properties, typography, and shared
|
|
component rules that this file depends on. */
|
|
|
|
/* SYST3M panel rules (rebuild queue / meta inputs / kept state)
|
|
live in system-sections.css. */
|
|
@import "./system-sections.css";
|
|
@import "@hive/shared/jobq-rollup.css";
|
|
@import "@hive/shared/jobq-graph.css";
|
|
|
|
/* ─── tabbed dashboard chrome ──────────────────────────────────────
|
|
Top-of-page sticky header with banner + tab strip. SSE stays
|
|
alive across tab switches so count pills update live on inactive
|
|
tabs. See docs/web-ui/dashboard.md::Chrome header + Tab strip for the
|
|
routing model. */
|
|
|
|
body.dashboard-shell {
|
|
/* Full-bleed body (no horizontal gutter). The 1.5em side gutter lives
|
|
on the inner `.page-content` wrapper (see common.css), so the sticky
|
|
chrome bar and the footer span the full
|
|
width edge-to-edge with no negative-margin breakout. `padding-bottom`
|
|
keeps a little breathing room above the viewport edge (overridden to
|
|
clear the selection bar via `.has-selection` below). No max-width cap
|
|
— wide screens use the full width rather than empty side margins. */
|
|
margin: 0;
|
|
padding-bottom: 1.5em;
|
|
}
|
|
|
|
.dashboard-chrome {
|
|
/* Pages menu + tab strip share one row (flex). The chrome owns the
|
|
horizontal gutter and the single full-width bottom divider; its
|
|
children drop their own side padding / border so they sit inline. */
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75em;
|
|
/* Stickiness lives on the `.sticky-top` wrapper (common.css) that
|
|
common.js wraps this chrome in. */
|
|
background: color-mix(in srgb, var(--bg) 86%, transparent);
|
|
-webkit-backdrop-filter: blur(8px) saturate(120%);
|
|
backdrop-filter: blur(8px) saturate(120%);
|
|
border-bottom: 1px solid var(--purple-dim);
|
|
padding: 0.3em 1em;
|
|
margin: 0 0 1em;
|
|
}
|
|
/* Pages menu (links-menu.js): one icon button at the left of the chrome
|
|
that opens a popover of the standalone pages and the swarm UI. Same
|
|
shape as the swarm UI's LinksMenu — quiet until hovered or open. The
|
|
explicit `[hidden]` rule is needed because `display: flex` on the
|
|
popover would otherwise beat the UA `[hidden]` style. */
|
|
.links-menu {
|
|
position: relative;
|
|
flex: none;
|
|
}
|
|
.links-menu-button {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 2em;
|
|
height: 2em;
|
|
padding: 0;
|
|
border: 1px solid transparent;
|
|
border-radius: 0.4em;
|
|
background: none;
|
|
color: var(--purple);
|
|
font-size: 1em;
|
|
line-height: 1;
|
|
cursor: pointer;
|
|
}
|
|
.links-menu-button:hover,
|
|
.links-menu-button[aria-expanded="true"] {
|
|
border-color: var(--border);
|
|
background: var(--bg-elev);
|
|
}
|
|
.links-menu-popover {
|
|
position: absolute;
|
|
top: calc(100% + 0.4em);
|
|
left: 0;
|
|
z-index: 10;
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-width: 10em;
|
|
padding: 0.4em;
|
|
border: 1px solid var(--border);
|
|
border-radius: 0.5em;
|
|
background: var(--bg-elev);
|
|
box-shadow: 0 0.25em 0.75em rgba(0, 0, 0, 0.3);
|
|
}
|
|
.links-menu-popover[hidden] {
|
|
display: none;
|
|
}
|
|
.links-menu-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5em;
|
|
padding: 0.4em 0.6em;
|
|
border-radius: 0.35em;
|
|
color: var(--fg);
|
|
text-decoration: none;
|
|
white-space: nowrap;
|
|
}
|
|
.links-menu-item:hover {
|
|
background: var(--bg);
|
|
}
|
|
.banner-thin {
|
|
text-align: center;
|
|
margin: 0;
|
|
padding: 0 1em;
|
|
font-size: 0.78em;
|
|
color: var(--purple-dim);
|
|
letter-spacing: 0.15em;
|
|
white-space: pre;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.tabbar {
|
|
display: flex;
|
|
flex-wrap: nowrap;
|
|
align-items: center;
|
|
gap: 0.2em;
|
|
/* On one row with the pages menu; the chrome owns the gutter +
|
|
the bottom divider, so no outer padding / border here. */
|
|
}
|
|
/* Shares the flat-pill aesthetic of the shared `.hive-tab` (sub-tabs on
|
|
the standalone pages) so the chrome reads as one family. Kept a touch
|
|
weightier than bare `.hive-tab` — larger font, bold labels, count
|
|
pills — since this is the dashboard's primary nav, not a sub-strip. */
|
|
.tabbar .tab {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.5em;
|
|
flex-shrink: 0;
|
|
padding: 0.4em 0.95em;
|
|
color: var(--muted);
|
|
font-family: inherit;
|
|
font-size: 0.92em;
|
|
letter-spacing: 0.06em;
|
|
text-decoration: none;
|
|
border: 0;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
transition:
|
|
color 0.15s ease,
|
|
background 0.15s ease;
|
|
}
|
|
.tabbar .tab:hover {
|
|
color: var(--fg);
|
|
background: var(--border);
|
|
}
|
|
.tabbar .tab.hive-tab--active {
|
|
color: var(--purple);
|
|
background: var(--border);
|
|
}
|
|
.tab-label {
|
|
font-weight: bold;
|
|
white-space: nowrap;
|
|
}
|
|
.tab-count {
|
|
display: inline-block;
|
|
background: var(--purple-dim);
|
|
color: var(--purple);
|
|
border-radius: 999px;
|
|
padding: 0 0.55em;
|
|
min-width: 1.6em;
|
|
text-align: center;
|
|
font-size: 0.82em;
|
|
font-variant-numeric: tabular-nums;
|
|
font-weight: bold;
|
|
}
|
|
/* `display` above beats the UA's `[hidden]` rule, so an empty count (the
|
|
4G3NTS and R3BU1LD QU3U3 pills at zero) would otherwise draw a "0". */
|
|
.tab-count[hidden] {
|
|
display: none;
|
|
}
|
|
|
|
/* Tab pane visibility — createTabStrip (@hive/shared/tabs.js) sets the
|
|
`hidden` attribute on every inactive pane (resolved from the URL hash;
|
|
default — no hash — is 4G3NTS). Only the active pane stays visible. */
|
|
.tab-pane[hidden] {
|
|
display: none;
|
|
}
|
|
|
|
.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)
|
|
);
|
|
}
|
|
.banner.active {
|
|
animation: banner-shimmer 1.8s linear infinite;
|
|
}
|
|
@keyframes banner-shimmer {
|
|
from {
|
|
background-position: 200% 0;
|
|
}
|
|
to {
|
|
background-position: -100% 0;
|
|
}
|
|
}
|
|
|
|
.role {
|
|
display: inline-block;
|
|
margin-left: 0.4em;
|
|
padding: 0.05em 0.5em;
|
|
border: 1px solid;
|
|
border-radius: 2px;
|
|
font-size: 0.8em;
|
|
letter-spacing: 0.1em;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
/* Container rows: a full-height square agent icon on the left, the
|
|
identity / actions / drill-in lines stacked in the card body on the
|
|
right. Pending rows dim everything except the pending indicator. */
|
|
.containers {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.4em;
|
|
}
|
|
.container-row {
|
|
padding: 0.6em 0.8em;
|
|
border: 1px solid var(--border);
|
|
border-radius: 4px;
|
|
background: color-mix(in srgb, var(--bg-elev) 55%, transparent);
|
|
transition:
|
|
opacity 200ms ease,
|
|
border-color 200ms ease;
|
|
}
|
|
.container-row:not(.tombstone) {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 0.7em;
|
|
}
|
|
.container-row:not(.tombstone) > .container-icon {
|
|
position: relative;
|
|
overflow: hidden;
|
|
flex: none;
|
|
width: 5em;
|
|
aspect-ratio: 1;
|
|
border-radius: 6px;
|
|
background-color: color-mix(in srgb, var(--crust) 60%, transparent);
|
|
cursor: pointer;
|
|
transition:
|
|
box-shadow 120ms ease,
|
|
transform 120ms ease;
|
|
}
|
|
.container-row:not(.tombstone) > .container-icon:hover {
|
|
box-shadow: 0 0 0 2px var(--purple);
|
|
}
|
|
.container-row:not(.tombstone) > .container-icon:focus-visible {
|
|
outline: 2px solid var(--purple);
|
|
outline-offset: 2px;
|
|
}
|
|
.container-row.selected {
|
|
border-color: var(--purple);
|
|
box-shadow: inset 0 0 0 1px var(--purple);
|
|
background: color-mix(in srgb, var(--purple) 6%, transparent);
|
|
}
|
|
.container-row.selected > .container-icon {
|
|
box-shadow:
|
|
0 0 0 2px var(--purple),
|
|
0 0 12px -4px var(--purple);
|
|
}
|
|
.container-row:not(.tombstone) > .container-icon > .container-icon-img {
|
|
position: absolute;
|
|
inset: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: contain;
|
|
}
|
|
.container-row:not(.tombstone) > .container-icon.icon-unreachable {
|
|
filter: grayscale(1);
|
|
opacity: 0.4;
|
|
}
|
|
.container-row .card-body {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
/* ── per-agent three-dot context menu ─────────────────────────────────────
|
|
Positioned after .card-body in the flex row. `<hive-agent-menu>`
|
|
(agent-menu/hive-agent-menu.js) is a light-DOM element with no shadow
|
|
root of its own — the generic dropdown mechanics (shadow attach,
|
|
open/close, positioning) live in the shared `<hive-menu>` component
|
|
instead (@hive/shared/hive-menu.js), which slots this element's
|
|
trigger/content nodes into position rather than moving them into its
|
|
own shadow root. That keeps these nodes in the ordinary light DOM the
|
|
whole way down, so this ordinary global stylesheet reaches them
|
|
directly — see hive-agent-menu.js's header for the full reasoning.
|
|
`hive-agent-menu` itself just needs the flex/alignment role the old
|
|
light-DOM `.agent-menu` wrapper div played in this row; the
|
|
`position: relative` an absolutely-positioned dropdown needs now lives
|
|
on `<hive-menu>`'s own `:host` instead, since that's the element whose
|
|
shadow tree the dropdown is actually positioned within. */
|
|
hive-agent-menu {
|
|
flex: none;
|
|
align-self: flex-start;
|
|
margin-top: 0.3em;
|
|
}
|
|
/* .agent-menu-btn carries no styling of its own anymore — the trigger is
|
|
a top-level `slot="trigger"` node inside <hive-menu>'s shadow tree, so
|
|
its base icon-button chrome now lives in hive-menu.css's
|
|
`::slotted([slot='trigger'])` rules instead (reachable and genuinely
|
|
generic — any <hive-menu> trigger gets the same treatment). The class
|
|
itself stays, just as an identification hook (aria-label/title
|
|
already carry the real semantics). */
|
|
/* Box chrome (background/border/radius/shadow/min-width/white-space) now
|
|
lives on <hive-menu>'s own `.menu-dropdown` wrapper (shadow-DOM-owned
|
|
markup, no slotting constraint) — this is just the `<ul>`'s own
|
|
list-reset + item padding, which only the caller (owning the actual
|
|
list markup) can set. */
|
|
.agent-menu-dropdown {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0.3em 0;
|
|
}
|
|
.agent-menu-item {
|
|
display: block;
|
|
width: 100%;
|
|
background: none;
|
|
border: none;
|
|
color: var(--fg);
|
|
font-family: inherit;
|
|
font-size: 0.88em;
|
|
letter-spacing: 0.04em;
|
|
text-align: left;
|
|
padding: 0.4em 0.9em;
|
|
cursor: pointer;
|
|
text-decoration: none;
|
|
box-sizing: border-box;
|
|
}
|
|
.agent-menu-item:hover {
|
|
background: var(--border);
|
|
color: var(--purple);
|
|
}
|
|
.agent-menu-sep {
|
|
height: 1px;
|
|
background: var(--purple-dim);
|
|
margin: 0.3em 0;
|
|
padding: 0;
|
|
}
|
|
/* Hover reveal: `--menu-btn-opacity` is forced to 1 while a hover
|
|
selector (or the trigger's own open state, set by <hive-menu>) applies
|
|
— inherits down through `hive-agent-menu` → `<hive-menu>` → the
|
|
trigger button same as any custom property, shadow trees included. */
|
|
.container-row:hover hive-agent-menu {
|
|
--menu-btn-opacity: 1;
|
|
}
|
|
/* Card actions dim while a row has any pending-state badge (transient-
|
|
driven only now — see agents.js's pending-badge derivation comment;
|
|
there is no separate queued-but-not-running row state to tell apart
|
|
from this one anymore). */
|
|
.container-row.pending-running .actions {
|
|
opacity: 0.4;
|
|
pointer-events: none;
|
|
}
|
|
.container-row.pending-running {
|
|
border-color: var(--amber);
|
|
background: color-mix(in srgb, var(--amber) 5%, transparent);
|
|
}
|
|
@keyframes container-icon-spin {
|
|
from {
|
|
transform: rotate(0deg);
|
|
}
|
|
to {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
.container-row.pending-running > .container-icon::after {
|
|
content: "";
|
|
position: absolute;
|
|
inset: 0;
|
|
border-radius: 50%;
|
|
border: 3px solid transparent;
|
|
border-top-color: var(--amber);
|
|
animation: container-icon-spin 0.9s linear infinite;
|
|
pointer-events: none;
|
|
}
|
|
.container-row .head {
|
|
display: flex;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
gap: 0.5em;
|
|
margin-bottom: 0.4em;
|
|
}
|
|
.container-row .head .name {
|
|
font-size: 1.05em;
|
|
font-weight: bold;
|
|
}
|
|
.container-row .head .meta {
|
|
margin-left: auto;
|
|
}
|
|
.container-row .head .nav-strip {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
gap: 0.35em;
|
|
}
|
|
.nav-link {
|
|
color: var(--muted);
|
|
font-size: 0.95em;
|
|
line-height: 1;
|
|
padding: 0.15em 0.35em;
|
|
border-radius: 3px;
|
|
text-decoration: none;
|
|
transition:
|
|
background 0.12s ease,
|
|
color 0.12s ease;
|
|
}
|
|
.nav-link:hover {
|
|
background: color-mix(in srgb, var(--purple) 12%, transparent);
|
|
color: var(--cyan);
|
|
text-shadow: 0 0 6px color-mix(in srgb, var(--cyan) 50%, transparent);
|
|
}
|
|
.container-row .actions {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.4em;
|
|
}
|
|
.container-row .actions form.inline {
|
|
display: inline-block;
|
|
margin: 0;
|
|
}
|
|
|
|
.agent-status {
|
|
font-size: 0.82em;
|
|
color: var(--subtext0);
|
|
padding: 0.1em 0.3em 0.25em;
|
|
/* Row layout: the message can wrap to two lines and clamp; the age
|
|
stamp is a non-shrinking sibling so it is always shown (it wraps to
|
|
the next line if there is no room, but is never clipped away). */
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: baseline;
|
|
column-gap: 0.3em;
|
|
}
|
|
.agent-status .status-msg {
|
|
flex: 0 1 auto;
|
|
min-width: 0;
|
|
/* Wrap to at most two lines, then ellipsize the overflow. */
|
|
display: -webkit-box;
|
|
-webkit-box-orient: vertical;
|
|
-webkit-line-clamp: 2;
|
|
line-clamp: 2;
|
|
overflow: hidden;
|
|
overflow-wrap: anywhere;
|
|
}
|
|
.agent-status .status-icon {
|
|
opacity: 0.65;
|
|
}
|
|
.agent-status .status-age {
|
|
flex: 0 0 auto;
|
|
opacity: 0.5;
|
|
font-size: 0.9em;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.container-row.tombstone {
|
|
border-style: dashed;
|
|
background: color-mix(in srgb, var(--bg-elev) 35%, transparent);
|
|
opacity: 0.85;
|
|
}
|
|
.container-row.tombstone .name {
|
|
color: var(--muted);
|
|
}
|
|
|
|
/* K3PT ST4T3 caveat and the port-collision banner both moved to the
|
|
shared <hive-warn> component — amber/no-pulse for
|
|
the standing caveat (a pulsing banner that's always present just
|
|
teaches you to stop seeing it), `severity="red" pulse` for the port
|
|
collision (an incident, not a caveat). See kept.js/agents.js and
|
|
hive-warn.css for the actual styling now. */
|
|
|
|
/* Build-queue summary banner on the 4G3NTS tab moved to the shared
|
|
`JobqRollup` Preact component (`.jqr-*` classes, imported above) —
|
|
mounted by agents.js::initJobqRollup into #jobq-rollup-section. */
|
|
|
|
.pending-state {
|
|
color: var(--amber);
|
|
font-size: 0.85em;
|
|
letter-spacing: 0.08em;
|
|
text-transform: uppercase;
|
|
text-shadow: 0 0 6px color-mix(in srgb, var(--amber) 55%, transparent);
|
|
animation: badge-pulse 1.6s ease-in-out infinite;
|
|
}
|
|
@keyframes badge-pulse {
|
|
0%,
|
|
100% {
|
|
opacity: 1;
|
|
}
|
|
50% {
|
|
opacity: 0.7;
|
|
}
|
|
}
|
|
|
|
/* Inline drill-in triggers (logs / config repo / view diff). */
|
|
.drill-ins {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.15em 1.1em;
|
|
margin-top: 0.4em;
|
|
}
|
|
.drill-ins .panel-trigger {
|
|
margin-top: 0;
|
|
}
|
|
|
|
ul form.inline {
|
|
display: inline-block;
|
|
}
|
|
|
|
.role-pending {
|
|
color: var(--amber);
|
|
border-color: var(--amber);
|
|
}
|
|
.btn-inline {
|
|
font-family: inherit;
|
|
background: transparent;
|
|
cursor: pointer;
|
|
margin-left: 0.4em;
|
|
}
|
|
/* Off-palette warm-amber hover tint: not an exact theme var (brighter than
|
|
--amber), left as a literal pending a dedicated named var. */
|
|
.btn-inline:hover {
|
|
background: rgba(255, 184, 77, 0.1);
|
|
}
|
|
.kind {
|
|
display: inline-block;
|
|
margin-left: 0.4em;
|
|
padding: 0.05em 0.5em;
|
|
border: 1px solid var(--purple-dim);
|
|
color: var(--purple-dim);
|
|
border-radius: 2px;
|
|
font-size: 0.75em;
|
|
letter-spacing: 0.1em;
|
|
text-transform: uppercase;
|
|
}
|
|
details {
|
|
margin-top: 0.5em;
|
|
}
|
|
summary {
|
|
cursor: pointer;
|
|
color: var(--muted);
|
|
font-size: 0.85em;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.1em;
|
|
}
|
|
summary:hover {
|
|
color: var(--purple);
|
|
}
|
|
.diff {
|
|
background: var(--bg-elev);
|
|
border: 1px solid var(--border);
|
|
padding: 0.8em;
|
|
margin-top: 0.4em;
|
|
overflow-x: auto;
|
|
font-size: 0.85em;
|
|
line-height: 1.4;
|
|
color: var(--muted);
|
|
white-space: pre;
|
|
}
|
|
.diff span {
|
|
display: block;
|
|
}
|
|
.diff .diff-add {
|
|
color: var(--green);
|
|
}
|
|
.diff .diff-del {
|
|
color: var(--red);
|
|
}
|
|
.diff .diff-hunk {
|
|
color: var(--cyan);
|
|
}
|
|
.diff .diff-file {
|
|
color: var(--purple);
|
|
font-weight: bold;
|
|
}
|
|
.diff .diff-ctx {
|
|
color: var(--fg);
|
|
}
|
|
|
|
footer {
|
|
margin-top: 4em;
|
|
text-align: center;
|
|
color: var(--muted);
|
|
font-size: 0.9em;
|
|
}
|
|
footer a {
|
|
color: var(--purple);
|
|
}
|
|
footer .banner-thin {
|
|
margin-bottom: 0.8em;
|
|
}
|
|
|
|
/* Selection bar — sticky-bottom strip for bulk actions. */
|
|
.selection-bar {
|
|
position: fixed;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
z-index: 40;
|
|
display: flex;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
gap: 0.6em;
|
|
padding: 0.55em 1em;
|
|
background: color-mix(in srgb, var(--bg) 74%, transparent);
|
|
-webkit-backdrop-filter: blur(12px) saturate(140%);
|
|
backdrop-filter: blur(12px) saturate(140%);
|
|
border-top: 1px solid var(--purple);
|
|
box-shadow: 0 -6px 18px rgba(0, 0, 0, 0.4);
|
|
}
|
|
.selection-bar[hidden] {
|
|
display: none;
|
|
}
|
|
.selection-count {
|
|
color: var(--purple);
|
|
font-weight: bold;
|
|
letter-spacing: 0.05em;
|
|
}
|
|
.selection-names {
|
|
color: var(--muted);
|
|
font-size: 0.85em;
|
|
flex: 1 1 auto;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
.selection-actions {
|
|
display: inline-flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.4em;
|
|
align-items: center;
|
|
}
|
|
.selection-actions .btn {
|
|
font-size: 0.75em;
|
|
padding: 0.2em 0.7em;
|
|
}
|
|
.selection-clear {
|
|
color: var(--muted);
|
|
border-color: var(--purple-dim);
|
|
font-size: 0.75em;
|
|
padding: 0.2em 0.7em;
|
|
}
|
|
/* Pad the dashboard body so the sticky bar doesn't cover the
|
|
bottom of the agent list. Only applies when bar is visible. */
|
|
body.dashboard-shell.has-selection {
|
|
padding-bottom: 4.5em;
|
|
}
|
|
|
|
/* ST4TS moved to its own page (`/stats.html`): the
|
|
window selector / summary chips / bars live in stats.css, and the
|
|
shared `.hive-stats-table` moved to common.css. */
|
|
|
|
/* ─── build tabs (builds.js) ────────────────────────────────────────
|
|
R3BU1LD QU3U3 and M3T4 1NPUTS are styled by system-sections.css and
|
|
jobq-graph.css; the BUILD L0GS list rules are below. */
|
|
|
|
.logs-toolbar {
|
|
display: flex;
|
|
gap: 0.6em;
|
|
align-items: center;
|
|
margin-bottom: 0.9em;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
/* ─── build-log list ──────────────────────────────────────────────
|
|
Scrollable list of log header rows; clicking a row expands full
|
|
stdout+stderr inline. */
|
|
.build-logs-list {
|
|
list-style: none;
|
|
padding: 0;
|
|
margin: 0;
|
|
overflow-y: auto;
|
|
flex: 1 1 0;
|
|
min-height: 0;
|
|
}
|
|
.build-logs-item {
|
|
border-bottom: 1px solid var(--border);
|
|
padding: 0.15em 0;
|
|
}
|
|
.build-logs-row-btn {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.4em;
|
|
align-items: baseline;
|
|
width: 100%;
|
|
background: none;
|
|
border: none;
|
|
color: var(--fg);
|
|
font-family: inherit;
|
|
font-size: 0.85em;
|
|
text-align: left;
|
|
padding: 0.25em 0.4em;
|
|
cursor: pointer;
|
|
border-radius: 3px;
|
|
}
|
|
.build-logs-row-btn:hover {
|
|
background: var(--bg-elev);
|
|
}
|
|
.build-logs-kind {
|
|
font-weight: 600;
|
|
}
|
|
.build-logs-age {
|
|
font-size: 0.88em;
|
|
}
|
|
.build-logs-cmdline {
|
|
color: var(--muted);
|
|
font-size: 0.82em;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
max-width: 40em;
|
|
}
|
|
.build-logs-detail {
|
|
padding: 0 0.4em 0.4em;
|
|
}
|
|
.build-logs-output {
|
|
margin: 0.3em 0 0;
|
|
padding: 0.4em 0.6em;
|
|
background: var(--crust);
|
|
color: var(--fg);
|
|
border: 1px solid var(--purple-dim);
|
|
font-size: 0.82em;
|
|
line-height: 1.4;
|
|
white-space: pre-wrap;
|
|
word-break: break-word;
|
|
overflow-x: auto;
|
|
max-height: 30em;
|
|
overflow-y: auto;
|
|
}
|
|
.build-logs-dl {
|
|
color: var(--muted);
|
|
font-size: 0.8em;
|
|
text-decoration: none;
|
|
border: 1px solid var(--purple-dim);
|
|
padding: 0.1em 0.4em;
|
|
border-radius: 2px;
|
|
margin-top: 0.3em;
|
|
display: none;
|
|
}
|
|
.build-logs-dl:not([hidden]) {
|
|
display: inline-block;
|
|
}
|
|
.build-logs-dl:hover {
|
|
color: var(--fg);
|
|
border-color: var(--purple-dim);
|
|
}
|
|
|
|
.build-logs-live-badge {
|
|
margin-bottom: 0.4em;
|
|
}
|
|
|
|
@keyframes live-pulse {
|
|
0%,
|
|
100% {
|
|
opacity: 1;
|
|
}
|
|
50% {
|
|
opacity: 0.45;
|
|
}
|
|
}
|
|
.build-logs-live-badge.badge-running {
|
|
animation: live-pulse 1.4s ease-in-out infinite;
|
|
}
|
|
|
|
.build-logs-runtime {
|
|
font-size: 0.85em;
|
|
color: var(--muted);
|
|
}
|