:root {
  color-scheme: light;
  --bg: #f7f7f8;
  --panel: #ffffff;
  --border: #e2e2e6;
  --text: #1f1f24;
  --muted: #6b6b76;
  --accent: #2563eb;
  --user-bubble: #e0e7ff;
  --assistant-bubble: #ffffff;
  --error: #b91c1c;
  --error-bg: #fee2e2;
  --error-border: #fecaca;
  --inline-code-bg: #eef0f4;
}

/* Dark theme: follows the OS setting unless the user picked a side — the theme toggle
   (app.js) persists an explicit data-theme on <html>, and the head script in each template
   applies a stored pick before first paint so dark users never see a light flash. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --bg: #16161b;
    --panel: #1f1f26;
    --border: #34343e;
    --text: #e7e7ee;
    --muted: #9d9dab;
    --accent: #60a5fa;
    --user-bubble: #2e3a66;
    --assistant-bubble: #26262e;
    --error: #f87171;
    --error-bg: #3b1d1f;
    --error-border: #6b2a2e;
    --inline-code-bg: #35353f;
  }
}

/* A stored dark pick also applies when the OS is light (the media query above covers
   the no-attribute and dark-attribute cases). */
:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: #16161b;
  --panel: #1f1f26;
  --border: #34343e;
  --text: #e7e7ee;
  --muted: #9d9dab;
  --accent: #60a5fa;
  --user-bubble: #2e3a66;
  --assistant-bubble: #26262e;
  --error: #f87171;
  --error-bg: #3b1d1f;
  --error-border: #6b2a2e;
  --inline-code-bg: #35353f;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  background: var(--bg);
  color: var(--text);
  display: flex;
  flex-direction: column;
  height: 100vh;
  height: var(--app-height, 100dvh); /* visual viewport height, see app.js */
  overflow: hidden; /* the app is a fixed viewport: the panels scroll internally */
}

/* Pages without the app layout (auth cards, admin) scroll as normal documents. */
body.auth, body.admin-page {
  height: auto;
  min-height: 100vh;
  min-height: var(--app-height, 100dvh);
  overflow: visible;
}

/* PWA offline banner (shown via the [hidden] attribute toggle) */
.offline-banner {
  background: var(--error);
  color: #fff;
  text-align: center;
  font-size: 0.875rem;
  padding: 0.35rem 1rem;
}

button {
  font: inherit;
  cursor: pointer;
  border: 1px solid var(--border);
  background: var(--panel);
  border-radius: 6px;
  padding: 0.35rem 0.75rem;
}

button:hover { border-color: var(--accent); }

a { color: var(--accent); }

/* Auth pages */
body.auth {
  display: grid;
  place-items: center;
}

/* Auth pages have no topbar: the theme toggle floats in the corner. */
body.auth #theme-toggle {
  position: fixed;
  top: 0.75rem;
  right: 0.75rem;
}

.auth-card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 2rem;
  width: min(24rem, 90vw);
  display: grid;
  gap: 1rem;
}

.auth-card h1 { margin: 0; }

.auth-card form { display: grid; gap: 0.75rem; }

.auth-card label { display: grid; gap: 0.25rem; font-size: 0.9rem; color: var(--muted); }

input, textarea {
  font: inherit;
  padding: 0.45rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--panel);
  color: var(--text);
}

/* Top bar */
.topbar {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.5rem 1rem;
  background: var(--panel);
  border-bottom: 1px solid var(--border);
  flex: none;
}

.topbar .brand { font-weight: 700; }
.topbar .user { color: var(--muted); }
.topbar nav { margin-left: auto; display: flex; gap: 0.75rem; align-items: center; }

.menu-toggle, .sidebar-head { display: none; }

/* Layout */
/* The single row fills the fixed-height body; minmax(0, 1fr) lets the sidebar and the
   thread shrink below their content so each scrolls in its own panel. */
.layout { display: grid; grid-template-columns: 18rem 1fr; grid-template-rows: minmax(0, 1fr); flex: 1; min-height: 0; }

