/* 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"; /* ─── 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: rgba(30, 30, 46, 0.72); --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 rgba(203, 166, 247, 0.45)); } 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%, rgba(203, 166, 247, 0.06) 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 rgba(203, 166, 247, 0.4); } .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 rgba(203, 166, 247, 0.4); 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 rgba(137, 220, 235, 0.4); transition: color 0.15s ease, text-shadow 0.15s ease, background 0.15s ease; } .agent-nav-link:hover { color: var(--fg); background: rgba(137, 220, 235, 0.08); text-shadow: 0 0 10px rgba(137, 220, 235, 0.85); } /* 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: rgba(203, 166, 247, 0.08); 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: rgba(250, 179, 135, 0.1); border-color: var(--amber); } .overflow-item-dashboard { color: var(--cyan); } .overflow-item-dashboard:hover { background: rgba(137, 220, 235, 0.1); 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 { color: var(--muted); font-variant-numeric: tabular-nums; } .overflow-item-model:hover { color: var(--fg); background: rgba(203, 166, 247, 0.08); border-color: var(--purple-dim); } .overflow-item-model.active { color: var(--purple); border-color: var(--purple-dim); background: rgba(203, 166, 247, 0.06); } /* Header pill — inbox / loose-ends triggers. Compact, count-prominent. */ .header-pill { background: transparent; border: 1px solid var(--purple-dim); color: var(--fg); font-family: inherit; font-size: 0.85em; letter-spacing: 0.04em; border-radius: 999px; padding: 0.25em 0.7em; 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: rgba(250, 179, 135, 0.18); color: var(--amber); } .header-pill-loose .header-pill-count { background: rgba(243, 139, 168, 0.18); color: var(--red); } .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 rgba(166, 227, 161, 0.55); } .status-needs-login { color: var(--amber); text-shadow: 0 0 6px rgba(250, 179, 135, 0.55); } code { background: rgba(203, 166, 247, 0.12); padding: 0.05em 0.3em; border-radius: 2px; } a { color: var(--cyan); text-shadow: 0 0 4px rgba(137, 220, 235, 0.5); } a:hover { color: var(--fg); text-shadow: 0 0 12px rgba(137, 220, 235, 0.9); } .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: rgba(205, 214, 244, 0.06); 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; } .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); } .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; } .agent-inbox .answer-form { /* Block-level under the new layout — `grid-column: 1 / -1` was for the legacy grid; under block layout the form naturally starts on its own row. */ display: flex; gap: 0.4em; align-items: flex-start; margin-top: 0.5em; padding-left: 0.8em; } .agent-inbox .answer-form textarea { flex: 1; font-family: inherit; font-size: inherit; background: var(--bg); color: var(--fg); border: 1px solid var(--border); border-radius: 3px; padding: 0.3em; resize: vertical; } .agent-inbox .answer-form button { 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 .answer-form button:hover:not(:disabled) { border-color: var(--purple); color: var(--purple); } .agent-inbox .answer-form button:disabled { opacity: 0.5; cursor: default; } .agent-inbox .answer-status { color: var(--muted); align-self: center; } /* Inline answer slot mounted under each `ask → operator` row in the live terminal stream. Mirrors the side-panel `.answer-form` look-and-feel — same textarea, same send button — so the operator doesn't have to context-switch between "answering in the panel" and "answering inline". Empty slot collapses to nothing (no margin) so pre-bind rows stay tidy; populated slot gets a thin top divider to separate the question body from the form. A `[resolved]` tag (neutral, covers answered / cancelled / TTL-expired uniformly) replaces the form once the question leaves the pending list. */ .live .ask-answer-inline-slot:empty { display: none; } .live .ask-answer-inline-slot { margin-top: 0.5em; padding-top: 0.5em; border-top: 1px dashed var(--border); } .live .ask-answer-inline-slot .answer-form { display: flex; gap: 0.4em; align-items: flex-start; } .live .ask-answer-inline-slot .answer-form textarea { flex: 1; font-family: inherit; font-size: inherit; background: var(--bg); color: var(--fg); border: 1px solid var(--border); border-radius: 3px; padding: 0.3em; resize: vertical; } .live .ask-answer-inline-slot .answer-form button { 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; } .live .ask-answer-inline-slot .answer-form button:hover:not(:disabled) { border-color: var(--purple); color: var(--purple); } .live .ask-answer-inline-slot .answer-form button:disabled { opacity: 0.5; cursor: default; } .live .ask-answer-inline-slot .answer-status { color: var(--muted); align-self: center; } .live .ask-answer-inline-slot .ask-resolved-tag { color: var(--muted); font-style: italic; text-decoration: line-through; text-decoration-color: var(--muted); } /* "mark all read" header row sits above the recent-messages list in the inbox side-panel flyout. Same look as the answer-form button (mauve hover, bg-elev background) so they 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; } .model-chip { display: inline-block; padding: 0.1em 0.6em; border: 1px solid var(--purple-dim); border-radius: 999px; color: var(--cyan); font-size: 0.78em; letter-spacing: 0.04em; } /* 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 { display: inline-block; padding: 0.1em 0.6em; border: 1px solid var(--purple-dim); border-radius: 999px; color: var(--green); font-size: 0.78em; letter-spacing: 0.04em; cursor: default; white-space: pre-line; } /* Harness reachability badge. Same chip shape + sizing as `.state-badge` / `.model-chip` so the state row stays visually uniform; colour communicates the actual reachability state. */ .status-badge { display: inline-block; padding: 0.25em 0.8em; border: 1px solid; border-radius: 999px; font-size: 0.85em; letter-spacing: 0.05em; } .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 rgba(166, 227, 161, 0.55); } .status-badge.status-rate-limited { color: var(--red); border-color: var(--red); text-shadow: 0 0 6px rgba(243, 139, 168, 0.55); } .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: rgba(243, 139, 168, 0.1); 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). */ .state-badge { display: inline-block; padding: 0.25em 0.8em; border: 1px solid; border-radius: 999px; font-size: 0.85em; letter-spacing: 0.05em; 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 rgba(137, 220, 235, 0.55); } .state-badge.state-thinking { color: var(--amber); border-color: var(--amber); text-shadow: 0 0 6px rgba(250, 179, 135, 0.65); 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 rgba(203, 166, 247, 0.65); 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 rgba(166, 227, 161, 0.6); 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. Copy of the dashboard's side-panel pattern — candidate for extraction into @hive/shared once both surfaces stabilize. */ .side-panel { position: fixed; inset: 0; z-index: 50; /* Closed: ignore pointer events so the agent page underneath stays interactive; `.open` flips it back on. */ pointer-events: none; } .side-panel-backdrop { position: absolute; inset: 0; background: rgba(0, 0, 0, 0.55); opacity: 0; transition: opacity 0.2s ease; } .side-panel-drawer { position: absolute; top: 0; right: 0; bottom: 0; width: min(640px, 92vw); display: flex; flex-direction: column; background: var(--bg-elev); border-left: 2px solid var(--purple); box-shadow: -10px 0 30px rgba(0, 0, 0, 0.45); transform: translateX(100%); transition: transform 0.25s ease; } .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-head { flex: 0 0 auto; display: flex; align-items: center; justify-content: space-between; gap: 1em; padding: 0.7em 1em; border-bottom: 1px solid var(--border); } .side-panel-title { color: var(--purple); font-weight: bold; letter-spacing: 0.05em; word-break: break-all; } .side-panel-close { flex: 0 0 auto; background: var(--bg); color: var(--fg); border: 1px solid var(--border); font-family: inherit; font-size: 1em; line-height: 1; padding: 0.25em 0.55em; cursor: pointer; } .side-panel-close:hover { border-color: var(--red); color: var(--red); } .side-panel-body { flex: 1 1 auto; overflow: auto; padding: 0.8em 1em; } /* Inbox / loose-ends lists rendered into the side-panel body. The legacy
-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. */ .side-panel-body .agent-inbox { margin: 0; font-size: inherit; color: var(--fg); } .side-panel-body .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 ────────────────────────────────────────────────────────── */ .stats-nav { display: flex; gap: 0.75rem; align-items: baseline; margin-bottom: 0.5rem; } .stats-nav h2 { margin: 0; } .stats-nav a { color: var(--cyan); text-decoration: none; } .stats-nav a:hover { text-decoration: underline; } .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; } .window-tabs button.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; } .stats-empty-note { color: var(--muted); font-style: italic; } /* ─── /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; } /* Toolbar strip at the top of the screen page. */ #toolbar { display: flex; align-items: center; gap: 0.75rem; padding: 0.4rem 0.75rem; background: var(--bg-elev); border-bottom: 1px solid var(--border); } #toolbar a { color: var(--blue); text-decoration: none; font-size: 0.85rem; } #toolbar a:hover { text-decoration: underline; } /* 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 toolbar. */ #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: rgba(17, 17, 27, 0.95); 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; }