
/* UIQ R3: horizontal padding now comes from app-shell's shared
   .destination-page rule, matching Routines and Connections exactly. */
/* Round-6 (operator: "too much padding at the very bottom under the file/preview space"):
   design-system/app-shell/chrome-visual.css's global `.tab-panel { padding-block: 1.6rem
   3.5rem; }` gives every tab-panel 56px of bottom padding — reasonable breathing room
   below the LAST bit of content on a normal SCROLLING page, but #files-tab is the
   bounded-height flex column below (Defect 1/8): that 56px is dead space eaten out of
   the fixed-height file/preview area before any content ever renders, not scroll
   breathing room. Override to something a bounded page actually wants — small and
   roughly symmetric with the 0.6rem top this same file already gets from chrome-visual's
   desktop-grid rule. */
#files-tab { padding-bottom: 10px; }

/* T461: /files_new — Files page rebuilt from scratch against the prototype (files.html).
   Fresh surface: fn- prefixed classes, scoped to #files-tab. Prototype rules are
   the measured truth; platform tokens supply the values. */

/* Defect 1/8 (operator live-test, 2026-08-18): the page-level .tab-panel is the app
   shell's single scroll region (design-system/foundation.css `.tab-panel { flex:1 1
   auto; min-height:0; overflow:auto; }`) — shared by every tab. Without its own bounded
   height, #files-tab just grew with the file list and the WHOLE panel scrolled,
   carrying the preview pane out of view; the preview column then only sized to its own
   content (hence "renders very small"). Fix mirrors the prototype's `body.app-fixed`
   pattern (mf.css) but scoped to this ID only — the tab-panel fills the viewport
   (already bounded by the shell), this section becomes a column that consumes that
   full height, and only the active list pane / preview body scroll internally.
   `#files-tab` beats the shared `.tab-panel` rule on specificity, so no other
   surface is touched. */
#files-tab {
  --fn-tab-rule-space: 10px;
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
  overflow: hidden;
}
#files-tab .fn-page-head,
#files-tab .fn-page-tabs {
  flex: 0 0 auto;
}
/* Stage 3: drag-and-drop upload visual feedback (core.js), ported from the old page's
   #files-pane.dragging treatment — a dashed accent outline over the whole layout area. */
#files-tab .fn-files-layout.fn-dragging {
  outline: 2px dashed var(--ui-accent);
  outline-offset: -6px;
  border-radius: var(--ui-radius, 18px);
  background: color-mix(in srgb, var(--ui-accent) 5%, transparent);
}
#files-tab .fn-files-layout {
  --pv-width: 52%;
  display: grid;
  grid-template-columns: minmax(0, 1fr) 0;
  gap: 0 22px;
  align-items: start;
  padding-top: 0;
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
}
#files-tab .fn-files-layout.fn-previewing { grid-template-columns: minmax(0, 1fr) minmax(340px, var(--pv-width)); }
/* Round 8 (operator: full-width document reading — collapse the file-list column rather
   than a dedicated tab, so the current selection/tab state never gets disturbed and this
   matches the shell's own sidebar-collapse pattern). The collapsed track goes to 0 — the
   second track's minmax(0,1fr) then claims 100% of the row, so .fn-preview-col (and its
   descendant .fn-pdf-pages) really does grow to the full available width, not just visually
   overlap the hidden column. `transition: grid-template-columns` is supported by current
   evergreen engines (Chromium included, empirically confirmed); .fn-files-side's own
   opacity transition softens the disappearance regardless of grid-track interpolation
   support in any given browser, so the collapse still reads as smooth even in a browser
   that would otherwise snap the grid tracks instantly. */
#files-tab .fn-files-layout.fn-previewing {
  transition: grid-template-columns 240ms cubic-bezier(0.4, 0, 0.2, 1);
}
/* Round 9 (operator: full-width residue — collapsed mode "does not reach all the
   way", unnecessary LEFT padding remains): CSS grid always inserts the declared
   column-gap between adjacent tracks REGARDLESS of a track's own size — the
   layout's 22px gap (line ~44, `gap: 0 22px`) kept reserving space between the
   now-0-width first track and .fn-preview-col even though nothing visible sits
   in that gap. Measured with Playwright before this fix: .fn-page-head h1 and
   .fn-preview-col's collapsed left edge disagreed by exactly 22px (278px vs
   300px in a 1440-wide viewport) — the grid gap, not any padding on
   .fn-preview-col itself (which has none) or a leftover container inset
   (#files-tab's own 46px gutter is shared by h1/tabs/layout alike and stays
   correct in both states). Zeroing column-gap only while collapsed removes
   exactly that dead 22px so the preview column's left edge lands flush with
   where the page head and tab row already start — true edge-to-edge, not
   viewport-edge-to-edge. */
#files-tab .fn-files-layout.fn-previewing.fn-side-collapsed {
  grid-template-columns: 0 minmax(0, 1fr);
  column-gap: 0;
}
#files-tab .fn-files-side {
  min-width: 0; min-height: 0; height: 100%;
  display: flex; flex-direction: column; overflow: hidden;
  transition: opacity 180ms ease;
}
#files-tab .fn-files-layout.fn-side-collapsed .fn-files-side {
  opacity: 0; pointer-events: none;
}
/* The pane that is actually shown (JS toggles [hidden] on the other three) owns the
   scroll for the list — the preview column never moves because it lives in the sibling
   grid cell, sized to the same bounded row. Round-5 (operator: overlay scrollbar sits ON
   TOP of the cards): scrollbar-gutter reserves its own track — always, so a pane that
   isn't tall enough to scroll yet doesn't shift width once it grows — plus a little
   right padding so card content doesn't run flush against that track. */
#files-tab .fn-pane:not([hidden]) {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-gutter: stable;
  padding-right: 6px;
}

/* Search field — the pane's anchor element. */
#files-tab .fn-file-search { position: relative; margin: 6px 0 14px; }
#files-tab .fn-file-search input {
  width: 100%; padding: 12px 64px 12px 42px;
  border-radius: 14px; border: 1px solid var(--ui-line);
  background: var(--ui-panel); color: var(--ui-text);
  font-family: var(--font-ui); font-size: var(--text-md, 15px);
  transition: border-color var(--speed, 140ms) ease;
}
#files-tab .fn-file-search input::placeholder { color: var(--ui-muted); }
#files-tab .fn-file-search input:focus { outline: none; border-color: color-mix(in srgb, var(--ui-accent) 45%, transparent); }
#files-tab .fn-search-ico { position: absolute; left: 15px; top: 50%; transform: translateY(-50%); color: var(--ui-muted); pointer-events: none; }
#files-tab .fn-search-hint {
  position: absolute; right: 14px; top: 50%; transform: translateY(-50%);
  font-family: var(--font-mono); font-size: var(--text-xs, 11px); color: var(--ui-muted); pointer-events: none;
}

/* View tabs — shared selection language (design-system/tabs.css); the
   bottom-shade underline is retired here and in Connections. */
#files-tab .fn-page-tabs { display: flex; align-items: center; gap: 3px; margin: 14px 0 0; }
#files-tab .fn-page-tab {
  appearance: none; display: inline-flex; align-items: center; gap: 7px;
  padding: var(--cc-tab-pad); background: none; cursor: pointer;
  border: none; border-radius: var(--cc-tab-radius);
  font-family: var(--font-ui); font-size: 13.5px; font-weight: 600; color: var(--cc-tab-ink);
  transition: color var(--speed, 140ms) ease, background var(--speed, 140ms) ease,
    box-shadow var(--speed, 140ms) ease;
}
#files-tab .fn-page-tab:hover { color: var(--cc-tab-ink-hover); background: var(--cc-tab-bg-hover); }
#files-tab .fn-page-tab[aria-selected="true"] {
  color: var(--cc-tab-ink-selected);
  background: var(--cc-tab-bg-selected);
  box-shadow: inset 0 0 0 1px var(--cc-tab-line-selected);
}
#files-tab .fn-pt-count {
  font-family: var(--font-mono); font-size: var(--text-xs, 11px); font-weight: 500;
  padding: 1px 7px; border-radius: 99px;
  background: var(--ui-hover); color: var(--ui-muted);
}
#files-tab .fn-page-tab[aria-selected="true"] .fn-pt-count { color: var(--ui-accent); }

