:root {
  --bg: #FAFAF7;
  --panel-bg: #FFFFFF;
  --ink: #1A1D1A;
  --ink-soft: #55594F;
  --label: #8A8A82;
  --line: #E7E7E0;
  --green: #1E4D36;
  --green-hover: #173D2B;
  --green-tint: #EEF3EF;
  --red: #B23A2F;
  --red-tint: #FBEAE8;
  --radius: 8px;
  --sidebar-w-left: 340px;
  --icon-rail-w: 72px;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

html, body { height: 100%; }

body {
  background: var(--bg);
  color: var(--ink);
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 14px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
}

button { font-family: inherit; cursor: pointer; }
textarea, input { font-family: inherit; }

/* ---------- App shell ---------- */

.shell {
  display: flex;
  height: 100vh;
  width: 100vw;
}

/* ---------- Icon rail: Feed & Chat / Brain / Deposits. Always visible,
   fixed width, never collapsible. The two panels to its right (tree/list
   panel, main panel) are resized by dragging the divider between them,
   see .divider below; there is no collapse/expand control anywhere in
   this layout. ---------- */

.icon-rail {
  width: var(--icon-rail-w);
  flex-shrink: 0;
  height: 100%;
  background: var(--panel-bg);
  border-right: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 14px 6px;
  gap: 6px;
  /* The node filter list below can add more rows than the rail's nominal
     height comfortably fits (Brain parent nodes are created/deleted
     freely, unlike a fixed project list); scroll the rail itself rather
     than letting it overflow into the panel beside it. */
  overflow-y: auto;
}
/* Icon-plus-label pairs stacked vertically, not icon-only circles or
   plain text tabs (matches a typical app-rail nav pattern: small icon on
   top, short label underneath). */
.icon-rail-btn {
  width: 100%;
  min-height: 52px;
  padding: 8px 4px 7px;
  border-radius: 10px;
  background: none;
  border: none;
  color: var(--ink-soft);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  flex-shrink: 0;
}
.icon-rail-label {
  font-size: 10px;
  font-weight: 600;
  line-height: 1.15;
  text-align: center;
}
.icon-rail-btn:hover { background: var(--bg); color: var(--ink); }
.icon-rail-btn.active { background: var(--green-tint); color: var(--green); }

/* ---------- Node filter list: lives directly in the icon rail's own DOM
   container, right below the Brain button, at rail width (not a wider
   panel styled to look adjacent). Only shown on the Brain tab. Compact
   wrapped text like .icon-rail-label above, no icons; active selection
   distinguished by a background fill, same convention as the rail
   buttons' own .active state. ---------- */
.rail-project-list {
  display: none;
  flex-direction: column;
  gap: 2px;
  width: 100%;
  padding-top: 6px;
  margin-top: 2px;
  border-top: 1px solid var(--line);
  flex-shrink: 0;
}
body.tab-brain .rail-project-list { display: flex; }
.rail-project-item {
  display: block;
  width: 100%;
  background: none;
  border: none;
  border-radius: 6px;
  padding: 5px 2px;
  font-size: 10px;
  font-weight: 600;
  line-height: 1.15;
  text-align: center;
  color: var(--ink-soft);
}
.rail-project-item:hover { background: var(--bg); color: var(--ink); }
.rail-project-item.active { background: var(--green-tint); color: var(--green); }

.sidebar {
  background: var(--panel-bg);
  height: 100%;
  overflow: hidden;
  flex-shrink: 0;
  width: var(--sidebar-w-left);
}

.sidebar-inner {
  height: 100%;
  display: flex;
  flex-direction: column;
  width: 100%;
  min-width: 280px;
}

/* Draggable resize handle, the only way to adjust the split between the
   tree/list panel and the main panel (no collapse/expand buttons). Wider
   hit target than its visible line for an easier grab. */
.divider {
  width: 8px;
  flex-shrink: 0;
  cursor: col-resize;
  background: transparent;
  position: relative;
}
.divider::after {
  content: "";
  position: absolute;
  top: 0; bottom: 0; left: 3px;
  width: 2px;
  background: var(--line);
}
.divider:hover::after, .divider.dragging::after { background: var(--green); }

.main-panel-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 16px 20px;
  border-bottom: 1px solid var(--line);
  flex-shrink: 0;
}

/* Persistent app title, always present regardless of which rail tab is
   active (main-panel-head itself never goes away), pinned to the far
   right of the header row. */
.main-panel-head-right {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 10px;
  flex-shrink: 0;
}
.app-brand {
  font-family: 'Fraunces', serif;
  font-weight: 600;
  font-size: 14px;
  color: var(--green);
  white-space: nowrap;
}

.panel-close-btn {
  background: none;
  border: none;
  color: var(--label);
  font-size: 18px;
  line-height: 1;
  width: 24px; height: 24px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.panel-close-btn:hover { color: var(--red); background: var(--red-tint); }

/* ---------- Main panel: Feed & Chat, or Contents/Deposits, always fills
   whatever space isn't claimed by the icon rail and the tree/list panel
   (flex:1, no fixed/max width), so its content never sits narrow inside a
   mostly-empty container. Contents/Deposits can be closed outright (see
   .panel-close-btn above); while closed the tree/list panel expands to
   fill the space it frees, the same way it does when the whole tab has
   no left-panel content (Feed & Chat, see the tab-chat rule below).
   Reopens automatically once a node/item is selected again (app.js). ---- */

.main-panel {
  flex: 1;
  min-width: 0;
  height: 100%;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  background: var(--panel-bg);
}

@media (min-width: 901px) {
  body.contents-closed.tab-brain .main-panel,
  body.contents-closed.tab-brain #divider-left,
  body.contents-closed.tab-documents .main-panel,
  body.contents-closed.tab-documents #divider-left,
  body.contents-closed.tab-deposits .main-panel,
  body.contents-closed.tab-deposits #divider-left { display: none; }

  body.contents-closed.tab-brain .sidebar-left,
  body.contents-closed.tab-documents .sidebar-left,
  body.contents-closed.tab-deposits .sidebar-left {
    flex: 1 1 auto;
    width: auto !important;
    max-width: none;
  }
}

/* ---------- Sidebar chrome ---------- */

.sidebar-brand {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 16px 18px;
  border-bottom: 1px solid var(--line);
  flex-shrink: 0;
}

.panel-heading {
  font-family: 'Fraunces', serif;
  font-weight: 600;
  font-size: 16px;
  color: var(--green);
}

/* ---------- Mobile-only chrome (topbar, hamburger menu backdrop) -----
   Inert on desktop: display:none here, only ever shown inside the mobile
   media query below, so none of this affects the desktop layout. ---------- */

.mobile-topbar { display: none; }
.mobile-backdrop { display: none; }

.mobile-icon-btn {
  background: none;
  border: none;
  color: var(--ink);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 8px;
  min-width: 44px;
  min-height: 44px;
  border-radius: 8px;
  flex-shrink: 0;
}
.mobile-icon-btn:hover { background: var(--bg); }
.mobile-icon-btn-label { font-size: 13px; font-weight: 600; }

/* Exactly one .tab-panel is ever unhidden at a time; the [hidden] attribute
   (native display:none) is what does the actual hiding, this rule only
   supplies the flex layout for whichever one is currently shown, and is
   deliberately scoped with :not([hidden]) so it never fights that. Same
   trick applies below to .chat-view, which is also toggled via [hidden]
   but (unlike #right-panel-body) needs its own flex layout when visible. */
.tab-panel:not([hidden]) {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}

/* ---------- Deposits: flat list of raw ingested files ---------- */
.tab-panel-deposits { padding: 14px 18px; overflow: hidden; }

/* ---------- Documents tab: searchable flat list of real uploaded
   documents, same list-row shape as Deposits below. ---------- */
.tab-panel-documents { padding: 14px 18px; overflow: hidden; }
.documents-search-row { flex-shrink: 0; margin-bottom: 10px; }
#documents-search-input { width: 100%; }
.deposits-hint { font-size: 12px; color: var(--label); margin-bottom: 10px; }
.deposits-list { flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 4px; margin: 0 -6px; }
.deposit-row {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  padding: 8px 10px;
  border: 1px solid transparent;
  border-radius: 6px;
  background: none;
  text-align: left;
  width: 100%;
}
.deposit-row:hover { background: var(--bg); }
.deposit-row.selected-item { background: var(--green-tint); border-color: var(--green); }
.deposit-title {
  font-size: 12.5px; font-weight: 500; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%;
}
.deposit-meta { font-size: 11px; color: var(--label); }

/* Toggled by JS to hide the Brain-specific breadcrumb/back-button while
   the Feed & Chat or Deposits tab is active (neither has a breadcrumb
   concept). Scoped to desktop widths only so a viewport resize down to
   mobile (which never exposes tab switching, see the mobile media query
   at the bottom of this file) can never leave these hidden while mobile's
   own Contents view is showing real Brain content. */
@media (min-width: 901px) {
  .desktop-tab-hidden { display: none !important; }
}

/* Feed & Chat has no left-panel content at all (a tree/list panel next to
   an empty chat would just waste the space), so the tree/list panel and
   its divider are hidden outright for that tab and the main panel
   (already flex:1) simply fills the freed width. Desktop-scoped for the
   same reason as .desktop-tab-hidden above. */
@media (min-width: 901px) {
  body.tab-chat .sidebar-left, body.tab-chat #divider-left { display: none; }
}

/* ---------- Left panel: the Brain tree diagram fills it entirely, no
   Recall section (removed: Download now lives directly in Contents,
   see .asset-link below). ---------- */

.pane {
  flex: 1 1 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.pane-brain { padding: 10px 6px; }

.brain-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 0 6px 6px; flex-shrink: 0; flex-wrap: wrap; }
.brain-toolbar-group { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.icon-btn {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  background: none;
  border: 1px solid var(--line);
  border-radius: 6px;
  color: var(--ink-soft);
  font-size: 11px;
  font-weight: 600;
  padding: 4px 8px;
}
.icon-btn:hover:not(:disabled) { color: var(--green); border-color: var(--green); }
.icon-btn:disabled { opacity: 0.35; cursor: not-allowed; }
#tb-delete-node:hover:not(:disabled) { color: var(--red); border-color: var(--red); }

/* Back navigation, shared look for the main panel head's back button */
.tree-back-btn {
  background: none;
  border: 1px solid var(--line);
  border-radius: 6px;
  color: var(--ink-soft);
  font-size: 11px;
  font-weight: 600;
  padding: 3px 8px;
  flex-shrink: 0;
}
.tree-back-btn:hover:not(:disabled) { color: var(--green); border-color: var(--green); }
.tree-back-btn:disabled { opacity: 0.35; cursor: not-allowed; }

/* Breadcrumb trail in the main panel (Brain tab only) */
.breadcrumb {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px;
  padding: 10px 20px 0;
  flex-shrink: 0;
}
.crumb {
  background: none;
  border: none;
  color: var(--label);
  font-size: 12px;
  padding: 2px 2px;
}
.crumb:not(:disabled):hover { color: var(--green); text-decoration: underline; }
.crumb.current { color: var(--ink); font-weight: 600; }
.crumb-sep { color: var(--line); font-size: 12px; }

.mode-panel { margin-bottom: 4px; }

@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.35; } }

.audio-status { font-size: 12px; color: var(--label); }
.audio-status.ready { color: var(--green); font-weight: 500; }
.audio-status.error { color: var(--red); }

.drops-section { margin-top: 12px; flex-shrink: 0; }
.drop-list { display: flex; flex-direction: column; gap: 6px; max-height: 140px; overflow-y: auto; }
.drop-row {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  padding: 7px 10px;
  border-radius: 6px;
  background: var(--bg);
  border: 1px solid var(--line);
  font-size: 12px;
}
.drop-preview { color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.drop-status { color: var(--label); flex-shrink: 0; }
.drop-row.done .drop-status { color: var(--green); }
.drop-row.failed .drop-status { color: var(--red); }

/* ---------- Brain tree: ONE merged horizontal node-link diagram (D3)
   covering every top-level folder as sibling root branches, not separate
   boxed canvases. Pan/zoom (d3.zoom: click-drag to pan, wheel/pinch to
   zoom) inside one fixed-height viewport, not a scrollbar-driven one, so
   branching in any direction stays reachable without fighting a
   scrollbar. Loading/empty placeholders reuse the same classes Deposits
   uses for the same states. ---------- */

.brain-tree-viz { flex: 1; overflow: hidden; padding: 4px 4px; display: flex; }
/* overflow:hidden, not auto: panning happens via the SVG's own transform
   (d3.zoom), a native scrollbar here would just double up on that. */
.brain-tree-svg-wrap {
  flex: 1;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--bg);
}
.brain-tree-svg-wrap svg { display: block; }

.tree-link {
  fill: none;
  stroke: var(--line);
  stroke-width: 1.5px;
}


/* Nodes render as a pill (rounded rect) with the (truncated) title
   inscribed inside, not a small dot with an external label; width is
   sized per-node to fit its own text (see the getBBox measurement in
   app.js), not a fixed width, so short titles don't look artificially
   wide and truncated ones never get cut off mid-pill. The full,
   untruncated title is available as a native SVG <title> tooltip on
   hover, and always shown in full once a node is selected (Contents). */
.tree-node-pill {
  fill: var(--panel-bg);
  stroke: var(--green);
  stroke-width: 1.5px;
}
.tree-node-pill.root { fill: var(--green-tint); }
.tree-node-g:hover .tree-node-pill { stroke: var(--green-hover); }
.tree-node-g:hover .tree-node-label { fill: var(--green); }
.tree-node-g.selected .tree-node-pill { fill: var(--green); stroke: var(--green); }
.tree-node-g.selected .tree-node-label { font-weight: 600; fill: #fff; }

/* High-fan-out nodes (10+ children) collapse into a single "N items"
   cluster badge instead of spreading every child into its own branch
   line; clicking it opens a scrollable list in Contents rather than
   drilling into the diagram (see onTreeNodeClick/showClusterList). Styled
   distinctly (dashed, muted) so it reads as a summary, not real content. */
.tree-node-g.cluster .tree-node-pill { fill: var(--bg); stroke: var(--label); stroke-dasharray: 3 2; }
.tree-node-g.cluster .tree-node-label { fill: var(--label); font-style: italic; }
.tree-node-g.cluster:hover .tree-node-label { fill: var(--ink-soft); }

.tree-node-label {
  font-size: 11px;
  fill: var(--ink);
  font-family: 'Inter', system-ui, sans-serif;
}

.tree-empty { color: var(--label); font-size: 12px; padding: 4px 6px 4px 8px; }
.tree-loading { color: var(--label); font-size: 12px; padding: 4px 6px 4px 8px; }

/* ---------- Chat view: message history + input pinned at the bottom.
   No max-width cap: fills the main panel's full width like every other
   view does, so it never sits narrow inside a wide container. ---------- */

.chat-view:not([hidden]) {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  padding: 20px 24px;
}

.chat-window {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--panel-bg);
  padding: 18px 20px;
  overflow: hidden;
}

