/* ─────────────────────────────  tokens  ───────────────────────────── */
:root {
  --bg: #0c0e12;
  --bg-1: #12151b;
  --bg-2: #171b23;
  --bg-3: #1d222c;
  --border: #242a35;
  --border-soft: #1d222b;
  --text: #e7eaf0;
  --muted: #8d97a8;
  --muted-2: #6b7484;
  --accent: #7aa2f7;
  --accent-dim: #3d5c96;
  --violet: #a48cf2;
  --ok: #62d393;
  --err: #ef6b74;
  --warn: #e0b168;
  --sb-w: 284px;
  --insp-w: 340px;
  --radius: 12px;
  --sans: ui-sans-serif, system-ui, "Segoe UI", Roboto, Inter, "Helvetica Neue", sans-serif;
  --mono: ui-monospace, "JetBrains Mono", "SF Mono", Menlo, Consolas, monospace;

  /* Derived surfaces. Everything below --bg is mixed from --bg rather than written out as a hex, so a
     persona theme that only changes the eight or so colours above still repaints these correctly.
     That is the whole point: a literal #0a0c10 code block in a plum theme is how a theme stops being
     a theme. --well is where text the app did not write goes (code, tool output); --wash is weather. */
  --sel: color-mix(in srgb, var(--accent) 38%, transparent);
  --scroll: color-mix(in srgb, var(--muted) 32%, transparent);
  --well: color-mix(in srgb, var(--bg) 52%, #000);
  --well-2: color-mix(in srgb, var(--bg) 68%, #000);
  --code-bg: color-mix(in srgb, var(--bg-3) 90%, var(--accent) 10%);
  --btn-hover: color-mix(in srgb, var(--bg-3) 88%, var(--text) 12%);
  --btn-hover-border: color-mix(in srgb, var(--border) 78%, var(--text) 22%);
  --primary-hover: color-mix(in srgb, var(--accent) 82%, #fff);
  --on-accent: color-mix(in srgb, var(--bg) 45%, #000);
  --wash: radial-gradient(115% 55% at 50% 0%, color-mix(in srgb, var(--accent) 9%, transparent) 0%, transparent 62%);
}

/* Persona brand colours, fixed on purpose. A Bog line has to stay moss-green while Mochi's plum theme
   is up, because who said something comes off the message and not off whatever is selected now — the
   same reason the speaker name is stamped at write time. Any element can carry data-persona; --pc is
   what its name and face are painted with. */
[data-persona='bog'] {
  --pc: #a3c76d;
  --pc-fg: #cfe7a3;
  --pc-1: #2d3d1d;
  --pc-2: #19240f;
  --pc-bd: #43592c;
}
[data-persona='mochi'] {
  --pc: #ff8cc0;
  --pc-fg: #ffc9de;
  --pc-1: #4b2142;
  --pc-2: #2c1630;
  --pc-bd: #703a5e;
}

/* Persona themes. One block per persona, tokens only — the persona picked in Settings lands on
   <html data-theme> and that is the entire repaint, because every component below reads these, including
   the derived ones (--well, --code-bg, --sel) that are mixed out of these rather than written as hex.
   Faces and name colours are the one exception and live with the messages, keyed off data-persona: they
   belong to whoever said the line, not to whoever is selected now. */
[data-theme='bog'] {
  --bg: #0d1009;
  --bg-1: #141a10;
  --bg-2: #1b2214;
  --bg-3: #232c1a;
  --border: #2e3a22;
  --border-soft: #252f1c;
  --text: #e6e8da;
  --muted: #95a083;
  --muted-2: #727c63;
  --accent: #a3c76d;
  --accent-dim: #55713c;
  --violet: #c2b06b;
  --ok: #86bd6a;
  --warn: #d8b26a;
  /* Murk at both ends of the transcript: light coming through the wrong kind of trees, and the water
     the whole conversation is slowly sinking into. */
  --wash:
    radial-gradient(100% 45% at 18% 0%, rgba(140, 180, 80, 0.09) 0%, transparent 60%),
    radial-gradient(90% 42% at 86% 100%, rgba(45, 90, 45, 0.16) 0%, transparent 66%);
}

[data-theme='mochi'] {
  --bg: #1b1120;
  --bg-1: #241729;
  --bg-2: #2d1d34;
  --bg-3: #37253f;
  --border: #452c51;
  --border-soft: #38233f;
  --text: #fbeef8;
  --muted: #bb9dc7;
  --muted-2: #9a7ba6;
  --accent: #ff8cc0;
  --accent-dim: #a94c80;
  --violet: #cba2ff;
  --ok: #7fe0b0;
  --err: #ff7aa0;
  /* The same two washes as blush and frosting. */
  --wash:
    radial-gradient(100% 45% at 80% 0%, rgba(255, 150, 200, 0.10) 0%, transparent 60%),
    radial-gradient(90% 42% at 10% 100%, rgba(200, 130, 255, 0.10) 0%, transparent 66%);
}

* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; }
body {
  background: var(--bg);
  color: var(--text);
  font: 15px/1.6 var(--sans);
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
}
button, input, select, textarea { font: inherit; color: inherit; }
::selection { background: var(--sel); }
* { scrollbar-width: thin; scrollbar-color: var(--scroll) transparent; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-thumb { background: var(--scroll); border-radius: 8px; border: 3px solid transparent; background-clip: content-box; }

/* ─────────────────────────────  layout  ───────────────────────────── */
.app {
  display: grid;
  grid-template-columns: var(--sb-w) minmax(0, 1fr) var(--insp-w);
  /* minmax(0,1fr) instead of the implicit auto row: a grid row sized by `auto` grows to its
     content's minimum height, so one long reply stretched the whole page past the viewport
     and pushed the composer off-screen with nothing left to scroll. */
  grid-template-rows: minmax(0, 1fr);
  height: 100dvh;
  overflow: hidden;
  transition: grid-template-columns 0.16s ease;
}
/* The collapse state lives on <html> rather than on .app so that a script in <head> can restore it
   from localStorage before the first paint; otherwise every reload draws the sidebar open and then
   snaps it shut. Custom properties inherit, so setting them on the root still reaches .app's grid.
   The selectors are unprefixed because the class means one thing wherever it is. */
.no-sidebar { --sb-w: 0px; }
.no-inspector { --insp-w: 0px; }

/* Explicit tracks. Below 1120px the panels become position:fixed and therefore leave the grid
   flow — with auto-placement that slid the chat into the collapsed first track (0px wide). */
.sidebar { grid-column: 1; grid-row: 1; }
.chat { grid-column: 2; grid-row: 1; }
.inspector { grid-column: 3; grid-row: 1; }

/* ─────────────────────────────  buttons  ──────────────────────────── */
.btn {
  background: var(--bg-3);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 9px;
  padding: 7px 12px;
  cursor: pointer;
  font-size: 14px;
  line-height: 1.2;
  transition: background 0.12s, border-color 0.12s, opacity 0.12s;
}
.btn:hover { background: var(--btn-hover); border-color: var(--btn-hover-border); }
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
.btn.primary { background: var(--accent); border-color: transparent; color: var(--on-accent); font-weight: 600; }
.btn.primary:hover { background: var(--primary-hover); }
.btn.ghost { background: transparent; }
.btn.ghost:hover { background: var(--bg-2); }
.btn.small { padding: 4px 9px; font-size: 12.5px; }
.btn.block { width: 100%; }
.icon-btn {
  background: transparent;
  border: 1px solid transparent;
  color: var(--muted);
  width: 30px;
  height: 30px;
  border-radius: 8px;
  cursor: pointer;
  font-size: 15px;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.icon-btn:hover { background: var(--bg-2); color: var(--text); }
.only-hidden { display: none; }

/* The way back in. The collapse button lives *inside* the sidebar, so hiding the sidebar hides it with
   everything else, and on a wide screen the header expander used to stay display:none — collapse the
   chat list and there was nothing left to click. Below 1120px the panel is a drawer and `.only-hidden`
   above already shows it. The inspector's toggle has always lived in the header, outside its own panel,
   which is why hiding the right side was never a trap and the left side was. */
.no-sidebar #btn-show-sidebar { display: inline-flex; }

/* ─────────────────────────────  sidebar  ──────────────────────────── */
.sidebar {
  background: var(--bg-1);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  min-width: 0;
  overflow: hidden;
}
.sidebar > * { width: var(--sb-w, 284px); min-width: 0; }
.no-sidebar .sidebar > * { width: 284px; }
.sidebar-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 12px 8px 14px;
}
.brand {
  display: flex;
  align-items: center;
  gap: 9px;
  font-weight: 650;
  letter-spacing: 0.2px;
  white-space: nowrap;
}
.brand-mark {
  width: 18px;
  height: 18px;
  border-radius: 6px;
  background: linear-gradient(140deg, var(--accent), var(--violet));
  box-shadow: 0 0 0 1px #ffffff14 inset;
  flex: none;
}
#conv-search {
  width: 100%;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: 9px;
  padding: 7px 10px;
  font-size: 13.5px;
  color-scheme: dark;
}
#conv-search:focus { outline: none; border-color: var(--accent-dim); }
.search-field { padding: 10px 12px 6px; }
.sidebar .btn.primary { margin: 4px 12px 0; width: calc(var(--sb-w) - 24px); }
.conv-list { flex: 1; overflow-y: auto; padding: 6px 8px 12px; }
.conv-group {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted-2);
  padding: 12px 8px 5px;
}
.conv {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 8px 9px;
  border-radius: 10px;
  cursor: pointer;
  border: 1px solid transparent;
  position: relative;
}
.conv:hover { background: var(--bg-2); }
.conv.active { background: var(--bg-3); border-color: var(--border); }
.conv-main { min-width: 0; flex: 1; }
.conv-title {
  font-size: 13.5px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.conv-preview {
  font-size: 12px;
  color: var(--muted-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-top: 1px;
}
.conv-actions { display: flex; gap: 2px; opacity: 0; }
.conv:hover .conv-actions, .conv.menu-open .conv-actions { opacity: 1; }
.conv-actions .icon-btn { width: 24px; height: 24px; font-size: 13px; }
.conv input.rename {
  width: 100%;
  background: var(--bg);
  border: 1px solid var(--accent-dim);
  border-radius: 6px;
  padding: 3px 6px;
  font-size: 13.5px;
}
.empty-note { color: var(--muted-2); font-size: 13px; padding: 14px 10px; }
.sidebar-foot { border-top: 1px solid var(--border-soft); padding: 9px 14px; }
.status { display: flex; align-items: center; gap: 7px; font-size: 12px; color: var(--muted); }
.status .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--muted-2); flex: none; }
.status.ok .dot { background: var(--ok); }
.status.bad .dot { background: var(--err); }
.status .status-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ─────────────────────────────  chat column  ──────────────────────── */
.chat {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0; /* grid items default to min-height:auto, i.e. "never smaller than my content" */
  position: relative;
  background: var(--wash);
}
.chat-head {
  display: flex;
  flex: none;
  align-items: center;
  gap: 10px;
  padding: 10px 16px;
  border-bottom: 1px solid var(--border);
  background: color-mix(in srgb, var(--bg) 86%, transparent);
  backdrop-filter: blur(8px);
}
.chat-title-wrap { min-width: 0; }
.chat-title {
  font-size: 14.5px;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 46vw;
  cursor: text;
}
.chat-title[contenteditable='true'] { outline: 1px solid var(--accent-dim); border-radius: 6px; padding: 0 4px; }
.chat-sub { font-size: 11.5px; color: var(--muted-2); }
.spacer { flex: 1; }
.chip {
  font-size: 11.5px;
  color: var(--muted);
  border: 1px solid var(--border);
  background: var(--bg-1);
  border-radius: 999px;
  padding: 3px 10px;
  max-width: 260px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.messages-wrap { position: relative; flex: 1 1 auto; min-height: 0; }
/* height:100% is what gives overflow-y anything to overflow: without a definite height this
   box simply grew with the transcript and no scrollbar ever appeared */
.messages {
  height: 100%;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 8px 20px 40px;
  scroll-behavior: smooth;
}
.thread { max-width: 820px; margin: 0 auto; }

.jump-btn {
  position: absolute;
  right: 24px;
  bottom: 14px;
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 5px 12px;
  font-size: 12px;
  cursor: pointer;
  box-shadow: 0 6px 20px #0008;
}

/* welcome */
.welcome { max-width: 680px; margin: 8vh auto 0; text-align: center; color: var(--muted); }
.welcome h1 { font-size: 24px; color: var(--text); margin: 0 0 6px; font-weight: 650; }
.welcome p { margin: 0 0 22px; }
.suggestions { display: grid; gap: 10px; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }
.suggestion {
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 12px 14px;
  text-align: left;
  cursor: pointer;
  font-size: 13.5px;
  color: var(--text);
}
.suggestion:hover { border-color: var(--accent-dim); background: var(--bg-2); }
.suggestion b { display: block; font-size: 12.5px; color: var(--accent); margin-bottom: 3px; }

/* ─────────────────────────────  messages  ─────────────────────────── */
.msg { display: flex; gap: 12px; padding: 14px 0; align-items: flex-start; }
.avatar {
  width: 26px;
  height: 26px;
  border-radius: 8px;
  flex: none;
  display: grid;
  place-items: center;
  font-size: 11px;
  font-weight: 700;
  margin-top: 2px;
  background: var(--bg-3);
  color: var(--muted);
  border: 1px solid var(--border);
}
.msg.assistant .avatar { background: linear-gradient(140deg, #2d3f66, #3b2f63); color: #cdd9ff; border-color: #3a445e; }
.msg.user .avatar { background: color-mix(in srgb, var(--bg-2) 80%, var(--text) 8%); }
/* A face, when the persona has one. The gradient comes from --pc-1/-2 so the disc is the speaker's,
   whichever theme happens to be on screen; without a data-persona it falls back to the old blue. */
.avatar.has-face { padding: 3px; background: linear-gradient(150deg, var(--pc-1, #2d3f66), var(--pc-2, #3b2f63)); color: var(--pc-fg, #cdd9ff); border-color: var(--pc-bd, #3a445e); }
.avatar.has-face svg { display: block; width: 100%; height: 100%; }
.body { min-width: 0; flex: 1; }
.who { font-size: 11.5px; color: var(--muted-2); margin-bottom: 3px; display: flex; gap: 8px; align-items: center; }
/* The name reads as a name: brighter and heavier than the timing next to it, and in the speaker's own
   colour when it is one of the ones with a face. */
.speaker { color: var(--pc, var(--muted)); font-weight: 600; }
.bubble { min-width: 0; }
.msg.user .bubble {
  background: var(--bg-2);
  border: 1px solid var(--border-soft);
  border-radius: 12px;
  padding: 9px 13px;
  white-space: pre-wrap;
  word-break: break-word;
}
.msg.system-note .bubble { font-size: 12.5px; color: var(--muted-2); font-style: italic; }

.msg-actions { display: flex; gap: 2px; margin-top: 4px; opacity: 0; transition: opacity 0.12s; }
.msg:hover .msg-actions { opacity: 1; }
.msg-actions .icon-btn { width: 25px; height: 25px; font-size: 12.5px; }
.meta-line { font-size: 11px; color: var(--muted-2); }

/* prose */
.prose { word-break: break-word; }
.prose > :first-child { margin-top: 0; }
.prose > :last-child { margin-bottom: 0; }
.prose p { margin: 0 0 10px; }
.prose h1, .prose h2, .prose h3, .prose h4 { margin: 18px 0 8px; line-height: 1.3; font-weight: 650; }
.prose h1 { font-size: 20px; }
.prose h2 { font-size: 17.5px; }
.prose h3 { font-size: 15.5px; }
.prose h4 { font-size: 14.5px; }
.prose ul, .prose ol { margin: 0 0 10px; padding-left: 22px; }
.prose li { margin: 3px 0; }
.prose li > ul, .prose li > ol { margin: 3px 0 0; }
.prose a { color: var(--accent); text-decoration: none; }
.prose a:hover { text-decoration: underline; }
.prose hr { border: 0; border-top: 1px solid var(--border); margin: 16px 0; }
.prose blockquote {
  margin: 0 0 10px;
  padding: 2px 0 2px 12px;
  border-left: 3px solid var(--border);
  color: var(--muted);
}
.prose code {
  font-family: var(--mono);
  font-size: 0.88em;
  background: var(--code-bg);
  padding: 1.5px 5px;
  border-radius: 5px;
}
.prose table { border-collapse: collapse; margin: 0 0 12px; font-size: 13.5px; display: block; overflow-x: auto; }
.prose th, .prose td { border: 1px solid var(--border); padding: 6px 10px; vertical-align: top; }
.prose th { background: var(--bg-2); }
.codeblock { margin: 0 0 12px; border: 1px solid var(--border); border-radius: 10px; overflow: hidden; background: var(--well); }
.codeblock-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 4px 8px 4px 12px;
  background: var(--well-2);
  border-bottom: 1px solid var(--border);
  font-size: 11.5px;
  color: var(--muted-2);
}
.codeblock .lang { font-family: var(--mono); }
.copy-btn {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--muted);
  border-radius: 6px;
  padding: 2px 8px;
  font-size: 11.5px;
  cursor: pointer;
}
.copy-btn:hover { color: var(--text); border-color: var(--accent-dim); }
.prose pre { margin: 0; padding: 12px 14px; overflow-x: auto; }
.prose pre code { background: none; padding: 0; font-size: 13px; line-height: 1.55; }

/* thinking */
.reasoning { margin: 0 0 10px; border: 1px dashed var(--border); border-radius: 10px; background: var(--well-2); }
.reasoning > summary {
  cursor: pointer;
  padding: 6px 12px;
  font-size: 12px;
  color: var(--muted-2);
  list-style: none;
}
.reasoning > summary::-webkit-details-marker { display: none; }
.reasoning > summary::before { content: '✦ '; color: var(--violet); }
.reasoning[open] > summary { border-bottom: 1px solid var(--border-soft); }
.reasoning .think { padding: 8px 14px; font-size: 13px; color: var(--muted); white-space: pre-wrap; }
/* Settings → "Show model reasoning / thinking" off. Hidden, not deleted: ticking it again brings
   back the thinking of messages already stored. */
body.hide-reasoning .reasoning { display: none !important; }

/* A turn's tool rounds, folded. The work is worth keeping and not worth scrolling past to reach the
   answer, so it collapses to one line that says how much work happened and what failed. */
.work { border: 1px solid var(--border); border-radius: 12px; background: var(--bg-1); margin: 8px 0 14px; }
.work > .work-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 9px 12px;
  cursor: pointer;
  font-size: 12.5px;
  color: var(--muted-2);
  user-select: none;
  list-style: none;
}
.work > .work-head::-webkit-details-marker { display: none; }
.work > .work-head:hover { color: var(--text); }
.work .work-caret { color: var(--muted-2); transition: transform 0.12s; }
.work[open] .work-caret { transform: rotate(90deg); }
.work .work-label { font-family: var(--mono); font-size: 12px; }
.work .work-warn { color: var(--err); }
.work-body { display: none; padding: 0 12px 4px; border-top: 1px solid var(--border); }
.work[open] .work-body { display: block; }
.work-body .msg { padding: 10px 0; }
.work-body .msg + .msg { border-top: 1px dashed var(--border-soft); }

/* tool cards */
.tools { display: flex; flex-direction: column; gap: 8px; margin: 0 0 10px; }
.tool-card { border: 1px solid var(--border); border-radius: 10px; background: var(--bg-1); overflow: hidden; }
.tool-card.err { border-color: #4a2b30; }
.tc-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 10px;
  cursor: pointer;
  font-size: 13px;
  user-select: none;
}
.tc-head:hover { background: var(--bg-2); }
.tc-name { font-family: var(--mono); font-size: 12.5px; }
.tc-state { margin-left: auto; display: flex; align-items: center; gap: 8px; font-size: 11.5px; color: var(--muted-2); }
.tc-caret { transition: transform 0.12s; color: var(--muted-2); }
.tool-card[open] .tc-caret { transform: rotate(90deg); }
.tc-body { display: none; border-top: 1px solid var(--border); padding: 9px 11px; background: var(--well); }
.tool-card[open] .tc-body { display: block; }
.tc-section-title { font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.07em; color: var(--muted-2); margin: 6px 0 3px; }
.tc-body pre { margin: 0; padding: 8px 10px; background: var(--well-2); border-radius: 7px; overflow-x: auto; font-family: var(--mono); font-size: 12px; line-height: 1.5; white-space: pre-wrap; word-break: break-word; }
.badge {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  border-radius: 5px;
  padding: 1.5px 6px;
  border: 1px solid var(--border);
  color: var(--muted);
}
.badge.stub { color: var(--warn); border-color: #4a3c22; background: #1c1710; }
.badge.ok { color: var(--ok); border-color: #24483a; background: #10201a; }
.badge.err { color: var(--err); border-color: #4a2b30; background: #1d1114; }
.spin {
  width: 11px;
  height: 11px;
  border: 2px solid var(--bg-3);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
  flex: none;
}
@keyframes spin { to { transform: rotate(360deg); } }
.caret-blink { display: inline-block; width: 7px; height: 15px; background: var(--accent); vertical-align: -2px; animation: blink 1s steps(2, start) infinite; }
@keyframes blink { to { visibility: hidden; } }

/* ─────────────────────────────  composer  ─────────────────────────── */
.composer {
  flex: none; /* the transcript gives way, never the reply box */
  padding: 10px 20px 12px;
  border-top: 1px solid var(--border);
  background: var(--bg);
}
.composer-box {
  max-width: 820px;
  margin: 0 auto;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--bg-1);
  padding: 6px 8px 6px;
  transition: border-color 0.12s;
}
.composer-box:focus-within { border-color: var(--accent-dim); }
#input {
  width: 100%;
  background: transparent;
  border: 0;
  resize: none;
  padding: 8px 8px 4px;
  max-height: 42vh;
  overflow-y: auto;
  outline: none;
}
.composer-bar { display: flex; align-items: center; gap: 8px; padding: 2px 4px 2px; }
.hint { font-size: 11.5px; color: var(--muted-2); flex: 1; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.composer-note { text-align: center; font-size: 11px; color: var(--muted-2); margin-top: 6px; }

/* ─────────────────────────────  attachments  ────────────────────────────
   A file in the composer and a file in a bubble are the same object: the server holds the bytes and the
   page only ever has a link to them, so one set of sizes here is used twice. Everything is mixed from the
   tokens, because a plum-themed chat with a blue paperclip is a theme with a hole in it. */
.att-tray { display: flex; flex-wrap: wrap; gap: 6px; padding: 4px 4px 6px; }
.att-chip {
  display: flex;
  align-items: center;
  gap: 7px;
  max-width: 100%;
  padding: 4px 6px 4px 4px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--bg-2);
  font-size: 11.5px;
  color: var(--muted);
}
/* Still uploading: the border is the thing that says so, since the word is already in the chip. */
.att-chip.uploading { border-style: dashed; }
.att-chip.error { border-color: var(--err); }
.att-chip.error .att-name { color: var(--err); }
.att-chip .att-thumb { width: 30px; height: 30px; border-radius: 7px; object-fit: cover; flex: none; }
.att-glyph { width: 30px; height: 30px; display: grid; place-items: center; font-size: 15px; flex: none; }
.att-name {
  max-width: 180px;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--text);
}
.att-size { flex: none; color: var(--muted-2); }
.att-x {
  flex: none;
  width: 20px;
  height: 20px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--muted-2);
  font-size: 11px;
  cursor: pointer;
}
.att-x:hover { background: var(--bg-3); color: var(--text); }

/* A drop in flight. The box is the target, so it is the box that says yes. */
#btn-attach { font-size: 14px; }
.composer-box.dropping {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 16%, transparent);
}

/* In a message: thumbnails that open the original, chips that open the text. */
/* centred, not stretched: a text chip beside a tall photo should stay a chip, not become a banner. */
.att-list { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-bottom: 6px; }
.att-caption { white-space: pre-wrap; word-break: break-word; }
.att-attachment {
  display: block;
  max-width: 100%;
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
  background: var(--well);
  text-decoration: none;
}
.att-attachment .att-thumb {
  display: block;
  width: auto;
  height: auto;
  max-width: min(320px, 100%);
  max-height: 240px;
  cursor: zoom-in;
}
.att-attachment.file { display: flex; align-items: center; gap: 8px; padding: 7px 11px; color: var(--text); font-size: 12.5px; }
.att-attachment.file:hover { border-color: var(--btn-hover-border); background: var(--bg-3); }
/* The bytes are gone but the message named them: say that instead of drawing a broken-image icon. */
.att-attachment.gone { display: flex; align-items: center; padding: 8px 12px; color: var(--muted-2); font-size: 12px; font-style: italic; }
.att-attachment.gone::after { content: attr(data-name) " is gone"; }
/* A picture with no caption needs less bubble around it, or the frame outweighs the photo. */
.msg.user .bubble.att-only { padding: 5px; background: transparent; border-color: var(--border-soft); }

/* ─────────────────────────────  inspector  ────────────────────────── */
.inspector {
  background: var(--bg-1);
  border-left: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  min-width: 0;
  overflow: hidden;
}
.tabs { display: flex; gap: 2px; padding: 8px 10px; border-bottom: 1px solid var(--border); }
.tab {
  flex: 1;
  background: transparent;
  border: 0;
  border-radius: 8px;
  padding: 6px 8px;
  font-size: 13px;
  color: var(--muted);
  cursor: pointer;
}
.tab:hover { background: var(--bg-2); color: var(--text); }
.tab.active { background: var(--bg-3); color: var(--text); }
.panel {
  /* same story as .messages: as a flex child it needs min-height:0 before overflow-y:auto
     can scroll, otherwise a long memory list or the settings form is clipped for good */
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 14px;
}
.panel-help { font-size: 12.5px; color: var(--muted-2); margin: 0 0 12px; }
.panel-help code { font-family: var(--mono); font-size: 11.5px; background: var(--code-bg); padding: 1px 4px; border-radius: 4px; }
.row { display: flex; gap: 6px; }
.row input {
  flex: 1;
  min-width: 0;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: 9px;
  padding: 7px 10px;
}
.row input:focus { outline: none; border-color: var(--accent-dim); }
.memory-list, .tool-list { list-style: none; margin: 12px 0; padding: 0; display: flex; flex-direction: column; gap: 7px; }
.tool-bar { display: flex; align-items: center; gap: 6px; margin: 0 0 2px; }
.tool-count { font-size: 11.5px; color: var(--muted-2); margin-left: 2px; }
.memory-item {
  display: flex;
  gap: 8px;
  align-items: flex-start;
  background: var(--bg-2);
  border: 1px solid var(--border-soft);
  border-radius: 9px;
  padding: 8px 10px;
  font-size: 13px;
}
.memory-item .txt { flex: 1; min-width: 0; word-break: break-word; }
.memory-item .src { font-size: 10.5px; color: var(--muted-2); }
.tool-item {
  display: flex;
  gap: 10px;
  align-items: center;
  background: var(--bg-2);
  border: 1px solid var(--border-soft);
  border-radius: 9px;
  padding: 9px 11px;
}
.tool-item .t-main { flex: 1; min-width: 0; cursor: pointer; }
.tool-item .t-name { font-family: var(--mono); font-size: 12.5px; display: flex; gap: 6px; align-items: center; }
.tool-item .t-main:focus-visible { outline: 1px solid var(--accent); outline-offset: 3px; border-radius: 4px; }
/* One line per tool until clicked: the panel is a switchboard, and the manual is one click away. */
.tool-item .t-desc {
  font-size: 11.5px;
  color: var(--muted-2);
  margin-top: 2px;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 1;
  line-clamp: 1;
  overflow: hidden;
}
.tool-item.open .t-desc {
  display: block;
  -webkit-line-clamp: unset;
  line-clamp: unset;
  overflow: visible;
}
.tool-item .t-chev { margin-left: auto; font-size: 9px; color: var(--muted-2); transition: transform 0.15s ease; }
.tool-item:hover .t-chev { color: var(--text); }
.tool-item.open .t-chev { transform: rotate(180deg); }
.switch { position: relative; width: 34px; height: 19px; flex: none; }
.switch input { opacity: 0; width: 0; height: 0; }
.slider {
  position: absolute;
  inset: 0;
  background: var(--bg-3);
  border-radius: 999px;
  transition: 0.15s;
  cursor: pointer;
}
.slider::before {
  content: '';
  position: absolute;
  height: 15px;
  width: 15px;
  left: 2px;
  top: 2px;
  background: var(--muted);
  border-radius: 50%;
  transition: 0.15s;
}
.switch input:checked + .slider { background: var(--accent); }
.switch input:checked + .slider::before { transform: translateX(15px); background: var(--on-accent); }
.label { display: block; font-size: 12.5px; color: var(--muted); margin: 14px 0 0; }
.label .val { color: var(--text); font-variant-numeric: tabular-nums; }
.label input[type='range'], .label input[type='number'], .label select, .label textarea {
  width: 100%;
  margin-top: 6px;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: 9px;
  padding: 7px 9px;
  color: var(--text);
  font-size: 13px;
}
.label textarea { font-family: var(--mono); font-size: 12px; line-height: 1.5; resize: vertical; }
.label input[type='range'] { padding: 0; height: 22px; accent-color: var(--accent); }
.small { font-size: 11.5px; }
.check { display: flex; gap: 8px; align-items: center; font-size: 13px; color: var(--muted); margin-top: 12px; cursor: pointer; }
.check input { accent-color: var(--accent); }
.panel-foot { margin-top: 18px; }
.kv { font-size: 11.5px; color: var(--muted-2); margin-bottom: 10px; line-height: 1.7; word-break: break-all; }
.kv b { color: var(--muted); font-weight: 500; }

/* ─────────────────────────────  drawer scrim  ─────────────────────── */
/* Only ever displayed by the <=1120px media query while a drawer is open. */
.scrim { display: none; }

/* ─────────────────────────────  toasts  ───────────────────────────── */
.toast-wrap { position: fixed; right: 16px; bottom: 16px; display: flex; flex-direction: column; gap: 8px; z-index: 90; }
.toast {
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: 9px;
  padding: 9px 13px;
  font-size: 13px;
  max-width: 380px;
  box-shadow: 0 10px 30px #000a;
  animation: rise 0.16s ease;
}
.toast.err { border-left-color: var(--err); }
.toast.ok { border-left-color: var(--ok); }
@keyframes rise { from { transform: translateY(6px); opacity: 0; } }

/* ─────────────────────────────  responsive  ───────────────────────── */
/* ─────────────────────────  odds and ends  ─────────────────────────── */
.muted { color: var(--muted-2); }

@media (max-width: 1120px) {
  .app { grid-template-columns: 0 minmax(0, 1fr) 0; }
  .only-hidden { display: inline-flex; }
  .sidebar, .inspector {
    position: fixed;
    top: 0;
    bottom: 0;
    width: min(86vw, 330px);
    z-index: 50;
    box-shadow: 0 0 40px #000c;
    transition: transform 0.18s ease;
  }
  .sidebar { left: 0; transform: translateX(-100%); }
  .inspector { right: 0; transform: translateX(100%); }
  /* Drawers, not blankets: closed until explicitly opened, otherwise both panels cover the
     chat on load and there is nothing left to read or type into. */
  body.drawer-sidebar .sidebar { transform: none; }
  body.drawer-inspector .inspector { transform: none; }
  body.drawer-sidebar .scrim,
  body.drawer-inspector .scrim { display: block; }
  .sidebar > * { width: min(86vw, 330px); }
  .sidebar .btn.primary { width: calc(min(86vw, 330px) - 24px); }
  .scrim {
    position: fixed;
    inset: 0;
    z-index: 45;
    background: rgba(4, 6, 10, 0.55);
    backdrop-filter: blur(1px);
  }
}
@media (max-width: 620px) {
  .messages { padding: 8px 12px 30px; }
  .composer { padding: 8px 12px 10px; }
  .chat-title { max-width: 52vw; }
}

/* ────────────────────────  temporary chats  ─────────────────────────────── */
/* A temporary chat is never written to disk: it is marked in the header, in the composer
   footer, and with a dashed entry in the sidebar so it can never be mistaken for a real one. */
.sidebar .btn-temp {
  margin: 6px 12px 0;
  width: calc(var(--sb-w) - 24px);
  color: var(--muted);
  border: 1px dashed var(--border);
}
.sidebar .btn-temp:hover {
  color: var(--text);
  border-color: var(--accent-dim);
}
.chip.temp {
  color: var(--violet);
  border-color: #3b3260;
  background: #171328;
  white-space: nowrap;
}
.temp-chat .composer-box { border-color: #3b3260; }
.temp-chat .composer-note { color: var(--violet); }

/* ────────────────────────  personas  ──────────────────────── */
.persona-row {
  display: flex;
  gap: 6px;
  margin-top: 6px;
}
.persona-row .persona {
  flex: 1 1 0;
  min-width: 0;
  text-align: center;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}
/* The same face, shrunk, so the picker shows who you are about to summon rather than three words. */
.persona-row .avatar.mini {
  /* 20px with a 1px pad: the padding and the border come off the box, and anything under ~16px of art
     stops being a face and becomes texture. */
  width: 20px;
  height: 20px;
  margin-top: 0;
  border-radius: 6px;
  padding: 1px;
}
.persona-row .avatar.mini svg { display: block; width: 100%; height: 100%; }
.persona-row .persona.active {
  border-color: var(--accent);
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, var(--bg-2));
}
.tool-item.off-here { opacity: 0.55; }

/* ────────────────────────  login  ──────────────────────── */
/* Reachable only when WEBLLM_PASSWORD is set. The app shell is hidden, not unmounted: this one
   index.html has to be both the door and the app, and on the other side of the door boot() has not
   run, so there is nothing to unmount. */
.login { display: none; }
body.signed-out .login {
  display: grid;
  place-items: center;
  min-height: 100vh;
  padding: 24px;
}
body.signed-out .app,
body.signed-out #toasts {
  display: none;
}
.login-card {
  width: min(430px, 100%);
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 22px 22px 18px;
  box-shadow: 0 18px 50px rgb(0 0 0 / 45%);
}
.login-brand {
  display: flex;
  align-items: center;
  gap: 9px;
  font-weight: 650;
  letter-spacing: 0.2px;
}
.login-blurb { font-size: 13px; color: var(--muted); margin: 10px 0 4px; line-height: 1.55; }
.login-card .field { display: block; font-size: 12.5px; color: var(--muted); margin-top: 14px; }
.login-card .field em { color: var(--muted-2); font-style: normal; }
.login-card .field input {
  width: 100%;
  margin-top: 6px;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: 9px;
  padding: 9px 10px;
  color: var(--text);
  font-size: 14px;
}
.login-card .field input:focus {
  outline: none;
  border-color: var(--accent);
}
.login-recover { margin-top: 14px; font-size: 12.5px; color: var(--muted); }
.login-recover summary { cursor: pointer; color: var(--muted); }
.login-recover .panel-help { margin: 8px 0 0; }
.login-card .block { margin-top: 16px; }
.login-error { min-height: 18px; margin: 10px 0 0; font-size: 12.5px; color: var(--err); }
.login-id { margin: 12px 0 4px; }
.login-id code {
  font-family: var(--mono);
  font-size: 22px;
  letter-spacing: 2px;
  color: var(--accent);
  background: var(--bg-2);
  border: 1px dashed var(--border);
  border-radius: 10px;
  padding: 8px 12px;
  user-select: all;
}
.login-actions { display: flex; gap: 8px; margin-top: 12px; }
.login-actions .btn { flex: 1; }

/* The id block in Settings sits above the backend readout it shares a panel with, and is hidden by the
   script when there is no auth, so solo users never see it. It is #who, not .who: every message in the
   transcript has a .who line too (the name above it), and a selector this loose used to draw a box around
   all of those as well. */
#who {
  border: 1px solid var(--border-soft);
  border-radius: 12px;
  padding: 10px 12px;
  margin-bottom: 16px;
  background: var(--bg-1);
}
#who .kv { margin-bottom: 8px; }
#who code { font-family: var(--mono); font-size: 13px; color: var(--accent); letter-spacing: 0.5px; }
.who-actions { display: flex; gap: 6px; }
.who-actions .btn { flex: 1 1 0; font-size: 12px; padding: 5px 8px; }

@media (max-width: 1120px) {
  .sidebar .btn-temp { width: calc(min(86vw, 330px) - 24px); }
}
