/* ══════ VARIABLES & RESET ══════ */
:root {
  --bg:#e8e8e8; --bg2:#e0e0e0; --bg3:#f1f1f1; --border:#d8d3cb;
  --text:black; --text2:#6b6460; --text3:#a8a29e;
  --accent:lightgray; --accent2:#6e6e6e; --acc-text:#fff;
  --accent-btn:#4a6fa5; --accent-btn2:#3d5c8a; --accent-btn-text:#fff;
  --user-bg:#3a3a3a; --user-fg:#fff; --ai-bg:#ede9e3; --ai-fg:#1c1917;
  --input-bg:#fff; --remote:#3b82f6;

  --shadow:0 1px 3px rgba(0,0,0,.10); --shadow-lg:0 4px 20px rgba(0,0,0,.14);
  --r:4px; --left-w:272px;
  --font:'DM Sans',system-ui,sans-serif; --mono:'DM Mono','Courier New',monospace;
}
@media (prefers-color-scheme: dark) {
  :root {
    --bg:#181614; --bg2:#211f1c; --bg3:#2d2a27; --border:#3a3632;
    --text:#e8e3dc; --text2:#9a948d; --text3:#5c5751;
    --accent:#b8b8b8; --accent2:#d0d0d0; --accent-btn:#5b84bd; --accent-btn2:#71a0da; --user-bg:#3a3a3a; --user-fg:#e8e3dc;
    --ai-bg:#211f1c; --ai-fg:#e8e3dc; --input-bg:#2a2724; --remote:#5b9bf8;
    --shadow:0 1px 3px rgba(0,0,0,.3); --shadow-lg:0 4px 20px rgba(0,0,0,.5);
  }
}
*,*::before,*::after { box-sizing:border-box; margin:0; padding:0; }
html,body { height:100%; height:100dvh; overflow:hidden; font-family: "sans-serif"; }
body { font-family:var(--font); color:var(--text); background:var(--bg); -webkit-font-smoothing:antialiased; }
button,input,textarea,select { font-family:var(--font); }
button { cursor:pointer; }
a { color:var(--accent); }
::-webkit-scrollbar { width:5px; height:5px; }
::-webkit-scrollbar-track { background:transparent; }
::-webkit-scrollbar-thumb { background:var(--border); border-radius:3px; }
::-webkit-scrollbar-thumb:hover { background:var(--text3); }

/* ══════ BASE COMPONENTS ══════ */
.btn { display:inline-flex; align-items:center; gap:.35rem; padding:.35rem .7rem; border-radius:var(--r);
  background:var(--bg3); border:1px solid var(--border); color:var(--text); font-size:13px; font-weight:500; line-height:1;
  white-space:nowrap; }
