/* No native tap-highlight flash on any control (buttons get their own
   :active feedback below instead — a bare tap-highlight-color: transparent
   would otherwise leave taps feeling unacknowledged). */
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

:root {
  --peek: 30px; /* keep in sync with PEEK in shell.js */

  /* ---- Cyberpunk / neon token system ----
     Void + panel are near-black; the three neon accents (cyan/magenta/green)
     carry all signal — everything else stays dim lavender-grey so the accents
     read clearly. If the Orbitron @import (shell.html) can't reach the network,
     --font-display falls back to a bold, wide-tracked system stack; every rule
     that uses it also sets letter-spacing/weight so the fallback still looks
     deliberate rather than "font failed to load". */
  --void: #050409;
  --panel: #0a0812;
  --panel-2: #120e1e;
  --panel-3: #1a1430;
  --line: rgba(0, 242, 255, 0.28);
  --line-soft: rgba(0, 242, 255, 0.14);

  --cyan: #00f2ff;
  --cyan-dim: rgba(0, 242, 255, 0.5);
  --magenta: #ff00ff;
  --magenta-dim: rgba(255, 0, 255, 0.5);
  --green: #39ff14;
  --green-dim: rgba(57, 255, 20, 0.45);
  --amber: #ffb300;
  --amber-dim: rgba(255, 179, 0, 0.45);
  --red: #ff2965;
  --red-dim: rgba(255, 41, 101, 0.4);

  --ink: #eaf6ff;
  --ink-dim: #8f8ba8;
  --ink-faint: #5c5878;

  --font-display: 'Orbitron', ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-mono: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
}

/* Buttons/select already suppress double-tap scroll gestures by nature, but
   iOS Safari still interprets rapid repeat taps on them as a double-tap-to-
   zoom unless told otherwise. */
button, select { touch-action: manipulation; }

html, body {
  margin: 0;
  height: 100%;
  font-family: var(--font-body);
  background: var(--void);
  color: var(--ink);
  /* Without this, overscrolling past the top of the page (e.g. a swipe that
     starts on #app-frame or bounces past a maxed-out scroller) can trigger
     the browser's own pull-to-refresh/navigation-swipe instead of just
     rubber-banding the sheet or scroller in place. */
  overscroll-behavior-y: none;
}

/* The app fills the screen except a small bottom gutter where the pull-up
   coding window peeks. */
#app-frame {
  display: block;
  width: 100%;
  height: calc(100% - var(--peek));
  border: 0;
  background: #fff;
}


/* Transient indicator while the app hot-swaps CSS or reloads */
#refresh-pill {
  position: fixed;
  top: max(10px, env(safe-area-inset-top));
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 14px;
  border-radius: 999px;
  background: rgba(10, 8, 18, 0.9);
  border: 1px solid var(--line);
  color: var(--ink);
  font-size: 13px;
  letter-spacing: 0.02em;
  z-index: 45;
  box-shadow: 0 0 16px rgba(0, 242, 255, 0.25);
}

#refresh-pill[hidden] { display: none; }

#toast {
  position: fixed;
  bottom: 90px;
  left: 50%;
  transform: translateX(-50%);
  padding: 8px 16px;
  border-radius: 999px;
  background: rgba(10, 8, 18, 0.92);
  border: 1px solid var(--line);
  color: var(--ink);
  font-size: 13px;
  z-index: 60;
  opacity: 0;
  transition: opacity 0.25s;
  pointer-events: none;
  box-shadow: 0 0 16px rgba(0, 242, 255, 0.2);
}

#toast.show { opacity: 1; }
#toast.error { background: rgba(58, 6, 22, 0.95); border-color: var(--red); color: #ffd6e2; box-shadow: 0 0 16px var(--red-dim); }

.spinner {
  width: 12px;
  height: 12px;
  border: 2px solid rgba(0, 242, 255, 0.25);
  border-top-color: var(--cyan);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

@keyframes spin { to { transform: rotate(360deg); } }

/* Bottom sheet */
#refs-row[hidden] { display: none; }

.sheet {
  position: fixed;
  left: 0;
  right: 0;
  /* Fallback 0; shell.js sets --kb-inset from the visual viewport on iOS
     Safari, where the keyboard shrinks the visual but not the layout
     viewport, so bottom: 0 alone would leave the sheet under the keyboard. */
  bottom: var(--kb-inset, 0px);
  background: var(--panel);
  border-radius: 16px 16px 0 0;
  border-top: 1px solid var(--line);
  box-shadow: 0 -6px 32px rgba(0, 0, 0, 0.6), 0 -1px 0 var(--cyan-dim), 0 -18px 40px -20px rgba(0, 242, 255, 0.25);
  /* Left/right follow the same env() pattern as bottom: in landscape on a
     notched phone the notch (or the rounded-corner exclusion zone) sits on
     one side, not just the bottom, so a plain 14px would tuck bar buttons
     and view content half under it. */
  padding: 0 calc(14px + env(safe-area-inset-right)) calc(12px + env(safe-area-inset-bottom)) calc(14px + env(safe-area-inset-left));
  z-index: 50;
  display: flex;
  flex-direction: column;
  /* The sheet itself never scrolls — only #chat-log (and the settings/projects
     views) do. Without this, content below the pinned input row could push the
     whole sheet into a scroll and drag the typing row out of view. */
  overflow: hidden;
  /* No gap: the views butt up against the control bar so scrolled content is
     cut off exactly at the bar's bottom border. Breathing room comes from the
     views' own top padding, which scrolls with the content. */
  gap: 0;
  /* vh fallback for browsers without dvh; dvh tracks the dynamic (toolbar-
     aware) viewport so a fully opened sheet never extends under the
     browser chrome on mobile Safari. */
  height: 45vh;
  height: 45dvh;
  transform: translateY(0);
  transition: transform 0.25s ease;
}

.sheet.collapsed {
  transform: translateY(calc(100% - var(--peek)));
}

.sheet.dragging { transition: none; }

/* Settings view: a calm, single-purpose surface — a darker inset panel that
   drops every editing control (Save/Discard, projects, element-pick) so only
   the ⚙ toggle remains to leave. */
.sheet.settings-mode { background: var(--panel-2); }
.sheet.settings-mode #save-group,
.sheet.settings-mode #branches-btn,
.sheet.settings-mode #select-btn { display: none !important; }

/* Projects view: same full-takeover treatment, but the ‹ back button
   (#branches-btn) is how the user leaves it, so it's the one control the bar
   keeps — everything else (element-pick, save/undo, settings) hides. */
.sheet.projects-mode { background: var(--panel-2); }
.sheet.projects-mode #save-group,
.sheet.projects-mode #select-btn,
.sheet.projects-mode #settings-btn { display: none !important; }

/* During a run, strip the bar to just the run: hide back/picker/settings so the
   composer + Stop are all that remain in reach. */
.sheet.generating #branches-btn,
.sheet.generating #select-btn,
.sheet.generating #settings-btn { display: none !important; }

/* Peek header. Collapsed it's just the slim grabber pill (a short peek strip);
   open it becomes a compact row of floating controls — projects (‹), the
   element-pick toggle (◎), the save/discard group (when dirty), then settings
   (⚙) pinned right. It has NO background of its own: the buttons float directly
   over the sheet's surface and the chat content. Collapsed height must match
   --peek (min-height); open, it grows to fit the button row. Drag to
   open/resize/close; tap to toggle. */
#drag-handle {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  min-height: var(--peek);
  flex: none;
  padding-top: 7px;
  position: relative; /* paint above the views so the shadow casts onto them */
  z-index: 1;
  cursor: grab;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
}

/* Chat content hiding under the bar: cast a glow so it reads as "pull up". */
.sheet.overflowed #drag-handle {
  box-shadow: 0 7px 14px -7px rgba(0, 242, 255, 0.3);
}

#drag-handle:active { cursor: grabbing; }

#drag-handle .pill {
  width: 38px;
  height: 4.5px;
  border-radius: 999px;
  background: var(--cyan);
  box-shadow: 0 0 8px var(--cyan-dim);
  flex: none;
}

/* The floating control row appears only when the sheet is open; collapsed, the
   peek strip shows nothing but the drag pill. */
.sheet.collapsed .bar-row { display: none; }

.bar-row {
  display: flex;
  align-items: center;
  width: 100%;
  gap: 8px;
  font-size: 12px;
  /* Nudge the whole icon-chip row (projects ‹ · select ◎ · save/undo · ⚙)
     up 5px so the chips sit tighter to the grabber pill. Transform-only: no
     layout shift, the drag-handle keeps its --peek height. */
  transform: translateY(-5px);
}

/* Pushes settings to the far right, leaving the projects/select buttons (and
   the save group, when present) on the left. */
.bar-spacer { flex: 1 1 auto; }

/* Icon controls (projects / select / settings): dark neon-bordered chips that
   float over the content. Phone-sized hit areas come from an invisible
   ::after extension rather than inflating the visuals — the drag-handle's
   pointerdown ignores target.closest('button'), so a pseudo-element hit still
   resolves to the button and never turns into a drag. */