.chat-messages {
  flex: 1;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 4px 2px 12px;
}

/* ---------- Empty state: greeting + tappable suggestion chips, shown
   only until the first message is sent ---------- */
.chat-empty-state {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 16px;
  padding: 24px 12px;
  text-align: center;
}
.chat-empty-welcome {
  font-family: 'Fraunces', serif;
  font-weight: 700;
  font-size: 22px;
  color: var(--ink);
  margin: 0;
}
.chat-empty-greeting {
  font-family: 'Fraunces', serif;
  font-weight: 600;
  font-size: 17px;
  color: var(--ink-soft);
  margin: 0;
}
.chat-suggestions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  max-width: 480px;
}
.chat-chip {
  border: 1px solid var(--line);
  background: var(--bg);
  color: var(--ink-soft);
  border-radius: 16px;
  padding: 8px 14px;
  font-size: 12px;
  text-align: left;
}
.chat-chip:hover { border-color: var(--green); color: var(--green); background: var(--green-tint); }

.chat-msg { padding: 10px 14px; border-radius: 12px; font-size: 14px; line-height: 1.5; max-width: 85%; white-space: pre-wrap; }
.chat-msg.user { align-self: flex-end; background: var(--green); color: #fff; border-bottom-right-radius: 3px; }
.chat-msg.assistant { align-self: flex-start; background: var(--panel-bg); border: 1px solid var(--line); border-bottom-left-radius: 3px; }
.chat-msg.pending { align-self: flex-start; color: var(--label); font-style: italic; background: none; }
.chat-msg.error { align-self: flex-start; background: var(--red-tint); color: var(--red); }
.chat-msg.action { align-self: flex-start; background: var(--green-tint); color: var(--green); font-size: 13px; border: 1px solid var(--line); }
.chat-msg.action.chat-action-done { background: none; border-style: dashed; }
.chat-msg.action.chat-action-failed { background: var(--red-tint); color: var(--red); }

/* Markdown rendered inside an assistant bubble (see renderChatMarkdown):
   font-size/line-height are deliberately left alone here so they keep
   inheriting the 14px/1.5 set on .chat-msg above, only spacing/indentation
   changes between block types. */
.chat-msg.assistant p { margin: 0 0 8px; }
.chat-msg.assistant ul, .chat-msg.assistant ol { margin: 0 0 8px; padding-left: 20px; }
.chat-msg.assistant li { margin-bottom: 3px; }
.chat-msg.assistant li:last-child { margin-bottom: 0; }
.chat-msg.assistant .chat-md-heading { font-weight: 700; margin: 4px 0 6px; }
.chat-msg.assistant strong { font-weight: 700; }
.chat-msg.assistant em { font-style: italic; }
.chat-msg.assistant code {
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 1px 5px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.9em;
}
.chat-msg.assistant a { color: var(--green); text-decoration: underline; text-decoration-color: var(--line); }
.chat-msg.assistant a:hover { text-decoration-color: var(--green); }
.chat-msg.assistant > *:first-child { margin-top: 0; }
.chat-msg.assistant > *:last-child { margin-bottom: 0; }

.chat-form { flex-shrink: 0; }
.chat-input-wrap {
  display: flex;
  align-items: center;
  gap: 8px;
  border: 1px solid var(--line);
  border-radius: 24px;
  /* Tall enough for the placeholder (or typed text) to wrap onto two
     full lines without clipping; align-items:center above is what keeps
     the +/send buttons centered on this as it grows taller still. */
  min-height: 72px;
  padding: 6px;
  background: var(--panel-bg);
}
.chat-input {
  flex: 1;
  border: none;
  background: none;
  /* 16px, not 14px: also the minimum that keeps iOS Safari from
     auto-zooming the whole page in when the field gets focus. */
  font-size: 16px;
  line-height: 1.4;
  color: var(--ink);
  min-width: 0;
  resize: none;
  overflow-y: auto;
  max-height: 200px;
  padding: 8px 2px;
  font-family: inherit;
}
.chat-input:focus { outline: none; }
.chat-input:disabled { color: var(--label); }
/* The placeholder is allowed to wrap like any other content in the
   textarea (this is the default; stated explicitly so a future change
   doesn't reintroduce nowrap/ellipsis truncation here). Long copy wraps
   onto its second line instead of getting cut off. */
.chat-input::placeholder { white-space: normal; overflow: visible; text-overflow: clip; }
.chat-send-btn {
  border: none;
  width: 32px; height: 32px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
  background: var(--green);
  color: #fff;
}
.chat-send-btn:hover { background: var(--green-hover); }
.chat-send-btn:disabled { opacity: 0.5; cursor: not-allowed; }

/* ---------- Feed into Brain (left) and Ask Vectram (right): parallel
   peer input blocks side by side on desktop/tablet, sharing this one row
   so together they take noticeably less vertical space than the old
   stacked layout did, leaving more room for the greeting/chips above.
   Top-aligned (not stretched to match height) since Feed into Brain's
   mode panel + drop history is naturally much taller than Ask Vectram's
   single input bar. Reverts to stacked (Feed above Ask) below 901px, see
   the mobile media query. ---------- */
.feed-ask-row {
  flex-shrink: 0;
  display: flex;
  /* stretch, not flex-start: Feed into Brain's height varies by mode
     (Record/Text/Upload) and grows with drop history, Ask Vectram is
     normally just a short input bar; stretch makes both cards match
     whichever is taller instead of leaving Ask Vectram visibly shorter. */
  align-items: stretch;
  gap: 14px;
  margin-top: 4px;
}
.feed-ask-row .feed-block { flex: 1; min-width: 0; margin-top: 0; }
.feed-ask-row .chat-form {
  flex: 1.15;
  min-width: 0;
  display: flex;
  align-items: stretch;
}
/* The *visible* bordered/background pill is .chat-input-wrap, not
   .chat-form itself (a plain unstyled <form>): stretching only the form
   left the actual box people see still short, floating centered inside
   invisible extra space, which still reads as "Ask Vectram is smaller"
   even though the form element's own height matched. Stretching this
   instead makes the visible box itself match feed-block's height; its
   own internal align-items:center (set further down) keeps the
   icon/textarea/button centered within that taller box rather than
   pinned to the top. */
.feed-ask-row .chat-form .chat-input-wrap { flex: 1; }

/* ---------- Feed into Brain: always-visible capture block, matching the
   Ask Vectram input's own visual language (white background, solid
   border, same corner radius) so the two read as symmetrical peers side
   by side, not a primary input plus a secondary/muted attachment
   affordance. There is no destination picker here (unlike the sibling
   Vectram apps), since this vault has only one possible destination for
   any drop. ---------- */

.feed-block {
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
  border: 1px solid var(--line);
  border-radius: 24px;
  background: var(--panel-bg);
  padding: 14px 16px;
  margin-top: 4px;
}
.feed-block-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--label);
}
.feed-mode-btns {
  display: flex;
  gap: 6px;
}
/* Text-only, no icons: three equal-width plain buttons, not tabs styled
   to look like a segmented control. */