/* Panes. */
#files-tab .fn-pane { min-width: 0; margin-top: var(--fn-tab-rule-space); }

/* Tree rows. Round-3 defect (operator live-test): expanding a directory wrapped its
   revealed rows in ANOTHER .fn-tree-list — loadDir() creates a fresh .fn-tree-list for
   every level it loads, nested or not — so every expanded group got its own bordered,
   rounded card nested inside .fn-tree-children, reading as a stray circle/box around the
   children. Only the root list is the card; nested lists are flat, matching the
   prototype's flat tree. */
#files-tab #fn-pane-tree > .fn-tree-list { border: 1px solid var(--ui-line); border-radius: var(--ui-radius, 18px); overflow: hidden; background: var(--ui-panel); }
#files-tab .fn-tree-children > .fn-tree-list { border: 0; border-radius: 0; background: transparent; overflow: visible; }
#files-tab .fn-tree-row {
  display: flex; align-items: center; gap: 8px; width: 100%; text-align: left;
  padding: 8px 14px; cursor: pointer; background: transparent; border: 0;
  border-bottom: 1px solid var(--ui-line-soft);
  font-family: var(--font-ui); font-size: 13.5px; color: var(--ui-text-soft);
}
#files-tab .fn-tree-row:last-child { border-bottom: none; }
#files-tab .fn-tree-row:hover { background: var(--ui-hover); color: var(--ui-text); }
/* The open file, marked persistently (accent bar + the shared selected field, see
   design-system/tabs.css). Distinct from :hover and :focus-visible; hover deepens. */
#files-tab .fn-tree-row.is-active-file { background: var(--cc-tab-bg-selected); color: var(--ui-text); box-shadow: inset 2px 0 0 var(--ui-accent); }
#files-tab .fn-tree-row.is-active-file:hover { background: color-mix(in srgb, var(--ui-accent) 20%, transparent); }
#files-tab .fn-tree-row.is-active-file .fn-row-name { font-weight: 650; }
#files-tab .fn-tree-row.is-active-file .fn-row-icon { color: var(--ui-accent); }
/* Round-6: brief landing highlight after "open this directory in Browse" from a search
   result — confirms which row the jump landed on. */
#files-tab .fn-tree-row.fn-row-flash { animation: fn-row-flash 1.2s ease; }
@keyframes fn-row-flash {
  0% { background: color-mix(in srgb, var(--ui-accent) 22%, transparent); }
  100% { background: transparent; }
}
@media (prefers-reduced-motion: reduce) {
  #files-tab .fn-tree-row.fn-row-flash { animation: none; }
}
/* Defect 2 (operator live-test): expanding a row painted a full outset ring around it.
   Root cause: .fn-tree-row is a <button> and only overrides `border`, not `outline` — it
   fell through to the shared button:focus-visible default (design-system/foundation.css,
   outline:2px solid var(--blue); outline-offset:2px, drawn OUTSIDE the row) which a mouse
   click can trigger on a button whose hit target is an inner SVG/span. The prototype
   defines its own inset, accent-colored focus-visible style for exactly this element
   (.tree-row:focus-visible { outline:2px solid var(--ui-accent); outline-offset:-2px }) —
   matched here. */
#files-tab .fn-tree-row:focus { outline: none; }
#files-tab .fn-tree-row:focus-visible { outline: 2px solid var(--ui-accent); outline-offset: -2px; }
#files-tab .fn-tree-row .fn-caret { width: 14px; flex: none; color: var(--ui-muted); font-size: 10px; transition: transform var(--speed, 140ms) ease; }
#files-tab .fn-tree-row[data-open="true"] .fn-caret { transform: rotate(90deg); }
#files-tab .fn-tree-row .fn-row-icon { width: 16px; flex: none; color: var(--ui-muted); }
#files-tab .fn-tree-row .fn-row-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#files-tab .fn-tree-row .fn-row-end-slot { position: relative; flex: none; min-width: 0; }
#files-tab .fn-tree-row .fn-row-meta { display: block; flex: none; font-family: var(--font-mono); font-size: 10.5px; color: var(--ui-muted); transition: opacity var(--speed, 140ms) ease; }
#files-tab .fn-tree-children { margin-left: 18px; }
#files-tab .fn-chev { width: 14px; height: 14px; flex: none; color: var(--ui-muted);
  transition: transform var(--speed, 140ms) ease; }
#files-tab .fn-tree-row[data-open="true"] .fn-chev { transform: rotate(90deg); }
#files-tab .fn-fstar { color: var(--ui-warning); font-size: 11px; flex: none; }
#files-tab .fn-tree-children[hidden] { display: none; }

/* Recent rows — file-card line composition. */
#files-tab .fn-recent-card {
  display: grid; grid-template-columns: 30px minmax(0, 1fr); gap: 12px; align-items: center;
  width: 100%; text-align: left; padding: 8px 13px; cursor: pointer;
  background: var(--ui-panel); border: 1px solid var(--ui-line); border-radius: 11px; margin-bottom: 6px;
  transition: background var(--speed, 140ms) ease;
}
#files-tab .fn-recent-card:hover { background: var(--ui-hover); }
#files-tab .fn-card-ico {
  width: 30px; height: 30px; border-radius: 9px; display: grid; place-items: center;
  background: var(--ui-hover); color: var(--ui-text-soft);
}
#files-tab .fn-card-line1 { display: flex; align-items: baseline; gap: 10px; min-width: 0; }
/* The name truncates, but NEVER through the extension — that suffix is the only
   thing telling same-basename siblings (X.pdf / X.html) apart in Recent. */
#files-tab .fn-card-name {
  font-size: 13.5px; font-weight: 600; color: var(--ui-text);
  display: flex; align-items: baseline; min-width: 0; overflow: hidden;
}
#files-tab .fn-card-stem { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
#files-tab .fn-card-ext { flex: none; white-space: nowrap; }
#files-tab .fn-card-time { margin-left: auto; flex: none; font-family: var(--font-mono); font-size: 10.5px; color: var(--ui-muted); }
#files-tab .fn-card-line2 { display: flex; align-items: baseline; gap: 10px; min-width: 0; margin-top: 1px; }
#files-tab .fn-card-path {
  font-family: var(--font-mono); font-size: 10.5px; color: var(--ui-muted); line-height: 1.3;
  flex: none; max-width: 46%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
#files-tab .fn-card-peek { font-family: var(--font-mono); font-size: 10.5px; color: var(--ui-muted); flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Pinned cards. */
#files-tab .fn-pin-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 14px; }
#files-tab .fn-pin-card {
  border: 1px solid var(--ui-line); border-radius: var(--ui-radius, 18px);
  background: var(--ui-panel); padding: 16px 17px;
  transition: background var(--speed, 140ms) ease;
}
#files-tab .fn-pin-card:hover { background: var(--ui-hover); }
#files-tab .fn-pin-head { display: flex; align-items: center; gap: 11px; margin-bottom: 12px; }
#files-tab .fn-pin-star { color: var(--ui-warning); font-size: 15px; line-height: 1; }
#files-tab .fn-pin-unstar { border: 0; background: transparent; cursor: pointer; padding: 2px; border-radius: 6px; flex: none; }
#files-tab .fn-pin-unstar:hover { background: var(--ui-hover); }
#files-tab .fn-pin-unstar:disabled { opacity: 0.5; cursor: default; }
#files-tab .fn-pin-name { font-size: var(--text-md, 15px); font-weight: 650; color: var(--ui-text); }
#files-tab .fn-pin-path { font-family: var(--font-mono); font-size: var(--text-xs, 11px); color: var(--ui-muted); margin-top: 1px; }
/* Round 10 (operator: recent files inside a pinned card are clickable, the folder name is
   not — add the direct way in to the folder itself). Was a bare, non-interactive <div>; now
   a reset <button> wrapping the exact same .fn-pin-name/.fn-pin-path children with their
   existing typography untouched (their own rules above are unaffected by the parent tag).
   No padding/margin change vs the old div — geometry is unchanged, only interactivity is
   added, so this can't shift the card's existing layout. */