#branches-btn, #select-btn, #settings-btn {
  border: 1px solid var(--line);
  background: var(--panel-2);
  color: var(--ink-dim);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  padding: 6px 9px;
  border-radius: 8px;
  flex: none;
  position: relative;
}

#branches-btn::after, #select-btn::after, #settings-btn::after {
  content: '';
  position: absolute;
  inset: -9px -5px; /* ~44px tall / ~40px wide hit target */
}

#branches-btn {
  font-size: 18px;
  color: var(--cyan);
}

/* Active state (element-pick on, or settings open): brand-tinted so it reads as
   a pressed toggle. */
#select-btn.active, #settings-btn.active {
  background: rgba(0, 242, 255, 0.12);
  border-color: var(--cyan);
  color: var(--cyan);
  box-shadow: 0 0 10px rgba(0, 242, 255, 0.35);
}

/* Projects view */
#branches-view {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain; /* see #chat-log */
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px 2px 4px;
  color: var(--ink-dim);
}

#branches-view[hidden] { display: none; }

#branches-view h3 {
  margin: 0 0 4px;
  font-size: 15px;
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink);
}

#branch-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

#branch-list li {
  display: flex;
  align-items: center;
  gap: 8px;
  /* Vertical room isn't scarce in this scrolling list, so rows reach >=44px
     tall via real padding rather than a pseudo-element hit area. */
  padding: 12px 10px;
  border: 1px solid var(--line-soft);
  border-radius: 10px;
  background: var(--panel-2);
}

#branch-list li.current { border-color: var(--cyan); background: rgba(0, 242, 255, 0.08); box-shadow: 0 0 12px rgba(0, 242, 255, 0.15); }

#branch-list .b-open {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 4px;
  border: none;
  background: none;
  text-align: left;
  font-size: 14px;
  color: var(--ink);
  cursor: pointer;
}

#branch-list .b-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

#branch-list .b-check {
  flex: none;
  color: var(--cyan);
  text-shadow: 0 0 6px var(--cyan-dim);
}

#branch-list .b-act {
  border: 1px solid var(--line);
  background: var(--panel-3);
  color: var(--ink-dim);
  font-size: 12px;
  padding: 3px 10px;
  border-radius: 8px;
  cursor: pointer;
  flex: none;
  position: relative; /* see #branches-btn for the ::after hit-area pattern */
}

#branch-list .b-act::after {
  content: '';
  position: absolute;
  inset: -13px -3px; /* row gap is 8px, so 3px per side leaves clear space */
}

#branch-list .b-act.danger { color: var(--red); border-color: var(--red-dim); }

#save-group {
  display: flex;
  align-items: center;
  gap: 7px;
  color: var(--amber);
}

#save-group[hidden] { display: none; }

#save-group .dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--amber);
  box-shadow: 0 0 8px var(--amber-dim);
  animation: pulse 1.6s ease-in-out infinite;
}

@keyframes pulse { 50% { opacity: 0.35; } }

#save-group button {
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel-2);
  color: var(--ink);
  font-size: 12px;
  padding: 3px 10px;
  cursor: pointer;
  position: relative; /* see #branches-btn for the ::after hit-area pattern */
}

#save-group button::after {
  content: '';
  position: absolute;
  inset: -13px -3px; /* group gap is 7px, so 3px per side leaves clear space */
}

#save-btn {
  border-color: var(--cyan) !important;
  background: linear-gradient(90deg, var(--magenta), var(--cyan)) !important;
  color: #05040c !important;
  font-weight: 700;
  box-shadow: 0 0 12px rgba(0, 242, 255, 0.35);
}

/* Chat view vs full-takeover settings view */
#chat-view {
  display: flex;
  flex-direction: column;
  gap: 10px;
  flex: 1;
  min-height: 0;
  /* Only #chat-log inside here scrolls; the input row stays pinned. This stops
     the view from ever becoming its own scroller and hiding the typing row. */
  overflow: hidden;
}

#chat-view[hidden] { display: none; }

#settings-view {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain; /* see #chat-log */
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px 2px 4px;
  color: var(--ink-dim);
}

#settings-view[hidden] { display: none; }

#settings-view h3 {
  margin: 0 0 4px;
  font-size: 15px;
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink);
}

#settings-view label {
  font-size: 12px;
  color: var(--ink-dim);
}

/* Chain-of-thought disclosure inside an assistant bubble */
.msg .reasoning {
  margin: 0 0 6px;
  font-size: 12px;
}