.btn:hover { background:var(--border); }
.btn:disabled { opacity:.45; cursor:default; }
.btn-primary { background:var(--accent-btn); border-color:transparent; color:var(--accent-btn-text); }
.btn-primary:hover { background:var(--accent-btn2); }
.btn-danger { color:#e44; border-color:transparent; background:transparent; }
.btn-danger:hover { background:rgba(228,68,68,.1); }
.btn-icon { padding:.3rem .45rem; font-size:14px; background:transparent; border-color:transparent; color:var(--text2); }
.btn-icon:hover { background:var(--bg3); color:var(--text); }
/* Inert echo of a button, for referencing UI by name inside help text (see uiRef, app-dom.js) — same look, no hover/click. */
.btn-ref { pointer-events:none; font-size:12px; padding:.15rem .5rem; }
/* Inline echo of a slash command / typed parameter (see commandRef, app-dom.js) —
   deliberately kbd-like rather than button-like: it's something you type. */
.cmd-ref { font-family:var(--mono); font-size:11.5px; color:var(--accent); background:var(--bg3); border:1px solid var(--border); border-radius:var(--r); padding:.05rem .35rem; white-space:nowrap; }
/* Click-path separator between pathRef chips (see pathRef, app-dom.js). */
.path-sep { color:var(--text3); }

.field { margin-bottom:.7rem; }
.field label { display:block; font-size:11.5px; font-weight:600; letter-spacing:.04em; text-transform:uppercase; color:var(--text3); margin-bottom:.3rem; }
.field label .sub { font-weight:400; text-transform:none; letter-spacing:0; opacity:.6; }
/* Checkboxes/radios are excluded: they're laid out inline beside their label
   (see CHECKBOX_LABEL_STYLE, app-fields.js), so stretching them to width:100%
   pushes the label text against the far edge — and on Safari the resulting row
   is wider than the modal, which then scrolls sideways. */
.field input:not([type=checkbox]):not([type=radio]),.field textarea,.field select { width:100%; padding:.5rem .65rem; border-radius:var(--r);
  background:var(--input-bg); border:1.5px solid var(--border); color:var(--text); outline:none; }
.field input:focus,.field textarea:focus,.field select:focus { border-color:var(--accent); }
.field input[type=checkbox],.field input[type=radio] { flex:none; }
.field textarea { resize:vertical; min-height:80px; line-height:1.5; }
.field select option { background:var(--bg2); }
.field .hint { font-size:11.5px; color:var(--text3); margin-top:.25rem; }
/* A checkbox field's sub-option (e.g. character-builder's "Avatar style"
   select) — indented under the checkbox it belongs to, laid out inline
   rather than as its own full-width labelled field, and dimmed to show
   it's inert while the governing checkbox is unchecked. */
.builder-subfield { display:flex; align-items:center; gap:.5rem; margin:.35rem 0 .5rem 1.6rem; transition:opacity .15s; }
.builder-subfield label { font-size:12px; font-weight:400; text-transform:none; letter-spacing:0; color:var(--text3); margin:0; }
.builder-subfield select { width:auto; padding:.3rem .5rem; }
.builder-subfield.is-disabled { opacity:.5; }
/* Keyboard-shortcuts help overlay (the ? modal). */
.kbd-row { display:flex; align-items:center; justify-content:space-between; gap:1rem; padding:.4rem 0; border-bottom:1px solid var(--border); }
.kbd-row:last-child { border-bottom:none; }
.kbd-desc { font-size:13px; color:var(--text2); }
kbd { font-family:var(--mono); font-size:11.5px; background:var(--bg3); border:1px solid var(--border); border-radius:var(--r); padding:.15rem .45rem; color:var(--text); white-space:nowrap; }
.field-row { display:flex; gap:.6rem; }
.field-row > .field { flex:1; }
.section-label { font-size:11px; font-weight:600; letter-spacing:.06em; text-transform:uppercase; color:var(--accent); margin:1.1rem 0 .6rem; padding-bottom:.3rem; border-bottom:1px solid var(--border); }
/* Collapsible provider sub-section (a module's settings nested under a parent section). */
.settings-sub { margin:1.1rem 0 .6rem; }
.settings-sub > summary { list-style:none; cursor:pointer; user-select:none; display:flex; align-items:center; gap:.4rem;
  font-size:11px; font-weight:600; letter-spacing:.06em; text-transform:uppercase; color:var(--text2);
  padding-bottom:.3rem; border-bottom:1px solid var(--border); }
.settings-sub > summary::-webkit-details-marker { display:none; }
.settings-sub > summary::before { content:'▸'; font-size:9px; opacity:.8; }
.settings-sub[open] > summary::before { transform:rotate(90deg); }
.settings-sub > summary:hover { color:var(--accent2); }
.settings-sub[open] > summary { margin-bottom:.6rem; }
.section-label:first-child { margin-top:0; }
/* Top-level Settings accordion: one pane open at a time via native [name] grouping. */
.settings-acc { margin:0; }
.settings-acc + .settings-acc, .settings-acc:first-child { border-top:1px solid var(--border); }
.settings-acc > summary { list-style:none; cursor:pointer; user-select:none; display:flex; align-items:center; gap:.45rem;
  font-size:11px; font-weight:600; letter-spacing:.06em; text-transform:uppercase; color:var(--text2);
  padding:.7rem 0 .65rem; }
.settings-acc > summary::-webkit-details-marker { display:none; }
.settings-acc > summary::before { content:'▸'; font-size:9px; opacity:.8; transition:transform .15s; }
.settings-acc[open] > summary::before { transform:rotate(90deg); }
.settings-acc > summary:hover { color:var(--accent2); }
.settings-acc[open] > summary { color:var(--accent2); }
.settings-acc > .settings-fieldset { margin:0 0 .8rem; }
.settings-acc > .settings-fieldset > .section-label:first-child { display:none; }
/* Help modal: an accordion of module-contributed topics (reuses .settings-acc).
   Body text reads as prose rather than the compact form-hint style. */
.help-body { margin:0 0 .8rem; display:flex; flex-direction:column; gap:.55rem; }
.help-body > .help-p { font-size:13px; line-height:1.5; color:var(--text2); }
.help-body > .btn { align-self:flex-start; }
/* Top-level Settings group (General, Server, Advanced, …): a collapsible
   boxed fieldset holding that group's .settings-acc sections; one open at a
   time via native [name] grouping. */
.settings-group { border:1px solid var(--border); border-radius:var(--r); margin:0 0 .55rem; padding:0 .75rem; }
.settings-group > summary { list-style:none; cursor:pointer; user-select:none; display:flex; align-items:center; gap:.45rem;
  font-size:11px; font-weight:700; letter-spacing:.06em; text-transform:uppercase; color:var(--text3);
  padding:.65rem 0; margin:0 -.75rem; padding-left:.75rem; padding-right:.75rem; }
.settings-group > summary::-webkit-details-marker { display:none; }
.settings-group > summary::before { content:'▸'; font-size:9px; opacity:.8; transition:transform .15s; }
.settings-group[open] > summary::before { transform:rotate(90deg); }
.settings-group > summary:hover, .settings-group[open] > summary { color:var(--text); }
.settings-group[open] > summary { border-bottom:1px solid var(--border); }
.settings-group > .settings-acc:first-of-type { border-top:none; }
/* A Settings group that lives in its own sub-modal: label + hint + Open button. */
.settings-group-link { display:flex; align-items:center; gap:.75rem; border:1px solid var(--border); border-radius:var(--r);
  margin:0 0 .55rem; padding:.45rem .75rem; }
.settings-group-link-text { flex:1; min-width:0; }
.settings-group-link-label { font-size:11px; font-weight:700; letter-spacing:.06em; text-transform:uppercase; color:var(--text3); }
.settings-group-link-text > .hint { font-size:11.5px; color:var(--text3); margin-top:.15rem; }
/* A fieldset whose prerequisite toggle (e.g. "Enable memory") is off. */
/* A fieldset gated behind an enable toggle: dim + disable every direct child
   except a .gate-keep row (the toggle itself), so it can always be flipped back
   on. Dimming per-child (not the whole node) keeps the toggle at full opacity. */
.settings-fieldset-disabled > *:not(.gate-keep) { opacity:.45; pointer-events:none; }

.badge { font-size:10px; padding:.08rem .35rem; border-radius:4px; background:var(--bg3); color:var(--text2);
  display:inline-flex; align-items:center; gap:.2rem; font-weight:500; line-height:1.4; }
.badge.remote { color:var(--remote); background:color-mix(in srgb,var(--remote) 14%,transparent); }

/* ══════ LAYOUT ══════ */
#app { display:flex; height:100vh; height:100dvh; overflow:hidden; }
#left-backdrop { display:none; }

#left-col { width:var(--left-w); min-width:var(--left-w); display:flex; flex-direction:column;
  background:var(--bg2); border-right:1px solid var(--border); overflow:hidden; container-type:inline-size; }
#left-header { display:flex; align-items:center; gap:.4rem; padding:.5rem .6rem .4rem; border-bottom:1px solid var(--border); flex-shrink:0; }
#app-title { font-size:15px; font-weight:600; flex:1; letter-spacing:-.01em; }
#app-title span { color:var(--accent); }
#close-left-btn { display:none; }

