/* VibeAI POC — layout + theme
   Self-contained, no external fonts or frameworks. Dark by default since
   this is a coding tool.

   Theme resolution, in order:
     1. An explicit choice from Settings -> Appearance -> Theme (persisted
        as localStorage["vibeai-theme"], applied as document.documentElement's
        data-theme attribute — see index.php's inline pre-paint script and
        app.js's initThemePreference()) always wins when present.
     2. Otherwise ("Match my device", the default — same as before this
        setting existed), the OS-level prefers-color-scheme media query
        below decides, exactly as it always has.
   The bare :root block below is the dark palette (this app's long-
   standing default look), used whenever neither of the above says
   "light." */

:root {
  --bg:        #0f1115;
  --bg-raised: #161923;
  --bg-inset:  #0b0d12;
  --border:    #262b38;
  --text:      #e6e8ef;
  --text-dim:  #9aa1b4;
  --accent:    #5b8cff;
  --accent-dim:#2c3b66;
  --good:      #3ecf8e;
  --warn:      #e0a33e;
  --danger:    #e05a5a;
  --mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --radius: 10px;
  --topbar-h: 60px;
  --sidebar-w: 260px;
  --output-w: 440px;
  /* Caps how wide the chat log/composer/status bar get and centers them —
     without this they'd stretch to fill the entire middle grid column,
     which becomes uncomfortably wide once the Output column is collapsed
     (see .layout.output-collapsed below). */
  --chat-max-w: 1000px;
}

/* "Match my device" (no explicit choice made, or explicitly set back to
   auto — see app.js's applyTheme(), which removes data-theme entirely for
   "auto" rather than ever writing data-theme="auto") + the OS reports a
   light preference. :not([data-theme="dark"]) is what lets an explicit
   DARK choice override a light OS preference; there's no equivalent guard
   needed for the block below it, since an explicit data-theme="light"
   selector is simply more specific than this media-query block and wins
   on its own. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --bg:        #f5f6f9;
    --bg-raised: #ffffff;
    --bg-inset:  #eceef3;
    --border:    #dbdee6;
    --text:      #1c1f27;
    --text-dim:  #5b6172;
    --accent:    #3563e9;
    --accent-dim:#dce6ff;
  }
}

/* Explicit "Light" choice — wins regardless of OS preference (covers a
   dark-OS visitor who wants this app specifically in light mode, which
   the media query above alone could never do). Same palette as the
   prefers-color-scheme block above; kept as a literal duplicate rather
   than factored together since CSS has no clean way to share a
   declaration block across a media query and a plain selector without a
   preprocessor, and this app deliberately has no build step. */
:root[data-theme="light"] {
  --bg:        #f5f6f9;
  --bg-raised: #ffffff;
  --bg-inset:  #eceef3;
  --border:    #dbdee6;
  --text:      #1c1f27;
  --text-dim:  #5b6172;
  --accent:    #3563e9;
  --accent-dim:#dce6ff;
}

* { box-sizing: border-box; }

html, body {
  height: 100%;
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
}

body {
  display: flex;
  flex-direction: column;
}

/* ---------- topbar ---------- */

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.75rem 1.25rem;
  border-bottom: 1px solid var(--border);
  background: var(--bg-raised);
  flex-wrap: wrap;
}

.brand { display: flex; flex-direction: column; line-height: 1.2; }
.brand-mark { font-weight: 700; font-size: 1.05rem; letter-spacing: 0.02em; }
.brand-sub { font-size: 0.75rem; color: var(--text-dim); }

.topbar-controls {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
}

.model-select-wrap {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.8rem;
  color: var(--text-dim);
}

select {
  background: var(--bg-inset);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.35rem 0.5rem;
  font-size: 0.8rem;
  max-width: 220px;
}

.engine-toggle,
.output-toggle-btn,
.sessions-toggle-btn {
  background: var(--bg-inset);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.4rem 0.9rem;
  font-size: 0.78rem;
  cursor: pointer;
}
.engine-toggle:disabled { opacity: 0.6; cursor: default; }
.engine-toggle.active { border-color: var(--accent); color: var(--accent); }
.output-toggle-btn:hover, .sessions-toggle-btn:hover { border-color: var(--accent); color: var(--accent); }

