/* Shared terminal pane: a scroll-sticky log of rows + a "↓ N new" pill. Pages wrap their stream container in `.terminal-wrap` and give the log itself the `.live` class; renderer JS appends `.row` (flat line) or `details.row` (collapsible body) elements. Row-kind classes (`.turn-start`, `.tool-use`, `.thinking`, etc.) carry the per-event colour; pages that don't emit a given kind simply never produce that class — the unused rule sits in the bundle harmlessly. `.terminal-wrap` provides the crust-on-black phosphor chrome that makes the agent page feel like a terminal. Pages can opt in by wrapping a block in this class; or skip it and the rows still render with their class colours, just without the frame. No `.term-input` here — composers are a separate concern, owned by each page's own CSS. Row taxonomy + layout contract documented in `docs/web-ui/shape.md::Shared terminal pane` and `docs/web-ui/terminal-rendering.md`. */ .terminal-wrap { position: relative; background: color-mix(in srgb, var(--crust) 78%, transparent); -webkit-backdrop-filter: blur(8px) saturate(120%); backdrop-filter: blur(8px) saturate(120%); border: 1px solid var(--purple-dim); box-shadow: inset 0 0 24px rgba(0, 0, 0, 0.7); border-radius: 4px; font-family: "JetBrains Mono", "Fira Code", "Cascadia Code", "Source Code Pro", monospace; font-size: 0.92em; color: var(--fg); margin-top: 0.6em; } .live { background: rgba(255, 255, 255, 0.02); border: 1px solid var(--purple-dim); padding: 0.4em 0.6em; overflow-y: auto; max-height: 32em; font-family: inherit; /* Disable browser scroll anchoring — the terminal manages scroll position manually (snapToBottom + loadMore compensation). Scroll anchoring would double-compensate scrollTop during loadMore() prepends, causing an erratic jump. */ overflow-anchor: none; /* Single source of truth for the icon column's width, shared by every row kind's own grid below (`.live .row` and `details.row > summary`). Structural alignment: both contexts read the *same* custom property rather than each hand-computing an offset that has to independently agree with the other (mara: "dont do it by offsets at all, it should be part of the layout that they align" — see the incident this replaced, two paragraphs down). */ --row-icon-col: 1.4em; } .live.terminal { background: transparent; border: 0; box-shadow: none; border-radius: 0; padding: 0.8em 1em 0.4em; overflow-y: auto; height: min(72vh, 60em); max-height: none; font-family: inherit; font-size: inherit; color: inherit; } .live .row, .live details.row { animation: row-fade-in 220ms ease-out both; } .live .row.no-anim, .live details.row.no-anim { animation: none; } @keyframes row-fade-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } } /* Unified prefix column for every row kind, via CSS grid rather than hand-computed offsets (see the incident this replaced, below). A flat row is its own 2-column grid (`--row-icon-col` | content); a `details` row instead grids its `` (see further down) so its `
`
   body can stack under the *whole* row, full width, not just the content
   column. `.row-glyph` and the row's actual content are both placed by
   explicit `grid-column` (below), never by auto-placement inference —
   an icon-less row's content still lands in column 2, not wherever the
   grid would otherwise auto-place a lone child.

   Previously: `padding-left` + a negative `text-indent` pulling the first
   inline box (or bare first character) back into the reserved slot.
   Replaced after a real bug, found the same session: `.row-glyph` needed
   its own copy of that indent to actually paint there (being
   `inline-block`, it's a block container for its own text, so it doesn't
   inherit the *effect* just by inheriting the *value*) — and
   `details.row > summary` separately reset `text-indent: 0` for its own
   hanging-caret needs, which silently zeroed the *inherited* indent for
   the icon nested inside it too, since indent inherits by computed
   value, not by rule. A details-row icon ended up painted 1.4em right of
   a flat row's despite both icons' own boxes already sharing a left edge
   — two independent numbers (`padding-left`/`text-indent` here,
   `margin-left`/`padding-left` on `details.row > summary`) that had to
   agree by hand, and silently didn't. mara: "dont do it by offsets at
   all, it should be part of the layout that they align" — a shared grid
   column, read from one custom property, replaces both. */