/* Collapsed = a compact 💭 badge; tap to disclose the thinking below it. */
.msg .reasoning summary {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 1px 9px 1px 7px;
  border-radius: 999px;
  background: rgba(0, 242, 255, 0.1);
  border: 1px solid var(--line-soft);
  color: var(--cyan);
  font-size: 11px;
  font-weight: 500;
  cursor: pointer;
  list-style: none;
  user-select: none;
}

.msg .reasoning summary::-webkit-details-marker { display: none; }
/* Caret sits after the label and flips when open. */
.msg .reasoning summary::after {
  content: '▸';
  font-size: 9px;
  opacity: 0.7;
  transition: transform 0.12s ease;
}
.msg .reasoning[open] summary::after { transform: rotate(90deg); }

.msg .reasoning-body {
  margin-top: 4px;
  padding: 6px 8px;
  border-left: 2px solid var(--cyan-dim);
  background: rgba(0, 242, 255, 0.05);
  border-radius: 4px;
  color: var(--ink-dim);
  white-space: pre-wrap;
  font-family: var(--font-mono);
  font-size: 11px;
  line-height: 1.5;
  max-height: 40vh;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.settings-note {
  margin: 0;
  font-size: 12px;
  color: var(--ink-faint);
}

#model-select {
  font-size: 16px; /* >=16px stops iOS Safari auto-zooming on focus */
  padding: 8px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--panel-2);
  color: var(--ink);
}

textarea {
  width: 100%;
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 10px;
  font: inherit;
  resize: none;
  background: var(--panel-2);
  color: var(--ink);
}

textarea::placeholder { color: var(--ink-faint); }
textarea:focus { outline: none; border-color: var(--cyan); box-shadow: 0 0 0 1px var(--cyan) inset, 0 0 14px rgba(0, 242, 255, 0.25); }

button.primary {
  border: none;
  border-radius: 10px;
  padding: 10px 16px;
  background: linear-gradient(90deg, var(--magenta), var(--cyan));
  color: #05040c;
  font: inherit;
  font-weight: 700;
  letter-spacing: 0.02em;
  cursor: pointer;
  box-shadow: 0 0 14px rgba(0, 242, 255, 0.3);
}

button.primary:disabled { opacity: 0.4; box-shadow: none; }

/* Replaces the native tap-highlight (disabled globally above) with a quick
   opacity dip, so buttons still visibly react to touch. */
button:active { opacity: 0.7; }

/* Chat */
#chat { max-height: 88vh; max-height: 88dvh; } /* dvh fallback, see .sheet */

#chat-log {
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-height: 0; /* may collapse fully in the compact (tap-opened) state */
  flex: 1;
  padding-top: 10px; /* scrolls away with the content; clip stays at the bar */
  /* Contain rubber-banding to this scroller: without it, overscrolling past
     the top/bottom chains into the page behind the sheet (pull-to-refresh,
     or the app iframe visibly shifting). */
  overscroll-behavior: contain;
}

.msg {
  max-width: 85%;
  padding: 8px 12px;
  border-radius: 12px;
  font-size: 14px;
  white-space: pre-wrap;
  word-break: break-word;
}

/* Dark violet-slate, deliberately NOT the neon accents — user messages
   shouldn't compete with the primary-action (Send/Save/Popplo) glow. */
.msg.user { align-self: flex-end; background: var(--panel-3); border: 1px solid var(--line-soft); color: var(--ink); }
/* A message waiting behind an active run: dimmed, with a small "queued" chip so
   it's clearly pending (not swallowed). Both clear when its run starts. */
.msg.user.queued { opacity: 0.6; }
.queued-chip {
  display: inline-block;
  margin-left: 6px;
  padding: 0 6px;
  border-radius: 8px;
  font-size: 10px;
  line-height: 15px;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--muted, #9aa);
  background: var(--panel-2, rgba(255,255,255,0.06));
  border: 1px solid var(--line-soft);
  vertical-align: middle;
}
/* No bubble behind the agent turn — the badges carry their own pill fills and
   the summary reads as plain prose, so a filled blob (empty while the run spins
   up, oversized around a badge row) is just noise. */
