/*
 * joe-server SPA — additions on top of tfi-livery (//projects/tfi-livery).
 * Only joe-server-specific structure. All colors, type, controls inherit
 * from tfi.css; touch them sparingly here.
 */

/* tfi base resets to max-width: 960px on body. We want a bit wider
 * for the conversation views and own the header chrome ourselves. */
body { padding: 0; max-width: none; }

main { padding: 1.5rem 2rem; max-width: 1100px; margin: 0 auto; }

/* ── Header / nav ──────────────────────────────────────────────── */

header {
  display: flex;
  align-items: baseline;
  gap: 2rem;
  padding: 1rem 2rem;
  border-bottom: 2px solid var(--tfi-yellow);
  background: var(--tfi-white);
  /* Pin to the top across every view. In chat-mode the body is a flex column
     with overflow:hidden so the header already holds the top; in the scrolling
     list views (body scrolls normally) sticky keeps it from scrolling away. */
  position: sticky;
  top: 0;
  z-index: 50;
}

header h1 {
  margin: 0;
  font-size: 1.4rem;
}

.brand-logo {
  height: 1.5em;
  width: auto;
  vertical-align: -0.35em;
  margin-right: 0.5rem;
}

header h1 a {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: var(--tfi-orange);
  cursor: pointer;
}

header h1 a:hover {
  text-decoration-thickness: 2px;
}

nav { display: flex; gap: 0.5rem; flex: 1; }

nav a {
  padding: 0.25rem 0.75rem;
  border: 2px solid transparent;
  border-radius: 4px;
}

nav a.active {
  border-color: var(--tfi-orange);
  color: var(--tfi-orange);
}

#whoami {
  font-size: 0.85rem;
  color: var(--tfi-dark-grey);
}

/* ── Helpers ───────────────────────────────────────────────────── */

.loading, .hint { color: var(--tfi-dark-grey); font-size: 0.9rem; }
.loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  padding: 2.5rem 0;
}
.loading-mark { display: block; }
.mono { font-family: var(--mono); }
.small { font-size: 0.8rem; color: var(--tfi-dark-grey); }

dl.meta {
  display: grid;
  grid-template-columns: 8rem 1fr;
  gap: 0.25rem 1rem;
  font-size: 0.9rem;
  margin: 1rem 0;
}
dl.meta dt { color: var(--tfi-dark-grey); font-weight: bold; }
dl.meta dd { margin: 0; }

tr.clickable { cursor: pointer; }
tr.clickable:hover td { background: var(--tfi-cream); }

/* Sessions view: active / queued / completed groups. */
.sessions-group { margin-bottom: 1.5rem; }
.sessions-group > h3 { margin-bottom: 0.2rem; }
.sessions-group .group-count { color: var(--tfi-dark-grey); font-weight: normal; font-size: 0.85rem; }
.live-pip { color: var(--tfi-green); font-weight: bold; }
.queued-pip { color: var(--tfi-dark-grey); }

/* Attention-router decision log: scan NOW vs BATCH vs SKIP at a glance. */
td.verdict { font-weight: bold; }
tr.verdict-skip td { color: var(--tfi-dark-grey); }         /* dropped — muted */
tr.verdict-now td.verdict { color: var(--tfi-green); }
tr.verdict-batch td.verdict { color: var(--tfi-orange); }

/* Unfurled decision row: the full message body (markdown) + routing metadata. */
tr.decision-detail > td {
  background: var(--tfi-cream);
  border-left: 3px solid var(--tfi-yellow);
  padding: 0.5rem 0.75rem;
}
.decision-detail-meta { margin-bottom: 0.4rem; }
.decision-detail-body { overflow-wrap: anywhere; }

/* ── Message header / ornaments ────────────────────────────────── */
/* Shared by the chat view and the session-detail tailer. Both use
   appendMsg → .msg → [.msg-head, .content]. Session detail passes
   turnId + fork=true; chat view leaves them off. */

.role {
  text-transform: uppercase;
  font-size: 0.7rem;
  letter-spacing: 0.05em;
  color: var(--tfi-dark-grey);
  font-weight: bold;
}

.msg-head {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  font-size: 0.85rem;
  margin-bottom: 0.25rem;
}

.msg-ornaments {
  display: inline-flex;
  align-items: baseline;
  gap: 0.5rem;
  margin-left: auto;
}

.msg-ornaments .turn-id { color: var(--tfi-dark-grey); }

/* Completion time + duration sit left of the turn-id chip. Muted so the
   chain id and content stay primary; duration gets a faint pill so it reads
   as a measured quantity at a glance. */
.msg-ornaments .turn-time { color: var(--tfi-grey); }
.msg-ornaments .turn-duration {
  color: var(--tfi-dark-grey);
  background: var(--tfi-light-grey);
  border-radius: 3px;
  padding: 0 0.3rem;
}

/* Empty model response — a meta-note, not content. */
.msg-empty { color: var(--tfi-dark-grey); font-style: italic; }

/* Composer image attachments (drag-drop / paste) — a preview strip of staged
   frames above the textarea, and a highlight while a drag hovers the composer. */
.composer-attachments {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0 0 0.4rem;
}
.attach-thumb {
  position: relative;
  width: 64px;
  height: 64px;
  border: 1px solid var(--tfi-light-grey);
  border-radius: 4px;
  overflow: hidden;
  background: var(--tfi-light-grey);
}
.attach-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.attach-remove {
  position: absolute;
  top: 1px;
  right: 1px;
  width: 18px;
  height: 18px;
  line-height: 16px;
  padding: 0;
  font-size: 0.85rem;
  border: none;
  border-radius: 3px;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  cursor: pointer;
}
form.drag-over { outline: 2px dashed var(--tfi-orange); outline-offset: 3px; }

/* Inline image attachments — conditions camera frames + operator drops. Capped
   thumbnails (the frames are already low-res); click to open the full blob. */
.msg-image {
  display: block;
  max-width: min(320px, 100%);
  max-height: 240px;
  height: auto;
  margin: 0.35rem 0;
  border: 1px solid var(--tfi-light-grey);
  border-radius: 4px;
  cursor: zoom-in;
  background: var(--tfi-light-grey);
}

button.fork-btn {
  background: var(--tfi-white);
  color: var(--tfi-orange);
  border: 1px solid var(--tfi-orange);
  padding: 0.1rem 0.5rem;
  font-size: 0.7rem;
  font-weight: normal;
  border-radius: 3px;
}

button.fork-btn:hover {
  background: var(--tfi-orange);
  color: var(--tfi-white);
}

/* ── Layout Modes ────────────────────────────────────────────────── */

body.chat-mode {
  height: 100vh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

body.chat-mode main {
  flex: 1;
  display: flex;
  flex-direction: column;
  padding: 0;
  max-width: none;
  overflow: hidden;
}

body.chat-mode #view {
  width: 100%;
  max-width: 1100px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  flex: 1;
  overflow: hidden;
}

body.chat-mode #view > h2,
body.chat-mode #view > .hint {
  padding: 1.5rem 2rem 0;
}

body.chat-mode .chat-log {
  flex: 1;
  max-height: none;
  margin-bottom: 0;
}

body.chat-mode form.chat-input {
  padding: 1rem 2rem 1.5rem;
  border-top: 1px solid var(--tfi-light-grey);
  background: var(--tfi-white);
}

/* ── Context-usage meter ───────────────────────────────────────── */
/* Sits above the chat box on session-detail. Fill width = context used
   against the auto-compaction threshold; colour bands green/blue/yellow/
   red at 25/50/75%. The track carries 10% tick marks (overlaid on top of
   the fill so they read across the whole bar). Resets on compaction
   because the value tracks the latest round's (compacted) prompt size. */
