    /* sidebar */
    .sidebar {
      background: var(--pane);
      border-right: 1px solid var(--border);
      /* Row layout: 44px ActivityBar rail on the left, the active
       * sidebar-panel filling the rest. The panel itself owns the
       * vertical stack (header / list). */
      display: flex;
      flex-direction: row;
      min-width: 0;
      min-height: 0;
      /* Explicit height so the inner `.file-list { flex: 1 }` knows how
       * much to grow into — without it, .sidebar would shrink to
       * content size and the file list would no longer fill the column. */
      height: 100%;
      position: relative;
      /* Clip content as the grid column resizes / collapses to the bare
       * 44px rail — without this, file names visually spill into the
       * main pane mid-transition. */
      overflow: hidden;
    }

    /* VS Code / Obsidian-style narrow icon rail. Holds the three
     * mutually-exclusive view toggles (Files / Search / Library) and
     * paints the active indicator as a 2px accent strip on its left
     * edge. Hover state on individual buttons; the rail itself is a
     * flat background so it reads as part of the chrome, not as a
     * separate panel. */
    .activity-bar {
      flex: 0 0 44px;
      display: flex;
      flex-direction: column;
      align-items: stretch;
      padding: 4px 0;
      gap: 2px;
      background: var(--pane);
      border-right: 1px solid var(--border);
    }
    .activity-bar-btn {
      appearance: none;
      position: relative;
      display: flex;
      align-items: center;
      justify-content: center;
      width: 44px;
      height: 40px;
      border: 0;
      background: transparent;
      color: var(--muted);
      cursor: pointer;
      padding: 0;
    }
    .activity-bar-btn:hover {
      color: var(--fg);
      background: var(--hover);
    }
    .activity-bar-btn.active {
      color: var(--fg);
    }
    .activity-bar-btn.active::before {
      content: '';
      position: absolute;
      left: 0;
      top: 6px;
      bottom: 6px;
      width: 2px;
      background: var(--accent);
      border-radius: 0 2px 2px 0;
    }
    /* Recording: the record button turns red and softly pulses while a
     * capture is in progress. */
    .activity-bar-btn.recording {
      color: #dc2626;
      animation: activity-rec-pulse 1.6s ease-in-out infinite;
    }
    .activity-bar-btn.recording:hover {
      color: #dc2626;
    }
    @keyframes activity-rec-pulse {
      0%, 100% { opacity: 1; }
      50% { opacity: 0.45; }
    }
    /* Shared breathing pulse used by the stashing placeholder logo and
     * tab / pill stashing marks. */
    @keyframes stashing-pulse {
      0%, 100% { opacity: 0.35; }
      50% { opacity: 0.8; }
    }
    /* Push the settings gear to the bottom of the rail (VSCode-style),
     * keeping the view toggles grouped at the top. */
    .activity-bar-btn-bottom {
      margin-top: auto;
    }
    .activity-bar-btn svg {
      width: 18px;
      height: 18px;
    }

    /* The right-hand 280px column of the sidebar. Owns the vertical
     * stack: header / file-list (or search/kb
     * equivalents). `.activity-bar` is sibling on the left; this is
     * the panel that swaps content based on `state.activeSidebarView`. */
    .sidebar-panel {
      flex: 1 1 auto;
      display: flex;
      flex-direction: column;
      min-width: 0;
      min-height: 0;
      overflow: hidden;
    }
    .files-panel,
    .search-panel {
      display: flex;
      flex-direction: column;
      flex: 1;
      min-height: 0;
      overflow: hidden;
    }
    .search-panel-body {
      display: flex;
      flex-direction: column;
      flex: 1;
      min-height: 0;
      /* Results scroll vertically; the input above stays pinned. */
      overflow-y: auto;
      overflow-x: hidden;
    }


    /* Primary destructive action — used for "Remove key" confirmation.
     * `color` is set explicitly: `.modal-btn.danger` (red text, for the
     * outline variant) would otherwise win over `.modal-btn.primary`'s
     * white by source order, leaving red text on this red fill. */
    .modal-btn.primary.danger {
      background: #b91c1c;
      border-color: #b91c1c;
      color: #fff;
    }
    .modal-btn.primary.danger:hover:not(:disabled) {
      background: #b91c1c;
      filter: brightness(0.95);
    }

    /* Shared modal scaffolding for the embedder flow (key entry +
     * re-embed confirmation). Veil dims the app; card centers a
     * fixed-width panel. Click on the veil cancels; the card
     * stops propagation so clicks inside don't dismiss. */
    .modal-veil {
      position: fixed;
      inset: 0;
      background: rgba(0, 0, 0, 0.35);
      display: grid;
      place-items: center;
      z-index: 100;
    }

    .modal-veil.top {
      z-index: 10000;
    }

    .modal-card {
      background: var(--bg);
      border: 1px solid var(--border);
      border-radius: 10px;
      width: min(420px, 90vw);
      padding: 22px 24px 20px;
      box-shadow: 0 16px 48px rgba(0, 0, 0, 0.18);
    }

    .modal-card.wide {
      width: min(480px, 92vw);
    }

    .modal-card h3 {
      margin: 0 0 8px;
      font-size: 16px;
    }

    .modal-hint {
      font-size: 13px;
      color: var(--muted);
      margin: 0 0 14px;
      line-height: 1.5;
    }

    .modal-hint code {
      font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
      font-size: 12px;
      color: var(--accent);
    }

    /* Thumbnail of a clipboard image we're offering to import. Capped
     * so a large screenshot doesn't blow out the modal; checkerboard-ish
     * neutral frame so transparent PNGs stay legible. */
    .clipboard-offer-preview {
      display: flex;
      justify-content: center;
      margin: 0 0 14px;
    }
    .clipboard-offer-preview img {
      max-width: 100%;
      max-height: 260px;
      object-fit: contain;
      border: 1px solid var(--border);
      border-radius: 6px;
      background: var(--bg);
    }

    .modal-input {
      width: 100%;
      border: 1px solid var(--border);
      border-radius: 6px;
      padding: 8px 10px;
      font: inherit;
      font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
      font-size: 13px;
      background: var(--bg);
      color: var(--fg);
      box-sizing: border-box;
    }

    .modal-input:focus {
      outline: 2px solid var(--accent);
      outline-offset: -1px;
    }

    .modal-input::placeholder {
      color: #b8bcc2;
      font-weight: 400;
    }

    .modal-check {
      display: flex;
      align-items: center;
      gap: 8px;
      margin-top: 10px;
      margin-bottom: 14px;
      color: var(--fg);
      font-size: 13px;
    }

    .modal-check input {
      margin: 0;
      accent-color: var(--accent);
    }

    /* Import folder preview: read as a grouped "what will happen"
     * summary, separated from the input above. */
    .modal-import-preview {
      margin-top: 12px;
      padding-top: 12px;
      border-top: 1px solid var(--border);
      display: flex;
      flex-direction: column;
      gap: 4px;
    }

    /* The item count + size is the scannable anchor of the preview. */
    .modal-import-stat {
      color: var(--fg);
      font-weight: 600;
    }

    .modal-error {
      margin-top: 10px;
      padding: 8px 10px;
      background: rgba(220, 38, 38, 0.08);
      color: #b91c1c;
      border-radius: 6px;
      font-size: 12px;
      line-height: 1.45;
      max-width: 100%;
      max-height: min(180px, 32vh);
      overflow-y: auto;
      overflow-wrap: anywhere;
      word-break: break-word;
    }

    .modal-warning {
      margin-top: 10px;
      padding: 8px 10px;
      background: rgba(217, 119, 6, 0.1);
      color: #b45309;
      border-radius: 6px;
      font-size: 12px;
      line-height: 1.45;
      max-width: 100%;
      max-height: min(180px, 32vh);
      overflow-y: auto;
      overflow-wrap: anywhere;
      word-break: break-word;
    }

    .mcp-client-list {
      display: flex;
      flex-direction: column;
      border: 1px solid var(--border);
      border-radius: 8px;
      max-height: min(56vh, 620px);
      overflow-y: auto;
      background: var(--bg);
    }

    .mcp-client-row {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 14px;
      padding: 8px 14px;
    }

    .mcp-client-row + .mcp-client-row {
      border-top: 1px solid var(--border);
    }

    .mcp-client-label {
      display: inline-flex;
      align-items: center;
      gap: 9px;
      min-width: 0;
    }

    /* At-a-glance connection state: solid accent dot when connected,
     * hollow grey when not — complements the Connect/Disconnect button. */
    .mcp-status-dot {
      width: 7px;
      height: 7px;
      border-radius: 50%;
      flex-shrink: 0;
      border: 1.5px solid var(--border);
      background: transparent;
    }
    .mcp-status-dot.on {
      border-color: var(--accent);
      background: var(--accent);
    }

    .mcp-client-name {
      font-size: 13px;
      font-weight: 600;
      color: var(--fg);
    }

    /* Compact connect/disconnect — smaller than a default modal button
     * so it doesn't dominate an otherwise-sparse row. Sized via padding
     * (not an explicit height) so the label stays vertically centred. */
    /* `.modal-btn.mcp-connector-btn` (not bare `.mcp-connector-btn`) so
     * these win over `.modal-btn` — which is defined later in the file
     * and would otherwise override the smaller font/padding. min-width
     * fits the longer "Disconnect" so it and "Connect" share a width and
     * the column stays aligned. */
    .modal-btn.mcp-connector-btn {
      flex-shrink: 0;
      min-width: 96px;
      padding: 5px 12px;
      font-size: 12px;
      text-align: center;
    }

    /* Connecting is the primary action of each row — give the idle
     * button an accent-tinted hover instead of the neutral grey. The
     * connected state keeps its own (red, "disconnect") hover below. */
    .mcp-connector-btn:not(.connected):hover:not(:disabled) {
      color: var(--accent);
      border-color: rgba(8, 145, 178, 0.32);
      background: rgba(8, 145, 178, 0.08);
    }

    .mcp-connector-btn.connected {
      color: var(--accent);
      border-color: rgba(8, 145, 178, 0.32);
      background: rgba(8, 145, 178, 0.08);
    }

    .mcp-connector-btn.connected:hover:not(:disabled) {
      color: #b91c1c;
      border-color: rgba(220, 38, 38, 0.36);
      background: rgba(220, 38, 38, 0.06);
    }

    .mcp-success {
      margin-top: 10px;
      padding: 8px 10px;
      background: rgba(8, 145, 178, 0.08);
      color: var(--accent);
      border-radius: 6px;
      font-size: 12px;
      line-height: 1.45;
    }

    .mcp-other {
      margin-top: 18px;
      display: flex;
      flex-direction: column;
      gap: 10px;
    }

    .mcp-config-preview {
      border: 1px solid var(--border);
      border-radius: 8px;
      background: var(--hover);
      overflow: hidden;
    }

    .mcp-config-preview-head {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding: 8px 12px;
      border-bottom: 1px solid var(--border);
      font-size: 11px;
      font-weight: 600;
      color: var(--muted);
      text-transform: uppercase;
      letter-spacing: 0.04em;
    }

    /* Icon-only copy button (clipboard ↔ check). */
    .mcp-config-copy {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      width: 26px;
      height: 26px;
      border: 1px solid var(--border);
      background: var(--bg);
      color: var(--muted);
      border-radius: 5px;
      cursor: pointer;
      transition: background 0.12s, border-color 0.12s, color 0.12s;
    }

    .mcp-config-copy:hover {
      background: var(--hover);
      border-color: var(--accent);
      color: var(--accent);
    }

    /* Confirmation: accent check (palette is cyan/amber/red, no green). */
    .mcp-config-copy.copied,
    .mcp-config-copy.copied:hover {
      color: var(--accent);
      border-color: rgba(8, 145, 178, 0.4);
      background: rgba(8, 145, 178, 0.08);
    }

    .mcp-config-copy-icon {
      width: 14px;
      height: 14px;
    }

    .mcp-config-preview pre {
      margin: 0;
      padding: 12px;
      max-height: 320px;
      overflow: auto;
      font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
      font-size: 11.5px;
      line-height: 1.45;
      white-space: pre;
      color: var(--fg);
    }

    .modal-stats {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 10px 18px;
      margin: 4px 0 18px;
    }

    .modal-stat {
      display: flex;
      flex-direction: column;
      gap: 2px;
    }

    .modal-stat-label {
      font-size: 11px;
      color: var(--muted);
      text-transform: uppercase;
      letter-spacing: 0.05em;
    }

    .modal-stat-value {
      font-size: 18px;
      font-weight: 600;
      font-variant-numeric: tabular-nums;
    }

    .modal-stat-value.highlight {
      color: var(--accent);
    }

    .modal-actions {
      display: flex;
      justify-content: flex-end;
      gap: 8px;
      margin-top: 14px;
    }

    .modal-btn {
      border: 1px solid var(--border);
      background: transparent;
      color: var(--fg);
      padding: 7px 16px;
      border-radius: 6px;
      cursor: pointer;
      font: inherit;
      font-size: 13px;
      transition: background 0.12s, border-color 0.12s, color 0.12s;
    }

    .modal-btn:hover:not(:disabled) {
      background: var(--hover);
    }

    .modal-btn.primary {
      background: var(--accent);
      border-color: var(--accent);
      color: #fff;
    }

    .modal-btn.primary:hover:not(:disabled) {
      filter: brightness(0.95);
      background: var(--accent);
    }

    .modal-btn:disabled {
      opacity: 0.55;
      cursor: not-allowed;
    }

    /* Replace Chromium's chunky yellow `-webkit-focus-ring-color` with
     * a subtle accent halo for keyboard tab navigation. Clicked focus
     * (mouse) doesn't trigger this — :focus-visible only fires for
     * keyboard / accessibility focus. */
    .modal-btn:focus {
      outline: none;
    }
    .modal-btn:focus-visible {
      outline: none;
      box-shadow: 0 0 0 2px rgba(8, 145, 178, 0.32);
    }


    /* Search input + mode toggles. The activity bar already carries
     * the magnifier icon for the view itself, so the input has no
     * leading glyph — left padding is symmetric. Mode toggles
     * (≈/= flip, plus Aa / Word when keyword) live on the right of the
     * input row via the absolute-positioned `.side-search-mode`. */
    .side-search {
      position: relative;
      padding: 10px 12px 8px;
    }

    .side-search input {
      width: 100%;
      box-sizing: border-box;
      padding: 6px 112px 6px 10px;
      border: 1px solid var(--border);
      border-radius: 6px;
      background: var(--bg);
      color: var(--fg);
      font: inherit;
      font-size: 13px;
      outline: none;
    }

    .side-search input:focus {
      border-color: var(--accent);
    }

    .side-search-mode {
      position: absolute;
      right: 16px;
      top: 50%;
      transform: translateY(-50%);
      display: flex;
      align-items: center;
      gap: 2px;
    }

    .side-search-mode-btn {
      appearance: none;
      display: inline-flex;
      align-items: center;
      justify-content: center;
      min-width: 22px;
      height: 20px;
      padding: 0 5px;
      border: 1px solid transparent;
      border-radius: 4px;
      background: transparent;
      color: var(--muted);
      font: inherit;
      font-size: 11px;
      cursor: pointer;
      user-select: none;
    }
    .side-search-mode-btn:hover {
      background: var(--hover);
      color: var(--fg);
    }
    .side-search-mode-btn:disabled {
      cursor: default;
      opacity: 0.45;
    }
    .side-search-mode-btn:disabled:hover {
      background: transparent;
      color: var(--muted);
    }
    .side-search-mode-btn.active {
      background: var(--active);
      border-color: var(--border);
      color: var(--fg);
    }
    .side-search-mode-btn.word-toggle {
      min-width: 34px;
    }
    .side-search-mode-btn.side-search-mode-flip {
      font-size: 13px;
      font-weight: 600;
    }

    /* Brief glow when something programmatically focuses the search
     * input (empty-tab landing → "Search notes", ⌘O hotkey). Without
     * this the focus border alone is easy to miss when the user's
     * eyes are still on the main pane. */
    @keyframes search-focus-flash {
      0%   { box-shadow: 0 0 0 0 rgba(8, 145, 178, 0.45); }
      40%  { box-shadow: 0 0 0 5px rgba(8, 145, 178, 0.25); }
      100% { box-shadow: 0 0 0 0 rgba(8, 145, 178, 0); }
    }
    .side-search input.flash-focus {
      animation: search-focus-flash 0.7s ease-out;
    }

    .side-search input::-webkit-search-cancel-button {
      cursor: pointer;
    }

    /* Library-overview row — sits above the space header and is shown
     * for every space (the AGENT.md it points at lives at kbRoot, not
     * inside a space). Styled as a soft nav row so it doesn't compete
     * with the space tree below it, but with an accent tint on hover /
     * when its tab is active so it reads as a peer to the workspace
     * itself rather than a tree entry. */
    .kb-row {
      display: flex;
      align-items: center;
      gap: 8px;
      padding: 6px 12px;
      margin: 2px 6px 6px;
      border-radius: 5px;
      color: var(--muted);
      cursor: pointer;
      font-size: 12.5px;
      user-select: none;
    }
    .kb-row:hover {
      background: var(--hover);
      color: var(--fg);
    }
    .kb-row.active {
      background: rgba(8, 145, 178, 0.08);
      color: var(--accent);
      font-weight: 500;
    }
    .kb-row-icon {
      width: 16px;
      height: 16px;
      flex-shrink: 0;
    }
    .kb-row-label {
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    /* Second row: space label + VSCode-style action toolbar. */
    /* Top tier of the VSCode-style two-row header: a quiet section label
     * ("SPACE") on the left + the space-actions ⋯ on the right. The
     * folder row (.side-head) with the current space name + file-action
     * icons sits directly below it. */
    .panel-section-head {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding: 8px 8px 2px 12px;
      gap: 6px;
      min-height: 30px;
    }

    .panel-section-title {
      flex: 1;
      min-width: 0;
      font-size: 11px;
      font-weight: 600;
      letter-spacing: .06em;
      text-transform: uppercase;
      color: var(--muted);
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }

    /* SPACE header: keep the "SPACE" label content-width so the stashing
     * pill sits snug to its right, and push the ⋯ actions to the far
     * right edge (they're hover-only, so left-aligning the pill is the
     * only stable layout). */
    .space-section-head .panel-section-title {
      flex: 0 0 auto;
    }
    .space-section-head .side-actions {
      margin-left: auto;
    }

    .side-head {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding: 2px 8px 2px 12px;
      gap: 6px;
    }

    /* Wraps chevron + label so they share one click target and one
     * hover-highlight rule, mirroring the VSCode workspace header. */
    .space-title {
      display: flex;
      align-items: center;
      gap: 2px;
      flex: 1;
      min-width: 0;
      cursor: pointer;
      color: var(--muted);
    }

    .space-title:hover {
      color: var(--fg);
    }

    .space-chev {
      width: 16px;
      height: 16px;
      display: inline-flex;
      align-items: center;
      justify-content: center;
      transition: transform .15s;
      flex-shrink: 0;
    }

    .space-chev svg {
      width: 14px;
      height: 14px;
    }

    /* Collapsed state: chevron points right, file list is hidden. The
     * `expanded` set inside the JS is untouched, so re-expanding the
     * SPACE restores every inner folder's prior open/closed state. */
    .space-title.collapsed .space-chev {
      transform: rotate(-90deg);
    }

    .file-list.collapsed {
      display: none;
    }

    /* The current space name — the prominent row, brighter than the
     * muted "SPACE" section label above it (mirrors VSCode's workspace
     * name reading stronger than the EXPLORER header). */
    .folder-label {
      font-size: 11px;
      font-weight: 600;
      letter-spacing: .04em;
      text-transform: uppercase;
      color: var(--fg);
      flex: 1;
      min-width: 0;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }

    /* Files not yet indexed are dimmed in the sidebar with a slow
     * breathing pulse — visual cue that Claude / MCP can't search
     * them yet AND that something's actively happening. Transitions
     * smoothly back to full opacity the moment the daemon finishes
     * embedding (polled every 1.5s while anything is pending).
     *
     * Hover cancels the animation and pins a static slightly-brighter
     * opacity so the user can read the filename clearly. */
    @keyframes indexing-pulse {

      0%,
      100% {
        opacity: 0.30;
      }

      50% {
        opacity: 0.65;
      }
    }

    .tree-row.file {
      transition: opacity .25s ease-out;
    }

    .tree-row.file.not-indexed {
      animation: indexing-pulse 1.8s ease-in-out infinite;
    }

    .tree-row.file.not-indexed:hover {
      animation: none;
      opacity: 0.85;
    }

    .side-actions {
      display: flex;
      gap: 2px;
      opacity: 0;
      transition: opacity .12s;
    }

    .sidebar:hover .side-actions {
      opacity: 1;
    }

    .icon-btn {
      width: 28px;
      height: 28px;
      border: 0;
      background: transparent;
      cursor: pointer;
      color: var(--muted);
      border-radius: 4px;
      display: grid;
      place-items: center;
    }

    .icon-btn:hover {
      background: var(--hover);
      color: var(--fg);
    }

    .icon-btn svg {
      width: 16px;
      height: 16px;
    }

    /* Spinner for the sidebar sync (↻) button. JS sets a guaranteed
     * minimum spin window even on fast requests so the click always
     * leaves a visible trace. */
    @keyframes sync-spin {
      to { transform: rotate(360deg); }
    }
    .icon-btn.spinning svg {
      animation: sync-spin 0.7s linear infinite;
    }

    /* Stashing indicator. A file is "stashing" while the server is still
     * converting it into searchable content (pymupdf / marker on a PDF,
     * RapidOCR on an image, transcode on a recording) — the slow, user-
     * visible phase. The "N stashing" pill lives in the SPACE header
     * (left of ⋯) and stays visible at all times; clicking it opens the
     * `.stashing-list` dropdown. Auto-dismisses when the conversion
     * finishes (the index-status poll drops the entry from
     * `pendingConversions`).
     *
     * Each of the 5 cyan paths draws in sequence (stroke-dashoffset),
     * with the stagger baked into the keyframe percentages rather than
     * animation-delay. That way all 5 paths hit their snap-off keyframe
     * at the same point in the shared 3 s cycle and vanish together.
     * steps(1, end) at 74% makes the wipe instantaneous.
     *
     */
    /* 5 connected paths, proportional draw windows with ~3 % overlap so
     * each stroke starts before the previous one finishes — simulates a
     * single flowing pen rather than discrete segments. Draw 0–57%,
     * hold 57–80%, fade 80–88%, reset 88–100%. 2.4 s cycle. */
    @keyframes s-draw-1 {
      0%   { stroke-dashoffset: 300; opacity: 0; animation-timing-function: ease-in-out; }
      5%   { opacity: 1; }
      15%  { stroke-dashoffset: 0; }
      80%  { stroke-dashoffset: 0; opacity: 1; }
      88%  { stroke-dashoffset: 0; opacity: 0; }
      89%  { stroke-dashoffset: 300; opacity: 0; }
      100% { stroke-dashoffset: 300; opacity: 0; }
    }
    @keyframes s-draw-2 {
      /* starts 3 % before path-1 ends → flows through shared point 92,158 */
      0%   { stroke-dashoffset: 400; opacity: 0; }
      12%  { stroke-dashoffset: 400; opacity: 0; animation-timing-function: ease-in-out; }
      17%  { opacity: 1; }
      32%  { stroke-dashoffset: 0; }
      80%  { stroke-dashoffset: 0; opacity: 1; }
      88%  { stroke-dashoffset: 0; opacity: 0; }
      89%  { stroke-dashoffset: 400; opacity: 0; }
      100% { stroke-dashoffset: 400; opacity: 0; }
    }
    @keyframes s-draw-3 {
      /* starts 3 % before path-2 ends → flows through shared point 420,158 */
      0%   { stroke-dashoffset: 190; opacity: 0; }
      29%  { stroke-dashoffset: 190; opacity: 0; animation-timing-function: ease-in-out; }
      33%  { opacity: 1; }
      41%  { stroke-dashoffset: 0; }
      80%  { stroke-dashoffset: 0; opacity: 1; }
      88%  { stroke-dashoffset: 0; opacity: 0; }
      89%  { stroke-dashoffset: 190; opacity: 0; }
      100% { stroke-dashoffset: 190; opacity: 0; }
    }
    @keyframes s-draw-4 {
      /* starts 3 % before path-3 ends → flows through shared point 420,342 */
      0%   { stroke-dashoffset: 200; opacity: 0; }
      38%  { stroke-dashoffset: 200; opacity: 0; animation-timing-function: ease-in-out; }
      42%  { opacity: 1; }
      50%  { stroke-dashoffset: 0; }
      80%  { stroke-dashoffset: 0; opacity: 1; }
      88%  { stroke-dashoffset: 0; opacity: 0; }
      89%  { stroke-dashoffset: 200; opacity: 0; }
      100% { stroke-dashoffset: 200; opacity: 0; }
    }
    @keyframes s-draw-5 {
      /* starts 3 % before path-4 ends → flows through shared point 256,436 */
      0%   { stroke-dashoffset: 100; opacity: 0; }
      47%  { stroke-dashoffset: 100; opacity: 0; animation-timing-function: ease-in-out; }
      51%  { opacity: 1; }
      57%  { stroke-dashoffset: 0; }
      80%  { stroke-dashoffset: 0; opacity: 1; }
      88%  { stroke-dashoffset: 0; opacity: 0; }
      89%  { stroke-dashoffset: 100; opacity: 0; }
      100% { stroke-dashoffset: 100; opacity: 0; }
    }
    .stashing-pill-logo,
    .stashing-list-logo,
    .tab-stashing-logo {
      flex-shrink: 0;
    }
    .stashing-pill-logo .s-cyan > path:nth-child(1),
    .stashing-list-logo .s-cyan > path:nth-child(1),
    .tab-stashing-logo .s-cyan > path:nth-child(1),
    .stashing-placeholder-logo .s-cyan > path:nth-child(1) {
      stroke-dasharray: 300; animation: s-draw-1 2.4s linear infinite;
    }
    .stashing-pill-logo .s-cyan > path:nth-child(2),
    .stashing-list-logo .s-cyan > path:nth-child(2),
    .tab-stashing-logo .s-cyan > path:nth-child(2),
    .stashing-placeholder-logo .s-cyan > path:nth-child(2) {
      stroke-dasharray: 400; animation: s-draw-2 2.4s linear infinite;
    }
    .stashing-pill-logo .s-cyan > path:nth-child(3),
    .stashing-list-logo .s-cyan > path:nth-child(3),
    .tab-stashing-logo .s-cyan > path:nth-child(3),
    .stashing-placeholder-logo .s-cyan > path:nth-child(3) {
      stroke-dasharray: 190; animation: s-draw-3 2.4s linear infinite;
    }
    .stashing-pill-logo .s-cyan > path:nth-child(4),
    .stashing-list-logo .s-cyan > path:nth-child(4),
    .tab-stashing-logo .s-cyan > path:nth-child(4),
    .stashing-placeholder-logo .s-cyan > path:nth-child(4) {
      stroke-dasharray: 200; animation: s-draw-4 2.4s linear infinite;
    }
    .stashing-pill-logo .s-cyan > path:nth-child(5),
    .stashing-list-logo .s-cyan > path:nth-child(5),
    .tab-stashing-logo .s-cyan > path:nth-child(5),
    .stashing-placeholder-logo .s-cyan > path:nth-child(5) {
      stroke-dasharray: 100; animation: s-draw-5 2.4s linear infinite;
    }
    /* Freeze paths fully assembled while hovering the pill. */
    .stashing-pill:hover .s-cyan > path {
      animation: none;
      stroke-dashoffset: 0;
      opacity: 1;
    }
    @media (prefers-reduced-motion: reduce) {
      .stashing-pill-logo .s-cyan > path,
      .stashing-list-logo .s-cyan > path,
      .tab-stashing-logo .s-cyan > path,
      .stashing-placeholder-logo .s-cyan > path {
        animation: none;
        stroke-dashoffset: 0;
        opacity: 0.7;
      }
    }

    .stashing-pill {
      display: inline-flex;
      align-items: center;
      gap: 5px;
      flex-shrink: 0;
      border: 0;
      background: transparent;
      cursor: pointer;
      color: var(--muted);
      font-size: 11px;
      font-weight: 400;
      letter-spacing: 0.02em;
      padding: 2px 7px;
      border-radius: 999px;
      white-space: nowrap;
    }
    .stashing-pill:hover {
      background: var(--hover);
      color: var(--fg);
    }
    .stashing-pill-logo {
      width: 13px;
      height: 13px;
    }

    /* Chrome-downloads-style dropdown of in-flight stashing files.
     * Mirrors `.menu`'s card / fade-in treatment. */
    .stashing-list {
      z-index: 100;
      min-width: 220px;
      max-width: 320px;
      padding: 5px;
      background: var(--card);
      border: 1px solid var(--border);
      border-radius: 9px;
      box-shadow:
        0 10px 30px rgba(0, 0, 0, 0.14),
        0 2px 6px rgba(0, 0, 0, 0.07);
      display: flex;
      flex-direction: column;
      gap: 1px;
      font-size: 12.5px;
      color: var(--fg);
      opacity: 0;
      transform: translateY(-2px);
      pointer-events: none;
    }
    .stashing-list.ready {
      opacity: 1;
      transform: none;
      pointer-events: auto;
      transition:
        opacity 0.09s ease-out,
        transform 0.09s ease-out;
    }
    .stashing-list-head {
      font-size: 11px;
      font-weight: 600;
      letter-spacing: 0.06em;
      text-transform: uppercase;
      color: var(--muted);
      padding: 4px 8px 6px;
    }
    .stashing-list-row {
      display: flex;
      align-items: center;
      gap: 8px;
      padding: 5px 8px;
      border-radius: 6px;
      min-width: 0;
    }
    .stashing-list-logo {
      width: 14px;
      height: 14px;
    }
    .stashing-list-name {
      flex: 1;
      min-width: 0;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }
    .stashing-list-phase {
      color: var(--muted);
      font-size: 11px;
      flex-shrink: 0;
    }

    /* Snapshot-import warning. Sits above the file tree when the active
     * space's most recent snapshot import skipped chunks whose embedder
     * didn't match the library's current provider — searchable content is
     * silently incomplete, so we surface it rather than report "imported".
     * Shares the amber warning palette with .pdf-failure-banner. Dismiss
     * (×) clears it via /api/index-status. */
    .snapshot-warning {
      display: flex;
      align-items: flex-start;
      gap: 8px;
      padding: 8px 10px 9px 14px;
      border-bottom: 1px solid #f0c060;
      background: #fff4e0;
      color: #5a3b00;
      font-size: 12px;
    }
    .snapshot-warning-body {
      flex: 1;
      min-width: 0;
      display: flex;
      flex-direction: column;
      gap: 2px;
    }
    .snapshot-warning-title {
      font-weight: 600;
    }
    .snapshot-warning-msg {
      line-height: 1.4;
      opacity: 0.92;
    }
    .snapshot-warning-dismiss {
      flex-shrink: 0;
      width: 18px;
      height: 18px;
      line-height: 16px;
      padding: 0;
      border: none;
      border-radius: 4px;
      background: transparent;
      color: #5a3b00;
      font-size: 15px;
      cursor: pointer;
      opacity: 0.6;
    }
    .snapshot-warning-dismiss:hover {
      background: #fef0c5;
      opacity: 1;
    }

    .file-list {
      flex: 1;
      overflow-y: auto;
      padding: 0 0 8px;
    }


    /* Tree rows. Indent is set inline per node depth.
   * Folder rows toggle expand/collapse; file rows open the file. */
    .tree-row {
      display: flex;
      align-items: center;
      gap: 4px;
      padding: 3px 8px 3px 0;
      cursor: pointer;
      border-left: 2px solid transparent;
      font-size: 13px;
      color: var(--fg);
      min-width: 0;
      user-select: none;
    }

    .tree-row:hover {
      background: var(--hover);
    }

    .tree-row.active {
      background: var(--active);
      border-left-color: var(--accent);
      font-weight: 500;
    }

    .tree-row.folder.active-folder {
      background: var(--active);
      box-shadow: inset 2px 0 0 var(--accent);
    }

    /* SPACE header gets the same selected-state styling as
     * `.tree-row.folder.active-folder` when `activeFolder === ''` —
     * VSCode-style "workspace root selected" indication so the user
     * can tell where `+ new note` will create. */
    .side-head.active-root {
      background: var(--active);
      box-shadow: inset 2px 0 0 var(--accent);
    }

    /* Drop indicators for drag-to-reorder. Inset shadow draws a 2px
     * accent line at the top (above) or bottom (below) edge of the
     * row, signalling "the dragged item will land here". `above` /
     * `below` are mutually exclusive with `drop-target` (which is the
     * "into folder" state) — the drag handler picks one or the other,
     * never both. Defined after `.active-folder` so the inset shadow
     * wins when both classes happen to land together. */
    .tree-row.drop-edge-above {
      box-shadow: inset 0 2px 0 var(--accent);
    }
    .tree-row.drop-edge-below {
      box-shadow: inset 0 -2px 0 var(--accent);
    }

    /* Highlight a folder row while files are being dragged over it —
     * tells the user "drop here → into this folder". Cleared on drop
     * or when the cursor leaves the row. The SPACE header gets the
     * same highlight when it's the drop target (= move to root). */
    .tree-row.folder.drop-target,
    .side-head.drop-target {
      background: rgba(217, 160, 0, 0.18);
      box-shadow: inset 2px 0 0 #d9a000;
    }

    .tree-row .chev {
      width: 16px;
      height: 16px;
      flex-shrink: 0;
      color: var(--muted);
      display: grid;
      place-items: center;
      transition: transform .12s;
    }

    .tree-row.collapsed .chev {
      transform: rotate(-90deg);
    }

    .tree-row .chev svg {
      width: 12px;
      height: 12px;
    }

    .tree-row.new-folder-row {
      cursor: text;
      user-select: text;
    }

    .tree-row.new-folder-row:hover {
      background: transparent;
    }

    .tree-row .new-folder-spacer {
      visibility: hidden;
    }

    /* Flex-center the glyph so it sits on the row's vertical midline.
     * Without this the span is inline and the SVG rides the text
     * baseline (looks high) — same treatment as `.kb-file-icon`. */
    .tree-row .icon {
      display: inline-flex;
      align-items: center;
      flex-shrink: 0;
    }
    .tree-row .icon svg {
      width: 16px;
      height: 16px;
      flex-shrink: 0;
      color: var(--muted);
    }

    .tree-row.file .icon svg {
      color: var(--muted);
    }

    /* Format glyphs (md / html / pdf) come from the Material Icon Theme and
     * carry their own brand `fill` — no `currentColor` tint here. The old
     * `.format-md` / `.format-html` colour rules were dropped: they only
     * affected `currentColor`, which the filled Material SVGs don't use. */

    /* Agent-maintained meta files (STASHBASE.md, *-metadata.md) carry a
     * bot / 目录 glyph (still `currentColor`) and stay neutral muted — they're
     * system files, not notes. */
    .tree-row.file.meta-file .icon svg {
      color: var(--muted);
    }

    .tree-row .label {
      flex: 1;
      min-width: 0;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }

    .tree-children {
      position: relative;
    }

    /* Obsidian-style indent guide. Each folder's children wrapper sets
     * `--guide-left` based on the folder's depth so the line aligns
     * with that folder's chevron. Nested folders draw their own line,
     * giving one guide per ancestor level. */
    .tree-children::before {
      content: '';
      position: absolute;
      top: 0;
      bottom: 0;
      left: var(--guide-left, 33px);
      width: 1px;
      background: #d0d0d0;
      pointer-events: none;
      z-index: 1;
    }

    .tree-children.collapsed {
      display: none;
    }

    .tree-create-input {
      flex: 1;
      min-width: 0;
      margin: 0;
      padding: 2px 6px;
      border: 1px solid var(--accent);
      border-radius: 4px;
      background: var(--card);
      color: var(--fg);
      font: inherit;
      font-size: 13px;
      outline: none;
    }

    .empty-list {
      padding: 24px 16px;
      text-align: center;
      color: var(--muted);
      font-size: 12px;
    }

    /* Semantic search results (replaces the tree when the search box is
     * non-empty). Card-per-hit: heading breadcrumb at top, chunk preview
     * with `<mark>`-highlighted query terms, filename at bottom. */
    .search-hits {
      padding: 4px 6px;
    }

    .search-hit {
      padding: 8px 10px;
      border-radius: 6px;
      cursor: pointer;
      margin-bottom: 4px;
      border: 1px solid transparent;
    }

    .search-hit:hover {
      background: var(--hover);
      border-color: var(--border);
    }

    .search-hit-heading {
      font-size: 11px;
      color: var(--muted);
      font-weight: 500;
      letter-spacing: 0.02em;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
      margin-bottom: 4px;
    }

    .search-hit-snippet {
      font-size: 12.5px;
      line-height: 1.45;
      color: var(--fg);
      display: -webkit-box;
      -webkit-line-clamp: 3;
      line-clamp: 3;
      -webkit-box-orient: vertical;
      overflow: hidden;
    }

    .search-hit-snippet mark {
      background: rgba(217, 160, 0, 0.28);
      color: inherit;
      padding: 0 1px;
      border-radius: 2px;
    }

    .search-hit-meta {
      margin-top: 6px;
      display: flex;
      justify-content: space-between;
      align-items: baseline;
      font-size: 11px;
      color: var(--muted);
    }

    .search-hit-file {
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }

    /* Keyword (ripgrep) results — VS-Code-style: a per-file header with a
     * match-count badge, then one row per match (line-number gutter + a
     * single-line snippet that ellipsises rather than wrapping into a
     * wall of text). */
    .keyword-summary {
      font-size: 11px;
      color: var(--muted);
      padding: 2px 10px 6px;
    }
    .keyword-file-group {
      margin-bottom: 6px;
    }
    .keyword-file-header {
      display: flex;
      align-items: center;
      gap: 6px;
      padding: 4px 10px;
      border-radius: 6px;
      cursor: pointer;
    }
    .keyword-file-header:hover {
      background: var(--hover);
    }
    .keyword-file-name {
      flex: 1;
      min-width: 0;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
      font-size: 12.5px;
      font-weight: 500;
      color: var(--fg);
    }
    .keyword-file-count {
      flex-shrink: 0;
      font-size: 10px;
      line-height: 16px;
      min-width: 16px;
      padding: 0 5px;
      text-align: center;
      color: var(--muted);
      background: var(--hover);
      border-radius: 8px;
    }
    .keyword-match-row {
      display: flex;
      gap: 8px;
      align-items: baseline;
      padding: 2px 10px 2px 16px;
      cursor: pointer;
      font-size: 12px;
      line-height: 1.5;
      border-radius: 4px;
    }
    .keyword-match-row:hover {
      background: var(--hover);
    }
    .keyword-line-num {
      flex-shrink: 0;
      min-width: 26px;
      text-align: right;
      color: var(--muted);
      font-variant-numeric: tabular-nums;
      user-select: none;
    }
    .keyword-line-text {
      flex: 1;
      min-width: 0;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
      color: var(--fg);
    }
    .keyword-line-text mark {
      background: rgba(217, 160, 0, 0.28);
      color: inherit;
      padding: 0 1px;
      border-radius: 2px;
    }
    .keyword-truncated {
      color: var(--muted);
      font-size: 11px;
      padding: 2px 10px 2px 16px;
      cursor: default;
    }
    .keyword-truncated:hover {
      background: transparent;
    }

    /* KNOWLEDGE BASE section — the two KB-root governance files
     * (STASHBASE.md + space-metadata.md) pinned at the top of the Files
     * panel under a `.panel-section-head` "Knowledge base" label. Rows
     * mirror the file-tree row metrics exactly (3px rhythm, 16px icon,
     * 12px indent aligning the icon with the SPACE header's chevron) so
     * the two sections read as one tidy list. Per-space rules are NOT
     * here — they live in the tree below, where they physically are. */
    .kb-file-list {
      padding: 0;
    }
    .kb-file-row {
      appearance: none;
      display: flex;
      align-items: center;
      gap: 4px;
      width: 100%;
      text-align: left;
      padding: 3px 8px 3px 12px;
      border: 0;
      border-left: 2px solid transparent;
      background: transparent;
      color: var(--fg);
      font: inherit;
      font-size: 13px;
      cursor: pointer;
    }
    .kb-file-row:hover {
      background: var(--hover);
    }
    .kb-file-row.selected {
      background: var(--active);
      border-left-color: var(--accent);
      font-weight: 500;
    }
    .kb-file-icon {
      display: inline-flex;
      align-items: center;
      flex-shrink: 0;
    }
    .kb-file-icon svg {
      width: 16px;
      height: 16px;
      color: var(--muted);
    }
    .kb-file-label {
      flex: 1;
      min-width: 0;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }
