:root {
  --presenter-font-size: 18px;
  /* Recording-active accent — default dark theme uses vibrant lime.
     Theme presets override below for palette harmony (Josh 2026-04-21:
     "super lime green on top of Iowa vibe is really ugly"). */
  --recording-indicator-color: #00FF66;
  --recording-indicator-glow: rgba(0, 255, 102, 0.18);
  /* Engaged-state accent — used by toolbar buttons + composer Type btn
     when their panel is open (Joshua 2026-05-20: "show as highlighted
     the button itself"). Reading-mode overrides below remap to the
     iowa palette so the same .is-engaged class works across themes
     without per-rule conditionals. */
  --engaged-accent: #FF6600;
  --engaged-bg: #2a2a2a;
}
body.reading-mode {
  /* Iowa-accent / panel-alt only exist once a preset is selected
     (body.reading-mode[data-reading-preset="iowa"|"gruvbox"|...]).
     Provide a sensible fallback so engaged-state never paints a
     missing-var as transparent/black if reading-mode is enabled
     before a preset has been chosen. */
  --engaged-accent: var(--iowa-accent, #FF6600);
  --engaged-bg: var(--iowa-panel-alt, #2a2a2a);
}
* { margin: 0; padding: 0; box-sizing: border-box; }

body {
  font-family: 'VT323', monospace;
  background: #111;
  color: #e0e0e0;
  overflow: hidden;
  height: 100vh;
  display: flex;
  flex-direction: column;
}

/* === Titlebar === */
#titlebar {
  display: flex;
  align-items: center;
  background: #0a0a0a;
  height: 32px;
  flex-shrink: 0;
  border-bottom: 1px solid #222;
}
#titlebar-drag {
  flex: 1;
  -webkit-app-region: drag;
  padding: 0 10px;
  display: flex;
  align-items: center;
  gap: 8px;
  height: 100%;
}
#titlebar-text { font-size: 14px; color: #FF6600; text-transform: uppercase; letter-spacing: 1px; }
#queue-count { font-size: 13px; color: #FFCC00; }
#btn-hood {
  -webkit-app-region: no-drag;
  background: none; border: none;
  color: #00BFFF;
  font-size: 12px;
  font-family: 'VT323', monospace;
  cursor: pointer;
  padding: 2px 8px;
  letter-spacing: 0.5px;
  white-space: nowrap;
}
#btn-hood:hover { color: #FF6600; text-decoration: underline; }
#btn-minimize {
  -webkit-app-region: no-drag;
  background: none; border: none; color: #555;
  font-size: 16px; width: 32px; height: 32px;
  cursor: pointer; display: flex; align-items: center; justify-content: center;
}
#btn-minimize:hover { color: #fff; background: #222; }

/* Under the hood iframe */
#hood-iframe {
  display: none;
  flex: 1;
  width: 100%;
  border: none;
  background: #111;
}

/* Titlebar controls */
#titlebar-controls {
  -webkit-app-region: no-drag;
  display: flex; align-items: center; gap: 6px; padding: 0 4px; height: 100%;
}
#titlebar-controls input[type="range"] { width: 50px; height: 3px; cursor: pointer; accent-color: #FF6600; }
#titlebar-controls label { font-size: 10px; color: #555; display: flex; align-items: center; gap: 3px; white-space: nowrap; }
#titlebar-controls .ctrl-btn {
  background: none; border: 1px solid #333; color: #888;
  font-size: 11px; width: 18px; height: 18px; cursor: pointer;
  display: flex; align-items: center; justify-content: center; border-radius: 3px; padding: 0; line-height: 1;
}
#titlebar-controls .ctrl-btn:hover { color: #fff; border-color: #555; }
#titlebar-controls .ctrl-val { font-size: 10px; color: #aaa; min-width: 22px; text-align: center; }

/* === Inbox Container === */
#inbox-container {
  flex: 1;
  display: flex;
  overflow: hidden;
  position: relative;
}

/* === Conversation Panel === */
#conversation-panel {
  flex: 1;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  min-width: 0;
  position: relative;
}
#conv-header {
  padding: 8px 12px;
  border-bottom: 1px solid #222;
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
  background: #0d0d0d;
}
#conv-steward-icon { font-size: 28px; }
#conv-steward-name { font-size: 20px; color: #FF6600; flex: 1; }
#conv-steward-status { width: 10px; height: 10px; border-radius: 50%; }

#conv-thread {
  flex: 1;
  overflow-y: auto;
  padding: 10px;
  display: flex;
  flex-direction: column;
}
#conv-empty {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  color: #444;
}
#conv-empty .empty-icon { font-size: 48px; margin-bottom: 10px; }
#conv-empty .empty-text { font-size: 16px; }

/* === Chat Bubbles === */
.chat-row {
  display: flex;
  margin-bottom: 12px;
  max-width: 95%;
}
.chat-row.from-steward { justify-content: flex-start; }
.chat-row.from-user { justify-content: flex-end; margin-left: auto; }

.msg-bubble {
  padding: 12px 16px;
  border-radius: 0 0 12px 12px;
  position: relative;
}
.msg-bubble.from-steward {
  background: #111;
  border: 3px solid #00FF66;
  border-top: none;
}
.msg-bubble.from-user {
  background: #111;
  border: 3px solid #FF6600;
  border-top: none;
}
.msg-bubble.active { border-color: #00FF66; }
.msg-bubble.archived {
  opacity: 0.55;
  transition: opacity 0.3s, border-color 0.3s;
  border-width: 2px;
}
.msg-bubble.archived .msg-card-title { font-size: 15px; color: #999; }
.msg-bubble.archived .msg-body { font-size: 12px; line-height: 1.4; color: #777; }
.msg-bubble.archived .msg-body h1, .msg-bubble.archived .msg-body h2, .msg-bubble.archived .msg-body h3 { font-size: 13px; color: #888; }
.chat-row.archived-row { max-width: 85%; }
.chat-row.archived-row .msg-avatar-bar { padding: 2px 8px; font-size: 11px; border-width: 2px; }
.chat-row.archived-row .msg-avatar-bar .avatar-time-actual { font-size: 11px; font-weight: normal; color: #666; }
.chat-row.archived-row .msg-avatar-bar .avatar-time-relative { font-size: 10px; }
.msg-bubble.unread { border-color: #FFCC00; }
.msg-bubble.sent-local { opacity: 0.8; }
.msg-bubble.sent-confirmed { opacity: 1; }

/* Sent flash animation */
@keyframes sent-flash {
  0% { background: #FF660030; }
  100% { background: #111; }
}
.chat-row.just-sent .msg-bubble {
  animation: sent-flash 1.5s ease-out;
}
.sent-check {
  color: #00FF66;
  font-size: 12px;
  margin-left: 6px;
}

/* Avatar bar at top of bubble */
.msg-avatar-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 10px;
  border-radius: 12px 12px 0 0;
  font-size: 13px;
}
.msg-avatar-bar.steward { background: #00FF6620; border: 3px solid #00FF66; border-bottom: none; }
.msg-avatar-bar.user { background: #FF660020; border: 3px solid #FF6600; border-bottom: none; }
.msg-avatar-bar .avatar-icon { font-size: 18px; }
.msg-avatar-bar .avatar-name { color: #888; flex: 1; }
.msg-avatar-bar .avatar-source {
  font-size: 12px;
  color: #666;
  margin-left: -4px;
}
.msg-avatar-bar .avatar-source-sub {
  font-size: 15px;
  color: #FFCC00;
  font-weight: 600;
  margin-left: 0;
}
.msg-avatar-bar .avatar-time-block {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 1px;
  flex-shrink: 0;
}
.msg-avatar-bar .avatar-time-actual {
  color: #FFAA00;
  font-size: 15px;
  font-weight: bold;
}
.msg-avatar-bar .avatar-time-relative {
  color: #888;
  font-size: 13px;
}

/* Card title - large and prominent */
.msg-card-title {
  font-size: 22px;
  color: #FFCC00;
  font-weight: bold;
  margin-bottom: 8px;
  line-height: 1.3;
}

.msg-bubble .msg-header {
  display: flex; align-items: center; gap: 10px; margin-bottom: 6px;
}
.msg-bubble .msg-header-right { display: flex; align-items: center; gap: 6px; }
.msg-bubble .msg-title { font-size: 17px; color: #FFCC00; font-weight: bold; flex: 1; }
.msg-bubble .msg-time { font-size: 12px; color: #555; }
.msg-bubble .msg-category {
  font-size: 11px; padding: 1px 6px; border-radius: 4px; text-transform: uppercase;
}
.msg-bubble .msg-category.situation { background: #FF660030; color: #FF6600; }
.msg-bubble .msg-category.build { background: #00BFFF30; color: #00BFFF; }
.msg-bubble .msg-session { font-size: 11px; color: #444; margin-bottom: 4px; }

.msg-bubble .msg-body {
  font-size: var(--presenter-font-size, 16px); color: #ccc; line-height: 1.6; word-wrap: break-word;
}
.msg-bubble .msg-body h1, .msg-bubble .msg-body h2, .msg-bubble .msg-body h3 { color: #FFCC00; margin: 8px 0 4px; }
.msg-bubble .msg-body h1 { font-size: 20px; }
.msg-bubble .msg-body h2 { font-size: 17px; }
.msg-bubble .msg-body h3 { font-size: 15px; }
.msg-bubble .msg-body code { background: #2a2a2a; padding: 1px 4px; border-radius: 3px; font-size: 13px; color: #FFCC00; }
.msg-bubble .msg-body pre { background: #0a0a0a; padding: 10px; border-radius: 6px; overflow-x: auto; margin: 6px 0; border: 1px solid #333; }
.msg-bubble .msg-body pre code { background: none; padding: 0; color: #ccc; }
.msg-bubble .msg-body a { color: #00BFFF; text-decoration: underline; cursor: pointer; }
.msg-bubble .msg-body img { max-width: 100%; height: auto; display: block; margin: 8px 0; border-radius: 6px; }
.link-actions { display: inline; margin-left: 4px; }
.link-action-btn {
  display: inline;
  background: none;
  border: 1px solid #444;
  color: #666;
  font-family: 'VT323', monospace;
  font-size: 11px;
  padding: 0 4px;
  margin: 0 1px;
  border-radius: 3px;
  cursor: pointer;
  line-height: 1.4;
  vertical-align: middle;
}
.link-action-btn:hover { color: #aaa; border-color: #666; }
.link-action-btn:active { color: #00BFFF; }
.msg-bubble .msg-body ul, .msg-bubble .msg-body ol { padding-left: 24px; margin: 8px 0; }
.msg-bubble .msg-body ul { list-style: none; }
.msg-bubble .msg-body ul li { position: relative; padding-left: 4px; margin-bottom: 4px; }
.msg-bubble .msg-body ul li::before { content: '▸'; position: absolute; left: -16px; color: #FF6600; font-family: 'VT323', monospace; }
.msg-bubble .msg-body ol li { margin-bottom: 4px; }
.msg-bubble .msg-body li p { margin: 0; }
.msg-bubble .msg-body blockquote { border-left: 4px solid #FF6600; padding-left: 12px; color: #888; margin: 6px 0; }
.msg-bubble .msg-body strong { color: #FFCC00; }
.msg-bubble .msg-body table {
  width: 100%; border-collapse: collapse; margin: 10px 0;
  background: #0d0d0d; border: 1px solid #333; border-radius: 6px; overflow: hidden;
}
.msg-bubble .msg-body thead { background: #1a1a1a; }
.msg-bubble .msg-body th {
  text-align: left; padding: 8px 12px; border-bottom: 2px solid #FFCC0030;
  color: #FFCC00; font-size: 14px; letter-spacing: 0.02em;
}
.msg-bubble .msg-body td {
  padding: 6px 12px; border-bottom: 1px solid #1a1a1a; color: #ccc;
}
.msg-bubble .msg-body tr:nth-child(even) td { background: #14141480; }
.msg-bubble .msg-body tr:hover td { background: #1a1a1a; }

/* Inline response (inside same bubble) */
.msg-inline-response {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid #333;
}
.msg-inline-response.pending-response { opacity: 0.7; }
.msg-response-label {
  font-size: 12px;
  color: #888;
  margin-bottom: 4px;
}
.msg-response-button {
  color: #FF6600;
  font-size: 15px;
  font-weight: bold;
  margin-bottom: 2px;
}
.msg-response-text {
  color: #ccc;
  font-size: var(--presenter-font-size, 14px);
  line-height: 1.5;
}

/* === CARD-LEVEL INLINE UNDO ===
   DO NOT REDUCE SIZE, REMOVE BACKGROUND, OR REVERT TO TEXT-LINK STYLING.
   Joshua flagged the small/hidden undo regression on 2026-05-30 with the
   words "this fix got overwritten." It must read as a real button, with
   enough touch area (~44px) to be the obvious post-reply action and
   enough separation from the dismiss/pin row that it never disappears
   behind them. Centering the bar (not right-aligned) is part of the fix —
   right-aligned puts it in the same visual column as dismiss/pin.
   Any change here requires Joshua signoff. !important on size/background/
   border is intentional regression-armor. */
.inline-undo {
  margin-top: 10px;
  margin-bottom: 12px;
  padding-top: 10px;
  border-top: 1px solid #333;
  text-align: center;
}
.undo-link {
  display: inline-block !important;
  background: #FFCC00 !important;
  border: 2px solid #FFCC00 !important;
  color: #1a1a1a !important;
  font-size: 16px !important;
  font-weight: bold !important;
  cursor: pointer;
  padding: 10px 28px !important;
  min-height: 44px !important;
  border-radius: 8px !important;
  font-family: 'VT323', monospace;
  letter-spacing: 0.5px;
  box-shadow: 0 0 12px rgba(255, 204, 0, 0.35);
  transition: background 0.15s, transform 0.05s;
}
.undo-link:hover {
  background: #FFD633 !important;
  border-color: #FFD633 !important;
}
.undo-link:active { transform: translateY(1px); }

.thread-empty { text-align: center; color: #444; padding: 40px; font-size: 16px; }

/* Header action buttons (TTS, dismiss) */
.btn-tts, .btn-dismiss {
  background: #1a1a1a; border: 1px solid #333; color: #888;
  width: 28px; height: 28px; border-radius: 6px;
  cursor: pointer; display: flex; align-items: center; justify-content: center;
  font-size: 14px; transition: all 0.15s; padding: 0;
}
.btn-tts:hover { color: #FFCC00; border-color: #FFCC00; background: #FFCC0015; }
.btn-tts.playing { color: #00FF66; border-color: #00FF66; }
.btn-tts.loading { color: #FFCC00; opacity: 0.7; }
.btn-dismiss:hover { color: #FF3333; border-color: #FF3333; background: #FF333315; }

/* Response area */
.msg-response { margin-top: 10px; padding-top: 10px; border-top: 1px solid #2a2a2a; }
/* All buttons in presenter — dark by default */
#inbox-container button, .msg-bubble button {
  background: #1a1a1a;
  color: #aaa;
  border: 1px solid #333;
  font-family: 'VT323', monospace;
  cursor: pointer;
}

.msg-buttons { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.msg-btn {
  padding: 10px 20px; border-radius: 8px;
  border: 2px solid #FF660060; background: #1a1a1a; color: #FF6600;
  cursor: pointer; font-family: 'VT323', monospace; font-size: var(--presenter-font-size, 17px);
  transition: all 0.15s; letter-spacing: 0.5px;
}
.msg-btn:first-child { background: #FF660025; color: #FF6600; border-color: #FF6600; font-weight: bold; }
.msg-btn:first-child:hover { background: #FF6600; color: #000; }
.msg-btn:hover { background: #FF660030; color: #FF6600; border-color: #FF6600; transform: translateY(-1px); }
.msg-btn:active { transform: translateY(0); }
.msg-btn:disabled { opacity: 0.5; cursor: default; transform: none; }
.msg-btn.run-btn { border-color: #00FF6660; color: #00FF66; background: #00FF6615; }
.msg-btn.run-btn:hover { background: #00FF6630; color: #00FF66; border-color: #00FF66; }
.msg-btn.msg-btn-dismiss {
  margin-top: 10px;
  padding: 6px 16px;
  font-size: 13px;
  color: #555;
  border: 1px solid #333;
  background: none;
  opacity: 0.7;
}
.msg-btn.msg-btn-dismiss:hover {
  color: #FF3333;
  border-color: #FF333360;
  background: #FF333310;
  opacity: 1;
  transform: none;
}

/* --- Pin button (save card from bulk-dismiss) --- */
/* Unpinned: gray outline. Pinned: bright solid gold fill — unmistakable at
   a glance. Click = visible change in the same frame (optimistic update in
   the JS handler), with a bounce animation to reinforce. */
.msg-btn.msg-btn-pin {
  margin-top: 10px;
  margin-right: 8px;
  padding: 6px 14px;
  font-size: 13px;
  color: #888;
  border: 1px solid #333;
  background: none;
  opacity: 0.85;
}
.msg-btn.msg-btn-pin:hover {
  color: #FFCC00;
  border-color: #FFCC00;
  background: #FFCC0018;
  opacity: 1;
  transform: none;
}
/* Pinned button — solid gold fill, dark text, unmistakable */
.msg-btn.msg-btn-pin.pinned {
  color: #1a1a1a;
  border-color: #FFCC00;
  background: #FFCC00;
  font-weight: bold;
  opacity: 1;
  box-shadow: 0 0 12px rgba(255, 204, 0, 0.5);
}
.msg-btn.msg-btn-pin.pinned:hover {
  color: #1a1a1a;
  border-color: #FF9900;
  background: #FF9900;
  box-shadow: 0 0 16px rgba(255, 153, 0, 0.6);
}
/* Bounce animation — plays each time the pin state flips */
@keyframes pin-bounce {
  0%   { transform: scale(1); }
  35%  { transform: scale(1.2); }
  60%  { transform: scale(0.92); }
  100% { transform: scale(1); }
}
.msg-btn.msg-btn-pin.pin-bounce {
  animation: pin-bounce 280ms cubic-bezier(0.25, 1.5, 0.5, 1);
}

/* Pinned card visual state — unmistakable across the room:
   - Gold left-border (4px, was 3px)
   - Warm gold tinted background on the whole bubble
   - Soft outer glow so it stands out from non-pinned cards
   - Corner badge with 📌 */
.msg-bubble.pinned {
  position: relative;
  border-left: 4px solid #FFCC00;
  background: linear-gradient(
    90deg,
    rgba(255, 204, 0, 0.12) 0%,
    rgba(255, 204, 0, 0.04) 60%,
    rgba(255, 204, 0, 0.02) 100%
  );
  box-shadow:
    inset 0 0 0 1px rgba(255, 204, 0, 0.3),
    0 0 16px rgba(255, 204, 0, 0.15);
  transition: background 150ms ease, box-shadow 150ms ease, border-color 150ms ease;
}
.msg-pin-badge {
  position: absolute;
  top: 6px;
  right: 8px;
  font-size: 16px;
  background: #FFCC00;
  color: #1a1a1a;
  padding: 2px 6px;
  border-radius: 12px;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);
  z-index: 2;
  pointer-events: none;
  animation: pin-badge-in 250ms ease-out;
}
@keyframes pin-badge-in {
  from { opacity: 0; transform: scale(0.6) translateY(-4px); }
  to   { opacity: 1; transform: scale(1) translateY(0); }
}

.msg-input-wrap { margin-top: 10px; display: flex; flex-direction: column; gap: 6px; }

/* Per-card layout toggle — small chip at the top of the input wrap so it
   sits inside the card, right next to where Joshua is typing. */
.msg-layout-toggle {
  align-self: flex-start;
  background: #1a1a1a; color: #888; border: 1px solid #333;
  padding: 3px 8px; border-radius: 4px; font-family: 'VT323', monospace;
  font-size: 13px; line-height: 1; cursor: pointer;
  transition: color 0.15s, border-color 0.15s, background 0.15s;
}
.msg-layout-toggle:hover { color: #FF6600; border-color: #FF6600; background: #221409; }

.msg-input {
  width: 100%; background: #0d0d0d; border: 2px solid #333; color: #e0e0e0;
  padding: 12px; border-radius: 8px; font-family: 'VT323', monospace; font-size: var(--presenter-font-size, 16px);
  resize: vertical;
  /* Joshua wants a real writing surface — roughly half the visible card. */
  min-height: 35vh; max-height: 60vh;
}
.msg-input:focus { border-color: #FF6600; outline: none; }

.msg-input-row {
  display: flex; gap: 6px; align-items: flex-end;
}
.msg-input-row .msg-input { flex: 1; }

/* Side-by-side layout: content fills the left half, input fills the right
   half from top to bottom. Uses CSS grid so we don't have to restructure
   the existing bubble children — input-wrap explicitly grabs column 2,
   row 1 / -1; everything else implicitly flows into column 1. */
.msg-bubble.layout-side-by-side {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: 14px;
  align-items: start;
}
.msg-bubble.layout-side-by-side > .msg-input-wrap {
  grid-column: 2;
  grid-row: 1 / -1;
  margin-top: 0;
  align-self: stretch;
  display: flex;
  flex-direction: column;
  min-height: 100%;
}
.msg-bubble.layout-side-by-side > .msg-input-wrap > .msg-input-row {
  flex: 1;
}
.msg-bubble.layout-side-by-side > .msg-input-wrap .msg-input {
  /* Match the height of the left column; cap so it doesn't dominate
     huge cards. */
  min-height: 100%;
  max-height: 70vh;
}
/* Pin badge stays absolute inside a grid container — no fix needed, but the
   pin button (.btn-pin) etc. should respect column 1 so they don't escape. */
.msg-bubble.layout-side-by-side > .msg-pin-badge { grid-column: 1; grid-row: 1; }
.msg-send-btn {
  background: #FF6600 !important; color: #000 !important; border: none !important;
  width: 36px; height: 36px; border-radius: 50%;
  font-size: 18px; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0; transition: background 0.15s;
}
.msg-send-btn:hover { background: #FF8800 !important; }
.msg-send-btn.has-recording {
  background: var(--recording-indicator-color, #00FF66) !important;
  color: #000 !important;
  animation: recording-pulse 1.5s ease-in-out infinite;
}
.msg-send-btn.has-recording:hover {
  /* Keep hover bright in default; reading-mode has its own rule below. */
  background: #33FF88 !important;
}

/* Bottom persistent input */
#conv-bottom-input {
  display: flex; gap: 6px; padding: 8px 12px; align-items: center;
  border-top: 1px solid #222; background: #0d0d0d; flex-shrink: 0;
}

/* Target pill — shows which steward the next message will be sent to.
   Sits next to the send button so Joshua's send-hand sees where it's going.
   Theme-aware: default = dark surface, reading-mode presets repaint via
   --iowa-* tokens. Per-steward accent stays steady through --pill-color. */
/* Split-button children — buttons inside the pill. Only the pill itself is
   colored; the inner buttons are transparent tap-zones. No border/focus ring
   so the split visual stays smooth (Josh 2026-04-21: "ugly little border
   that makes it look really bad"). */
#conv-bottom-target-pill .pill-icon-half,
#conv-bottom-target-pill .pill-card-half {
  display: none; /* Desktop = classic pill (label mode). Embedded re-shows. */
  background: transparent;
  border: none;
  outline: none;
  padding: 0;
  margin: 0;
  color: inherit;
  font: inherit;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  -webkit-tap-highlight-color: transparent;
}
#conv-bottom-target-pill .pill-icon-half:focus,
#conv-bottom-target-pill .pill-card-half:focus,
#conv-bottom-target-pill .pill-icon-half:focus-visible,
#conv-bottom-target-pill .pill-card-half:focus-visible {
  outline: none;
  border: none;
  box-shadow: none;
}
#conv-bottom-target-pill .pill-divider {
  display: none; /* revealed only in embedded split mode */
  width: 1px;
  align-self: stretch;
  background: currentColor;
  opacity: 0.3;
}

/* Embedded mode: pill becomes a split button. */
body.embedded #conv-bottom-target-pill .pill-label { display: none; }
body.embedded #conv-bottom-target-pill .pill-icon-half,
body.embedded #conv-bottom-target-pill .pill-card-half {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  padding: 0 14px;
  min-width: 48px;
  /* On Android WebView a long-press on a <button> triggers the native
     text-selection / context callout. Suppress the native callout + selection
     so the pill's own tap gesture owns the interaction cleanly. */
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
  touch-action: manipulation;
}
/* Balanced halves — "equal distance from the edges" (Josh 2026-08-14, chose
   this from a 5-way visual pick). The two send-halves are EQUAL width (each
   fills half the pill, so hover/tap tint reaches both rounded corners), AND
   the icon + number sit the SAME distance in from their OUTER rounded corner.
   Just splitting 50/50 with both glyphs centered still read lopsided: the
   emoji is visually heavier and drifts toward its edge, so the icon looked
   closer to the left than the number was to the right. Fix: keep flex:1 1 0
   for equal halves, then push each glyph toward the divider by an equal inset
   — icon aligns to its right (toward center) with a right pad, number aligns
   to its left (toward center) with a matching left pad. That makes the two
   equidistant from the outer edges → optically balanced. (.pill-single
   zero-card state overrides this with higher specificity, so the lone
   icon-half still fills + centers in the whole pill.) */
body.embedded #conv-bottom-target-pill .pill-icon-half,
body.embedded #conv-bottom-target-pill .pill-card-half { flex: 1 1 0; }
body.embedded #conv-bottom-target-pill .pill-icon-half {
  justify-content: flex-end;
  padding: 0 16px 0 14px;
}
body.embedded #conv-bottom-target-pill .pill-card-half {
  justify-content: flex-start;
  padding: 0 14px 0 16px;
}
body.embedded #conv-bottom-target-pill .pill-divider {
  display: block;
}
/* Kill the per-half "little square box" around each button (Josh 2026-08-12:
   "both the icon and the number buttons have this weird little box around
   them… the boxes themselves should go away. The buttons can live without
   those boxes"). A reading-mode button reset was leaking a 1px border onto
   each half. Only the outer oval (+ its recording glow ring) should read as
   a shape — the halves are transparent tap-zones. The divider line between
   them stays (that's the intentional split, not a box). */
body.embedded #conv-bottom-target-pill .pill-icon-half,
body.embedded #conv-bottom-target-pill .pill-card-half {
  border: none !important;
  box-shadow: none !important;
  outline: none !important;
  background: transparent !important;
}
body.embedded #conv-bottom-target-pill {
  padding: 0;
  gap: 0;
  overflow: hidden;
  height: 48px;
  /* R14 patch (Josh 2026-04-21): kill the "dorky orange border" around
     the split pill. Each half owns its own visual via background tint;
     the outer 2px border was redundant + janky when hovered. */
  border: none !important;
  box-shadow: none !important;
  /* Explicit minimum width so BOTH halves are fully tappable even when
     the right group squeezes. Josh: "the pill got really squished… I
     can't even see the card portion very well. I have to like kind of
     guess as to where it's at and click on it." */
  min-width: 120px;
  max-width: none;
  flex-shrink: 0;
}
body.embedded #conv-bottom-target-pill .pill-icon-half .pill-icon {
  font-size: 24px;
  line-height: 1;
}

/* WORKER badge (Josh 2026-09-10) — "the first two letters of the worker's name
   should be the big thing that takes up a lot of the room, [and] the icon
   should be small, and kind of almost like attached at the corner. Like,
   almost like a little badge. That way I can know which setting I'm in, and
   then the actual first two letters will tell me which worker I'm associated
   to."

   Only applied when JS has resolved the selection to a worker (it adds
   .pill-icon-worker). On a top-level steward none of this matches and the pill
   keeps its plain single-glyph rendering, unchanged.

   overflow is deliberately NOT clipped here: the badge sits on the icon's
   top-right corner and may overhang a hair. The pill itself has
   `overflow: hidden`, so the badge is inset just enough to stay inside it. */
body.embedded #conv-bottom-target-pill .pill-icon.pill-icon-worker {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* NO padding to "make room" for the badge. Josh 2026-09-11 compared this
     against the Android version and the Android one won: "the letters are
     centered but the icon is in the upper right. Really simple." Reserving
     corner space here shoved the letters ~13px off-centre in their tap zone,
     which is exactly the "pushed way over to the left" he called out. The
     badge is absolutely positioned, so it costs no layout space and the
     letters stay centred on their own. */
}
body.embedded #conv-bottom-target-pill .pill-worker-letters {
  /* Inherit the pill's theme font like .pill-card-num does — no hard-coded
     families (Josh 2026-04-21). */
  font-family: inherit;
  font-size: 21px;
  font-weight: 700;
  letter-spacing: 0.5px;
  line-height: 1;
  color: var(--pill-fg);
}
body.embedded #conv-bottom-target-pill .pill-worker-badge {
  position: absolute;
  /* Pinned to the TOP-RIGHT of the letters and nothing more — no disc, no ring,
     no backing colour. The emoji sits on the pill's own surface exactly the way
     the Android one does. The previous disc + ring read to Josh as a "weird
     white circle" (under his gruvbox reading preset the backing resolved to
     near-white) and made the icon look like it was "hanging out". An emoji
     already carries its own colour and needs no plate behind it. */
  top: -4px;
  right: -6px;
  font-size: 13px;
  line-height: 1;
  pointer-events: none;
}
body.embedded #conv-bottom-target-pill .pill-card-half .pill-card-num {
  /* Theme-adjustable: inherit from pill (sans-serif default, Iowa Old Style
     under reading-mode, etc.). Josh 2026-04-21: NO hard-coded fonts. */
  font-family: inherit;
  font-size: 22px;
  font-weight: 700;
  letter-spacing: 0.5px;
  font-variant-numeric: tabular-nums;
  color: var(--pill-fg);
  min-width: 22px;
  text-align: center;
}
/* Smooth hover (Josh 2026-04-21): no border flash, just a subtle
   background shift. Theme-aware via var — default gets an orange tint,
   reading-mode gets a parchment tint derived from the active palette. */
body.embedded #conv-bottom-target-pill .pill-icon-half:hover,
body.embedded #conv-bottom-target-pill .pill-card-half:hover {
  background: var(--pill-half-hover-bg, rgba(255, 102, 0, 0.08));
}
body.embedded #conv-bottom-target-pill .pill-icon-half:active,
body.embedded #conv-bottom-target-pill .pill-card-half:active {
  background: var(--pill-half-active-bg, rgba(255, 102, 0, 0.16));
}
body.reading-mode {
  --pill-half-hover-bg: rgba(0, 0, 0, 0.05);
  --pill-half-active-bg: rgba(0, 0, 0, 0.10);
}
body.reading-mode[data-reading-preset="gruvbox"],
body.reading-mode[data-reading-preset="gruvbox-hard"] {
  --pill-half-hover-bg: rgba(254, 128, 25, 0.10);
  --pill-half-active-bg: rgba(254, 128, 25, 0.18);
}
body.embedded #conv-bottom-target-pill.card-disabled .pill-card-half {
  opacity: 0.35;
  cursor: not-allowed;
}
/* Zero cards to display → the card-number half is removed entirely (there's no
   card to respond to). The [hidden] attr is set from JS on steading-switch;
   these override the embedded `display: inline-flex/block` rules above so the
   half + its divider actually disappear, leaving only the message-the-steward
   icon-half. (Josh 2026-08-11 empty-steading count-button fix.) */
body.embedded #conv-bottom-target-pill .pill-card-half[hidden],
body.embedded #conv-bottom-target-pill .pill-divider[hidden] {
  display: none !important;
}
/* Zero-card state → collapse the split pill into ONE solid button. The
   two-half min-width (120px) leaves dead space when only the icon-half
   remains, and the recording glow/pulse (applied to the whole pill) lights
   up that empty space — "dorky." With only one option, make it a single
   solid button: drop the min-width and let the lone icon-half fill the pill
   so the glow wraps the one live button. (Josh 2026-08-11.) */
body.embedded #conv-bottom-target-pill.pill-single {
  min-width: 0;
}
body.embedded #conv-bottom-target-pill.pill-single .pill-icon-half {
  flex: 1 1 auto;
  width: 100%;
  /* Re-center the lone icon: the two-half balance rule aligns the icon to
     flex-end (toward the divider) with an asymmetric pad, but in single mode
     there's no divider — the icon must sit centered in the full-width pill. */
  justify-content: center;
  padding: 0 14px;
}

/* Arm/ready state — Josh asked for visual feedback that tapping a send
   button will actually do something. When the pill is "armed" (textarea
   has staged text OR a recording is active), halves brighten to the
   pulse-green used by the existing .has-recording indicator. Otherwise
   they dim to 60% opacity, signaling "nothing will happen."
   Gated by body.pill-armed (toggled from JS). */
body.embedded #conv-bottom-target-pill .pill-icon-half,
body.embedded #conv-bottom-target-pill .pill-card-half {
  transition: opacity 120ms ease, color 120ms ease, background 120ms ease;
}
body.embedded:not(.pill-armed) #conv-bottom-target-pill .pill-icon-half,
body.embedded:not(.pill-armed) #conv-bottom-target-pill .pill-card-half {
  opacity: 0.55;
}
body.embedded.pill-armed #conv-bottom-target-pill .pill-card-half .pill-card-num {
  color: var(--recording-indicator-color, #00FF66);
}
body.embedded.pill-armed #conv-bottom-target-pill {
  /* Armed state — a glowing ring that wraps the WHOLE oval (Josh 2026-08-12:
     "there should be a glowing ring around all the buttons, whether there's
     one or two"). Two hard-won lessons baked in here:

     1. The base embedded pill rule (~line 820) sets `box-shadow: none
        !important` to kill the old dorky orange border. So this ring MUST be
        `!important` too or it silently loses and NO glow shows at all
        (Josh 2026-08-12: "now the glow is entirely gone").
     2. The ring's crisp edge is drawn INSET, not outset. An outset ring gets
        clipped by an ancestor (#conv-bottom-input has overflow-y:hidden), so
        the top/bottom of an outer ring vanishes. Inset lives inside the pill's
        own box → never clipped. The earlier "only see it on the right" bug was
        NOT the inset itself — it was the two halves painting their own fill
        over the inset ring. Now the halves are transparent (see the box-kill
        rule above), so the inset ring shows crisp on ALL four sides.
     A soft OUTER bloom rides along on top for the halo; if an ancestor clips
     part of the bloom, the crisp inset ring still fully wraps the oval. */
  box-shadow:
    inset 0 0 0 2px var(--recording-indicator-color, #00FF66),
    0 0 10px 2px color-mix(in srgb, var(--recording-indicator-color, #00FF66) 55%, transparent) !important;
  /* Let the outer bloom breathe where ancestors allow it. */
  overflow: visible;
}
/* Recording pulse — while a recording is active the ring softly breathes.
   Same inset-crisp-ring + outer-bloom, animated. Also !important so it wins
   over the base `box-shadow: none !important`. */
body.embedded.pill-armed.pill-armed-recording #conv-bottom-target-pill {
  animation: pill-ring-pulse 1.5s ease-in-out infinite;
}
@keyframes pill-ring-pulse {
  0%, 100% {
    box-shadow:
      inset 0 0 0 2px var(--recording-indicator-color, #00FF66),
      0 0 6px 1px color-mix(in srgb, var(--recording-indicator-color, #00FF66) 45%, transparent) !important;
  }
  50% {
    box-shadow:
      inset 0 0 0 2px var(--recording-indicator-color, #00FF66),
      0 0 16px 5px color-mix(in srgb, var(--recording-indicator-color, #00FF66) 70%, transparent) !important;
  }
}

#conv-bottom-target-pill {
  --pill-bg: #151515;
  --pill-fg: var(--pill-color, #FF6600);
  --pill-border: var(--pill-color, #FF6600);
  display: inline-flex; align-items: center; gap: 6px;
  padding: 8px 12px; border-radius: 999px;
  background: var(--pill-bg);
  border: 2px solid var(--pill-border);
  color: var(--pill-fg);
  /* Steady, theme-neutral font — no VT323 cartoon. System stack in the
     default dark theme; reading-mode's body serif flows through naturally
     since nothing below overrides font-family for the pill. */
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
  font-size: 15px; font-weight: 600; letter-spacing: 0.01em;
  white-space: nowrap; flex-shrink: 0;
  max-width: 45%; overflow: hidden; text-overflow: ellipsis;
  cursor: default; user-select: none;
}
#conv-bottom-target-pill .pill-icon {
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 20px; line-height: 1; flex-shrink: 0;
  /* Emoji glyphs shouldn't inherit the pill's accent color. */
  color: initial;
}
#conv-bottom-target-pill .pill-label {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
#conv-bottom-target-pill.is-empty {
  --pill-fg: #777;
  --pill-border: #333;
}

/* Reading-mode repaint — pill pulls theme tokens so it looks native to
   Iowa / Gruvbox / Gruvbox Hard. Per-steward --pill-color still drives
   the accent border + text so identity stays recognizable across themes. */
body.reading-mode #conv-bottom-target-pill {
  --pill-bg: var(--iowa-panel);
  --pill-fg: var(--pill-color, var(--iowa-accent));
  --pill-border: var(--pill-color, var(--iowa-border));
  /* Inherit the reading-mode body's iA Writer serif stack for steady feel. */
  font-family: inherit;
}
body.reading-mode #conv-bottom-target-pill.is-empty {
  --pill-fg: var(--iowa-text-faint);
  --pill-border: var(--iowa-border);
}
#conv-bottom-textarea {
  flex: 1; background: #111; border: 2px solid #333; color: #e0e0e0;
  padding: 10px; border-radius: 8px; font-family: 'VT323', monospace;
  font-size: var(--presenter-font-size, 16px); resize: none;
  min-height: 40px; max-height: 100px;
}
#conv-bottom-textarea:focus { border-color: #FF6600; outline: none; }
#conv-bottom-send {
  background: #FF6600 !important; color: #000 !important; border: none !important;
  width: 40px; height: 40px; border-radius: 50%;
  font-size: 20px; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
#conv-bottom-send:hover { background: #FF8800 !important; }
/* Embedded: standalone ➤ is redundant with split-pill left-half
   (Josh 2026-04-21). Hidden from layout but kept in DOM because the
   pill left-half's handler does `bottomSend.click()` — element.click()
   works on display:none elements, so the flow is preserved without
   visual duplication. */
body.embedded #conv-bottom-send { display: none !important; }

/* Smart send button — pulses when recording detected */
#conv-bottom-send.has-recording {
  background: var(--recording-indicator-color, #00FF66) !important;
  color: #000 !important;
  animation: recording-pulse 1.5s ease-in-out infinite;
}
/* Restored 2026-09-01: deleted as collateral in d29dfdc36a (dead-sidebar
   removal, 2026-08-29) while FOUR live rules still referenced it — the
   yellow working/status dots + card-nav ring. A CSS animation naming an
   undefined keyframe fails SILENTLY (no error, element just sits static),
   which is why this read as "the blinking stopped" with nothing broken.
   Josh 2026-09-01: "they used to kind of fade in and out... now they're
   not moving at all." Original definition restored verbatim. */
@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.4; }
}

@keyframes recording-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(0, 255, 102, 0.4); }
  50% { box-shadow: 0 0 12px 4px rgba(0, 255, 102, 0.6); }
}

/* Resolved feedback */
.msg-feedback {
  margin-top: 8px; padding: 6px 10px; background: #0d0d0d;
  border-radius: 6px; border-left: 3px solid #00FF66;
}
.msg-feedback .fb-button { color: #00FF66; font-size: 13px; }
.msg-feedback .fb-text { color: #aaa; font-size: 13px; margin-top: 2px; }
.msg-feedback.dismissed { border-left-color: #666; }
.msg-feedback.dismissed .fb-button { color: #666; }

/* === Undo Toast === */
#undo-toast {
  position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%);
  background: #1a1a1a;
  color: #e0e0e0;
  padding: 10px 14px;
  border: 1px solid #333;
  border-radius: 10px;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.5);
  z-index: 10050;
  display: none;
  flex-direction: column;
  gap: 6px;
  font-family: inherit; /* theme-aware */
  font-size: 14px;
  min-width: 220px;
  max-width: min(420px, 92vw);
}
/* Embedded: bottom bar lives at the bottom of the viewport and is ~56-60px
   tall with the new bigger buttons. Push the toast ABOVE the bar so it's
   actually visible (Josh R11). */
body.embedded #undo-toast { bottom: 84px; }
#undo-toast.visible { display: flex; }
#undo-toast .undo-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 2px 0;
}
#undo-toast .undo-msg {
  flex: 1;
  font-weight: 500;
  letter-spacing: 0.2px;
}
#undo-toast .undo-msg-pending { color: #FFCC00; }
#undo-toast .undo-msg-success { color: #8fd19e; }
#undo-toast .undo-msg-failed { color: #ff8a8a; }
#undo-toast .undo-sep {
  color: #555;
  font-size: 13px;
}
#undo-toast .undo-countdown {
  color: #888;
  font-variant-numeric: tabular-nums;
  font-size: 13px;
  min-width: 24px;
  text-align: center;
}
#undo-toast button,
#undo-toast .undo-btn {
  background: transparent;
  color: #FF6600;
  border: 1px solid #FF6600;
  padding: 5px 14px;
  border-radius: 6px;
  cursor: pointer;
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.3px;
  transition: background 0.1s, color 0.1s;
}
#undo-toast button:hover,
#undo-toast .undo-btn:hover {
  background: #FF6600;
  color: #000;
}
body.reading-mode #undo-toast {
  background: var(--iowa-panel);
  border-color: var(--iowa-border);
  color: var(--iowa-text);
}
body.reading-mode #undo-toast .undo-sep { color: var(--iowa-border); }
body.reading-mode #undo-toast .undo-countdown { color: var(--iowa-text-faint); }
body.reading-mode #undo-toast .undo-msg-pending { color: var(--iowa-gold-dark); }
body.reading-mode #undo-toast .undo-msg-success { color: var(--iowa-sage); }
body.reading-mode #undo-toast .undo-msg-failed { color: var(--iowa-red); }
body.reading-mode #undo-toast button,
body.reading-mode #undo-toast .undo-btn {
  color: var(--iowa-accent);
  border-color: var(--iowa-accent);
}
body.reading-mode #undo-toast button:hover,
body.reading-mode #undo-toast .undo-btn:hover {
  background: var(--iowa-accent);
  color: var(--iowa-bg);
}

/* === Shared upper-left Undo pill ===
   Joshua 2026-06-02: every undoable action (voice-send, dismiss, reply) shows
   its undo button in the SAME upper-left spot above the active card. The
   underlying timer/rollback logic still lives in startUndoSend /
   startUndoSendWithRollback / showInlineUndo — this is the unified visual
   surface, not a new state machine. Mocked + signed off in card de4f9f0655b0. */
#undo-pill-slot {
  position: fixed;
  /* Joshua 2026-09-05 (V6): VERTICAL STACK anchored to the CARD's upper-right.
     Replaces the V5 full-width horizontal strip at bottom:72px, whose whole
     problem was that it sat exactly on top of the steward row (#embedded-topbar)
     and the worker row (#embedded-subbar) — measured overlap, not a guess.
     Killing that obstruction is the entire point of this change.

     Josh's words: the undos "pop over" the card's background steading icon and
     "march down the right side of the card", newest on top, each one sliding
     UP and out as it expires while the ones below move up to fill the gap.

     WHY fixed + JS-measured instead of a child of the card frame:
     .mobile-deck-card is overflow:hidden (it is the authoritative clip box for
     the deck), so a pill parented to it would be SHAVED at the card edge — the
     same clipping trap documented on #embedded-subbar. So the slot stays
     position:fixed at the root and positionUndoSlotToCard() (app.js) writes the
     card's live top/right edges into --undo-anchor-top / --undo-anchor-right.
     Falls back to sane viewport values when no deck card is mounted.

     - Column, top-anchored: DOM order is newest-first (app.js insertBefore
       firstChild), so newest renders at TOP and older ones march down. That
       preserves the existing insertion semantics exactly.
     - align-items:flex-end so pills hug the card's right edge and grow leftward.
     - pointer-events:none on the slot; pills re-enable on themselves, so the
       empty column never eats a tap meant for the card underneath. */
  /* DESKTOP FALLBACK (the 0 default). The card deck is mobile-only — it is
     built behind an isMobile() gate in applyMobileDeckImpl — so on desktop
     there is no card frame to hug and positionUndoSlotToCard() leaves these
     unset. Anchoring flush to the VIEWPORT's right edge keeps the identical
     tag emerging from the identical side, which is what Josh's "same between
     desktop and mobile" is actually about: the tag, not a card offset that
     desktop has no equivalent for. */
  top: var(--undo-anchor-top, 96px);
  /* The embedded deck card is full-bleed and lands a hair WIDER than the visual
     viewport (measured live: card.right 375.0 vs innerWidth 374), so an
     edge-welded tag spilled its seconds tail ~1px off-screen. max() floors the
     anchor at 1px so the tail is always fully on screen, however the card's
     box happens to round. NOT env(safe-area-inset-right) — that resolves to 0
     in this WebView and would silently do nothing. */
  right: max(1px, var(--undo-anchor-right, 0px));
  left: auto;
  bottom: auto;
  /* Never let a tag push the stack across the whole card. */
  max-width: min(72vw, 300px);
  box-sizing: border-box;
  z-index: 10060;
  pointer-events: none;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  justify-content: flex-start;
  gap: 6px;
  /* Visible so the enter/exit transforms aren't clipped. The stack is capped
     by JS (MAX_VISIBLE_UNDO_PILLS) rather than scrolled, so it can never run
     down over the toolbar — a scroller here would also re-introduce the
     overflow-clip trap that crops the pills' shadow. */
  overflow: visible;
}
/* V7 palette hooks (Josh 2026-09-06). The tag must look deliberate on ALL FOUR
   themes — default, iowa, gruvbox, gruvbox-hard — so every colour it paints is
   a token, never a literal. Josh caught this: the V6 seconds shade was a fixed
   black, which is a dark smudge on iowa's cream tag.
   `--undo-tag-ink` is the CARD's own background, so the tail can be a wash of
   it (currentColor at low alpha) instead of a hardcoded black. */
#undo-pill-slot {
  --undo-tag-bg:  #FFCC00;
  --undo-tag-ink: #1a1a1a;
}
body.reading-mode #undo-pill-slot {
  --undo-tag-bg:  var(--iowa-accent, #7A5C3E);
  --undo-tag-ink: var(--iowa-bg, #F4EFE6);
}
#undo-pill-slot .undo-pill {
  pointer-events: auto;
  /* V7: a small tag PEEKING IN from the card's right edge. Josh 2026-09-06:
     "literally just emerging from the side of the card like it's peeking at
     us". Two shaded halves — .undo-pill-core (the button) and
     .undo-pill-countdown (a darker tail hanging off the end).
     The right side is squared and border-less because that edge is welded to
     the card's edge; only the left corners round, so it reads as emerging FROM
     the card rather than floating on it. */
  display: inline-flex;
  align-items: stretch;
  justify-content: flex-end;
  gap: 0;
  padding: 0;
  max-width: 100%;
  background: var(--undo-tag-bg);
  color: var(--undo-tag-ink);
  border: 2px solid var(--undo-tag-bg);
  border-right: 0;
  font-family: inherit;
  font-size: 11px;
  font-weight: 700;
  min-height: 26px;
  border-radius: 9px 0 0 9px;
  overflow: hidden;
  /* Shadow thrown LEFT (away from the card edge it's welded to), so the tag
     reads as lifting off the card's side rather than hovering over it. */
  box-shadow: -2px 2px 8px rgba(0, 0, 0, 0.34);
  letter-spacing: 0.2px;
  cursor: pointer;
  white-space: nowrap;
  flex-shrink: 0;
  /* Grows FROM the edge it emerges from. */
  transform-origin: top right;
  animation: undo-pill-enter 240ms cubic-bezier(0.34, 1.56, 0.64, 1);
  transition: background 0.1s, border-color 0.1s, transform 80ms ease, opacity 200ms ease;
}
/* The two halves. */
#undo-pill-slot .undo-pill .undo-pill-core {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 7px 3px 8px;
  min-width: 0;
}
/* Label is blank at rest (the ↺ carries the meaning) and only takes space when
   setCommitting/setSuccess/setFailed writes a word into it. :empty is what
   keeps the resting tag tiny without deleting the node those methods drive. */
#undo-pill-slot .undo-pill .undo-pill-label:empty { display: none; }
/* `display: inline-flex` above beats the UA's `[hidden] { display: none }`, so
   the capped-out pills stayed VISIBLE even though enforceUndoStackCap() had set
   el.hidden — caught on the real screen with N=7. Restore the contract. */
#undo-pill-slot .undo-pill[hidden] { display: none !important; }
/* Depth cue: older pills further down the stack sit back very slightly, so N
   stacked reads as a deck marching down the card rather than a flat list.
   Floors at the 4th so nothing becomes illegible. */
/* The 2026-09-05 mockup variants (body.undo-v-compact / .undo-v-inset) are
   GONE. Josh picked inset-from-the-right on 2026-09-06, so it is the single
   permanent behavior above rather than a flag — nothing in the product ever
   set those classes; they existed only for the screenshot harness. */
#undo-pill-slot .undo-pill:nth-child(2) { opacity: 0.94; }
#undo-pill-slot .undo-pill:nth-child(3) { opacity: 0.88; }
#undo-pill-slot .undo-pill:nth-child(n+4) { opacity: 0.82; }
/* Overflow counter — "+N more" — shown when more undos are live than the cap
   displays, so the stack can never march down over the toolbar. */
#undo-pill-slot .undo-pill-overflow {
  pointer-events: none;
  align-self: flex-end;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.3px;
  padding: 2px 8px;
  border-radius: 7px;
  background: rgba(255, 204, 0, 0.92);
  color: #1a1a1a;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.35);
  animation: undo-pill-enter 200ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
#undo-pill-slot .undo-pill.leaving {
  /* V6 exit — Josh's spec verbatim: "as they disappear they move up, they
     transition smoothly up and out". app.js sets .leaving, waits for this
     transition, then detaches; the survivors below then slide UP into the
     freed space via the FLIP pass in reflowUndoStack(). V5 translated DOWN,
     which is the opposite of what he described. */
  opacity: 0;
  transform: translateY(-14px) scale(0.9);
  pointer-events: none;
  transition: opacity 220ms ease, transform 220ms cubic-bezier(0.4, 0, 1, 1);
}
/* V7 enter: the tag SLIDES IN FROM THE RIGHT EDGE — Josh 2026-09-06,
   "literally just emerging from the side of the card like it's peeking at us".
   V6 dropped it in from above, which reads as a toast landing on the card
   rather than something peeking out of its edge. Horizontal, with a small
   overshoot so it peeks out and settles back. */
@keyframes undo-pill-enter {
  0%   { opacity: 0; transform: translateX(100%); }
  65%  { opacity: 1; transform: translateX(-3px); }
  100% { opacity: 1; transform: translateX(0); }
}
#undo-pill-slot .undo-pill:hover {
  filter: brightness(1.08);
}
#undo-pill-slot .undo-pill:active { transform: translateY(1px); }
#undo-pill-slot .undo-pill .undo-pill-icon { font-size: 13px; line-height: 1; }
#undo-pill-slot .undo-pill .undo-pill-label { letter-spacing: 0.3px; }
/* Steward identity as its own glyph — the same icon as the steward row. */
#undo-pill-slot .undo-pill .undo-pill-steward {
  font-size: 12px;
  line-height: 1;
}
/* Bare card number (no "of N" since V7 — the total carried no decision). */
#undo-pill-slot .undo-pill .undo-pill-num {
  font-size: 11px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  opacity: 0.9;
}
/* Legacy sublabel node: V7 never renders one (identity is the glyph + number,
   and the full string lives on the tag's title). Kept hidden rather than
   deleted so an older caller passing `sublabel` can't blow the tag wide. */
#undo-pill-slot .undo-pill .undo-pill-sublabel { display: none; }
/* The seconds — a darker tail hanging off the end. currentColor at low alpha,
   so it is a wash of whatever the active theme's ink is rather than a fixed
   black (which was invisible-adjacent on iowa's cream tag). */
#undo-pill-slot .undo-pill .undo-pill-countdown {
  display: inline-flex;
  align-items: center;
  padding: 3px 7px;
  background: color-mix(in srgb, currentColor 14%, transparent);
  font-size: 11px;
  font-weight: 600;
  opacity: 0.9;
  font-variant-numeric: tabular-nums;
  min-width: 18px;
  text-align: right;
}
/* Nothing to show mid-commit — collapse the tail so "Sending…" doesn't sit
   next to an empty shaded box. */
#undo-pill-slot .undo-pill .undo-pill-countdown:empty { display: none; }
#undo-pill-slot .undo-pill.committing {
  background: var(--undo-pill-committing-bg, #2a2a2a);
  color: var(--undo-pill-committing-fg, #FFCC00);
  border-color: var(--undo-pill-committing-fg, #FFCC00);
  cursor: default;
  box-shadow: none;
}
#undo-pill-slot .undo-pill.success {
  background: var(--undo-pill-success-bg, #163d20);
  color: var(--undo-pill-success-fg, #8fd19e);
  border-color: var(--undo-pill-success-fg, #8fd19e);
  cursor: default;
  box-shadow: none;
}
#undo-pill-slot .undo-pill.failed {
  background: var(--undo-pill-failed-bg, #3d1616);
  color: var(--undo-pill-failed-fg, #ff8a8a);
  border-color: var(--undo-pill-failed-fg, #ff8a8a);
  box-shadow: none;
}
/* Reading-mode pill — must use !important to beat the NUCLEAR
   `body.reading-mode button` rule at L3742. Same regression-armor
   pattern as .undo-link (L3796). Tokens fall back when no preset is set. */
body.reading-mode #undo-pill-slot .undo-pill {
  background: var(--undo-tag-bg) !important;
  color: var(--undo-tag-ink) !important;
  /* Per-side, NOT the `border` shorthand: the shorthand re-adds the right
     border that welds the tag to the card edge, which would float the tag off
     the edge on all three reading themes. */
  border-top: 2px solid var(--undo-tag-bg) !important;
  border-bottom: 2px solid var(--undo-tag-bg) !important;
  border-left: 2px solid var(--undo-tag-bg) !important;
  border-right: 0 !important;
  box-shadow: -2px 2px 8px rgba(0, 0, 0, 0.22) !important;
}
body.reading-mode #undo-pill-slot .undo-pill:hover {
  filter: brightness(1.08);
}
body.reading-mode #undo-pill-slot .undo-pill.committing {
  background: var(--iowa-panel) !important;
  color: var(--iowa-accent) !important;
  border-color: var(--iowa-accent) !important;
  box-shadow: none !important;
}
body.reading-mode #undo-pill-slot .undo-pill.success {
  background: var(--iowa-panel) !important;
  color: var(--iowa-sage, #6a8c5e) !important;
  border-color: var(--iowa-sage, #6a8c5e) !important;
  box-shadow: none !important;
}
body.reading-mode #undo-pill-slot .undo-pill.failed {
  background: var(--iowa-panel) !important;
  color: var(--iowa-red, #8E4A3E) !important;
  border-color: var(--iowa-red, #8E4A3E) !important;
  box-shadow: none !important;
}

/* === Error Toast === */
#error-toast {
  position: fixed; bottom: 70px; left: 50%; transform: translateX(-50%);
  background: #441111; border: 1px solid #ff4444; color: #ff6666;
  padding: 10px 16px; border-radius: 8px;
  font-size: 13px; font-family: 'VT323', monospace;
  z-index: 3000; display: none; align-items: center; gap: 10px;
  max-width: 90vw; box-shadow: 0 4px 12px rgba(255,0,0,0.3);
}
#error-toast.visible { display: flex; }
.error-toast-msg { flex: 1; }
.error-toast-retry {
  background: #ff4444; color: #000; border: none;
  padding: 4px 12px; border-radius: 4px; cursor: pointer;
  font-family: 'VT323', monospace; font-size: 13px; font-weight: bold;
  white-space: nowrap;
}
.error-toast-retry:hover { background: #ff6666; }
.error-toast-dismiss {
  background: none; border: none; color: #ff6666; cursor: pointer;
  font-size: 16px; padding: 0 4px; line-height: 1;
}
.error-toast-dismiss:hover { color: #fff; }

/* Name toast (iter4) — brief, neutral, self-dismissing pill that reveals a
   truncated worker's full name. Centered above the subbar. Inherits the theme
   font (no hardcoded VT323). */
#name-toast {
  position: fixed;
  bottom: 96px;
  left: 50%;
  transform: translateX(-50%) translateY(6px);
  background: #2b2b2b;
  border: 1px solid rgba(255, 255, 255, 0.22);
  color: #f0f0f0;
  padding: 7px 14px;
  border-radius: 9px;
  font-size: 13px;
  font-family: inherit;
  z-index: 3000;
  max-width: 90vw;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.45);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.14s ease, transform 0.14s ease;
}
#name-toast.visible {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}
body.reading-mode #name-toast {
  background: var(--iowa-text, #333);
  color: var(--iowa-bg, #fff);
  border-color: color-mix(in srgb, var(--iowa-text) 30%, transparent);
}

/* === StewInt Drag Handle === */
#stewint-drag-handle {
  width: 5px;
  flex-shrink: 0;
  background: #1a1a1a;
  cursor: col-resize;
  position: relative;
  z-index: 10;
}
#stewint-drag-handle:hover,
#stewint-drag-handle.dragging {
  background: #FF6600;
}
#stewint-drag-handle::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 3px;
  height: 30px;
  border-left: 1px solid #555;
  border-right: 1px solid #555;
}
#stewint-drag-handle:hover::after,
#stewint-drag-handle.dragging::after {
  border-left-color: #fff;
  border-right-color: #fff;
}

/* === StewInt Right Panel === */
#stewint-panel {
  width: 400px;
  min-width: 200px;
  max-width: 80%;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  border-left: none;
  background: #0a0a0a;
  overflow: hidden;
}
#stewint-iframe { background: #111; }
#stewint-header {
  display: flex;
  align-items: center;
  padding: 4px 8px;
  border-bottom: 1px solid #222;
  flex-shrink: 0;
  gap: 4px;
}
#stewint-tabs {
  flex: 1;
  display: flex;
  gap: 2px;
}
.stewint-tab {
  background: none !important;
  border: 1px solid transparent !important;
  color: #555 !important;
  font-size: 13px;
  padding: 4px 10px;
  border-radius: 4px;
  cursor: pointer;
  font-family: 'VT323', monospace;
  text-transform: uppercase;
  letter-spacing: 1px;
}
.stewint-tab:hover { color: #888 !important; }
.stewint-tab.active {
  color: #FF6600 !important;
  border-color: #FF660040 !important;
  background: #FF660010 !important;
}
#stewint-toggle {
  background: #1a1a1a !important;
  border: 1px solid #333 !important;
  color: #888 !important;
  width: 24px; height: 24px;
  border-radius: 4px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
}
#stewint-toggle:hover { color: #fff !important; border-color: #555 !important; }
#stewint-content {
  flex: 1;
  overflow-y: auto;
  padding: 12px;
  background: #0d0d0d;
}
#stewint-content .si-section { margin-bottom: 16px; }
#stewint-content .si-section-title {
  font-size: 13px; color: #888; text-transform: uppercase;
  letter-spacing: 1px; margin-bottom: 8px; padding-bottom: 4px;
  border-bottom: 1px solid #222;
}
#stewint-content .si-build {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 10px; margin-bottom: 4px;
  background: #1a1a1a; border-radius: 6px; border: 1px solid #222;
}
#stewint-content .si-build-dot {
  width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0;
}
#stewint-content .si-build-name {
  flex: 1; font-size: 14px; color: #ccc;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
#stewint-content .si-build-status { font-size: 12px; color: #888; }
#stewint-content .si-info {
  padding: 8px 10px; background: #1a1a1a; border-radius: 6px;
  border: 1px solid #222; margin-bottom: 4px;
}
#stewint-content .si-info-label { font-size: 12px; color: #888; }
#stewint-content .si-info-value { font-size: 14px; color: #ccc; margin-top: 2px; }

/* Toggle button in conv header */
#stewint-open-btn {
  background: #1a1a1a !important;
  border: 1px solid #333 !important;
  color: #888 !important;
  width: 28px; height: 28px;
  border-radius: 4px;
  cursor: pointer;
  display: none;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  flex-shrink: 0;
}
#stewint-open-btn:hover { color: #FF6600 !important; border-color: #FF6600 !important; }

@media (max-width: 600px) {
  #stewint-panel { display: none !important; }
  #stewint-drag-handle { display: none !important; }
}
body.embedded #stewint-panel { display: none !important; }
body.embedded #stewint-drag-handle { display: none !important; }

/* R15 sub-ship 3 (Josh 2026-04-21): kill the topbar ☰ expand button in
   embedded too — it opens the permanent side-nav which is now dead.
   All stewards reachable via Row 1/2/3 + inline sub-sub chips. */
body.embedded .topbar-expand { display: none !important; }

/* === Embedded Topbar: horizontal steward icon strip === */
#embedded-topbar {
  display: none;
}
body.embedded #embedded-topbar {
  display: flex;
  flex-direction: row;
  align-items: center;
  width: 100%;
  height: 56px;
  flex-shrink: 0;
  background: rgba(20, 20, 20, 0.95);
  border-top: 1px solid #333;
  padding: 0 6px;
  overflow-x: auto;
  /* overflow-y MUST be `hidden`, not `visible` (Josh: the steward strip must
     only ever scroll horizontally, never vertically). Per the CSS overflow
     spec, `visible` on one axis paired with a non-visible value (overflow-x:
     auto) COERCES to `auto` — which silently gives the row a VERTICAL
     scrollbar. This was fixed once (e6b6012957, "un-crop badge") and lost to
     autosave/merge churn; restoring it. `hidden` keeps horizontal scroll and
     kills the vertical one. The 56px strip has ~7px slack above/below the 42px
     icons, so the top-0 badge + centered recency ring are NOT clipped. */
  overflow-y: hidden;
  order: 99;
  gap: 4px;
  z-index: 30;
}
/* Each steward icon cell in the topbar */
.topbar-icon {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  flex-shrink: 0;
  cursor: pointer;
  border-radius: 8px;
  /* Josh 2026-08-12: every steward reads as a RAISED / unpressed physical
     button (OPT-1 look he picked). The selected one is orange-filled and
     pressed IN below — see .topbar-icon.active. Ring is untouched. */
  border: 1px solid #57504a;
  background: linear-gradient(#45403d, #38332f);
  box-shadow: 0 2px 0 #211f1d, 0 3px 4px rgba(0, 0, 0, 0.35);
  transition: background 0.1s, box-shadow 0.1s, transform 0.1s;
}
.topbar-icon:active { transform: translateY(1px); box-shadow: 0 1px 0 #211f1d, 0 2px 3px rgba(0, 0, 0, 0.35); }
/* SELECTED = orange fill + pressed IN (deep inset shadow, sinks 2px). This is
   the "you're here" state that must beat the recency-ring glow for the eye. */
.topbar-icon.active {
  background: linear-gradient(#e0700f, #c25e08);
  border-color: #a34e05;
  transform: translateY(2px);
  box-shadow:
    inset 0 4px 7px rgba(0, 0, 0, 0.55),
    inset 0 1px 0 rgba(0, 0, 0, 0.35),
    inset 0 -2px 2px rgba(255, 255, 255, 0.10),
    0 0 0 1px #8f4404;
}
/* Badge on the orange plate: invert to dark-on-gold so the count stays legible. */
.topbar-icon.active .topbar-badge { background: #282828; color: #FABD2F; border-color: #a34e05; }
.topbar-icon.active .topbar-status { border-color: #7a3a03; }

/* All mode (Josh 2026-09-27): the steward that owns the card on screen gets an
   orange OUTLINE — obvious, but not the filled/pressed real selection. */
.topbar-icon.play-owner {
  border-color: #ff8a2a;
  box-shadow: 0 0 0 2px #ff8a2a, 0 0 10px rgba(255, 138, 42, 0.55), 0 2px 0 #211f1d;
}
body.reading-mode .topbar-icon.play-owner {
  border-color: var(--iowa-accent);
  box-shadow: 0 0 0 2px var(--iowa-accent), 0 0 10px color-mix(in srgb, var(--iowa-accent) 55%, transparent), 0 2px 0 rgba(0, 0, 0, 0.45);
}

/* TWO-LEVEL steward selection (Josh 2026-08-25, revised 2026-08-27). `.active`
   alone = 'all' level (this steward's own + its workers' cards, mixed
   chronological). Adding `.level-own` means Josh tapped the already-selected
   steward again to DRILL into JUST this steward's own cards.
   Josh 2026-08-27: the WHOLE cell background goes solid blue — no corner dot.
   "instead of a little blue dot showing up, I want the whole background to go
   blue on the steward. That way I know like oh I've double clicked and I'm just
   focused right now on this steward." The full-plate blue IS the focus signal;
   the corner pip is gone. Still pressed-in (it's the selection). */
.topbar-icon.active.level-own {
  background: linear-gradient(#1f6f8f, #17566f);
  border-color: #0f4256;
  box-shadow:
    inset 0 4px 7px rgba(0, 0, 0, 0.55),
    inset 0 0 0 2px rgba(180, 230, 255, 0.55),
    inset 0 1px 0 rgba(0, 0, 0, 0.35),
    0 0 0 1px #0b3547;
}
.topbar-icon.active.level-own .topbar-badge { background: #05202b; color: #8fe4ff; border-color: #0f4256; }
.topbar-icon.active.level-own .topbar-status { border-color: #0b3547; }

/* Recency ring — a WATCH-FACE BEZEL of 30 white tick marks along a 270° arc
   around a steward icon, showing how much of Josh's last-30 walkie sends went
   to that steward and where in time they fell.
   Josh iter3 (2026-08-05): scattered dots looked like crumbs; ticks make the
   same layout read as a deliberate dial. Every slot is always a faint tick
   (uniform bezel); this steward's slots light up bright + taller so count and
   recency read instantly. The arc skips the upper-right quadrant so the
   notification count badge stays clear. Sits BEHIND the emoji + badge. */
.recency-ring {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  pointer-events: none;
  z-index: 0;
  overflow: visible;
}
/* Josh iter4 (2026-08-05): lit and unlit ticks are the SAME length + width —
   the only difference is brightness. Lit = solid white; unlit = dim white. */
.recency-ring .recency-tick {
  stroke: #ffffff;
  stroke-width: 1.3;
}
.recency-ring .recency-tick.lit {
  opacity: 0.95;
}
.recency-ring .recency-tick.unlit {
  opacity: 0.16;
}
/* Sidebar steward icon needs a positioning context + room for the ring. */
.steward-icon.has-recency-ring {
  position: relative;
  overflow: visible;
}
.steward-icon.has-recency-ring .recency-ring {
  z-index: 0;
}

.topbar-icon .topbar-emoji {
  font-size: 28px;
  line-height: 1;
  position: relative;
  z-index: 1;
}
/* Status dot: bottom-right of icon */
/* R15 lower-right: steward's own status dot. Pairs with the activity ring
   (which wraps the dot) — see .topbar-activity-ring below. */
.topbar-icon .topbar-status {
  position: absolute;
  bottom: 2px;
  right: 2px;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  border: 1.5px solid #111;
  z-index: 2;
}
/* Badge: upper-right of icon. R15 (Josh 2026-04-21): unread signal now
   attaches HERE (not middle-bottom). .has-unread = there are unread cards
   too; the badge gets a small blue attached dot to signal "+unread". */
.topbar-icon .topbar-badge {
  position: absolute;
  top: 0;
  right: -2px;
  background: #FF6600;
  color: #000;
  font-size: 9px;
  font-weight: bold;
  /* Theme-aware font — inherit (no hardcoded VT323). */
  font-family: inherit;
  min-width: 14px;
  height: 14px;
  line-height: 14px;
  text-align: center;
  border-radius: 7px;
  padding: 0 3px;
  z-index: 2;
}
/* R15 v3 (Josh 2026-04-21): unread indicator is a teal BORDER around the
   badge itself (was underline strip, was dot). box-shadow instead of
   border so it doesn't change the badge's box-model size / layout. */
.topbar-icon .topbar-badge.has-unread {
  box-shadow: 0 0 0 2px var(--unread-accent, #00E5D1),
              0 0 6px var(--unread-glow, rgba(0, 229, 209, 0.7));
}
.topbar-icon .topbar-badge.unread-only {
  background: #00E5D1;
  color: #003A36;
}
body.reading-mode .topbar-icon .topbar-badge.has-unread {
  box-shadow: 0 0 0 2px var(--unread-accent, var(--iowa-slate, #00E5D1)),
              0 0 6px var(--unread-glow, rgba(0, 229, 209, 0.5));
}
body.reading-mode .topbar-icon .topbar-badge.unread-only {
  background: var(--iowa-slate, #00E5D1);
  color: var(--iowa-bg, #003A36);
}
/* Per-preset unread accent tuning — default dark gets vivid teal;
   reading-mode presets pick a tone that fits their palette. */
:root {
  --unread-accent: #00E5D1;
  --unread-glow: rgba(0, 229, 209, 0.7);
}
body.reading-mode {
  --unread-accent: var(--iowa-slate, #4A6B8E);
  --unread-glow: color-mix(in srgb, var(--iowa-slate, #4A6B8E) 55%, transparent);
}
/* Substeward activity ring — top-left of steward icon */
/* R15 v3: ring hugs the status dot tighter — Josh 2026-04-21 "too much
   space in between". Ring now sits just 1px outside the dot border so
   they read as one combined indicator. */
.topbar-icon .topbar-activity-ring {
  position: absolute;
  bottom: 0;
  right: 0;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  border: 1.5px solid #FFCC00;
  background: transparent;
  z-index: 1;
  pointer-events: none;
  animation: pulse 1.5s ease-in-out infinite;
}
/* PER-WORKER DOTS (Josh 2026-09-20): "really small rings right along the
   bottom border of each button that we could see like how many workers there
   are, which ones were working." One dot per direct worker, centred on the
   button's bottom edge so they read as sitting ON the border.
   Colours match the worker rows' own status pills exactly — yellow pulsing =
   working, green = resting, grey = no session — so the two surfaces never
   disagree about the same worker.

   SIZED FOR THE WORST CASE, measured not guessed: Venture has 9 workers, whose
   natural run (9x4px + 8x2px gaps) is 52px — wider than the 42px button, and
   it overlapped the own-count pill in the lower-left by 13px. So the dots are
   3px with 1.5px gaps (9 workers = 37px) and the track is inset to the space
   BETWEEN the two lower corners rather than the full button width. Beyond
   that, flex-shrink lets them compress rather than burst or clip. */
.topbar-icon .topbar-worker-dots {
  position: absolute;
  bottom: -4px;
  /* Full button width. An earlier attempt inset this between the two lower
     corners (own pill left, activity ring right) and measurement killed it:
     the usable track collapsed to 20px, which fits FOUR dots — so Venture's 9
     still overflowed even after two rounds of shrinking. The dots instead sit
     BELOW the corner furniture (bottom:-4px vs the pill's -3px and the ring's
     0), where the full 42px is available and nothing competes for the space. */
  left: 0;
  right: 0;
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  justify-content: center;
  gap: 1.5px;
  z-index: 2;
  pointer-events: none;
}
.topbar-icon .topbar-worker-dots .wd {
  width: 3px;
  height: 3px;
  /* Shrink rather than overflow when a steward grows more workers than the
     track can hold at full size. */
  flex: 0 1 auto;
  min-width: 2px;
  border-radius: 50%;
  /* Dark halo so a dot never dissolves into the button edge or the row behind. */
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.7);
}
.topbar-icon .topbar-worker-dots .wd.working {
  background: #FFCC00;
  animation: pulse 1s ease-in-out infinite;
}
.topbar-icon .topbar-worker-dots .wd.ready { background: #00FF66; }
.topbar-icon .topbar-worker-dots .wd.idle  { background: #5a5a5a; }
/* Overflow marker: a steward with more workers than the row can legibly hold
   ends in a small dash rather than a dot, so the count never silently lies.
   The tooltip carries the true number. */
.topbar-icon .topbar-worker-dots .wd.more {
  width: 5px;
  height: 2px;
  border-radius: 1px;
  background: #8a8178;
  box-shadow: none;
}

/* OWN-CARD COUNT (Josh 2026-09-20): how many cards belong to the STEWARD
   ITSELF, as opposed to the upper-right badge which totals the steward plus
   every worker under it. Lower-LEFT — the only corner left free (badge upper
   right, activity ring lower right, 💤 top middle).
   Deliberately quieter than the upper-right badge: muted fill, no unread
   states. That badge is the one Josh acts on; this one is reference. */
.topbar-icon .topbar-owncount {
  position: absolute;
  /* LIFTED clear of the worker-dot row along the bottom border. Measured: with
     the pill sitting at bottom:-3px, 4 of Venture's 9 dots rendered underneath
     it. The dots own the bottom edge (that is what Josh asked for — "right
     along the bottom border"), so the pill moves up to sit against the button's
     lower-left corner instead of hanging off it. */
  bottom: 6px;
  left: -3px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 13px;
  height: 13px;
  padding: 0 3px;
  font-size: 8px;
  font-weight: 700;
  line-height: 13px;
  color: #d8d2c8;
  background: #2b2b2b;
  border: 1px solid rgba(255, 255, 255, 0.28);
  border-radius: 7px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.55);
  font-variant-numeric: tabular-nums;
  z-index: 2;
  pointer-events: none;
}

/* Sleep indicator: top-middle of icon. Joshua 2026-04-28: "directly over
   them, top and middle." Renders when watchdog watchlist enabled=false AND
   the steward has no live tmux session. Cute, NOT alarming — gentle pulse
   sells the "sleeping" vibe. Pointer-events:none so the icon click area
   stays clean for tap-to-select. */
.topbar-icon .topbar-asleep {
  position: absolute;
  top: -2px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 18px;
  line-height: 1;
  z-index: 3;
  pointer-events: none;
  user-select: none;
  animation: pulse 2.5s ease-in-out infinite;
  text-shadow: 0 0 4px rgba(0, 0, 0, 0.6);
}
/* R15 DEPRECATED: Blue unread dot was middle-bottom. Josh 2026-04-21
   moved unread signal up to the count badge (upper-right). Element no
   longer rendered by JS. Rule kept defensively hidden in case legacy
   markup emits one. */
.topbar-icon .topbar-unread-dot {
  display: none !important;
  position: absolute;
  bottom: 2px;
  left: 50%;
  transform: translateX(-50%);
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #00BFFF;
  box-shadow: 0 0 4px rgba(0, 191, 255, 0.6);
  z-index: 2;
}
/* Expand button at right end of topbar */
.topbar-expand {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  flex-shrink: 0;
  margin-left: auto;
  cursor: pointer;
  color: #555;
  font-size: 16px;
  background: none;
  border: 1px solid #333;
  border-radius: 4px;
  font-family: 'VT323', monospace;
}
.topbar-expand:active { color: #FF6600; border-color: #FF6600; }

/* Embedded subbar: second row for substewards */
#embedded-subbar {
  display: none;
}
body.embedded #embedded-subbar {
  display: none;
  flex-direction: row;
  /* Josh 2026-08-07/08: gap KILLED and badge must NOT be cropped.
     The crop cause: a horizontal scroller (overflow-x: auto) forces the browser
     to CLIP overflow-y too (visible+auto coerces to auto), so a badge bleeding
     past the strip's top edge got chopped by the steward row above. The old fix
     (tall transparent top zone) was the gap Josh hates.
     New model: the badge stays INSIDE the subbar's own box (no vertical bleed =
     nothing to clip), and the WHOLE subbar is pulled UP with a negative margin so
     it OVERLAPS the steward row — killing the gap AND letting the badge (which
     sits high in the subbar's top padding) visually ride up onto the steward row,
     fully visible. z-index 31 > topbar 30 so the overlap paints on top. */
  align-items: flex-start;
  width: 100%;
  /* 58px, was 48px (Josh 2026-09-19). This element is `box-sizing: border-box`,
     so padding grows INWARD against a fixed height — raising the bottom padding
     to 12px alone did NOT make the box taller and the bottom badge kept getting
     clipped. The height has to grow by the same 10px for the new padding to be
     real room. Verified by measurement, not assumed: with 48px the badge still
     ran 10px past the box.
     The strip's BOTTOM edge is pinned by the flex column above the nav, so
     this extra 10px grows the box UPWARD. That growth replaces the hand-rolled
     -10px margin-top (now 0) — see the margin note below. Buttons end up in
     exactly their old position and no gap comes back. */
  height: 58px;
  flex-shrink: 0;
  background: rgba(15, 15, 15, 0.95);
  /* No top border: with the -10px overlap onto the steward row (below), a
     border-top painted ON TOP of the steward row above, looking dorky.
     Josh 2026-08-11: kill it. */
  /* 12px top AND bottom padding: a badge hanging off either edge (cell
     top:-7px / bottom:-7px) lands a few px INSIDE the subbar's box, so the
     scroller's vertical clip never touches it.
     BOTTOM PADDING RAISED 2px -> 12px (Josh 2026-09-19: "the bottom of the
     pill is still getting cut off... looks dorky when they're hidden").
     THE CAUSE WAS OVERFLOW CLIPPING, NOT STACKING — measured: the bottom chip
     ran to y=846 while this box ended at y=843, and overflow-y:hidden (which
     overflow-x:auto coerces, see below) chopped those 3px. z-index was never
     the problem; 215 already beats the nav's 210, which is why the earlier
     z-index fix for this same complaint did not resolve it. Giving the badge
     room INSIDE the box is the actual fix — the identical trick the top edge
     has always used, finally extended downward. */
  padding: 12px 6px 12px;
  /* Pull the strip UP over the steward row: kills the gap AND creates the overlap
     where the badge reads as sitting on the steward row.
     0, not -10px (Josh 2026-09-19). The strip's BOTTOM is pinned by the flex
     column above the nav, so the 10px height increase grows the box UPWARD,
     not downward — measured. That extra 10px at the top now supplies the
     overlap the -10px margin used to create by hand, so keeping the margin as
     well would double it and shift every button 10px up (which measurement
     caught twice). Net effect: the buttons sit exactly where they always did,
     the strip overlaps the steward row exactly as much as it always did, and
     the bottom badge finally has room inside the box. */
  margin-top: 0;
  overflow-x: auto;
  overflow-y: hidden;
  order: 100;
  gap: 4px;
  /* z-index 215: raise the whole workers row ABOVE the fixed bottom nav
     (#conv-bottom-input, z 210) so each worker button — and its bottom diff
     chip that hangs off the lower-right corner — paints IN FRONT of the nav
     below it, instead of behind it (Josh 2026-08-13: the original ask — give the
     bottom chip the same "always on top" quality the top time badge already has).
     Stays UNDER #mobile-card-input-bar (220) so that still layers on top. The
     subbar and the nav both resolve at the root stacking context (their
     relative-but-z-index-less ancestors — #inbox-container, #conversation-panel,
     body — don't establish stacking contexts), so 215 > 210 wins cleanly.
     Was 31 (only needed to beat the topbar's 30). */
  z-index: 215;
}
body.embedded #embedded-subbar.visible {
  display: flex;
}
.subbar-icon {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  flex-shrink: 0;
  cursor: pointer;
  border-radius: 6px;
  border-bottom: 2px solid transparent;
  transition: background 0.1s;
}
.subbar-icon:active { background: rgba(255, 255, 255, 0.08); }
/* Josh 2026-08-12: match the topbar's raised-button + orange pressed-IN look.
   No translateY here (would fight .subbar-icon--arriving's transform) — the
   inset shadow alone reads as pressed. */
.subbar-icon {
  border: 1px solid #57504a;
  background: linear-gradient(#45403d, #38332f);
  box-shadow: 0 1px 0 #211f1d, 0 2px 3px rgba(0, 0, 0, 0.35);
}
.subbar-icon.active {
  background: linear-gradient(#e0700f, #c25e08);
  border-color: #a34e05;
  box-shadow:
    inset 0 3px 6px rgba(0, 0, 0, 0.55),
    inset 0 1px 0 rgba(0, 0, 0, 0.35),
    0 0 0 1px #8f4404;
}
/* Freeze-on-nav newcomer (Josh 2026-08-07): a worker that first spawns while
   Josh is already on the steading slides in at the far left instead of the row
   silently reshuffling. Plays once (the JS clears the class after one render). */
.subbar-icon--arriving { animation: subbarWorkerArrive 0.42s cubic-bezier(0.22, 1, 0.36, 1) both; }
@keyframes subbarWorkerArrive {
  0%   { opacity: 0; transform: translateX(-14px) scale(0.9); }
  60%  { opacity: 1; }
  100% { opacity: 1; transform: translateX(0) scale(1); }
}
.subbar-icon .subbar-emoji { font-size: 22px; line-height: 1; }
.subbar-icon .topbar-status {
  position: absolute;
  bottom: 2px;
  right: 2px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  border: 1.5px solid #0f0f0f;
}

/* Labeled worker cell — [icon] [name] [status dot] [time]. Widens the cell
   from an icon-only square into a horizontal pill so each worker in the phone
   subbar shows its name and last-status-update age, per Josh's ask. */
/* Worker cell as a nested BUTTON (Josh 2026-08-03), mirroring the top-level
   steward cell: a rounded button holding [emoji-over-time column] + [name],
   with the status dot hanging off the lower-right corner. */
/* iter4 (Josh 2026-08-04): swaggier worker button — emoji in a tinted chip,
   time + status as REAL hanging badges, names re-truncated with a "⋯" toast.
   Also: NO text-selection on the button (long-press on phone was selecting the
   name + popping the dorky selection menu — Josh sends recordings via
   long-press, so selection must never fire). */
.subbar-icon.has-label {
  width: auto;
  min-width: 0;
  /* iter8 (Josh): a defined right edge ("the wall") so the name can run the
     full button width and fade only right at the end. The "⋯" button overlaps
     ON TOP of that faded tail (absolute, see below) rather than pushing the
     name in — so the name flows UNDER it all the way to the wall. */
  max-width: 190px;
  height: 40px;
  /* Small symmetric side padding so the name runs nearly to the button edge
     ("the wall"); the absolute "⋯" overlaps that faded tail. */
  padding: 0 6px;
  gap: 7px;
  border-radius: 10px;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.07), rgba(255, 255, 255, 0.03));
  border: 1px solid rgba(255, 255, 255, 0.10);
  border-bottom: 2px solid transparent;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
  /* Long-press must NOT select text or raise the OS selection callout. */
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}
.subbar-icon.has-label.active {
  border-color: rgba(255, 102, 0, 0.55);
  border-bottom-color: #FF6600;
  background: linear-gradient(180deg, rgba(255, 102, 0, 0.20), rgba(255, 102, 0, 0.08));
}
/* Left column: the emoji inside a rounded tinted chip. */
.subbar-icon.has-label .subbar-iconcol {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.subbar-icon.has-label .subbar-emoji-chip {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 9px;
  background: radial-gradient(circle at 30% 25%, rgba(255, 255, 255, 0.16), rgba(255, 255, 255, 0.05));
  border: 1px solid rgba(255, 255, 255, 0.12);
  box-shadow: inset 0 1px 1px rgba(255, 255, 255, 0.12), 0 1px 2px rgba(0, 0, 0, 0.3);
}
.subbar-icon.has-label.active .subbar-emoji-chip {
  background: radial-gradient(circle at 30% 25%, rgba(255, 160, 90, 0.30), rgba(255, 102, 0, 0.10));
  border-color: rgba(255, 140, 70, 0.45);
}
.subbar-icon.has-label .subbar-emoji { font-size: 18px; line-height: 1; }
/* Time badge — iter4: a REAL pill badge hanging off the upper-right corner.
   Filled, bordered against the strip, clear presence (not floating text). */
/* COMBINED status-dot + last-activity-time badge (Josh 2026-08-07 redesign).
   Top-right corner. Holds a status dot and — only when NOT working — the time
   text beside it. Working shows just the dot (pill shrinks to a circle). */
/* TIME/STATUS PILL — BOTTOM RIGHT (Josh 2026-09-19 three-corner layout).
   Was top-right. Moved down so the card-count pill can take the upper right.
   bottom:-7px mirrors the old top:-7px: it hangs off the lower edge the same
   distance it used to hang off the upper one, and the subbar's bottom padding
   (raised to 12px to match the top) keeps it INSIDE the scroller's box so
   overflow-y:hidden cannot clip it. */
.subbar-icon.has-label .subbar-timebadge {
  position: absolute;
  bottom: -7px;
  right: -5px;
  display: inline-flex;
  align-items: center;
  gap: 3px;
  height: 15px;
  padding: 0 5px;
  color: #f0f0f0;
  background: #2b2b2b;
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: 8px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  z-index: 3;
  pointer-events: none;
}
/* Dot-only (working) collapses to a tight circle instead of a wide pill. */
.subbar-icon.has-label .subbar-timebadge:not(.has-time) {
  padding: 0;
  width: 15px;
  justify-content: center;
}
.subbar-icon.has-label .subbar-timebadge-time {
  font-size: 9px;
  font-weight: 600;
  line-height: 15px;
}
.subbar-icon.has-label .subbar-timebadge-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  flex-shrink: 0;
  background: #444;
  /* Dark ring around the dot so it POPS and never blends into the badge/cream
     background (Josh 2026-08-07). box-shadow (not border) so it doesn't grow the
     dot's box. A thin dark halo reads as a deliberate status chip in every theme. */
  box-shadow: 0 0 0 1.5px rgba(0, 0, 0, 0.55);
}
.subbar-icon.has-label .subbar-timebadge.working .subbar-timebadge-dot {
  background: #FFCC00;
  animation: pulse 1s ease-in-out infinite;
}
.subbar-icon.has-label .subbar-timebadge.ready .subbar-timebadge-dot { background: #00FF66; }
.subbar-icon.has-label .subbar-timebadge.unknown .subbar-timebadge-dot { background: #444; }

/* CARD-COUNT PILL (Josh 2026-09-19): how many of his queued cards came from
   this worker. Same "doodad hanging off the corner" family as the time badge
   and the diff chip — same height, radius, ring and shadow — so the three read
   as one set rather than three unrelated stickers.

   UPPER RIGHT, per Josh's three-corner layout: count upper-right, time
   bottom-right, diff upper-left.

   Amber rather than the diff chip's grey: this is a "you have things waiting"
   signal, not a neutral stat, and it must not be mistaken for the git numbers
   in the opposite corner. Only ever rendered when the count is >= 1 (see
   workerCardCount), so it never shows a zero. */
.subbar-icon.has-label .subbar-cardcount {
  position: absolute;
  top: -7px;
  right: -5px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 14px;
  height: 14px;
  padding: 0 4px;
  font-size: 8px;
  font-weight: 700;
  line-height: 14px;
  color: #1c1400;
  background: #FFCC00;
  border: 1px solid rgba(0, 0, 0, 0.45);
  border-radius: 7px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  z-index: 3;
  pointer-events: none;
}
/* Name — iter7/8 (Josh): NO artificial ellipsis. The name runs the FULL button
   width (flex-fills to "the wall" = the button's right edge) and only fades to
   transparent right AT the wall — not early, not to the left of the "⋯". The
   "⋯" button overlaps ON TOP of this faded tail (absolute, below), so the name
   flows UNDER it all the way to the end. `flex: 1; min-width: 0` lets it grow
   into all remaining button space up to the button's max-width. */
.subbar-icon.has-label .subbar-name {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 13px;
  color: #cfcfcf;
  white-space: nowrap;
  overflow: hidden;
}
/* Fade only when the name actually overflows — and only the last ~16px, right
   at the wall (was ~28px starting too early per Josh). A short name that fits
   is never dimmed. */
.subbar-icon.has-label.name-clipped .subbar-name {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 16px), transparent 100%);
  mask-image: linear-gradient(to right, #000 calc(100% - 16px), transparent 100%);
}
.subbar-icon.has-label.active .subbar-name { color: #fff; }
/* "⋯" more-button — iter8 (Josh): sits ON TOP of the name's faded tail at the
   button's right edge (absolute, overlapping) so the name can run UNDERNEATH it
   all the way to the wall. Appears only when the name is clipped; taps toast the
   full name. Its own tap target (stops the cell's open-tmux). */
.subbar-icon.has-label .subbar-name-more {
  position: absolute;
  right: 5px;
  top: 50%;
  transform: translateY(-50%);
  width: 18px;
  height: 18px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  line-height: 1;
  color: #e8e8e8;
  /* Slightly opaque disc so the ⋯ stays legible over the name text under it. */
  background: rgba(28, 28, 28, 0.82);
  border: 1px solid rgba(255, 255, 255, 0.20);
  border-radius: 50%;
  cursor: pointer;
  z-index: 4;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}
.subbar-icon.has-label .subbar-name-more:active { background: rgba(60, 60, 60, 0.9); }
/* GitHub-style ahead-only diff chip (Josh 2026-08-07 redesign): the bottom-right
   slot (formerly the bare status dot, now moved up into the time badge) shows how
   much work is in this worker's branch that ISN'T in main yet — "Nf +X −Y".
   AHEAD-only: Josh only cares about work the worker has that main doesn't. Hidden
   entirely when there's nothing ahead (zero files) so a clean/merged worker
   carries no chip. */
.subbar-icon.has-label .subbar-diff {
  position: absolute;
  /* UPPER LEFT (Josh 2026-09-19 three-corner layout). SUPERSEDES the previous
     "hangs off the lower-right corner — do NOT lift it up into the button"
     note: Josh has since asked for the three badges to occupy three different
     corners, and this one moved to the top left. The "little doodad hanging
     off" quality he liked is PRESERVED — it still hangs off a corner, just the
     opposite one, riding ABOVE the name text rather than beside it, exactly as
     the status badge does. The name needs no extra room: this is a hanging
     badge, never an inline element in the name's flow. */
  top: -7px;
  /* left:1px, NOT -5px (Josh 2026-09-20: "there's a collision between the
     number icon and the file change icon... if they are butted up next to each
     other, they will collide"). Reproduced and measured: the count pill
     overhangs its cell 5px to the RIGHT, this chip overhung 5px to the LEFT,
     and the flex gap between cells is only 4px — so 5+5-4 left them mutually
     encroaching, observed as a 4px overlap on two adjacent pairs.
     Pulling this one just inside its own cell clears the count pill of the
     button to its left with room to spare, and it still reads as hanging off
     the top edge because the -7px vertical offset is untouched. Moving THIS
     one (per Josh: "we can just move one of the badges over slightly") keeps
     the count pill exactly where he already signed off on it. */
  left: 1px;
  display: inline-flex;
  align-items: center;
  gap: 3px;
  height: 14px;
  padding: 0 5px;
  font-size: 8px;
  font-weight: 700;
  line-height: 14px;
  background: #1c1c1c;
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 7px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  z-index: 3;
  pointer-events: none;
}
.subbar-icon.has-label .subbar-diff-files { color: #cfcfcf; }
.subbar-icon.has-label .subbar-diff-add { color: #3fb950; }   /* GitHub green + */
.subbar-icon.has-label .subbar-diff-del { color: #f85149; }   /* GitHub red − */
/* Caught-up (0-ahead) checkmark (Josh 2026-08-08): subtle, muted green ✓ so a
   fully-merged worker reads as "all caught up" rather than a blank corner.
   Smaller/quieter than the +/- chip so busy workers still draw the eye. */
.subbar-icon.has-label .subbar-diff-caughtup {
  color: #3fb950;
  opacity: 0.72;
  font-weight: 700;
  padding: 0 4px;
  gap: 0;
}

/* ---------------------------------------------------------------------------
   Inline sub-sub-steward chips in Row 2 (Josh R13 2026-04-21)
   Live inside #embedded-subbar, rendered immediately after their parent
   substeward cell. NOT navigable (no click handler, no active state) —
   purely informational: name + activity dot so Josh can see "who's
   working under this manager right now" without tapping through.
   Distinct visual treatment so Josh can tell manager vs. worker at a glance.
   --------------------------------------------------------------------------- */
/* R22 (Josh 2026-04-21): sub-sub chips default COLLAPSED. A tap-to-expand
   count badge sits inline after each substeward that has children.
   Shows {total} count + one flashing dot per working child. Tap toggles. */
.subsub-count-badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 28px;
  padding: 3px 8px;
  flex-shrink: 0;
  cursor: pointer;
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.14);
  color: #aaa;
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.2px;
  transition: background 0.12s, border-color 0.12s;
  line-height: 1;
  -webkit-tap-highlight-color: transparent;
}
.subsub-count-badge:hover {
  background: rgba(255, 255, 255, 0.1);
  border-color: rgba(255, 255, 255, 0.24);
}
.subsub-count-badge.expanded {
  background: rgba(255, 102, 0, 0.15);
  border-color: var(--iowa-accent, #FF6600);
  color: var(--iowa-accent, #FF6600);
}
.subsub-count-num {
  font-variant-numeric: tabular-nums;
  min-width: 10px;
  text-align: center;
}
.subsub-count-dots {
  display: inline-flex;
  align-items: center;
  gap: 3px;
}
.subsub-count-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #FFCC00;
}
.subsub-count-dot.working {
  animation: pulse 1s ease-in-out infinite;
}
.subsub-count-extra {
  font-size: 10px;
  color: #FFCC00;
  font-weight: 500;
  margin-left: 2px;
}
body.reading-mode .subsub-count-badge {
  background: var(--iowa-panel-alt);
  border-color: var(--iowa-border);
  color: var(--iowa-text-soft);
}
body.reading-mode .subsub-count-badge:hover {
  background: var(--iowa-panel-alt);
  border-color: var(--iowa-accent);
}
body.reading-mode .subsub-count-badge.expanded {
  background: var(--iowa-panel-alt);
  border-color: var(--iowa-accent);
  color: var(--iowa-accent);
}
body.reading-mode .subsub-count-dot { background: var(--iowa-gold); }
body.reading-mode .subsub-count-extra { color: var(--iowa-gold); }

.subsub-chip {
  position: relative;
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 3px 8px 3px 10px;
  height: 28px;
  flex-shrink: 0;
  cursor: default;              /* not navigable */
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.03);
  /* Subtle left-edge marker to show parentage to the preceding substeward. */
  border-left: 2px solid rgba(255, 102, 0, 0.35);
  white-space: nowrap;
  /* Theme-aware font — inherit from body stack. No hardcoded VT323. */
  font-family: inherit;
  font-size: 12px;
  color: #999;
  /* Slight downshift + smaller scale so the eye reads subs > subsubs. */
  transform: scale(0.94);
  transform-origin: left center;
}
.subsub-chip .subsub-chip-emoji { font-size: 14px; line-height: 1; opacity: 0.85; }
.subsub-chip .subsub-chip-name {
  font-weight: 500;
  letter-spacing: 0.2px;
  /* Long name guard — ellipsis if absurdly long, tooltip via title attr. */
  max-width: 160px;
  overflow: hidden;
  text-overflow: ellipsis;
}
.subsub-chip .topbar-status {
  position: static;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  border: 1.5px solid #0f0f0f;
  margin-left: 2px;
  flex-shrink: 0;
}
body.reading-mode .subsub-chip {
  background: var(--iowa-panel-alt);
  border-left-color: var(--iowa-accent);
  color: var(--iowa-text-soft);
}
body.reading-mode .subsub-chip .topbar-status {
  border-color: var(--iowa-panel);
}

/* Expanded sidebar overlay (triggered from topbar expand button) */
body.embedded #sidebar.expanded-overlay {
  display: flex !important;
  position: absolute;
  left: 0; top: 72px; bottom: 0;
  width: 240px;
  max-width: 75vw;
  z-index: 100;
  background: rgba(13, 13, 13, 0.95);
  border: none;
  border-right: 1px solid #222;
  flex-direction: column;
  overflow-y: auto;
  overflow-x: hidden;
}
/* Fix text overflow in expanded sidebar overlay */
body.embedded #sidebar.expanded-overlay .steward-row {
  max-width: 100%;
  overflow: hidden;
}
body.embedded #sidebar.expanded-overlay .steward-info {
  min-width: 0;
  overflow: hidden;
}
body.embedded #sidebar.expanded-overlay .steward-name {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
  display: block;
}
body.embedded #sidebar.expanded-overlay .steward-preview {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}
/* Font size controls in expanded sidebar */
.font-size-controls {
  display: none;
  justify-content: center;
  align-items: center;
  gap: 12px;
  padding: 10px;
  border-top: 1px solid #222;
  background: #0a0a0a;
}
body.embedded #sidebar.expanded-overlay .font-size-controls { display: flex; }
/* R23 regression fix (Josh 2026-04-21): original unhide rule scoped to
   the old side-nav overlay which R15 killed. The Settings panel never
   un-hid these controls so they rendered present-but-invisible.
   !important because some cascade was still winning with display:none
   even at 0,2,1 specificity — defensive, not speculative. */
body.embedded #settings-panel .font-size-controls { display: flex !important; }
#settings-panel .font-size-controls { display: flex !important; }
.font-btn {
  background: #222;
  color: #e0e0e0;
  border: 1px solid #333;
  border-radius: 6px;
  padding: 6px 14px;
  font-size: 16px;
  /* Theme-aware — inherit from body stack (Iowa serif in reading-mode). */
  font-family: inherit;
  cursor: pointer;
}
.font-btn:hover { background: #333; }
.font-btn:active { background: #444; }
.font-size-label { color: #888; font-size: 14px; font-family: inherit; min-width: 40px; text-align: center; }

/* Inline theme tiles — mobile sidebar footer. Always-visible swatches for
   quickly switching Off / Iowa / Gruvbox / Gruvbox Hard without a popover. */
.theme-tiles-inline {
  display: none;
  gap: 6px;
  padding: 8px 10px 12px;
  background: #0a0a0a;
  border-top: 1px solid #1a1a1a;
  justify-content: space-between;
}
body.embedded #sidebar.expanded-overlay .theme-tiles-inline { display: flex; }
body.embedded #settings-panel .theme-tiles-inline { display: flex !important; }
#settings-panel .theme-tiles-inline { display: flex !important; }
.theme-tile-inline {
  flex: 1;
  background: #1a1a1a;
  border: 1px solid #333;
  border-radius: 6px;
  padding: 6px 4px;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  min-width: 0;
}
.theme-tile-inline:hover { background: #222; border-color: #444; }
.theme-tile-inline.active { border-color: #FF6600; background: #FF660015; }
.theme-tile-inline-swatches {
  display: flex;
  gap: 2px;
  width: 100%;
  justify-content: center;
}
.theme-tile-inline-swatch {
  width: 10px;
  height: 10px;
  border-radius: 2px;
  display: inline-block;
}
.theme-tile-inline-name {
  color: #ccc;
  font-size: 11px;
  font-family: inherit;
  line-height: 1;
  text-align: center;
}
.theme-tile-inline.active .theme-tile-inline-name { color: #FF6600; }

body.embedded #sidebar-overlay.visible {
  display: block !important;
  position: absolute;
  top: 72px; left: 0; right: 0; bottom: 0;
  z-index: 99;
  background: rgba(0, 0, 0, 0.5);
}

/* === Embedded font & layout overrides === */
body.embedded { padding-top: 0; }
body.embedded #conv-thread { padding: 6px; }
body.embedded #conversation-panel { position: relative; }
body.embedded .chat-row { max-width: 100%; }
body.embedded .chat-row.archived-row { max-width: 100%; }
body.embedded .msg-bubble .msg-body { font-size: var(--presenter-font-size, 18px); }
body.embedded .msg-card-title { font-size: 26px; }
body.embedded .msg-avatar-bar { font-size: 15px; padding: 6px 10px; }
body.embedded .msg-avatar-bar .avatar-time-actual { font-size: 17px; }
body.embedded .msg-avatar-bar .avatar-time-relative { font-size: 15px; }
body.embedded .msg-avatar-bar .avatar-source { font-size: 14px; }
body.embedded .msg-avatar-bar .avatar-source-sub { font-size: 16px; }
body.embedded .msg-btn { font-size: 20px; padding: 12px 24px; }
/* Send Here button — full row on mobile so it's not hidden behind APK bar */
body.embedded .msg-send-btn {
  width: 100%; height: 44px; border-radius: 8px;
  font-size: 16px; font-family: 'VT323', monospace;
}
body.embedded .msg-send-btn.has-recording::after { content: ' Send Here'; }
body.embedded .msg-input-row {
  flex-direction: column; gap: 8px;
}
body.embedded .msg-input-row .msg-send-btn { order: 1; }
body.embedded .msg-input { font-size: 18px; }
body.embedded #conv-bottom-textarea { font-size: 18px; }
body.embedded #conv-bottom-input {
  padding-bottom: 16px;
  /* Mobile: pin walkie bar to viewport bottom. body padding-bottom (56px / 116px)
     already reserves space; this just promotes it from flex flow to fixed. */
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 210;       /* below #mobile-card-input-bar (220), above deck cards */
  box-sizing: border-box;
}
body.embedded #conv-header { padding: 10px 12px; }
body.embedded #conv-steward-name { font-size: 22px; }

/* Hide the connection-status bar injected by Android app */
body.embedded #connection-status { display: none !important; }
body.embedded { padding-bottom: 72px !important; }

/* Mobile-only top spacer (2026-04-20 v3 — Josh said 100px was too tall,
   and solid black didn't feel themed. Now 33px, theme-flavored per preset.
   Scoped to phone-width viewports so desktop Electron is untouched. */
@media (max-width: 600px) {
  body.embedded {
    padding-top: 40px;
    position: relative;
  }
  body.embedded::before {
    content: '';
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 40px;
    /* Default (non-reading-mode): dark steward-theme panel tone. Matches
       the orange-accent palette used elsewhere. */
    background: #1a1208;
    z-index: 9999;
    pointer-events: none;
  }
  /* Iowa reading-mode (cream bg): use the warm dark-tan accent-hover for
     a theme-flavored spacer that contrasts against white status-bar icons. */
  body.reading-mode[data-reading-preset="iowa"].embedded::before {
    background: var(--iowa-accent-hover);
  }
  /* Gruvbox dark: use the iconic bg color — status icons read cleanly,
     spacer feels like a natural top-chrome extension. */
  body.reading-mode[data-reading-preset="gruvbox"].embedded::before {
    background: var(--iowa-bg);
  }
  /* Gruvbox hard: use the next-step-up panel so spacer reads as chrome
     rather than bleeding into the near-black body bg. */
  body.reading-mode[data-reading-preset="gruvbox-hard"].embedded::before {
    background: var(--iowa-panel);
  }
  /* Fallback for any reading-mode preset without a specific rule above. */
  body.reading-mode.embedded::before {
    background: var(--iowa-accent-hover, #1a1612);
  }
}

/* Activity log section — fixed above input bar, starts minimized */
#fixed-activity-log {
  flex-shrink: 0;
  border-top: 1px solid #222;
  z-index: 10;
}
.activity-log-section {
  margin: 0;
  border: none;
  border-radius: 0;
  background: #0a0a0a;
  overflow: hidden;
}
.activity-log-header {
  padding: 6px 10px;
  font-size: 12px;
  color: #FFCC00;
  font-family: 'VT323', monospace;
  border-bottom: 1px solid #222;
  background: #111;
  user-select: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 8px;
}
.activity-log-header .activity-expand-icon {
  font-size: 10px;
  color: #555;
  transition: transform 0.2s;
}
.activity-log-section.expanded .activity-expand-icon { transform: rotate(90deg); }
.activity-log-header .activity-latest {
  flex: 1;
  color: #888;
  font-size: 11px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Minimized: show only the header with latest activity summary */
.activity-log-body {
  max-height: 0;
  overflow-y: auto;
  padding: 0;
  transition: max-height 0.2s ease, padding 0.2s ease;
}
/* Expanded: 80% of viewport height */
.activity-log-section.expanded .activity-log-body {
  max-height: 80vh;
  padding: 4px 0;
}
.activity-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 2px 8px;
  font-size: 11px;
  color: #666;
  font-family: 'VT323', monospace;
}
.activity-row.in-progress { color: #FFCC00; }
.activity-time { color: #444; flex-shrink: 0; min-width: 36px; }
.activity-tool { color: #888; flex-shrink: 0; min-width: 60px; }
.activity-msg { color: #555; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
.activity-row.in-progress .activity-msg { color: #FFCC00; }
.activity-row.response-entry { border-top: 1px solid #1a1a1a; padding-top: 3px; margin-top: 2px; }
.activity-row.response-entry .activity-msg { color: #aaa; white-space: normal; line-height: 1.3; }
.activity-row.response-entry .activity-tool { color: #00BFFF; }
body.embedded .activity-row { font-size: 13px; padding: 3px 8px; }

/* Bottom glow for unseen cards */
#bottom-glow {
  height: 0;
  pointer-events: none;
  flex-shrink: 0;
  position: relative;
}
#bottom-glow.active {
  height: 36px;
  background: linear-gradient(to top, rgba(255, 140, 0, 0.25), transparent);
  animation: glow-pulse 2.5s ease-in-out infinite;
}
@keyframes glow-pulse {
  0%, 100% { opacity: 0.4; }
  50% { opacity: 1; }
}

/* Scroll-to-bottom button */
#scroll-to-bottom {
  display: none;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: rgba(255, 102, 0, 0.5);
  border: 2px solid rgba(255, 102, 0, 0.6);
  color: #fff;
  font-size: 22px;
  cursor: pointer;
  z-index: 20;
  align-items: center;
  justify-content: center;
  opacity: 0.4;
  transition: opacity 0.2s;
}
#scroll-to-bottom:hover { opacity: 0.9; background: rgba(255, 102, 0, 0.8); }
#scroll-to-bottom:active { opacity: 1; background: #FF6600; color: #000; }
#scroll-to-bottom.visible { display: flex; }
body.embedded #scroll-to-bottom {
  width: 52px;
  height: 52px;
  font-size: 26px;
}

/* Show more button for older cards */
.show-more-btn {
  text-align: center;
  padding: 10px;
  color: #FF6600;
  font-family: 'VT323', monospace;
  font-size: 16px;
  cursor: pointer;
  border: 1px dashed #333;
  border-radius: 8px;
  margin: 8px;
}
.show-more-btn:hover { background: #1a1a1a; border-color: #FF6600; }
body.embedded .show-more-btn { font-size: 18px; padding: 14px; }

/* Pending queue items at bottom of thread */
.pending-queue-section {
  margin: 12px 8px;
  padding: 8px 10px;
  background: rgba(255, 165, 0, 0.08);
  border: 1px solid rgba(255, 165, 0, 0.2);
  border-radius: 8px;
}
.pending-header {
  font-size: 12px;
  color: #FF9900;
  margin-bottom: 6px;
  font-family: 'VT323', monospace;
}
.pending-item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 6px 8px;
  border-top: 1px solid rgba(255, 165, 0, 0.1);
  border-left: 3px solid #555;
  margin-top: 4px;
  position: relative;
}
.pending-item.from-josh { border-left-color: #FF6600; }
.pending-item.from-steward { border-left-color: #00FF66; }
.pending-direction {
  font-size: 11px;
  font-family: 'VT323', monospace;
  color: #666;
}
.pending-direction .pending-from { color: #FF6600; }
.pending-item.from-steward .pending-direction .pending-from { color: #00FF66; }
.pending-direction .pending-to { color: #888; }
.pending-text {
  font-size: 12px;
  color: #999;
  line-height: 1.4;
  word-break: break-word;
}
.pending-cancel {
  position: absolute;
  top: 4px;
  right: 4px;
  background: none;
  border: 1px solid #555;
  color: #888;
  border-radius: 4px;
  width: 22px;
  height: 22px;
  font-size: 11px;
  cursor: pointer;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.pending-cancel:hover { border-color: #FF3333; color: #FF3333; }
.pending-status-icon { font-size: 11px; }
.pending-time { color: #555; margin-left: auto; }
.pending-item.status-confirmed { opacity: 0.5; border-left-color: #333; }
.pending-item.status-confirmed .pending-direction .pending-status-icon { color: #00FF66; }
.pending-item.status-delivered .pending-direction .pending-status-icon { color: #00BFFF; }
.pending-item.status-failed .pending-direction .pending-status-icon { color: #FF3333; }
.pending-item.status-pending .pending-direction .pending-status-icon { color: #FFCC00; }

/* ===== Unified message log (read-time merge of cards + replies + walkie) ===== */
/* Row = [ big time column | content column ] (Josh's #5). */
.ulog-item {
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  gap: 10px;
  padding: 8px 8px;
  border-top: 1px solid rgba(255, 165, 0, 0.1);
  border-left: 3px solid #555;
  margin-top: 4px;
  position: relative;
}
/* Direction color coding on the left rail: sent=orange (you), received=green
   (steward→you), steward↔steward=dim blue. */
.ulog-item.ulog-sent { border-left-color: #FF6600; }
.ulog-item.ulog-received { border-left-color: #00FF66; }
.ulog-item.ulog-steward { border-left-color: #3a6ea5; }

/* TIME COLUMN — the big, prominent "how long ago" number down the left side
   (Josh's #5: was "shoved upper-right and really faint"). */
.ulog-time {
  flex: 0 0 auto;
  min-width: 64px;
  text-align: right;
  font-family: 'VT323', monospace;
  white-space: nowrap;
  padding-top: 1px;
}
/* Big "how long ago" number. */
.ulog-time-ago {
  font-size: 22px;
  line-height: 1.1;
  color: #FFA500;
  font-weight: bold;
  opacity: 0.85;
}
/* Real abbreviated date+time under it — quieter, so the relative number still
   reads as the dominant element (Josh's #5) while the exact stamp is available
   at a glance. Split across two lines (Josh's tweak): date over clock. */
.ulog-time-abs {
  font-size: 12px;
  line-height: 1.2;
  color: #FFA500;
  opacity: 0.45;
  margin-top: 2px;
}
.ulog-time-date, .ulog-time-clock {
  white-space: nowrap;
}

/* CONTENT COLUMN — dir badge, status, name+emoji, body — right of the time. */
.ulog-content {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.ulog-header {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 14px;
  font-family: 'VT323', monospace;
  color: #888;
  padding: 1px 0;
  /* Header is NO LONGER a toggle — free text, no special touch handling. */
}
.ulog-dir { font-weight: bold; white-space: nowrap; }
.ulog-dir-sent { color: #FF9944; }
.ulog-dir-recv { color: #44FF88; }
.ulog-dir-steward { color: #7aa8d8; }
.ulog-status {
  font-size: 12px;
  padding: 0 4px;
  border-radius: 3px;
  white-space: nowrap;
}
.ulog-status-queued { color: #FFCC00; border: 1px solid rgba(255,204,0,0.4); }
.ulog-status-failed { color: #FF3333; border: 1px solid rgba(255,51,51,0.4); }

/* THE collapse control — a real button, the ONLY toggle target. Sized as a
   comfortable phone tap target and pushed to the right of the header. */
.ulog-chevron {
  margin-left: auto;
  flex: 0 0 auto;
  min-width: 34px;
  height: 30px;
  padding: 0 8px;
  color: #FFA500;
  font-size: 16px;
  line-height: 1;
  text-align: center;
  background: rgba(255, 165, 0, 0.08);
  border: 1px solid rgba(255, 165, 0, 0.3);
  border-radius: 6px;
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  /* A tap on THIS is always a toggle — kill tap-delay / double-tap zoom. */
  touch-action: manipulation;
}
.ulog-chevron:active { background: rgba(255, 165, 0, 0.2); }

.ulog-body {
  font-size: 15px;
  color: #aaa;
  line-height: 1.45;
  word-break: break-word;
  /* Body is FREE for text selection/copy — it is NOT a collapse target.
     -webkit-touch-callout:default is REQUIRED on Android WebView: some ancestor
     rules suppress the long-press callout; without re-enabling it here, a
     long-press on the body text never raises the native copy menu (Josh's #1
     bug — copy "didn't work" on his phone). touch-action:auto lets the browser
     own the long-press-select gesture instead of treating it as a pan. */
  user-select: text;
  -webkit-user-select: text;
  -webkit-touch-callout: default;
  touch-action: auto;
}
.ulog-preview { color: #999; }
.ulog-full { color: #ccc; }
/* Rendered-markdown niceties inside the expanded body. */
.ulog-full p { margin: 0 0 6px; }
.ulog-full p:last-child { margin-bottom: 0; }
.ulog-full ul, .ulog-full ol { margin: 4px 0 6px; padding-left: 18px; }
.ulog-full li { margin: 1px 0; }
.ulog-full code {
  background: #1a1a1a;
  padding: 0 3px;
  border-radius: 3px;
  font-family: 'VT323', monospace;
  color: #FFCC66;
}
.ulog-full pre {
  background: #111;
  padding: 6px 8px;
  border-radius: 4px;
  overflow-x: auto;
}
.ulog-full pre code { background: none; padding: 0; }
.ulog-full strong { color: #fff; }
.ulog-full a { color: #66BBFF; }
.ulog-full h1, .ulog-full h2, .ulog-full h3 { font-size: 16px; margin: 6px 0 4px; color: #fff; }

/* One-click "copy whole message" button — bottom of the expanded view. Full
   width so it's an easy phone tap target; a real button so the tap is a copy,
   never a text-select or collapse. */
.ulog-copy {
  display: block;
  width: 100%;
  margin-top: 10px;
  padding: 9px 10px;
  font-family: 'VT323', monospace;
  font-size: 15px;
  color: #FFA500;
  background: rgba(255, 165, 0, 0.08);
  border: 1px solid rgba(255, 165, 0, 0.3);
  border-radius: 6px;
  cursor: pointer;
  text-align: center;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  touch-action: manipulation;
}
.ulog-copy:active { background: rgba(255, 165, 0, 0.2); }
.ulog-copy-done { color: #44FF88; border-color: rgba(68, 255, 136, 0.5); }

/* Bottom toolbar */
#bottom-toolbar {
  flex-shrink: 0;
  border-top: 1px solid #222;
  background: #0a0a0a;
  /* Anchor for popover panels (queue + history) so they pop ABOVE the
     button row via `bottom: 100%` while leaving the button row visible. */
  position: relative;
}
.toolbar-btn-row {
  display: flex;
  gap: 4px;
  padding: 4px 8px;
}
/* Permanent right-gutter on mobile/embedded so the APK floating side-rail
   column (mic 56dp + 16dp edge margin = ~72dp footprint) has somewhere to
   live without colliding with the rightmost toolbar buttons regardless of
   vertical position. 80px clears it with a small buffer. */
body.embedded .toolbar-btn-row { padding-right: 80px; }
.toolbar-btn {
  flex: 1;
  background: #1a1a1a;
  border: 1px solid #333;
  border-radius: 6px;
  color: #888;
  font-size: 12px;
  font-family: 'VT323', monospace;
  padding: 5px 8px;
  cursor: pointer;
  text-align: center;
}
.toolbar-btn:hover { background: #222; color: #ccc; }
.toolbar-btn.has-pending { color: #FFCC00; border-color: #FFCC0040; }
.toolbar-btn.toolbar-pause { color: #FF6600; border-color: #FF660040; }

/* Icon + small text label stacked vertically. Joshua flagged 2026-05-30:
   icon-only buttons leave him guessing — "inbox" was Foreman's word, not
   his. Every bottom-toolbar button now declares what it does in plain
   text below its icon. */
.toolbar-btn .toolbar-icon {
  display: block;
  font-size: 16px;
  line-height: 1;
}
.toolbar-btn .toolbar-label {
  display: block;
  font-size: 10px;
  line-height: 1.1;
  margin-top: 2px;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  opacity: 0.8;
}
body.embedded .toolbar-btn .toolbar-icon { font-size: 18px; }
body.embedded .toolbar-btn .toolbar-label { font-size: 11px; }
/* Deck count rendered next to the Prev/Next arrows. Inline with the arrow,
   smaller and dimmer so the action label (Prev/Next) stays primary. */
.card-nav-btn .toolbar-icon { display: inline-flex; align-items: baseline; gap: 5px; }
/* Josh 2026-08-11: long-press the Prev/Next buttons opens a jump-to-first/last
   menu. Android WebView otherwise hijacks long-press on <button> (text-select /
   callout), killing the gesture before our 500ms timer fires. Suppress it. */
.card-nav-btn {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
  touch-action: manipulation;
}
.card-nav-count {
  font-size: 13px;
  opacity: 0.7;
  font-variant-numeric: tabular-nums;
  font-family: inherit;
}
body.embedded .card-nav-count { font-size: 14px; }

/* === Card dictation player (Josh 2026-08-12) ===
   Replaces the old Pause button on the card-nav row. A Dictate/Play button
   collapses/expands into a Pause·Back10·Fwd10 transport cluster, with a
   draggable scrubber bar mounted ABOVE the button row. */
.tts-transport-slot {
  display: flex;
  gap: 4px;
  flex: 1 1 auto;        /* occupy the old Pause slot; grows to fit the cluster */
  min-width: 64px;       /* never collapse to 0 behind the nav buttons' min-content */
}
.tts-transport-slot.expanded { flex: 2 1 auto; min-width: 168px; }   /* three buttons need more room */
.tts-transport-slot .toolbar-btn { flex: 1; min-width: 0; }
/* Android WebView hijacks long-press on <button> (text-select / callout),
   which kills the Back10 long-press-to-start gesture. Suppress it across the
   transport cluster (matches the card-nav treatment). */
.tts-transport-slot .toolbar-btn {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
  touch-action: manipulation;
}
.tts-dictate-btn { color: #00CFFF; border-color: #00CFFF40; }
.tts-dictate-btn:hover { color: #00CFFF; border-color: #00CFFF; background: #00CFFF15; }
.tts-dictate-btn.loading { opacity: 0.7; }
/* Failed dictation (Josh 2026-09-19): the toast self-dismisses after 15s, so the
   button itself has to keep showing that the last attempt failed and offer Retry —
   otherwise a glance a minute later is indistinguishable from never having tapped. */
.tts-dictate-btn.tts-failed,
.toolbar-btn.tts-failed { color: #ff6666; border-color: #ff4444; background: #ff444418; }
.tts-dictate-btn.tts-failed:hover,
.toolbar-btn.tts-failed:hover { color: #ff8888; border-color: #ff6666; background: #ff444430; }
.tts-playpause-btn { color: #00FF66; border-color: #00FF6640; }
.tts-playpause-btn:hover { background: #00FF6615; color: #00FF66; }
.tts-back10-btn, .tts-fwd10-btn { color: #00CFFF; border-color: #00CFFF33; }
.tts-back10-btn:hover, .tts-fwd10-btn:hover { background: #00CFFF12; color: #00CFFF; }
.tts-back10-btn .toolbar-icon, .tts-fwd10-btn .toolbar-icon {
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.5px;
}

/* === "Reading: <card>" bar + jump-back (Josh 2026-09-20) ===
   Sits ABOVE the scrubber. Only visible when the card being read aloud is NOT the
   card on screen — so it costs zero height in the common case. The whole bar is one
   button: on a phone the tap target should be the full width, not an icon. */
.tts-nowplaying-row {
  display: flex;
  align-items: stretch;
  width: 100%;
  background: #00CFFF14;
  border-top: 1px solid #00CFFF33;
  border-bottom: 1px solid #00CFFF22;
}
.tts-nowplaying-bar {
  display: flex;
  align-items: center;
  gap: 7px;
  flex: 1 1 auto;
  min-width: 0;
  padding: 7px 10px 7px 12px;
  background: transparent;
  border: none;
  font-family: 'VT323', monospace;
  text-align: left;
  cursor: pointer;
  /* Android WebView hijacks long-press on <button> — same treatment as the
     transport cluster and card-nav, or the tap reads as a text selection. */
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
  touch-action: manipulation;
}
.tts-nowplaying-bar:active { background: #00CFFF2E; }
.tts-nowplaying-icon { font-size: 14px; color: #00FF66; flex: 0 0 auto; }
.tts-nowplaying-bar.paused .tts-nowplaying-icon { color: #00CFFF; }
.tts-nowplaying-label {
  font-size: 15px;
  color: #00CFFF;
  opacity: 0.8;
  flex: 0 0 auto;
}
/* The card name. Ellipsizes rather than wrapping — this bar must stay one line
   tall on a phone however long the card title is. */
.tts-nowplaying-title {
  font-size: 15px;
  color: #eaeaea;
  flex: 1 1 auto;
  /* Floor the name's width so the fixed-width buttons beside it cannot squeeze it
     down to "Phot…" (observed before this floor existed). It still ellipsizes on a
     long title — it just always shows a readable run of characters first. */
  min-width: 11ch;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
/* "Back to card" is the primary action and stays spelled out — Josh reads these
   as plain English, not icons — so it never shrinks or clips. The title takes
   whatever space is left (and ellipsizes), which is the right way round. */
.tts-nowplaying-jump {
  font-size: 14px;
  color: #00CFFF;
  flex: 0 0 auto;
  white-space: nowrap;
  padding: 1px 7px;
  border: 1px solid #00CFFF55;
  border-radius: 4px;
  background: #00CFFF12;
}
body.embedded .tts-nowplaying-label,
body.embedded .tts-nowplaying-title { font-size: 16px; }
body.embedded .tts-nowplaying-jump { font-size: 15px; }

/* "Read this" — start the card on screen instead. Only reachable while something
   else is playing (the row it lives in is hidden otherwise), which is exactly when
   the transport slot below has no room for a Dictate button. */
.tts-nowplaying-switch {
  display: flex;
  align-items: center;
  gap: 4px;
  flex: 0 0 auto;
  padding: 0 12px;
  background: transparent;
  border: none;
  border-left: 1px solid #00CFFF33;
  color: #00FF66;
  font-family: 'VT323', monospace;
  font-size: 14px;
  white-space: nowrap;
  cursor: pointer;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
  touch-action: manipulation;
}
.tts-nowplaying-switch:active { background: #00FF6622; }
.tts-nowplaying-switch-icon { font-size: 12px; }
body.embedded .tts-nowplaying-switch { font-size: 15px; }

/* On a real phone (~390px) the two labels together starve the card name down to
   ~11 characters — measured, not guessed. "Read this" is the secondary action, so
   it gives up its words there and keeps its ▶ glyph; "Back to card" and the name
   both stay readable. Above phone width everything is spelled out. */
@media (max-width: 480px) {
  .tts-nowplaying-switch,
  body.embedded .tts-nowplaying-switch { padding: 0 10px; gap: 0; font-size: 0; }
  .tts-nowplaying-switch .tts-nowplaying-switch-icon { font-size: 15px; }
  body.embedded .tts-nowplaying-switch .tts-nowplaying-switch-icon { font-size: 16px; }
}

/* Reading-mode (parchment) presets: the dark-theme cyan/near-white is unreadable
   on a light surface, so repaint from the iowa palette like the rest of the
   toolbar does. Verified against the light theme, not assumed. */
body.reading-mode .tts-nowplaying-row {
  background: var(--iowa-panel);
  border-top-color: var(--iowa-border);
  border-bottom-color: var(--iowa-border);
}
body.reading-mode .tts-nowplaying-label { color: var(--iowa-accent); }
body.reading-mode .tts-nowplaying-title { color: var(--iowa-text); }
body.reading-mode .tts-nowplaying-jump {
  color: var(--iowa-accent);
  border-color: var(--iowa-border);
  background: transparent;
}
body.reading-mode .tts-nowplaying-switch {
  color: var(--iowa-accent);
  border-left-color: var(--iowa-border);
}
body.reading-mode .tts-nowplaying-icon { color: var(--iowa-accent); }

/* Scrubber bar — sits above the button row (prepended to the toolbar). */
.tts-scrubber-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px 2px;
}
.tts-scrubber-time {
  font-family: 'VT323', monospace;
  font-size: 13px;
  color: #00CFFF;
  opacity: 0.85;
  font-variant-numeric: tabular-nums;
  min-width: 34px;
  text-align: center;
}
body.embedded .tts-scrubber-time { font-size: 14px; }
.tts-scrubber-range {
  -webkit-appearance: none;
  appearance: none;
  flex: 1;
  height: 6px;
  border-radius: 3px;
  background: #00CFFF33;
  outline: none;
  touch-action: none;    /* let the thumb be dragged, not scroll the page */
  cursor: pointer;
}
.tts-scrubber-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: #00CFFF;
  border: 2px solid #0a0a0a;
  box-shadow: 0 0 4px #00CFFF88;
  cursor: pointer;
}
.tts-scrubber-range::-moz-range-thumb {
  width: 18px;
  height: 18px;
  border: 2px solid #0a0a0a;
  border-radius: 50%;
  background: #00CFFF;
  box-shadow: 0 0 4px #00CFFF88;
  cursor: pointer;
}
.toolbar-panel {
  max-height: 200px;
  overflow-y: auto;
  border-top: 1px solid #222;
  padding: 4px 0;
}
body.embedded .toolbar-btn { font-size: 14px; padding: 8px; }
body.embedded .toolbar-panel { max-height: 40vh; }

/* Queue panel deepens when it's the active surface — Joshua wants real
   scroll-through-history, not a 3-row peek. Bumps both desktop + embedded. */
.toolbar-panel.queue-panel { max-height: 60vh; }
body.embedded .toolbar-panel.queue-panel { max-height: 70vh; }

/* === Unified pop-above-toolbar popovers (embedded only) ===
   Queue + History dropdowns share the same overlay geometry on mobile/embedded:
   anchored to the toolbar's top edge via JS-measured `bottom`, full width,
   overlay the card body above. The action button row + composer stay visible
   underneath so Joshua can tap the same trigger to toggle off. */
body.embedded .toolbar-panel.queue-panel.docked-above-toolbar,
body.embedded #history-viewer-panel.docked-above-toolbar {
  position: fixed;
  left: 0;
  right: 0;
  /* `bottom` is set inline by JS to the live toolbar offsetHeight so the
     popover sits flush above the button row regardless of toolbar growth. */
  top: 80px; /* visible gap above so Joshua sees the data top-boundary */
  width: auto;
  max-width: none;
  transform: none;
  margin: 0;
  /* No max-height — `top` + JS-set `bottom` already bound the panel.
     Joshua flagged 2026-05-30: overlay reaches the top edge with no
     visible boundary so he can't tell where the data starts. The 80px
     top gap is the boundary. */
  max-height: none;
  z-index: 50;
  border-radius: 6px 6px 0 0;
  border-top: 2px solid #444;
  box-shadow: 0 -8px 24px rgba(0,0,0,0.6);
}

/* Filter header for the Q view — toggle chip + count */
.queue-filter-header {
  display: flex;
  align-items: center;
  /* Two toggles now (Josh #3) — allow wrap so they never overflow on a phone. */
  flex-wrap: wrap;
  gap: 6px;
  padding: 6px 8px;
  border-bottom: 1px solid #222;
  position: sticky;
  top: 0;
  background: #0a0a0a;
  z-index: 1;
}
/* Footer variant (Josh #3): the toggles live at the BOTTOM of the panel.
   Stick to the bottom edge and flip the divider to a top border. */
.queue-filter-footer {
  top: auto;
  bottom: 0;
  border-bottom: none;
  border-top: 1px solid #222;
}
.queue-filter-chip {
  background: #1a1a1a;
  border: 1px solid #333;
  border-radius: 12px;
  color: #888;
  font-size: 13px;
  font-family: inherit;
  padding: 5px 12px;
  cursor: pointer;
}
.queue-filter-chip.active {
  color: #FF6600;
  border-color: #FF660066;
  background: rgba(255, 102, 0, 0.08);
}
.queue-filter-chip:hover { color: #ccc; }
.queue-filter-count {
  font-size: 11px;
  color: #555;
  font-family: inherit;
  margin-left: auto;
}
.queue-list { padding: 2px 0; }

/* Paired card+reply rendering inside .pending-item — original card on top,
   Joshua's reply below. Both halves are visible together so the reply has
   context. Fonts inherit from .pending-item to avoid hardcoded font-family. */
.pending-item.has-pair { gap: 4px; }
.pair-card,
.pair-reply {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 4px 6px;
  border-radius: 4px;
  font-family: inherit;
}
.pair-card {
  background: rgba(0, 255, 102, 0.04);
  border-left: 2px solid rgba(0, 255, 102, 0.4);
}
.pair-reply {
  background: rgba(255, 102, 0, 0.05);
  border-left: 2px solid rgba(255, 102, 0, 0.5);
}
.pair-label {
  font-size: 10px;
  color: #777;
  font-family: inherit;
  text-transform: lowercase;
  letter-spacing: 0.5px;
}
.pair-label-card { color: #5fbf6f; }
.pair-label-reply { color: #FF8833; }
.pair-card-text,
.pair-reply-text {
  font-size: 13px;
  line-height: 1.4;
  color: #cfcfcf;
  font-family: inherit;
  word-break: break-word;
  white-space: pre-wrap;
}
.pair-reply-text { color: #FFD9B0; }

/* Reading-mode overrides for everything new above (lesson-no-hardcoded-fonts
   pairing rule — every new color rule ships with its Iowa counterpart). */
body.reading-mode .queue-filter-header {
  background: var(--iowa-panel);
  border-bottom-color: var(--iowa-border);
}
body.reading-mode .queue-filter-chip {
  background: var(--iowa-panel);
  border-color: var(--iowa-border);
  color: var(--iowa-text);
}
body.reading-mode .queue-filter-chip.active {
  color: var(--iowa-accent);
  border-color: var(--iowa-accent);
  background: rgba(122, 92, 62, 0.12);
}
body.reading-mode .queue-filter-count { color: var(--iowa-text-faint); }
body.reading-mode .pair-card {
  background: rgba(149, 169, 137, 0.08);
  border-left-color: var(--iowa-sage);
}
body.reading-mode .pair-reply {
  background: rgba(122, 92, 62, 0.08);
  border-left-color: var(--iowa-accent);
}
body.reading-mode .pair-label-card { color: var(--iowa-sage-dark); }
body.reading-mode .pair-label-reply { color: var(--iowa-accent); }
body.reading-mode .pair-card-text,
body.reading-mode .pair-reply-text { color: var(--iowa-text); }

/* Pause agent button */
.pause-agent-row {
  display: flex;
  justify-content: center;
  padding: 8px;
}
.pause-agent-btn {
  padding: 8px 20px;
  font-size: 14px;
  font-family: 'VT323', monospace;
  background: rgba(255, 204, 0, 0.1);
  border: 1px solid rgba(255, 204, 0, 0.3);
  color: #FFCC00;
  border-radius: 6px;
  cursor: pointer;
}
.pause-agent-btn:hover { background: rgba(255, 204, 0, 0.2); border-color: #FFCC00; }
.pause-agent-btn:disabled { opacity: 0.5; cursor: default; }

/* Bookmarks pill — opens dropdown of per-steward links */
/* R4 (Josh 2026-08-13): the Bookmarks pill + dropdown and the top Links pill
   were removed. Links now open from a per-card footer button into the
   #card-links-dropdown overlay (styled below). The .bookmark-* and
   #embedded-view-pill / #card-links-pill rules are gone with them. */

/* === Card Links: auto-saved links from presenter cards === */
/* Opened from the per-card footer "Links" button (R4). Anchored to the bottom
   of the screen, just above the footer bar, so it's reachable on the phone
   where the trigger now lives. */
/* Facelift (Josh 2026-08-23): Josh HATED the old look — "ugly, hard to read".
   Real hierarchy now: bright title, a pill-tagged Opened/Added meta line, roomy
   tap targets, a header strip, and a hairline between rows so links read as a
   clean scannable list instead of grey soup. */
#card-links-dropdown {
  display: none;
  position: absolute;
  /* Josh 2026-09-09: lifted from 96px. At 96 the panel's bottom edge landed at
     y≈608 while the card's footer row (dismiss / 🔗 / pin) spans y≈593-633 —
     so the DISMISS button sat directly under the panel's lower edge, and only
     42px separated that edge from the Links button he taps to close. He kept
     catching dismiss and losing the card: "right near the links button is also
     the dismiss button and sometimes I'll accidentally click the dismiss
     button... which is no good." 148px clears the whole footer row, so a
     mis-aimed close-tap can't reach dismiss. */
  bottom: 148px;
  left: 10px;
  right: 10px;
  z-index: 21;
  max-width: 440px;
  margin: 0 auto;
  /* Anchored to the bottom (just above the toolbar) and grows UPWARD, hugging
     its content for short lists. Cap the height so a long list never climbs into
     the top status bar / titlebar zone (~150px) — it scrolls internally instead
     of clipping the first row behind the status pill. */
  /* Josh 2026-09-04: the old cap (100vh - 246px) let a long list climb until
     its header sat at y = -16px — literally off the top of the screen ("it's
     almost leaving the screen"). The reserve is now sized off the real top
     chrome: 40px status-bar spacer + the CPU-dot row + a comfortable margin, so
     the panel's top edge stops WELL clear of the status bar. The list just
     scrolls a little more inside — which is free, now that the freshest link is
     parked at the bottom instead of the top. */
  /* Grows with the raised bottom edge (+52px) so lifting the panel moves the
     WHOLE panel down-to-up as one — without this the top edge would climb the
     same 52px back toward the status bar and undo the 2026-09-04 fix. */
  max-height: calc(100vh - 382px);
  overflow-y: auto;
  flex-direction: column;
  /* Vertical breathing room (Josh 2026-09-04: rows were "jammed up each against
     each other top to bottom"). The gap replaces the old hairline divider —
     separated rows don't need a line to be legible as a list.
     Round 2: the rows now carry their own margin-top to make room for the
     badges hanging off their top edge, so the panel's own gap drops to 0 —
     otherwise the two stack and the list gets airy again. */
  gap: 0;
  padding: 6px;
  background: rgba(16, 16, 18, 0.98);
  border: 1px solid #3a3a3a;
  border-radius: 12px;
  box-shadow: 0 12px 40px rgba(0,0,0,0.55);
  font-family: 'VT323', monospace;
}
/* Header strip — labels the panel + shows the count so the list has a top edge.
   Sticky (Josh 2026-09-04): it used to be a plain in-flow strip, so on a long
   list it scrolled away with the content and the panel lost its top edge
   entirely. Pinned to the panel's own top edge it stays readable at any scroll
   position — which matters more now that the panel opens scrolled to the
   BOTTOM, where an in-flow header would never be on screen at all. The opaque
   background is required: rows scroll UNDER it. */
.card-links-header {
  position: sticky;
  top: -6px;               /* cancel the panel's 6px padding so it truly pins */
  z-index: 2;
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 8px 10px 8px;
  margin: -6px -6px 2px;   /* bleed to the panel edges so nothing shows beside it */
  background: rgba(16, 16, 18, 0.98);
  border-bottom: 1px solid #2a2a2a;
  border-radius: 12px 12px 0 0;
}
.card-links-header .cl-h-title {
  font-size: 17px;
  letter-spacing: 0.5px;
  color: #eee;
}
.card-links-header .cl-h-count {
  font-size: 13px;
  color: #ff8a2a;
  opacity: 0.9;
}
/* "Signal Dots" row (Josh 2026-08-23, design #1 of the 10-variation gallery):
   colored recency dot → title + color-coded time chips → forget button. */
.card-link-row {
  display: flex;
  align-items: center;
  gap: 6px;
  border-radius: 8px;
  padding-left: 4px;
  /* Positioned parent for the badges that hang off this row's button. */
  position: relative;
  /* Josh 2026-09-04 — THE THING HE CALLED OUT: "you never consider it having to
     show over the parent element". A badge that hangs above the button has to
     PAINT OVER the neighbouring row, and by default it loses twice: later rows
     paint over earlier ones, and any clip in the chain cuts it off. So:
     z-index lifts the row (with its overhanging badges) above the row ABOVE it,
     and no ancestor between here and the panel may clip. `overflow: visible` is
     explicit so nobody re-adds a clip here later without noticing. */
  z-index: 1;
  overflow: visible;
  /* The badges sit ABOVE the button's top edge, so the row reserves that strip
     for them. This is the row's only real height cost and it's what buys the
     title the entire button width. */
  margin-top: 9px;
  /* Each row is now its own card (Josh 2026-09-04). With real gaps between
     them, a faint surface reads as a discrete tappable item far better than the
     old edge-to-edge strip did — and it makes the separation obvious without
     the divider line the gap replaced. */
  background: rgba(255, 255, 255, 0.028);
}
/* Hover/active must out-stack their neighbours too, so a hovered row's badges
   are never painted over by the row below it. */
.card-link-row:hover { background: rgba(255,255,255,0.06); z-index: 3; }
/* The recency dot — background color + glow set inline from openColor(); this is
   the at-a-glance "how recently did I open this" signal. Muted grey for never. */
.card-link-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  flex: none;
  margin-left: 4px;
}
.card-link-dot.is-never { background: #4a4a50; box-shadow: none; }
/* Josh 2026-09-04: this was a COLUMN — title on line one, time chips on line
   two — which is exactly why every row ran ~67px tall while the right-hand half
   of the title line sat empty ("a lot of wasted space to the right... make them
   a little bit shorter"). It's a ROW now: the title takes the space it needs and
   the chips move up beside it into that dead width, so one line does the work
   of two and the row height roughly halves. The title flexes and ellipsises;
   the chips are flex:none so they never get squeezed out by a long title. */
/* Josh 2026-09-04 (round 2): the button is now TITLE ONLY. The time/worker
   badges moved OUT of it and hang off its top edge, so the full width of this
   button belongs to the link name — that's the fix for "I can't even read the
   title of some of these... I can only see one character". */
.card-link-btn {
  flex: 1;
  min-width: 0;                /* lets the title actually ellipsis inside flex */
  display: flex;
  flex-direction: row;
  align-items: center;
  text-align: left;
  padding: 6px 8px 6px 10px;
  font-size: 15px;
  font-family: 'VT323', monospace;
  color: #eaeaea;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 8px;
  cursor: pointer;
  /* MUST stay visible: `hidden` here is one of the two clips that would chop
     the hanging badges off (the other was the panel's overflow-x). The title
     still ellipsises correctly — that's driven by min-width:0 + the title's own
     overflow, not by a clip on the button. */
  overflow: visible;
  min-height: 34px;            /* still a comfortable phone tap target */
}
.card-link-btn:hover { color: #FF8A2A; }
.card-link-title {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  /* The badges no longer share this line, so the title takes ALL of it and
     only truncates when the name genuinely exceeds the row (Josh 2026-09-04:
     "we leave the whole space in between for the actual title of the link
     only"). Still nowrap — a wrapped title would put the row height back. */
  flex: 1 1 auto;
  min-width: 0;
  font-size: 17px;
  line-height: 1.15;
  color: #f2f2f2;
}
.card-link-btn:hover .card-link-title { color: #FF8A2A; }
/* Time chip row — OPENED (warm scale) + ADDED (cool scale) sit side by side; the
   chip's color IS the read, so which-is-which and how-recent land at a glance. */
/* THE HANGING BADGE STRIP (Josh 2026-09-04). Lifted out of the button and
   pinned to the row's top-right, riding ABOVE the button's top edge so it
   overhangs into the gap and over the row above — costing the row no width at
   all. Right-anchored so it never collides with the title, which grows from the
   left. `bottom: 100%` + the negative margin is what produces the overhang;
   the row's z-index (not this element's) is what wins the paint order against
   the neighbouring row. pointer-events:none so the badges can never eat a tap
   meant for the link or the forget button underneath them. */
.card-link-meta-row {
  position: absolute;
  bottom: 100%;
  right: 36px;                 /* clear of the ✕ forget button */
  margin-bottom: -11px;        /* drop back down so it straddles the edge */
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  justify-content: flex-end;
  gap: 4px;
  max-width: calc(100% - 44px);
  pointer-events: none;
}
/* A single color-coded time chip. Background/color/border are set inline from
   openColor()/addColor(); these rules carry only the shared shape. */
/* Badge scale (Josh 2026-09-04 round 3): "those can be smaller... that font
   size really isn't that much smaller than the actual title". Title is 17px;
   these drop to 9px so they read clearly as secondary tags, not competing
   labels. */
.card-link-chip {
  display: inline-flex;
  align-items: baseline;
  gap: 3px;
  font-size: 9px;
  font-family: 'VT323', monospace;
  padding: 0 5px;
  border-radius: 999px;
  border: 1px solid transparent;
  white-space: nowrap;
  line-height: 1.6;
  /* Josh: "that orange shows right through all the badges... that looks dorky."
     The badge fill is a translucent tint over the button's own border line, so
     the line showed through. timeChip() now paints the tint as a background-
     IMAGE layer over an opaque panel-toned background-color (set here), so the
     tint keeps its colour while nothing behind the badge shows through. The
     matching ring covers the 1px the border itself occupies. */
  background-color: #101012;
  box-shadow: 0 0 0 2px #101012;
}
.card-link-chip-k {
  font-size: 10px;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  opacity: 0.85;
}
.card-link-chip-v { font-weight: 700; letter-spacing: 0.3px; }
.card-link-chip.is-muted .card-link-chip-v { font-style: italic; }
/* Josh 2026-09-04: on one line, an "OPENED never" chip is the single biggest
   waste of row width — and it's redundant, because the leading dot is already
   the muted grey that means never-opened. Hide the chip when it's muted; the
   width goes to the title instead. A real OPENED time still shows. */
.card-link-chip.is-muted { display: none; }
/* Originating-worker line (Joshua 2026-08-14 "who made this link") — accent
   tint so the worker name stands out; only present when a worker made the link. */
/* The third hanging badge: steading icon + worker name (Josh 2026-09-04
   "show the icon for that setting, and then also show the worker's name").
   Same pill shape as the time badges so the three read as one set. */
.card-link-worker {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 9px;
  line-height: 1.6;
  padding: 0 5px;
  border-radius: 999px;
  white-space: nowrap;
  color: var(--pill-color, #FF8A2A);
  /* Opaque base + tint layer, same reason as .card-link-chip: nothing behind
     the badge may show through it. */
  background-color: #101012;
  background-image: linear-gradient(rgba(255,138,42,0.14), rgba(255,138,42,0.14));
  border: 1px solid rgba(255, 138, 42, 0.32);
  box-shadow: 0 0 0 2px #101012;
  min-width: 0;
  /* Josh 2026-09-04: "it'd be great if we could shorten that part". Capped so
     the badge stays a tag rather than a sentence; the icon carries the identity
     at a glance and the full name is in the tooltip. The cap is in ems so it
     tracks the 9px badge text — at that size ~150px still reads as a short tag
     while fitting most worker names whole. */
  max-width: 16em;
}
/* The icon stays legible even though the text shrank — it's the fastest read
   in the badge (Josh: "I like how we snuck the icon in here"). */
.card-link-worker-icon { font-size: 10px; line-height: 1; flex: none; }
/* The name yields first if the badge strip runs out of room — the icon and the
   time badges are fixed, so this is the only elastic part. */
.card-link-worker-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
body.reading-mode .card-link-worker {
  color: var(--iowa-accent);
  /* Opaque cream base so the button's line can't show through on light themes
     either; --iowa-panel is the panel tone these badges sit on. */
  background-color: var(--iowa-panel, #f4ece0);
  background-image: linear-gradient(rgba(0,0,0,0.06), rgba(0,0,0,0.06));
  border-color: rgba(0, 0, 0, 0.18);
  box-shadow: 0 0 0 2px var(--iowa-panel, #f4ece0);
}
body.reading-mode .card-link-chip {
  background-color: var(--iowa-panel, #f4ece0);
  box-shadow: 0 0 0 2px var(--iowa-panel, #f4ece0);
}
.card-link-action {
  align-self: center;
  width: 30px; height: 30px;
  padding: 0;
  font-size: 13px;
  font-family: 'VT323', monospace;
  color: #777;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 8px;
  cursor: pointer;
  flex-shrink: 0;
}
.card-link-action:hover { color: #FF6600; border-color: #444; background: rgba(255,102,0,0.08); }
/* Three-dot row menu button (Josh 2026-09-09) — replaced the bare ✕. Bigger
   glyph than the old ✕ so the three dots read clearly at phone size, and a
   comfortably tappable box. */
.card-link-menu-btn {
  font-size: 20px;
  line-height: 1;
  letter-spacing: 1px;
}
/* Pinned rows carry a visible marker so "why is this one parked down here" is
   answerable at a glance — a warm left edge plus a slightly lifted surface. */
.card-link-row.is-pinned {
  background: rgba(255, 138, 42, 0.075);
  box-shadow: inset 2px 0 0 #ff8a2a;
}
.card-link-row.is-pinned:hover { background: rgba(255, 138, 42, 0.12); }
/* The row menu is portaled to <body> so the panel's scroll can't clip it; it is
   positioned from JS against the dots' live rect. */
.card-link-menu {
  position: fixed;
  z-index: 4000;
  min-width: 148px;
  padding: 5px;
  background: #1b1b1f;
  border: 1px solid #35353c;
  border-radius: 10px;
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.55);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.card-link-menu-item {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  padding: 10px 11px;
  font-size: 17px;
  font-family: 'VT323', monospace;
  text-align: left;
  color: #d8d8dc;
  background: transparent;
  border: none;
  border-radius: 7px;
  cursor: pointer;
}
.card-link-menu-item:hover { background: rgba(255, 102, 0, 0.14); color: #ff8a2a; }
.card-link-menu-glyph { font-size: 14px; width: 18px; flex: none; text-align: center; }
body.reading-mode .card-link-menu {
  background: var(--iowa-panel, #f4ece0);
  border-color: var(--iowa-border, #d8ccb8);
}
body.reading-mode .card-link-menu-item { color: var(--iowa-ink, #2e2a24); }
body.reading-mode .card-link-menu-item:hover {
  background: rgba(0, 0, 0, 0.06);
  color: var(--iowa-accent);
}
body.reading-mode .card-link-row.is-pinned {
  background: rgba(0, 0, 0, 0.045);
  box-shadow: inset 2px 0 0 var(--iowa-accent);
}
.card-links-empty {
  padding: 22px 14px;
  font-size: 16px;
  font-family: 'VT323', monospace;
  color: #7a7a7a;
  text-align: center;
}

/* === Custom card components === */
.msg-component {
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px dashed #333;
}
.card-component-error {
  color: #ff6666;
  font-family: 'VT323', monospace;
  font-size: 13px;
  padding: 8px;
  border: 1px solid #442222;
  border-radius: 4px;
}


/* --- Sidebar kebab + bulk-actions popover --- */
.steward-kebab {
  flex-shrink: 0;
  padding: 2px 6px;
  margin-left: 4px;
  font-size: 16px;
  color: #555;
  cursor: pointer;
  border-radius: 4px;
  line-height: 1;
}
.steward-kebab:hover { color: #fff; background: #2a2a2a; }

/* Always-visible upper-right session kebab — used for clear-suggestion menu.
   Pulled out of flex flow so it doesn't steal horizontal space from names. */
.steward-row .session-kebab {
  position: absolute;
  top: 4px;
  right: 6px;
  margin-left: 0;
  font-size: 14px;
  color: #3a3a3a;
  padding: 2px 6px;
  z-index: 2;
}
.steward-row:hover .session-kebab { color: #888; }
.steward-row .session-kebab:hover { color: #fff; background: #2a2a2a; }

/* Subtle per-session context bar at the bottom of each steward row.
   Shows % of 1M tokens used since last /clear + time-since-clear. */
.steward-context-bar {
  position: absolute;
  left: 6px;
  right: 6px;
  bottom: 2px;
  height: 10px;
  display: flex;
  align-items: center;
  gap: 6px;
  pointer-events: none;
  z-index: 1;
}
.steward-context-fill {
  position: absolute;
  left: 0;
  bottom: 0;
  height: 2px;
  background: #2d5d3f; /* muted green */
  border-radius: 1px;
  transition: width 0.5s ease-out;
}
.steward-context-fill.warn { background: #8a6d2e; }
.steward-context-fill.danger { background: #7a2d2d; }
.steward-context-meta {
  margin-left: auto;
  font-size: 9px;
  color: #3a3a3a;
  font-family: 'VT323', monospace;
  letter-spacing: 0.5px;
  line-height: 1;
}
.steward-row:hover .steward-context-meta { color: #777; }

/* Smaller/subtler for sub and sub-sub rows */
.steward-row.sub-row .steward-context-bar,
.steward-row.sub-sub-row .steward-context-bar { bottom: 1px; height: 8px; }
.steward-row.sub-row .steward-context-meta,
.steward-row.sub-sub-row .steward-context-meta { font-size: 8px; }
.steward-row.sub-row .session-kebab,
.steward-row.sub-sub-row .session-kebab { font-size: 12px; top: 2px; right: 4px; }

/* --- Theme picker popover (📖 titlebar button) --- */
.theme-picker-popover {
  position: fixed;
  z-index: 10000;
  width: 340px;
  background: #121212;
  border: 1px solid #333;
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.6);
  padding: 10px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  font-family: 'VT323', monospace;
}
.theme-picker-header {
  color: #888;
  font-size: 12px;
  padding: 0 2px 6px;
  border-bottom: 1px solid #222;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.theme-picker-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6px;
}
.theme-tile {
  position: relative;
  background: #1a1a1a;
  border: 1px solid #333;
  border-radius: 6px;
  padding: 10px 8px;
  cursor: pointer;
  text-align: left;
  font-family: inherit;
  color: #ccc;
  transition: background 0.15s, border-color 0.15s;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.theme-tile:hover { background: #232323; border-color: #555; }
.theme-tile.active {
  border-color: #FF6600;
  background: #1f1a14;
}
.theme-tile-swatches {
  display: flex;
  gap: 2px;
  height: 14px;
  border-radius: 3px;
  overflow: hidden;
  border: 1px solid #333;
}
.theme-tile-swatch {
  flex: 1;
  display: block;
}
.theme-tile-name {
  font-size: 15px;
  color: #eee;
  font-weight: bold;
}
.theme-tile.active .theme-tile-name { color: #FF6600; }
.theme-tile-sub {
  font-size: 11px;
  color: #777;
}
.theme-tile-check {
  position: absolute;
  top: 6px;
  right: 8px;
  color: #FF6600;
  font-size: 14px;
  font-weight: bold;
}

.bulk-actions-popover {
  position: fixed;
  z-index: 10000;
  width: 260px;
  background: #121212;
  border: 1px solid #333;
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.6);
  padding: 10px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  font-size: 13px;
}
.bulk-actions-header {
  color: #888;
  font-size: 12px;
  padding: 0 2px 6px;
  border-bottom: 1px solid #222;
}
.bulk-actions-btn {
  background: #1f1f1f;
  color: #ddd;
  border: 1px solid #333;
  border-radius: 6px;
  padding: 8px 10px;
  text-align: left;
  cursor: pointer;
  font-size: 13px;
}
.bulk-actions-btn:hover:not(:disabled) {
  background: #2a1a10;
  border-color: #FF6600;
  color: #FF6600;
}
.bulk-actions-btn:disabled { opacity: 0.35; cursor: not-allowed; }
.bulk-actions-sublabel {
  color: #666;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  padding: 2px 2px 0;
}
.bulk-actions-thresholds {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 4px;
}
/* Adaptive dismissal buckets — vertical stack (variable count, 1–4
   typically). Each button is a full-width row showing the threshold age
   and the number of cards that would be dismissed. */
.bulk-actions-buckets {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.bulk-actions-btn.threshold {
  padding: 6px 10px;
  text-align: left;
  font-size: 12px;
}

/* Mobile — bigger popover + bigger buttons. Desktop stays compact.
   Josh 2026-04-21: "pop-up that shows up, that needs to be a little bit
   bigger. Because it works great on desktop, but I think it'd be small
   on mobile." */
body.embedded .bulk-actions-popover {
  width: min(320px, 92vw);
  padding: 14px;
  gap: 10px;
}
body.embedded .bulk-actions-btn {
  padding: 14px 14px;
  font-size: 15px;
  min-height: 48px;
  border-radius: 8px;
}
body.embedded .bulk-actions-btn.threshold {
  padding: 12px 14px;
  font-size: 14px;
  min-height: 44px;
}
body.embedded .bulk-actions-header {
  font-size: 14px;
  padding: 6px 4px 8px;
}
body.embedded .bulk-actions-sublabel {
  font-size: 12px;
  padding: 4px 2px 2px;
}

/* ============================================================
   Reading mode — Iowa preset (warm editorial, iA Writer feel)
   Toggled via #ctrl-reading-mode in the titlebar. Default OFF.
   body.reading-mode root scope.
   Goal: calm, warm, readable. NOT remove all color — soften neon
   glows and saturated badges into muted earth-tone equivalents.
   Custom components (tables, code blocks) escape the prose cap.
   ============================================================ */

/* Toggle button state */
#ctrl-reading-mode { padding: 0 4px; }
#ctrl-reading-mode.active { color: #C8A96A; border-color: #C8A96A; }
#ctrl-fullscreen { padding: 0 4px; }
#ctrl-fullscreen.active { color: #FF6600; border-color: #FF6600; }
body.reading-mode #ctrl-fullscreen.active { color: var(--iowa-accent); border-color: var(--iowa-accent); }

/* --- CPU readout pill in titlebar ---
   Selector must beat #titlebar-controls .ctrl-btn (which sets tighter
   height/font/padding) since the pill also carries the .ctrl-btn class. */
#titlebar-controls #ctrl-cpu-pill {
  padding: 3px 10px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  font-size: 13px;
  font-family: 'VT323', monospace;
  letter-spacing: 0.3px;
  width: auto;
  min-width: 100px;
  border: 1px solid #2a2a2a;
  background: rgba(255, 255, 255, 0.03);
  color: #aaa;
  cursor: pointer;
  border-radius: 4px;
  height: 24px;
  margin-left: 4px;
}
#titlebar-controls #ctrl-cpu-pill #cpu-pill-cpu,
#titlebar-controls #ctrl-cpu-pill #cpu-pill-procs { line-height: 1; }
#ctrl-cpu-pill:hover { border-color: #555; color: #ccc; }
#ctrl-cpu-pill #cpu-pill-sep { color: #444; }
#ctrl-cpu-pill.cpu-low #cpu-pill-cpu { color: #00FF66; }
#ctrl-cpu-pill.cpu-mid #cpu-pill-cpu { color: #FFCC00; }
#ctrl-cpu-pill.cpu-high #cpu-pill-cpu { color: #FF3333; }
body.reading-mode #titlebar-controls #ctrl-cpu-pill {
  border-color: var(--iowa-border);
  color: var(--iowa-text-soft);
  font-family: inherit;
  /* !important needed: `body.reading-mode button` sets bg with !important.
     Per CLAUDE.md CSS lesson — specificity can't beat !important. */
  background: rgba(255, 255, 255, 0.04) !important;
}
body.reading-mode #ctrl-cpu-pill:hover {
  border-color: var(--iowa-accent);
  color: var(--iowa-accent);
}
body.reading-mode #ctrl-cpu-pill.cpu-low #cpu-pill-cpu { color: var(--iowa-sage); }
body.reading-mode #ctrl-cpu-pill.cpu-mid #cpu-pill-cpu { color: var(--iowa-gold); }
body.reading-mode #ctrl-cpu-pill.cpu-high #cpu-pill-cpu { color: var(--iowa-red); }

/* Expanded popover */
.cpu-popover {
  position: fixed;
  z-index: 10000;
  width: 260px;
  background: #121212;
  border: 1px solid #333;
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.6);
  padding: 10px;
  font-family: 'VT323', monospace;
  font-size: 13px;
  color: #ccc;
}
.cpu-pop-section {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.cpu-pop-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
  padding: 2px 0;
}
.cpu-pop-label {
  color: #777;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.3px;
}
.cpu-pop-val { color: #ddd; font-family: 'VT323', monospace; }
.cpu-pop-val.cpu-low { color: #00FF66; }
.cpu-pop-val.cpu-mid { color: #FFCC00; }
.cpu-pop-val.cpu-high { color: #FF3333; }
.cpu-pop-val.cpu-orphans { color: #FF8800; }
.cpu-pop-divider {
  border-top: 1px solid #222;
  margin: 8px 0;
}
.cpu-pop-action {
  width: 100%;
  padding: 6px 8px;
  background: transparent;
  border: 1px solid #442222;
  color: #FF6666;
  border-radius: 4px;
  cursor: pointer;
  font-family: 'VT323', monospace;
  font-size: 12px;
}
.cpu-pop-action:hover:not(:disabled) {
  border-color: #FF3333;
  background: rgba(255, 51, 51, 0.1);
}
.cpu-pop-action:disabled { opacity: 0.5; cursor: default; }

body.reading-mode .cpu-popover {
  background: var(--iowa-panel);
  border-color: var(--iowa-border);
  color: var(--iowa-text);
  font-family: inherit;
}
body.reading-mode .cpu-pop-label { color: var(--iowa-text-faint); }
body.reading-mode .cpu-pop-val { color: var(--iowa-text); font-family: inherit; }
body.reading-mode .cpu-pop-val.cpu-low { color: var(--iowa-sage); }
body.reading-mode .cpu-pop-val.cpu-mid { color: var(--iowa-gold); }
body.reading-mode .cpu-pop-val.cpu-high { color: var(--iowa-red); }
body.reading-mode .cpu-pop-val.cpu-orphans { color: var(--iowa-accent); }
body.reading-mode .cpu-pop-divider { border-top-color: var(--iowa-border); }
body.reading-mode .cpu-pop-action {
  border-color: var(--iowa-red);
  color: var(--iowa-red);
  background: transparent;
}
body.reading-mode .cpu-pop-action:hover:not(:disabled) {
  background: rgba(142, 74, 62, 0.1);
}

/* Default body chrome for any reading-mode preset. Individual presets
   below override palette vars + can override fonts if they depart from
   the iA Writer Quattro / Charter serif stack. */
body.reading-mode {
  background: var(--iowa-bg);
  color: var(--iowa-text);
  font-family: "iA Writer Quattro", "Charter", "Iowan Old Style", Georgia, serif;
  font-size: 17px;
  line-height: 1.65;
}

/* Panels / cards: swap dark backgrounds for warm cream, and switch from
   VT323 monospace to the prose serif stack. Higher-specificity selector
   wins over the existing dark theme rules. */
body.reading-mode #titlebar,
body.reading-mode #sidebar,
body.reading-mode #conv-header,
body.reading-mode #conversation-panel,
body.reading-mode #inbox-container {
  background: var(--iowa-bg);
  color: var(--iowa-text);
  border-color: var(--iowa-border-soft);
}

body.reading-mode .msg-bubble {
  background: var(--iowa-panel);
  border-color: var(--iowa-border-soft);
  box-shadow: none;
  color: var(--iowa-text);
  font-family: inherit;
}
body.reading-mode .msg-body {
  /* Prose-only width cap — buttons, inputs, tables inside escape via
     .msg-component, .msg-buttons, .msg-input-wrap rules below. */
  max-width: 72ch;
}
body.reading-mode .msg-card-title {
  color: var(--iowa-text);
  font-family: inherit;
  font-weight: 600;
}

/* Buttons: swap neon outlines for the palette's accent. */
body.reading-mode .msg-btn {
  background: transparent;
  border-color: var(--iowa-accent);
  color: var(--iowa-accent);
  font-family: inherit;
  box-shadow: none;
}
body.reading-mode .msg-btn:hover {
  background: var(--iowa-panel-alt);
  color: var(--iowa-accent-hover);
}
body.reading-mode .msg-btn.msg-btn-dismiss {
  color: var(--iowa-accent);
  border-color: var(--iowa-border);
}

/* Pin button + pinned card: gold concept carried across palettes via
   --iowa-gold. */
body.reading-mode .msg-btn.msg-btn-pin.pinned {
  background: var(--iowa-gold);
  border-color: var(--iowa-gold-dark);
  color: var(--iowa-bg);
  box-shadow: none;
}
body.reading-mode .msg-bubble.pinned {
  background: linear-gradient(90deg, rgba(200, 169, 106, 0.15), rgba(200, 169, 106, 0.03));
  border-left: 4px solid var(--iowa-gold);
  box-shadow: inset 0 0 0 1px rgba(200, 169, 106, 0.25);
}
body.reading-mode .msg-pin-badge {
  background: var(--iowa-gold);
  color: var(--iowa-bg);
  box-shadow: none;
}

/* Sidebar rows: palette-aware active border, no neon. */
body.reading-mode .steward-row {
  background: transparent;
  border-bottom-color: var(--iowa-border-soft);
  color: var(--iowa-text-soft);
  font-family: inherit;
}
body.reading-mode .steward-row:hover { background: var(--iowa-panel-alt); }
body.reading-mode .steward-row.active {
  background: var(--iowa-panel-alt);
  border-left-color: var(--iowa-accent);
}
body.reading-mode .steward-row.active .steward-name { color: var(--iowa-accent); }
body.reading-mode .steward-row .steward-name { color: var(--iowa-text); }
body.reading-mode .steward-row .steward-preview { color: var(--iowa-text-faint); }
body.reading-mode .steward-row.sub-row.active,
body.reading-mode .steward-row.sub-sub-row.active {
  border-left-color: var(--iowa-gold-dark);
}

/* Badges: palette-aware earth-tone equivalents. */
body.reading-mode .steward-badge.unresponded {
  background: var(--iowa-gold);
  color: var(--iowa-bg);
}
body.reading-mode .steward-unread-dot {
  background: var(--iowa-accent);
  box-shadow: 0 0 3px rgba(122, 92, 62, 0.4);
}

/* Kebab popover: palette panel with palette border. */
body.reading-mode .bulk-actions-popover {
  background: var(--iowa-panel);
  border-color: var(--iowa-border);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
}

/* Theme picker — palette-aware in reading mode */
body.reading-mode .theme-picker-popover {
  background: var(--iowa-panel);
  border-color: var(--iowa-border);
  box-shadow: 0 8px 24px rgba(0,0,0,0.25);
  font-family: inherit;
}
body.reading-mode .theme-picker-header {
  color: var(--iowa-text-soft);
  border-bottom-color: var(--iowa-border);
}
body.reading-mode .theme-tile {
  background: transparent;
  border-color: var(--iowa-border);
  color: var(--iowa-text);
}
body.reading-mode .theme-tile:hover {
  background: var(--iowa-panel-alt);
  border-color: var(--iowa-accent);
}
body.reading-mode .theme-tile.active {
  background: var(--iowa-panel-alt);
  border-color: var(--iowa-accent);
}
body.reading-mode .theme-tile-name { color: var(--iowa-text); }
body.reading-mode .theme-tile.active .theme-tile-name { color: var(--iowa-accent); }
body.reading-mode .theme-tile-sub { color: var(--iowa-text-faint); }
body.reading-mode .theme-tile-check { color: var(--iowa-accent); }
body.reading-mode .bulk-actions-header { color: var(--iowa-text-faint); border-bottom-color: var(--iowa-border-soft); }
body.reading-mode .bulk-actions-btn {
  background: transparent;
  border-color: var(--iowa-border);
  color: var(--iowa-text);
}
body.reading-mode .bulk-actions-btn:hover:not(:disabled) {
  background: var(--iowa-panel-alt);
  border-color: var(--iowa-accent);
  color: var(--iowa-accent);
}

/* Input fields: palette panel with palette border. */
body.reading-mode .msg-input {
  background: var(--iowa-panel);
  border-color: var(--iowa-border);
  color: var(--iowa-text);
  font-family: inherit;
}
body.reading-mode .msg-input:focus { border-color: var(--iowa-accent); }

/* Conv-steward-name: high-contrast palette foreground instead of the accent.
   The accent (warm orange in Gruvbox) sat too close in hue to the warm-brown
   header panel — it technically cleared AA but the colors collided and the
   name was barely legible. Using --iowa-text (cream in Gruvbox, near-black in
   iowa) clears a comfortable contrast in every preset while still reading as
   Gruvbox. Slight weight bump keeps it distinct from body text as a header. */
body.reading-mode #conv-steward-name {
  color: var(--iowa-text);
  font-weight: 600;
}

/* Status dots: keep the green (working) / orange (waiting) semantics but
   desaturate a notch so they don't shout. */
body.reading-mode .steward-status { opacity: 0.75; }

/* Custom components (shakedownReport etc.) escape the prose-width cap —
   they render at their natural component width. */
body.reading-mode .msg-component,
body.reading-mode .msg-buttons,
body.reading-mode .msg-input-wrap {
  max-width: none;
}


/* ============================================================
   Reading mode — Iowa, pass 2 (comprehensive re-skin)
   Joshua's feedback after pass 1: body text looks gray (beaten by
   .msg-body color:#ccc); send/recording buttons still neon lime;
   markdown styles (headings, links, code, lists) still video-game;
   bottom steward-chat input still dark; right-pane stewint still dark;
   every button everywhere needs the soft Iowa treatment.
   ============================================================ */

/* Palette vars — Iowa (default reading-mode palette). Rules below use
   --iowa-* names regardless of preset; each preset block remaps them
   to its own palette values. So adding a new preset = one more
   body.reading-mode[data-reading-preset="X"] block, no rule rewrites. */
body.reading-mode[data-reading-preset="iowa"] {
  --iowa-bg:          #F4EFE6;   /* page */
  --iowa-panel:       #FAF5EC;   /* cards, popovers */
  --iowa-panel-alt:   #EFE4CD;   /* active sidebar row, focus */
  --iowa-active-bg:   #EFE4CD;   /* active sidebar row bg (tunable per preset) */
  --iowa-border:      #D0C4AE;   /* subtle divider */
  --iowa-border-soft: #E0D7C4;   /* very subtle divider */
  --iowa-text:        #1A1815;   /* body text */
  --iowa-text-soft:   #5A4430;   /* secondary/meta */
  --iowa-text-faint:  #7A6B55;   /* tertiary */
  --iowa-accent:      #7A5C3E;   /* warm tan (primary) */
  --iowa-accent-hover:#5A4430;   /* darker tan (hover) */
  --iowa-gold:        #C8A96A;   /* muted gold (pin, badges) */
  --iowa-gold-dark:   #A88A4E;   /* muted gold hover */
  --iowa-sage:        #6B8E4E;   /* muted lime replacement (recording) */
  --iowa-sage-dark:   #4F6E36;   /* sage hover */
  --iowa-slate:       #4A6B8E;   /* muted blue replacement (links) */
  --iowa-red:         #8E4A3E;   /* muted red-brown (dismiss hover, errors) */
  /* Recording indicator — sage + warm tan glow fit the parchment palette. */
  --recording-indicator-color: var(--iowa-sage);
  --recording-indicator-glow:  rgba(107, 142, 78, 0.22);
}

/* Palette vars — Gruvbox Dark Medium. The iconic canonical Gruvbox look:
   warm-dark bg #282828, warm cream text #EBDBB2, iconic orange accent
   #FE8019. Joshua said: dark-but-not-crazy-black. Same iA-Writer serif
   stack so typography feels consistent across presets. */
body.reading-mode[data-reading-preset="gruvbox"] {
  --iowa-bg:          #282828;   /* gruv dark bg (iconic) */
  --iowa-panel:       #3C3836;   /* cards */
  --iowa-panel-alt:   #504945;   /* active row / hover */
  --iowa-active-bg:   #32302F;   /* subtle active highlight — just a notch above bg */
  --iowa-border:      #504945;
  --iowa-border-soft: #3C3836;
  --iowa-text:        #EBDBB2;   /* warm cream fg (iconic) */
  --iowa-text-soft:   #BDAE93;   /* dim */
  --iowa-text-faint:  #A89984;
  --iowa-accent:      #FE8019;   /* iconic warm orange */
  --iowa-accent-hover:#D65D0E;
  --iowa-gold:        #FABD2F;
  --iowa-gold-dark:   #D79921;
  --iowa-sage:        #B8BB26;   /* warm green */
  --iowa-sage-dark:   #98971A;
  --iowa-slate:       #83A598;   /* warm blue */
  --iowa-red:         #FB4934;
  --recording-indicator-color: #B8BB26;
  --recording-indicator-glow:  rgba(184, 187, 38, 0.22);
}

/* Gruvbox Hard — the very-dark variant. bg #1D2021 (near-black),
   brighter cream #FBF1C7 for contrast. Same orange + accents otherwise. */
body.reading-mode[data-reading-preset="gruvbox-hard"] {
  --iowa-bg:          #1D2021;   /* near-black */
  --iowa-panel:       #282828;
  --iowa-panel-alt:   #3C3836;
  --iowa-active-bg:   #282828;   /* subtle active highlight — just a notch above bg */
  --iowa-border:      #3C3836;
  --iowa-border-soft: #282828;
  --iowa-text:        #FBF1C7;   /* brighter cream for contrast */
  --iowa-text-soft:   #EBDBB2;
  --iowa-text-faint:  #BDAE93;
  --iowa-accent:      #FE8019;
  --iowa-accent-hover:#D65D0E;
  --iowa-gold:        #FABD2F;
  --iowa-gold-dark:   #D79921;
  --iowa-sage:        #B8BB26;
  --iowa-sage-dark:   #98971A;
  --iowa-slate:       #83A598;
  --iowa-red:         #FB4934;
  --recording-indicator-color: #B8BB26;
  --recording-indicator-glow:  rgba(184, 187, 38, 0.22);
}

/* === BODY TEXT — darker override (beats `.msg-bubble .msg-body {color:#ccc}`) === */
body.reading-mode .msg-bubble .msg-body,
body.reading-mode .msg-bubble .msg-body p,
body.reading-mode .msg-bubble .msg-body li,
body.reading-mode .msg-bubble .msg-body pre code {
  color: var(--iowa-text);
  font-family: inherit;
}
body.reading-mode .msg-bubble .msg-body h1,
body.reading-mode .msg-bubble .msg-body h2,
body.reading-mode .msg-bubble .msg-body h3 {
  color: var(--iowa-text);
  font-family: inherit;
  font-weight: 700;
}
body.reading-mode .msg-bubble .msg-body strong { color: var(--iowa-text); }
body.reading-mode .msg-bubble .msg-body code {
  background: var(--iowa-panel-alt);
  color: var(--iowa-accent);
  border: 1px solid var(--iowa-border-soft);
  font-family: "Menlo", "Monaco", "Courier New", monospace;
}
body.reading-mode .msg-bubble .msg-body pre {
  background: var(--iowa-panel-alt);
  border-color: var(--iowa-border);
}
body.reading-mode .msg-bubble .msg-body pre code {
  background: none;
  border: none;
  color: var(--iowa-text);
}
body.reading-mode .msg-bubble .msg-body a {
  color: var(--iowa-slate);
  text-decoration: underline;
}
body.reading-mode .msg-bubble .msg-body a:hover { color: var(--iowa-accent); }
body.reading-mode .msg-bubble .msg-body ul li::before {
  color: var(--iowa-accent);
  font-family: inherit;
}
body.reading-mode .msg-bubble .msg-body blockquote {
  border-left-color: var(--iowa-accent);
  color: var(--iowa-text-soft);
}
body.reading-mode .msg-bubble .msg-body table th {
  border-bottom-color: var(--iowa-border);
  color: var(--iowa-accent);
}
body.reading-mode .msg-bubble.archived .msg-body,
body.reading-mode .msg-bubble.archived .msg-body h1,
body.reading-mode .msg-bubble.archived .msg-body h2,
body.reading-mode .msg-bubble.archived .msg-body h3 {
  color: var(--iowa-text-soft);
}

/* === RUN-BTN (lime green) — most jarring per feedback === */
body.reading-mode .msg-btn.run-btn {
  background: rgba(107, 142, 78, 0.1);
  border-color: var(--iowa-sage);
  color: var(--iowa-sage);
}
body.reading-mode .msg-btn.run-btn:hover {
  background: rgba(107, 142, 78, 0.2);
  color: var(--iowa-sage-dark);
  border-color: var(--iowa-sage-dark);
}

/* === SEND BUTTON (card-level + bottom) — neon orange → warm tan === */
body.reading-mode .msg-send-btn,
body.reading-mode #conv-bottom-send {
  background: var(--iowa-accent) !important;
  color: var(--iowa-bg) !important;
  border: none;
  box-shadow: none;
}
body.reading-mode .msg-send-btn:hover,
body.reading-mode #conv-bottom-send:hover {
  background: var(--iowa-accent-hover) !important;
}
/* Recording-pulse state: muted sage instead of neon lime */
body.reading-mode .msg-send-btn.has-recording,
body.reading-mode #conv-bottom-send.has-recording {
  background: var(--iowa-sage) !important;
  color: var(--iowa-bg) !important;
}
body.reading-mode .msg-send-btn.has-recording:hover,
body.reading-mode #conv-bottom-send.has-recording:hover {
  background: var(--iowa-sage-dark) !important;
}

/* === BOTTOM STEWARD-CHAT INPUT ROW === */
body.reading-mode #conv-bottom-input {
  background: var(--iowa-panel);
  border-top: 1px solid var(--iowa-border);
}
body.reading-mode #conv-bottom-textarea {
  background: var(--iowa-panel);
  color: var(--iowa-text);
  border: 1px solid var(--iowa-border);
  font-family: inherit;
  font-size: 16px;
}
body.reading-mode #conv-bottom-textarea:focus {
  border-color: var(--iowa-accent);
  outline: none;
}
body.reading-mode #conv-bottom-textarea::placeholder {
  color: var(--iowa-text-faint);
}

/* === TTS + DISMISS PILLS (top-right of card) === */
body.reading-mode .btn-tts,
body.reading-mode .btn-dismiss {
  background: transparent;
  border-color: var(--iowa-border);
  color: var(--iowa-text-soft);
}
body.reading-mode .btn-tts:hover {
  color: var(--iowa-accent);
  border-color: var(--iowa-accent);
  background: rgba(122, 92, 62, 0.08);
}
body.reading-mode .btn-tts.playing {
  color: var(--iowa-sage);
  border-color: var(--iowa-sage);
}
body.reading-mode .btn-dismiss:hover {
  color: var(--iowa-red);
  border-color: var(--iowa-red);
  background: rgba(142, 74, 62, 0.08);
}

/* === TOOLBAR BUTTONS (queue / activity / pause in bottom toolbar) === */
body.reading-mode .toolbar-btn {
  background: var(--iowa-panel);
  border-color: var(--iowa-border);
  color: var(--iowa-text);
  font-family: inherit;
}
body.reading-mode .toolbar-btn:hover {
  background: var(--iowa-panel-alt);
  color: var(--iowa-accent);
}
body.reading-mode .toolbar-btn.has-pending {
  color: var(--iowa-gold);
  border-color: var(--iowa-gold);
}
body.reading-mode .toolbar-btn.toolbar-pause {
  color: var(--iowa-accent);
  border-color: var(--iowa-accent);
}

/* === TITLEBAR === */
body.reading-mode #titlebar {
  background: var(--iowa-panel);
  border-bottom: 1px solid var(--iowa-border);
}
body.reading-mode #titlebar-text { color: var(--iowa-accent); }
body.reading-mode #queue-count { color: var(--iowa-gold); }
body.reading-mode #btn-hood {
  color: var(--iowa-slate);
}
body.reading-mode #btn-hood:hover { color: var(--iowa-accent); }
body.reading-mode #titlebar-controls input[type="range"] { accent-color: var(--iowa-accent); }
body.reading-mode #titlebar-controls .ctrl-btn {
  background: transparent;
  border-color: var(--iowa-border);
  color: var(--iowa-text-soft);
}
body.reading-mode #titlebar-controls .ctrl-btn:hover {
  color: var(--iowa-accent);
  border-color: var(--iowa-accent);
}
body.reading-mode #titlebar-controls .ctrl-val { color: var(--iowa-text); }
body.reading-mode #btn-minimize { color: var(--iowa-text-soft); }
body.reading-mode #btn-minimize:hover { color: var(--iowa-text); background: var(--iowa-panel-alt); }

/* === SIDEBAR === */
body.reading-mode #sidebar {
  background: var(--iowa-panel);
  border-right-color: var(--iowa-border);
}
body.reading-mode #sidebar-header {
  color: var(--iowa-accent);
  border-bottom-color: var(--iowa-border);
}
body.reading-mode #sidebar-drag-handle { background: var(--iowa-border-soft); }
body.reading-mode #sidebar-drag-handle:hover { background: var(--iowa-accent); }

/* === CONVERSATION HEADER === */
body.reading-mode #conv-header {
  background: var(--iowa-panel);
  border-bottom: 1px solid var(--iowa-border);
}
body.reading-mode #sidebar-toggle {
  background: transparent;
  border-color: var(--iowa-border);
  color: var(--iowa-text-soft);
}
body.reading-mode #sidebar-toggle:hover { color: var(--iowa-accent); border-color: var(--iowa-accent); }

/* === AVATAR BARS (identity pills at top of card) === */
body.reading-mode .msg-avatar-bar.steward {
  background: rgba(107, 142, 78, 0.15);
  border-color: var(--iowa-sage);
}
body.reading-mode .msg-avatar-bar.user {
  background: rgba(122, 92, 62, 0.15);
  border-color: var(--iowa-accent);
}
body.reading-mode .msg-avatar-bar .avatar-name { color: var(--iowa-text); }

/* === MSG BUBBLE BORDERS (active/unread states) === */
body.reading-mode .msg-bubble.active { border-color: var(--iowa-sage); }
body.reading-mode .msg-bubble.unread { border-color: var(--iowa-gold); }
body.reading-mode .msg-bubble .msg-title { color: var(--iowa-accent); font-family: inherit; }

/* === CATEGORY BADGES === */
body.reading-mode .msg-bubble .msg-category.situation {
  background: rgba(200, 169, 106, 0.2);
  color: var(--iowa-gold-dark);
}
body.reading-mode .msg-bubble .msg-category.build {
  background: rgba(74, 107, 142, 0.2);
  color: var(--iowa-slate);
}

/* === LINK-ACTION BUTTONS (copy/open inside markdown links) === */
body.reading-mode .link-action-btn {
  color: var(--iowa-text-soft);
  border-color: var(--iowa-border);
  background: transparent;
}
body.reading-mode .link-action-btn:hover {
  color: var(--iowa-accent);
  border-color: var(--iowa-accent);
}

/* === STEWINT RIGHT PANEL (docs viewer) ===
   Even though it's an iframe, the visible chrome around it + any native
   non-iframe rendering (stewint-content) needs the Iowa treatment. */
body.reading-mode #stewint-panel {
  background: var(--iowa-panel);
}
body.reading-mode #stewint-header {
  background: var(--iowa-panel);
  border-bottom: 1px solid var(--iowa-border);
}
body.reading-mode #stewint-iframe {
  background: var(--iowa-bg);
}
body.reading-mode .stewint-tab {
  color: var(--iowa-text-soft);
  background: transparent;
  font-family: inherit;
}
body.reading-mode .stewint-tab:hover { color: var(--iowa-accent) !important; }
body.reading-mode .stewint-tab.active {
  color: var(--iowa-accent);
  border-bottom-color: var(--iowa-accent);
}
body.reading-mode #stewint-toggle {
  color: var(--iowa-text-soft);
  border-color: var(--iowa-border);
}
body.reading-mode #stewint-toggle:hover {
  color: var(--iowa-text) !important;
  border-color: var(--iowa-accent) !important;
}
body.reading-mode #stewint-content {
  color: var(--iowa-text);
  font-family: inherit;
}
body.reading-mode #stewint-content .si-section-title {
  color: var(--iowa-accent);
}

/* === EMBEDDED TOPBAR (mobile horizontal steward strip) === */
body.reading-mode #embedded-topbar {
  background: var(--iowa-panel);
  border-bottom: 1px solid var(--iowa-border);
}
/* iter8: the subbar keeps its "ledge" gradient in reading-mode too — cream
   panel below the ledge line (y=16), transparent+borderless above so the time
   badge hangs over the div above. */
body.reading-mode #embedded-subbar {
  background:
    linear-gradient(
      to bottom,
      transparent 0,
      transparent 15px,
      var(--iowa-border) 15px,
      var(--iowa-border) 16px,
      var(--iowa-panel) 16px,
      var(--iowa-panel) 100%
    );
}
/* Worker BUTTON must stay legible when the subbar background flips to the Iowa
   panel (light cream in the default reading-mode variant, dark grey in
   gruvbox/gruv-hard). Drive text AND the button chrome off the Iowa tokens the
   rest of reading-mode uses — never the dark-chrome hardcoded greys. */
body.reading-mode .subbar-icon.has-label {
  background: var(--iowa-panel-alt, rgba(0, 0, 0, 0.04));
  border-color: var(--iowa-border);
}
body.reading-mode .subbar-icon.has-label.active {
  border-bottom-color: var(--iowa-accent);
  background: var(--iowa-panel-alt);
}
body.reading-mode .subbar-icon.has-label .subbar-name { color: var(--iowa-text); }
/* Selected worker chip: its background is the bright accent (orange in Gruvbox),
   so the name MUST be the dark palette background color, not the accent — accent
   text on an accent background was orange-on-orange and effectively invisible.
   --iowa-bg is dark in the Gruvbox presets (and light on the light iowa preset,
   where the accent is a dark tan), so this reads clearly in every preset. */
body.reading-mode .subbar-icon.has-label.active .subbar-name {
  color: var(--iowa-bg);
  font-weight: 600;
}
/* iter4 reading-mode: emoji chip on the cream palette. */
body.reading-mode .subbar-icon.has-label .subbar-emoji-chip {
  background: var(--iowa-panel);
  border-color: var(--iowa-border);
  box-shadow: inset 0 1px 1px rgba(255, 255, 255, 0.4), 0 1px 2px rgba(0, 0, 0, 0.12);
}
body.reading-mode .subbar-icon.has-label.active .subbar-emoji-chip {
  background: color-mix(in srgb, var(--iowa-accent) 18%, var(--iowa-panel));
  border-color: color-mix(in srgb, var(--iowa-accent) 45%, transparent);
}
/* Combined status+time badge — a solid dark pill reads as a real badge on cream. */
body.reading-mode .subbar-icon.has-label .subbar-timebadge {
  color: var(--iowa-bg, #fff);
  background: var(--iowa-text, #333);
  border-color: color-mix(in srgb, var(--iowa-text) 30%, transparent);
}
/* "⋯" more-button on cream. */
body.reading-mode .subbar-icon.has-label .subbar-name-more {
  color: var(--iowa-text);
  background: var(--iowa-panel-alt, rgba(0, 0, 0, 0.06));
  border-color: var(--iowa-border);
}
/* Unknown-grey dot fill nudged so it stays visible on cream. */
body.reading-mode .subbar-icon.has-label .subbar-timebadge.unknown .subbar-timebadge-dot {
  background: color-mix(in srgb, var(--iowa-text) 40%, var(--iowa-panel));
}
/* Josh 2026-08-12: raised-button strip + orange pressed-IN selected state, in
   the reading-mode palettes (gruvbox is what Josh uses). Base = raised button;
   .active = orange fill, sunk in. Ring untouched. */
body.reading-mode .topbar-icon {
  border: 1px solid var(--iowa-border);
  background: linear-gradient(color-mix(in srgb, var(--iowa-panel-alt) 82%, #fff 0%), var(--iowa-panel));
  box-shadow: 0 2px 0 rgba(0, 0, 0, 0.45), 0 3px 4px rgba(0, 0, 0, 0.35);
}
body.reading-mode .topbar-icon.active {
  background: linear-gradient(var(--iowa-accent), var(--iowa-accent-hover)) !important;
  border-color: var(--iowa-accent-hover) !important;
  transform: translateY(2px);
  box-shadow:
    inset 0 4px 7px rgba(0, 0, 0, 0.55),
    inset 0 1px 0 rgba(0, 0, 0, 0.35),
    inset 0 -2px 2px rgba(255, 255, 255, 0.10),
    0 0 0 1px var(--iowa-accent-hover) !important;
}
body.reading-mode .topbar-icon.active .topbar-badge { background: var(--iowa-bg); color: var(--iowa-gold); border-color: var(--iowa-accent-hover); }
/* Josh 2026-08-27: SECOND click ('own' level) = the WHOLE tile goes solid BLUE
   so Josh sees he's drilled into just this steward's own cards. In reading-mode
   (gruvbox — the theme Josh actually uses) the .active rule above forces the
   orange accent with !important AND there was no .level-own variant here, so the
   blue never showed — the tile stayed orange. This override (higher specificity,
   !important to beat the .active rule) paints the full blue plate in-theme. */
body.reading-mode .topbar-icon.active.level-own {
  background: linear-gradient(#1f8fbf, #146a90) !important;
  border-color: #0f4f6b !important;
  box-shadow:
    inset 0 4px 7px rgba(0, 0, 0, 0.55),
    inset 0 0 0 2px rgba(180, 230, 255, 0.55),
    inset 0 1px 0 rgba(0, 0, 0, 0.35),
    0 0 0 1px #0b3f57 !important;
}
body.reading-mode .topbar-icon.active.level-own .topbar-badge { background: #05202b; color: #8fe4ff; border-color: #0f4f6b; }
/* Same treatment for the worker sub-row. */
body.reading-mode .subbar-icon {
  border: 1px solid var(--iowa-border);
  background: linear-gradient(var(--iowa-panel-alt), var(--iowa-panel));
  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.45), 0 2px 3px rgba(0, 0, 0, 0.35);
}
body.reading-mode .subbar-icon.active {
  background: linear-gradient(var(--iowa-accent), var(--iowa-accent-hover)) !important;
  border-color: var(--iowa-accent-hover) !important;
  box-shadow:
    inset 0 3px 6px rgba(0, 0, 0, 0.55),
    inset 0 1px 0 rgba(0, 0, 0, 0.35),
    0 0 0 1px var(--iowa-accent-hover) !important;
}
body.reading-mode .topbar-unread-dot { background: var(--iowa-accent); }
/* Steward count-badge on the gold pill. Josh 2026-08-07: in Gruvbox the fg was
   --iowa-text (warm CREAM #EBDBB2) on bright gold (#FABD2F) — a cream number on
   yellow, near-illegible. Gold is a light/warm color in every preset, so the
   number must be DARK. Use --iowa-bg (the preset's dark page color in gruvbox,
   near-black in gruvbox-hard) for a high-contrast dark-on-gold read. */
body.reading-mode .topbar-badge {
  background: var(--iowa-gold);
  color: var(--iowa-bg);
  font-weight: bold;
}
/* R4: Bookmarks + top Links pill reading-mode rules removed with the elements.
   The card-links dropdown reading-mode repaint stays — it's still opened from
   the per-card footer Links button. */
body.reading-mode #card-links-dropdown {
  background: var(--iowa-panel);
  border-color: var(--iowa-border);
  font-family: inherit;
}
body.reading-mode .card-link-btn,
body.reading-mode .card-link-action {
  color: var(--iowa-text);
  font-family: inherit;
}
body.reading-mode .card-link-btn:hover,
body.reading-mode .card-link-action:hover {
  color: var(--iowa-accent);
  border-color: var(--iowa-accent);
}
body.reading-mode .card-link-title { color: var(--iowa-text); }
body.reading-mode .card-links-header { border-bottom-color: var(--iowa-border); }
body.reading-mode .card-links-header .cl-h-title { color: var(--iowa-text); }
body.reading-mode .card-links-header .cl-h-count { color: var(--iowa-accent); }
body.reading-mode .card-link-row + .card-link-row { border-top-color: var(--iowa-border); }
/* The recency dot + time chips carry their color inline (the recency scale is
   palette-independent — green=fresh reads on cream or dark), so reading-mode
   needs no chip overrides. Keep the muted "never" chip legible on cream. */
body.reading-mode .card-link-chip.is-muted {
  background: var(--iowa-panel-alt, rgba(0,0,0,0.06)) !important;
  color: var(--iowa-text-soft) !important;
  border-color: var(--iowa-border) !important;
}
body.reading-mode .card-links-empty {
  color: var(--iowa-text-soft);
  font-family: inherit;
}

/* === CONV-EMPTY / SCROLL-TO-BOTTOM === */
body.reading-mode #conv-empty .empty-text { color: var(--iowa-text-soft); }
body.reading-mode #scroll-to-bottom {
  background: var(--iowa-accent);
  color: var(--iowa-bg);
  border: none;
  box-shadow: 0 2px 8px rgba(42, 39, 36, 0.15);
}
body.reading-mode #scroll-to-bottom:hover { background: var(--iowa-accent-hover); }

/* === HISTORY VIEWER PANEL — theme-adaptive ===
   Mirrors the queue-viewer reading-mode block above. Default styles use a
   cyan-on-black palette; under reading-mode (Iowa, Gruvbox, Gruvbox-Hard)
   the panel repaints to the active --iowa-* tokens so it matches the rest
   of the presenter UI. The font-family catch-all at the bottom of the
   reading-mode block (body.reading-mode *:not(code):not(pre):not(kbd))
   handles the VT323 holdouts on hv-* selectors automatically. */
body.reading-mode #history-viewer-panel {
  background: var(--iowa-panel);
  border-color: var(--iowa-border);
  color: var(--iowa-text);
}
body.reading-mode #hv-header {
  background: var(--iowa-panel);
  border-bottom: 1px solid var(--iowa-border);
}
body.reading-mode #hv-title { color: var(--iowa-accent); }
body.reading-mode #hv-search {
  background: var(--iowa-bg);
  border: 1px solid var(--iowa-border);
  color: var(--iowa-text);
}
body.reading-mode #hv-search:focus { border-color: var(--iowa-accent); }
body.reading-mode #hv-search::placeholder { color: var(--iowa-text-faint); }
body.reading-mode #hv-close { color: var(--iowa-text-faint); }
body.reading-mode #hv-close:hover { color: var(--iowa-red); }
body.reading-mode #hv-subheader {
  background: var(--iowa-panel);
  border-bottom: 1px solid var(--iowa-border);
}
body.reading-mode #hv-steward-filter {
  background: var(--iowa-bg);
  border: 1px solid var(--iowa-border);
  color: var(--iowa-text);
}
body.reading-mode #hv-count { color: var(--iowa-text-faint); }
body.reading-mode .hv-item { border-bottom: 1px solid var(--iowa-border); }
body.reading-mode .hv-item:hover { background: var(--pill-half-hover-bg); }
body.reading-mode .hv-item.expanded { background: var(--pill-half-active-bg); }
body.reading-mode .hv-steward { color: var(--iowa-accent); }
body.reading-mode .hv-when { color: var(--iowa-text-faint); }
body.reading-mode .hv-state.responded {
  background: rgba(102, 124, 71, 0.18);
  color: var(--iowa-sage, var(--iowa-accent));
}
body.reading-mode .hv-state.dismissed {
  background: rgba(142, 74, 62, 0.18);
  color: var(--iowa-red);
}
body.reading-mode .hv-title { color: var(--iowa-text); }
body.reading-mode .hv-snippet { color: var(--iowa-text-soft); }
body.reading-mode .hv-detail {
  background: var(--iowa-bg);
  border-top: 1px dashed var(--iowa-border);
  color: var(--iowa-text);
}
body.reading-mode .hv-detail .hv-detail-label { color: var(--iowa-accent); }
body.reading-mode .hv-detail .hv-feedback {
  background: rgba(102, 124, 71, 0.10);
  border-left: 2px solid var(--iowa-sage, var(--iowa-accent));
}
body.reading-mode .hv-empty,
body.reading-mode .hv-loading { color: var(--iowa-text-faint); }
body.reading-mode .hv-mark {
  background: rgba(217, 165, 56, 0.28);
  color: var(--iowa-gold-dark, var(--iowa-accent));
}
/* === ERROR TOAST — theme-adaptive (2026-04-20 per Joshua) ===
   Was previously hardcoded red in reading-mode but still used VT323
   font; button styling and inline fonts didn't follow the theme. Now
   the whole toast — container, retry btn, dismiss btn — draws from
   iowa-* palette vars so it matches whichever preset is active. */
body.reading-mode #error-toast {
  background: rgba(142, 74, 62, 0.12);
  border-color: var(--iowa-red);
  color: var(--iowa-red);
  font-family: 'Iowa Old Style', Georgia, serif;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
body.reading-mode .error-toast-retry {
  background: var(--iowa-red);
  color: var(--iowa-bg);
  font-family: 'Iowa Old Style', Georgia, serif;
}
body.reading-mode .error-toast-retry:hover {
  background: var(--iowa-accent-hover);
}
body.reading-mode .error-toast-dismiss {
  color: var(--iowa-red);
}
body.reading-mode .error-toast-dismiss:hover {
  color: var(--iowa-text);
}

/* === UNDO TOAST (bottom-center send-countdown) === */
body.reading-mode #undo-toast {
  background: var(--iowa-panel);
  color: var(--iowa-text);
  border-color: var(--iowa-border);
}

/* === TEXTAREAS INSIDE CARDS (msg-input) === */
body.reading-mode .msg-input {
  background: var(--iowa-panel);
  color: var(--iowa-text);
  border-color: var(--iowa-border);
  font-family: inherit;
}
body.reading-mode .msg-input:focus { border-color: var(--iowa-accent); }
body.reading-mode .msg-input::placeholder { color: var(--iowa-text-faint); }

/* Layout-toggle chip in reading mode — pick up palette tokens. */
body.reading-mode .msg-layout-toggle {
  background: var(--iowa-panel);
  color: var(--iowa-text-faint);
  border-color: var(--iowa-border);
}
body.reading-mode .msg-layout-toggle:hover {
  color: var(--iowa-accent);
  border-color: var(--iowa-accent);
  background: var(--iowa-panel);
}

/* === STATUS DOTS keep semantics but muted === */
body.reading-mode .steward-status { opacity: 0.75; }
body.reading-mode .steward-sub-activity { border-color: var(--iowa-gold); }

/* === FALLBACK: catch anything left behind with VT323 === */
body.reading-mode button,
body.reading-mode input,
body.reading-mode textarea,
body.reading-mode select {
  font-family: inherit;
}

/* ============================================================
   Reading mode — Iowa, pass 3 (hunt down remaining holdouts)
   Feedback after pass 2:
     A. Buttons still in VT323 font + green recording state leaked
        through on some surfaces (pending items, feedback rows).
     B. Scroll-to-bottom button center-screen on desktop is in the way.
     C. Stewint iframe markdown still video-game font (handled in JS
        via same-origin style injection).
     D. Area between cards and bottom input has buttons still in the
        old style.
   ============================================================ */

/* Font catch-all: anything not yet overridden. Specificity boost via
   compound [data-reading-preset] attribute + element selector. */
body.reading-mode *:not(code):not(pre):not(kbd) {
  font-family: inherit;
}
/* Except — actual monospace-required surfaces (inline code, pre blocks)
   should stay a monospace stack, but a readable one. */
body.reading-mode code,
body.reading-mode pre,
body.reading-mode pre code {
  font-family: "Menlo", "Monaco", "Courier New", monospace;
}

/* === Item A — green recording holdouts === */

/* .msg-feedback is the archived "Joshua clicked X" row. Green → sage. */
body.reading-mode .msg-feedback {
  border-left-color: var(--iowa-sage);
  background: rgba(107, 142, 78, 0.08);
}
body.reading-mode .msg-feedback .fb-button {
  color: var(--iowa-sage-dark);
}
body.reading-mode .msg-feedback .fb-text {
  color: var(--iowa-text);
}
body.reading-mode .msg-feedback.dismissed {
  border-left-color: var(--iowa-border);
}
body.reading-mode .msg-feedback.dismissed .fb-button {
  color: var(--iowa-text-faint);
}

/* .pending-item — queue-viewer + bottom-toolbar pending rows. Green → sage,
   orange → warm tan. */
body.reading-mode .pending-item {
  background: var(--iowa-panel);
  color: var(--iowa-text);
}
body.reading-mode .pending-item.from-josh {
  border-left-color: var(--iowa-accent);
}
body.reading-mode .pending-item.from-steward {
  border-left-color: var(--iowa-sage);
}
body.reading-mode .pending-item.from-steward .pending-direction .pending-from {
  color: var(--iowa-sage-dark);
}
body.reading-mode .pending-item.status-pending .pending-direction .pending-status-icon {
  color: var(--iowa-gold);
}
body.reading-mode .pending-item.status-confirmed {
  opacity: 0.6;
  border-left-color: var(--iowa-border);
}
body.reading-mode .pending-item.status-confirmed .pending-direction .pending-status-icon {
  color: var(--iowa-sage-dark);
}

/* Pause-agent button (sits inside bottom-toolbar) — was gold neon */
body.reading-mode .pause-agent-btn {
  background: rgba(200, 169, 106, 0.1);
  border-color: var(--iowa-gold);
  color: var(--iowa-gold-dark);
}
body.reading-mode .pause-agent-btn:hover {
  background: rgba(200, 169, 106, 0.2);
  border-color: var(--iowa-gold-dark);
}

/* Show-more / load-older button */
body.reading-mode .show-more-btn {
  color: var(--iowa-accent);
  border-color: var(--iowa-border);
}
body.reading-mode .show-more-btn:hover {
  border-color: var(--iowa-accent);
}

/* === Item B — Scroll-to-bottom button: desktop right-align, mobile center === */

/* Default reading-mode skin — softer warm tan instead of orange */
body.reading-mode #scroll-to-bottom {
  background: rgba(122, 92, 62, 0.5);
  border-color: rgba(122, 92, 62, 0.6);
  color: var(--iowa-bg);
}
body.reading-mode #scroll-to-bottom:hover {
  background: rgba(122, 92, 62, 0.8);
}
body.reading-mode #scroll-to-bottom:active {
  background: var(--iowa-accent);
}

/* Desktop: move scroll-to-bottom to bottom-right, out of the way.
   Applies regardless of reading-mode — Joshua wanted this in general. */
@media (min-width: 768px) {
  body:not(.embedded) #scroll-to-bottom {
    top: auto;
    left: auto;
    right: 20px;
    bottom: 20px;
    transform: none;
  }
}

/* === Item D — between cards and bottom input (bottom-toolbar area) ===
   Toolbar bg wasn't explicitly re-skinned in pass 2; do it now, plus
   belt-and-suspenders on the pending-from color classes. */
body.reading-mode #bottom-toolbar {
  background: var(--iowa-panel);
  border-top: 1px solid var(--iowa-border);
}
body.reading-mode .toolbar-panel {
  border-top-color: var(--iowa-border);
  background: var(--iowa-panel);
}
/* Ensure all buttons inside bottom-toolbar match Iowa treatment,
   even if they don't have .toolbar-btn class. */
body.reading-mode #bottom-toolbar button {
  font-family: inherit;
  color: var(--iowa-text);
}
body.reading-mode #bottom-toolbar button:hover {
  color: var(--iowa-accent);
}


/* ============================================================
   Reading mode — Iowa, pass 4 (diagnostic-driven, !important)
   After 3 rounds of Joshua spotting cartoonish holdouts, I finally
   drove the presenter in Chrome DevTools and proved specifically:

     WINNING selector for .msg-btn font-family was:
       "#inbox-container button, .msg-bubble button"
     — specificity (0,1,0,1) via ID, which BEAT my
       "body.reading-mode[data-reading-preset='iowa'] .msg-btn"
     — specificity (0,0,2,1).

   The ID-selector base rules cannot be out-specified by my
   attribute-chain selector. The only sane fix is !important on
   every reading-mode override that competes with an ID rule.

   Also found: #stewint-iframe has inline style="background:#111" —
   inline beats CSS. Fixed with !important + removed inline in HTML.
   ============================================================ */

/* NUCLEAR: in reading mode, every button/input everywhere adopts Iowa.
   !important necessary because many base rules use #inbox-container or
   .msg-bubble ID/ancestor combos that win specificity. */
body.reading-mode #inbox-container button,
body.reading-mode .msg-bubble button,
body.reading-mode button,
body.reading-mode .msg-btn {
  background: var(--iowa-panel) !important;
  color: var(--iowa-text) !important;
  border: 1px solid var(--iowa-border) !important;
  font-family: inherit !important;
  box-shadow: none !important;
}
body.reading-mode #inbox-container button:hover,
body.reading-mode .msg-bubble button:hover,
body.reading-mode .msg-btn:hover {
  background: rgba(122, 92, 62, 0.1) !important;
  color: var(--iowa-accent) !important;
  border-color: var(--iowa-accent) !important;
  transform: none !important;
}

/* Primary (first-child) button — solid warm tan fill instead of neon */
body.reading-mode .msg-btn:first-child {
  background: var(--iowa-accent) !important;
  color: var(--iowa-bg) !important;
  border-color: var(--iowa-accent) !important;
  font-weight: 700 !important;
}
body.reading-mode .msg-btn:first-child:hover {
  background: var(--iowa-accent-hover) !important;
  color: var(--iowa-bg) !important;
  border-color: var(--iowa-accent-hover) !important;
}

/* Run-btn (was lime green) */
body.reading-mode .msg-btn.run-btn {
  background: rgba(107, 142, 78, 0.12) !important;
  color: var(--iowa-sage) !important;
  border-color: var(--iowa-sage) !important;
}
body.reading-mode .msg-btn.run-btn:hover {
  background: rgba(107, 142, 78, 0.22) !important;
  color: var(--iowa-sage-dark) !important;
  border-color: var(--iowa-sage-dark) !important;
}

/* Inline undo button — reading-mode armor.
   The "NUCLEAR" rule above flattens every .msg-bubble button to the
   panel/border palette, which would silently regress the prominent
   undo back to a muted gray pill. This override restores the gold
   fill in iowa tokens so the button reads as prominent in BOTH
   dark and reading mode. Selector intentionally matches the
   NUCLEAR rule's specificity (0,3,1 > 0,2,2) so source-order +
   specificity both favor this rule and the regression-armor on
   .undo-link can't be silently undone again. Joshua-flagged
   regression 2026-05-30 — any change here requires Joshua signoff. */
body.reading-mode .msg-bubble button.undo-link,
body.reading-mode #inbox-container button.undo-link,
body.reading-mode button.undo-link {
  background: var(--iowa-accent) !important;
  color: var(--iowa-bg) !important;
  border: 2px solid var(--iowa-accent) !important;
  font-weight: bold !important;
  box-shadow: 0 0 12px rgba(122, 92, 62, 0.35) !important;
}
body.reading-mode .msg-bubble button.undo-link:hover,
body.reading-mode #inbox-container button.undo-link:hover,
body.reading-mode button.undo-link:hover {
  background: var(--iowa-accent-hover) !important;
  color: var(--iowa-bg) !important;
  border-color: var(--iowa-accent-hover) !important;
}

/* Dismiss button (muted red-brown on hover) */
body.reading-mode .msg-btn.msg-btn-dismiss {
  background: transparent !important;
  color: var(--iowa-text-soft) !important;
  border-color: var(--iowa-border) !important;
}
body.reading-mode .msg-btn.msg-btn-dismiss:hover {
  color: var(--iowa-red) !important;
  border-color: var(--iowa-red) !important;
  background: rgba(142, 74, 62, 0.08) !important;
}

/* Pin button — pinned state gets solid gold */
body.reading-mode .msg-btn.msg-btn-pin {
  background: transparent !important;
  color: var(--iowa-text-soft) !important;
  border-color: var(--iowa-border) !important;
}
body.reading-mode .msg-btn.msg-btn-pin:hover {
  color: var(--iowa-gold-dark) !important;
  border-color: var(--iowa-gold) !important;
  background: rgba(200, 169, 106, 0.1) !important;
}
body.reading-mode .msg-btn.msg-btn-pin.pinned {
  background: var(--iowa-gold) !important;
  color: var(--iowa-text) !important;
  border-color: var(--iowa-gold-dark) !important;
  font-weight: 700 !important;
  box-shadow: none !important;
}

/* TTS / dismiss pills (top of card) */
body.reading-mode .btn-tts,
body.reading-mode .btn-dismiss {
  background: transparent !important;
  color: var(--iowa-text-soft) !important;
  border-color: var(--iowa-border) !important;
}
body.reading-mode .btn-tts:hover {
  color: var(--iowa-accent) !important;
  border-color: var(--iowa-accent) !important;
  background: rgba(122, 92, 62, 0.08) !important;
}
body.reading-mode .btn-tts.playing {
  color: var(--iowa-sage) !important;
  border-color: var(--iowa-sage) !important;
}
body.reading-mode .btn-dismiss:hover {
  color: var(--iowa-red) !important;
  border-color: var(--iowa-red) !important;
  background: rgba(142, 74, 62, 0.08) !important;
}

/* Stewint + titlebar / sidebar buttons */
body.reading-mode #stewint-open-btn,
body.reading-mode #sidebar-toggle,
body.reading-mode #btn-hood,
body.reading-mode #btn-minimize,
body.reading-mode #titlebar-controls .ctrl-btn {
  background: transparent !important;
  color: var(--iowa-text-soft) !important;
  border-color: var(--iowa-border) !important;
  font-family: inherit !important;
}
body.reading-mode #stewint-open-btn:hover,
body.reading-mode #sidebar-toggle:hover,
body.reading-mode #btn-hood:hover,
body.reading-mode #btn-minimize:hover,
body.reading-mode #titlebar-controls .ctrl-btn:hover {
  color: var(--iowa-accent) !important;
  border-color: var(--iowa-accent) !important;
  background: rgba(122, 92, 62, 0.08) !important;
}

/* Stewint iframe box — inline style background:#111 beat by !important. */
body.reading-mode #stewint-iframe {
  background: var(--iowa-bg) !important;
}

/* Stewint panel chrome */
body.reading-mode #stewint-panel,
body.reading-mode #stewint-header,
body.reading-mode #stewint-content {
  background: var(--iowa-panel) !important;
  color: var(--iowa-text) !important;
}

/* Toolbar buttons — ID selectors win, need !important */
body.reading-mode #bottom-toolbar,
body.reading-mode #bottom-toolbar .toolbar-btn,
body.reading-mode #bottom-toolbar button {
  background: var(--iowa-panel) !important;
  color: var(--iowa-text) !important;
  border-color: var(--iowa-border) !important;
  font-family: inherit !important;
}
body.reading-mode #bottom-toolbar .toolbar-btn:hover {
  background: var(--iowa-panel-alt) !important;
  color: var(--iowa-accent) !important;
  border-color: var(--iowa-accent) !important;
}
/* Feed button ON state in reading mode — the blanket rules above would flatten
   it to a grey tile. At rest it takes those blanket rules like its siblings. */
body.reading-mode #bottom-toolbar .toolbar-btn.play-toolbar-btn.active {
  background: linear-gradient(var(--iowa-accent-hover), color-mix(in srgb, var(--iowa-accent-hover) 80%, #000)) !important;
  border-color: var(--iowa-accent-hover) !important;
  transform: translateY(2px);
  box-shadow: inset 0 4px 7px rgba(0, 0, 0, 0.55), 0 0 0 2px var(--iowa-accent), 0 0 12px color-mix(in srgb, var(--iowa-accent) 55%, transparent) !important;
}
body.reading-mode #bottom-toolbar .toolbar-btn.play-toolbar-btn.active,
body.reading-mode #bottom-toolbar .toolbar-btn.play-toolbar-btn.active .toolbar-label { color: #fff !important; }

/* Msg-input, conv-bottom-textarea */
body.reading-mode .msg-input,
body.reading-mode #conv-bottom-textarea {
  background: var(--iowa-panel) !important;
  color: var(--iowa-text) !important;
  border-color: var(--iowa-border) !important;
  font-family: inherit !important;
}
body.reading-mode .msg-input:focus,
body.reading-mode #conv-bottom-textarea:focus {
  border-color: var(--iowa-accent) !important;
}

/* Send button — warm tan always */
body.reading-mode .msg-send-btn,
body.reading-mode #conv-bottom-send {
  background: var(--iowa-accent) !important;
  color: var(--iowa-bg) !important;
  border: none !important;
  box-shadow: none !important;
}
body.reading-mode .msg-send-btn:hover,
body.reading-mode #conv-bottom-send:hover {
  background: var(--iowa-accent-hover) !important;
}
body.reading-mode .msg-send-btn.has-recording,
body.reading-mode #conv-bottom-send.has-recording {
  background: var(--iowa-sage) !important;
  color: var(--iowa-bg) !important;
}

/* Msg-body text color — winning selector is ".msg-bubble .msg-body" */
body.reading-mode .msg-bubble .msg-body,
body.reading-mode .msg-bubble .msg-body p,
body.reading-mode .msg-bubble .msg-body li {
  color: var(--iowa-text) !important;
  font-family: inherit !important;
}
body.reading-mode .msg-bubble .msg-body h1,
body.reading-mode .msg-bubble .msg-body h2,
body.reading-mode .msg-bubble .msg-body h3,
body.reading-mode .msg-bubble .msg-body strong {
  color: var(--iowa-text) !important;
  font-family: inherit !important;
}

/* Bubble bg — stays cream even when .active or .unread adds its border */
body.reading-mode .msg-bubble {
  background: var(--iowa-panel) !important;
  color: var(--iowa-text) !important;
  font-family: inherit !important;
}
body.reading-mode .msg-bubble.active { border-color: var(--iowa-sage) !important; }
body.reading-mode .msg-bubble.unread { border-color: var(--iowa-gold) !important; }

/* === Gruvbox/theme-aware overrides for hardcoded grays in the default theme ===
   These default rules use fixed hex colors (#444, #555, #666, #777, #888, #999, #aaa)
   that were readable on the default dark theme but end up dark-on-dark under
   Gruvbox and white-on-cream under Iowa. Rebind to palette tokens so every
   preset renders readable text. Use !important where the default rule uses
   an ID selector (specificity) or inline. */

/* Sidebar meta: time, kebab, expand-caret, build-row preview */
body.reading-mode .steward-row .steward-time,
body.reading-mode .steward-row.build-row .steward-time,
body.reading-mode .steward-row.sub-row .steward-preview,
body.reading-mode .steward-row.sub-sub-row .steward-preview { color: var(--iowa-text-faint) !important; }
body.reading-mode .steward-row .steward-kebab,
body.reading-mode .steward-row .steward-expand { color: var(--iowa-text-faint) !important; }
body.reading-mode .steward-row.build-row .steward-name,
body.reading-mode .steward-row.sub-row .steward-name,
body.reading-mode .steward-row.sub-sub-row .steward-name { color: var(--iowa-text) !important; }
body.reading-mode .steward-row.build-row.has-unread .steward-name,
body.reading-mode .steward-row.sub-row:hover .steward-name,
body.reading-mode .steward-row.sub-sub-row:hover .steward-name { color: var(--iowa-accent) !important; }

/* Active sidebar row — use tunable --iowa-active-bg so Gruvbox can use a
   subtle highlight (close to bg) while Iowa keeps its cream panel-alt look */
body.reading-mode .steward-row.active { background: var(--iowa-active-bg) !important; }

/* Chat-row avatar-bar meta (sender name, timestamps) */
body.reading-mode .msg-avatar-bar .avatar-name { color: var(--iowa-text) !important; }
body.reading-mode .msg-avatar-bar .avatar-time-actual,
body.reading-mode .msg-avatar-bar .avatar-time-relative,
body.reading-mode .chat-row.archived-row .msg-avatar-bar .avatar-time-actual { color: var(--iowa-text-faint) !important; }

/* Msg-card titles (the "🔁 Reset Jory One-Liner" style headings) */
body.reading-mode .msg-card-title,
body.reading-mode .msg-bubble.archived .msg-card-title { color: var(--iowa-text-soft) !important; }
body.reading-mode .msg-bubble.archived .msg-body,
body.reading-mode .msg-bubble.archived .msg-body h1,
body.reading-mode .msg-bubble.archived .msg-body h2,
body.reading-mode .msg-bubble.archived .msg-body h3 { color: var(--iowa-text-soft) !important; }

/* Response label ("👤 You replied:") and associated meta */
body.reading-mode .msg-response-label { color: var(--iowa-text-faint) !important; }
body.reading-mode .msg-response-button { color: var(--iowa-accent) !important; }

/* Msg-bubble titles + times + session badge (secondary meta) */
body.reading-mode .msg-bubble .msg-title { color: var(--iowa-accent) !important; }
body.reading-mode .msg-bubble .msg-time { color: var(--iowa-text-faint) !important; }
body.reading-mode .msg-bubble .msg-session { color: var(--iowa-text-faint) !important; }

/* Thread empty state + link-action-btn */
body.reading-mode .thread-empty { color: var(--iowa-text-faint) !important; }
body.reading-mode .link-action-btn { color: var(--iowa-text-faint) !important; }
body.reading-mode .link-action-btn:hover { color: var(--iowa-text-soft) !important; border-color: var(--iowa-border) !important; }

/* =============================================================================
   MOBILE SWIPE-DECK (body.embedded only)
   PLAN.md Step 1 — shell + temp arrow nav. Gestures + spring in later steps.
   ============================================================================= */
body.embedded #conv-thread.mobile-deck-active {
  position: relative;
  overflow: hidden;
  /* Ensure the deck has a real height to position cards inside. */
  display: block;
  /* Hide normal spacing rules that fight absolute positioning. */
  padding: 0 !important;
}
/* PARALLAX BACKDROP (Josh 2026-09-03 round 2): "almost like adding a
   transition type look to them too... make it look like in the background
   kind of moving or motioning with them."

   A soft glow that drifts with the deck at a FRACTION of the card's speed
   (--deck-parallax is written by applyDeckPositions/the drag handler). Slower
   motion behind the cards reads as depth, which is what makes the new gutter
   between cards look intentional instead of like a blank seam.

   Deliberately cheap: one composited element, transform-only, sitting behind
   every card (z-index 0 vs the cards' 1/2). Never intercepts touches. */
body.embedded #conv-thread.mobile-deck-active::before {
  content: '';
  position: absolute;
  /* Overscan horizontally so the drift never exposes an edge. */
  top: 0; bottom: 0; left: -30%; right: -30%;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(58% 48% at 20% 30%, rgba(255,170,80,0.17), transparent 68%),
    radial-gradient(52% 44% at 76% 70%, rgba(120,180,255,0.15), transparent 68%),
    radial-gradient(42% 38% at 50% 92%, rgba(180,130,255,0.10), transparent 70%);
  transform: translate3d(calc(var(--deck-parallax, 0px) * 0.18), 0, 0);
  transition: transform var(--deck-parallax-ms, 0ms) cubic-bezier(0.22, 0.61, 0.36, 1);
  will-change: transform;
}
/* Respect a system reduce-motion preference: keep the layout, drop the drift. */
@media (prefers-reduced-motion: reduce) {
  body.embedded #conv-thread.mobile-deck-active::before { transition: none; }
}
body.embedded #conv-thread.mobile-deck-active .show-more-btn,
body.embedded #conv-thread.mobile-deck-active .thread-empty,
body.embedded #conv-thread.mobile-deck-active .archived-row {
  /* Older-cards button, empty state, archived rows are not part of deck view. */
  display: none !important;
}
/* R4 (Josh 2026-08-13): the card is now a THREE-ZONE FIXED FRAME.
   - The .mobile-deck-card row is the FRAME: it fills the thread box, does NOT
     scroll (overflow:hidden), and is the position:relative containing block for
     the absolutely-positioned header, footer, and the scrolling body.
   - The header (.card-position-label) is welded to the top (position:absolute
     top:0) — DETACHED from the scroll region entirely, never slides.
   - The footer (.card-footer-actions) is welded to the bottom — Dismiss / Links
     / Pin stay in reach regardless of body scroll.
   - The bubble (.msg-bubble) is the ONLY scroller: it fills the frame and
     reserves padding-top (the header's measured height, published by
     updateCardPositionLabel as the --card-header-h custom property that the
     padding-top rule below reads — NOT an inline padding-top, which would lose
     to that rule's own `!important`) + padding-bottom (footer height), so body
     content clears both fixed zones.
   This replaces the R3 wrapper-scroll + sticky-header model: sticky pinned the
   header inside the scroller; Josh wanted it fully off the scroll region. */
body.embedded .mobile-deck-card {
  position: absolute !important;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  /* FRAME does not scroll — the bubble inside it does. Fills the thread box
     (thread has overflow:hidden, so it's the authoritative clip box). */
  overflow: hidden;
  display: block;
  padding: 0 !important;
  margin: 0 !important;
  box-sizing: border-box;
  /* Josh 2026-09-03 REVERSAL of the 2026-04-20 no-motion lock. He asked for
     the slide back: "if I use my left and right arrows, it's just a very jerky
     experience... they flash very quickly. It's very ugly."

     The card's own `transition` is now set INLINE by applyDeckPositions (it
     alternates per-move between a transform glide and none, e.g. instant while
     a finger is dragging), so this rule must NOT force it with !important.
     What the 04-20 lock got RIGHT and is kept below: the card's CHILDREN stay
     motionless. Animating the inner text/inputs is what made it read as a
     flash instead of a slide. */
  animation: none !important;
  will-change: transform;
}
/* Children only — never the card itself (its transform transition is inline). */
body.embedded .mobile-deck-card * {
  transition: none !important;
}
/* Horizontal thumb-swipe belongs to the deck; vertical stays with the card
   body so long cards still scroll (Josh 2026-09-03). */
body.embedded #conv-thread.mobile-deck-active {
  touch-action: pan-y;
}

/* The bubble is the scroll container — fills the frame, scrolls vertically.
   padding-top is set inline (= measured header height) by
   updateCardPositionLabel so the first body line clears the fixed header.
   padding-bottom reserves room for the fixed footer bar. */
body.embedded .mobile-deck-card .msg-bubble {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  width: 100%;
  max-width: 100%;
  margin: 0 !important;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  display: flex;
  flex-direction: column;
  /* Footer bar clearance (footer ≈ 44px button + 20px padding + 3px inset ≈
     70px — bump if that changes). The top padding is applied inline per-card =
     the header's measured height (set by updateCardPositionLabel). */
  padding-bottom: 70px;
  box-sizing: border-box;
}
/* The fixed header — welded to the top of the FRAME, off the scroll region.
   Inset 3px on top/left/right so it sits INSIDE the bubble's gradient border
   ring (the ring frames it). Rounded top corners match the card. */
body.embedded .mobile-deck-card .card-position-label {
  position: absolute;
  top: 3px;
  left: 3px;
  right: 3px;
  z-index: 6;
}
/* Temp dev nav — will be removed when real gestures land in Step 3. */
body.embedded #mobile-deck-nav {
  position: fixed;
  /* Park above both bars (walkie 56px + card-input bar ~60px) so dev nav
     stays usable until Step 3 removes it in favor of real swipe gestures. */
  bottom: 124px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 8px 14px;
  background: rgba(20, 20, 20, 0.92);
  border: 1px solid #333;
  border-radius: 22px;
  z-index: 300;
  font-size: 14px;
  color: #ddd;
}
body.embedded #mobile-deck-nav button {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: 1px solid #444;
  background: #1a1a1a;
  color: #eee;
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
}
body.embedded #mobile-deck-nav button:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}
body.embedded #mobile-deck-counter {
  min-width: 44px;
  text-align: center;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.5px;
}

/* Mobile bottom composer (v5 — keyboard-button + compact textarea). Text
   inputs on cards are hidden; the composer is the ONE place to type. The
   ⌨ button takes focus on tap → opens native APK keyboard → text stages
   in the (hidden on mobile) textarea. User then taps [↳ N] or [➤] to send. */
body.embedded .mobile-deck-card .msg-input-wrap {
  display: none !important;
}

/* Keyboard-summon button — mobile-only, hidden on desktop. Tapping
   focuses the (now-compact) textarea, which raises the native keyboard. */
#conv-bottom-type-btn { display: none; }
body.embedded #conv-bottom-type-btn {
  /* Always visible on mobile — Josh's explicit ask 2026-04-20.
     !important blocks any other rule from hiding it. */
  display: inline-flex !important;
  visibility: visible !important;
  opacity: 1 !important;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  /* Column layout with icon + label, parity with 🧹/🗑️/⚙️ (Josh R14). */
  width: 60px;
  min-width: 60px;
  height: 48px;
  padding: 3px 4px;
  border: 1px solid #333;
  border-radius: 8px;
  background: #1a1a1a;
  color: #ccc;
  line-height: 1;
  cursor: pointer;
  flex-shrink: 0;
}
body.embedded #conv-bottom-type-btn .btn-icon { font-size: 20px; line-height: 1; }
body.embedded #conv-bottom-type-btn .btn-sublabel {
  font-family: inherit;
  font-size: 11px;
  letter-spacing: 0.3px;
  color: #888;
  line-height: 1;
  text-transform: uppercase;
}
body.reading-mode.embedded #conv-bottom-type-btn .btn-sublabel {
  color: var(--iowa-text-faint, #7E7563);
}
/* Engaged state — ⌨ Type, 📨 Q, 🕒 History all use .is-engaged so the
   active panel always lights up the same way. Themed via the
   --engaged-* vars (reading-mode remaps to iowa palette). */
body.embedded #conv-bottom-type-btn.is-engaged {
  background: var(--engaged-bg);
  color: var(--engaged-accent);
  border-color: var(--engaged-accent);
}
/* #inbox-container button (ID-specificity, line ~3550 region) outranks
   a plain `.toolbar-btn.is-engaged` class selector, so anchor on the
   same container id to win. */
#inbox-container .toolbar-btn.is-engaged {
  background: var(--engaged-bg);
  color: var(--engaged-accent);
  border-color: var(--engaged-accent);
}
body.reading-mode #bottom-toolbar .toolbar-btn.is-engaged {
  background: var(--engaged-bg) !important;
  color: var(--engaged-accent) !important;
  border-color: var(--engaged-accent) !important;
}
body.embedded #conv-bottom-type-btn:hover {
  background: #2a2a2a;
  color: #fff;
  border-color: #FF6600;
}
body.reading-mode.embedded #conv-bottom-type-btn {
  background: var(--iowa-panel) !important;
  color: var(--iowa-text) !important;
  border-color: var(--iowa-border) !important;
}
body.reading-mode.embedded #conv-bottom-type-btn.is-engaged {
  background: var(--engaged-bg) !important;
  color: var(--engaged-accent) !important;
  border-color: var(--engaged-accent) !important;
}
body.reading-mode.embedded #conv-bottom-type-btn:hover {
  background: var(--iowa-panel-alt) !important;
  border-color: var(--iowa-accent) !important;
}

/* Hard-reload button — mobile-only, cache-busts via ?cachebust=<ts> so
   the APK WebView reliably drops the stale bundle. Hidden on desktop. */
#conv-bottom-reload-btn { display: none; }
body.embedded #conv-bottom-reload-btn {
  display: inline-flex !important;
  visibility: visible !important;
  opacity: 1 !important;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  width: 60px;
  min-width: 60px;
  height: 48px;
  padding: 3px 4px;
  border: 1px solid #333;
  border-radius: 8px;
  background: #1a1a1a;
  color: #ccc;
  line-height: 1;
  cursor: pointer;
  flex-shrink: 0;
}
body.embedded #conv-bottom-reload-btn .btn-icon { font-size: 20px; line-height: 1; }
body.embedded #conv-bottom-reload-btn .btn-sublabel {
  font-family: inherit;
  font-size: 11px;
  letter-spacing: 0.3px;
  color: #888;
  line-height: 1;
  text-transform: uppercase;
}
body.reading-mode.embedded #conv-bottom-reload-btn .btn-sublabel {
  color: var(--iowa-text-faint, #7E7563);
}
body.embedded #conv-bottom-reload-btn:hover {
  background: #2a2a2a;
  color: #fff;
  border-color: #FF6600;
}
body.embedded #conv-bottom-reload-btn.is-reloading {
  opacity: 0.5 !important;
  pointer-events: none;
}
body.reading-mode.embedded #conv-bottom-reload-btn {
  background: var(--iowa-panel) !important;
  color: var(--iowa-text) !important;
  border-color: var(--iowa-border) !important;
}
body.reading-mode.embedded #conv-bottom-reload-btn:hover {
  background: var(--iowa-panel-alt) !important;
  border-color: var(--iowa-accent) !important;
}

/* Embedded bottom bar: LEFT group (tools) · RIGHT group (composer + send).
   Josh R14 2026-04-21: send buttons right-aligned, tools left-aligned,
   no more send icons plopped in the middle. Overflow still scrolls if
   the combined row is too wide. */
body.embedded #conv-bottom-input {
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  /* Right-gutter mirror of .toolbar-btn-row — clears the APK floating
     side-rail column so the send pill doesn't sit under the mic FAB. */
  padding-right: 80px;
}
body.embedded #conv-bottom-input::-webkit-scrollbar { display: none; }

/* Ivy vine over the bottom-bar ledge REMOVED (Josh 2026-08-14): "the vine idea
   was actually a bad one... remove the vine and call it." Was a decorative
   ::before on #conv-bottom-input (added 2026-08-12). Deleted per his call. */

/* Left + right sub-groups — each a flex row. */
.conv-bottom-left,
.conv-bottom-right {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}
.conv-bottom-right { margin-left: auto; }
/* Desktop: the two groups collapse to a normal row (order doesn't matter
   visually since the composer grows). Keep gap consistent. */
body:not(.embedded) .conv-bottom-left,
body:not(.embedded) .conv-bottom-right {
  gap: 8px;
}
body:not(.embedded) .conv-bottom-right { flex: 1; }

/* Keep-card pin toggle styles REMOVED 2026-07-14 (Josh's pin unification):
   the #conv-bottom-keep-pin button is gone — reply-survival is governed solely
   by the card's own 📌 pin. */

/* Mobile bottom-bar "sexy row" — 🧹 clear cards · ⚙️ settings.
   Same shape as ⌨ / 🔄. Hidden on desktop presenter (body.embedded-only). */
#conv-bottom-clear-cards-btn,
#conv-bottom-settings-btn { display: none; }
body.embedded #conv-bottom-clear-cards-btn,
body.embedded #conv-bottom-settings-btn {
  display: inline-flex !important;
  visibility: visible !important;
  opacity: 1 !important;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  width: 60px;
  min-width: 60px;
  height: 48px;
  padding: 3px 4px;
  border: 1px solid #333;
  border-radius: 8px;
  background: #1a1a1a;
  color: #ccc;
  cursor: pointer;
  flex-shrink: 0;
  line-height: 1;
}
body.embedded #conv-bottom-clear-cards-btn .btn-icon,
body.embedded #conv-bottom-settings-btn .btn-icon {
  font-size: 20px;
  line-height: 1;
}
body.embedded #conv-bottom-clear-cards-btn .btn-sublabel,
body.embedded #conv-bottom-settings-btn .btn-sublabel {
  font-family: inherit;
  font-size: 11px;
  letter-spacing: 0.3px;
  color: #888;
  line-height: 1;
  text-transform: uppercase;
}
body.reading-mode.embedded #conv-bottom-clear-cards-btn .btn-sublabel,
body.reading-mode.embedded #conv-bottom-settings-btn .btn-sublabel {
  color: var(--iowa-text-faint, #7E7563);
}

body.embedded #conv-bottom-clear-cards-btn:hover,
body.embedded #conv-bottom-settings-btn:hover {
  background: #2a2a2a;
  color: #fff;
  border-color: #FF6600;
}
body.embedded #conv-bottom-clear-cards-btn:disabled,
body.embedded #conv-bottom-clear-cards-btn.disabled {
  opacity: 0.35 !important;
  cursor: not-allowed;
}
body.reading-mode.embedded #conv-bottom-clear-cards-btn,
body.reading-mode.embedded #conv-bottom-settings-btn {
  background: var(--iowa-panel) !important;
  color: var(--iowa-text) !important;
  border-color: var(--iowa-border) !important;
}
body.reading-mode.embedded #conv-bottom-clear-cards-btn:hover,
body.reading-mode.embedded #conv-bottom-settings-btn:hover {
  background: var(--iowa-panel-alt) !important;
  border-color: var(--iowa-accent) !important;
}

/* === Composer panel === (Joshua 2026-05-20)
   Toggled by the bottom-bar Type button. Sits between #conv-thread and
   #conv-bottom-input. Roomy textarea — multi-line drafting surface. Single
   DOM, per-card draft TEXT swapped on deck navigation (see app.js). Stays
   visible while open; closes on Esc, tap-outside, or a second Type-button
   tap. Does NOT block deck swipe — only overlays a strip above the bottom
   bar. */
#conv-composer {
  display: flex;
  flex-direction: column;
  padding: 8px 12px;
  background: #0d0d0d;
  border-top: 1px solid #222;
  flex-shrink: 0;
}
#conv-composer[hidden] { display: none; }
#conv-composer #conv-bottom-textarea {
  width: 100%;
  min-height: 96px;
  max-height: 240px;
  padding: 10px 12px;
  background: #1a1a1a;
  color: #e0e0e0;
  border: 1px solid #333;
  border-radius: 8px;
  font-family: inherit;
  font-size: var(--presenter-font-size, 18px);
  line-height: 1.4;
  resize: vertical;
  outline: none;
}
#conv-composer #conv-bottom-textarea:focus {
  border-color: #FF6600;
}
body.embedded #conv-composer {
  /* Mobile: pin composer directly above the bottom bar (which is fixed
     at bottom:0). Bottom bar height varies by chrome — measure once and
     expose as --composer-bottom-offset; defaults to a safe constant. */
  position: fixed;
  left: 0;
  right: 0;
  bottom: var(--composer-bottom-offset, 72px);
  z-index: 209; /* sits just under #conv-bottom-input (210) */
  padding: 8px 10px 10px;
  box-sizing: border-box;
}
/* SHORT mode (default open): the textarea is a single-line strip. It
   sits below the worker row (body padding lifts the rows above it) and
   above the fixed bottom bar. Hides nothing. */
body.embedded #conv-composer #conv-bottom-textarea {
  min-height: 44px;
  height: 44px;
  max-height: 44px;
  font-size: 18px;
  resize: none;
  overflow-y: auto;
  /* Keep typed text clear of the ⤢ expand + ✕ clear buttons that float
     over the right edge in the slim one-liner. */
  padding-right: 92px;
}
/* TALL mode (.composer-expanded): the old immersive multi-line height. */
body.embedded.composer-expanded #conv-composer #conv-bottom-textarea {
  min-height: 120px;
  height: auto;
  max-height: 40vh;
  resize: vertical;
}
/* Reading-mode theming — match the rest of the embedded chrome. */
body.reading-mode #conv-composer {
  background: var(--iowa-panel);
  border-top-color: var(--iowa-border);
}
body.reading-mode #conv-composer #conv-bottom-textarea {
  background: var(--iowa-bg);
  color: var(--iowa-text);
  border-color: var(--iowa-border);
}
body.reading-mode #conv-composer #conv-bottom-textarea:focus {
  border-color: var(--iowa-accent);
}
/* Composer bottom-padding: reserve room so nothing gets pinched under
   the fixed composer strip.
   - SHORT (default open): one-liner sits below the worker row, above the
     bottom bar. Only a modest extra band is reserved so the worker row
     stays fully visible (Joshua 2026-08-08: short mode hides NOTHING).
   - TALL (.composer-expanded): the old immersive height — bump padding
     hard so the deck doesn't get pinched behind the tall textarea. */
body.embedded.composer-open { padding-bottom: 132px !important; }
body.embedded.composer-open.composer-expanded { padding-bottom: 240px !important; }

/* ONE typing box on the phone (Joshua 2026-09-03).
   Tapping Type raises the phone app's own native typing box. That box is
   an input DEVICE for #conv-bottom-textarea, which stays the single source
   of truth for per-card drafts and for both send buttons — so the textarea
   must remain in the DOM, keep its value, and keep dispatching input
   events. What it must NOT do is take up screen: two stacked boxes left
   almost no vertical room with the keyboard up.
   So: hide the strip's PIXELS only. visibility+pointer-events, never
   display:none — display:none would drop it out of layout entirely and
   risk the mirroring/draft plumbing that reads and writes it.
   The reserved body padding goes back too, so the deck keeps its height. */
body.native-box-owns-screen #conv-composer {
  visibility: hidden !important;
  pointer-events: none !important;
  height: 0 !important;
  min-height: 0 !important;
  padding-top: 0 !important;
  padding-bottom: 0 !important;
  border-top-width: 0 !important;
  overflow: hidden !important;
}
/* Give back ONLY the band that was reserved for the web composer strip —
   drop from 132px (or 240px expanded) to the ordinary embedded 72px, which
   is the fixed bottom bar's own height. NOT 0: that band is what holds the
   worker strip up off the bottom bar, and zeroing it drops the strip down
   on top of the send buttons, making send untappable. */
body.embedded.composer-open.native-box-owns-screen,
body.embedded.composer-open.composer-expanded.native-box-owns-screen {
  padding-bottom: 72px !important;
}
/* The expanded mode hides the top/sub bars to make room for an immersive
   web composer. With the native box on screen instead there is no immersive
   web composer to make room for — so don't hide them. Done by narrowing the
   hide rule itself (below) rather than force-showing here: these three rows
   have different natural display values and their own visibility gates, and
   a blanket `display: flex` override would light up rows that other code
   deliberately keeps hidden. */

/* Typing-box modes (Joshua 2026-08-08):
   SHORT mode (composer open, NOT expanded) hides NOTHING — the arrows
   row, stewards/steward rows, and worker row all stay visible while the
   one-liner sits below them.
   TALL mode (.composer-expanded) restores the pre-2026-08-08 behavior:
   fully HIDE the queue/history/prev-next toolbar, the steward row, AND
   the worker row so the immersive composer owns the bottom of the
   screen. Joshua: "either I see the type box OR I see the other ones." */
body.composer-open.composer-expanded:not(.native-box-owns-screen) #bottom-toolbar,
body.composer-open.composer-expanded:not(.native-box-owns-screen) #embedded-topbar,
body.composer-open.composer-expanded:not(.native-box-owns-screen) #embedded-subbar { display: none !important; }

/* Saved-draft indicator on the Type button. Shown only when (a) composer
   is CLOSED and (b) the active card has a non-empty preserved draft
   (JS toggles .has-draft on the button). Small dot in the upper-right
   corner of the button, matches presenter accent vocabulary. */
#conv-bottom-type-btn { position: relative; }
#conv-bottom-type-btn.has-draft::after {
  content: '';
  position: absolute;
  top: 4px;
  right: 4px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #FF6600;
  box-shadow: 0 0 4px rgba(255, 102, 0, 0.6);
  pointer-events: none;
}
body.composer-open #conv-bottom-type-btn.has-draft::after { display: none; }
body.reading-mode #conv-bottom-type-btn.has-draft::after {
  background: var(--iowa-accent, #FF6600);
  box-shadow: 0 0 4px rgba(214, 138, 78, 0.6);
}

/* Clear button INSIDE the open composer. Small icon button in the
   upper-right of the textarea container; empties the draft on click. */
#conv-composer { position: relative; }
#conv-composer-clear {
  position: absolute;
  top: 14px;
  right: 18px;
  width: 28px;
  height: 28px;
  border-radius: 6px;
  border: 1px solid #333;
  background: rgba(26, 26, 26, 0.9);
  color: #aaa;
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  z-index: 1;
  font-family: inherit;
}
#conv-composer-clear:hover {
  background: rgba(40, 40, 40, 0.95);
  color: #e0e0e0;
  border-color: #555;
}
#conv-composer-clear:disabled {
  opacity: 0.35;
  cursor: default;
}
body.reading-mode #conv-composer-clear {
  background: var(--iowa-bg);
  color: var(--iowa-text);
  border-color: var(--iowa-border);
}

/* Expand / collapse button INSIDE the open composer (Joshua 2026-08-08).
   Sits just left of the ✕ clear button. Tapping it grows the one-liner
   into the tall immersive composer (and back). Same visual vocabulary as
   the clear button so the two read as a pair. */
#conv-composer-expand {
  position: absolute;
  top: 14px;
  right: 52px;
  width: 28px;
  height: 28px;
  border-radius: 6px;
  border: 1px solid #333;
  background: rgba(26, 26, 26, 0.9);
  color: #aaa;
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  z-index: 1;
  font-family: inherit;
}
#conv-composer-expand:hover {
  background: rgba(40, 40, 40, 0.95);
  color: #e0e0e0;
  border-color: #555;
}
body.reading-mode #conv-composer-expand {
  background: var(--iowa-bg);
  color: var(--iowa-text);
  border-color: var(--iowa-border);
}
/* Desktop (non-embedded) composer keeps its old always-tall layout, so
   the expand affordance is a mobile-only control. */
body:not(.embedded) #conv-composer-expand { display: none; }

/* Top-of-card header (Josh R17 2026-04-21) — title is the hero, N of M
   is a small muted subline. Header sits flush with the card's top edge:
   no margin, inherits the card's top border-radius so bubble + header
   read as one continuous card. Sticky while body scrolls. */
.card-position-label {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  padding: 14px 18px 12px;
  margin: 0;
  /* R4: the header is the card's fixed TOP CAP — welded to the frame top, off
     the scroll region. It sits INSIDE the bubble's gradient border (positioned
     at top/left/right:3px in the embedded rule below) so the bubble's border
     ring frames it — the header itself needs no top/side border, only a bottom
     rule to divide it from the scrolling body and rounded top corners to match
     the card. Opaque background so scrolling body content passes cleanly
     underneath, never bleeding through. */
  border-top-left-radius: 12px;
  border-top-right-radius: 12px;
  border-bottom: 1px solid #2a2a2a;
  box-sizing: border-box;
  position: relative;
  /* Theme-aware font. */
  font-family: inherit;
  color: #aaa;
  z-index: 5;
  background: var(--iowa-panel, #111);
  flex-shrink: 0;
}
/* Josh 2026-09-03: steading icon as a big faint watermark in the header's
   lower-right — "almost like a background... really big but faint", stylistic
   texture rather than a control.

   Sized in em off the header's font so it scales with Josh's text-size slider.
   Anchored bottom-right and allowed to bleed off the corner (the header clips
   it) so it reads as a watermark rather than a placed sticker. Sits BEHIND the
   text (z-index 0 vs. the rows' 1) and is pointer-events:none so it can never
   eat a tap on the card. Emoji ignore `color`, so opacity does the fading. */
/* Positioned against the card FRAME (not the header) so it can hang past the
   header's bottom edge — a header child cannot, because the header sets
   z-index + position and so creates a stacking context that clips its
   children's paint to its own layer. --cpl-h is the measured header height,
   set in updateCardPositionLabel(); the top offset is what makes it overhang.

   z-index 7 puts it just ABOVE the header (6) so its top half isn't buried by
   the header's opaque background, and above the scrolling body so the
   overhanging half stays visible instead of being painted over by body text.
   It is inert (pointer-events:none) and very low opacity, so sitting on top
   costs nothing — it cannot take a tap and cannot obscure text at 8.5%. */
.card-position-label ~ .cpl-steading-mark,
.mobile-deck-card > .cpl-steading-mark {
  position: absolute;
  /* Inside the frame's right edge — the frame clips (overflow:hidden), so a
     negative right would shave the mark rather than bleed it. */
  right: 0.02em;
  top: calc(var(--cpl-h, 140px) - 0.60em);
  font-size: 5.6em;
  line-height: 1;
  /* Josh 2026-09-05: "needs to be less faint... at least twice as visible."
     0.085 -> 0.17 was a literal 2x and he came back "still too faint", so this
     is ~3.5x the original (0.30 dark / 0.38 reading). Grayscale also dropped
     0.35 -> 0.15 so the icon keeps its own color, which reads as more present
     than opacity alone. Still inert and still painted behind the text rows —
     the rows carry z-index 2, so this cannot compete with a title. */
  opacity: 0.30;
  z-index: 7;
  pointer-events: none;
  user-select: none;
  filter: grayscale(0.15);
}
/* Cream reading-mode needs a touch more presence on light paper. */
body.reading-mode .mobile-deck-card > .cpl-steading-mark {
  opacity: 0.38;
}
/* Josh 2026-09-15: "up until hover, it's like hard to see the icons still,
   that's kind of annoying."
   The engine panel is drawn as wire specifically so this icon reads THROUGH
   it — but at 0.30/0.38 there was not much there to read. This is the third
   time he has asked for more presence here (0.085 -> 0.17 -> 0.30), so it
   goes up rather than a nudge. Colour comes back too: grayscale was flattening
   the very thing that makes an emoji recognisable at a glance. */
.mobile-deck-card > .cpl-steading-mark {
  opacity: 0.58;
  filter: grayscale(0);
}
body.reading-mode .mobile-deck-card > .cpl-steading-mark {
  opacity: 0.62;
  filter: grayscale(0);
}
/* Keep every real row above the watermark (mark is z 1, backdrop z 0). */
.card-position-label .cpl-title,
.card-position-label .cpl-name-row,
.card-position-label .cpl-timestamp,
.card-position-label .cpl-meta-row {
  position: relative;
  z-index: 2;
}

.card-position-label .cpl-title {
  /* R17: title is BIG. First thing you see. Hero typography.
     Josh 2026-09-03: bumped 22 -> 26px — he asked for more readable space and
     a title that reads as the first thing at the very top. */
  color: #f3f3f3;
  font-weight: 700;
  font-size: 26px;
  letter-spacing: 0.2px;
  line-height: 1.22;
  word-break: break-word;
  overflow-wrap: anywhere;
}
/* Josh 2026-09-03: worker/project name sits directly UNDER the title — it's
   identity, so it rides with the title rather than the navigational meta row.
   Reads "Crowne Vault \00b7 Catalog Field Overrides" (Option B). */
.card-position-label .cpl-name-row {
  display: flex;
  align-items: baseline;
  gap: 5px;
  min-width: 0;
  margin-top: 1px;
}
.card-position-label .cpl-name-under {
  font-size: 14px;
  font-weight: 600;
  color: #b9b9b9;
  letter-spacing: 0.2px;
  line-height: 1.25;
  word-break: break-word;
  overflow-wrap: anywhere;
}
body.reading-mode .card-position-label .cpl-name-under {
  color: var(--iowa-text-soft) !important;
}

/* Timestamp row — date + time + relative. Lives directly under the title,
   above the N-of-M / sender meta row. Muted tone, theme font via inherit. */
.card-position-label .cpl-timestamp {
  font-family: inherit;
  font-size: 11px;
  letter-spacing: 0.4px;
  color: #888;
  font-variant-numeric: tabular-nums;
  font-weight: 500;
}
body.reading-mode .card-position-label .cpl-timestamp {
  color: var(--iowa-text-faint) !important;
}

/* R17 + R20 meta row — N of M + sender chip, inline. */
.card-position-label .cpl-meta-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
}
.card-position-label .cpl-count {
  /* R17: small, muted, under the title. Navigational context only. */
  color: #777;
  font-size: 11px;
  letter-spacing: 0.8px;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  flex-shrink: 0;
}
.card-position-label .cpl-meta-sep {
  color: #333;
  font-size: 11px;
  flex-shrink: 0;
}
/* R20 sender chip — muted, small, inline. Doesn't compete with title. */
.card-position-label .cpl-sender-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 11px;
  color: #888;
  font-family: inherit;
  letter-spacing: 0.3px;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.card-position-label .cpl-sender-icon { font-size: 12px; line-height: 1; }
.card-position-label .cpl-sender-name {
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
}
body.reading-mode .card-position-label .cpl-meta-sep {
  color: var(--iowa-border);
}
body.reading-mode .card-position-label .cpl-sender-chip {
  color: var(--iowa-text-soft);
}
/* Reading-mode palette overrides — title in accent tan, count in soft text. */
body.reading-mode .card-position-label {
  background: var(--iowa-panel) !important;
  border-bottom-color: var(--iowa-border-soft) !important;
  color: var(--iowa-text-faint) !important;
}
body.reading-mode .card-position-label .cpl-title { color: var(--iowa-text) !important; }
body.reading-mode .card-position-label .cpl-count { color: var(--iowa-text-faint) !important; }

/* R23 (Josh 2026-04-21): in-body pin/dismiss are REMOVED from view in
   embedded mode — the floating persistent pair is canonical. Buttons stay
   in the DOM (hidden) because the floating pair forwards clicks to them
   via .click(), preserving a single handler per action (no logic dup). */
body.embedded .msg-btn.msg-btn-dismiss,
body.embedded .msg-btn.msg-btn-pin {
  display: none !important;
}
/* Legacy corner pin-badge (the little 📌 in the top-left of pinned cards)
   survives — that's just an identity marker, not an action. */

/* R4 (Josh 2026-08-13): full-width FOOTER ACTION BAR welded to the bottom of
   the card frame. Dismiss + Links (primary, left→right) with the Pin as a
   compact square glyph on the right. Anchored to the .mobile-deck-card row (the
   frame, position:absolute inset:0) via position:absolute bottom:0 — it does
   NOT scroll with body content. Replaces the R21 floating bottom-right circle
   pair. Inset 3px on sides + bottom so it sits inside the bubble's gradient
   border ring, matching the header inset at the top. */
/* R6 (Josh 2026-08-13): the solid footer BAR is gone — it ate vertical space
   with its opaque background + divider. The three icon buttons now FLOAT over
   the bottom of the card with no bar behind them, each slightly translucent
   (~85%) so Josh can see the content through them and tap as needed. The
   container is a transparent, no-padding strip welded to the card frame bottom;
   only the buttons themselves paint. */
body.embedded .card-footer-actions {
  position: absolute;
  bottom: 12px;
  left: 12px;
  right: 12px;
  z-index: 6;
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: flex-end;  /* Josh 2026-08-13: all three actions on the far right */
  gap: 8px;
  padding: 0;
  background: none;
  border: none;
  pointer-events: none;  /* strip itself is click-through; buttons re-enable */
}
body.embedded .card-footer-btn {
  height: 40px;
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 10px;
  /* R6: ~85% opaque so content shows through slightly. Dark translucent fill +
     a light blur keeps the glyph legible over any body text underneath. */
  background: rgba(20, 20, 20, 0.85);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  color: #ddd;
  font-family: inherit;
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  pointer-events: auto;  /* re-enable clicks (container is click-through) */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
  transition: background 0.12s, color 0.12s, border-color 0.12s, transform 0.08s;
}
body.embedded .card-footer-btn:active:not(:disabled) {
  transform: translateY(1px);
}
/* R5 (Josh 2026-08-13): all three footer actions are ICON buttons. Compact
   square glyphs, evenly sized. R6 (Josh 2026-08-13): all three — Dismiss,
   Links (icon + count), Pin — clustered together on the far RIGHT (the
   container's justify-content: flex-end does the anchoring). */
body.embedded .card-footer-icon {
  flex: 0 0 auto;
  width: 48px;
  padding: 0;
  font-size: 20px;
}
body.embedded .card-footer-dismiss:hover {
  color: #ff8a8a;
  border-color: #ff8a8a;
}
/* Links — icon (🔗) + optional count chip. Auto-widens to fit the number so
   two/three-digit counts don't clip. */
body.embedded .card-footer-links {
  width: auto;
  min-width: 48px;
  padding: 0 12px;
  gap: 5px;
}
body.embedded .card-footer-link-icon { font-size: 18px; line-height: 1; }
body.embedded .card-footer-link-count {
  font-size: 15px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: #eee;
  letter-spacing: 0.3px;
}
/* Pin — compact square glyph on the right. */
body.embedded .card-footer-pin.pinned {
  background: rgba(255, 190, 74, 0.22);
  border-color: var(--iowa-accent, #FFBE4A);
}
body.reading-mode.embedded .card-footer-link-count { color: var(--iowa-text); }
/* Reading-mode: footer strip stays transparent (R6 — no bar); only the
   floating buttons repaint from Iowa/Gruvbox vars (rule below). */
body.reading-mode.embedded .card-footer-btn {
  /* R6: keep the see-through feel in reading mode too — layer the theme panel
     under a translucency (color-mix) so content still shows faintly through. */
  background: color-mix(in srgb, var(--iowa-panel-alt) 85%, transparent);
  border-color: var(--iowa-border);
  color: var(--iowa-text-soft);
}
body.reading-mode.embedded .card-footer-dismiss:hover {
  color: var(--iowa-red);
  border-color: var(--iowa-red);
}
body.reading-mode.embedded .card-footer-pin.pinned {
  background: var(--iowa-panel-alt);
  border-color: var(--iowa-accent);
}

/* R17 — bubble in embedded gets full top border + full rounding so the
   sticky header can sit flush inside it. Overrides the default
   `.msg-bubble { border-radius: 0 0 12px 12px }` + `.from-steward { border-top: none }`. */
body.embedded .mobile-deck-card.msg-bubble,
body.embedded .mobile-deck-card .msg-bubble {
  border-radius: 12px !important;
  /* R4: sides/bottom own no padding (body sections add their own); TOP is the
     header-height reservation, driven by the --card-header-h custom property,
     which updateCardPositionLabel sets per-card on this element, so the first
     body line clears the fixed header. It goes through a custom property rather
     than an inline padding-top precisely BECAUSE this rule is !important (it has
     to be, to beat the old blanket padding:0) — an inline padding-top would lose
     to it.
     updateCardPositionLabel keeps this value CORRECTED as the header's height
     changes (ResizeObserver), rather than measuring once. On a cold font cache
     the header first lays out in the monospace fallback and measures TALLER
     than it ends up — measured +36px to +119px depending on card width — so a
     one-shot measure left a phantom gap above the body and Josh had to scroll
     to reach his message (2026-09-18). The 0px fallback here only applies
     before the first successful measurement; a 0 is never written over a real
     value, since that would drop the body under the header instead. */
  padding-left: 0 !important;
  padding-right: 0 !important;
  padding-top: var(--card-header-h, 0px) !important;
  /* R6: floating icon buttons (bottom:12px, ~40px tall ≈ 52px zone). Reserve
     just enough so the last body line clears them — no solid bar eating space. */
  padding-bottom: 56px !important;
}
/* Steward-card border gradient (Josh 2026-08-12) — replaces the old
   clashing two-tone border (neon #00FF66 top + gruvbox sage #B8BB26 sides,
   which looked "weird green"). A single calm gradient runs around the whole
   card: gruvbox olive → warm gold ("harvest"). Technique: two stacked
   backgrounds — an opaque panel-colored fill clipped to padding-box (the
   card face) over a gradient clipped to border-box (shows only in the 3px
   transparent border ring). Respects the 12px radius (border-image does not).
   Inner fill uses the theme panel var so it's correct in gruvbox and any
   reading preset, falling back to the dark-theme #111. */
body.embedded .mobile-deck-card.msg-bubble.from-steward,
body.embedded .mobile-deck-card .msg-bubble.from-steward {
  border: 3px solid transparent !important;
  background:
    linear-gradient(var(--iowa-panel, #111), var(--iowa-panel, #111)) padding-box,
    linear-gradient(135deg, #98971A 0%, #B8BB26 45%, #FABD2F 100%) border-box !important;
}
body.embedded .mobile-deck-card.msg-bubble.from-user,
body.embedded .mobile-deck-card .msg-bubble.from-user {
  border-top: 3px solid #FF6600 !important;
}
/* Body gets its own padding so the sticky header can span edge-to-edge. */
body.embedded .mobile-deck-card .msg-body,
body.embedded .mobile-deck-card .msg-input-wrap,
body.embedded .mobile-deck-card .msg-component {
  padding-left: 18px;
  padding-right: 18px;
}
/* R4: bottom clearance for the fixed footer bar is now owned by the scrolling
   bubble's own padding-bottom (see the .mobile-deck-card .msg-bubble rule) —
   the last content section no longer needs its own reservation. Kept at a small
   value for breathing room above the footer divider. */
body.embedded .mobile-deck-card .msg-body:last-child,
body.embedded .mobile-deck-card .msg-input-wrap:last-child,
body.embedded .mobile-deck-card .msg-component:last-child {
  padding-bottom: 8px;
}
body.embedded .mobile-deck-card .msg-body:first-child,
body.embedded .mobile-deck-card .msg-body:not(:first-child) {
  padding-top: 12px;
}

/* R16: Card's internal `.msg-card-title` is now consumed by the sticky
   header above. Hide the duplicate in embedded so it doesn't render
   twice. JS still reads its textContent for the header before hiding. */
body.embedded .msg-bubble .msg-card-title { display: none !important; }

/* R16: `.msg-avatar-bar` (the external icon+name+time strip above each
   bubble) duplicates the page-level steward header. Hide in embedded. */
body.embedded .msg-avatar-bar { display: none !important; }

/* R16: Make the bubble a vertical flex container so sticky header +
   scrollable body work correctly on mobile. */
body.embedded .mobile-deck-card.msg-bubble {
  display: flex;
  flex-direction: column;
}

/* [Send to card N] button — DEPRECATED 2026-04-21 (Josh merged into split-pill).
   Hidden in both desktop and embedded now; kept in DOM for any stragglers. */
#conv-bottom-card-send { display: none !important; }
body.embedded #conv-bottom-card-send { display: none !important; }

/* Dormant rule kept for reference if we ever un-merge. */
#conv-bottom-card-send.legacy-visible {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 0 12px;
  height: 40px;
  border: 1px solid #333;
  border-radius: 8px;
  background: #1a1a1a;
  color: #ccc;
  font-family: 'VT323', monospace;
  font-size: 15px;
  cursor: pointer;
  flex: 0 1 auto;
  white-space: nowrap;
}
body.embedded #conv-bottom-card-send:hover:not(:disabled) {
  background: #2a2a2a;
  color: #fff;
  border-color: #FF6600;
}
body.embedded #conv-bottom-card-send:disabled,
body.embedded #conv-bottom-card-send.disabled {
  opacity: 0.35;
  cursor: not-allowed;
}
body.embedded #conv-bottom-card-send .card-send-icon {
  font-size: 16px;
  color: #FF6600;
}
body.embedded #conv-bottom-card-send .card-send-label {
  letter-spacing: 0.5px;
}
/* Reading-mode palette overrides. */
body.reading-mode.embedded #conv-bottom-card-send {
  background: var(--iowa-panel) !important;
  color: var(--iowa-text) !important;
  border-color: var(--iowa-border) !important;
  font-family: 'Iowa Old Style', Georgia, serif;
}
body.reading-mode.embedded #conv-bottom-card-send:hover:not(:disabled) {
  background: var(--iowa-panel-alt) !important;
  border-color: var(--iowa-accent) !important;
}
body.reading-mode.embedded #conv-bottom-card-send .card-send-icon {
  color: var(--iowa-accent) !important;
}

/* ============================================================================
   Mobile card-nav cluster + preview drawer (body.embedded only)
   ============================================================================ */
body.embedded .card-nav-btn {
  min-width: 44px;
  font-size: 18px;
  padding: 6px 10px;
}
/* R18: ◀ / ▶ arrow buttons get a small teal badge when there are unread
   cards in that direction. Uses the same --unread-accent var as the
   topbar badge-ring for consistency. */
body.embedded .card-nav-prev,
body.embedded .card-nav-next {
  position: relative;
}
body.embedded .card-nav-unread-badge {
  position: absolute;
  top: -4px;
  right: -4px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  line-height: 16px;
  border-radius: 8px;
  background: var(--unread-accent, #00E5D1);
  color: #003A36;
  font-family: inherit;
  font-size: 10px;
  font-weight: 700;
  text-align: center;
  font-variant-numeric: tabular-nums;
  box-shadow: 0 0 4px var(--unread-glow, rgba(0, 229, 209, 0.6));
  pointer-events: none;
}
body.embedded .card-nav-prev .card-nav-unread-badge {
  left: -4px;
  right: auto;
}
body.reading-mode.embedded .card-nav-unread-badge {
  color: var(--iowa-bg);
}
body.reading-mode.embedded .card-nav-btn {
  background: var(--iowa-panel, #1a1a1a) !important;
  color: var(--iowa-text, #e8d9b0) !important;
  border: 1px solid var(--iowa-border, #2a2a2a) !important;
}

/* ============================================================================
   Settings panel — opened via ⚙️ in bottom bar. Hosts font + theme picker.
   Replaces the old expanded sidebar config (Josh killed the nav-style
   side-nav 2026-04-21 in favor of bottom-bar navigation).
   ============================================================================ */
#settings-panel {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: min(420px, 90vw);
  background: #0f0f0f;
  border-left: 1px solid #333;
  z-index: 10001;
  display: none;
  flex-direction: column;
  box-shadow: -8px 0 24px rgba(0, 0, 0, 0.5);
  color: #e0e0e0;
}
#settings-panel.open { display: flex; }
#settings-scrim {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  z-index: 10000;
  display: none;
}
#settings-scrim.open { display: block; }
#settings-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 18px;
  border-bottom: 1px solid #222;
  background: #151515;
}
#settings-title {
  /* Theme-aware — inherit from body stack. */
  font-family: inherit;
  font-size: 22px;
  letter-spacing: 0.5px;
  color: #FF6600;
}
#settings-close {
  background: transparent;
  border: none;
  color: #aaa;
  font-size: 22px;
  cursor: pointer;
  padding: 4px 10px;
  border-radius: 6px;
}
#settings-close:hover { background: #222; color: #fff; }
#settings-body {
  flex: 1;
  overflow-y: auto;
  padding: 18px;
  display: flex;
  flex-direction: column;
  gap: 20px;
}
.settings-section-label {
  /* Theme-aware — inherit from body. */
  font-family: inherit;
  font-size: 14px;
  letter-spacing: 1px;
  color: #888;
  text-transform: uppercase;
  margin-bottom: 6px;
}

/* Switch-to-web tile (APK-only) — lives in Settings panel between Theme and
   Stewards dashboard. Full-width tap target styled as a dark card matching
   the theme-tile-inline aesthetic. All fonts inherit so reading-mode themes
   re-skin it automatically. */
.settings-switch-web-row {
  display: flex;
  margin-bottom: 20px;
}
.settings-switch-web-tile {
  flex: 1;
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-rows: auto auto;
  column-gap: 12px;
  row-gap: 2px;
  align-items: center;
  background: #1a1a1a;
  border: 1px solid #333;
  border-radius: 8px;
  padding: 12px 14px;
  cursor: pointer;
  text-align: left;
  color: inherit;
  font-family: inherit;
}
.settings-switch-web-tile:hover { background: #222; border-color: #444; }
.settings-switch-web-tile:active { background: #2a2a2a; }
.settings-switch-web-icon {
  grid-row: 1 / span 2;
  font-size: 24px;
  line-height: 1;
}
.settings-switch-web-label {
  font-family: inherit;
  font-size: 16px;
  color: #eaeaea;
  font-weight: 600;
}
.settings-switch-web-sub {
  font-family: inherit;
  font-size: 12px;
  color: #888;
}
body.reading-mode .settings-switch-web-tile {
  background: var(--iowa-panel-alt, #E1D5B8);
  border-color: var(--iowa-border, #C8B896);
  color: var(--iowa-text, #1A1815);
}
body.reading-mode .settings-switch-web-tile:hover {
  background: var(--iowa-panel, #F4EFE6);
}
body.reading-mode .settings-switch-web-label {
  color: var(--iowa-text, #1A1815);
  font-family: 'Iowa Old Style', Georgia, serif;
}
body.reading-mode .settings-switch-web-sub {
  color: var(--iowa-text-faint, #7E7563);
  font-family: 'Iowa Old Style', Georgia, serif;
}

/* Reading-mode (Iowa/Gruvbox/etc.) palette overrides — panel feels native. */
body.reading-mode #settings-panel {
  background: var(--iowa-bg, #F4EFE6);
  border-left-color: var(--iowa-border, #D9CCB5);
  color: var(--iowa-text, #1A1815);
}
body.reading-mode #settings-header {
  background: var(--iowa-panel, #EDE4D0);
  border-bottom-color: var(--iowa-border-soft, #E4D7BC);
}
body.reading-mode #settings-title {
  color: var(--iowa-accent, #7A5C3E);
  font-family: 'Iowa Old Style', Georgia, serif;
}
body.reading-mode #settings-close { color: var(--iowa-text-soft, #55493A); }
body.reading-mode #settings-close:hover {
  background: var(--iowa-panel-alt, #E1D5B8);
  color: var(--iowa-accent);
}
body.reading-mode .settings-section-label {
  color: var(--iowa-text-faint, #7E7563);
  font-family: 'Iowa Old Style', Georgia, serif;
}

/* ============================================================
   triageReport component (custom card body)
   Spec: Triage Dispatch Contract v1 §4
   ============================================================ */

.triage-report {
  display: flex;
  flex-direction: column;
  gap: 14px;
  font-size: 14px;
  line-height: 1.4;
  color: inherit;
  margin-top: 8px;
}

.tr-header {
  border-bottom: 1px solid rgba(160,160,160,0.25);
  padding-bottom: 8px;
}
.tr-scope {
  font-weight: 600;
  font-size: 15px;
}
.tr-window {
  font-size: 12px;
  opacity: 0.75;
  margin-top: 2px;
}

.tr-section-title {
  font-weight: 600;
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: 6px;
  opacity: 0.85;
}

/* --- Pinned callouts --- */
.tr-pinned-box {
  border: 1px solid #C8A96A;
  background: rgba(200, 169, 106, 0.08);
  padding: 10px 12px;
  border-radius: 6px;
}
.tr-pinned-title { color: #A88A4E; }
.tr-pinned-row {
  padding: 6px 0;
  border-top: 1px dashed rgba(168,138,78,0.25);
}
.tr-pinned-row:first-of-type { border-top: 0; }
.tr-pinned-cardtitle { font-weight: 600; }
.tr-pinned-meta { font-size: 12px; opacity: 0.75; margin-top: 2px; }

/* --- Groups --- */
.tr-group {
  border: 1px solid rgba(160,160,160,0.3);
  border-radius: 6px;
  padding: 10px 12px;
  background: rgba(255,255,255,0.02);
}
.tr-group-header { margin-bottom: 8px; }
.tr-group-topic {
  font-weight: 700;
  font-size: 15px;
}
.tr-group-question {
  font-size: 13px;
  opacity: 0.85;
  margin-top: 2px;
  font-style: italic;
}
.tr-card-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin: 6px 0;
}
.tr-card-row {
  padding: 6px 8px;
  border-left: 2px solid rgba(160,160,160,0.3);
  background: rgba(0,0,0,0.05);
}
.tr-card-pinned {
  border-left-color: #C8A96A;
  background: rgba(200,169,106,0.06);
}
.tr-card-title { font-weight: 600; font-size: 13px; }
.tr-card-meta { font-size: 11px; opacity: 0.7; margin-top: 1px; }
.tr-card-preview { font-size: 12px; opacity: 0.85; margin-top: 4px; white-space: pre-wrap; }

/* --- Per-group answer --- */
.tr-group-answer {
  margin-top: 10px;
  padding-top: 8px;
  border-top: 1px dashed rgba(160,160,160,0.3);
}
.tr-group-answer-label {
  font-size: 12px;
  font-weight: 600;
  margin-bottom: 4px;
  opacity: 0.8;
}
.tr-group-textarea {
  width: 100%;
  font-family: inherit;
  font-size: 13px;
  padding: 6px 8px;
  border: 1px solid rgba(160,160,160,0.4);
  border-radius: 4px;
  background: rgba(0,0,0,0.06);
  color: inherit;
  resize: vertical;
  min-height: 36px;
}
.tr-card-buttonbar {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  padding: 4px 0;
  border-top: 1px dotted rgba(160,160,160,0.2);
}
.tr-card-buttonbar:first-of-type { border-top: 0; }
.tr-card-buttonbar-label {
  font-size: 12px;
  flex: 1 1 auto;
  min-width: 0;
  opacity: 0.85;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.tr-pickbtn {
  font-size: 12px;
  padding: 3px 10px;
  border: 1px solid rgba(160,160,160,0.5);
  background: transparent;
  color: inherit;
  border-radius: 3px;
  cursor: pointer;
}
.tr-pickbtn:hover { background: rgba(160,160,160,0.15); }
.tr-pickbtn.chosen {
  background: #6B8E4E;
  color: #fff;
  border-color: #4F6E36;
  font-weight: 600;
}

/* --- Culled (visible rows, never buried) --- */
.tr-culled-box {
  border: 1px dashed rgba(142,74,62,0.5);
  background: rgba(142,74,62,0.05);
  padding: 10px 12px;
  border-radius: 6px;
}
.tr-culled-title { color: #8E4A3E; }
.tr-culled-row {
  padding: 6px 0;
  border-top: 1px dashed rgba(142,74,62,0.2);
}
.tr-culled-row:first-of-type { border-top: 0; }
.tr-culled-title-row { font-weight: 600; font-size: 13px; }
.tr-culled-meta { font-size: 11px; opacity: 0.7; margin-top: 1px; }
.tr-culled-reason { font-size: 12px; margin-top: 4px; font-style: italic; }

/* --- Actions --- */
.tr-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding-top: 6px;
  border-top: 1px solid rgba(160,160,160,0.25);
}
.tr-apply-btn {
  font-size: 13px;
  font-weight: 600;
  padding: 6px 14px;
  background: #4A6B8E;
  color: #fff;
  border: 1px solid #38567A;
  border-radius: 4px;
  cursor: pointer;
}
.tr-apply-btn:hover { background: #38567A; }
.tr-apply-btn.flashed {
  background: #6B8E4E;
  border-color: #4F6E36;
}

/* --- Triage button in bulk-actions popover --- */
.bulk-actions-btn.triage {
  background: rgba(74,107,142,0.12);
  border-color: rgba(74,107,142,0.4);
  color: inherit;
  font-weight: 600;
}
.bulk-actions-btn.triage:hover:not(:disabled) {
  background: rgba(74,107,142,0.25);
}

/* --- Info-mode toast (reuses #error-toast) --- */
#error-toast.info {
  background: #1d3a5c;
  color: #fff;
  border-color: #66aaff;
  box-shadow: 0 4px 12px rgba(0,0,0,0.5);
}
body.reading-mode #error-toast.info {
  background: var(--iowa-panel);
  color: var(--iowa-text);
  border-color: var(--iowa-accent);
  box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}

/* =============================================================================
   HISTORY surface — cross-steward archive search.
   Separate panel from QUEUE. Same pattern, different colors (cyan accent so
   Joshua can tell them apart at a glance).
   ============================================================================= */
#history-viewer-panel {
  position: fixed;
  top: 34px;
  left: 50%;
  transform: translateX(-50%);
  width: 520px;
  max-width: calc(100vw - 16px);
  max-height: 80vh;
  background: #0d0d0d;
  border: 1px solid #66ddff;
  border-radius: 6px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  box-shadow: 0 8px 24px rgba(0,0,0,0.6);
  z-index: 2000;
}
body.embedded #history-viewer-panel {
  top: 10px;
  width: calc(100vw - 12px);
  max-width: calc(100vw - 12px);
  max-height: calc(100vh - 20px);
  z-index: 10001; /* above CPU pill (z:10000) so the search input isn't covered */
}
/* Mobile (≤600px): CPU pill is fixed at top:48px with z-index:10000, so push
   the panel below it to keep the search bar tappable. */
@media (max-width: 600px) {
  body.embedded #history-viewer-panel {
    top: 92px;
    max-height: calc(100vh - 100px);
  }
}
#hv-header {
  display: flex;
  align-items: center;
  padding: 6px 10px;
  background: #111;
  border-bottom: 1px solid #222;
  gap: 8px;
}
#hv-title {
  font-family: 'VT323', monospace;
  font-size: 16px;
  color: #66ddff;
  flex-shrink: 0;
}
#hv-search {
  flex: 1;
  background: #050505;
  border: 1px solid #333;
  color: #ddd;
  font-family: 'VT323', monospace;
  font-size: 15px;
  padding: 4px 8px;
  border-radius: 3px;
  min-width: 0;
}
#hv-search:focus {
  outline: none;
  border-color: #66ddff;
}
#hv-close {
  background: none;
  border: none;
  color: #555;
  font-size: 18px;
  cursor: pointer;
  padding: 0 4px;
  flex-shrink: 0;
}
#hv-close:hover { color: #fff; }
#hv-subheader {
  display: flex;
  align-items: center;
  padding: 4px 10px;
  background: #0a0a0a;
  border-bottom: 1px solid #1a1a1a;
  gap: 8px;
  font-family: 'VT323', monospace;
  font-size: 13px;
}
#hv-steward-filter {
  background: #050505;
  color: #ccc;
  border: 1px solid #2a2a2a;
  font-family: 'VT323', monospace;
  font-size: 13px;
  padding: 2px 4px;
  flex: 1;
  min-width: 0;
}
#hv-count {
  color: #555;
  flex-shrink: 0;
}
#hv-list {
  flex: 1;
  overflow-y: auto;
}
.hv-item {
  border-bottom: 1px solid #1a1a1a;
  padding: 8px 10px;
  cursor: pointer;
}
.hv-item:hover { background: rgba(102,221,255,0.04); }
.hv-item.expanded { background: rgba(102,221,255,0.07); }
.hv-row1 {
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-family: 'VT323', monospace;
  font-size: 13px;
}
.hv-steward {
  color: #66ddff;
  flex-shrink: 0;
  white-space: nowrap;
  max-width: 180px;
  overflow: hidden;
  text-overflow: ellipsis;
}
.hv-when {
  color: #444;
  flex-shrink: 0;
  margin-left: auto;
  font-size: 12px;
}
.hv-state {
  font-size: 11px;
  padding: 1px 4px;
  border-radius: 2px;
  flex-shrink: 0;
}
.hv-state.responded { background: rgba(0,255,102,0.15); color: #00ff66; }
.hv-state.dismissed { background: rgba(255,102,0,0.15); color: #ff6600; }
.hv-title {
  font-family: 'VT323', monospace;
  font-size: 15px;
  color: #ddd;
  margin-top: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.hv-snippet {
  font-family: 'VT323', monospace;
  font-size: 13px;
  color: #777;
  margin-top: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.hv-detail {
  padding: 8px 10px 12px;
  background: #050505;
  border-top: 1px dashed #1a1a1a;
  font-family: 'VT323', monospace;
  font-size: 14px;
  color: #ccc;
  white-space: pre-wrap;
  word-break: break-word;
}
.hv-detail .hv-detail-label {
  color: #66ddff;
  font-size: 12px;
  letter-spacing: 0.5px;
  margin-top: 8px;
  margin-bottom: 2px;
  text-transform: uppercase;
}
.hv-detail .hv-detail-label:first-child { margin-top: 0; }
.hv-detail .hv-feedback {
  background: rgba(0,255,102,0.06);
  border-left: 2px solid #00ff66;
  padding: 4px 8px;
  margin-top: 4px;
}
.hv-empty, .hv-loading {
  padding: 20px;
  text-align: center;
  color: #555;
  font-family: 'VT323', monospace;
  font-size: 14px;
}
.hv-mark {
  background: rgba(255, 220, 0, 0.25);
  color: #ffe680;
  padding: 0 1px;
}

/* Scope toggle (See all / Only [steward]) — leads the subheader. */
.hv-scope-toggle {
  background: #050505;
  color: #66ddff;
  border: 1px solid #2a2a2a;
  font-size: 13px;
  padding: 3px 10px;
  border-radius: 3px;
  cursor: pointer;
  flex-shrink: 0;
}
.hv-scope-toggle:hover {
  border-color: #66ddff;
  background: #0d0d0d;
}

/* "See more" — appended at the bottom of the result list when the page is full. */
.hv-more-btn {
  display: block;
  width: calc(100% - 20px);
  margin: 8px 10px 12px;
  padding: 8px 12px;
  background: #050505;
  color: #66ddff;
  border: 1px solid #2a2a2a;
  border-radius: 3px;
  font-size: 14px;
  cursor: pointer;
}
.hv-more-btn:hover {
  border-color: #66ddff;
  background: #0d0d0d;
}

/* Reading-mode treatment for both new buttons. */
body.reading-mode .hv-scope-toggle,
body.reading-mode .hv-more-btn {
  background: var(--iowa-bg);
  color: var(--iowa-accent);
  border: 1px solid var(--iowa-border);
}
body.reading-mode .hv-scope-toggle:hover,
body.reading-mode .hv-more-btn:hover {
  background: var(--pill-half-hover-bg);
  border-color: var(--iowa-accent);
}

@media (max-width: 600px) {
#hv-header { flex-wrap: wrap; }
  #hv-search { font-size: 16px; /* prevent iOS zoom */ }
  .hv-scope-toggle { font-size: 14px; padding: 4px 12px; }
  .hv-more-btn { font-size: 15px; padding: 10px 14px; }
}

/* =============================================================================
   Per-card context menu (long-press on mobile, right-click on desktop).
   Single floating menu; opened by window._presenterCtxMenu. Positioned with
   inline left/top in viewport coords. Native-style dark surface, font inherits
   from <body> so reading-mode + theme overrides apply automatically.
   ============================================================================= */
.presenter-ctx-menu {
  position: fixed;
  z-index: 100000;
  min-width: 200px;
  background: rgba(20, 20, 20, 0.98);
  border: 1px solid #444;
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6);
  padding: 4px;
  font-family: inherit;
  -webkit-user-select: none;
  user-select: none;
  /* Prevent the menu itself from being long-pressed for selection */
  -webkit-touch-callout: none;
}
.presenter-ctx-menu-item {
  display: block;
  width: 100%;
  padding: 10px 14px;
  background: transparent;
  color: #eee;
  border: none;
  border-radius: 4px;
  text-align: left;
  font-family: inherit;
  font-size: 15px;
  line-height: 1.2;
  cursor: pointer;
  /* Match larger touch target on mobile */
  min-height: 44px;
}
.presenter-ctx-menu-item:hover,
.presenter-ctx-menu-item:focus {
  background: rgba(0, 255, 102, 0.15);
  color: #fff;
  outline: none;
}
.presenter-ctx-menu-item:active {
  background: rgba(0, 255, 102, 0.25);
}
/* Reading-mode: warmer surface to match the editorial palette. */
body.reading-mode .presenter-ctx-menu {
  background: var(--iowa-paper, rgba(245, 240, 230, 0.98));
  border-color: var(--iowa-accent, #b8a070);
}
body.reading-mode .presenter-ctx-menu-item {
  color: var(--iowa-ink, #2b2b2b);
}
body.reading-mode .presenter-ctx-menu-item:hover,
body.reading-mode .presenter-ctx-menu-item:focus {
  background: var(--iowa-accent-soft, rgba(184, 160, 112, 0.2));
}

/* --- Delete-worker confirmation modal --- */
/* Sits above the ctx menu (z 100000). Full-viewport dim backdrop with a
   centered card so it reads clearly on the phone (body.embedded). */
.delete-worker-overlay {
  position: fixed;
  inset: 0;
  z-index: 100010;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(0, 0, 0, 0.6);
  -webkit-user-select: none;
  user-select: none;
}
.delete-worker-modal {
  width: 100%;
  max-width: 420px;
  background: rgba(24, 24, 24, 0.99);
  border: 1px solid #444;
  border-radius: 12px;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.7);
  padding: 20px 20px 16px;
  color: #eee;
  font-family: inherit;
}
.delete-worker-modal .dw-title {
  font-size: 18px;
  font-weight: 700;
  margin-bottom: 10px;
  color: #fff;
}
.delete-worker-modal .dw-body {
  font-size: 15px;
  line-height: 1.45;
  color: #ddd;
}
.delete-worker-modal .dw-body p { margin: 0 0 8px; }
.delete-worker-modal .dw-body strong { color: #ffcf70; }
.delete-worker-modal .dw-card-list {
  margin: 4px 0 10px;
  padding-left: 20px;
  max-height: 30vh;
  overflow-y: auto;
}
.delete-worker-modal .dw-card-list li {
  font-size: 14px;
  line-height: 1.4;
  color: #ccc;
  margin: 2px 0;
}
.delete-worker-modal .dw-work {
  margin-top: 6px;
  padding: 8px 10px;
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.05);
  font-size: 14px;
}
.delete-worker-modal .dw-diffsize {
  display: inline-block;
  margin-top: 4px;
  font-size: 13px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  color: #bbb;
}
.delete-worker-modal .dw-add { color: #4caf50; font-weight: 600; }
.delete-worker-modal .dw-del { color: #e06c6c; font-weight: 600; }
body.reading-mode .delete-worker-modal .dw-diffsize { color: #6b6b6b; }
body.reading-mode .delete-worker-modal .dw-add { color: #2e7d32; }
body.reading-mode .delete-worker-modal .dw-del { color: #b02a2a; }
.delete-worker-modal .dw-buttons {
  display: flex;
  gap: 10px;
  margin-top: 18px;
}
.delete-worker-modal .dw-btn {
  flex: 1 1 auto;
  min-height: 48px;
  border-radius: 8px;
  border: 1px solid #555;
  font-family: inherit;
  font-size: 16px;
  font-weight: 600;
  cursor: pointer;
}
.delete-worker-modal .dw-delete {
  background: #b02a2a;
  border-color: #d13a3a;
  color: #fff;
}
.delete-worker-modal .dw-delete:active { background: #8f2020; }
.delete-worker-modal .dw-cancel {
  background: transparent;
  color: #ddd;
}
.delete-worker-modal .dw-cancel:active { background: rgba(255, 255, 255, 0.08); }
/* Push the modal clear of the Android status bar on the phone. */
body.embedded .delete-worker-overlay {
  padding-top: max(24px, env(safe-area-inset-top, 40px));
}
/* Reading-mode: warmer surface to match the editorial palette. */
body.reading-mode .delete-worker-modal {
  background: var(--iowa-paper, rgba(245, 240, 230, 0.99));
  border-color: var(--iowa-accent, #b8a070);
  color: var(--iowa-ink, #2b2b2b);
}
body.reading-mode .delete-worker-modal .dw-title { color: var(--iowa-ink, #2b2b2b); }
body.reading-mode .delete-worker-modal .dw-body { color: var(--iowa-ink, #2b2b2b); }
body.reading-mode .delete-worker-modal .dw-body strong { color: #9a5b00; }

/* --- In-app tmux viewer (read-only) --- */
.tmux-view-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  flex-direction: column;
  background: #0a0a0a;
  color: #ebdbb2;
}
.tmux-view-header {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 12px;
  background: #141414;
  border-bottom: 1px solid #2a2a2a;
  -webkit-app-region: drag;
  user-select: none;
}
/* On the phone (APK WebView) the overlay is position:fixed inset:0, so its
   header sits UNDER the Android status bar and the ← back button is hidden
   off the top of the screen — Joshua reported no way back without restarting
   the whole presenter. Push the header down by the safe-area inset, with a
   40px fallback that matches the presenter's mobile status-bar spacer. Desktop
   Electron reports a zero inset, so this is a no-op there. */
body.embedded .tmux-view-header {
  padding-top: max(8px, env(safe-area-inset-top, 40px));
}
.tmux-view-back {
  -webkit-app-region: no-drag;
  appearance: none;
  background: transparent;
  border: 1px solid #3a3a3a;
  color: #ebdbb2;
  border-radius: 6px;
  width: 32px;
  height: 32px;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.tmux-view-back:hover { background: #1f1f1f; }
.tmux-view-back:active { background: #2a2a2a; }
.tmux-view-title {
  font-family: Menlo, Monaco, "SF Mono", monospace;
  font-size: 13px;
  font-weight: 600;
  color: inherit;
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.tmux-view-status {
  flex: 0 0 auto;
  font-size: 11px;
  color: #928374;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.tmux-view-body {
  flex: 1 1 auto;
  position: relative;
  overflow: hidden;
}
.tmux-view-term {
  position: absolute;
  inset: 0;
  padding: 8px;
  overflow: hidden;
}
.tmux-view-term .xterm,
.tmux-view-term .xterm-viewport {
  width: 100%;
  height: 100%;
}
.tmux-view-empty {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #928374;
  font-family: VT323, monospace;
  font-size: 16px;
  background: #0a0a0a;
  padding: 24px;
  text-align: center;
}

/* Clean-scrollback pane (PIECE 1). Shown when the user scrolls UP off the live
   bottom. Plain text (no ANSI) → inherently readable, never garbled. Shares the
   body box with the live xterm; only one is visible at a time. */
.tmux-view-history {
  position: absolute;
  inset: 0;
  margin: 0;
  padding: 8px;
  overflow-y: auto;
  overflow-x: hidden;
  white-space: pre-wrap;
  word-break: break-word;
  font-family: Menlo, Monaco, "SF Mono", "Fira Code", Consolas, "Courier New", monospace;
  font-size: 13px;
  line-height: 1.2;
  background: #0a0a0a;
  color: #ebdbb2;
  -webkit-overflow-scrolling: touch;
}
body.embedded .tmux-view-history { font-size: 14px; }
body.reading-mode .tmux-view-history {
  background: var(--iowa-bg, #F4EFE6);
  color: var(--iowa-text, #1A1815);
}

/* PIECE 2 — keep the bottom send bar (dictate + the two send buttons) visible
   and usable over the tmux view. #conv-bottom-input is already position:fixed
   bottom:0 (z-index:210); the overlay is z-index:1000 and covers it. Lift the
   bar above the overlay and reserve its height at the overlay's bottom so the
   terminal doesn't sit underneath it. The pill's existing click handlers are
   untouched — re-layering alone restores dictate/send from this screen. */
body.tmux-view-open #conv-bottom-input {
  z-index: 1002;
}
body.tmux-view-open .tmux-view-overlay {
  bottom: var(--tmux-sendbar-h, 88px);
}

/* Reading-mode: iowa palette for the viewer chrome. xterm itself uses its
   own theme object (set in JS via pickTheme()), but the surrounding panel
   should match the editorial cream/warm look. */
body.reading-mode .tmux-view-overlay {
  background: var(--iowa-bg, #F4EFE6);
  color: var(--iowa-text, #1A1815);
}
body.reading-mode .tmux-view-header {
  background: var(--iowa-panel, #FAF5EC);
  border-bottom-color: var(--iowa-border, #D0C4AE);
}
body.reading-mode .tmux-view-back {
  background: transparent;
  border-color: var(--iowa-border, #D0C4AE);
  color: var(--iowa-text, #1A1815);
}
body.reading-mode .tmux-view-back:hover {
  background: var(--iowa-panel-alt, #EFE4CD);
}
body.reading-mode .tmux-view-status {
  color: var(--iowa-text-faint, #7A6B55);
}
body.reading-mode .tmux-view-empty {
  background: var(--iowa-bg, #F4EFE6);
  color: var(--iowa-text-soft, #5A4430);
}


/* Feed button (Josh 2026-09-26, renamed 2026-09-28) — took The Docket's slot.
   At REST it looks like every other toolbar button (Josh: "I do not like the
   always on orange thing"). Only while the mode is on does it turn orange,
   pushed in with an orange ring. Tapping it again does nothing — tapping a
   steward is how you leave. #inbox-container prefix: the generic
   `#inbox-container button` rule carries ID specificity. */
/* ON = the SAME orange, pushed in with an orange ring (Josh 2026-09-28: the
   old teal flip read as a different button — "so obnoxious"). The ring matches
   the steward-row outline that marks whose card is on screen. */
#inbox-container .play-toolbar-btn.active {
  background: linear-gradient(#b8520f, #9a430a);
  border-color: #b8520f;
  color: #fff;
  transform: translateY(2px);
  box-shadow: inset 0 4px 7px rgba(0, 0, 0, 0.55), 0 0 0 2px #ff8a2a, 0 0 12px rgba(255, 138, 42, 0.55);
}
#inbox-container .play-toolbar-btn.active .toolbar-label { color: #fff; }

/* Persistent Links button (Josh 2026-08-23) — always in the toolbar row so the
   links dropdown is reachable even when the selected steward has ZERO cards.
   Reads like its siblings (icon over label); the 🔗 carries a small count chip
   in its corner. Muted at 0, warm tint + brighter count when links exist. */
.links-toolbar-btn .links-icon {
  position: relative;
  display: inline-block;
}
.links-toolbar-btn .links-count {
  position: absolute;
  top: -6px;
  right: -10px;
  min-width: 14px;
  padding: 0 3px;
  font-size: 10px;
  line-height: 14px;
  text-align: center;
  color: #9a9a9a;
  background: #2a2a2a;
  border-radius: 999px;
  transition: color .2s ease, background .2s ease;
}
.links-toolbar-btn.has-links .links-count {
  color: #101012;
  background: #ff8a2a;
  font-weight: 800;
}
.links-toolbar-btn.has-links {
  color: #d8d8d8;
  background: rgba(255,138,42,0.10);
  box-shadow: inset 0 0 0 1px rgba(255,138,42,0.30);
}

/* ============================================================================
   Machine diagnostics — full-screen stat sheet
   Josh 2026-08-29: "it should overtake the whole screen and be a really
   in-depth, beautiful stat sheet of the stats I would care about... make it
   easy to leave as well... I like the real numbers." So: real figures, no
   dumbing down, with charts that make the shape legible at a glance.
   Themed via --iowa-* so it follows reading-mode presets like everything else.
   ============================================================================ */
#machine-sheet {
  position: fixed;
  inset: 0;
  z-index: 10050;
  display: none;
  flex-direction: column;
  background: #100f0d;
  color: #ece4d4;
  overflow: hidden;
}
#machine-sheet.open { display: flex; }
body.reading-mode #machine-sheet {
  background: var(--iowa-bg, #2b2a26);
  color: var(--iowa-text, #efe7d6);
}

#machine-sheet-header {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 16px 18px 14px;
  border-bottom: 1px solid #2c2a25;
}
/* Android (and notched iPhones) draw edge-to-edge, so a position:fixed inset:0
   panel puts its header UNDER the status bar — Josh reported the title and the
   close button getting clipped.
   CAREFUL: `env(safe-area-inset-top, 40px)` does NOT give you 40px here. The
   fallback applies only when env() is UNSUPPORTED; this WebView supports it and
   reports 0 (the page does not set viewport-fit=cover), so the fallback never
   fires and you silently get 0. Measured in-page, 2026-08-29.
   So we add a real 34px status-bar baseline for the embedded/mobile view and
   ADD any genuine inset on top, rather than relying on a fallback that can't
   trigger. Desktop Electron never gets body.embedded + a phone-sized screen. */
body.embedded #machine-sheet-header {
  padding-top: calc(34px + env(safe-area-inset-top, 0px));
}
/* Desktop-width embedded (the Electron presenter window) has no status bar to
   clear, so drop back to the plain padding there. */
@media (min-width: 700px) {
  body.embedded #machine-sheet-header { padding-top: 16px; }
}
/* Keep the last card clear of the home indicator / gesture bar. */
body.embedded #machine-sheet-body {
  padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 48px);
}
body.reading-mode #machine-sheet-header { border-bottom-color: var(--iowa-border, #4d4a43); }

#machine-sheet-title { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
#machine-sheet-verdict {
  font-size: 26px;
  font-weight: 700;
  letter-spacing: .01em;
  line-height: 1.1;
}
#machine-sheet-sub {
  font-size: 12px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: #8d8676;
}
body.reading-mode #machine-sheet-sub { color: var(--iowa-text-faint, #a39b8a); }

/* Big, obvious exit — Josh asked for it to be easy to leave. */
#machine-sheet-close {
  flex: 0 0 auto;
  width: 46px; height: 46px;
  border-radius: 50%;
  border: 1px solid #3a3730;
  background: #1b1916;
  color: #cfc7b6;
  font-size: 19px;
  line-height: 1;
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  -webkit-tap-highlight-color: transparent;
}
#machine-sheet-close:hover { background: #262320; color: #fff; }
body.reading-mode #machine-sheet-close {
  background: var(--iowa-panel, #3a3833);
  border-color: var(--iowa-border, #4d4a43);
  color: var(--iowa-text, #efe7d6);
}

#machine-sheet-body {
  flex: 1 1 auto;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 16px 16px 40px;
}

/* --- cards ---------------------------------------------------------------- */
.ms-card {
  background: #191713;
  border: 1px solid #2c2a25;
  border-radius: 14px;
  padding: 15px 16px;
  margin: 0 0 13px;
}
body.reading-mode .ms-card {
  background: var(--iowa-panel, #3a3833);
  border-color: var(--iowa-border, #4d4a43);
}
.ms-card-label {
  font-size: 11px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: #8d8676;
  margin: 0 0 11px;
}
body.reading-mode .ms-card-label { color: var(--iowa-text-faint, #a39b8a); }

.ms-headline {
  display: flex;
  align-items: baseline;
  gap: 9px;
  margin: 0 0 3px;
}
.ms-big { font-size: 40px; font-weight: 700; line-height: 1; letter-spacing: -.02em; }
.ms-unit { font-size: 15px; color: #8d8676; }
body.reading-mode .ms-unit { color: var(--iowa-text-faint, #a39b8a); }
.ms-caption { font-size: 13px; color: #a39b8a; margin-top: 5px; }

/* --- severity colours ----------------------------------------------------- */
.ms-good { color: #6fcf6f; }
.ms-warn { color: #e0a83a; }
.ms-bad  { color: #e0655a; }
body.reading-mode .ms-good { color: var(--iowa-sage, #8fbf7f); }
body.reading-mode .ms-warn { color: var(--iowa-gold, #e0b64a); }
body.reading-mode .ms-bad  { color: var(--iowa-red,  #e06a5a); }

/* --- stacked share bar (who is using the CPU) ----------------------------- */
.ms-bar {
  display: flex;
  width: 100%;
  height: 15px;
  border-radius: 8px;
  overflow: hidden;
  background: #24211d;
  margin: 12px 0 10px;
}
body.reading-mode .ms-bar { background: rgba(0,0,0,.22); }
.ms-bar-seg { height: 100%; transition: width .45s ease; }
.ms-seg-claude { background: #d8a33a; }
.ms-seg-mcp    { background: #7fa8d8; }
.ms-seg-other  { background: #5c574d; }
.ms-seg-idle   { background: transparent; }

.ms-legend { display: flex; flex-direction: column; gap: 8px; margin-top: 12px; }
.ms-leg-row { display: flex; align-items: center; gap: 9px; font-size: 14px; }
.ms-dot { width: 11px; height: 11px; border-radius: 3px; flex: 0 0 auto; }
.ms-leg-name { flex: 1 1 auto; min-width: 0; }
.ms-leg-val { font-variant-numeric: tabular-nums; font-weight: 600; }
.ms-leg-sub { color: #8d8676; font-size: 12px; margin-left: 6px; font-weight: 400; }
body.reading-mode .ms-leg-sub { color: var(--iowa-text-faint, #a39b8a); }

/* --- two-up tiles --------------------------------------------------------- */
.ms-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 13px; margin: 0 0 13px; }
.ms-grid .ms-card { margin: 0; }
.ms-tile-val { font-size: 30px; font-weight: 700; line-height: 1.05; letter-spacing: -.02em; }
.ms-tile-sub { font-size: 12.5px; color: #8d8676; margin-top: 5px; line-height: 1.4; }
body.reading-mode .ms-tile-sub { color: var(--iowa-text-faint, #a39b8a); }

/* --- load gauge ----------------------------------------------------------- */
.ms-gauge-wrap { display: flex; align-items: center; gap: 16px; }
.ms-gauge { flex: 0 0 auto; }
.ms-gauge-meta { flex: 1 1 auto; min-width: 0; }

/* --- charts --------------------------------------------------------------- */
.ms-chart { width: 100%; display: block; }
.ms-chart-empty {
  font-size: 13px;
  color: #8d8676;
  padding: 18px 0 6px;
  text-align: center;
  line-height: 1.6;
}
body.reading-mode .ms-chart-empty { color: var(--iowa-text-faint, #a39b8a); }

/* --- plain rows ----------------------------------------------------------- */
.ms-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 14px;
  padding: 9px 0;
  border-bottom: 1px solid rgba(255,255,255,.055);
  font-size: 14px;
}
.ms-row:last-child { border-bottom: 0; }
.ms-row-label { color: #a39b8a; }
body.reading-mode .ms-row-label { color: var(--iowa-text-faint, #a39b8a); }
.ms-row-val { font-variant-numeric: tabular-nums; text-align: right; }

.ms-foot {
  text-align: center;
  font-size: 12px;
  color: #6f6a5e;
  margin-top: 18px;
  line-height: 1.6;
}

/* Chart axes — Josh 2026-08-29: "your charts are hard to read because I don't
   know what time frame it's over." Every chart now states its window in the
   card label, labels its vertical extent, and prints the clock time at each end. */
.ms-span { color: #8d8676; font-weight: 400; letter-spacing: .04em; text-transform: none; }
body.reading-mode .ms-span { color: var(--iowa-text-faint, #a39b8a); }

.ms-chart-wrap { position: relative; }
.ms-axis-y {
  display: flex;
  justify-content: space-between;
  font-size: 10.5px;
  color: #6f6a5e;
  font-variant-numeric: tabular-nums;
  margin-bottom: 2px;
}
.ms-axis-y span:last-child { opacity: .75; }
.ms-axis-x {
  display: flex;
  justify-content: space-between;
  font-size: 11px;
  color: #8d8676;
  font-variant-numeric: tabular-nums;
  margin-top: 5px;
  padding-top: 5px;
  border-top: 1px solid rgba(255,255,255,.06);
}
body.reading-mode .ms-axis-y,
body.reading-mode .ms-axis-x { color: var(--iowa-text-faint, #a39b8a); }

/* ===========================================================================
   Josh 2026-09-03 — "move the whole screen up so the card starts at the top,
   and that's where the title is — the first thing at the very top."
   ---------------------------------------------------------------------------
   The 40px top spacer is Josh's tuned phone status-bar clearance (he already
   rejected 100px as too tall) — LEFT ALONE, removing it puts the card under
   the phone's clock.

   The reclaimable strip is #conv-header (58px): a second row showing the
   steward icon + project name. It is now redundant, because the card's own
   header carries "Project \00b7 Job" (Option B). Collapsing it lifts the card
   ~58px so the TITLE is the first thing under the status bar, and it is also
   the "move the name down" half of the ask — identity moves out of the top
   bar and down into the card where the title is.

   Deck view only: the header still carries the kebab/side-panel affordance in
   chat/terminal views, so it is only collapsed while a card deck is showing.
   =========================================================================== */
@media (max-width: 600px) {
  /* :has() — collapse only when a card deck is actually on screen, so chat and
     terminal views keep the header (and its kebab affordance). Chrome/Android
     WebView support :has(); if it ever fails the header simply stays, which is
     today's behaviour. */
  body.embedded #inbox-container:has(.mobile-deck-card) #conv-header {
    display: none !important;
  }
}

body.embedded #titlebar-controls {
  display: block;
  /* Fixed, and sitting INSIDE the 40px status-bar spacer so the dot never
     overlaps the card body. Right-aligned, out of the way of the title. */
  position: fixed;
  top: 13px;
  right: 12px;
  z-index: 10000;   /* above the ::before spacer (9999) */
  padding: 0;
  border: 0;
  background: none;
}
/* The phone CPU "red light" itself lives in public/presenter/index.html — its
   inline <style> loads AFTER this file and uses !important on the same
   selector, so the dot rules must live there to win. Only the positioning of
   the containing strip stays here. */

/* ============================================================
   THE INSTRUMENT SHOP — one shared vintage theme
   ============================================================
   Josh 2026-09-15: "i want it to be like an old-timey fun retro dashboardy
   look ... more old tv ... i want these to be the same exact theme on a
   really cool vintage looking dashboard type thing like way cooler than
   like lean into the aesthetic brother i'm not looking for you to
   pussyfoot around here anymore."

   TWO PROBLEMS, ONE CAUSE. The panel and the modal looked like different
   products, and neither looked vintage. The cause was that every colour in
   both blocks was an inline hex, duplicated — the panel's amber was #FFC257,
   the modal's was #FFA51F, and nothing structural stopped them drifting
   further apart with each edit. So the fix is not "repaint both to match
   today"; it is to give them ONE vocabulary they physically cannot drift out
   of. Every colour below is a token. Neither block hardcodes a hue again.

   THE REFERENCE is a 1960s instrument panel / valve television set: warm
   cream lettering on near-black, brass-tinged bezels with a lit top edge and
   a shadowed bottom one, glass with a crescent highlight, engraved plates,
   and colour used ONLY to mean something. Lean, not loud — the aesthetic is
   in the materials and the typography, not in ornament.
*/
:root {
  /* --- materials --- */
  --ins-void:      #0b0a09;   /* deepest recess, behind glass */
  --ins-panel:     #191714;   /* the panel metal itself */
  --ins-panel-hi:  #241f1a;   /* its lit top edge */
  --ins-bezel:     #4a3f32;   /* brass-tinged bezel ring */
  --ins-bezel-hi:  #6d5c46;   /* the bezel's catch of light */
  --ins-engrave:   #0a0908;   /* engraved-line shadow */

  /* --- lettering --- */
  --ins-ink:       #e8dcc0;   /* warm cream — the main type colour */
  --ins-ink-dim:   #c9bda2;   /* body copy */
  --ins-ink-soft:  #a99b80;   /* secondary type */
  --ins-ink-faint: #7a6f5c;   /* legends, engraved labels */

  /* --- signal colours: these MEAN things, never decoration --- */
  --ins-go:        #6fd08c;   /* working / cool */
  --ins-go-deep:   #1d3a26;
  --ins-warn:      #f0b845;   /* getting warm */
  --ins-alarm:     #ff4b33;   /* broken / hot — the red Josh asked to SEE */
  --ins-alarm-deep:#3d1109;
  --ins-alarm-glow:rgba(255, 75, 51, 0.55);
  --ins-unknown:   #6b6154;   /* can't tell — deliberately mute, never red */

  /* --- the printed dial face --- */
  --ins-dial-band-cool: rgba(232, 220, 192, 0.14);
  --ins-dial-band-hot:  #c22a17;
}

/* ============================================================
   Health check sheet — the diagnose modal
   ============================================================
   Originally Josh 2026-09-11: "it should maybe give me a little modal that
   pops up that says yep this is down and then give me a link that gets me at
   least close to that app so I can find it." Still deliberately a small card
   rather than a full-screen takeover.

   Re-skinned 2026-09-15 onto the shared instrument theme above, because Josh
   read the old one as a different product from the panel that opens it.
   ============================================================ */
#health-modal {
  position: fixed;
  inset: 0;
  z-index: 10060;              /* above the stat sheet (10050) */
  display: none;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(8, 7, 6, 0.74);
}
#health-modal.open { display: flex; }

#health-modal-card {
  width: 100%;
  max-width: 400px;
  /* Never taller than the screen on a phone; the body scrolls instead. */
  max-height: calc(100vh - 120px);
  display: flex;
  flex-direction: column;
  /* Panel metal, lit from above the way a real fascia is. */
  background: linear-gradient(var(--ins-panel-hi), var(--ins-panel) 44%, var(--ins-void));
  color: var(--ins-ink);
  border: 1px solid var(--ins-bezel);
  border-radius: 10px;
  /* The bezel's lit top edge + its own drop shadow. This pair is what makes
     the card read as a machined object rather than a rounded rectangle. */
  box-shadow:
    inset 0 1px 0 var(--ins-bezel-hi),
    inset 0 -1px 0 var(--ins-engrave),
    0 20px 52px rgba(0, 0, 0, 0.66);
  overflow: hidden;
  position: relative;
}
/* Scanlines — the "old TV" in old TV. Kept very faint: at full strength this
   reads as a broken screen rather than a vintage one, and it must never fight
   the type it sits over. */
#health-modal-card::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: repeating-linear-gradient(
    rgba(255, 255, 255, 0.021) 0px,
    rgba(255, 255, 255, 0.021) 1px,
    rgba(0, 0, 0, 0) 1px,
    rgba(0, 0, 0, 0) 3px
  );
  z-index: 2;
}
body.reading-mode #health-modal-card {
  background: linear-gradient(var(--ins-panel-hi), var(--ins-panel) 44%, var(--ins-void));
  color: var(--ins-ink);
}

#health-modal-head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 15px;
  /* An engraved seam: dark groove with a lit lip under it. */
  border-bottom: 1px solid var(--ins-engrave);
  box-shadow: 0 1px 0 rgba(109, 92, 70, 0.22);
  position: relative;
  z-index: 3;
}
#health-modal-title {
  font-size: 12.5px;
  font-weight: 700;
  /* Engraved nameplate lettering: spaced small caps, not a headline. */
  text-transform: uppercase;
  letter-spacing: 0.15em;
  color: var(--ins-ink-dim);
  text-shadow: 0 1px 0 var(--ins-engrave);
}
#health-modal-close {
  background: none;
  border: 0;
  color: var(--ins-ink-faint);
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
  /* 32px box: a close button that is hard to hit on a phone is a trap. */
  min-width: 32px;
  min-height: 32px;
}
#health-modal-close:hover { color: var(--ins-ink); }

#health-modal-body {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 15px;
  position: relative;
  z-index: 3;
}

.hm-item + .hm-item {
  margin-top: 15px;
  padding-top: 15px;
  border-top: 1px solid var(--ins-engrave);
  box-shadow: 0 -1px 0 rgba(109, 92, 70, 0.14);
}
.hm-what {
  font-size: 11.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.13em;
  color: var(--ins-ink-faint);
  margin-bottom: 9px;
}

/* ---- the status plate ---- */
/* Josh: "the on off state is hard for me to read." This is the answer to that,
   and it is deliberately the largest thing in the card — he should know the
   state before he has decided whether to read anything else.

   Built as a recessed engraved plate (inset shadow, dark well) with the word
   sitting in its own signal colour, so it reads as part of the instrument
   rather than as a web badge. */
.hm-plate {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 9px 12px;
  margin-bottom: 11px;
  border-radius: 6px;
  background: var(--ins-void);
  border: 1px solid var(--ins-engrave);
  box-shadow:
    inset 0 2px 5px rgba(0, 0, 0, 0.72),
    0 1px 0 rgba(109, 92, 70, 0.16);
}
.hm-plate-word {
  font-size: 22px;
  font-weight: 800;
  letter-spacing: 0.1em;
  line-height: 1;
}
.hm-plate-sub {
  font-size: 11.5px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ins-ink-faint);
}
/* Working. Green, with a soft bloom — a lamp behind glass, not flat text. */
.hm-plate.is-on .hm-plate-word {
  color: var(--ins-go);
  text-shadow: 0 0 13px rgba(111, 208, 140, 0.45);
}
/* Broken. Red, brighter bloom, and the plate itself picks up the alarm. */
.hm-plate.is-off {
  border-color: rgba(255, 75, 51, 0.36);
  background: linear-gradient(var(--ins-alarm-deep), var(--ins-void) 72%);
}
.hm-plate.is-off .hm-plate-word {
  color: var(--ins-alarm);
  text-shadow: 0 0 15px var(--ins-alarm-glow);
}
/* Can't tell. Mute grey and NO glow — it must not read as an alarm, because
   the phone simply being elsewhere is not a fault. */
.hm-plate.is-unknown .hm-plate-word {
  color: var(--ins-unknown);
  font-size: 17px;
}

.hm-why {
  font-size: 13px;
  line-height: 1.5;
  color: var(--ins-ink-dim);
}
/* The observed fact behind the verdict. msSeverity's rule, inherited: "a
   verdict the reader can't trace back to a visible number is a bug" — so the
   modal always shows WHAT WAS SEEN, not just the conclusion. */
.hm-evidence {
  margin-top: 9px;
  font-size: 12px;
  line-height: 1.5;
  color: var(--ins-ink-faint);
  border-left: 2px solid var(--ins-bezel);
  padding-left: 10px;
}

/* ---- the fix switch ---- */
/* Josh: "especially the white button to to enable wireless debugging like it
   looks so friendly."
   It was a solid bright amber slab — the visual language of a cheerful
   call-to-action, on a control that only ever appears because something is
   broken. Now it is a red-lit panel switch: dark machined body, alarm-red
   engraved lettering, a lit top edge, and it physically depresses when
   pressed. It also only renders for a check that is genuinely down (see
   healthItemHtml). */
.hm-fix {
  display: block;
  width: 100%;
  margin-top: 13px;
  padding: 12px 13px;
  background: linear-gradient(#2b201c, #17100e);
  color: var(--ins-alarm);
  border: 1px solid rgba(255, 75, 51, 0.42);
  border-radius: 6px;
  font-size: 12.5px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  cursor: pointer;
  text-align: center;
  text-shadow: 0 0 11px var(--ins-alarm-glow);
  box-shadow:
    inset 0 1px 0 rgba(255, 140, 120, 0.2),
    inset 0 -2px 4px rgba(0, 0, 0, 0.55),
    0 2px 0 var(--ins-engrave);
  transition: transform 90ms ease, box-shadow 90ms ease;
}
.hm-fix:active {
  /* Travels down onto its own shadow — a switch that moves. */
  transform: translateY(2px);
  box-shadow:
    inset 0 2px 6px rgba(0, 0, 0, 0.7),
    0 0 0 var(--ins-engrave);
}
.hm-fix[disabled] { opacity: 0.55; cursor: default; }
.hm-hint {
  margin-top: 9px;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--ins-ink-soft);
}
.hm-result {
  margin-top: 10px;
  font-size: 12.5px;
  line-height: 1.5;
}
.hm-result:empty { margin-top: 0; }
.hm-result.ok { color: var(--ins-go); }
.hm-result.err { color: var(--ins-alarm); }
.hm-empty {
  font-size: 13px;
  color: var(--ins-ink-soft);
}

/* ============================================================
   ENGINE DASHBOARD — the instrument panel on the card header
   ============================================================
   Josh 2026-09-12: "it'd be kind of nice just to switch it so it's
   actually like a literal dashboard almost in the sense that like lights
   turn on ... instead of a number, it should almost like show up as like
   a temperature gauge. That way it looks like a literal engine ... and we
   just kind of create this little dashboard of like icons that we're
   watching for and they can be like clickable and you can go diagnose ...
   each one has its own flow instead of this two-dot system, it's kinda
   dorky."

   REBUILT 2026-09-15 on the shared instrument theme above. What changed and
   why:

   1. IT IS BIGGER. The old panel was 73px wide on a 412px phone. At that size
      a red band is a pixel, an on/off state is a guess, and no amount of
      styling rescues it. Nothing else here works without this.

   2. IT SITS ON ITS OWN FASCIA. Previously the steading watermark WAS the
      background (Josh, 2026-09-12: "that's the background of the dashboard is
      whatever that icon is"). That reading is kept — the emoji still shows
      through — but the instruments now sit on a translucent machined plate, so
      they read as mounted hardware rather than as icons floating on an emoji.
      Legibility over an arbitrary emoji was the practical forcing function:
      the watermark's colours vary per steading and cannot be relied on for
      contrast behind a needle.

   3. COLOUR IS SEMANTIC, AND THAT NOW INCLUDES ALL-CLEAR. The old rule was
      "no new accent hue; illumination is amber-for-down only." That rule made
      the healthy state indistinguishable from the can't-tell state, which is
      exactly what Josh could not read. Green now means working. See
      lampState() in app.js for the full reasoning.
*/

.engine-panel {
  /* Anchored to the same corner and the same --cpl-h header height the
     steading watermark uses, so the two stay locked together as Josh's
     text-size slider changes the header. */
  position: absolute;
  right: 0.7em;
  top: calc(var(--cpl-h, 140px) - 1.05em);
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 5px 7px;
  /* WIREFRAME — Josh 2026-09-15: "I need to be able to see the icon behind it
     still ... it could almost be more like a wireframe of what it is right
     now ... more wireframe-y instead of as opaque."

     NO FASCIA. The previous version painted a translucent machined plate here,
     which is exactly what buried the steading emoji. There is no background
     and no border on the panel at all now — the instruments are drawn lines
     floating ON the icon, which is what he asked for in the first place
     ("that's the background of the dashboard is whatever that icon is").

     Legibility is bought with GLOW rather than with an opaque ground: every
     stroke carries a dark drop-shadow halo (see --ins-wire-halo below), so a
     cream line stays readable over a bright emoji without hiding it. That is
     the whole trick that makes a wireframe work over unknown content. */
  background: none;
  border: 0;
  box-shadow: none;
  /* Above the watermark (7) so the instruments read crisply against it, but
     below the text rows so it can never compete with a title. */
  z-index: 8;
  pointer-events: auto;
}

/* The halo every wire gets.
   Josh 2026-09-15: "there's like a really hard black outline again on the one
   that I'm seeing... I like the one that you're showing [the mockup]."

   HE WAS LOOKING AT A REAL DIFFERENCE, AND IT WAS NOT THE ONE I FIRST GUESSED.
   The mockup and the live panel had byte-identical halo rules — so the halo
   was not "harder" here in any per-line sense. What differs is DENSITY OF
   GEOMETRY BEHIND IT: the dial packs 19 ticks, a bezel arc and two bands into
   ~40x26px, and `filter` applies to the WHOLE SVG, so all ~22 shapes each
   cast two dark shadows into the same few pixels. Individually invisible;
   summed, they FUSE INTO AN OPAQUE BLACK RECTANGLE with a crisp edge — which
   is precisely the solid plate this rewrite existed to remove, rebuilt out of
   shadows instead of a background. The mockup looked right because its
   specimens sit on a dark plate where that accumulated black is invisible.

   So: ONE soft shadow, not two tight ones — the opposite of what I concluded
   the first time. A single low-opacity pass cannot stack into an edge, and
   the earlier worry (that a soft blur reads as a grey cloud) turns out to be
   the far milder failure: a faint cloud still shows the icon through it, a
   fused black box does not.

   ⚠️ If you add geometry to this SVG, re-check this: the halo's cost scales
   with SHAPE COUNT, not with stroke weight, and nothing warns you. */
.engine-panel svg,
.engine-panel .el-plate {
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.55));
}

.engine-lamps {
  display: flex;
  gap: 6px;
}

/* ---- the temperature gauge ---- */
/* Josh explicitly preferred this over the percentage he first proposed:
   "instead of a number, it should almost like show up as like a temperature
   gauge ... the actual animation would actually give me some detail as to how
   hot it's actually burning." The needle IS the readout.

   It stays tappable, and what it opens names the number the needle is sitting
   on — msSeverity's rule: a verdict the reader can't trace back to a visible
   number is a bug. */
.engine-gauge {
  position: relative;
  width: 58px;
  height: 43px;
  border: 0;
  padding: 0;
  background: transparent;
  cursor: pointer;
  -webkit-app-region: no-drag;
  flex-shrink: 0;
}
.engine-gauge svg { display: block; width: 100%; height: 100%; overflow: visible; }

/* The dial, as WIRE. The bezel is an open arc, not a filled disc, and there is
   no dial face at all — the emoji behind shows straight through the
   instrument. */
.engine-gauge .eg-bezel {
  fill: none;
  stroke: var(--ins-bezel-hi);
  stroke-width: 1.4;
  stroke-linecap: round;
}

/* THE RED BAND — the thing Josh asked for by name: "i need to see an actual
   red like i need to see where the red actually is at."
   Now a thick stroked ARC rather than a filled wedge. It never carried the hot
   zone by opacity — it carries it by being red and by where it sits — so going
   to wire costs it nothing, and it stops hiding the icon. Its start is locked
   to engineHeat()'s 'high' threshold in app.js so the paint and the verdict
   can never disagree. */
.engine-gauge .eg-band-hot {
  fill: none;
  stroke: var(--ins-alarm);
  stroke-width: 3.4;
  stroke-linecap: butt;
  filter: drop-shadow(0 0 2.5px rgba(255, 75, 51, 0.9));
}
.engine-gauge .eg-band-cool {
  fill: none;
  stroke: rgba(232, 220, 192, 0.30);
  stroke-width: 1.2;
  stroke-linecap: butt;
}

.engine-gauge .eg-tick { stroke: var(--ins-ink-soft); stroke-width: 0.9; stroke-linecap: butt; }
.engine-gauge .eg-tick-major { stroke: var(--ins-ink); stroke-width: 1.5; }
.engine-gauge .eg-tick-hot { stroke: #ff9d88; }
.engine-gauge .eg-hot-label {
  font-size: 4.6px;
  font-weight: 800;
  letter-spacing: 0.14em;
  fill: #ff9d88;
}
.engine-gauge .eg-legend {
  font-size: 5.6px;
  font-weight: 800;
  letter-spacing: 0.22em;
  fill: var(--ins-ink-soft);
  text-transform: uppercase;
}
/* The pivot is one of the two things that stays solid — a hollow ring at this
   size reads as a smudge rather than a bearing. */
.engine-gauge .eg-pivot { fill: var(--ins-bezel-hi); }
.engine-gauge .eg-pivot-cap { fill: var(--ins-ink); }

/* The needle swings rather than jumps — this is the "animation" Josh asked
   for, and a slow sweep is what makes it read as an instrument rather than a
   number swap. The slight overshoot is a real needle settling. */
.engine-needle {
  transition: transform 1000ms cubic-bezier(.34, 1.26, .48, 1);
  transform-origin: 30px 31px;
}
/* The needle stays CREAM at normal temperatures, and only goes red once it is
   actually in the danger zone. Real instruments do not repaint their pointer;
   they paint the DIAL and let a plain pointer travel across it. The band
   carries the colour, the needle carries the position, and where they meet IS
   the alarm. Filled, not stroked — a hollow needle is not a needle. */
.engine-gauge .eg-needle-blade,
.engine-gauge .eg-needle-tail { fill: var(--ins-ink); }
.engine-gauge.heat-unknown .eg-needle-blade,
.engine-gauge.heat-unknown .eg-needle-tail { fill: var(--ins-unknown); }

/* A HOT ENGINE LIGHTS UP LIKE NEON TOO — same rule as the lamp: subtle when
   calm, unmissable when it is actually on fire (Josh, 2026-09-15).

   ⚠️ The whole-SVG `filter` is the ONLY safe place to put this glow, and it
   must stay a SMALL NUMBER OF PASSES. The stacking defect from earlier today
   applies in reverse here: ~22 shapes each render every drop-shadow listed, so
   a long list multiplies into a solid mass. A coloured mass is less ugly than
   the black one was, but it would still bury the icon. Three passes maximum,
   and the dark halo stays first so the wires keep their legibility. */
.engine-gauge.heat-high svg { animation: engineHeatThrob 1.6s ease-in-out infinite; }
@keyframes engineHeatThrob {
  /* Must echo the single-shadow halo above, not the old stacked pair — an
     animation that re-applies two dark passes would reinstate the black box
     for half of every cycle. */
  0%, 100% {
    filter:
      drop-shadow(0 1px 2px rgba(0,0,0,.55))
      drop-shadow(0 0 5px rgba(255, 75, 51, 0.75));
  }
  50% {
    filter:
      drop-shadow(0 1px 2px rgba(0,0,0,.55))
      drop-shadow(0 0 9px rgba(255, 120, 95, 0.95))
      drop-shadow(0 0 20px rgba(255, 75, 51, 0.7));
  }
}
/* The needle and the red band go NEAR-WHITE at the core when hot — the same
   "the tube is blown out, only the spill is coloured" trick the lamp uses. A
   uniformly red needle on a red band loses the needle at exactly the moment it
   matters most. */
.engine-gauge.heat-high .eg-needle-blade,
.engine-gauge.heat-high .eg-needle-tail { fill: #ff2d14; }
.engine-gauge.heat-high .eg-pivot-cap { fill: #ff2d14; }
/* ⚠️ The band goes DARKER when hot, not brighter — counterintuitive, and
   measured rather than guessed. Josh chose a red needle over the white-hot
   one, which reintroduces the problem white was solving: needle #ff2d14 against
   the old band #ff6a52 contrasts at only 1.32:1, so the pointer DISAPPEARS
   into the band at exactly the reading that matters most. Deepening the band
   restores the separation while keeping the zone obviously red; the glow
   carries the "lit" impression that the lighter stroke used to. */
.engine-gauge.heat-high .eg-band-hot {
  stroke: #8f1a0c;
  filter: drop-shadow(0 0 5px rgba(255, 45, 20, 0.95));
}
.engine-gauge.heat-high .eg-hot-label {
  fill: #ff4a2c;
}
.engine-gauge.heat-high .eg-tick-hot { stroke: #ffc9bd; }

/* ---- indicator lamps ---- */
/* Also wire: an OUTLINED bulb housing with the glyph drawn inside it, and an
   engraved plate under it reading ON / OFF / ··. The plate is not redundancy
   for its own sake: colour alone fails a red-green reader, and at this size
   the word is frequently the faster read. See lampState() in app.js for why
   darkness stopped meaning "healthy". */
.engine-lamp {
  position: relative;
  width: 30px;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 0;
  cursor: pointer;
  background: none;
  border: 0;
  -webkit-app-region: no-drag;
}
.el-bulb {
  position: relative;
  width: 28px;
  height: 25px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 5px;
  /* Outlined, not filled — the emoji reads straight through the housing. */
  border: 1px solid var(--ins-ink-faint);
  background: none;
  box-shadow: none;
  color: var(--ins-ink-soft);           /* the glyph inherits this */
  transition: color 300ms ease, box-shadow 300ms ease, border-color 300ms ease;
}
.engine-lamp svg { display: block; width: 15px; height: 15px; }
.engine-lamp:active .el-bulb { transform: translateY(1px); }

/* The engraved plate. Tiny, but it is what makes the state unambiguous
   without colour vision. */
.el-plate {
  font-size: 7px;
  font-weight: 800;
  letter-spacing: 0.12em;
  line-height: 1;
  color: var(--ins-ink-soft);
  transition: color 300ms ease;
}

/* WORKING — green wire, steady. A lit bulb that means "all good" is the state
   the old panel could not express at all. Illumination is now a GLOW on the
   outline rather than a filled bulb, so it still reads as lit without
   becoming a surface. */
.engine-lamp.is-on .el-bulb {
  color: var(--ins-go);
  border-color: var(--ins-go);
  box-shadow:
    0 0 5px rgba(111, 208, 140, 0.5),
    inset 0 0 5px rgba(111, 208, 140, 0.22);
}
.engine-lamp.is-on .el-plate { color: var(--ins-go); }

/* BROKEN — a NEON SIGN, not a tinted outline.
   Josh 2026-09-15: "when it's in the red category ... I want it to really look
   like a sign that's turned on, like very neon, very like really kind of
   streaming out like that. And then otherwise it's quite subtle. But right now
   when it's on fire it's too subtle."

   The old alarm was a 5px glow at 0.5 alpha — a tint, which is why it read as
   subtle next to the calm states rather than as an emergency. The fix is not
   "more opacity" on the same shape; it is the actual optics of a gas tube:

     1. A HOT, NEARLY-WHITE CORE. Real neon does not glow in its own hue at the
        centre — the tube is blown out to near-white and only the SPILL is
        coloured. A uniformly-red lamp reads as painted; a white-hot core with
        red bloom reads as lit. This is the single biggest contributor and the
        one the old version was missing entirely.
     2. LAYERED BLOOM AT WIDENING RADII (6 / 14 / 26px), each dimmer than the
        last, so the light falls off the way real light does instead of ending
        at a hard edge.
     3. A BRIGHT INNER RIM so the tube wall itself is the brightest line.

   Deliberately asymmetric with the calm states: `is-on` stays a quiet 5px
   glow. Josh's ask was explicitly two-sided — subtle normally, screaming when
   wrong — and that CONTRAST is the signal. Turning up the healthy state too
   would destroy the thing this exists to do. */
.engine-lamp.is-off .el-bulb {
  /* Josh 2026-09-15: "I wish the icon was red instead of white. I kinda like
     the pure red one before... make it really bright red neon."
     So the core is a SATURATED red rather than the near-white I had. The
     white-hot core is the physically accurate reading of a neon tube, but he
     looked at both and preferred the pure hue — and on a 15px glyph the
     white core was washing the icon out to near-colourless, which is the
     practical cost of that accuracy at this size. Brightness now comes from
     the BLOOM being heavy, not from desaturating the icon. */
  color: #ff2d14;
  border-color: #ff3a20;
  animation: engineLampNeon 1.6s ease-in-out infinite;
}
/* The glyph inside the tube gets its own bloom, or the icon stays flat while
   only the housing lights — which looks like a lit box, not a lit sign. */
.engine-lamp.is-off .el-bulb svg {
  /* Heavier bloom than before: with a red core rather than a white one, the
     glow is the only thing left carrying "this is LIT rather than painted",
     so it has to do more work. */
  filter:
    drop-shadow(0 0 2px rgba(255, 60, 40, 1))
    drop-shadow(0 0 6px rgba(255, 45, 20, 0.95))
    drop-shadow(0 0 13px rgba(255, 45, 20, 0.7));
}
.engine-lamp.is-off .el-plate {
  color: #ff2d14;
  text-shadow:
    0 0 3px rgba(255, 70, 45, 1),
    0 0 9px rgba(255, 45, 20, 0.95),
    0 0 18px rgba(255, 45, 20, 0.7);
}
@keyframes engineLampNeon {
  0%, 100% {
    box-shadow:
      inset 0 0 7px rgba(255, 45, 20, 0.7),
      0 0 6px rgba(255, 60, 35, 1),
      0 0 15px rgba(255, 45, 20, 0.85),
      0 0 28px rgba(255, 45, 20, 0.5);
  }
  50% {
    box-shadow:
      inset 0 0 11px rgba(255, 70, 45, 0.9),
      0 0 10px rgba(255, 80, 55, 1),
      0 0 24px rgba(255, 45, 20, 1),
      0 0 44px rgba(255, 45, 20, 0.75);
  }
}

/* CAN'T TELL — dim wire, no glow. Deliberately NOT red: the phone simply being
   off the home network is not a fault, and a panel that cries wolf stops
   being read at all. This guard predates the panel. */
.engine-lamp.is-unknown .el-bulb {
  color: var(--ins-unknown);
  border-color: var(--ins-unknown);
}
.engine-lamp.is-unknown .el-plate { color: var(--ins-unknown); }

/* Someone reading with reduced-motion still gets the state, just calmly. */
@media (prefers-reduced-motion: reduce) {
  .engine-lamp.is-off .el-bulb { animation: none; }
  .engine-gauge.heat-high svg { animation: none; }
  .engine-needle { transition: none; }
}

/* Reading mode paints the card on warm paper. A wireframe over a light ground
   needs its halo inverted — a dark halo on a dark line over pale paper reads
   as smudge — so the wires pick up a light halo instead, and the ink darkens
   enough to hold contrast. */
body.reading-mode .engine-panel svg,
body.reading-mode .engine-panel .el-plate {
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.45));
}

/* ---- reading mode: reclaim the two buttons the nuclear rule repaints ----
   `body.reading-mode button` (L5032) sets background / color / border /
   box-shadow with !important on EVERY button in the app, so specificity
   cannot reach these — the same constraint .undo-link and #ctrl-cpu-pill
   already document. Without this, reading mode repaints the fix switch as
   cream-on-grey panel: precisely the "looks so friendly" appearance Josh
   objected to, on the one control that only exists because something broke.
   Caught by reading computed styles on the live page rather than by eye —
   the default (non-reading) theme rendered it correctly the whole time. */
body.reading-mode .hm-fix {
  background: linear-gradient(#2b201c, #17100e) !important;
  color: var(--ins-alarm) !important;
  border: 1px solid rgba(255, 75, 51, 0.42) !important;
  box-shadow:
    inset 0 1px 0 rgba(255, 140, 120, 0.2),
    inset 0 -2px 4px rgba(0, 0, 0, 0.55),
    0 2px 0 var(--ins-engrave) !important;
}
body.reading-mode .hm-fix:active {
  box-shadow:
    inset 0 2px 6px rgba(0, 0, 0, 0.7),
    0 0 0 var(--ins-engrave) !important;
}
/* The close glyph is a bare mark on the fascia, not a chip. */
body.reading-mode #health-modal-close {
  background: none !important;
  border: 0 !important;
  color: var(--ins-ink-faint) !important;
  box-shadow: none !important;
}
body.reading-mode #health-modal-close:hover { color: var(--ins-ink) !important; }

/* ---- reading mode: keep the wireframe panel TRANSPARENT ----
   `body.reading-mode button` (L5032) sets background / border / box-shadow with
   !important on EVERY button in the app, and the gauge and each lamp ARE
   buttons — so reading mode was painting a solid #282828 slab behind each
   instrument. That is the exact opacity Josh asked me to remove, reintroduced
   by a rule that has nothing to do with this panel.

   This is the SECOND time this rule has silently undone work here (it repainted
   the .hm-fix switch earlier the same day). It cannot be beaten by specificity
   — only by matching !important, which is the pattern .undo-link and
   #ctrl-cpu-pill already document. Caught by reading computed styles on the
   live page; the screenshot alone read as "a dark panel behind the dial",
   which looks like a deliberate design choice rather than a bug. */
/* ⚠️ THE `#inbox-container` PREFIX IS LOAD-BEARING — do not "simplify" it away.
   The panel mounts INSIDE #inbox-container, and the nuclear button rule has an
   ID in one of its selectors (`body.reading-mode #inbox-container button`,
   specificity 1,2,1). A plain `body.reading-mode .engine-gauge` is (0,3,0) —
   LOWER — so it loses even though both carry !important and mine is later in
   the file. That is exactly what happened: I shipped the un-prefixed version,
   watched the slab survive, and had to measure the cascade to see why.

   ⚠️ And the reason my verification passed while the bug was live: I tested a
   panel appended to document.body, OUTSIDE #inbox-container, where the losing
   rule was never in play. A style check has to run on a node in the SAME
   PLACE IN THE TREE as the real one, or it tests a different cascade. */
body.reading-mode #inbox-container .engine-gauge,
body.reading-mode #inbox-container .engine-lamp,
body.reading-mode .msg-bubble .engine-gauge,
body.reading-mode .msg-bubble .engine-lamp,
body.reading-mode .engine-gauge,
body.reading-mode .engine-lamp {
  background: none !important;
  border: 0 !important;
  box-shadow: none !important;
}
/* The un-prefixed base rule loses to the same ID selector in dark mode too. */
#inbox-container .engine-gauge,
.msg-bubble .engine-gauge {
  background: transparent !important;
  border: 0 !important;
}
#inbox-container .engine-lamp,
.msg-bubble .engine-lamp {
  background: none !important;
  border: 0 !important;
}
/* The bulb housing keeps its outline — it is the one wire that IS a border. */
/* ⚠️ `:not(.is-off)` is load-bearing. This clears the leftover filled-bulb
   styling, but an !important box-shadow BEATS AN ANIMATION — so without the
   exclusion it silently flattens the alarm's neon keyframes to nothing while
   the rule looks entirely innocuous. The alarm's glow is animated; every other
   state's is static, so only the alarm needs carving out. */
body.reading-mode #inbox-container .engine-lamp:not(.is-off) .el-bulb,
body.reading-mode .msg-bubble .engine-lamp:not(.is-off) .el-bulb,
body.reading-mode .engine-lamp:not(.is-off) .el-bulb {
  background: none !important;
  box-shadow: none !important;
}
/* The alarm bulb still needs its background cleared, just not its shadow. */
body.reading-mode #inbox-container .engine-lamp.is-off .el-bulb,
body.reading-mode .msg-bubble .engine-lamp.is-off .el-bulb,
body.reading-mode .engine-lamp.is-off .el-bulb {
  background: none !important;
}
/* Same ID-specificity trap as above — these restore the lit glow that the
   blanket `box-shadow: none` two rules up would otherwise strip. */
/* The healthy state stays a quiet 5px glow ON PURPOSE. Josh's ask was
   two-sided — "otherwise it's quite subtle" — and the CONTRAST between this
   and the neon alarm is what carries the signal. Brightening this too would
   erase the very difference the alarm depends on. */
body.reading-mode #inbox-container .engine-lamp.is-on .el-bulb,
body.reading-mode .msg-bubble .engine-lamp.is-on .el-bulb,
body.reading-mode .engine-lamp.is-on .el-bulb {
  box-shadow:
    0 0 5px rgba(111, 208, 140, 0.5),
    inset 0 0 5px rgba(111, 208, 140, 0.22) !important;
}
/* ⚠️ THE ALARM STATE DELIBERATELY SETS **NO** box-shadow HERE.
   Its glow comes from the `engineLampNeon` keyframes, and an !important
   box-shadow on the element BEATS AN ANIMATION — so re-stating a static shadow
   here (as this rule used to, capping it at the old 5px tint) silently
   flattens the neon back to a whisper while every other check still passes.
   That is the third time today an override written for one purpose quietly
   undid a later change; the only thing that catches it is looking at the
   animated element, not at the rule.

   What this rule DOES still need to do is beat the nuclear
   `body.reading-mode #inbox-container button` reset, which would otherwise
   strip the shadow entirely. `revert-layer` is not available here, so the
   animation is left to supply the value and only the non-animated properties
   are forced. */
body.reading-mode #inbox-container .engine-lamp.is-off .el-bulb,
body.reading-mode .msg-bubble .engine-lamp.is-off .el-bulb,
body.reading-mode .engine-lamp.is-off .el-bulb {
  animation: engineLampNeon 1.6s ease-in-out infinite !important;
  color: #ff2d14 !important;
}
/* The bulb OUTLINE is the one border that must survive the resets above. */
#inbox-container .engine-lamp .el-bulb,
.msg-bubble .engine-lamp .el-bulb,
body.reading-mode #inbox-container .engine-lamp .el-bulb,
body.reading-mode .msg-bubble .engine-lamp .el-bulb {
  border: 1px solid var(--ins-ink-faint) !important;
}
#inbox-container .engine-lamp.is-on .el-bulb,
.msg-bubble .engine-lamp.is-on .el-bulb,
body.reading-mode #inbox-container .engine-lamp.is-on .el-bulb,
body.reading-mode .msg-bubble .engine-lamp.is-on .el-bulb {
  border-color: var(--ins-go) !important;
}
#inbox-container .engine-lamp.is-off .el-bulb,
.msg-bubble .engine-lamp.is-off .el-bulb,
body.reading-mode #inbox-container .engine-lamp.is-off .el-bulb,
body.reading-mode .msg-bubble .engine-lamp.is-off .el-bulb {
  /* Saturated, not pale: Josh chose pure red over the white-hot core. */
  border-color: #ff3a20 !important;
}
#inbox-container .engine-lamp.is-unknown .el-bulb,
.msg-bubble .engine-lamp.is-unknown .el-bulb,
body.reading-mode #inbox-container .engine-lamp.is-unknown .el-bulb,
body.reading-mode .msg-bubble .engine-lamp.is-unknown .el-bulb {
  border-color: var(--ins-unknown) !important;
}