.msg.assistant { align-self: flex-start; background: transparent; color: var(--ink); padding: 4px 2px; max-width: 92%; }
/* The 📝 summary rendered inline in the response area (no click needed). */
.msg.assistant .summary { display: block; margin-top: 6px; white-space: pre-wrap; }
.msg.system { align-self: center; background: rgba(255, 179, 0, 0.08); border: 1px solid var(--amber-dim); color: var(--amber); font-size: 12px; }
/* Visual-review verdict: 👁 line, tinted green for OK / amber for ISSUE via JS. */
.msg.review { align-self: stretch; background: rgba(57, 255, 20, 0.06); color: #8dffb0; font-size: 12px; border-left: 3px solid var(--green); }
.msg.review.issue { background: rgba(255, 179, 0, 0.08); color: var(--amber); border-left-color: var(--amber); }

/* Assistant turn = an ordered row of tappable action badges (the whole turn,
   segmented by action: 🧭 plan · ✎ edits · 🖼 images · 📝 summary · 👁 preview).
   Rendered on the live streaming bubble and re-rendered identically from the
   stored `actions` list on the final message and after a transcript reload. */
.msg .actions {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 5px;
  vertical-align: middle;
}
/* Preview trails the summary as its own line (plan → code → summary → preview). */
.msg.assistant .actions.preview:empty { display: none; }
.msg.assistant .actions.preview { display: flex; margin-top: 6px; }

/* One badge: an icon, an optional filename (edits/images), and an optional
   token count. Tap to open its captured content in the fullscreen viewer. */
.msg .badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  line-height: 1;
  color: var(--cyan);
  background: rgba(0, 242, 255, 0.1);
  border: 1px solid var(--line-soft);
  border-radius: 999px;
  padding: 4px 9px;
  cursor: pointer;
  vertical-align: middle;
  animation: action-pop 0.25s ease;
}

.msg .badge:active { background: rgba(0, 242, 255, 0.2); }
.msg .badge .ic { font-size: 12px; }