.live .row {
  display: grid;
  grid-template-columns: var(--row-icon-col) 1fr;
  column-gap: 0.5em;
  white-space: pre-wrap;
  word-break: break-word;
  padding: 0.05em 0;
  line-height: 1.45;
  border-left: 2px solid transparent;
  margin: 0.1em 0;
}
.live .row + .row {
  border-top: 0;
}
/* Icon column. Every row kind's icon lands in `grid-column: 1` of
   whichever grid is active in that context (this rule for a flat row,
   `details.row > summary`'s own grid further down for a collapsible
   one) — same column definition (`--row-icon-col`), so every icon's
   left edge lines up regardless of the glyph's own rendered width
   (emoji differ; some carry a variation selector). */
.live .row-glyph {
  grid-column: 1;
}
/* Content column — explicit, not left to grid auto-placement. Rows
   built with an icon (see terminal.js `row()` / Row.tsx) wrap their
   actual content in `.row-content` (a markdown body just uses its own
   `.md` class, styled the same way below) specifically so an icon-less
   row's lone remaining child still lands in column 2: without an
   explicit `grid-column`, auto-placement would put a row's *only* grid
   item in column 1 (the first available cell), landing text in the
   narrow icon column instead of the content one. */
.live .row-content,
.live .row > .md,
.live .row > details {
  grid-column: 2;
  min-width: 0;
}
/* Row colours, keyed by severity `level` (hive-agent's `term_msg.rs`),
   not by row kind any more — mara's terminal-message redesign dropped
   the server-side `kind` tag (turn-start/tool-use/tool-result/etc) in
   favour of one uniform shape, `{icon, level, summary, body,
   body_format, coalesce_key}`. What used to be a dozen-odd per-kind
   classes (`.turn-start`, `.tool-use`, `.tool-result.error`, `.sys`, …)
   is now four: the four severities every row already carries. Structural
   identity (this is a turn boundary, this is a tool call) is carried by
   the row's icon (`◆`, `🔧`, `✅`, …) and summary text instead of colour —
   see docs/web-ui/terminal-rendering.md. */
.live .level-debug {
  color: var(--muted);
}
.live .level-info {
  color: var(--fg);
}
.live .level-warn {
  color: var(--amber);
  border-left-color: var(--amber);
}
.live .level-error {
  color: var(--red);
  border-left-color: var(--red);
}
/* `badge-pulse` itself is no longer used by any terminal row (the
   turn-start `unread` count it animated is gone — see term_msg.rs's
   module doc), but agent.css's `.state-badge.state-thinking`/
   `.state-compacting` badges still reuse this keyframe via
   `@import "@hive/shared/terminal.css"` — keep the definition here, drop
   only the terminal-specific `.unread-badge` selector that used it. */
@keyframes badge-pulse {
  0%,
  100% {
    opacity: 1;
    text-shadow: 0 0 6px color-mix(in srgb, var(--amber) 55%, transparent);
  }
  50% {
    opacity: 0.7;
    text-shadow: 0 0 14px color-mix(in srgb, var(--amber) 95%, transparent);
  }
}
/* "↓ N new" pill: shown when new rows arrive while the operator is
   scrolled up; click to jump to bottom. Positioned by the wrapper's
   `position: relative` (terminal-wrap supplies it; pages that skip the
   wrapper must add their own positioned ancestor). */
.tail-pill {
  position: absolute;
  right: 1em;
  bottom: 4.2em;
  background: var(--amber);
  color: var(--crust);
  font-family: inherit;
  font-size: 0.8em;
  font-weight: bold;
  letter-spacing: 0.08em;
  border: 0;
  border-radius: 999px;
  padding: 0.35em 0.9em;
  cursor: pointer;
  box-shadow: 0 0 14px -2px color-mix(in srgb, var(--amber) 85%, transparent);
  opacity: 0;
  transform: translateY(6px);
  pointer-events: none;
  transition:
    opacity 160ms ease,
    transform 160ms ease;
}
.tail-pill.visible {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}
.tail-pill:hover {
  filter: brightness(1.1);
}
/* "↑ load older" pill: sits inline at the top of the log (not
   absolutely positioned) so it scrolls with the content. Appears
   when `has_more` is true after initial history load. */
.load-more-pill {
  display: block;
  width: 100%;
  background: transparent;
  color: var(--muted);
  font-family: inherit;
  font-size: 0.8em;
  font-weight: bold;
  letter-spacing: 0.08em;
  border: 0;
  border-bottom: 1px dashed var(--purple-dim);
  padding: 0.4em 1em;
  cursor: pointer;
  text-align: left;
  transition:
    color 120ms ease,
    background 120ms ease;
}
.load-more-pill:hover:not(:disabled) {
  color: var(--fg);
  background: var(--border);
}
.load-more-pill:disabled {
  cursor: default;
  opacity: 0.6;
}
/* Expandable rows don't grid themselves the way a flat row does — a
   `
` needs its `` and `
` body to stack normally
   (full row width each), not sit side by side in the row's own 2
   columns. `.live details.row` (not just `details.row`) overrides
   `.live .row`'s `display: grid` back to block for the `
` element itself — needs the extra `.live` to out-specify it (two classes beats one class + one type selector), or the override never applies and ``/`
` get squeezed into the *row's* 2-column
   grid instead of stacking (caught rendering this for real: a
   `` auto-placed into the 1.4em icon column wraps its text one
   character per line). The icon+text split happens one level down, on
   `` alone, which grids itself with the exact same
   `grid-template-columns: var(--row-icon-col) 1fr` as `.live .row` —
   same column, same custom property, so a details summary's icon and a
   flat row's icon share a left edge *because they're the same declared
   column*, not because two independently-computed offsets happen to
   agree. The disclosure caret (`▸ / ▾`) leads `.summary-text` (not the
   icon) via `::before`. The summary text carries no `→ / ←`; the row
   colour (cyan = outbound tool, muted = inbound result) carries the
   direction. */
.live details.row {
  display: block;
  white-space: normal;
}
details.row > summary {
  display: grid;
  grid-template-columns: var(--row-icon-col) 1fr;
  column-gap: 0.5em;
  align-items: baseline;
  cursor: pointer;
  list-style: none;
  white-space: pre-wrap;
  word-break: break-word;
}
/* `.row-glyph`'s own `grid-column: 1` (set once, above) already applies
   here too — nothing details-specific to repeat. `.summary-text` needs
   its own explicit `grid-column: 2` for the same reason `.row-content`
   does on a flat row: an icon-less summary's only child must not fall
   back to column 1 via auto-placement. */
details.row > summary > .summary-text {
  grid-column: 2;
  min-width: 0;
}
details.row > summary > .summary-text::before {
  content: "▸ ";
  color: inherit;
}
details.row[open] > summary > .summary-text::before {
  content: "▾ ";
}
details.row > pre.diff-body,
details.row > pre.tool-body {
  margin: 0.3em 0 0.4em 0;
  padding: 0.4em 0.6em;
  background: rgba(255, 255, 255, 0.02);
  border-left: 2px solid var(--purple-dim);
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 22em;
  overflow-y: auto;
}
details.row > pre.tool-body {
  color: var(--fg);
}
details.row > pre.diff-body .diff-add {
  color: var(--green);
}
details.row > pre.diff-body .diff-del {
  color: var(--red);
}
details.row > pre.diff-body .diff-ctx {
  color: var(--fg);
}
/* Markdown body inside a row (assistant text, send/recv message
   bodies). Inline elements get muted accents. */
.live .row .md p {
  margin: 0.2em 0;
}
.live .row .md p:first-child {
  margin-top: 0;
}
.live .row .md p:last-child {
  margin-bottom: 0;
}
.live .row .md code {
  background: rgba(255, 255, 255, 0.06);
  padding: 0.05em 0.3em;
  border-radius: 3px;
  font-size: 0.95em;
}
.live .row .md pre {
  margin: 0.3em 0;
  padding: 0.4em 0.6em;
  background: rgba(255, 255, 255, 0.04);
  border-left: 2px solid var(--purple-dim);
  white-space: pre-wrap;
  word-break: break-word;
}
.live .row .md pre code {
  background: transparent;
  padding: 0;
  border-radius: 0;
}
.live .row .md a {
  color: var(--cyan);
  text-decoration: underline;
}
/* Auto-linkified bare URLs in plain rows + tool-body blocks. */
.live .row a {
  color: var(--cyan);
  text-decoration: underline;
}
.live .row a:hover {
  color: var(--fg);
}
.live .row .md strong {
  color: inherit;
  font-weight: bold;
}
.live .row .md em {
  color: inherit;
  font-style: italic;
}
.live .row .md ul,
.live .row .md ol {
  margin: 0.2em 0 0.2em 1.4em;
  padding: 0;
}
.live .row .md li {
  margin: 0.05em 0;
}
.live .row .md blockquote {
  margin: 0.2em 0;
  padding-left: 0.6em;
  border-left: 2px solid var(--purple-dim);
  color: var(--muted);
}