/* Build version (git short SHA, baked at publish time) pinned to the bottom of the app. */
.app-footer {
  flex: 0 0 auto;
  text-align: center;
  padding: 0.15rem 0.5rem;
  font-size: 0.7rem;
  color: var(--muted);
  background: var(--panel);
  border-top: 1px solid var(--border);
}

.sidebar {
  border-right: 1px solid var(--border);
  background: var(--panel);
  padding: 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  overflow-y: auto;
}

#sidebar-backdrop {
  position: fixed;
  inset: 0;
  z-index: 30;
  background: rgb(15 15 20 / 0.4);
}

#chat-list { display: grid; gap: 0.25rem; }

.new-chat summary { cursor: pointer; font-weight: 600; }

.new-chat form {
  display: grid;
  gap: 0.5rem;
  padding: 0.6rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  margin-top: 0.25rem;
}

.new-chat label { display: grid; gap: 0.2rem; font-size: 0.85rem; color: var(--muted); min-width: 0; }

.new-chat label.reasoning-row,
.new-chat label.search-row { display: flex; align-items: center; gap: 0.4rem; color: var(--text); }

/* Long model names size the dropdowns past the narrow sidebar: grid items default to
   min-width auto, so clamp them to the form width instead. */
.new-chat select { font: inherit; padding: 0.3rem; border: 1px solid var(--border); border-radius: 6px; background: var(--panel); color: var(--text); width: 100%; max-width: 100%; min-width: 0; box-sizing: border-box; }

/* In-thread settings: the same model options as the new-chat form, collapsed above the
   messages so they never compete with the thread for space. */
.thread-settings {
  flex: none;
  background: var(--panel);
  border-bottom: 1px solid var(--border);
  padding: 0.4rem 1rem;
}

.thread-settings summary { cursor: pointer; font-weight: 600; font-size: 0.85rem; color: var(--muted); }

.thread-settings form {
  display: grid;
  gap: 0.5rem;
  padding: 0.6rem 0 0.4rem;
}

.thread-settings label { display: grid; gap: 0.2rem; font-size: 0.85rem; color: var(--muted); min-width: 0; }

.thread-settings label.reasoning-row,
.thread-settings label.search-row { display: flex; align-items: center; gap: 0.4rem; color: var(--text); }

.thread-settings select { font: inherit; padding: 0.3rem; border: 1px solid var(--border); border-radius: 6px; background: var(--panel); color: var(--text); width: 100%; max-width: 100%; min-width: 0; box-sizing: border-box; }

.settings-actions { display: flex; gap: 0.5rem; align-items: center; }

.settings-actions .delete-chat { margin-left: auto; color: var(--error); border-color: var(--error-border); }

/* Prompt-cache warning modal (thread settings): native dialog, centered. */
#cache-warning { border: 1px solid var(--border); border-radius: 10px; background: var(--panel); color: var(--text); padding: 1.25rem; max-width: min(26rem, 90vw); }

#cache-warning::backdrop { background: rgb(15 15 20 / 0.4); }

#cache-warning h2 { margin: 0 0 0.5rem; font-size: 1.05rem; }

#cache-warning p { margin: 0 0 1rem; color: var(--muted); font-size: 0.9rem; line-height: 1.5; }

#cache-warning .dialog-actions { display: flex; gap: 0.5rem; justify-content: flex-end; }

.chat-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.4rem 0.6rem;
  border-radius: 6px;
}

.chat-row:hover { background: var(--bg); }

.chat-row a { flex: 1; text-decoration: none; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.chat-row .delete-chat { border: none; background: none; color: var(--muted); padding: 0.1rem 0.4rem; }

/* Chat thread */
.chat { display: flex; flex-direction: column; min-width: 0; min-height: 0; }

#thread { display: flex; flex-direction: column; height: 100%; }

#messages {
  flex: 1;
  overflow-y: auto;
  padding: 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.msg {
  position: relative;
  max-width: min(48rem, 85%);
  padding: 0.6rem 0.9rem;
  border-radius: 10px;
  line-height: 1.45;
  overflow-wrap: break-word;
}

.msg-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.25rem;
  margin-top: 0.35rem;
  opacity: 0;
  transition: opacity 0.15s;
}