.pill {
  font-size: 0.72rem;
  padding: 0.25rem 0.6rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  color: var(--text-dim);
  white-space: nowrap;
}
.pill-idle    { color: var(--text-dim); }
.pill-gpu     { color: var(--good); border-color: var(--good); }
.pill-cpu     { color: var(--warn); border-color: var(--warn); }
.pill-error   { color: var(--danger); border-color: var(--danger); }
.pill-loading { color: var(--accent); border-color: var(--accent); }
.pill-gemini  { color: var(--accent); border-color: var(--accent); }

/* Only meaningful once a side panel becomes an off-canvas drawer (see the
   max-width media query below) — hidden on desktop where both panels are
   permanently docked. */
.output-toggle-btn, .sessions-toggle-btn { display: none; }

/* ---------- layout ---------- */
/* Desktop: chats (left) and output (right) are LOCKED-width columns that
   never grow or shrink with the viewport; the chat column in the middle
   is the only one that actually flexes. */

.layout {
  flex: 1;
  display: grid;
  grid-template-columns: var(--sidebar-w) minmax(0, 1fr) var(--output-w);
  gap: 1px;
  background: var(--border);
  min-height: 0;
  position: relative;
}

.chat-pane, .output-pane, .sessions-pane {
  background: var(--bg);
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.output-pane { width: var(--output-w); min-width: var(--output-w); }
.sessions-pane { width: var(--sidebar-w); min-width: var(--sidebar-w); }

.side-backdrop { display: none; }

/* Output panel: desktop-only collapse/expand ---------------------------
   Separate from the off-canvas drawer mechanism below (that's only for
   narrow viewports, driven by .open/.side-backdrop) — this lets the
   permanently-docked desktop column itself be tucked away to give the
   chat column more room, independent of screen width. Driven by
   .layout.output-collapsed (see setOutputCollapsed() in app.js), defaulted
   per category (defaultOutputCollapsedForCategory() — General starts
   collapsed, Coding starts open) and auto-cleared the moment a reply
   actually produces something to show (renderOutput()'s own comment). */
.output-collapse-btn {
  display: none;
  align-items: center;
  background: transparent;
  color: var(--text-dim);
  border: none;
  font-size: 1rem;
  line-height: 1;
  padding: 0 0.3rem;
  cursor: pointer;
}
.output-collapse-btn:hover { color: var(--accent); }

.output-expand-tab {
  display: none;
  position: absolute;
  top: 50%;
  right: 0;
  transform: translateY(-50%);
  background: var(--bg-raised);
  color: var(--text-dim);
  border: 1px solid var(--border);
  border-right: none;
  border-radius: 8px 0 0 8px;
  padding: 0.7rem 0.4rem;
  font-size: 0.95rem;
  cursor: pointer;
  z-index: 5;
}
.output-expand-tab:hover { color: var(--accent); border-color: var(--accent); }

@media (min-width: 1101px) {
  .output-collapse-btn { display: inline-flex; }

  .layout.output-collapsed { grid-template-columns: var(--sidebar-w) minmax(0, 1fr) 0; }
  .layout.output-collapsed .output-pane {
    width: 0;
    min-width: 0;
    overflow: hidden;
    border: none;
  }
  .layout.output-collapsed .output-expand-tab { display: flex; }
}

/* Narrow viewports: neither side panel can keep a fixed column next to a
   usable chat column, so both become locked-size off-canvas drawers
   (same width as their docked form, capped at 92% of the viewport on
   small phones) instead of being squeezed into something unreadable. */
@media (max-width: 1100px) {
  .layout { grid-template-columns: 1fr; }

  .output-toggle-btn, .sessions-toggle-btn { display: inline-block; }

  .output-pane, .sessions-pane {
    position: fixed;
    top: var(--topbar-h);
    bottom: 0;
    min-width: 0;
    max-width: 92vw;
    transition: transform 0.22s ease;
    z-index: 20;
  }

  .output-pane {
    right: 0;
    width: min(var(--output-w), 92vw);
    transform: translateX(100%);
    box-shadow: -8px 0 24px rgba(0, 0, 0, 0.35);
  }
  .output-pane.open { transform: translateX(0); }

  .sessions-pane {
    left: 0;
    width: min(var(--sidebar-w), 92vw);
    transform: translateX(-100%);
    box-shadow: 8px 0 24px rgba(0, 0, 0, 0.35);
  }
  .sessions-pane.open { transform: translateX(0); }

  .side-close-btn { display: inline-flex !important; }

  .side-backdrop {
    display: block;
    position: fixed;
    inset: var(--topbar-h) 0 0 0;
    background: rgba(0, 0, 0, 0.4);
    z-index: 15;
  }
  .side-backdrop[hidden] { display: none; }
}

/* ---------- sessions (chats) sidebar ---------- */

.sessions-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.7rem 1rem;
  border-bottom: 1px solid var(--border);
  font-size: 0.82rem;
  color: var(--text-dim);
}