#files-tab .fn-pin-headlink {
  display: block; flex: 1 1 auto; min-width: 0; text-align: left;
  background: none; border: 0; padding: 0; margin: 0; cursor: pointer; font: inherit; color: inherit;
}
#files-tab .fn-pin-headlink:hover .fn-pin-name {
  color: var(--ui-accent); text-decoration: underline;
  text-decoration-color: color-mix(in srgb, var(--ui-accent) 50%, transparent); text-underline-offset: 2px;
}
#files-tab .fn-pin-headlink:focus-visible { outline: 2px solid var(--ui-accent); outline-offset: 2px; border-radius: 6px; }
#files-tab .fn-pin-files { display: flex; flex-direction: column; gap: 2px; }
/* Loading shimmer while recentInside() fetch is in-flight — is-fetch-loading needs a height. */
#files-tab .fn-pin-files.is-fetch-loading { min-height: 44px; }
/* Round-2: missing-favorite parity with the old page's is-missing badge
   (static/files/files.css .files-pinned-missing) — a stale pin reads as stale. */
#files-tab .fn-pin-card-missing { opacity: 0.72; }
#files-tab .fn-pin-missing-badge {
  flex: none; font-family: var(--font-mono); font-size: 10px; font-weight: 600;
  letter-spacing: 0.03em; text-transform: uppercase; color: var(--ui-muted);
}
#files-tab .fn-pin-row {
  display: flex; align-items: center; gap: 8px; width: 100%; text-align: left;
  padding: 6px 8px; border-radius: 9px; border: 0; background: transparent; cursor: pointer;
  font-family: var(--font-ui); font-size: 13px; color: var(--ui-text-soft);
}
#files-tab .fn-pin-row:hover { background: var(--ui-hover); color: var(--ui-text); }
#files-tab .fn-pin-row .fn-row-meta { margin-left: auto; }

/* Search results. */
#files-tab .fn-search-empty { text-align: center; padding: 46px 20px; }
#files-tab .fn-state-ico {
  margin: 0 auto 10px; width: 42px; height: 42px; border-radius: 13px;
  display: grid; place-items: center; background: var(--ui-hover); color: var(--ui-text-soft);
}
#files-tab .fn-search-empty h3 { font-size: var(--text-md, 15px); font-weight: 600; color: var(--ui-text); }
#files-tab .fn-search-empty p { font-size: 13px; color: var(--ui-muted); margin-top: 3px; }
#files-tab .fn-result-card {
  display: grid; grid-template-columns: 30px minmax(0, 1fr) auto; gap: 12px; align-items: start;
  padding: 12px 14px; border-radius: 11px; background: var(--ui-panel); border: 1px solid var(--ui-line);
  margin-bottom: 8px; cursor: pointer; text-align: left; width: 100%;
  color: var(--ui-text); transition: background var(--speed, 140ms) ease;
}
#files-tab .fn-result-card:hover { background: var(--ui-hover); }
#files-tab .fn-result-ico {
  width: 30px; height: 30px; border-radius: 9px; display: grid; place-items: center;
  background: var(--ui-hover); color: var(--ui-text-soft); flex: none;
}
#files-tab .fn-result-name { font-size: 13.5px; font-weight: 600; display: flex; align-items: center; gap: 8px; }
#files-tab .fn-result-path { font-family: var(--font-mono); font-size: 10.5px; color: var(--ui-muted); margin-top: 1px; }
#files-tab .fn-result-snippet { font-size: 12.5px; color: var(--ui-text-soft); margin-top: 6px; line-height: 1.5; }
#files-tab .fn-result-side { font-family: var(--font-mono); font-size: var(--text-xs, 11px); color: var(--ui-muted); white-space: nowrap; }

/* Preview column. */
#files-tab .fn-preview-col {
  position: relative; min-width: 0; height: 100%; min-height: 0;
  border: 1px solid var(--ui-line); border-radius: var(--ui-radius, 18px);
  background: var(--ui-panel); overflow: hidden;
  display: flex; flex-direction: column;
}
/* Defect 9 (operator live-test): the Pinned/Recent/Browse/Search tab row must run full
   width ABOVE the preview area, like the prototype's .page-tabs (which sits above the
   whole .files-layout grid, not inside one column of it). Markup moved in index.html;
   this restyles it from "inside the side column" to the prototype's full-width strip. */
#files-tab .fn-page-tabs { margin: 10px 0 0; flex-wrap: wrap; }
/* Round-4 (operator live-test — "tabs cascade"/round-3's "preview covers the tabs"):
   .fn-file-tabs and .fn-preview-head are flex items of .fn-preview-col's column layout
   alongside .fn-preview-body, but neither ever declared flex-shrink:0 — the unset
   default (shrink:1) let the flexbox algorithm compress them whenever the ACTIVE file's
   body content was tall enough that the column's hypothetical total height exceeded the
   bounded container (a short test file never triggers this, which is why round 3's
   static geometry check on short synthetic files passed clean; a long file collapsed the
   tab strip to ~8px even with a single tab open — not really a per-tab-count cascade,
   but content-height-driven, and looked like one because later-opened files happened to
   be longer). .fn-preview-body already carries flex:1 1 auto and overflow:auto — it is
   the ONLY element meant to absorb height pressure; tabs and head must never shrink. */
#files-tab .fn-file-tabs {
  display: flex; gap: 4px; padding: 6px 10px 0; overflow-x: auto; scrollbar-width: thin;
  border-bottom: 1px solid var(--ui-line-soft);
  background: color-mix(in srgb, var(--ui-bg) 55%, transparent);
  flex: 0 0 auto;
}
#files-tab .fn-file-tab {
  display: inline-flex; align-items: center; gap: 8px; flex: none;
  padding: 4px 7px 4px 10px; border-radius: 8px 8px 0 0;
  background: transparent; border: 1px solid transparent; border-bottom: none;
  color: var(--ui-muted); cursor: pointer;
  font-family: var(--font-mono); font-size: var(--text-xs, 11px);
  max-width: 200px;
}
#files-tab .fn-file-tab:hover { color: var(--ui-text); }
#files-tab .fn-file-tab.active { background: var(--ui-hover); color: var(--ui-text); border-color: var(--ui-line); }
#files-tab .fn-ft-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#files-tab .fn-ft-close {
  width: 16px; height: 16px; border-radius: 5px; display: grid; place-items: center;
  color: var(--ui-muted); flex: none; border: 0; background: transparent; cursor: pointer;
  font-size: 13px; line-height: 1; padding: 0;
}
#files-tab .fn-ft-close:hover { background: var(--ui-hover); color: var(--ui-text); }
#files-tab .fn-preview-head {
  display: flex; align-items: center; gap: 10px; padding: 10px 16px;
  border-bottom: 1px solid var(--ui-line-soft);
  flex: 0 0 auto;
}
#files-tab .fn-crumb {
  min-width: 0; flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-family: var(--font-mono); font-size: var(--text-xs, 11px); color: var(--ui-muted);
}
#files-tab .fn-crumb b { color: var(--ui-text-soft); font-weight: 600; }
#files-tab .fn-preview-meta { font-family: var(--font-mono); font-size: 10.5px; color: var(--ui-muted); flex: none; display: flex; align-items: center; gap: 8px; }
#files-tab .fn-preview-meta:empty { display: none; }
/* Stage 2a: language chip (code preview) tinted to match the rest of the fn- toolbar's
   accent-pressed vocabulary (.fn-pm-btn[aria-pressed="true"]) rather than the old page's
   neutral .file-language — the surface's own accent language, same underlying data. */