.context-meter {
  margin: 0 0 1rem;
}
.context-meter-head {
  display: flex;
  justify-content: flex-start;
  align-items: baseline;
  gap: 0.6rem;
  margin-bottom: 0.3rem;
}
/* Conversation-mode chip leads the head row (coding/conv/ambient). */
.context-meter-mode {
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: 0.7rem;
  padding: 0.05rem 0.35rem;
  border-radius: 0.6rem;
  border: 1px solid var(--tfi-dark-grey);
  color: var(--tfi-dark-grey);
}
.context-meter-mode.mode-ambient { color: #d29922; border-color: #d29922; }
.context-meter-mode.mode-conv    { color: #2f81f7; border-color: #2f81f7; }
.context-meter-mode.mode-coding  { color: #3fb950; border-color: #3fb950; }
/* Turn counts lead the head row (total + turns since the last compaction),
   ahead of the "context" label; the readout is pushed to the far right. */
.context-meter-turns {
  color: var(--tfi-dark-grey);
}
.context-meter-label {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--tfi-dark-grey);
}
.context-meter-readout {
  color: var(--tfi-dark-grey);
  margin-left: auto;
}
.context-meter-track {
  position: relative;
  height: 10px;
  border-radius: 5px;
  background: var(--tfi-light-grey);
  overflow: hidden;
}
.context-meter-fill {
  height: 100%;
  width: 0;
  transition: width 0.4s ease, background-color 0.3s ease;
}
/* 10% tick marks, overlaid above the fill via ::after so they stay
   visible across both the filled and empty portions of the track. The
   last segment edge is the track border, so this draws the 9 interior
   dividers (ten equal segments). */
.context-meter-track::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: repeating-linear-gradient(
    to right,
    transparent 0,
    transparent calc(10% - 1px),
    rgba(0, 0, 0, 0.28) calc(10% - 1px),
    rgba(0, 0, 0, 0.28) 10%
  );
}
.context-meter-fill.meter-green  { background-color: #3fb950; }
.context-meter-fill.meter-blue   { background-color: #2f81f7; }
.context-meter-fill.meter-yellow { background-color: #d4a72c; }
.context-meter-fill.meter-red    { background-color: #e5534b; }

/* ── Chat panel ────────────────────────────────────────────────── */

.chat-log {
  background: var(--tfi-white);
  padding: 0.9rem;
  min-height: 220px;
  max-height: 60vh;
  overflow: auto;
  margin-bottom: 1rem;
  /* Flex column so .msg-user / .msg-assistant can self-align
     right / left via align-self. */
  display: flex;
  flex-direction: column;
}

/* Session-detail tailer reuses the .chat-log skeleton for shared
   bubble styling, but acts like a static log: take whatever vertical
   space the content needs, let the page itself scroll, no internal
   scroll, no scroll-to-end. The chat view keeps its internal
   scroll-with-fixed-container behaviour. */
.session-log {
  max-height: none;
  overflow: visible;
}

/* ── Tool-call diff blocks ─────────────────────────────────────── */
/* Used by the assistant turn renderer for edit_text / prepend_to_file
   / append_to_file calls. The header carries the tool name + path,
   the block is a monospace +/-/context diff with soft-coloured bg
   highlights so the bytes-that-changed view reads at a glance. */

.tool-edit-call {
  margin: 0.5rem 0;
}

.tool-call-head {
  font-family: var(--mono);
  font-size: 0.85rem;
  color: var(--tfi-dark-grey);
  margin-bottom: 0.2rem;
}

/* Intent / rationale string the model passed to a mutating tool —
   sits between the tool-call header and the diff so the "why" reads
   before the "what". Sans-serif + a leading border, italic to read
   as a quoted thought rather than executable code. */
.tool-call-intent {
  font-style: italic;
  font-size: 0.85rem;
  color: var(--tfi-dark-grey);
  border-left: 3px solid var(--tfi-orange);
  padding: 0.2rem 0.6rem;
  margin: 0 0 0.3rem 0;
  background: var(--tfi-cream);
  white-space: pre-wrap;
  word-wrap: break-word;
}

/* ── Content fold (collapsed-by-default for long messages) ─────── */
/* Any non-tool message bubble whose content exceeds ~20 lines gets
   .foldable + a "show more" toggle. The prelude / CLAUDE.md / first
   tool dumps are the canonical case: useful to have available, not
   useful to scroll past every session view. Click expands; click
   again collapses. */

.content.foldable {
  max-height: 480px;
  overflow: hidden;
  position: relative;
}

.content.foldable::after {
  /* Soft fade-out at the bottom edge so the eye reads the cut as
     intentional rather than as a hard truncation. */
  content: "";
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: 3rem;
  background: linear-gradient(to bottom, transparent, var(--tfi-white));
  pointer-events: none;
}

.content.foldable.expanded {
  max-height: none;
  overflow: visible;
}

.content.foldable.expanded::after {
  display: none;
}

.fold-toggle {
  display: block;
  margin-top: 0.3rem;
  margin-left: 0.1rem;
  font-size: 0.75rem;
  color: var(--tfi-orange);
  background: transparent;
  border: none;
  padding: 0;
  cursor: pointer;
  font-family: var(--mono);
}

.fold-toggle:hover {
  text-decoration: underline;
}

/* ── Markdown body styling ─────────────────────────────────────── */
/* Applied to any content rendered through renderMarkdown — assistant
   prose bodies, user messages, system-role text. Sized for the chat
   bubble context: headers are only slightly larger than body, code
   blocks have their own border + background, lists indent normally.
   Keeps everything readable without overflowing the bubble. */

.md {
  white-space: normal;
}

.md p {
  margin: 0 0 0.5rem 0;
}

.md p:last-child {
  margin-bottom: 0;
}

.md h1, .md h2, .md h3, .md h4, .md h5, .md h6 {
  margin: 0.6rem 0 0.3rem 0;
  font-weight: 600;
  line-height: 1.25;
}

.md h1 { font-size: 1.15em; }
.md h2 { font-size: 1.10em; }
.md h3 { font-size: 1.05em; }
.md h4, .md h5, .md h6 { font-size: 1em; }

.md ul, .md ol {
  margin: 0.25rem 0 0.5rem 0;
  padding-left: 1.5rem;
}

.md li {
  margin: 0.1rem 0;
}

.md li > p {
  margin: 0;
}

.md code {
  font-family: var(--mono);
  font-size: 0.85em;
  background: var(--tfi-cream);
  border: 1px solid var(--tfi-light-grey);
  border-radius: 3px;
  padding: 0.05em 0.3em;
}

.md pre {
  font-family: var(--mono);
  font-size: 0.82em;
  background: var(--tfi-cream);
  border: 1px solid var(--tfi-light-grey);
  border-radius: 3px;
  padding: 0.5rem 0.7rem;
  margin: 0.4rem 0;
  overflow-x: auto;
  white-space: pre;
}

.md pre code {
  background: transparent;
  border: 0;
  padding: 0;
  font-size: inherit;
}

/* MathJax (tex-svg) output. Display math gets its own scrollable line so a
   wide equation never blows out the column width; inline math rides the
   baseline of surrounding prose. Pre-typeset, the raw \(…\) text is hidden
   so the delimiters don't flash before MathJax swaps in the SVG. */
.md .math-tex:not(:has(mjx-container)) {
  visibility: hidden;
}
/* …unless MathJax failed to load — then show the raw TeX, don't hide content. */
.no-mathjax .md .math-tex {
  visibility: visible;
}
.md mjx-container[display="true"] {
  overflow-x: auto;
  overflow-y: hidden;
  max-width: 100%;
  margin: 0.4rem 0;
}
.md mjx-container {
  max-width: 100%;
}

.md blockquote {
  margin: 0.4rem 0;
  padding: 0.2rem 0.7rem;
  border-left: 3px solid var(--tfi-orange);
  color: var(--tfi-dark-grey);
  background: var(--tfi-cream);
}

.md a {
  color: var(--tfi-blue);
  text-decoration: underline;
}

.md hr {
  border: 0;
  border-top: 1px solid var(--tfi-light-grey);
  margin: 0.6rem 0;
}

.md table {
  border-collapse: collapse;
  font-size: 0.85em;
  margin: 0.4rem 0;
}

.md table th, .md table td {
  border: 1px solid var(--tfi-light-grey);
  padding: 0.2rem 0.5rem;
}

.md table th {
  background: var(--tfi-cream);
  font-weight: 600;
}

/* Synthetic (system-injected) tool-call pairs — framework noise like
   the per-turn recall / task_list snapshots. Dim and shrink so the
   eye skips over them when scanning real model actions; the operator
   can still read them with a deliberate look (or :hover for the
   transient highlight). */
.msg-synth {
  opacity: 0.45;
  font-size: 0.85em;
  transition: opacity 0.15s ease-out;
}

.msg-synth:hover {
  opacity: 1;
}

/* ── Compaction markers (mask / replace control turns) ─────────────
   A fold emits a ``replace`` (the pointer) + a ``mask`` (the folded ids).
   They're bookkeeping, not conversation — render each as a quiet, full-width
   centered divider instead of a chat bubble of raw JSON. */
.chat-log .msg-mask,
.chat-log .msg-replace {
  max-width: none;
  width: 100%;
  align-self: center;
  margin: 0.4rem 0;
}
.msg-mask .msg-head,
.msg-replace .msg-head {
  display: none;   /* drop the "mask" / "replace" role label */
}
.msg-mask .content,
.msg-replace .content {
  background: none;
  border: none;
  padding: 0;
  margin: 0;
}
.compaction-marker {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}
.compaction-marker::before,
.compaction-marker::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--tfi-light-grey);
}
.compaction-label {
  color: var(--tfi-dark-grey);
  font-size: 0.75rem;
  font-style: italic;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 80%;
  cursor: default;
}

/* Marker chip identifying which synth channel a row came from —
   "system" badge next to the role label. Drawn from the same colour
   family as the tfi-livery so it doesn't compete with the
   role-based bubble accents. */
.msg-synth .msg-head::before,
.msg-synth .msg-tool-header::before {
  content: "system";
  font-size: 0.6rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--tfi-dark-grey);
  background: var(--tfi-cream);
  padding: 0.05rem 0.4rem;
  border-radius: 3px;
  border: 1px solid var(--tfi-light-grey);
  margin-right: 0.4rem;
}

.diff-block {
  font-family: var(--mono);
  font-size: 0.78rem;
  line-height: 1.4;
  border: 1px solid var(--tfi-light-grey);
  border-radius: 3px;
  overflow: hidden;
  background: var(--tfi-white);
}

.diff-line {
  padding: 0.05rem 0.6rem;
  white-space: pre-wrap;
  word-wrap: break-word;
}

.diff-line.add {
  background: #e6ffed;
  color: #033c0a;
}

.diff-line.del {
  background: #ffeef0;
  color: #5c0000;
}

.diff-line.context {
  color: var(--tfi-dark-grey);
}

/* ── Shell tool-call blocks ────────────────────────────────────── */
/* The shell() / bash() renderer surfaces the command as a terminal-style
   block: ``$ `` prompt lines start each command, connector/pipe chains
   wrap to indented continuation lines. Dark chrome so it reads as a
   console transcript rather than as the model's prose. */

.tool-shell-call {
  margin: 0.5rem 0;
}

.shell-block {
  font-family: var(--mono);
  font-size: 0.78rem;
  line-height: 1.4;
  border: 1px solid var(--tfi-dark-grey);
  border-radius: 3px;
  overflow: hidden;
  background: #1d1f21;
  color: #e8e8e8;
  padding: 0.3rem 0;
}

.shell-line {
  padding: 0.05rem 0.6rem;
  white-space: pre-wrap;
  word-wrap: break-word;
}

.shell-line.cont {
  color: #b8c4cf;
}

.shell-prompt {
  color: var(--tfi-orange);
  user-select: none;
}

/* Inline prose body that accompanies a structured tool-call render —
   keeps paragraph wrap behaviour consistent with the plain-text
   .content path. */
.msg-body-text {
  white-space: pre-wrap;
  word-wrap: break-word;
  margin-bottom: 0.25rem;
}

/* Bubble width + L/R stagger. Two halves of the conversation read
   from opposite sides of the log so the eye can skip between speaker
   roles without reading every header:
     * agent's own assistant turns  → left  (FROM the agent)
     * user prompts, system instructions, tool results  → right
       (TO the agent — the inputs it's reacting to)
   80% of the log width is a hard cap — never full width — so even
   long messages keep enough negative space on the opposite side
   for the L/R axis to register. */
.chat-log .msg {
  margin-bottom: 1rem;
  display: flex;
  flex-direction: column;
  max-width: 80%;
}

.msg-assistant {
  align-self: flex-start;
  text-align: left;
}

.msg-user,
.msg-system,
.msg-tool {
  align-self: flex-end;
}

.msg-user .role { color: var(--tfi-blue); }
.msg-assistant .role { color: var(--tfi-orange); }
.msg-system .role { color: var(--tfi-green); }

.msg-user .content, .msg-assistant .content, .msg-system .content {
  background: var(--tfi-white);
  border: 1px solid var(--tfi-light-grey);
  border-radius: 8px;
  padding: 0.5rem 0.8rem;
  margin-top: 0.25rem;
  white-space: pre-wrap;
  word-wrap: break-word;
}

.msg-user .content {
  border-color: var(--tfi-blue);
  background: var(--tfi-cream);
}

/* Queued user message — fired while the agent was busy, not yet picked
   up. Dimmed + dashed so it reads as provisional; the tailer retires it
   the moment the matching real user turn lands. The container uses
   display:contents so its bubbles still participate as flex items of
   the log (keeping the .msg-user right-alignment). */
.queued-log { display: contents; }

.msg-queued { opacity: 0.6; }

.msg-queued .content {
  border-style: dashed;
}

.queued-tag {
  margin-left: 0.5rem;
  font-size: 0.65rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--tfi-blue);
  border: 1px solid var(--tfi-blue);
  border-radius: 3px;
  padding: 0 0.3rem;
}

.msg-tool {
  background: var(--tfi-white);
  border: 1px solid var(--tfi-yellow);
  border-radius: 4px;
  margin-bottom: 0.75rem;
  overflow: hidden;
}

.msg-tool-header {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.4rem 0.8rem;
  background: var(--tfi-cream);
  font-size: 0.85rem;
  font-weight: bold;
  /* The header is the fold control — only it is clickable. The body
     stays selectable so tool output can be copy-pasted. */
  cursor: pointer;
  user-select: none;
}

.msg-tool .role {
  color: var(--tfi-yellow);
  font-size: 0.7rem;
}

.tool-indicator {
  font-family: var(--mono);
  width: 1rem;
  text-align: center;
}

.msg-content {
  padding: 0.6rem 0.8rem;
  white-space: pre-wrap;
  word-wrap: break-word;
  font-size: 0.85rem;
  transition: max-height 0.2s ease-out;
}

.msg-content.collapsed {
  display: none;
}

.msg-content.expanded {
  display: block;
}

/* Role label sits inside .msg-head now; no flex-basis needed. */

form.chat-input {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

form.chat-input textarea {
  font-family: var(--mono);
  font-size: 0.9rem;
  background: var(--tfi-cream);
  color: var(--tfi-black);
  border: 2px solid var(--tfi-light-grey);
  border-radius: 4px;
  padding: 0.5rem;
}

form.chat-input textarea:focus {
  outline: none;
  border-color: var(--tfi-orange);
}

form.chat-input .row {
  display: flex;
  gap: 0.75rem;
  align-items: center;
}

form.chat-input label {
  font-weight: normal;
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex: 1;
}

/* Workspace gate hint sits left-of-spinner-and-send so it grows to
 * push the inflight spinner + send button against the right edge of
 * the prompt bar. When the session has a workspace, the hint is empty
 * and acts as an invisible flex spacer. */
form.chat-input .workspace-status {
  flex: 1;
}

.parent-info {
  display: block;
  margin-top: 0.5rem;
  color: var(--tfi-orange);
  font-size: 0.85rem;
}

/* ── Task panel ───────────────────────────────────────────────────── */

.task-panel {
  padding: 1rem 2rem;
  border-bottom: 1px solid var(--tfi-light-grey);
  background: var(--tfi-white);
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.task-row {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  font-size: 0.9rem;
}

.task-mark {
  font-family: var(--mono);
  font-weight: bold;
  color: var(--tfi-orange);
  min-width: 3em;
}

.task-desc {
  color: var(--tfi-dark-grey);
}

.task-summary {
  margin-top: 0.5rem;
  font-size: 0.8rem;
  color: var(--tfi-dark-grey);
  font-style: italic;
}
/* ── Placeholder typing dots (chat view only) ──────────────────── */

.msg-placeholder .content {
  display: flex;
  gap: 0.25rem;
  padding: 0.5rem 0.8rem;
}

.msg-placeholder .content .typing-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--tfi-dark-grey);
  opacity: 0.4;
  animation: typing-bounce 1.2s ease-in-out infinite;
}

@keyframes typing-bounce {
  0%, 60%, 100% {
    transform: translateY(0);
    opacity: 0.4;
  }
  30% {
    transform: translateY(-4px);
    opacity: 1;
  }
}

/* ── Session-level stop / end buttons ────────────────────────────── */
/* These live inline with the send button in the prompt bar. Match
 * the send button's box so the row reads as a single button group;
 * differ by color so stop (orange) and end (red, destructive) carry
 * their own affordance weight without legend cards. */

.session-stop,
.session-end {
  background: var(--tfi-white);
  border-radius: 3px;
  cursor: pointer;
  text-transform: lowercase;
  letter-spacing: 0.05em;
}

.session-stop {
  border: 1px solid var(--tfi-orange);
  color: var(--tfi-orange);
}

.session-end {
  border: 1px solid var(--tfi-red, #c0392b);
  color: var(--tfi-red, #c0392b);
}

.session-stop:hover {
  background: var(--tfi-orange);
  color: var(--tfi-white);
}

.session-end:hover {
  background: var(--tfi-red, #c0392b);
  color: var(--tfi-white);
}

.session-stop:disabled,
.session-end:disabled {
  opacity: 0.4;
  cursor: progress;
}

/* ── In-flight spinner indicator ─────────────────────────────────── */

.inflight-indicator {
  display: inline-flex;
  align-items: center;
  margin-left: 0.5rem;
  vertical-align: middle;
}

.inflight-indicator[hidden] {
  display: none;
}

.inflight-spinner {
  width: 20px;
  height: 20px;
  background: url(/ui/spinner.svg) center / contain no-repeat;
  display: inline-block;
}

/* Optional text beside the spinner, e.g. "Compacting… 50%". Empty most
   of the time (plain thinking spinner); only populated for phases the
   operator should see by name. */
.inflight-label {
  margin-left: 0.4rem;
  font-size: 0.85rem;
  color: var(--tfi-dark-grey);
  white-space: nowrap;
}

/* memory revision history ("git log for a memory") */
.rev-badge { color: var(--tfi-dark-grey); font-weight: normal; font-size: 0.9rem; }
ol.revisions { list-style: none; padding: 0; margin: 0; }
ol.revisions li.revision { border-left: 2px solid var(--tfi-light-grey, #ccc); padding: 0.3rem 0 0.3rem 0.6rem; margin: 0.3rem 0; }
.rev-line { display: flex; gap: 0.6rem; align-items: baseline; }
.rev-n { font-family: var(--mono); font-weight: bold; }
.rev-kind { color: var(--tfi-dark-grey); }
.rev-time { margin-left: auto; font-size: 0.8rem; color: var(--tfi-dark-grey); }
.rev-rationale { font-size: 0.85rem; margin-top: 0.2rem; white-space: pre-wrap; }

/* RAG phrase anchors (multi-vector retrieval pointers) */
.anchor-group { margin: 0.4rem 0; }
.anchor-label { font-size: 0.8rem; color: var(--tfi-dark-grey); margin-bottom: 0.25rem; }
.anchor-pills { display: flex; flex-wrap: wrap; gap: 0.3rem; }
.anchor-pill { display: inline-block; padding: 0.1rem 0.45rem; border-radius: 0.8rem; font-size: 0.82rem; border: 1px solid var(--tfi-light-grey, #ccc); }
.anchor-term, .anchor-query { font-family: var(--mono); background: var(--tfi-faint, #f4f4f4); }
.anchor-count { color: var(--tfi-dark-grey); font-size: 0.85em; }

/* Memory-detail content: rendered markdown, wrapped. Scoped to .mem-content so
   it never touches chat message bodies (which also carry .md.content). */
.mem-content { border-left: 3px solid var(--tfi-light-grey, #ccc); padding-left: 0.85rem; overflow-wrap: anywhere; }
.mem-content pre { white-space: pre-wrap; overflow-x: auto; }

/* Memories view: controls bar + metric columns */
.mem-controls { display: flex; gap: 1.2rem; align-items: center; margin: .5rem 0 .75rem; flex-wrap: wrap; }
.mem-controls label { display: flex; gap: .4rem; align-items: center; font-size: .85rem; }
.mem-controls select { padding: .15rem .3rem; }
.memories td.num { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
.memories td.keys { font-size: .8rem; line-height: 1.5; }
.memories .key { display: inline-block; margin-right: .5rem; white-space: nowrap; }
.memories .key em { color: #8a8a8a; font-style: normal; font-size: .85em; }

/* ── Social: the account / persona / dossier graph ─────────────────── */

.social-overview { margin: 1rem 0 1.25rem; }

.social-controls {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 1rem;
}
.social-controls label { display: inline-flex; align-items: center; gap: .35rem; }
/* An explicit display on the label above out-specifies the UA [hidden] rule, so
   spell out the hide for controls syncControlVis() toggles (tier / sort / search). */
.social-controls [hidden] { display: none; }
.social-controls select,
.social-controls input[type="search"] {
  font: inherit;
  padding: .2rem .35rem;
  border: 1px solid var(--tfi-light-grey);
  border-radius: 3px;
  background: var(--tfi-white);
}
.social-controls .social-count { margin-left: auto; }

.social-table-wrap { overflow-x: auto; }
.social-name { font-weight: bold; }

/* Tier badges — the engagement gradient, colour-coded. */
.tier-badge {
  display: inline-block;
  font-size: .72rem;
  font-weight: bold;
  text-transform: uppercase;
  letter-spacing: .03em;
  padding: .1rem .4rem;
  border-radius: 3px;
  border: 1px solid;
  white-space: nowrap;
}
.tier-ingroup { color: var(--tfi-green); border-color: var(--tfi-green); background: rgba(60, 140, 60, .08); }
.tier-engager { color: var(--tfi-orange); border-color: var(--tfi-orange); background: rgba(214, 122, 42, .08); }
.tier-outgroup { color: var(--tfi-dark-grey); border-color: var(--tfi-light-grey); background: var(--tfi-cream); }

/* Reputation + relationship inline tags. */
.rep-flag {
  display: inline-block;
  font-size: .68rem;
  padding: .05rem .3rem;
  margin-left: .3rem;
  border-radius: 3px;
  background: var(--tfi-light-grey);
  color: var(--tfi-dark-grey);
  vertical-align: middle;
}
.rep-flag.rep-bad { background: rgba(187, 45, 46, .12); color: var(--tfi-red); }
.rep-flag.rep-good { background: rgba(60, 140, 60, .12); color: var(--tfi-green); }
.rel-follows { color: var(--tfi-green); font-weight: bold; }
.rel-opted { color: var(--tfi-red); font-weight: bold; }
.dossier-yes { color: var(--tfi-green); font-weight: bold; }

/* Detail views. */
.social-back { margin: 0 0 .5rem; font-size: .85rem; }
.engagement-note {
  margin: .25rem 0 1rem;
  padding: .5rem .75rem;
  border-left: 3px solid var(--tfi-orange);
  background: var(--tfi-cream);
  font-size: .9rem;
}
.allow-chips { display: flex; gap: .5rem; flex-wrap: wrap; margin: .5rem 0; }
.allow-chip {
  font-size: .78rem;
  font-family: var(--mono);
  padding: .15rem .45rem;
  border-radius: 3px;
  border: 1px solid;
}
.allow-chip.allow-yes { color: var(--tfi-green); border-color: var(--tfi-green); background: rgba(60, 140, 60, .07); }
.allow-chip.allow-no { color: var(--tfi-dark-grey); border-color: var(--tfi-light-grey); background: var(--tfi-cream); }

.social-names { list-style: none; padding-left: 0; margin: .25rem 0; }
.social-names li { padding: .15rem 0; }
.name-kind {
  display: inline-block;
  min-width: 3.5rem;
  color: var(--tfi-dark-grey);
  text-transform: uppercase;
  font-size: .68rem;
}
.persona-accounts { margin: .25rem 0; }

/* ── Operator lockout (the pause button) ───────────────────────── */

/* Header badge — flags an active lockout from any tab. */
.lockout-badge {
  align-self: center;
  padding: 0.2rem 0.6rem;
  border-radius: 999px;
  background: var(--tfi-red);
  color: #fff;
  font-size: 0.75rem;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
}
.lockout-badge:hover { background: var(--tfi-red-dark); }

/* Panel on the Home view. */
.lockout-panel {
  margin: 0 0 1.25rem;
  padding: 1rem 1.1rem;
  border: 2px solid var(--tfi-yellow);
  border-radius: 8px;
  background: var(--tfi-cream);
}
.lockout-title {
  margin: 0 0 0.75rem;
  font-size: 1rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--tfi-dark-grey);
}
.lockout-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr);
  gap: 1rem;
}
@media (max-width: 720px) {
  .lockout-grid { grid-template-columns: 1fr; }
}
.lockout-card {
  padding: 0.9rem;
  border: 1px solid var(--tfi-light-grey);
  border-radius: 6px;
  background: var(--tfi-white);
}
.lockout-card.is-locked { border-color: var(--tfi-red); box-shadow: inset 0 0 0 1px var(--tfi-red); }
.lockout-card .hint { margin: 0.4rem 0 0.6rem; }

.lockout-row-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}
.lockout-name { font-weight: 600; }

.lockout-pill {
  padding: 0.1rem 0.5rem;
  border-radius: 999px;
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.pill-live { background: var(--tfi-green); color: var(--tfi-black); }
.pill-locked { background: var(--tfi-red); color: #fff; }

.lockout-meta {
  margin: 0.35rem 0 0.55rem;
  font-size: 0.78rem;
  color: var(--tfi-dark-grey);
}
.lockout-when { display: block; }
.lockout-reason { margin-top: 0.15rem; font-style: italic; color: var(--tfi-black-soft); }

.lockout-btn {
  margin-top: 0.4rem;
  padding: 0.35rem 0.8rem;
  border: 2px solid transparent;
  border-radius: 5px;
  font-weight: 600;
  cursor: pointer;
}
.lockout-btn:disabled { opacity: 0.5; cursor: default; }
.btn-lock { background: var(--tfi-red); color: #fff; }
.btn-lock:hover:not(:disabled) { background: var(--tfi-red-dark); }
.btn-unlock { background: var(--tfi-green); color: var(--tfi-black); }
.btn-unlock:hover:not(:disabled) { filter: brightness(0.95); }

.lockout-conn-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-top: 0.5rem;
}
.lockout-row {
  padding: 0.55rem 0.65rem;
  border: 1px solid var(--tfi-light-grey);
  border-radius: 5px;
  background: var(--tfi-cream);
}
.lockout-row.is-locked { border-color: var(--tfi-red); }
.lockout-row .lockout-btn { margin-top: 0.5rem; padding: 0.2rem 0.6rem; font-size: 0.8rem; }