.msg .badge .fn {
  font-family: var(--font-mono);
  font-size: 10.5px;
  max-width: 14ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Per-badge token count (e.g. "· 0.3k"); hidden when zero/none (preview). */
.msg .badge .tok { font-size: 10.5px; opacity: 0.75; white-space: nowrap; }
.msg .badge .tok[hidden] { display: none; }

/* Live spinner on the currently-running badge: a tiny ring that shows ONLY
   while the badge is .active, so it's always obvious which action is in flight
   (alongside the pulse + ticking token count). currentColor tints it per kind. */
.msg .badge .badge-spin {
  display: none;
  width: 9px;
  height: 9px;
  border: 1.5px solid rgba(0, 242, 255, 0.25);
  border-top-color: currentColor;
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
  flex: none;
}
.msg .badge.active .badge-spin,
.msg .badge.image.activating .badge-spin { display: inline-block; }

/* Per-kind tints, all on the neon palette so a mixed row stays scannable:
   plan = violet-cyan, edits = cyan, summary = electric blue, preview = acid
   green (a completed, verified state). */
.msg .badge.planning { color: #b18cff; background: rgba(177, 140, 255, 0.12); border-color: rgba(177, 140, 255, 0.3); }
.msg .badge.editing { color: var(--cyan); background: rgba(0, 242, 255, 0.1); border-color: var(--line-soft); }
.msg .badge.summarizing { color: #6ea8ff; background: rgba(110, 168, 255, 0.12); border-color: rgba(110, 168, 255, 0.3); }
.msg .badge.preview { color: var(--green); background: rgba(57, 255, 20, 0.1); border-color: var(--green-dim); }

/* The currently-running badge pulses; exactly one at a time (see setActive). */
.msg .badge.active {
  animation: action-pop 0.25s ease, badge-pulse 1.1s ease-in-out infinite;
}

/* Async generate_image badge: pulses while generating (activating), settles to
   a green tint on success, red on failure — flipped by the image-done event. */
.msg .badge.image.activating {
  color: #d6b8ff;
  animation: action-pop 0.25s ease, badge-pulse 1.1s ease-in-out infinite;
}
.msg .badge.image.ok { color: var(--green); background: rgba(57, 255, 20, 0.12); border-color: var(--green-dim); }
.msg .badge.image.error { color: var(--red); background: rgba(255, 41, 101, 0.12); border-color: var(--red-dim); }

@keyframes badge-pulse { 50% { opacity: 0.45; } }
/* Badges rise up + scale in from below — from the spot Popplo occupies while
   working — so they read as being emitted upward out of the robot. */
@keyframes action-pop { from { transform: scale(0.55) translateY(8px); opacity: 0; } }

/* ---- Popplo: the persistent mascot ----
   A FREE-STANDING sprite (no chip / border / card) that lives in the composer
   and physically relocates with the run state, toggled by setBusy():
     • idle    → perched quietly on the Send button (bottom-right), slow
                 frame-cycle + gentle bob.
     • working → risen to the top-left, at the head of the streaming badge row,
                 faster frame-cycle + brighter glow. The action badges pop
                 upward out of this spot (see action-pop), so Popplo reads as
                 EMITTING them.
   Position is animated via the `right`/`bottom` anchors so the move between the
   two spots arcs smoothly; a tap fires a Web Audio beep + a scale pulse (the
   .beep class, cleared on animationend in shell.js). */
.composer { position: relative; }

.vibot {
  position: absolute;
  z-index: 3;
  width: 36px;
  height: 44px;
  cursor: pointer;
  /* Sprite only — no background/border/box-shadow: it stands on its own. */
  transition: right 0.5s cubic-bezier(0.45, 1.25, 0.4, 1),
              bottom 0.5s cubic-bezier(0.45, 1.25, 0.4, 1);
}

/* Idle: sit on the Send button in the bottom-right corner of the composer. */
.vibot.idle { right: 10px; bottom: 40px; }
/* Working: rise to the top-left, just above the composer where the newest
   streaming badges land — the head of the activity row. */
.vibot.working { right: calc(100% - 40px); bottom: calc(100% - 8px); }

.vibot-sprite {
  display: block;
  width: 36px;
  height: 44px;
  background: url('/__evolve/ui/robot.png') no-repeat 0 0;
  background-size: 144px 44px;
  image-rendering: pixelated;
  /* Cyan glow hugs the sprite's own shape (drop-shadow), not a box. */
  filter: drop-shadow(0 0 5px rgba(0, 242, 255, 0.55));
  animation: vibot-frames 2.4s steps(4) infinite, vibot-bob 2.8s ease-in-out infinite;
}

.vibot.working .vibot-sprite {
  filter: drop-shadow(0 0 7px rgba(0, 242, 255, 0.9)) drop-shadow(0 0 12px rgba(255, 0, 255, 0.4));
  animation: vibot-frames 0.5s steps(4) infinite, vibot-bob 1.2s ease-in-out infinite;
}

/* Tap feedback: a quick scale pulse on the container (transform is free — the
   position rides on right/bottom), leaving the sprite's frame animation intact. */
.vibot.beep { animation: vibot-pulse 0.45s ease; }

@keyframes vibot-frames { from { background-position: 0 0; } to { background-position: -144px 0; } }
@keyframes vibot-bob { 0%, 100% { transform: translateY(3px); } 50% { transform: translateY(-1px); } }
@keyframes vibot-pulse { 0%, 100% { transform: scale(1); } 35% { transform: scale(1.35); } }

@media (prefers-reduced-motion: reduce) {
  .vibot-sprite { animation: none; background-position: 0 0; }
  .vibot { transition: none; }
}

/* Fanfare: a one-shot confetti burst when a finished run's changes land and the
   app auto-refreshes to "ready to test". The layer is pointer-transparent and
   self-removes after ~1.5s (JS). Each .confetti flies to a per-particle
   (--dx,--dy) offset while spinning (--rot), then fades. */
.fanfare {
  position: fixed;
  inset: 0;
  z-index: 95; /* above the app, sheet (50), toast (60) and file-view (90); below fatal (100) */
  pointer-events: none;
  overflow: hidden;
}
.fanfare .confetti {
  position: absolute;
  width: 8px;
  height: 8px;
  border-radius: 2px;
  opacity: 0;
  will-change: transform, opacity;
  animation: confetti-fly 1.1s cubic-bezier(0.2, 0.7, 0.3, 1) forwards;
}
@keyframes confetti-fly {
  0%   { transform: translate(-50%, -50%) scale(0.4) rotate(0deg); opacity: 0; }
  15%  { opacity: 1; }
  100% { transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy) + 90px)) scale(1) rotate(var(--rot)); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .fanfare { display: none; }
}

/* Fullscreen read-only source/diff viewer */
#file-view {
  position: fixed;
  inset: 0;
  z-index: 90; /* above the sheet (50) and toast (60), below fatal (100) */
  background: var(--void);
  color: var(--ink);
  display: flex;
  flex-direction: column;
  padding: max(10px, env(safe-area-inset-top)) max(12px, env(safe-area-inset-right)) max(10px, env(safe-area-inset-bottom)) max(12px, env(safe-area-inset-left));
}

#file-view[hidden] { display: none; }

.fv-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--line-soft);
  margin-bottom: 8px;
}

#fv-name {
  flex: 1;
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ink);
}

#fv-mode, #fv-close {
  border: 1px solid var(--line);
  background: var(--panel-2);
  color: var(--ink);
  border-radius: 8px;
  font-size: 13px;
  padding: 8px 14px; /* real padding: nothing tightly packed in this bar */
  cursor: pointer;
  flex: none;
}