#files-tab .fn-preview-language {
  background: color-mix(in srgb, var(--ui-accent) 13%, transparent); color: var(--ui-accent);
}
#files-tab .fn-preview-lines::before { content: "·"; margin-right: 6px; }
#files-tab .fn-html-note { white-space: nowrap; }
#files-tab .fn-preview-body { flex: 1 1 auto; overflow: auto; padding: 16px 24px 30px; }
/* Round-16 (operator: some rendered markdown files scroll far left-right): the pane never
   scrolls horizontally for markdown/text — overflow-x:hidden forces wide content (unwrapped
   code lines, wide tables) into ITS OWN inner overflow-x:auto container (.fn-doc pre/table
   below) instead of stretching the whole preview body sideways. Explicitly excludes
   .code-preview-host — the shared code viewer's own horizontal scroll (static/app-shell/
   overlays.css's .code-preview-host, long unwrapped source lines) is legitimate there and
   must keep working; this rule only touches the markdown/plain-text default state. */
#files-tab .fn-preview-body:not(.code-preview-host):not(.html-preview-host) { overflow-x: hidden; }
/* Stage 2a: HTML sandbox + syntax-highlighted code previews reuse the old page's global
   .html-preview-host/.code-preview-host classes (static/app-shell/overlays.css, theme-aware
   --ui-* tokens already shared page-wide) for the iframe/line-numbered layout itself; these
   two rules only zero out this panel's own padding so those layouts reach the edges the same
   way they do on the old page, instead of sitting inset by fn-preview-body's 16/24/30 padding. */
#files-tab .fn-preview-body.html-preview-host,
#files-tab .fn-preview-body.code-preview-host { padding: 0; }
#files-tab .fn-preview-empty {
  height: 100%; min-height: 320px; display: grid; place-content: center; justify-items: center;
  gap: 8px; color: var(--ui-muted); text-align: center; font-size: 13.5px;
}
#files-tab .fn-preview-content {
  max-width: 92ch; margin: 0 auto;
  font-family: var(--font-mono); font-size: 13px; line-height: 1.6; color: var(--ui-text-soft);
  white-space: pre-wrap; word-break: break-word;
}
#files-tab .fn-preview-error { color: var(--ui-danger); font-size: 13px; }

/* Section labels. */
#files-tab .fn-pane-label {
  font-family: var(--font-mono); font-size: 10.5px; font-weight: 500; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--ui-muted); margin: 10px 4px 8px;
}
#files-tab .fn-empty-quiet { color: var(--ui-muted); font-size: 13px; padding: 24px 8px; text-align: center; }
/* Warming/loading in Recent: never the "empty" copy. The dot, copy and skeleton
   markup are the SHARED treatment (design-system/loading.css). */
#files-tab .fn-recent-warming,
#files-tab .fn-recent-loading { display: grid; gap: 4px; padding: 30px 4px 10px; }
/* Browse's rows are un-carded, so the skeleton sits on the row rhythm. */
#files-tab .fn-tree-loading { padding: 4px 0 6px 8px; }
#files-tab .fn-tree-loading .cc-skel-row { height: 26px; }
#files-tab .fn-tree-loading .cc-skel--icon { width: 16px; height: 16px; border-radius: 5px; }
/* The reading surface: a document-shaped skeleton, page-width, not list rows. */
#files-tab .fn-preview-loading { padding: 26px 22px; max-width: 640px; }
#files-tab .fn-preview-loading .cc-skel-list { gap: 11px; }
#files-tab .fn-preview-loading .cc-skel--title { width: 46%; height: 15px; }
#files-tab .fn-preview-loading .cc-skel--line { width: 100%; }
#files-tab .fn-preview-loading .cc-skel--sub { width: 62%; }
/* Failed preview: keeps its voice, and offers a way forward. */
#files-tab .fn-preview-failed { display: grid; justify-items: start; gap: 10px; padding: 22px; }

/* Divider. */
#files-tab .fn-divider {
  position: absolute; left: -13px; top: 0; bottom: 0; width: 26px;
  cursor: col-resize; z-index: 5;
}
#files-tab .fn-divider::after {
  content: ""; position: absolute; left: 12px; top: 0; bottom: 0; width: 2px;
  background: transparent; transition: background var(--speed, 140ms) ease;
}
#files-tab .fn-divider:hover::after, #files-tab .fn-divider.dragging::after { background: var(--ui-accent); }
/* Round 8: the divider drags --pv-width, which is meaningless while the side column is
   collapsed (its track is hard-coded to 0, not var(--pv-width)) — disable it rather than
   let a drag silently queue up a width that only appears once re-expanded. */
#files-tab .fn-files-layout.fn-side-collapsed .fn-divider { pointer-events: none; }

/* Round 8: collapse/expand chevron, positioned on the column edge like the divider (same
   -13px anchor relative to .fn-preview-col, so it tracks the boundary in both states
   without extra positioning logic — collapsed, the boundary IS the far left edge). Only
   offered at >=980px (operator: below that, keep the existing sheet/stacked behaviour —
   the narrow layout already gives the preview the full width on its own). */
/* left:2px, not the divider's -13px: .fn-preview-col has overflow:hidden (needed to clip
   scrolled content to its own 18px rounded corners), which also clips any child's negative
   offset — confirmed empirically (elementFromPoint at the button's own geometric center
   landed on the grid/background behind it, not the button; only the right sliver still
   inside the padding box was actually hit-testable). Staying inside the box entirely keeps
   the whole 20px target clickable. */
#files-tab .fn-side-toggle {
  position: absolute; left: 2px; top: 50%; transform: translateY(-50%);
  width: 20px; height: 40px; border-radius: 8px; z-index: 6;
  display: none; align-items: center; justify-content: center;
  background: var(--ui-panel); border: 1px solid var(--ui-line); color: var(--ui-muted);
  cursor: pointer; padding: 0;
  transition: background var(--speed, 140ms) ease, color var(--speed, 140ms) ease;
}
@media (min-width: 980px) {
  #files-tab .fn-side-toggle { display: inline-flex; }
}
#files-tab .fn-side-toggle:hover { background: var(--ui-hover); color: var(--ui-text); border-color: var(--ui-accent); }
#files-tab .fn-side-toggle-chev { transition: transform var(--speed, 140ms) ease; }
#files-tab .fn-side-toggle[aria-pressed="true"] .fn-side-toggle-chev { transform: rotate(180deg); }

@media (max-width: 820px) {
  /* Narrow/sheet mode: list and preview stack, so the two-pane height containment from
     the desktop fix (defect 1/8) would force an awkward nested scroll. Fall back to the
     page's own scroll like every other tab. */
  #files-tab { display: block; height: auto; overflow: visible; }
  #files-tab .fn-files-layout, #files-tab .fn-files-layout.fn-previewing {
    grid-template-columns: minmax(0, 1fr); overflow: visible; min-height: 0;
  }
  #files-tab .fn-files-side { height: auto; overflow: visible; }
  #files-tab .fn-pane:not([hidden]) { overflow-y: visible; }
  #files-tab .fn-preview-col { order: 2; margin-top: 14px; height: auto; min-height: 320px; }
}

@media (max-width: 480px) {
  /* Phone: the preview header's title + mode-seg + PDF zoom-seg + actions no longer
     fit one row (title text and the Raw toggle overlapped, Copy/close were pushed off
     the right edge). Title gets its own full-width row; the control groups wrap onto
     a second row below it. */
  #files-tab .fn-preview-head { flex-wrap: wrap; row-gap: 8px; }
  #files-tab .fn-preview-id { flex: 1 1 100%; }
  #files-tab .fn-pdf-zoomseg,
  #files-tab .fn-preview-modeseg,
  #files-tab .fn-preview-actions {
    flex: 0 0 auto; margin-left: 0; flex-wrap: wrap;
  }
  #files-tab .fn-preview-actions { width: 100%; }
}

/* T466: upload button, row actions, preview variants, toast. */
#files-tab .fn-tab-upload {
  display: inline-flex; align-items: center; gap: 8px; line-height: 1;
  margin-bottom: 7px; padding: 8px 14px; border-radius: 11px; cursor: pointer;
  font-family: var(--font-ui); font-size: 13px; font-weight: 600;
  background: var(--ui-panel); border: 1px solid var(--ui-line); color: var(--ui-text-soft);
  transition: background var(--speed, 140ms) ease, color var(--speed, 140ms) ease;
}
#files-tab .fn-tab-upload:hover { background: var(--ui-hover); color: var(--ui-text); }
#files-tab .fn-tab-upload:active { transform: translateY(1px); }
/* Round-18: icon-only refresh, same button anatomy as the preview toolbar's icon buttons
   (Round 16) — pushed to the right edge alongside +Upload via the auto margin that used to
   live on .fn-tab-upload itself (now the FIRST of the two right-aligned controls). */