.msg:hover .msg-actions { opacity: 1; }

.msg-actions button {
  border: none;
  background: none;
  color: var(--muted);
  padding: 0 0.35rem;
  font-size: 0.9em;
  cursor: pointer;
}

.msg.edit-form {
  display: grid;
  gap: 0.5rem;
  background: var(--panel);
  border: 1px solid var(--border);
}

.msg.edit-form textarea { width: 100%; resize: vertical; }

.edit-actions { display: flex; gap: 0.5rem; }

.msg p { margin: 0 0 0.5rem; }
.msg p:last-child { margin-bottom: 0; }
.msg pre { background: #0f172a; color: #e2e8f0; padding: 0.6rem; border-radius: 6px; overflow-x: auto; }
.msg code { background: var(--inline-code-bg); border-radius: 4px; padding: 0.1rem 0.3rem; font-size: 0.9em; }
.msg pre code { background: none; padding: 0; }
.msg ul, .msg ol { margin: 0.3rem 0; }
.msg-content table { width: 100%; border-collapse: collapse; margin: 0.5rem 0; font-size: 0.9em; }
.msg-content th, .msg-content td { border: 1px solid var(--border); padding: 0.4rem 0.6rem; text-align: left; }
.msg-content th { background: var(--panel); font-weight: 600; }
.msg-content svg { max-width: 100%; height: auto; display: block; margin: 0.5rem 0; }

/* SVG wrapper with download/expand overlay (Markdown.cs WrapSvg) */
.msg-content .svg-wrap {
  position: relative;
  display: block;
  margin: 0.6rem 0;
  border-radius: 8px;
}
.msg-content .svg-wrap > svg {
  margin: 0;
  width: 100%;
  height: auto;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--panel);
}
.svg-actions {
  position: absolute;
  top: 8px;
  right: 8px;
  display: flex;
  gap: 6px;
  opacity: 0;
  transition: opacity 0.15s ease;
  pointer-events: none;
}
.svg-wrap:hover .svg-actions,
.svg-wrap:focus-within .svg-actions {
  opacity: 1;
  pointer-events: auto;
}
@media (hover: none) {
  .svg-actions { opacity: 1; pointer-events: auto; }
}
.svg-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--panel);
  color: var(--text);
  box-shadow: 0 1px 6px rgba(0, 0, 0, 0.15);
  cursor: pointer;
}
.svg-btn:hover { border-color: var(--accent); background: var(--bg); }
.svg-btn svg { width: 16px; height: 16px; display: block; margin: 0; }

.msg.user { align-self: flex-end; background: var(--user-bubble); }
.msg.assistant { align-self: flex-start; background: var(--assistant-bubble); border: 1px solid var(--border); }
.msg.streaming { color: var(--muted); }
.msg.error { align-self: flex-start; background: var(--error-bg); color: var(--error); border: 1px solid var(--error-border); }

/* Braille spinner (D18): pi's 10 frames ⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏ at 80 ms/frame, redrawn as an
   SVG dot-grid sprite. The old text-strip stepped in ch units of the primary monospace
   font, so any braille fallback font with different glyph metrics (Segoe UI Symbol,
   Noto Sans Symbols, mobile fonts) misaligned the frames and the 1ch clip showed only a
   sliver of each glyph. A sprite has no font dependency: the mask is sized to 1000%
   (10x the 0.6em box) and stepped 9 times via mask-position, so each frame is exactly
   0.6em wide on every platform. currentColor makes the dots inherit the text color.
   Static frame under prefers-reduced-motion. */