.new-chat-row {
  padding: 0.7rem 0.8rem;
  border-bottom: 1px solid var(--border);
  position: relative;
}

.new-chat-btn {
  width: 100%;
  background: var(--accent);
  color: #fff;
  border: none;
  border-radius: 8px;
  padding: 0.5rem;
  font-size: 0.82rem;
  font-weight: 600;
  cursor: pointer;
}

.new-chat-picker {
  margin-top: 0.5rem;
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.6rem;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}
/* .new-chat-picker's own `display: flex` above has the same specificity as
   the browser's built-in `[hidden] { display: none }` rule, and an author
   stylesheet rule always wins a tie against the user-agent stylesheet — so
   without this, the `hidden` attribute app.js toggles (on "+ New chat" /
   after picking a category) was silently ignored and the picker stayed
   visible all the time. This compound selector is more specific and wins. */
.new-chat-picker[hidden] { display: none; }
.new-chat-picker p { margin: 0 0 0.2rem; font-size: 0.72rem; color: var(--text-dim); }

.category-btn {
  background: var(--bg-inset);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.45rem 0.6rem;
  font-size: 0.8rem;
  text-align: left;
  cursor: pointer;
}
.category-btn:hover { border-color: var(--accent); color: var(--accent); }

.session-list {
  flex: 1;
  overflow-y: auto;
  padding: 0.4rem;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.session-item {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.5rem 0.6rem;
  border-radius: 8px;
  cursor: pointer;
  border: 1px solid transparent;
}
.session-item:hover { background: var(--bg-inset); }
.session-item.active { background: var(--accent-dim); border-color: var(--accent); }

.session-item-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}
.session-item-title {
  font-size: 0.82rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.session-item-meta {
  font-size: 0.68rem;
  color: var(--text-dim);
  display: flex;
  gap: 0.4rem;
  align-items: center;
}

.category-badge {
  font-size: 0.62rem;
  padding: 0.05rem 0.4rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  white-space: nowrap;
}
.category-badge.coding { color: var(--accent); border-color: var(--accent); }
.category-badge.general { color: var(--good); border-color: var(--good); }

.session-delete-btn {
  background: transparent;
  border: none;
  color: var(--text-dim);
  font-size: 1rem;
  line-height: 1;
  padding: 0.2rem 0.4rem;
  cursor: pointer;
  border-radius: 6px;
}
.session-delete-btn:hover { color: var(--danger); background: var(--bg-inset); }

.session-list-empty {
  padding: 1rem 0.6rem;
  font-size: 0.78rem;
  color: var(--text-dim);
  text-align: center;
}

/* ---------- chat ---------- */

.chat-log {
  flex: 1;
  overflow-y: auto;
  padding: 1rem 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  /* Caps and centers the whole log instead of letting it stretch edge to
     edge — most noticeable once the Output column is collapsed and this
     middle grid column has a lot more room than 88%-wide bubbles need.
     The auto side margins are what actually center a flex item that's
     narrower than its container (see .chat-composer/.status-bar below for
     the same pattern) — CSS resolves auto margins on a flex item's cross
     axis before any alignment property, so this wins regardless of
     .chat-pane's default (stretch) alignment. */
  width: 100%;
  max-width: var(--chat-max-w);
  margin: 0 auto;
}

/* Each message is a bubble (.msg) inside a wrapper (.msg-wrapper) that also
   holds its hover-reveal action row — see attachMessageActions()'s own
   comment in app.js for why the actions row has to live OUTSIDE the bubble
   rather than as one of its children. The wrapper, not the bubble, is what
   .chat-log actually stacks and aligns. */
.msg-wrapper {
  display: flex;
  flex-direction: column;
  max-width: 88%;
}
.msg-wrapper-user { align-self: flex-end; align-items: flex-end; }
.msg-wrapper-assistant { align-self: flex-start; align-items: flex-start; }
.msg-wrapper-system, .msg-wrapper-original { align-self: center; align-items: center; max-width: 100%; }

.msg {
  padding: 0.65rem 0.9rem;
  border-radius: var(--radius);
  font-size: 0.9rem;
  line-height: 1.5;
  white-space: pre-wrap;
  word-wrap: break-word;
  width: 100%;
}

.msg p { margin: 0 0 0.5em; }
.msg p:last-child { margin-bottom: 0; }

/* Hover-reveal Copy/Resend row — see attachMessageActions() in app.js.
   Always occupies its (small) space so hovering doesn't shift anything
   else, but stays invisible/inert until the message (or the row itself,
   for keyboard focus) is actually hovered or focused. */
.msg-actions {
  display: flex;
  gap: 0.3rem;
  margin-top: 0.3rem;
  opacity: 0;
  transition: opacity 0.15s ease;
}
.msg-wrapper:hover .msg-actions,
.msg-actions:focus-within {
  opacity: 1;
}
.msg-action-btn {
  background: var(--bg-inset);
  border: 1px solid var(--border);
  color: var(--text-dim);
  border-radius: 6px;
  font-size: 0.68rem;
  line-height: 1;
  padding: 0.2rem 0.45rem;
  cursor: pointer;
}
.msg-action-btn:hover { color: var(--accent); border-color: var(--accent); }

/* The "you scrolled to the top and it's still there" reminder of what this
   chat originally started from — see renderOriginalPromptBanner() and
   state.originalPrompt's own comment in app.js. Styled distinctly from a
   plain .msg-system note (dashed border + accent-colored left edge) so
   it's clearly a fixed reference point, not just another status message. */
.msg-original-prompt {
  background: var(--bg-inset);
  border: 1px dashed var(--border);
  border-left: 3px solid var(--accent);
  font-size: 0.82rem;
  color: var(--text-dim);
}
.msg-original-prompt-label {
  font-size: 0.68rem;
  font-weight: 600;
  color: var(--accent);
  margin-bottom: 0.3rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.msg-original-prompt p { margin: 0; color: var(--text); white-space: pre-wrap; }

/* Real links the assistant deliberately creates — see appendTextWithLinks()
   / MARKDOWN_LINK_RE in app.js. Every such link already carries
   target="_blank" + rel="noopener noreferrer" from the rendering code
   itself; this just makes it visually read as a link against either
   message bubble color. */
.msg a {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.msg a:hover { opacity: 0.85; }

/* Alignment (which side of .chat-log a message sits on) now lives on
   .msg-wrapper-* above, since the wrapper — not the bubble — is the actual
   flex item .chat-log stacks. These classes just style the bubble itself. */
.msg-system {
  background: var(--bg-inset);
  border: 1px dashed var(--border);
  color: var(--text-dim);
  font-size: 0.82rem;
}

.msg-user {
  background: var(--accent-dim);
  border: 1px solid var(--accent);
}

.msg-assistant {
  background: var(--bg-raised);
  border: 1px solid var(--border);
}

.msg-assistant.streaming::after {
  content: "▍";
  animation: blink 1s steps(1) infinite;
  color: var(--accent);
}

.msg-assistant.cancelled { border-color: var(--warn); }

/* Shown between hitting Send and the first token actually arriving, so
   the wait doesn't read as a hang — see the "⏳ Thinking… Ns" placeholder
   in runGeneration() (assets/js/app.js). */
.msg-assistant.thinking p {
  color: var(--text-dim);
  font-style: italic;
}

@keyframes blink { 50% { opacity: 0; } }

.msg pre {
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.7rem 0.8rem;
  overflow-x: auto;
  font-family: var(--mono);
  font-size: 0.82rem;
  margin: 0.5rem 0;
}

.chat-composer {
  border-top: 1px solid var(--border);
  padding: 0.9rem 1.25rem;
  /* Same centering as .chat-log above, so the input bar doesn't stretch
     into a very long single-line row once the Output column is
     collapsed. */
  width: 100%;
  max-width: var(--chat-max-w);
  margin: 0 auto;
}

.chat-input-row {
  display: flex;
  gap: 0.6rem;
}

.image-preview {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.6rem;
  padding: 0.4rem 0.6rem;
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 0.78rem;
  color: var(--text-dim);
}
/* Same CSS-specificity issue as .new-chat-picker[hidden] above: a plain
   author-stylesheet class selector ties with the browser's built-in
   [hidden]{display:none} rule, and author origin wins ties — so without
   this compound selector the preview stayed visible (flex) even with the
   hidden attribute set. */
.image-preview[hidden] { display: none; }
.image-preview img {
  width: 40px;
  height: 40px;
  object-fit: cover;
  border-radius: 4px;
  border: 1px solid var(--border);
}
.image-preview span {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.image-preview button {
  background: transparent;
  border: none;
  color: var(--text-dim);
  font-size: 1rem;
  cursor: pointer;
  padding: 0 0.3rem;
}
.image-preview button:hover { color: var(--danger); }

.attach-btn {
  background: var(--bg-inset);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0 0.9rem;
  height: 2.1rem;
  font-size: 0.95rem;
}
.attach-btn:disabled { opacity: 0.35; cursor: default; }
.attach-btn:not(:disabled):hover { border-color: var(--accent); color: var(--accent); }

.msg-image {
  max-width: 260px;
  max-height: 260px;
  border-radius: 8px;
  border: 1px solid var(--border);
  display: block;
  margin-bottom: 0.5rem;
}

textarea#chat-input {
  flex: 1;
  resize: vertical;
  background: var(--bg-inset);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.6rem 0.7rem;
  font-family: var(--sans);
  font-size: 0.88rem;
}
textarea#chat-input:focus { outline: 1px solid var(--accent); }

.chat-input-buttons {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

/* Holds the 📎 attach button and the 🎤 push-to-talk mic button side by
   side, above Send/Cancel — see index.php's comment-free markup and
   app.js's handleMicToggleClick(). The mic button used to live in the
   topbar next to the TTS toggle; it moved down here (both narrower,
   circular .audio-toggle-btn icons rather than full-width text buttons,
   so pairing them side by side doesn't cost much horizontal space) once
   "conversation mode" needed the topbar's 🔊 slot for its own toggle —
   voice INPUT and voice OUTPUT no longer share one row, but push-to-talk
   stays right where a visitor is already looking: next to the input box
   they're about to type or speak into. */
.chat-input-buttons-row {
  display: flex;
  gap: 0.4rem;
}

button { font-family: var(--sans); cursor: pointer; }

#chat-send {
  background: var(--accent);
  color: #fff;
  border: none;
  border-radius: 8px;
  padding: 0 1.3rem;
  height: 2.1rem;
  font-size: 0.88rem;
  font-weight: 600;
}
#chat-send:disabled { opacity: 0.5; cursor: default; }

#chat-cancel {
  background: transparent;
  color: var(--danger);
  border: 1px solid var(--danger);
  border-radius: 8px;
  padding: 0 1.3rem;
  height: 2.1rem;
  font-size: 0.82rem;
}
#chat-cancel:disabled { opacity: 0.35; cursor: default; border-color: var(--border); color: var(--text-dim); }

.status-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.75rem;
  padding: 0.5rem 1.25rem;
  border-top: 1px solid var(--border);
  font-size: 0.75rem;
  color: var(--text-dim);
  /* Same centering as .chat-log/.chat-composer above. */
  width: 100%;
  max-width: var(--chat-max-w);
  margin: 0 auto;
}

progress { flex: 1; max-width: 220px; height: 6px; }

#load-cancel-cpu {
  background: var(--bg-inset);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.25rem 0.6rem;
  font-size: 0.72rem;
  white-space: nowrap;
}
#load-cancel-cpu:hover { border-color: var(--accent); color: var(--accent); }