#files-tab .fn-tab-refresh {
  display: inline-flex; align-items: center; justify-content: center;
  margin-left: auto; margin-bottom: 7px; padding: 8px; border-radius: 11px; cursor: pointer;
  background: var(--ui-panel); border: 1px solid var(--ui-line); color: var(--ui-text-soft);
  transition: background var(--speed, 140ms) ease, color var(--speed, 140ms) ease;
}
#files-tab .fn-tab-refresh .ui-icon { width: 16px; height: 16px; }
#files-tab .fn-tab-refresh:hover { background: var(--ui-hover); color: var(--ui-text); }
#files-tab .fn-tab-refresh:active { transform: translateY(1px); }
#files-tab .fn-tab-refresh:disabled { opacity: 0.5; cursor: default; }
#files-tab .fn-tab-refresh.fn-refreshing .ui-icon { animation: fn-refresh-spin 0.8s linear infinite; }
@keyframes fn-refresh-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  #files-tab .fn-tab-refresh.fn-refreshing .ui-icon { animation: none; }
}

#files-tab .fn-tree-row .fn-row-actions { position: absolute; top: 0; bottom: 0; right: 0; display: inline-flex; align-items: center; gap: 2px; pointer-events: none; opacity: 0; transition: opacity var(--speed, 140ms) ease; }
/* No transform here: a transformed ancestor becomes the containing block for the
   row's position:fixed .fn-actions-menu, so its viewport coordinates land
   off-screen and the open menu is invisible. Vertical centering is flex. */
#files-tab .fn-tree-row:hover .fn-row-actions,
#files-tab .fn-tree-row:focus-within .fn-row-actions { pointer-events: auto; opacity: 1; }
#files-tab .fn-tree-row:hover .fn-row-end-slot > .fn-row-meta,
#files-tab .fn-tree-row:focus-within .fn-row-end-slot > .fn-row-meta { opacity: 0; }
#files-tab .fn-ra-btn {
  width: 26px; height: 26px; border-radius: 8px; display: grid; place-items: center;
  background: transparent; border: none; cursor: pointer; color: var(--ui-muted);
  font-size: 13px; line-height: 1; padding: 0;
}
#files-tab .fn-ra-btn:hover { background: var(--ui-hover); color: var(--ui-text); }
#files-tab .fn-ra-btn.starred { color: var(--ui-warning); }
/* Round-6: subtle error pulse when an optimistic pin/unpin write fails and the star or
   card reverts — brief enough to read as "that didn't stick", not an alarm. */
#files-tab .fn-star-error { animation: fn-fail-pulse 1.6s ease; }
#files-tab .fn-pin-card-error { animation: fn-fail-pulse 1.6s ease; }
@keyframes fn-fail-pulse {
  0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--ui-danger) 55%, transparent); }
  20% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--ui-danger) 35%, transparent); }
  100% { box-shadow: 0 0 0 0 transparent; }
}
/* Round-16: same pulse language, accent-colored — confirms a surgically-inserted row (upload
   landing in an open directory) without a disruptive full-pane reload/rebuild to signal it. */
#files-tab .fn-row-landed { animation: fn-landed-pulse 1.6s ease; }
@keyframes fn-landed-pulse {
  0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--ui-accent) 45%, transparent); background: var(--ui-accent-soft); }
  20% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--ui-accent) 30%, transparent); background: var(--ui-accent-soft); }
  100% { box-shadow: 0 0 0 0 transparent; background: transparent; }
}
@media (prefers-reduced-motion: reduce) {
  #files-tab .fn-star-error, #files-tab .fn-pin-card-error, #files-tab .fn-row-landed { animation: none; }
}
/* Round-5: fixed (viewport-relative, left/top set in JS at open time) — an absolute menu
   nested in a root-level row got clipped by #fn-pane-tree > .fn-tree-list's own
   overflow:hidden (round-3, needed for that card's rounded corners). */
#files-tab .fn-actions-menu {
  position: fixed; z-index: 60;
  min-width: 200px; padding: 5px;
  background: color-mix(in srgb, var(--ui-bg) 55%, var(--ui-panel));
  backdrop-filter: blur(14px);
  border: 1px solid var(--ui-line); border-radius: 12px;
  box-shadow: var(--ui-shadow);
}
#files-tab .fn-actions-menu[hidden] { display: none; }
#files-tab .fn-actions-menu button {
  display: block; width: 100%; padding: 8px 11px; border: none; border-radius: 8px;
  background: transparent; color: var(--ui-text-soft); font-family: inherit;
  font-size: 13px; font-weight: 500; cursor: pointer; text-align: left;
}
#files-tab .fn-actions-menu button:hover { background: var(--ui-hover); color: var(--ui-text); }
/* Stage 2b: delete is destructive — flag it visually, matching .fn-preview-error's --ui-danger. */
#files-tab .fn-actions-menu button.fn-ra-danger { color: var(--ui-danger); }
#files-tab .fn-actions-menu button.fn-ra-danger:hover {
  background: color-mix(in srgb, var(--ui-danger) 12%, transparent); color: var(--ui-danger);
}
#files-tab .fn-actions-menu button.fn-ra-danger:disabled { color: var(--ui-muted); }

#files-tab .fn-preview-image { display: block; max-width: 100%; margin: 0 auto; border-radius: 10px; }
#files-tab .fn-preview-frame { width: 100%; height: 100%; min-height: 480px; border: 0; background: transparent; }

/* Defect 4/6: read/raw mode segment + PDF zoom segment (prototype .preview-modeseg /
   .pdf-zoomseg, .pm-btn) — production toolbar vocabulary (doc 57 §6). */
#files-tab .fn-preview-modeseg, #files-tab .fn-pdf-zoomseg {
  display: inline-flex; align-items: center; gap: 2px;
  border: 1px solid var(--ui-line); border-radius: 8px; padding: 2px; flex: none;
}
#files-tab .fn-pm-btn {
  padding: 3px 10px; border-radius: 6px; border: none; background: transparent;
  color: var(--ui-muted); cursor: pointer; font-family: var(--font-mono);
  font-size: var(--text-xs, 11px); font-weight: 500;
  transition: background var(--speed, 140ms) ease, color var(--speed, 140ms) ease;
}
#files-tab .fn-pm-btn:hover { color: var(--ui-text); }
#files-tab .fn-pm-btn:disabled { opacity: 0.4; cursor: default; }
#files-tab .fn-pm-btn[aria-pressed="true"] { background: color-mix(in srgb, var(--ui-accent) 13%, transparent); color: var(--ui-accent); }
#files-tab .fn-pz-label { font-family: var(--font-mono); font-size: 10px; color: var(--ui-muted); min-width: 30px; text-align: center; }

/* PDF pages — canvas-rendered via pdf.js, fetched through the authenticated blob path. */
#files-tab .fn-pdf-pages {
  width: 100%; min-width: 0; min-height: 0; flex: 1 1 auto; overflow: auto;
  scrollbar-gutter: stable;
  display: flex; flex-direction: column; align-items: center; gap: 4px; padding: 0;
}
#files-tab .fn-pdf-page {
  display: block;
  box-shadow: 0 4px 18px color-mix(in srgb, var(--ui-bg, #000) 45%, transparent);
  border-radius: 4px; background: #fff;
}
#files-tab .fn-pdf-pages.fn-pdf-fit-width .fn-pdf-page {
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ui-line) 65%, transparent);
}
/* Single-page mode is a bounded presentation viewport: the page uses all available width /
   height after the toolbar, and the body itself does not gain a second vertical scroll. */