#search-wrap { padding:.4rem .6rem; flex-shrink:0; position:relative; }
#search-input { width:100%; padding:.4rem .7rem .4rem 2rem; border-radius:3px; background:var(--bg3);
  border:1px solid transparent; color:var(--text); font-size:13px; outline:none; }
#search-input:focus { background:var(--input-bg); border-color:var(--accent); }
#search-input::placeholder { color:var(--text3); }
#search-icon { position:absolute; left:1.05rem; top:50%; transform:translateY(-50%); color:var(--text3); font-size:13px; pointer-events:none; }

#thread-list { flex:1; overflow-y:auto; padding:.3rem .35rem; display:flex; flex-direction:column; gap:1px; font-size: 80%; }
#thread-list-empty { padding:.75rem .5rem; font-size:12.5px; color:var(--text3); text-align:center; }

.thr, .folder-item { font-size: 90%; display:flex; align-items: flex-start; gap:.55rem; padding: 0.5rem; border-radius:3px; cursor:pointer; text-decoration:none; color:inherit; background: var(--bg); margin-top: 0.5em; position:relative; overflow:hidden; flex-shrink:0; }
.thr:hover { background:var(--bg3); }
.thr[draggable="true"] { cursor:grab; }
.thr.dragging { opacity:.4; }
/* Fill-bar for the mobile long-press-to-move gesture — grows over the same
   500ms as the hold threshold so the user sees the action arming in real
   time, instead of a move happening with no warning. */
.thr::after { content:''; position:absolute; left:0; bottom:0; height:2px; width:0; background:var(--accent); }
.thr.pressing::after { width:100%; transition:width .5s linear; }
.thr.active { background:var(--accent); border: 1px solid var(--accent2); color:var(--acc-text); }
.thr.active .thr-char { opacity:.8; }
.thr-av { width:48px; height:48px; border-radius:3px; flex-shrink:0; background:var(--bg3); display:flex; align-items:center; justify-content:center; font-size:14px; overflow:hidden; background-size:cover; background-position:center; }
.thr.active .thr-av { background-color:rgba(255,255,255,.15); }
.thr-info { flex:1; min-width:0; }
.thr-name { font-weight:600; }
.thr-char { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; margin-top:1px; display:flex; align-items:center; gap:.25rem; }
.thr-cloud { color:var(--remote); }
.thr-snippet { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; margin-top:2px; color:var(--text3); font-style:italic; }
.thr.active .thr-snippet { opacity:.8; }
.thr-del { opacity:0; padding:.2rem .3rem; font-size:13px; background:transparent; border:none; color:inherit; border-radius:4px; flex-shrink:0; }
.thr:hover .thr-del { opacity:.5; }
.thr.active .thr-del { opacity:.6; }
.thr-del:hover { opacity:1 !important; background:rgba(0,0,0,.15); }

.folder-header { font-size:10.5px; font-weight:600; letter-spacing:.06em; text-transform:uppercase; color:var(--text3);
  padding:.55rem .6rem .2rem; margin-top:.2rem; }

.folder-back { display:flex; align-items:center; gap:.35rem; padding:.35rem .55rem; border-radius:3px; border:none;
  background:transparent; color:var(--text3); font-size:12.5px; font-weight:500; cursor:pointer; width:100%;
  text-align:left; margin-bottom:.15rem; }
.folder-back:hover { background:var(--bg3); color:var(--text2); }
.folder-back.drag-over, .folder-item.drag-over { background:var(--accent); color:var(--acc-text); outline:2px dashed var(--accent2); outline-offset:-2px; }