/* ---------- output pane ---------- */

.output-header {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.7rem 1rem;
  border-bottom: 1px solid var(--border);
  font-size: 0.82rem;
  color: var(--text-dim);
}

.output-tabs { display: flex; gap: 0.4rem; overflow-x: auto; flex: 1; }

.output-tab {
  background: var(--bg-inset);
  border: 1px solid var(--border);
  color: var(--text-dim);
  border-radius: 6px;
  padding: 0.25rem 0.6rem;
  font-size: 0.72rem;
  font-family: var(--mono);
  white-space: nowrap;
}
.output-tab.active { color: var(--accent); border-color: var(--accent); }

.side-close-btn {
  display: none;
  background: transparent;
  color: var(--text-dim);
  border: none;
  font-size: 1.2rem;
  line-height: 1;
  padding: 0 0.3rem;
}

/* Unlike .side-close-btn (a "close the off-canvas drawer" control, only
   meaningful once the output pane stops being a permanently docked desktop
   column — see that class's own comment), the 📁 Files toggle is a real
   feature switch that matters just as much on desktop, where the output
   pane is visible all the time. So it stays a plain always-visible button
   (subject only to its own `hidden` attribute, toggled by syncCodeFilesUI()
   per category) rather than following .side-close-btn's mobile-only rule. */
.output-files-toggle-btn {
  display: inline-flex;
  align-items: center;
  background: transparent;
  color: var(--text-dim);
  border: none;
  font-size: 1rem;
  line-height: 1;
  padding: 0 0.3rem;
  cursor: pointer;
}
.output-files-toggle-btn[hidden] { display: none; }
.output-files-toggle-btn:hover { color: var(--accent); }

.output-body {
  flex: 1;
  overflow: hidden;
  display: flex;
  min-height: 0;
  background: var(--bg-inset);
}

.output-mount { flex: 1; min-height: 0; min-width: 0; }
.output-mount .cm-editor { height: 100%; }

.output-actions {
  display: flex;
  gap: 0.6rem;
  padding: 0.7rem 1rem;
  border-top: 1px solid var(--border);
}

.output-actions button {
  background: var(--bg-inset);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.4rem 0.8rem;
  font-size: 0.78rem;
}
.output-actions button:disabled { opacity: 0.5; cursor: default; }
.output-actions button:not(:disabled):hover { border-color: var(--accent); color: var(--accent); }

/* ---------- output pane: per-chat code-file memory + editing ---------- */
/* See recordCodeFileVersions()/renderCodeFilesPanel()/getActiveCodeEntry()
   in app.js. Coding-category only — hidden entirely otherwise, see
   syncCodeFilesUI(). */