#files-tab .fn-preview-body.pdf-preview-host { padding: 0; }
#files-tab .fn-preview-body.pdf-preview-host.fn-pdf-single-host { overflow: hidden; }
#files-tab .fn-pdf-pages.fn-pdf-single {
  width: 100%; height: 100%; min-height: 0; justify-content: center; overflow: hidden;
}
/* Figure wrapper remains only as the annotation-link positioning context. */
#files-tab .fn-pdf-page-wrap {
  position: relative; margin: 0; max-width: 100%; flex: 0 0 auto;
}

/* Mouse activation may leave a button focused in Chromium. Keep the viewer quiet for mouse
   clicks while retaining the keyboard-only focus-visible ring for tab navigation. */
#files-tab .fn-pdf-zoomseg button:focus:not(:focus-visible),
#files-tab .fn-pdf-pages:focus:not(:focus-visible),
#files-tab .fn-preview-actions button:focus:not(:focus-visible) { outline: none; box-shadow: none; }
#files-tab .fn-pdf-zoomseg button[data-edge="first"],
#files-tab .fn-pdf-zoomseg button[data-edge="last"] { opacity: .55; }

/* Defect 5: Full view (Esc exits) — prototype's in-page fullview toggle, scoped to the
   preview column so it overlays the whole viewport without leaving the tab. */
#files-tab .fn-files-layout.fn-fullview { position: relative; }
#files-tab .fn-files-layout.fn-fullview .fn-preview-col {
  position: fixed; inset: 0; z-index: 120; width: 100%; height: 100dvh;
  border-radius: 0;
  /* --ui-panel is a ~3% translucent glass tint (by design, for cards sitting on the
     page background) — as a fixed full-viewport overlay it let the rail/tabs/tree
     ghost straight through underneath. Full view needs the page's actual opaque
     surface here, not the glass tint. */
  background: var(--ui-bg);
}
#files-tab .fn-files-layout.fn-fullview .fn-divider { display: none; }
/* Round 8: Full view already takes the entire viewport (supersedes the collapse feature
   outright — there's no side column left to collapse once .fn-preview-col is fixed/inset:0). */
#files-tab .fn-files-layout.fn-fullview .fn-side-toggle { display: none; }
#files-tab .fn-preview-table { border-collapse: collapse; width: 100%; font-size: 12px; }
#files-tab .fn-preview-table th, #files-tab .fn-preview-table td {
  border: 1px solid var(--ui-line-soft); padding: 5px 9px; text-align: left; white-space: nowrap;
}
#files-tab .fn-preview-table th { color: var(--ui-muted); font-weight: 600; background: var(--ui-hover); position: sticky; top: 0; }

#files-tab .fn-toast {
  position: fixed; left: 50%; bottom: 22px; transform: translateX(-50%) translateY(8px);
  z-index: 130; padding: 9px 16px; border-radius: 11px;
  background: color-mix(in srgb, var(--ui-bg) 30%, var(--ui-panel));
  backdrop-filter: blur(14px); border: 1px solid var(--ui-line);
  color: var(--ui-text); font-size: 13px; font-weight: 500;
  opacity: 0; pointer-events: none;
  transition: opacity var(--speed, 140ms) ease, transform var(--speed, 140ms) ease;
}
#files-tab .fn-toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
#files-tab .fn-toast-error { border-color: color-mix(in srgb, var(--ui-danger) 40%, transparent); color: var(--ui-danger); }

/* T468: time tiers in recent cards (prototype .ft-wrap thresholds). */
#files-tab .fn-recent-card { container-type: inline-size; }
#files-tab .fn-card-time { margin-left: auto; flex: none; }
#files-tab .fn-ft-wrap .t1, #files-tab .fn-ft-wrap .t2 { display: none; }
@container (max-width: 380px) {
  #files-tab .fn-ft-wrap .t0 { display: none; }
  #files-tab .fn-ft-wrap .t1 { display: inline; }
}
@container (max-width: 300px) {
  #files-tab .fn-ft-wrap .t1 { display: none; }
  #files-tab .fn-ft-wrap .t2 { display: inline; }
}
@container (max-width: 230px) {
  #files-tab .fn-card-time { display: none; }
}

/* T473/T480: prototype page-head — measured truth (no top search field; the field lives in the search pane). */
#files-tab .fn-page-head { margin-bottom: 12px; }
#files-tab .fn-page-head h1 { font-size: var(--text-xl, 22px); font-weight: 700; letter-spacing: -0.015em; color: var(--ui-text); line-height: 1.55; }
/* Round-7 (operator: the index status must sit INLINE with the subtitle, zero added
   vertical height — round 6 put it on its own row below and it cost real height). One
   flex row now carries both the subtitle and the status; the row owns the 3px top offset
   from h1 that used to live on the bare subtitle <p>, so total header height is unchanged
   from before round 6 ever added a status line. */
#files-tab .fn-page-subline {
  /* center, not baseline: .fn-index-status is itself a flex container (dot + text), and
     baseline alignment against a nested-flex sibling can synthesize an oversized cross-axis
     line box in some engines — measured actually adding height back, the exact regression
     this row exists to avoid. center keeps the row's height pinned to the taller single
     item's own line-height, nothing more. */
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; margin-top: 3px;
}
/* .fn-page-sub carries its own class (not a bare `p` selector) so its rules never win a
   specificity fight against .fn-index-status, which is ALSO a <p> in this same row —
   a bare `#files-tab .fn-page-subline p` rule out-specifies `#files-tab
   .fn-index-status` (extra type selector) and was silently forcing the status text to the
   subtitle's 15px/--ui-text-soft instead of its own 11px mono/--ui-muted. Truncates with
   an ellipsis rather than wrapping to a second line — letting it wrap was the actual
   source of the "added height" round 6/7's own inline fix was suppose to avoid: at
   narrower widths the subtitle text no longer fit the row next to the status widget and
   wrapped to two lines, which is what "zero added vertical height" must rule out at every
   width, not just wide desktop. */
#files-tab .fn-page-sub {
  color: var(--ui-text-soft); font-size: var(--text-md, 15px); line-height: 1.55; margin: 0;
  flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Round-6: index status relocates from the removed .fn-quiet footer to the header, next to
   the "Working memory" heading/subtitle — live file count when idle, a pulsing dot + label
   while the workspace index is actively rebuilding (supervisor/workspace_index.py's
   GET /api/v1/system/index-health). Hidden until the first fetch resolves so it never flashes
   an unknown/placeholder value. */
#files-tab .fn-index-status {
  display: flex; align-items: center; gap: 7px; flex: none; white-space: nowrap;
  font-family: var(--font-mono); font-size: var(--text-xs, 11px); color: var(--ui-muted);
}
#files-tab .fn-index-status-dot {
  width: 6px; height: 6px; border-radius: 50%; background: var(--ui-muted); flex: none;
}
#files-tab .fn-index-status[data-indexing="true"] .fn-index-status-dot {
  background: var(--ui-accent); animation: fn-index-pulse 1.1s ease-in-out infinite;
}
#files-tab .fn-index-status[data-indexing="true"] { color: var(--ui-text-soft); }
@keyframes fn-index-pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: 0.35; transform: scale(0.7); }
}
@media (prefers-reduced-motion: reduce) {
  #files-tab .fn-index-status[data-indexing="true"] .fn-index-status-dot { animation: none; }
}
/* Round 10 (operator: "index warming up" after a restart, when a last-known count is already
   cached, reads as confusing/wrong): a cached-count fallback gets a quiet dim, not the
   pulsing dot (that stays reserved for an ACTUALLY running rebuild, data-indexing="true"
   above) — the number itself is real and current-ish, "· refreshing" in the text already
   says the rest. */
#files-tab .fn-index-status[data-refreshing="true"] { opacity: 0.78; }
/* Round 9: subtle "refreshing" indicator for Recent's background reconcile (item 2) — same
   small pulsing-dot language as .fn-index-status-dot above, deliberately not a spinner and
   never drawn over existing content; it sits next to the Recent tab's own count badge and
   is the ONLY visual sign a background refresh is in flight. */