#fv-mode { color: var(--cyan); border-color: var(--line); }

#fv-content {
  flex: 1;
  margin: 0;
  overflow: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  font-size: 12px;
  line-height: 1.5;
  color: var(--ink);
  white-space: pre; /* lines keep their shape; the container scrolls both ways */
}
/* Badge prose (plan/summary/preview text) wraps instead of scrolling sideways. */
#fv-content.wrap { white-space: pre-wrap; word-break: break-word; }

#fv-content .add { background: rgba(57, 255, 20, 0.12); color: #8dffb0; }
#fv-content .del { background: rgba(255, 41, 101, 0.12); color: #ffb3c8; }
#fv-content .hunk { background: rgba(0, 242, 255, 0.1); color: var(--cyan); }

/* Preview badge → the screenshot itself, shown at full width with its
   compliance verdict captioned below. */
#fv-content .fv-shot {
  display: block;
  max-width: 100%;
  height: auto;
  border: 1px solid var(--line);
  border-radius: 8px;
  box-shadow: 0 0 20px rgba(0, 242, 255, 0.15);
}
#fv-content .fv-caption {
  margin-top: 12px;
  white-space: pre-wrap;
  font-family: var(--font-body);
  font-size: 13px;
  line-height: 1.5;
  color: var(--ink-dim);
}

.chat-row {
  display: flex;
  gap: 8px;
  align-items: flex-end;
}

.chat-row[hidden] { display: none; }

/* Stop control: appears (left of the input) only while a run is generating. */
#stop-btn {
  flex: none;
  align-self: stretch;
  min-width: 44px;
  border: 1px solid var(--red-dim);
  background: var(--panel-2);
  color: var(--red);
  border-radius: 10px;
  font-size: 14px;
  cursor: pointer;
}

#stop-btn:active { background: rgba(255, 41, 101, 0.15); }
#stop-btn[hidden] { display: none; }

.chat-row textarea { flex: 1; }

#chat-send { min-height: 44px; } /* real box growth is fine here — nothing tightly packed beside it */

/* Pending app errors: one tappable banner; tapping submits them to the agent */
#error-pending {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 3px;
  text-align: left;
  border: 1px solid var(--red-dim);
  background: rgba(255, 41, 101, 0.08);
  color: #ffb3c8;
  border-radius: 10px;
  padding: 8px 10px;
  font-size: 12px;
  font-family: var(--font-mono);
  cursor: pointer;
  flex: none;
  max-height: 96px;
  overflow: hidden;
  word-break: break-word;
}

#error-pending[hidden] { display: none; }

#error-pending .err-hint {
  font-family: var(--font-body);
  font-size: 11px;
  font-weight: 600;
  color: var(--red);
}

/* Element reference chips: pending (in refs row) and embedded in messages */
#refs-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.ref-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  max-width: 100%;
  padding: 3px 9px;
  border-radius: 999px;
  border: 1px solid var(--amber-dim);
  background: rgba(255, 179, 0, 0.08);
  color: var(--amber);
  font-family: var(--font-mono);
  font-size: 12px;
  cursor: pointer;
  user-select: none;
  /* No hit-area ::after here: a pseudo-element always paints after (on top
     of) its element's real children, so a chip-wide ::after would sit over
     the .remove button below and swallow every tap meant for it. */
}

/* Hover-only on devices that actually have a hovering pointer: on touch,
   Chrome/Safari apply :hover to whatever was last tapped and never clear it
   (there's no pointer to "leave" with), so a tapped chip would otherwise
   stay highlighted until some other element is tapped. :active is transient
   (press-only) so it stays unconditional as the touch tap feedback. */
@media (hover: hover) and (pointer: fine) {
  .ref-chip:hover { background: rgba(255, 179, 0, 0.16); }
}
.ref-chip:focus-visible, .ref-chip:active { background: rgba(255, 179, 0, 0.16); }

.ref-chip .label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ref-chip .remove {
  border: none;
  background: none;
  color: inherit;
  font-size: 12px;
  padding: 2px 4px; /* grows the visual box slightly; the negative margin cancels the layout shift */
  margin: -2px -4px;
  cursor: pointer;
  line-height: 1;
  position: relative; /* see #branches-btn for the ::after hit-area pattern */
}

/* Reach ~30x30 without swallowing the chip: the label sits immediately to
   the left, so extend mostly right/vertical and barely at all leftward. */
.ref-chip .remove::after {
  content: '';
  position: absolute;
  inset: -7px -8px -7px -2px;
}

.msg .ref-chip {
  margin: 2px 4px 2px 0;
  vertical-align: middle;
}

.msg.user .ref-chip {
  border-color: rgba(255, 255, 255, 0.35);
  background: rgba(255, 255, 255, 0.08);
  color: var(--ink);
}

/* Harness (shell) failures: full-screen, persistent, impossible to miss.
   App-iframe errors never land here — they go to the coding agent. */
#fatal-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: #1a020c;
  color: #ffd6e2;
  padding: max(20px, env(safe-area-inset-top)) max(20px, env(safe-area-inset-right)) max(20px, env(safe-area-inset-bottom)) max(20px, env(safe-area-inset-left));
  overflow-y: auto;
  overscroll-behavior: contain; /* see #chat-log */
  display: flex;
  flex-direction: column;
  gap: 14px;
}

#fatal-overlay[hidden] { display: none; }

#fatal-overlay h2 {
  margin: 0;
  font-size: 18px;
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--red);
  text-shadow: 0 0 10px var(--red-dim);
}

.fatal-entry {
  border: 1px solid var(--red-dim);
  border-radius: 10px;
  padding: 10px 12px;
  background: rgba(255, 41, 101, 0.08);
}

.fatal-title {
  font-size: 14px;
  font-weight: 600;
  word-break: break-word;
}

.fatal-entry pre {
  margin: 8px 0 0;
  font-size: 11.5px;
  white-space: pre-wrap;
  word-break: break-word;
  color: #ffb3c8;
  font-family: var(--font-mono);
}

.fatal-actions {
  display: flex;
  gap: 10px;
  flex: none;
}

.fatal-actions button {
  border: 1px solid var(--red);
  border-radius: 10px;
  background: rgba(255, 41, 101, 0.15);
  color: #ffd6e2;
  font: inherit;
  padding: 8px 16px;
  cursor: pointer;
}

/* --------------------------------------------------------------------------
   Account button (bar) + sign-in wall
   -------------------------------------------------------------------------- */
#account-btn {
  border: 1px solid var(--line);
  background: var(--panel-2);
  color: var(--cyan);
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.02em;
  line-height: 1;
  cursor: pointer;
  padding: 7px 10px;
  border-radius: 8px;
  flex: none;
  max-width: 42vw;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
#account-btn.authed {
  color: var(--green);
  border-color: var(--green-dim);
}

#signin-overlay {
  position: fixed;
  inset: 0;
  z-index: 95; /* above the sheet + file-view, below the fatal overlay (100) */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(5, 4, 9, 0.82);
  backdrop-filter: blur(6px);
}
#signin-overlay[hidden] { display: none; }

.signin-card {
  width: min(420px, 100%);
  background: linear-gradient(180deg, var(--panel-2), var(--panel));
  border: 1px solid var(--line);
  border-radius: 16px;
  box-shadow: 0 0 40px rgba(255, 0, 255, 0.18), 0 0 22px rgba(0, 242, 255, 0.2);
  padding: 26px 22px 20px;
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.signin-card #signin-bot {
  position: static;
  margin: 0 auto;
  transform: scale(1.15);
}
#signin-headline {
  font-family: var(--font-display);
  font-size: 18px;
  line-height: 1.3;
  color: var(--ink);
  margin: 2px 0 4px;
}
#google-btn {
  border: 1px solid var(--line);
  background: var(--panel-3);
  color: var(--ink);
  font: inherit;
  font-weight: 600;
  padding: 11px 16px;
  border-radius: 10px;
  cursor: pointer;
}
#google-btn:hover { border-color: var(--cyan); }
.signin-or {
  color: var(--ink-faint);
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
#magic-form { display: flex; flex-direction: column; gap: 10px; }
#magic-email {
  border: 1px solid var(--line);
  background: var(--void);
  color: var(--ink);
  font: inherit;
  padding: 11px 14px;
  border-radius: 10px;
  outline: none;
}
#magic-email:focus { border-color: var(--cyan); box-shadow: 0 0 10px rgba(0, 242, 255, 0.3); }
.signin-msg {
  min-height: 1.1em;
  font-size: 13px;
  color: var(--green);
  margin: 0;
}
.signin-msg.error { color: var(--red); }
.signin-fine {
  font-size: 11px;
  color: var(--ink-faint);
  margin: 2px 0 0;
  line-height: 1.4;
}
#signin-close {
  border: none;
  background: none;
  color: var(--ink-dim);
  font: inherit;
  font-size: 13px;
  padding: 6px;
  cursor: pointer;
}
#signin-close:hover { color: var(--ink); }