.output-files-panel {
  max-height: 40%;
  overflow-y: auto;
  border-bottom: 1px solid var(--border);
  background: var(--bg-inset);
}
.output-files-panel[hidden] { display: none; }

.output-files-list { display: flex; flex-direction: column; }

.output-files-empty {
  padding: 0.6rem 1rem;
  font-size: 0.74rem;
  color: var(--text-dim);
}

.output-file-row { border-bottom: 1px solid var(--border); }
.output-file-row:last-child { border-bottom: none; }

.output-file-header {
  display: flex;
  align-items: center;
  gap: 0.15rem;
  padding-right: 0.4rem;
}

.output-file-name {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  background: transparent;
  border: none;
  color: var(--text);
  text-align: left;
  padding: 0.5rem 1rem;
  font-size: 0.78rem;
  font-family: var(--mono);
  cursor: pointer;
}
.output-file-name:hover { color: var(--accent); }
.output-file-name .output-file-count {
  font-family: inherit;
  font-size: 0.68rem;
  color: var(--text-dim);
  white-space: nowrap;
}

/* Shared by the per-file delete button (in .output-file-header) and the
   per-version delete button (in .output-version-item) — see
   renderCodeFilesPanel()/deleteCodeFile()/deleteCodeFileVersion() in
   app.js. Same "small, quiet until hovered, red on hover" treatment as
   .session-delete-btn, so a delete control reads the same way everywhere
   in this app. */
.output-delete-btn {
  flex-shrink: 0;
  background: transparent;
  border: none;
  color: var(--text-dim);
  font-size: 0.8rem;
  line-height: 1;
  padding: 0.25rem 0.4rem;
  border-radius: 6px;
  cursor: pointer;
}
.output-delete-btn:hover { color: var(--danger); background: var(--bg-inset); }

.output-file-versions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  padding: 0 1rem 0.6rem;
}
.output-file-versions[hidden] { display: none; }

.output-version-item {
  display: inline-flex;
  align-items: center;
  gap: 0.1rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding-right: 0.15rem;
}
.output-version-item:has(.output-version-pill.active) { border-color: var(--accent); }

.output-version-pill {
  background: transparent;
  border: none;
  color: var(--text-dim);
  border-radius: 999px 0 0 999px;
  padding: 0.15rem 0.15rem 0.15rem 0.55rem;
  font-size: 0.68rem;
  cursor: pointer;
}
.output-version-pill:hover { color: var(--accent); }
.output-version-pill.active { color: var(--accent); }
.output-version-pill.user-edit { font-style: italic; }

.output-version-item .output-delete-btn {
  font-size: 0.66rem;
  padding: 0.1rem 0.4rem 0.1rem 0.1rem;
  border-radius: 0 999px 999px 0;
}