.folder-item { text-align: start; border: none; }
.folder-item:hover { background:var(--bg3); }
.folder-item-icon { font-size:10px; color:var(--accent); flex-shrink:0; opacity:.8; }
.folder-item-name { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.folder-item-count { font-size:11px; color:var(--text3); flex-shrink:0; }

#new-chat-btn { justify-content:center; margin:.3rem .4rem; padding:.5rem; font-size:13px; flex-shrink:0; }

/* Defaults to a full vertical stack — the sidebar is narrower than the
   buttons' combined natural width, so a row wrap would just break them
   across two uneven lines. Switches to a single row once the sidebar
   itself (the @container ancestor) is wide enough for all of them. */
#left-footer { display:flex; flex-wrap: wrap; gap:.3rem; padding:.4rem; border-top:1px solid var(--border); flex-shrink:0; }
.lf-btn { font-size:12px; padding:.35rem .5rem; justify-content:center; }
@container (min-width: 380px) {
  #left-footer { flex-direction:row; flex-wrap:wrap; }
  .lf-btn { flex:1; min-width:fit-content; }
}

#mid-col { flex:1; display:flex; flex-direction:column; min-width:0; overflow:hidden; }

#char-select { flex:1; display:flex; flex-direction:column; padding:1.1rem 1.1rem .8rem; overflow-y:auto; }
#char-select-top { display:flex; align-items:center; gap:.6rem; margin-bottom:.8rem; }
#char-select-top h2 { font-size:18px; font-weight:600; flex:1; letter-spacing:-.02em; }
#open-left-char { display:none; }
#char-grid { flex:1; overflow-y:auto; display:grid; grid-template-columns:repeat(auto-fill,minmax(220px,1fr)); gap:.5rem; align-content:start; }
#char-grid-empty { font-size:13px; color:var(--text3); padding:.5rem; grid-column:1/-1; }

.char-card { display:flex; flex-direction:column; gap:.4rem; padding:.7rem; border-radius:5px; background:var(--bg2); border:1.5px solid var(--border); cursor:pointer; position:relative; }
.char-card:hover { border-color:var(--accent); transform:translateY(-1px); box-shadow:var(--shadow-lg); }
.char-card-top { display:flex; align-items:center; gap:.6rem; }
.char-av-lg { width:44px; height:44px; border-radius:4px; flex-shrink:0; background:var(--bg3); display:flex; align-items:center; justify-content:center; font-size:22px; overflow:hidden; background-size:cover; background-position:center; }
.char-name { font-weight:600; font-size:14.5px; }
.char-namerow { display:flex; align-items:center; gap:.4rem; flex-wrap:wrap; }
.char-desc { font-size:12px; color:var(--text2); display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; line-height:1.5; }
.char-edit { position:absolute; top:.5rem; right:.5rem; opacity:0; font-size:11px; padding:.2rem .4rem; }
.char-card:hover .char-edit { opacity:1; }

.char-card-action { align-items:center; justify-content:center; border-style:dashed; border-color:var(--text2); background:var(--bg3); color:var(--text2); font-size:13px; font-weight:500; min-height:96px; text-align:center; }
.char-card-action:hover { border-color:var(--accent-btn); color:var(--accent-btn); background:var(--bg3); transform:none; box-shadow:none; }
.char-card-action-icon { font-size:20px; }

.char-card[draggable="true"] { cursor:grab; }
.char-card.dragging { opacity:.4; }
/* Fill-bar for the mobile long-press-to-move gesture — grows over the same
   500ms as the hold threshold so the user sees the action arming in real
   time, instead of a move happening with no warning. Same treatment as the
   thread list's .thr::after. */
.char-card::after { content:''; position:absolute; left:0; bottom:0; height:2px; width:0; background:var(--accent); }
.char-card.pressing::after { width:100%; transition:width .5s linear; }
.char-card-folder.drag-over { border-color:var(--accent); background:var(--accent); color:var(--acc-text); outline:2px dashed var(--accent2); outline-offset:-2px; }
.char-card-folder .char-card-action-icon { color:var(--accent); }
.char-card-folder .folder-item-count { display:block; margin-top:.15rem; }

#chat { flex:1; display:flex; flex-direction:column; overflow:hidden; }
#chat-header { display:flex; align-items:center; gap:.5rem; padding:.45rem .7rem; border-bottom:1px solid var(--border); background:var(--bg2); flex-shrink:0; min-height:46px; }
#open-left-chat { display:none; }
#chat-av { width:28px; height:28px; border-radius:3px; flex-shrink:0; background:var(--bg3); display:flex; align-items:center; justify-content:center; font-size:13px; overflow:hidden; background-size:cover; background-position:center; }
#chat-title { font-weight:600; font-size:14.5px; flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; letter-spacing:-.01em; }
#chat-char { font-size:12px; color:var(--text3); white-space:nowrap; }
#opts-btn { padding:.3rem .5rem; font-size:16px; }
.opt { display:flex; align-items:center; gap:.5rem; width:100%; padding:.5rem .65rem; border-radius:3px; border:none; background:transparent;
  color:var(--text); font-size:13px; text-align:left; cursor:pointer; }
.opt:hover { background:var(--bg3); }
.opt-sep { height:1px; background:var(--border); margin:.3rem 0; }

#feed { flex:1; overflow-y:auto; overflow-anchor:none; padding:.8rem .9rem; display:flex; flex-direction:column; gap:.65rem; }
#feed-empty { text-align:center; color:var(--text3); font-size:13px; padding:2rem; }

.msg { display:flex; gap:.5rem; }
.msg-av { width:48px; height:48px; flex-shrink:0; background:var(--bg3); display:flex; align-items:center; justify-content:center; font-size:13px; overflow:hidden; background-size:cover; background-position:center; margin-top:.1rem; }
.msg-body { flex:1; min-width:0; }
/* Wraps rather than overflowing: on a phone the full set (name, instruction
   badge, edit/regen/delete/branch, the variant navigator and the order) is
   wider than the feed, and without this the whole feed scrolls sideways. */
.msg-meta { display:flex; align-items:center; flex-wrap:wrap; gap:.3rem; row-gap:.15rem; margin-bottom:.25rem; }
.msg-name { font-weight:600; font-size: 90%; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.msg-acts { display:flex; gap:.2rem;}
.msg-act { padding:.28rem .42rem; font-size:14px; cursor:pointer; border: none; border-radius:4px; line-height:1; }
.msg-order { font-size:90%; color:var(--text2); }
.msg-act:hover { background:var(--bg3); border-color:var(--border); color:var(--text); }
/* Edit is the one msg-act always visible (Regenerate/Delete are last-message-only)
   and the most-used — give it a persistent tint so it reads as a primary action
   rather than blending in with the ghost icon buttons around it. */
.msg-act-edit { font-size:15px; padding:.32rem .5rem; background:var(--bg3); color:var(--accent2); }
.msg-act-edit:hover { background:var(--border); color:var(--text); }
/* Inline regenerate/delete show on the true last message (see app-render.js)
   — driven purely by :has() so they stay correct as messages stream in/out
   without any JS re-render bookkeeping — and on the active "continue from
   here" branch point (.msg-acts-branch-tail, set in JS since that message
   isn't necessarily the last DOM node once later messages already exist). */
.msg-act-last-only { display:none; }
#feed .msg:not(:has(~ .msg)) .msg-act-last-only,
.msg-acts-branch-tail .msg-act-last-only { display:inline-block; }
.msg-variants { display:flex; align-items:center; gap:.1rem; }
.msg-variant-count { font-size:90%; color:var(--text2); min-width:2.1rem; text-align:center; }

/* "Continue from here" branch point — set in app-render.js alongside
   .msg-acts-branch-tail above (same condition), so this always tracks the
   real insertion point rather than staying pinned to wherever ⤸ was first
   clicked as later inserts shift the cursor down the feed. This message
   isn't necessarily the last in the feed, so it needs its own marker
   showing where new messages will be inserted. */
.msg.branch-anchor .msg-body { background:color-mix(in srgb,var(--accent-btn) 8%,transparent); border-left:3px solid var(--accent-btn); border-radius:0 4px 4px 0; padding:.3rem .5rem; margin:-.3rem -.5rem; }
.msg.branch-anchor .msg-meta::after { content:'⤸ branching from here'; font-size:10.5px; font-weight:600; color:var(--accent-btn); white-space:nowrap; }

/* A user turn sent by clicking a <choice> button (branching.js) rather than
   typed — same left-accent-border treatment as branch-anchor above, but in
   --accent (the color the choice buttons themselves use) so it visually
   reads as "this came from that button", not a second branching marker. */
.msg.is-choice-pick .msg-body { background:color-mix(in srgb,var(--accent) 8%,transparent); border-left:3px solid var(--accent); border-radius:0 4px 4px 0; padding:.3rem .5rem; margin:-.3rem -.5rem; }
.msg-choice-badge { color:var(--accent); }

.msg-bubble { line-height:1.6; overflow-wrap:break-word; }
.msg-bubble p { margin-bottom:.5rem; }
.msg-bubble p:last-child { margin-bottom:0; }
.msg-bubble pre { background:rgba(0,0,0,.22); border-radius:3px; overflow-x:auto; margin:.5rem 0; position:relative; border:1px solid rgba(255,255,255,.06); }
.msg-bubble pre code { padding:.75rem 1rem; display:block; font-size:12.5px; font-family:var(--mono); background:transparent; }
.msg-bubble code:not(pre code) { font-family:var(--mono); font-size:12.5px; background:rgba(0,0,0,.12); padding:.1rem .3rem; border-radius:3px; }
.msg.user .msg-bubble code:not(pre code) { background:rgba(255,255,255,.15); }
.copy-code-btn { position:absolute; top:.35rem; right:.35rem; padding:.2rem .45rem; font-size:11px; border-radius:4px;
  background:rgba(255,255,255,.08); border:1px solid rgba(255,255,255,.1); color:rgba(255,255,255,.6); cursor:pointer; }
.copy-code-btn:hover { background:rgba(255,255,255,.16); color:#fff; }
.msg-bubble img { max-width:100%; max-height:380px; margin:.3rem 0; display:block; }
.msg-bubble ul,.msg-bubble ol { padding-left:1.5rem; margin:.3rem 0; }
.msg-bubble li { margin-bottom:.2rem; }
.msg-bubble h1,.msg-bubble h2,.msg-bubble h3,.msg-bubble h4 { margin:.6rem 0 .25rem; font-weight:600; }
.msg-bubble blockquote { border-left:3px solid var(--accent); padding-left:.9rem; opacity:.8; margin:.3rem 0; font-style:italic; }
.msg-bubble table { border-collapse:collapse; width:100%; margin:.4rem 0; font-size:13px; }
.msg-bubble th,.msg-bubble td { border:1px solid var(--border); padding:.3rem .55rem; }
.msg-bubble th { background:var(--bg3); font-weight:600; font-size:12px; }
.msg-bubble hr { border:none; border-top:1px solid var(--border); margin:.6rem 0; }
.msg-bubble a { word-break:break-all; }
.msg-bubble em { opacity: 0.7; }
.stream-cursor::after { content:'▋'; animation:blink .7s step-start infinite; font-size:.85em; color:var(--accent); }
@keyframes blink { 50% { opacity:0; } }
.msg-instruction-badge { max-width:9rem; min-width:0; cursor:help; }
.msg-instruction-text { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-style:italic; min-width:0; }
.msg-goal-badge { max-width:9rem; min-width:0; cursor:help; color:var(--accent); }
.msg-goal-text { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-style:italic; min-width:0; }
.msg-reasoning { margin:.1rem 0 .5rem; border-left:2px solid var(--border); background:var(--bg3); border-radius:0 4px 4px 0; }
.msg-reasoning > summary { list-style:none; cursor:pointer; user-select:none; padding:.3rem .6rem; font-size:11px; font-weight:600;
  letter-spacing:.04em; text-transform:uppercase; color:var(--text3); display:flex; align-items:center; gap:.35rem; }
.msg-reasoning > summary::-webkit-details-marker { display:none; }
.msg-reasoning > summary::after { content:'▸'; font-size:9px; opacity:.7; margin-left:auto; transition:transform .15s; }
.msg-reasoning[open] > summary::after { transform:rotate(90deg); }
.msg-reasoning > summary:hover { color:var(--text2); }
.msg-reasoning .reason-body { padding:.1rem .65rem .5rem; font-size:12.5px; color:var(--text2); opacity:.85; }
.msg-reasoning .reason-body > :first-child { margin-top:0; }
.msg-reasoning .reason-body > :last-child { margin-bottom:0; }
.msg-hidden-badge { opacity:.75; }
/* Dims the whole message when it's hidden from the LLM, so that's visible at
   a glance rather than only via the badge — :has(), same pattern as
   .msg-act-last-only above. */
//.msg:has(.msg-hidden-badge) { opacity:.5; }
.msg:has(.msg-hidden-badge) { position: relative; }
.msg:has(.msg-hidden-badge)::after {content: '', position: absolute; top: 0; left: 0; width: 100%; height: 100%; opacity: 0.5; }
.msg:has(.msg-hidden-badge) .gen-media { z-index: 1; }
.msg-collapse-btn { display:none; background:none; border:none; color:var(--text3); font-size:12px; cursor:pointer; padding:.3rem 0; margin-top:.2rem; width:100%; text-align:center; }
.msg-collapse-btn:hover { color:var(--text2); }
.msg-body.is-tall .msg-collapse-btn { display:block; }
.msg-body.is-tall.is-collapsed .msg-bubble { max-height:480px; overflow:hidden; mask-image:linear-gradient(to bottom, black 65%, transparent 100%); -webkit-mask-image:linear-gradient(to bottom, black 65%, transparent 100%); }

/* Scene banding for in-scene messages lives in scene.css alongside the markers. */

.typing { display:flex; gap:5px; padding:.35rem 0; }
.typing-dot { width:7px; height:7px; border-radius:50%; background:var(--text3); animation:bounce 1.2s infinite; }
.typing-dot:nth-child(2) { animation-delay:.2s; }
.typing-dot:nth-child(3) { animation-delay:.4s; }
@keyframes bounce { 0%,60%,100% { transform:translateY(0); opacity:.35; } 30% { transform:translateY(-5px); opacity:1; } }

.status-bar { padding:.15rem .9rem; font-size:11.5px; background: beige; color: red; flex-shrink:0; min-height:0; }

#input-area { padding:.5rem .7rem calc(.55rem + env(safe-area-inset-bottom)); border-top:1px solid var(--border); background:var(--bg2); flex-shrink:0; }
#attach-row { display:flex; gap:.4rem; flex-wrap:wrap; margin-bottom:.4rem; }
.att-wrap { position:relative; display:inline-block; }
.att-thumb { width:54px; height:54px; border-radius:3px; object-fit:cover; border:2px solid var(--border); display:block; }
.att-rm { position:absolute; top:-5px; right:-5px; width:16px; height:16px; border-radius:50%; background:#c0392b; color:#fff;
  font-size:10px; line-height:16px; text-align:center; border:none; cursor:pointer; padding:0; }
#input-row { display:flex; align-items:flex-end; gap:.4rem; }
#msg-ta { flex:1; border-radius:var(--r); resize:none; min-height:38px; max-height:200px; overflow-y:auto; padding:.5rem .7rem; line-height:1.5; background:var(--input-bg); border:1.5px solid var(--border); color:var(--text); outline:none; }
#msg-ta:focus { border-color:var(--accent); }
/* The box is one line tall until you type into it, so a placeholder that wraps
   is half-hidden — keep it on a single line and clip instead. */
#msg-ta::placeholder { color:var(--text3); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
#attach-btn { width:38px; height:38px; padding:0; font-size:17px; flex-shrink:0; }
#file-in { display:none; }
#send-btn { height:38px; padding:0 1rem; flex-shrink:0; }
/* Swapped in while the composer is repurposed away from plain chat (e.g. Auto-RPG's
   direction prompt, see enterComposerMode) — green instead of the default accent so
   it's obvious Send won't just post a message right now. */
#send-btn.composer-mode-active { background:#27ae60; border-color:transparent; color:#fff; }
#send-btn.composer-mode-active:hover { background:#219150; }
#stop-btn { height:38px; padding:0 1rem; flex-shrink:0; background:#c0392b; border-color:transparent; color:#fff; display:none; }
#stop-btn:hover { background:#a93226; }
#ctx-counter { font-size:11px; color:var(--text3); text-align:right; margin-top:.3rem; min-height:1em; }
#msg-history-drop { position:absolute; bottom:calc(100% + 4px); left:0; right:0; max-height:220px; overflow-y:auto;
  background:var(--bg2); border:1.5px solid var(--accent); border-radius:var(--r); z-index:200; box-shadow:0 4px 16px #0004; }
#msg-history-drop button { display:block; width:100%; text-align:left; padding:.45rem .75rem; border:none; background:none;
  color:var(--text); font-size:13px; cursor:pointer; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
#msg-history-drop button:hover { background:var(--bg3); }
#msg-history-drop .mh-empty { padding:.5rem .75rem; color:var(--text3); font-size:12px; }
#cmd-complete-drop { position:absolute; bottom:calc(100% + 4px); left:0; right:0; max-height:220px; overflow-y:auto;
  background:var(--bg2); border:1.5px solid var(--accent); border-radius:var(--r); z-index:200; box-shadow:0 4px 16px #0004; }
#cmd-complete-drop button { display:flex; align-items:baseline; gap:.5rem; width:100%; text-align:left; padding:.4rem .75rem; border:none; background:none;
  color:var(--text); font-size:13px; cursor:pointer; white-space:nowrap; }
#cmd-complete-drop button code { color:var(--accent); }
#cmd-complete-drop button .cmd-desc { color:var(--text3); font-size:11.5px; overflow:hidden; text-overflow:ellipsis; }
#cmd-complete-drop button:hover, #cmd-complete-drop button.selected { background:var(--bg3); }

/* ══════ CAST BAR (multi-character quick replies) ══════ */
/* Collapse trigger for narrow screens — hidden on wide screens where the
   bar just shows inline (see the max-width:640px block below). */
#cast-toggle-btn { display:none; width:38px; height:38px; padding:0; font-size:15px; flex-shrink:0; }
#cast-bar { display:flex; align-items:center; flex-wrap:wrap; gap:.35rem; margin-bottom:.45rem; }
.cast-group { display:flex; align-items:center; gap:.35rem; flex-wrap:wrap; }
.cast-group.extras { margin-left:auto; padding-left:.6rem; border-left:1px solid var(--border); }
.cast-chip { display:inline-flex; align-items:center; gap:.35rem; padding:.22rem .5rem .22rem .3rem; border-radius:4px;
  background:var(--bg3); border:1px solid var(--border); color:var(--text); font-size:12px; font-weight:500; line-height:1;
  cursor:pointer; white-space:nowrap; }
.cast-chip:hover { border-color:var(--accent); background:var(--border); }
.cast-chip:disabled { opacity:.5; cursor:default; }
.cc-av { width:18px; height:18px; border-radius:50%; flex-shrink:0; background:var(--bg2); display:flex;
  align-items:center; justify-content:center; font-size:11px; overflow:hidden; background-size:cover; background-position:center; }
.cast-chip.add { padding:.22rem .55rem; color:var(--text2); border-style:dashed; }
.cast-chip.add:hover { color:var(--accent); }
.cast-chip.special { color:var(--text2); }
.cast-chip.special .cc-av { background:transparent; }
/* Auto-RPG (rpg-director.js): the toggle's highlighted "on" state. */
.cast-chip.rpg-active { color:var(--text); border-color:var(--accent); background:var(--border); }
/* …and the "armed" state in between a click and answering the opening-goal
   prompt the composer swaps in — dashed rather than solid so it doesn't read
   as fully "on" yet. */
.cast-chip.rpg-armed { color:var(--text); border-color:var(--accent); border-style:dashed; }

/* Read-only sibling of .cast-chip — same avatar+name chip, but static: no
   cursor/hover, since renderCharacterStrip() (app-render.js) never wires an
   onclick. */
.char-strip { display:flex; align-items:center; gap:.35rem; flex-wrap:wrap; }
.char-strip-item { display:inline-flex; align-items:center; gap:.35rem; padding:.22rem .5rem .22rem .3rem; border-radius:4px;
  background:var(--bg3); border:1px solid var(--border); color:var(--text); font-size:12px; font-weight:500; line-height:1; white-space:nowrap; }

/* 🖼 Image / 🎬 Video split button (media.js, media-styles.js's ▾ one-shot
   style picker) — a .cast-chip that's a div, not a button: padding moves to
   its two real <button> children so each half is its own click target, with
   overflow:hidden keeping their square corners inside the chip's rounding. */
.cast-chip.split { padding:0; overflow:hidden; }
.cast-chip.split > button { display:inline-flex; align-items:center; gap:.35rem; height:100%;
  border:none; background:none; color:inherit; font:inherit; cursor:pointer; padding:.22rem .5rem .22rem .3rem; }
.cast-chip.split > button:hover:not(:disabled) { background:var(--border); }
.cast-chip.split > button:disabled { opacity:.5; cursor:default; }
.cast-chip.split > .cast-chip-caret { padding:.22rem .5rem; border-left:1px solid var(--border); color:var(--text2); font-size:11px; }
/* Fixed-position popup, not the app's usual #input-area-anchored idiom (see
   #cast-menu/#cmd-complete-drop below) — the caret this hangs off can sit
   anywhere pinned cast-bar chips do, not just a fixed right edge. */
#style-pick-menu { position:fixed; z-index:200; min-width:150px; max-width:240px; max-height:min(50vh, 320px); overflow-y:auto; padding:.25rem;
  background:var(--bg2); border:1.5px solid var(--accent); border-radius:var(--r); box-shadow:0 4px 16px #0004; display:none; }
#style-pick-menu button { display:block; width:100%; text-align:left; padding:.4rem .6rem; border:none; border-radius:4px; background:none;
  color:var(--text); font-size:12.5px; cursor:pointer; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
#style-pick-menu button:hover { background:var(--bg3); }

/* ⚒ Tools — the overflow chip. Only pinned extras (a running scene, a finished
   scan) sit beside it in the bar; the rest live in #cast-menu, so the bar stays
   one row however many modules register a chip. */
.cast-chip.cast-more { padding-right:.4rem; }
.cast-more-caret { font-size:9px; opacity:.6; }
.cast-chip.cast-more[aria-expanded="true"] { border-color:var(--accent); background:var(--border); color:var(--text); }
/* Opens upward, over the feed, so it never pushes the composer around. Anchored
   to #input-area (position:relative, set inline in index.html) — see the
   #cmd-complete-drop / #msg-history-drop pair above for the same idiom. */
/* right:.7rem, not 0: an absolutely-positioned child resolves against the
   padding box, and #input-area's .7rem side padding is what the cast bar's own
   right edge sits on — 0 would hang the menu past every control it belongs to. */
#cast-menu { position:absolute; bottom:calc(100% + 4px); right:.7rem; z-index:200; min-width:210px; max-width:min(280px, calc(100vw - 1.6rem));
  max-height:min(60vh, 420px); overflow-y:auto; padding:.25rem;
  background:var(--bg2); border:1.5px solid var(--accent); border-radius:var(--r); box-shadow:0 4px 16px #0004; }
/* The chips are the very same nodes the bar would have shown — relaid as
   full-width rows rather than rebuilt, so each keeps its own handler, title
   and live label ("Drafts (3)", "Scanning…"). */
#cast-menu .cast-chip { display:flex; width:100%; gap:.5rem; padding:.45rem .5rem; border:none; border-radius:4px;
  background:none; color:var(--text); font-size:13px; line-height:1.3; text-align:left; }
#cast-menu .cast-chip:hover:not(:disabled) { background:var(--bg3); color:var(--text); }
#cast-menu .cast-chip .cc-av { width:20px; height:20px; font-size:13px; }

/* ══════ BRANCHING ("continue from here" insert mode) ══════ */
#insert-banner { display:flex; align-items:center; gap:.6rem; margin-bottom:.5rem; padding:.4rem .6rem;
  border:1px solid var(--accent); border-radius:4px; background:var(--bg3); }
#insert-banner .insert-banner-text { font-size:12px; color:var(--text); flex:1; }
#insert-banner .btn { flex-shrink:0; }
.cast-manage-row { display:flex; align-items:center; justify-content:space-between; gap:.5rem; padding:.5rem .6rem;
  border:1px solid var(--border); border-radius:var(--r); margin-bottom:.4rem; }
.cast-manage-row .cm-info { display:flex; align-items:center; gap:.5rem; min-width:0; }
.cast-manage-row .cm-name { font-weight:500; font-size:13px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.cast-manage-row .cm-main { font-size:10px; }

/* Scratchpad rows live in scratchpad.css. */

/* ══════ TOAST ══════ */
.app-toast { position:fixed; top:.9rem; left:50%; transform:translateX(-50%); z-index:1100;
  display:flex; align-items:center; gap:.7rem; padding:.5rem .6rem .5rem .9rem;
  background:var(--bg2); border:1.5px solid var(--accent); border-radius:var(--r);
  box-shadow:0 8px 24px #0004; font-size:13px; color:var(--text); animation:app-toast-in .15s ease-out;
  max-width:calc(100vw - 1.6rem); }
@keyframes app-toast-in { from { opacity:0; transform:translateX(-50%) translateY(-8px); } to { opacity:1; transform:translateX(-50%) translateY(0); } }

/* Small grey hint pinned after the last feed message (e.g. auto-reply's
   "waiting for you" notice) — part of the feed's flex column, so it scrolls
   with the conversation instead of floating fixed. */
.feed-tail { color:var(--text3); font-size:12px; padding:0 .3rem; }
/* Auto-RPG's "your move" nudge — same slot, but styled as its own inline
   system-message block (not a small caption) so the handback is impossible to
   miss. Wins over .feed-tail (defined after) on the properties both set. */
.rpg-nudge { display:flex; align-items:center; gap:.55rem; color:var(--text); font-size:15px; font-weight:600;
  background:color-mix(in srgb, var(--accent) 14%, var(--bg2)); border:1px solid color-mix(in srgb, var(--accent) 40%, transparent);
  border-radius:8px; padding:.7rem .9rem; margin:.4rem .3rem 0; }
.rpg-nudge-icon { font-size:20px; line-height:1; flex-shrink:0; }

/* Steering strip (director.js, rpg-director.js) — the goal a running scene or
   an active Auto-RPG session is steering toward, shown and edited in the feed
   itself. Steering is a thing you do repeatedly and mid-story, so it can't sit
   behind ＋ → ⚒ Tools → a chip's own menu on a phone; here it's one tap from
   where the story already is. Same feed-tail slot as .rpg-nudge, but styled as
   a quiet bar rather than a block, so a nudge stacked below it stays the loud
   one. Declared after .feed-tail (and .rpg-nudge) so it wins the properties
   they all set. */
.steer-strip { display:flex; align-items:center; gap:.5rem; flex-wrap:wrap; margin:.4rem .3rem 0;
  padding:.45rem .5rem .45rem .6rem; border-radius:8px; background:var(--bg2); border:1px solid var(--border);
  font-size:12.5px; color:var(--text2); }
.steer-icon { font-size:14px; line-height:1; flex-shrink:0; }
/* flex-basis in ems, not 0: the goal takes the row to itself and pushes the
   buttons onto a second line once it can't fit, rather than crushing to a
   sliver beside them on a narrow screen. */
.steer-goal { flex:1 1 11rem; min-width:0; color:var(--text); overflow:hidden;
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; }
.steer-goal.empty { color:var(--text3); font-style:italic; }
.steer-progress { color:var(--text3); font-variant-numeric:tabular-nums; white-space:nowrap; }
/* The buttons are their own flex item so they wrap as a pair: left to the
   strip's own wrapping they break between themselves at phone widths, leaving
   one button stranded on a line of its own. margin-left:auto keeps them on the
   right while the goal shares their row, and flex-wrap lets the pair drop
   whole to the next line when it no longer does. */
.steer-actions { display:flex; align-items:center; gap:.4rem; margin-left:auto; flex-wrap:wrap; justify-content:flex-end; }
.steer-btn { display:inline-flex; align-items:center; gap:.3rem; padding:.32rem .55rem; border-radius:4px;
  background:var(--bg3); border:1px solid var(--border); color:var(--text); font-size:12px; font-weight:500;
  line-height:1; cursor:pointer; white-space:nowrap; }
.steer-btn:hover:not(:disabled) { border-color:var(--accent); background:var(--border); }
.steer-btn:disabled { opacity:.5; cursor:default; }
/* Icon-only and quieter than ✎/🎭 Run — it ends the goal rather than acting on
   it, so it shouldn't compete with them for attention. */
.steer-btn-dismiss { padding:.32rem .5rem; color:var(--text3); }
.steer-btn-dismiss:hover { color:var(--text); }

/* Modal + busy-overlay live in modal-core.css. */

@media (max-width: 640px) {
  #left-col { position:fixed; left:0; top:0; bottom:0; z-index:500; width:min(85vw,320px); min-width:0; transform:translateX(-102%); }
  #left-col.open { transform:translateX(0); box-shadow:4px 0 24px rgba(0,0,0,.25); }
  #close-left-btn,#open-left-char,#open-left-chat { display:inline-flex !important; }
  #char-grid { grid-template-columns:1fr; }
  /* The "New Character" / "Import Card" tiles share the grid's auto-fill cells —
     on mobile those cells are full-width, so a 96px min-height pushes the first
     real character card below the fold. Lay the two tiles out side-by-side with
     a tap-sized height so they're visible but don't eat the screen. */
  .char-card-action { min-height:48px; flex-direction:row; gap:.5rem; font-size:12px; }
  .char-card-action-icon { font-size:16px; }
  #left-backdrop { display:block; position:fixed; inset:0; z-index:499; background:rgba(0,0,0,.4);
    opacity:0; pointer-events:none; transition:opacity .15s; }
  #left-col.open ~ #left-backdrop { opacity:1; pointer-events:auto; }

  /* Cast bar collapses behind the + toggle; #input-area.cast-open (set in
     app-composer.js) reveals it again. */
  #cast-toggle-btn { display:inline-flex; }
  #cast-bar { display:none !important; }
  #input-area.cast-open #cast-bar { display:flex !important; }
  /* The desktop separator is a vertical rule held in place by margin-left:auto —
     both of which stop meaning anything once the bar wraps to a second line, so
     at this width extras take a full row of their own with a horizontal rule
     above instead. Right-aligned, keeping ⚒ Tools on the send-button side. */
  .cast-group.extras { flex-basis:100%; justify-content:flex-end; margin-left:0;
    padding:.4rem 0 0; border-left:none; border-top:1px solid var(--border); }
  /* Roomier rows where the pointer is a thumb. */
  #cast-menu .cast-chip { padding:.6rem .55rem; }
  /* Likewise for the steering strip's buttons — this is the one director
     control meant to be tapped mid-story, so it gets a real tap target. */
  .steer-btn { padding:.5rem .7rem; }

  #send-btn .btn-label { display:none; }
  #send-btn { width:44px; padding:0; justify-content:center; font-size:22px; }

  /* A modal's action row (Delete, Regenerate, Insert above, …, Cancel, Save)
     needs two or three lines at this width. Let the buttons grow so each line
     fills the modal instead of trailing off ragged against the right edge. */
  .modal-btns > .btn { flex:1 1 auto; justify-content:center; }
}

/* Hover-gated reveal (thr-del, char-edit) is unreachable on touch, which has
   no hover state — always show them when the pointer can't hover. */
@media (hover: none) {
  .thr-del, .char-edit { opacity:1; }
}

/* iOS Safari magnifies the viewport when you focus a text control rendering
   below 16px, and never zooms back out — in the edit-message modal that
   strands Save off screen. Our controls otherwise inherit the UA's ~13px
   default, so pin them to 16px wherever the pointer is touch. */
@media (pointer: coarse) {
  input:not([type=checkbox]):not([type=radio]), textarea, select, #search-input { font-size:16px; }
  /* Comfortable tap targets for the checkboxes that rule no longer grows. */
  .field input[type=checkbox],.field input[type=radio] { width:18px; height:18px; }
}