#files-tab .fn-recent-refresh-dot {
  display: inline-block; width: 5px; height: 5px; border-radius: 50%; margin-left: 5px;
  background: var(--ui-accent); vertical-align: middle;
  animation: fn-index-pulse 1.1s ease-in-out infinite;
}
#files-tab .fn-recent-refresh-dot[hidden] { display: none; }
@media (prefers-reduced-motion: reduce) {
  #files-tab .fn-recent-refresh-dot { animation: none; }
}
#files-tab .fn-search-pane .fn-file-search { margin: 0 0 14px; }

#files-tab .fn-pin-count { margin-left: auto; font-family: var(--font-mono); font-size: var(--text-xs, 11px); color: var(--ui-muted); flex: none; }

/* T474: prototype markup alignments — pin rows (span + em, no icon), recency labels, preview id row. */
#files-tab .fn-pin-row { display: flex; align-items: center; gap: 8px; width: 100%; text-align: left;
  padding: 6px 8px; border-radius: 9px; border: 0; background: transparent; cursor: pointer;
  font-family: var(--font-ui); font-size: 13px; color: var(--ui-text-soft); }
#files-tab .fn-pin-row:hover { background: var(--ui-hover); color: var(--ui-text); }
#files-tab .fn-pin-row em { margin-left: auto; font-style: normal; font-family: var(--font-mono); font-size: 10.5px; color: var(--ui-muted); flex: none; }
#files-tab .fn-recency-label {
  font-family: var(--font-mono); font-size: var(--text-xs, 11px); font-weight: 500;
  letter-spacing: 0.07em; text-transform: uppercase; color: var(--ui-muted);
  margin: 16px 0 7px; padding-left: 2px;
}
#files-tab .fn-recency-label:first-child { margin-top: 14px; }
/* Round-3: bucket count + show-more (Recent no longer silently truncates to 12). */
#files-tab .fn-recency-count { color: var(--ui-muted); opacity: 0.7; }
#files-tab .fn-recent-more {
  display: block; width: 100%; margin: 4px 0 10px; padding: 8px; border-radius: 9px;
  border: 1px dashed var(--ui-line); background: transparent; color: var(--ui-muted);
  font-family: var(--font-mono); font-size: 11.5px; font-weight: 500; cursor: pointer;
}
#files-tab .fn-recent-more:hover { background: var(--ui-hover); color: var(--ui-text); }
#files-tab .fn-preview-id { display: flex; align-items: baseline; gap: 9px; min-width: 0; flex: 1; }
/* Round-5 (operator: "the open item goes over the tabs" — a long filename bled into the
   Download/Full view/Copy row): .fn-preview-title had flex:none + no overflow handling,
   so past a point it just kept growing full-width, past .fn-preview-path (which already
   truncated correctly) and under .fn-preview-actions. Gave it shrink+truncation, capped at
   55%, so a long name couldn't crowd out the toolbar.
   Round 9 (operator: shrink priority was backwards — the FILE NAME shrank first when space
   ran out; it must be the LAST thing to shrink, with the path ellipsizing first and a
   title-attribute tooltip carrying the full path). Flexbox has no true "shrink A completely
   before B starts" primitive — flex-shrink only does WEIGHTED proportional distribution of
   the deficit — so priority here is approximated two ways: (1) .fn-preview-path's shrink
   factor is 24x .fn-preview-title's, so in every realistic crowding scenario the deficit is
   overwhelmingly absorbed by the path first; (2) .fn-preview-title's min-width is a generous
   88px floor (was 40px) and its max-width raised from 55% to 78% (was an artificial ceiling
   from round 5, back when path had no defined priority at all) — the name only ever
   ellipsizes once the path is ALREADY at its own min-width:0 floor and space is still short.
   fn-preview-actions/.fn-pv-btn get flex:none (matching the modeseg/zoomseg pattern already
   used elsewhere in this file) so the toolbar's button labels are never a shrink candidate at
   all — "Action buttons keep their labels" is enforced structurally, not by hoping the other
   two items give way first. Full path/name shown via native title-attribute tooltips (see
   renderPreview() in files-tabs.js) — belt-and-braces even for the name, which should
   essentially never truncate now. */
#files-tab .fn-preview-title {
  font-size: 13.5px; font-weight: 650; white-space: nowrap; color: var(--ui-text);
  overflow: hidden; text-overflow: ellipsis; flex: 0 1 auto; min-width: 88px; max-width: 78%;
}
#files-tab .fn-preview-path {
  display: flex; align-items: baseline; gap: 0;
  font-family: var(--font-mono); font-size: 10.5px; color: var(--ui-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  flex: 0 24 auto; min-width: 0;
}
#files-tab .fn-preview-path-segment {
  flex: 0 1 auto; min-width: 0; padding: 0; overflow: hidden;
  border: 0; border-radius: 2px; text-overflow: ellipsis; white-space: nowrap;
  background: transparent; color: inherit; font: inherit; line-height: inherit;
  cursor: pointer;
}
#files-tab .fn-preview-path-segment:hover,
#files-tab .fn-preview-path-segment:focus-visible {
  color: var(--ui-accent); text-decoration: underline; text-underline-offset: 2px;
}
#files-tab .fn-preview-path-segment:focus-visible {
  outline: 1px solid var(--ui-accent); outline-offset: 2px;
}
#files-tab .fn-preview-path-separator { flex: 0 0 auto; padding: 0 0.2em; }
#files-tab .fn-preview-actions { margin-left: auto; display: flex; align-items: center; gap: 6px; flex: none; }
#files-tab .fn-pv-btn {
  flex: none;
  display: inline-flex; align-items: center; gap: 6px; padding: 6px 10px; border-radius: 9px; cursor: pointer;
  background: transparent; border: 1px solid var(--ui-line); color: var(--ui-text-soft);
  font-family: inherit; font-size: 12px; font-weight: 600;
}
#files-tab .fn-pv-btn:hover { background: var(--ui-hover); color: var(--ui-text); }
/* Round-16 (operator decision, orchestrator concurs): icon-ify the preview toolbar —
   Copy/Download/Full view are icon-only now (Read/Raw stays textual — a mode word, not an
   action). Square, centered padding replaces the text button's asymmetric horizontal
   padding; the icon itself is sized down from the shell's 1.25rem .ui-icon default to keep
   this compact row from growing. */
#files-tab .fn-pv-icon-btn { padding: 6px; }
#files-tab .fn-pv-icon-btn .ui-icon { width: 18px; height: 18px; }
#files-tab .fn-pv-icon-btn[aria-pressed="true"] {
  background: var(--ui-accent-soft);
  border-color: var(--ui-accent);
  color: var(--ui-accent);
}
/* AR-round6 (doc 65, coordinator addendum): full-view-only Refresh — same spin convention as
   the tree's own #fn-refresh-tree (fn-refresh-spin, already defined above), scoped to this
   button's own id rather than a shared class since it lives in a different toolbar row. */
#files-tab .fn-pv-btn:disabled { opacity: 0.5; cursor: default; }
#fn-fullview-refresh.fn-refreshing .ui-icon { animation: fn-refresh-spin 0.8s linear infinite; }
@media (prefers-reduced-motion: reduce) {
  #fn-fullview-refresh.fn-refreshing .ui-icon { animation: none; }
}
#files-tab .fn-preview-close { margin-left: 0; border: 0; background: transparent; color: var(--ui-muted);
  font-size: 16px; line-height: 1; cursor: pointer; padding: 4px 8px; border-radius: 8px; }
#files-tab .fn-preview-close:hover { background: var(--ui-hover); color: var(--ui-text); }