.feed-mode-btn {
  flex: 1;
  text-align: center;
  background: none;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 8px 10px;
  font-size: 13px;
  font-weight: 500;
  color: var(--ink-soft);
}
.feed-mode-btn:hover { border-color: #CFCFC6; color: var(--ink); }
.feed-mode-btn.active { background: var(--green); border-color: var(--green); color: #fff; }

/* ---- Record: one compact row, icon-only toggle + inline status text.
   No separate "Record" sub-button duplicating the outer mode button's own
   label, and no standalone submit step: stopping the recording submits
   immediately (see toggleRecord in app.js). ---- */
.feed-record-row { display: flex; align-items: center; gap: 10px; }
.feed-record-btn {
  flex-shrink: 0;
  width: 30px; height: 30px;
  border-radius: 50%;
  border: 1px solid var(--red);
  background: var(--red-tint);
  display: flex; align-items: center; justify-content: center;
}
.feed-record-btn:hover { background: #f7dcd9; }
.feed-record-btn-dot { width: 10px; height: 10px; border-radius: 50%; background: var(--red); }
.feed-record-btn.recording {
  background: var(--red);
  animation: pulse 1.2s infinite;
}
.feed-record-btn.recording .feed-record-btn-dot { border-radius: 3px; background: #fff; }

/* ---- Text / Upload caption: a pill input row matching the Ask Vectram
   input's own chat-input-wrap treatment, with a small inline send arrow
   instead of a standalone "Feed to Brain" CTA block. ---- */
/* :not([hidden]), not a bare .feed-input-row: #feed-caption-row is toggled
   via the [hidden] attribute (see feedCaptionRow.hidden in app.js), and an
   unconditional `display: flex` here has equal specificity to the [hidden]
   attribute's own UA-stylesheet `display: none` and was winning by source
   order, showing the caption box even with no file chosen yet. Same fix
   already applied to .tab-panel/.chat-view elsewhere in this file. */
.feed-input-row:not([hidden]) {
  display: flex;
  /* flex-end, not center: the textarea now grows well past one line by
     default, so the send button should anchor to its bottom edge rather
     than float centered against a box taller than it. */
  align-items: flex-end;
  gap: 6px;
  border: 1px solid var(--line);
  border-radius: 18px;
  padding: 4px 4px 4px 12px;
  background: var(--bg);
}
.feed-text-input {
  display: block;
  flex: 1;
  min-width: 0;
  border: none;
  background: none;
  padding: 6px 0;
  font-size: 14px;
  line-height: 1.4;
  color: var(--ink);
  font-family: inherit;
  resize: none;
  /* Multi-line by default (~3-4 visible lines), not a cramped single-line
     box: min-height is the resting floor, max-height the auto-grow
     ceiling (see autoResizeTextarea in app.js), matching .chat-input's
     own grow-then-scroll behavior below. */
  min-height: 82px;
  max-height: 200px;
  overflow-y: auto;
}
.feed-text-input:focus { outline: none; }
.feed-input-row:focus-within { border-color: var(--green); }

.feed-send-btn {
  flex-shrink: 0;
  margin-bottom: 2px;
  width: 26px; height: 26px;
  border-radius: 50%;
  border: none;
  background: var(--green);
  color: #fff;
  display: flex; align-items: center; justify-content: center;
}
.feed-send-btn:hover:not(:disabled) { background: var(--green-hover); }
.feed-send-btn:disabled { opacity: 0.45; cursor: not-allowed; }

/* ---- Upload: one row for the file picker + status, a second (only once
   a file is actually chosen) for the optional caption + send. Never more
   than two rows total. ---- */
.feed-file-row { display: flex; align-items: center; gap: 10px; }
.feed-file-label {
  flex-shrink: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 6px 12px;
  font-size: 12px;
  font-weight: 500;
  color: var(--ink-soft);
  background: var(--bg);
}
.feed-file-label:hover { border-color: #CFCFC6; color: var(--ink); }
#file-mode .feed-input-row { margin-top: 6px; }

/* ---------- Contents / Deposits view ---------- */

.right-panel-body { flex: 1; overflow-y: auto; padding: 16px 24px; }

.muted { color: var(--label); }
.small { font-size: 12px; }

.hub-head { display: flex; flex-direction: column; gap: 4px; margin-bottom: 10px; }
.hub-name { font-family: 'Fraunces', serif; font-weight: 600; font-size: 18px; color: var(--ink); }
.hub-subtitle { font-size: 12px; color: var(--label); }
.hub-summary { margin-bottom: 12px; font-size: 13px; color: var(--ink-soft); }
.hub-summary p { margin-bottom: 8px; }
.hub-summary ul { padding-left: 18px; margin-bottom: 8px; }

/* A curated pull of specific, checkable facts (see the vault's "Key
   claims" convention), surfaced as its own labeled section rather than
   left invisible inside the raw hub file. */
.key-claims { margin-bottom: 12px; }
.key-claims-heading {
  font-size: 12px;
  font-weight: 600;
  color: var(--label);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  margin-bottom: 6px;
}
.key-claims-body { font-size: 13px; color: var(--ink-soft); }
.key-claims-body ul { padding-left: 18px; margin-bottom: 8px; }
.key-claims-body p { margin-bottom: 8px; }

.page-detail p { margin-bottom: 10px; font-size: 13px; line-height: 1.6; }
.page-detail ul { padding-left: 18px; margin-bottom: 10px; }
.page-detail .hub-name { margin-bottom: 12px; }

.hub-summary a, .page-detail a, .key-claims-body a {
  color: var(--green);
  text-decoration: underline;
  text-decoration-color: var(--line);
  text-underline-offset: 2px;
}
.hub-summary a:hover, .page-detail a:hover, .key-claims-body a:hover { text-decoration-color: var(--green); }

/* Download is a direct action right where content is viewed, no separate
   Recall staging area to add something to first. */
.asset-links { display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0 16px; }
.asset-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 6px 10px;
  font-size: 12px;
  color: var(--green);
  text-decoration: none;
  background: var(--green-tint);
}
.asset-link:hover { background: #e2ece5; }
.asset-link.btn-like { border: none; cursor: pointer; }
.asset-link.destructive { color: var(--red); background: var(--red-tint); }
.asset-link.destructive:hover { background: #f6d9d5; }

/* Where a Deposits item was filed to, alongside its Download link. No
   "Change" override control here (unlike the sibling Vectram apps with
   more than one category), there is nowhere else it could be moved to. */
.deposit-routed-tag {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  border-radius: 6px;
  padding: 6px 10px;
  font-size: 12px;
  color: var(--ink-soft);
  background: var(--bg);
}
.deposit-routed-tag.unrouted { color: var(--label); font-style: italic; }
.deposit-routed-tag.unclassified { color: var(--red); background: var(--red-tint); }
.deposit-flag {
  display: inline-block;
  font-size: 10px;
  font-weight: 600;
  color: var(--red);
  background: var(--red-tint);
  border-radius: 4px;
  padding: 1px 5px;
  margin-top: 1px;
}

.bit-view {
  display: flex;
  gap: 16px;
  flex-wrap: wrap;
  align-items: flex-start;
}
.bit-view .bit-image { flex: 1 1 320px; max-width: 100%; }
.bit-view .bit-image img { width: 100%; border-radius: 8px; border: 1px solid var(--line); display: block; }
.bit-view .bit-text { flex: 1 1 320px; min-width: 0; }

.node-list { display: flex; flex-direction: column; gap: 4px; }
.node-row {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--panel-bg);
  text-align: left;
  width: 100%;
}
.node-row:hover { border-color: var(--green); }
.node-title { font-size: 13px; font-weight: 500; color: var(--ink); }
.node-desc { font-size: 11px; color: var(--label); }

/* ---------- Generic modal: create node/page, add connection, delete
   confirm. Built dynamically in app.js (openModal), not static markup. ---- */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(26, 29, 26, 0.35);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 1000;
}
.modal-dialog {
  background: var(--panel-bg);
  border-radius: var(--radius);
  width: 100%;
  max-width: 380px;
  max-height: 80vh;
  display: flex;
  flex-direction: column;
  box-shadow: 0 12px 40px rgba(26, 29, 26, 0.25);
}
.modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 16px;
  border-bottom: 1px solid var(--line);
  flex-shrink: 0;
}
.modal-title { font-family: 'Fraunces', serif; font-weight: 600; font-size: 15px; color: var(--ink); }
.modal-body { padding: 14px 16px; overflow-y: auto; display: flex; flex-direction: column; gap: 10px; }
.modal-text { font-size: 13px; color: var(--ink-soft); }
.modal-input {
  width: 100%;
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 8px 10px;
  font-size: 13px;
  color: var(--ink);
  background: var(--bg);
}
.modal-input:focus { outline: none; border-color: var(--green); }
.modal-footer {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding: 12px 16px;
  border-top: 1px solid var(--line);
  flex-shrink: 0;
}
.modal-btn {
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 6px 14px;
  font-size: 12px;
  font-weight: 600;
  background: none;
  color: var(--ink-soft);
}
.modal-btn-cancel:hover { color: var(--ink); }
.modal-btn-confirm { border-color: var(--green); background: var(--green); color: #fff; }
.modal-btn-confirm:hover { background: var(--green-hover); }
.modal-btn-confirm.danger { border-color: var(--red); background: var(--red); }
.modal-btn-confirm.danger:hover { background: #8f2d24; }
.modal-btn:disabled { opacity: 0.6; cursor: not-allowed; }

.picker-list { max-height: 220px; overflow-y: auto; display: flex; flex-direction: column; gap: 3px; }
.picker-row {
  text-align: left;
  border: 1px solid transparent;
  border-radius: 6px;
  padding: 6px 8px;
  font-size: 12.5px;
  color: var(--ink);
  background: none;
}
.picker-row:hover { background: var(--bg); }
.picker-row.selected-item { background: var(--green-tint); border-color: var(--green); }

/* ---------- Inline content editor: swaps a node/page's rendered body for
   a raw-markdown textarea in place, no modal (content can be long). ---- */
.edit-view { display: flex; flex-direction: column; gap: 10px; }
.edit-textarea {
  width: 100%;
  min-height: 320px;
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 10px 12px;
  font-family: ui-monospace, 'SF Mono', Menlo, monospace;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--ink);
  background: var(--bg);
  resize: vertical;
}
.edit-textarea:focus { outline: none; border-color: var(--green); }
.edit-actions { display: flex; gap: 8px; }

/* ---------- Mobile: single main view (Chat, or a selected Brain/Deposits
   item's detail) below a top bar. The top-left button is a hamburger that
   opens a dropdown menu (the icon rail, reused as its content, hidden
   until opened rather than a persistent bottom bar) listing all three
   destinations, Feed & Chat / Brain / Deposits; it swaps to a back arrow,
   returning to that tab's list, while a Brain/Deposits detail is showing.
   Desktop layout above is entirely untouched; every rule here is scoped
   inside this media query. ---------- */

@media (max-width: 900px) {
  /* Header offset is inlined directly (calc(52px + env(...))) everywhere
     it's needed below, rather than indirected through a custom property.
     A custom property chaining var()+calc()+env() together has had real
     compatibility gaps on some mobile engines/embedded webviews (the
     property silently fails to resolve, and `top`/`height` fall back to
     their initial values instead of erroring loudly), which let the Brain
     drawer render starting at the very top of the screen, overlapping the
     topbar instead of sitting below it. Inlining removes that failure
     mode entirely: each declaration is self-contained. */

  body { overflow: hidden; }
  .shell { flex-direction: column; height: 100vh; overflow: hidden; }

  .mobile-topbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    /* max(..., 14px): env(safe-area-inset-top) resolves to 0 in several
       real contexts that still need clearance above the hamburger/title
       (a regular browser tab rather than an installed home-screen app,
       some in-app/embedded webviews that don't report a safe area at
       all), which otherwise left the topbar's content pinned flush to
       the very top edge with no breathing room, reading as cut off. The
       guaranteed 14px floor keeps that from ever collapsing to 0, while
       still growing past it on devices with a real notch/inset. */
    height: calc(52px + max(env(safe-area-inset-top, 0px), 14px));
    padding: max(env(safe-area-inset-top, 0px), 14px) 8px 0 14px;
    border-bottom: 1px solid var(--line);
    background: var(--panel-bg);
    box-shadow: 0 2px 6px rgba(20, 20, 15, 0.05);
    flex-shrink: 0;
    /* Above the menu/backdrop (85/80): even if the menu's own top offset
       ever miscalculates, the topbar still visually wins and stays
       reachable, rather than silently getting painted over. */
    z-index: 90;
  }
  .mobile-topbar-title {
    font-family: 'Fraunces', serif;
    font-weight: 600;
    font-size: 15px;
    color: var(--green);
  }

  /* Drag-resize is a desktop concept for reclaiming horizontal space
     between panels; the single-view-per-tab pattern replaces that
     entirely on mobile. Closing Contents outright is also a desktop-only
     concept (see the .contents-closed rule, already scoped to
     min-width:901px), the button itself is hidden here too so there's
     nothing to tap into that dead state. */
  .divider { display: none; }
  .panel-close-btn { display: none !important; }

  /* Icon rail becomes the hamburger menu's content: hidden until opened,
     anchored as a dropdown card below the topbar rather than a fixed bar
     (mobile-menu-btn toggles "mobile-menu-open" on the body, see app.js).
     Its buttons are already wired to setActiveTab (shared with desktop);
     app.js additionally closes the menu once a tab is actually picked. */
  .icon-rail {
    display: none;
    position: fixed;
    top: calc(52px + max(env(safe-area-inset-top, 0px), 14px) + 8px);
    left: 8px;
    width: 220px;
    height: auto;
    max-height: calc(100vh - 52px - max(env(safe-area-inset-top, 0px), 14px) - 16px);
    overflow-y: auto;
    flex-direction: column;
    align-items: stretch;
    padding: 6px;
    border: 1px solid var(--line);
    border-radius: 12px;
    background: var(--panel-bg);
    box-shadow: 0 8px 24px rgba(20, 20, 15, 0.18);
    z-index: 85;
  }
  body.mobile-menu-open .icon-rail { display: flex; }
  .icon-rail-btn {
    flex-direction: row;
    justify-content: flex-start;
    gap: 10px;
    width: 100%;
    min-height: 44px;
    padding: 10px 12px;
  }
  .icon-rail-label { font-size: 14px; }

  .mobile-backdrop.visible {
    display: block;
    position: fixed;
    top: calc(52px + max(env(safe-area-inset-top, 0px), 14px));
    left: 0; right: 0; bottom: 0;
    background: rgba(20, 20, 15, 0.4);
    z-index: 80;
  }

  /* ---- Brain tree / Deposits list: full-screen sub-view of whichever of
     those two tabs is active, not an overlay drawer (a partial-width or
     off-canvas panel here would leave real page content visibly showing
     through beside or behind it). ---- */
  .sidebar-left {
    width: 100% !important;
    height: auto;
    flex: 1;
    min-height: 0;
  }
  .sidebar-inner { min-width: 0; }
  .sidebar-left .sidebar-brand { display: none; } /* heading: redundant with the topbar */

  body.tab-brain .sidebar-left, body.tab-deposits .sidebar-left { display: flex !important; }
  body.tab-chat .sidebar-left { display: none !important; }
  body.mobile-show-contents .sidebar-left { display: none !important; }

  /* ---- Main view: Chat (always) or a selected Brain/Deposits item's
     detail (only while "mobile-show-contents" is set). !important is
     required here since app.js also toggles some of this via the native
     `hidden` attribute for the desktop tab logic, which these rules must
     override on mobile regardless of that (see the .tab-panel comment
     above the same pattern). ---- */
  .main-panel {
    width: 100% !important;
    height: auto;
    flex: 1;
    min-height: 0;
  }
  body.tab-brain:not(.mobile-show-contents) .main-panel,
  body.tab-deposits:not(.mobile-show-contents) .main-panel { display: none !important; }

  .main-panel .chat-view, .main-panel #right-panel-body {
    display: flex !important;
    flex-direction: column;
    width: 100%;
    min-height: 0;
  }
  body.mobile-show-contents .main-panel .chat-view { display: none !important; }
  body:not(.mobile-show-contents) .main-panel #right-panel-body { display: none !important; }
  /* The main panel's own header (heading + back button) only makes sense
     while Contents is showing; while Chat is active the mobile topbar
     above already carries the app name, so this would just be a
     redundant second header bar. */
  body:not(.mobile-show-contents) .main-panel-head,
  body:not(.mobile-show-contents) .breadcrumb { display: none !important; }

  .chat-view:not([hidden]) { padding: 8px 6px; }
  .chat-window { padding: 10px 8px; }

  /* Side by side doesn't fit a narrow phone screen: revert to stacked,
     Feed into Brain above Ask Vectram (their existing DOM order). */
  .feed-ask-row { flex-direction: column; gap: 10px; }
  .feed-ask-row .feed-block, .feed-ask-row .chat-form { flex: 1 1 auto; width: 100%; }

  /* min-height:72px already covers the two-line floor (see the shared
     rule above); horizontal padding/gap trimmed down further from the
     desktop values here to give the row as much width as possible on a
     narrow phone screen. */
  .chat-input-wrap { padding: 8px 6px 8px 10px; gap: 4px; border-radius: 26px; }
  .feed-block { padding: 12px 12px; border-radius: 20px; }

  /* Slightly larger tap target than the desktop 32px circle. */
  .chat-send-btn { width: 40px; height: 40px; }

  .main-panel-head { padding: 10px 14px; }
  /* The mobile topbar already carries "Vectram Marco"; showing it again
     here would just be a duplicate title. */
  .app-brand { display: none; }
  .right-panel-body { padding: 14px 16px; }
  .breadcrumb { padding: 10px 16px 0; }
  .pane-brain { padding: 10px 10px; }
}