.turn-spinner {
  display: inline-block;
  width: 0.6em;
  height: 1.2em;
  vertical-align: -0.15em;
  background-color: currentColor;
  mask: url("data:image/svg+xml,%3Csvg xmlns%3D%22http%3A//www.w3.org/2000/svg%22 viewBox%3D%220 0 60 12%22%3E%3Cg fill%3D%22%23000%22%3E%3Ccircle cx%3D%221.5%22 cy%3D%222%22 r%3D%221.1%22/%3E%3Ccircle cx%3D%221.5%22 cy%3D%226%22 r%3D%221.1%22/%3E%3Ccircle cx%3D%224.5%22 cy%3D%222%22 r%3D%221.1%22/%3E%3Ccircle cx%3D%227.5%22 cy%3D%222%22 r%3D%221.1%22/%3E%3Ccircle cx%3D%2210.5%22 cy%3D%222%22 r%3D%221.1%22/%3E%3Ccircle cx%3D%2210.5%22 cy%3D%226%22 r%3D%221.1%22/%3E%3Ccircle cx%3D%2213.5%22 cy%3D%222%22 r%3D%221.1%22/%3E%3Ccircle cx%3D%2216.5%22 cy%3D%222%22 r%3D%221.1%22/%3E%3Ccircle cx%3D%2216.5%22 cy%3D%226%22 r%3D%221.1%22/%3E%3Ccircle cx%3D%2216.5%22 cy%3D%2210%22 r%3D%221.1%22/%3E%3Ccircle cx%3D%2222.5%22 cy%3D%222%22 r%3D%221.1%22/%3E%3Ccircle cx%3D%2222.5%22 cy%3D%226%22 r%3D%221.1%22/%3E%3Ccircle cx%3D%2222.5%22 cy%3D%2210%22 r%3D%221.1%22/%3E%3Ccircle cx%3D%2225.5%22 cy%3D%2210%22 r%3D%221.1%22/%3E%3Ccircle cx%3D%2228.5%22 cy%3D%222%22 r%3D%221.1%22/%3E%3Ccircle cx%3D%2228.5%22 cy%3D%226%22 r%3D%221.1%22/%3E%3Ccircle cx%3D%2228.5%22 cy%3D%2210%22 r%3D%221.1%22/%3E%3Ccircle cx%3D%2231.5%22 cy%3D%2210%22 r%3D%221.1%22/%3E%3Ccircle cx%3D%2234.5%22 cy%3D%226%22 r%3D%221.1%22/%3E%3Ccircle cx%3D%2234.5%22 cy%3D%2210%22 r%3D%221.1%22/%3E%3Ccircle cx%3D%2237.5%22 cy%3D%226%22 r%3D%221.1%22/%3E%3Ccircle cx%3D%2237.5%22 cy%3D%2210%22 r%3D%221.1%22/%3E%3Ccircle cx%3D%2240.5%22 cy%3D%2210%22 r%3D%221.1%22/%3E%3Ccircle cx%3D%2243.5%22 cy%3D%222%22 r%3D%221.1%22/%3E%3Ccircle cx%3D%2243.5%22 cy%3D%226%22 r%3D%221.1%22/%3E%3Ccircle cx%3D%2243.5%22 cy%3D%2210%22 r%3D%221.1%22/%3E%3Ccircle cx%3D%2246.5%22 cy%3D%2210%22 r%3D%221.1%22/%3E%3Ccircle cx%3D%2249.5%22 cy%3D%222%22 r%3D%221.1%22/%3E%3Ccircle cx%3D%2249.5%22 cy%3D%226%22 r%3D%221.1%22/%3E%3Ccircle cx%3D%2249.5%22 cy%3D%2210%22 r%3D%221.1%22/%3E%3Ccircle cx%3D%2255.5%22 cy%3D%222%22 r%3D%221.1%22/%3E%3Ccircle cx%3D%2255.5%22 cy%3D%226%22 r%3D%221.1%22/%3E%3Ccircle cx%3D%2258.5%22 cy%3D%222%22 r%3D%221.1%22/%3E%3Ccircle cx%3D%2258.5%22 cy%3D%226%22 r%3D%221.1%22/%3E%3C/g%3E%3C/svg%3E") 0 0 / 1000% 100% no-repeat;
  animation: turn-spin 800ms steps(9) infinite;
}