/* T476: document reading typography (prototype .doc). */
#files-tab .fn-doc { max-width: 64ch; margin: 0 auto; overflow-wrap: anywhere; }
#files-tab .fn-doc h1 { font-size: var(--text-xl, 22px); font-weight: 700; letter-spacing: -0.015em; margin-bottom: 4px; color: var(--ui-text); }
#files-tab .fn-doc h2 { font-size: var(--text-lg, 17px); font-weight: 650; margin: 22px 0 8px; color: var(--ui-text); }
#files-tab .fn-doc h3 { font-size: var(--text-md, 15px); font-weight: 650; margin: 18px 0 6px; color: var(--ui-text); }
#files-tab .fn-doc p { font-size: var(--text-base, 14px); color: var(--ui-text-soft); line-height: 1.65; margin-bottom: 12px; font-family: var(--font-ui); }
#files-tab .fn-doc strong { color: var(--ui-text); }
#files-tab .fn-doc code { font-family: var(--font-mono); font-size: 0.85em; background: var(--ui-hover); border-radius: 4px; padding: 1px 5px; }
#files-tab .fn-doc ul { margin: 0 0 12px 18px; }
#files-tab .fn-doc li { font-size: var(--text-base, 14px); color: var(--ui-text-soft); line-height: 1.6; }
/* Round-16 (operator: some rendered markdown files scroll far left-right). Root cause:
   .fn-doc never styled <pre>/<table> (renderMarkdown's own sanitized output — marked.js +
   DOMPurify, static/runtime/markdown-shared.js) at all, so an unwrapped long code-block line
   or a wide table rendered at raw browser-default width and pushed .fn-preview-body's own
   scroll region wider, scrolling the WHOLE pane. Fix: the pane's ancestor never scrolls
   horizontally (#fn-preview-body below); a code block and a table each get their own
   overflow-x:auto container instead, so only THAT element scrolls, prose still just wraps.
   Note for whoever next touches the OLD page's shared .markdown-preview table rule
   (static/app-shell/overlays.css): it sets overflow-x:auto then a later overflow:hidden in
   the same declaration silently cancels it (the shorthand resets both axes) — a real,
   separate bug, left unfixed here since it's outside this page's own CSS and this round's
   scope, but worth flagging. */
#files-tab .fn-doc pre {
  overflow-x: auto; max-width: 100%; margin: 12px 0; padding: 12px 14px; border-radius: 8px;
  border: 1px solid var(--ui-line); background: var(--ui-hover);
}
#files-tab .fn-doc pre code { background: transparent; padding: 0; white-space: pre; }
#files-tab .fn-doc table {
  display: block; max-width: 100%; overflow-x: auto; margin: 12px 0;
  border-collapse: separate; border-spacing: 0; border: 1px solid var(--ui-line); border-radius: 8px;
}
#files-tab .fn-doc th, #files-tab .fn-doc td {
  padding: 7px 11px; text-align: left; font-size: var(--text-base, 14px);
  border-right: 1px solid var(--ui-line); border-bottom: 1px solid var(--ui-line); white-space: nowrap;
}
#files-tab .fn-doc th:last-child, #files-tab .fn-doc td:last-child { border-right: 0; }
#files-tab .fn-doc tr:last-child td { border-bottom: 0; }
#files-tab .fn-doc th { color: var(--ui-text); font-weight: 650; }
#files-tab .fn-doc blockquote {
  margin: 12px 0; padding-left: 12px; border-left: 3px solid var(--ui-line); color: var(--ui-muted);
}
#files-tab .fn-doc img { max-width: 100%; height: auto; }
#files-tab .fn-doc a { color: var(--ui-accent); }

/* T479: measured prototype tokens scoped to the page — the platform's solid panel/navy line
   values made cards and hairlines read as a different design system. */
#files-tab {
  --ui-panel: rgba(255, 255, 255, 0.027);
  --ui-panel-hover: rgba(255, 255, 255, 0.05);
  --ui-hover: rgba(255, 255, 255, 0.05);
  --ui-line: rgba(255, 255, 255, 0.075);
  --ui-line-soft: rgba(255, 255, 255, 0.05);
}
html[data-theme="light"] #files-tab {
  --ui-panel: #fcfbf7;
  --ui-panel-hover: #ffffff;
  --ui-hover: rgba(20, 32, 51, 0.045);
  --ui-line: rgba(20, 32, 51, 0.1);
  --ui-line-soft: rgba(20, 32, 51, 0.07);
}

/* Round-6 (operator: "internal jargon, annoying" — remove entirely): the T481 .fn-quiet
   footer ("Indexing workspace… · search ranks by strength, then recency · files are the
   system's truth") is gone. Its live count now lives in the header (.fn-index-status,
   above); the other two lines were static copy with no operator value. Removing it also
   closed the dead padding gap the left column had below it (see #files-tab's
   padding-bottom fix, top of this file) — the pane that used to stop short to make room
   for this footer's 42px margin-top now runs the full column height. */

/* Dock polish (doc 65, operator round): `/files/<path>?view=full` reading mode.
   The default Files experience is unchanged — this only applies when the route
   carried the param and bootstrap.js set .fn-full-view on the layout.
   Collapses the file-list track to zero exactly like the round-8/round-9
   collapse above (including the column-gap zeroing that removed the dead 22px
   grid gutter, same reasoning), and additionally drops the view tab strip and
   the drag divider so the document is the page. `:has()` is needed because
   .fn-page-tabs is a SIBLING of #fn-layout, not a descendant; it is already
   used by the shared form controls elsewhere in this codebase.
   The page head stays put so there is still a visible way back to Files. */
#files-tab:has(.fn-files-layout.fn-full-view) .fn-page-tabs {
  display: none;
}
#files-tab .fn-files-layout.fn-previewing.fn-full-view {
  grid-template-columns: 0 minmax(0, 1fr);
  column-gap: 0;
}
/* NOT `display:none`: a display:none child is removed from the grid entirely,
   so the preview column would slide back into the FIRST track — the 0px one —
   and render 2px wide (measured). The round-8 collapse above avoids this the
   same way, by zeroing the track and hiding the pane in place. */
#files-tab .fn-files-layout.fn-full-view .fn-files-side {
  opacity: 0;
  pointer-events: none;
}
#files-tab .fn-files-layout.fn-full-view #fn-divider {
  display: none;
}

/* ── Operator round (2026-09-04): first-class empty + loading states.
   Pinned empty: centered illustration + heading + guidance + primary action
   (panes.js pinnedEmptyHtml). All color through tokens, so the one markup
   reads correctly in dark and light without a second rule. */
#files-tab .fn-empty-state {
  display: grid; justify-items: center; text-align: center;
  gap: 5px; max-width: 430px; margin: 0 auto; padding: 58px 24px 40px;
}
#files-tab .fn-empty-illu-svg { display: block; width: 104px; height: auto; margin-bottom: 8px; }
#files-tab .fn-empty-illu-body {
  fill: color-mix(in srgb, var(--ui-accent) 8%, transparent);
  stroke: color-mix(in srgb, var(--ui-accent) 36%, transparent); stroke-width: 1.6;
}
#files-tab .fn-empty-illu-fold { stroke: color-mix(in srgb, var(--ui-accent) 46%, transparent); stroke-width: 1.6; stroke-linecap: round; }
#files-tab .fn-empty-illu-star { fill: var(--ui-warning); }
#files-tab .fn-empty-title { font-size: var(--text-md, 15px); font-weight: 650; color: var(--ui-text); }
#files-tab .fn-empty-copy { font-size: 13px; line-height: 1.55; color: var(--ui-muted); max-width: 36ch; }
#files-tab .fn-empty-action {
  margin-top: 10px; padding: 9px 18px; border-radius: 11px; cursor: pointer;
  border: 1px solid color-mix(in srgb, var(--ui-accent) 55%, transparent);
  background: color-mix(in srgb, var(--ui-accent) 16%, transparent);
  color: var(--ui-accent); font-family: var(--font-ui); font-size: 13px; font-weight: 600;
  transition: background var(--speed, 140ms) ease, border-color var(--speed, 140ms) ease;
}
#files-tab .fn-empty-action:hover {
  background: color-mix(in srgb, var(--ui-accent) 26%, transparent);
  border-color: color-mix(in srgb, var(--ui-accent) 75%, transparent);
}
#files-tab .fn-empty-action:active { transform: translateY(1px); }
#files-tab .fn-empty-action:focus-visible { outline: 2px solid var(--ui-accent); outline-offset: 2px; }

/* The bespoke .fn-skel* rules are gone — replaced by design-system/loading.css. */
#files-tab .fn-recent-loading .cc-skel-list,
#files-tab .fn-recent-warming .cc-skel-list { width: 100%; max-width: 560px; }