.output-edit-area {
  flex: 1;
  min-height: 0;
  min-width: 0;
  resize: none;
  border: none;
  outline: none;
  background: var(--bg-inset);
  color: var(--text);
  font-family: var(--mono);
  font-size: 0.82rem;
  line-height: 1.5;
  padding: 1rem;
}
.output-edit-area[hidden] { display: none; }

/* ---------- model recommendation hint ---------- */

.model-hint {
  margin: 0;
  padding: 0.4rem 1.25rem;
  font-size: 0.76rem;
  color: var(--text-dim);
  background: var(--bg-inset);
  border-bottom: 1px solid var(--border);
  min-height: 1.2em;
}

/* ---------- "Continue" (truncated response) ---------- */

.continue-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap; /* a second action button (see addContinueRow()'s extraButton) plus its label can outgrow a narrow/mobile width otherwise */
  gap: 0.6rem;
  margin-top: 0.6rem;
  padding-top: 0.6rem;
  border-top: 1px dashed var(--border);
  font-size: 0.78rem;
  color: var(--warn);
}

.continue-btn {
  background: transparent;
  color: var(--accent);
  border: 1px solid var(--accent);
  border-radius: 6px;
  padding: 0.25rem 0.7rem;
  font-size: 0.76rem;
}
.continue-btn:hover { background: var(--accent-dim); }

/* ---------- Gemini API fallback badge ---------- */

.gemini-badge {
  margin-top: 0.6rem;
  padding-top: 0.6rem;
  border-top: 1px dashed var(--border);
  font-size: 0.78rem;
  color: var(--text-dim);
}

/* ---------- audio controls (TTS voice output; mic input is a disabled
   placeholder until voice input/hands-free mode is built — see the
   README's Roadmap section) ---------- */

.audio-toggle-btn {
  position: relative;
  background: var(--bg-inset);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 999px;
  width: 2.1rem;
  height: 2.1rem;
  padding: 0;
  font-size: 1rem;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
}
.audio-toggle-btn:not(:disabled):hover { border-color: var(--accent); }
.audio-toggle-btn:disabled { cursor: default; }

/* "On" — voice output enabled and unmuted. */
.audio-toggle-btn.active { border-color: var(--good); color: var(--good); }

/* "Off"/muted — greyed out, plus a diagonal red line drawn across the
   icon so the on/off state reads at a glance rather than needing to
   notice a subtle opacity change (this is the "icon with inactive red
   line or grayed out" toggle requested for both the audio output mute
   button and the (placeholder, for now) mic input button). */
.audio-toggle-btn.muted { color: var(--text-dim); opacity: 0.65; }
.audio-toggle-btn.muted::after {
  content: "";
  position: absolute;
  left: 14%;
  right: 14%;
  top: 50%;
  height: 2px;
  margin-top: -1px;
  background: var(--danger);
  transform: rotate(-45deg);
  border-radius: 2px;
}

/* "Recording" — the mic button while a push-to-talk recording is in
   progress (see app.js's handleMicToggleClick()). Deliberately its own
   look, not the .active green the TTS toggle uses for "voice output is
   on" — a listening mic and an enabled speaker aren't the same state and
   shouldn't read as one at a glance. Reuses the existing `blink`
   keyframes (see the streaming-cursor rule above) rather than adding a
   second animation for the same "something is actively happening" cue. */
.audio-toggle-btn.recording {
  border-color: var(--danger);
  color: var(--danger);
  animation: blink 1s steps(1) infinite;
}

/* --- shared modal shell — used by the first-run onboarding wizard (see
   app.js's maybeShowOnboardingWizard()) AND the Settings dialog (see
   initSettingsModal()). A true full-viewport modal (unlike .side-backdrop
   above, which only becomes an overlay on narrow screens) — both of these
   are whole-page decisions/dialogs, not panels meant to sit beside the
   chat. Originally written just for the wizard as `.onboarding-backdrop`/
   `.onboarding-card`; generalized to `.modal-*` once Settings needed the
   exact same shell rather than a second copy of it. */