@keyframes turn-spin {
  to { mask-position: 100% 0; }
}

@media (prefers-reduced-motion: reduce) {
  .turn-spinner { animation: none; }
}

/* Muted "Retrying…" note inside the streaming bubble while a mid-stream retry runs (D8). */
.retry-note {
  font-style: italic;
  opacity: 0.85;
}

/* A2UI clarifying form surface: the vendored renderer mounts inside the questions bubble
   (D8). Width/spacing only — the renderer brings its own component styles. */
.a2ui-surface {
  width: min(100%, 30rem);
  margin-top: 0.75rem;
  padding: 0.25rem 0;
}

.a2ui-error { color: var(--error); margin-top: 0.5rem; font-size: 0.9rem; }

.empty { color: var(--muted); padding: 2rem 1rem; }

.composer {
  display: flex;
  gap: 0.5rem;
  padding: 0.75rem;
  border-top: 1px solid var(--border);
  background: var(--panel);
}

/* Ghost-text hint (D2): the textarea and the hint share one grid cell, so the hint can
   never stretch the composer row — the textarea anchors the cell height. The hint matches
   the textarea's text metrics (padding + transparent border replace the textarea's real
   border) and paints the suggestion behind it; pointer-events: none keeps typing and
   selection on the textarea. */
.composer-field {
  flex: 1;
  min-width: 0;
  display: grid;
}

.composer-field textarea,
.completion-hint {
  grid-area: 1 / 1;
  width: 100%;
  min-width: 0;
}

.composer-field textarea {
  background: transparent;
}

.completion-hint {
  height: 100%;
  overflow: hidden;
  white-space: pre-wrap;
  overflow-wrap: break-word;
  pointer-events: none;
  font: inherit;
  line-height: inherit;
  color: var(--muted);
  padding: 0.45rem 0.6rem;
  border: 1px solid transparent;
}

/* Autogrow (D7): the JS sets the height; the textarea scrolls once it reaches the cap. */
.composer textarea[data-autogrow],
.msg.edit-form textarea[data-autogrow] {
  resize: none;
  overflow-y: auto;
}

.error { color: var(--error); min-height: 1.2em; margin: 0; }

/* Admin */
.admin { max-width: 48rem; margin: 0 auto; padding: 1.5rem; display: grid; gap: 1rem; }

.admin table { width: 100%; border-collapse: collapse; background: var(--panel); }

.admin th, .admin td { text-align: left; padding: 0.5rem 0.75rem; border-bottom: 1px solid var(--border); }

#create-user { display: flex; gap: 0.5rem; flex-wrap: wrap; }

/* Mobile */
@media (max-width: 767px) {
  .menu-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    padding: 0;
    font-size: 1.1rem;
  }

  .topbar { gap: 0.5rem; flex-wrap: wrap; }
  .topbar .user { display: none; }

  .layout { grid-template-columns: 1fr; }

  .sidebar {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    width: min(18rem, 85vw);
    z-index: 40;
    transform: translateX(-105%);
    transition: transform 0.2s ease;
    border-right: 1px solid var(--border);
  }

  body.sidebar-open .sidebar {
    transform: translateX(0);
    box-shadow: 2px 0 12px rgb(0 0 0 / 0.2);
  }

  .sidebar-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    font-weight: 600;
    padding-bottom: 0.25rem;
  }

  .sidebar-head button {
    border: none;
    background: none;
    font-size: 1.1rem;
    color: var(--muted);
    padding: 0.25rem 0.5rem;
  }

  .msg { max-width: 92%; }
}

/* Touch devices have no hover to reveal actions */
@media (hover: none) {
  .msg-actions { opacity: 1; }
  .msg-actions button { padding: 0.35rem; }
}