.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 50;
  background: rgba(0, 0, 0, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
}
/* Bug fix — the wizard couldn't be closed: `display: flex` above and the
   browser's own default `[hidden] { display: none }` rule are the SAME
   specificity (one class selector vs. one attribute selector), so which
   one wins is decided by cascade ORIGIN, not the `hidden` attribute's
   presence — and an author stylesheet rule (this one) always beats the
   browser's built-in default at equal specificity, `hidden` attribute or
   not. Net effect: `el.onboardingBackdrop.hidden = true` in app.js was
   setting the attribute correctly, but it never actually hid anything,
   because `display: flex` above kept winning regardless. This exact trap
   already bit `.side-backdrop` above once (see its own `[hidden]` rule) —
   same fix here: an explicit `[hidden]` rule with matching specificity to
   reassert `display: none` once the attribute is actually set. Applies to
   BOTH modals now that they share this class — worth remembering if a
   third one is ever added on top of `.modal-backdrop`. */
.modal-backdrop[hidden] { display: none; }

.modal-card {
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 1.75rem;
  max-width: 560px;
  width: 100%;
  max-height: 85vh;
  overflow-y: auto;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.35);
}

.modal-card h2 {
  margin: 0 0 0.6rem;
  font-size: 1.15rem;
}

.modal-card p {
  margin: 0 0 1.1rem;
  color: var(--text-dim);
  font-size: 0.88rem;
  line-height: 1.5;
}

.modal-secondary-btn {
  background: transparent;
  color: var(--text-dim);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.5rem 0.9rem;
  font-size: 0.82rem;
}
.modal-secondary-btn:hover { border-color: var(--accent); color: var(--accent); }

.modal-primary-btn {
  background: var(--accent);
  color: #fff;
  border: none;
  border-radius: 8px;
  padding: 0.55rem 1.4rem;
  font-size: 0.85rem;
  font-weight: 600;
}

.modal-link-btn {
  background: transparent;
  color: var(--text-dim);
  border: none;
  font-size: 0.8rem;
  text-decoration: underline;
  padding: 0.3rem 0;
}
.modal-link-btn:hover { color: var(--accent); }

/* --- onboarding wizard's own step-specific pieces (category picker, model
   list) — everything else it uses is the shared .modal-* shell above. */
.onboarding-category-choices {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  margin-bottom: 1rem;
}

.onboarding-category-btn {
  background: var(--bg-inset);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.9rem 1rem;
  font-size: 0.95rem;
  font-weight: 600;
  text-align: left;
}
.onboarding-category-btn:hover { border-color: var(--accent); color: var(--accent); }

.onboarding-model-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-bottom: 1rem;
  max-height: 40vh;
  overflow-y: auto;
}

.onboarding-model-btn {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  background: var(--bg-inset);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.7rem 0.9rem;
  text-align: left;
}
.onboarding-model-btn:hover { border-color: var(--accent); }
.onboarding-model-btn strong { font-size: 0.88rem; word-break: break-word; }
.onboarding-model-btn span { font-size: 0.78rem; color: var(--text-dim); }

.onboarding-step-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
}

/* --- Settings dialog's own pieces — see initSettingsModal() in app.js. */
.settings-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 0.5rem;
}
.settings-header h2 { margin: 0; }
.settings-header .side-close-btn { display: inline-flex !important; position: static; }

.settings-section {
  border-top: 1px solid var(--border);
  padding-top: 1rem;
  margin-top: 1rem;
}
.settings-section:first-of-type { border-top: none; padding-top: 0; margin-top: 0.5rem; }
.settings-section h3 {
  margin: 0 0 0.75rem;
  font-size: 0.85rem;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.settings-field {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  margin-bottom: 0.9rem;
  font-size: 0.85rem;
}
.settings-field > span:first-child { color: var(--text-dim); }
.settings-field select {
  max-width: none;
  width: 100%;
}

.settings-slider-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}
.settings-slider-row input[type="range"] { flex: 1; }
.settings-slider-value {
  font-size: 0.8rem;
  color: var(--text-dim);
  min-width: 2.6rem;
  text-align: right;
}

.settings-hint {
  font-size: 0.78rem;
  color: var(--text-dim);
  margin: 0.5rem 0 0;
}

.mic-test-meter-track {
  margin-top: 0.7rem;
  height: 0.5rem;
  border-radius: 999px;
  background: var(--bg-inset);
  border: 1px solid var(--border);
  overflow: hidden;
}
.mic-test-meter-fill {
  height: 100%;
  width: 0%;
  background: var(--good);
  /* No transition — this updates every animation frame while testing (see
     app.js's mic-level monitor), and a CSS transition fighting that many
     updates per second would just make the meter feel laggy instead of
     responsive. */
}

.settings-actions {
  display: flex;
  justify-content: flex-end;
  margin-top: 1.25rem;
}
