/* Starlight Training Platform — one stylesheet, one system.

   The app is a console: dense tables, forms and queues that somebody reads all
   day. So the chrome is quiet and the content is not.

   Structure:
     1  Tokens          colour, type, space, elevation
     2  Base
     3  Shell           rail, topbar, content, views
     4  Cards
     5  Buttons
     6  Fields
     7  Tables
     8  Signals         pills, banners, stats, tips
     9  Documents       upload, review, chunks, retrieval
    10  Chat
    11  Admin           lifecycle, settings, profiles, keys
    12  Sign-in
    13  Utilities
    14  Responsive
*/

/* ==========================================================================
   1  TOKENS
   ========================================================================== */

:root {
  /* Warm neutrals rather than cool slate: they sit under the Starlight yellow
     without the two casts fighting each other. */
  --ink: #16150f;
  --ink-soft: #57543f;
  /* Dark enough to clear 4.5:1 on the page ground, not just on white. */
  --ink-faint: #6f6b57;
  --line: #e7e4d8;
  --line-strong: #d3cfbe;
  --surface: #ffffff;
  --surface-sunken: #faf8f1;
  --page: #f4f2e9;

  /* Interactive weight is carried by near-black, not by the brand colour.
     Yellow at text size fails contrast; as a fill behind dark text it is
     strong, so that is the only way it is used. */
  --accent: #1d1b12;
  --accent-tint: #fff8e0;

  --brand: #ffc800;
  --brand-strong: #e0ac00;
  --brand-ink: #1d1500;

  /* The navigation rail. The sign-in page already establishes the brand as
     charcoal with a yellow star; the app used to drop it at the door and go
     white-on-white. The rail carries it through, and buys the one thing a
     white sidebar cannot: chrome that is plainly not content. */
  --rail: #17160e;
  --rail-raised: #211f15;
  --rail-line: rgba(253, 251, 244, .09);
  --rail-text: rgba(253, 251, 244, .70);
  --rail-strong: #fdfbf4;
  --rail-faint: rgba(253, 251, 244, .40);

  --good: #15803d;
  --good-tint: #f0fdf4;
  --good-line: #bbf7d0;
  /* Orange, deliberately away from the brand yellow so a warning never reads
     as a highlight. */
  --warn: #c2410c;
  --warn-tint: #fff5ed;
  --warn-line: #fed7aa;
  --warn-ink: #7c2d12;
  --bad: #b91c1c;
  --bad-tint: #fef2f2;
  --bad-line: #fecaca;
  --bad-ink: #7f1d1d;

  /* Space. Every gap and pad in the file comes from here, so the app has one
     vertical rhythm instead of the four it grew. */
  --s1: 4px;
  --s2: 8px;
  --s3: 12px;
  --s4: 16px;
  --s5: 20px;
  --s6: 28px;
  --s7: 40px;

  --r-sm: 7px;
  --r: 10px;
  --r-lg: 13px;

  --shadow-1: 0 1px 2px rgba(22, 21, 15, .05), 0 1px 3px rgba(22, 21, 15, .04);
  --shadow-2: 0 2px 4px rgba(22, 21, 15, .05), 0 6px 16px rgba(22, 21, 15, .08);
  --shadow-3: 0 8px 28px rgba(22, 21, 15, .16);
  /* One ring, so focus looks the same on a table button and a sign-in field. */
  --ring: 0 0 0 3px rgba(224, 172, 0, .32);

  /* Segoe UI Variable is the Windows 11 interface face and is a real step up
     from Segoe UI at small sizes. No web font: this runs behind a corporate
     network where fonts.googleapis.com is not reachable. */
  --font: "Segoe UI Variable Text", "Segoe UI", system-ui, -apple-system,
    "Helvetica Neue", sans-serif;
  --font-display: "Segoe UI Variable Display", "Segoe UI Variable Text",
    "Segoe UI", system-ui, -apple-system, sans-serif;
  --font-mono: ui-monospace, "Cascadia Mono", "SFMono-Regular", Consolas, monospace;
}

/* ==========================================================================
   2  BASE
   ========================================================================== */

* { box-sizing: border-box; }

/* Author display rules below would otherwise beat the browser default for
   [hidden] and render empty banners and pills. */
[hidden] { display: none !important; }

.platform-update {
  position: fixed;
  z-index: 2000;
  right: var(--s4);
  bottom: var(--s4);
  left: var(--s4);
  width: fit-content;
  max-width: calc(100% - 32px);
  margin-left: auto;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s4);
  padding: var(--s4) var(--s5);
  border: 1px solid var(--warn-line);
  border-radius: var(--r);
  background: var(--warn-tint);
  color: var(--warn-ink);
  box-shadow: var(--shadow-3);
}

.platform-update p { margin: var(--s1) 0 0; }

html { height: 100%; }

body {
  margin: 0;
  font: 15px/1.5 var(--font);
  color: var(--ink);
  background: var(--page);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* A table of sizes and counts is read down, not across, and proportional
     digits make that impossible. */
  font-variant-numeric: tabular-nums;
}

h1, h2, h3 {
  margin: 0;
  font-family: var(--font-display);
  line-height: 1.22;
  letter-spacing: -.014em;
  font-variant-numeric: normal;
}

h1 { font-size: 1.3rem; font-weight: 680; }
h2 { font-size: 1.04rem; font-weight: 650; }
h3 { font-size: .93rem; font-weight: 640; }
p { margin: 0; }
svg { flex: none; }
code { font-family: var(--font-mono); }

::selection { background: var(--brand); color: var(--brand-ink); }

/* A default Windows scrollbar is a grey slab against warm paper. */
.content, .table-wrap, .queue-scroll, .chunk-list, .outcomes, .filelist,
.kb-checks, .sidebar-nav {
  scrollbar-width: thin;
  scrollbar-color: var(--line-strong) transparent;
}

.content::-webkit-scrollbar,
.table-wrap::-webkit-scrollbar,
.queue-scroll::-webkit-scrollbar { width: 10px; height: 10px; }

.content::-webkit-scrollbar-thumb,
.table-wrap::-webkit-scrollbar-thumb,
.queue-scroll::-webkit-scrollbar-thumb {
  border: 3px solid transparent;
  border-radius: 999px;
  background: var(--line-strong);
  background-clip: content-box;
}

.brand-mark {
  width: 34px;
  height: 34px;
  border-radius: 8px;
  object-fit: cover;
  transition: box-shadow .13s;
}

@keyframes spin { to { transform: rotate(360deg); } }
@keyframes pulse { 50% { opacity: .35; } }

/* ==========================================================================
   3  SHELL

   Three fixed regions and exactly one that scrolls:
     .sidebar   where you are and where else you can go
     .topbar    what you are looking at, and the actions that apply to all of it
     .content   the only thing that moves

   Long pages are cut into views, one visible at a time, so a page is a screen
   or two rather than a mile. The sidebar switches them.
   ========================================================================== */

body.app {
  height: 100%;
  /* The content region scrolls, not the document. Without this the whole shell
     scrolls away and fixed regions are pointless. */
  overflow: hidden;
}

.shell {
  display: grid;
  grid-template-columns: 258px 1fr;
  height: 100%;
}

/* --- Rail ---------------------------------------------------------------- */

.sidebar {
  display: grid;
  grid-template-rows: auto 1fr auto;
  min-height: 0;
  background: var(--rail);
  color: var(--rail-text);
  z-index: 1;
}

.sidebar-brand {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: 18px var(--s4);
  border-bottom: 1px solid var(--rail-line);
  color: var(--rail-strong);
  text-decoration: none;
}

/* The logo is the way home, so it has to look like it does something. */
.sidebar-brand:hover { background: var(--rail-raised); }
.sidebar-brand:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }

.sidebar-brand .brand-mark {
  width: 32px;
  height: 32px;
  /* The mark is yellow on white, so on the dark rail it needs a ring or it
     floats. */
  box-shadow: 0 0 0 2px rgba(255, 200, 0, .28);
}

.sidebar-brand:hover .brand-mark { box-shadow: 0 0 0 2px var(--brand); }
.sidebar-brand span { min-width: 0; display: grid; gap: 1px; }
.sidebar-brand strong { font-size: .94rem; font-weight: 640; letter-spacing: -.01em; }

.sidebar-brand small {
  color: var(--brand);
  font-size: .64rem;
  font-weight: 700;
  letter-spacing: .19em;
}

.sidebar-nav { min-height: 0; overflow-y: auto; padding: var(--s4) 10px var(--s5); }
.nav-group + .nav-group { margin-top: var(--s5); }

.nav-group-name {
  padding: 0 10px var(--s2);
  color: var(--rail-faint);
  font-size: .65rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .14em;
}

.nav-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 9px 10px;
  margin-bottom: 2px;
  border: 0;
  border-radius: var(--r-sm);
  background: none;
  color: var(--rail-text);
  font: inherit;
  font-size: .875rem;
  font-weight: 550;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition: background .12s, color .12s;
}

.nav-item:hover { background: var(--rail-raised); color: var(--rail-strong); }
.nav-item:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.nav-item svg { flex: none; color: var(--rail-faint); transition: color .12s; }
.nav-item:hover svg { color: var(--rail-text); }

/* The label takes what is left and the count keeps its own width, or the count
   wraps onto a second line and the item is suddenly two rows tall. */
.nav-item > span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The current view is filled rather than underlined: in a sidebar the eye needs
   a block to land on. On the dark rail the brand can finally be the marker at
   full strength, which it never could on white. */
.nav-item.is-on {
  background: rgba(255, 200, 0, .13);
  color: var(--brand);
  font-weight: 620;
  box-shadow: inset 2px 0 0 var(--brand);
}

.nav-item.is-on svg { color: var(--brand); }

/* A count on a nav item, so a problem is visible from anywhere in the app
   rather than only once you have navigated to the page that lists it. */
.nav-count {
  flex: none;
  margin-left: var(--s1);
  padding: 1px 7px;
  border-radius: 999px;
  background: rgba(253, 251, 244, .10);
  color: var(--rail-text);
  font-size: .72rem;
  font-weight: 650;
}

.nav-item.is-on .nav-count { background: rgba(255, 200, 0, .20); color: var(--brand); }
.nav-count.is-bad { background: rgba(248, 113, 113, .18); color: #fca5a5; }
.nav-count.is-warn { background: rgba(251, 146, 60, .18); color: #fdba74; }

/* The foot is three unrelated things — a job, an identity and a way out —
   ordered by how often each is wanted. */

.sidebar-foot {
  display: grid;
  gap: var(--s2);
  padding: var(--s3) 10px;
  border-top: 1px solid var(--rail-line);
  background: rgba(0, 0, 0, .18);
}

.shell-sync {
  justify-content: flex-start;
  gap: 9px;
  padding: 9px 11px;
  border-color: var(--rail-line);
  background: rgba(253, 251, 244, .06);
  color: var(--rail-text);
  font-size: .85rem;
}

.shell-sync:hover {
  background: rgba(253, 251, 244, .12);
  border-color: rgba(255, 200, 0, .4);
  color: var(--rail-strong);
}

.shell-sync svg { color: var(--brand); }
.shell-sync[aria-busy="true"] svg { animation: spin 1s linear infinite; }

.shell-sync-status {
  min-height: 0;
  padding: 0 3px;
  color: var(--rail-faint);
  font-size: .72rem;
  line-height: 1.4;
}

.shell-sync-status:not(:empty) { padding-bottom: 2px; }

.profile-link {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 9px;
  border: 1px solid var(--rail-line);
  border-radius: var(--r-sm);
  color: var(--rail-strong);
  text-decoration: none;
  transition: background .12s, border-color .12s;
}

.profile-link:hover { background: rgba(253, 251, 244, .08); border-color: rgba(253, 251, 244, .2); }
.profile-link:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
.profile-link > svg { color: var(--rail-faint); }
.profile-link .sidebar-who { flex: 1; padding: 0; }

.sidebar-who { display: grid; gap: 1px; min-width: 0; padding: 0 var(--s1); }
.sidebar-who strong { font-size: .84rem; font-weight: 600; overflow-wrap: anywhere; }
.sidebar-who small { color: var(--rail-faint); font-size: .72rem; }

/* Initials rather than a photo: there is no avatar to fetch, and a coloured
   disc still gives the block something to be anchored on. */
.who-avatar {
  display: grid;
  flex: none;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--brand);
  color: var(--brand-ink);
  font-size: .78rem;
  font-weight: 700;
  text-transform: uppercase;
}

.sidebar-foot form .btn { color: var(--rail-faint); font-size: .84rem; }
.sidebar-foot form .btn:hover { background: rgba(253, 251, 244, .08); color: var(--rail-strong); }

/* --- Workspace ----------------------------------------------------------- */

.workspace { display: flex; flex-direction: column; min-width: 0; min-height: 0; }

/* Everything above the scrolling region keeps its own height, whatever there is
   of it. Only .content grows. */
.topbar, .scope-bar { flex: none; }

.topbar {
  display: flex;
  align-items: center;
  gap: var(--s3) var(--s4);
  flex-wrap: wrap;
  padding: var(--s4) var(--s6);
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  z-index: 1;
}

.topbar-title { min-width: 0; display: grid; gap: 2px; }
.topbar-title h1 { font-size: 1.3rem; }
.topbar-title p { color: var(--ink-soft); font-size: .84rem; max-width: 72ch; }
.topbar-actions { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; margin-left: auto; }

.content {
  /* The only region that grows, and the only one that scrolls. */
  flex: 1;
  min-width: 0;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.page {
  display: grid;
  gap: var(--s5);
  align-content: start;
  max-width: 1240px;
  margin: 0 auto;
  padding: var(--s6) var(--s6) 72px;
}

.page-narrow { max-width: 640px; }
.page-wide { max-width: 1560px; }
.content > .page { padding-top: var(--s5); }

/* A view is a group of cards, spaced like the page it replaced. */
.view { display: grid; gap: var(--s5); align-content: start; }
.view[hidden] { display: none !important; }

/* The scope bar: one control that applies to every view, so it lives outside
   the scrolling region. A global filter you have to scroll to find is one
   people forget is switched on. */
.scope-bar {
  display: flex;
  align-items: flex-end;
  gap: var(--s3);
  flex-wrap: wrap;
  padding: var(--s3) var(--s6);
  background: var(--surface-sunken);
  border-bottom: 1px solid var(--line);
}

.scope-bar .filters { gap: 10px; }
.scope-bar .field { min-width: 120px; }
.scope-bar .field > span { font-size: .7rem; }
.scope-bar .input, .scope-bar .select { padding: 6px 9px; font-size: .84rem; }

/* Anything scoped shows it, so a filtered page never looks like the whole
   platform. */
.scope-note { margin-left: auto; color: var(--ink-soft); font-size: .82rem; }
.scope-note b { color: var(--ink); font-weight: 600; }

/* The drawer toggle only exists on narrow screens. */
.sidebar-toggle { display: none; }

/* ==========================================================================
   4  CARDS

   One gap between cards, one padding inside them. Four values were in use.
   ========================================================================== */

.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-1);
}

.card-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px 18px;
  flex-wrap: wrap;
  padding: var(--s4) var(--s5) 15px;
  border-bottom: 1px solid var(--line);
}

.card-head.plain { border-bottom: 0; }
.card-head .head-actions { margin-left: auto; }
.card-title { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.card-body { padding: 18px var(--s5) var(--s5); }
.sub { margin-top: 3px; max-width: 78ch; color: var(--ink-soft); font-size: .85rem; line-height: 1.5; }
.head-actions { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }

/* Two lists in one card — annotations above, rejected below — need a line
   between them, or the second heading reads as a caption for the first table. */
.card > .table-wrap + .card-head,
.card > .card-body + .card-head { border-top: 1px solid var(--line); }

/* A card whose last element is a table or a stats band closes on the card's own
   rounded corner, not on that element's stray bottom hairline. */
.card > .table-wrap:last-child { border-radius: 0 0 var(--r-lg) var(--r-lg); }
.card > .table-wrap:last-child table tbody tr:last-child td { border-bottom: 0; }

.card > .stat-grid:last-child {
  border-bottom: 0;
  border-radius: 0 0 var(--r-lg) var(--r-lg);
  overflow: hidden;
}

/* Two panels side by side, collapsing rather than shrinking. */
.split { display: grid; gap: var(--s5); grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); align-items: start; }

.empty { padding: 46px var(--s5); color: var(--ink-faint); text-align: center; font-size: .87rem; }

/* ==========================================================================
   5  BUTTONS

   One hierarchy across the app. Brand yellow is the page's single loudest
   thing, so only the one action a screen exists for may wear it. Dark ink is
   the second voice, for a confirm that is not the page's purpose. Everything
   else is a ghost.
   ========================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 8px 15px;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  font: inherit;
  font-weight: 550;
  line-height: 1.2;
  white-space: nowrap;
  cursor: pointer;
  transition: background .13s, border-color .13s, box-shadow .13s, transform .13s, color .13s;
}

.btn:active:not([disabled]) { transform: translateY(.5px); }

/* The brand colour always sits behind dark text, where its contrast is strong. */
.btn-primary {
  background: var(--brand);
  border-color: var(--brand-strong);
  color: var(--brand-ink);
  box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .14), 0 1px 2px rgba(22, 21, 15, .10);
}

.btn-primary:hover { background: var(--brand-strong); box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .16), 0 2px 8px rgba(224, 172, 0, .30); }

.btn-ghost {
  background: var(--surface);
  border-color: var(--line-strong);
  color: var(--ink);
  box-shadow: 0 1px 1px rgba(22, 21, 15, .03);
}

.btn-ghost:hover { background: var(--surface-sunken); border-color: var(--ink-faint); }

/* The second voice: a real action that is not the reason the page exists. */
.btn-dark { background: var(--accent); border-color: var(--accent); color: #fdfbf4; }
.btn-dark:hover { background: #000; border-color: #000; }

/* A ghost that should not read as a button at all — sign out, cancel. */
.btn-quiet { background: none; border-color: transparent; color: var(--ink-soft); }
.btn-quiet:hover { background: var(--surface-sunken); color: var(--ink); }

.btn-danger { background: var(--bad); border-color: var(--bad); color: #fff; }
.btn-danger:hover:not(:disabled) { background: #991b1b; border-color: #991b1b; }

.btn-block { width: 100%; justify-content: center; padding: 10px 14px; }

/* Compact buttons for row-level actions, where a full .btn would set the row
   height for every row that has no action in it. */
.btn-sm { padding: 5px 10px; font-size: .82rem; }
.btn-sm .spinner { width: 12px; height: 12px; }
.btn-square { padding: 6px; }
.btn-square[aria-pressed="true"] { color: var(--brand-ink); border-color: var(--brand-strong); background: var(--accent-tint); }

.btn:focus-visible,
.input:focus-visible,
.select:focus-visible,
textarea.input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.btn[disabled] { opacity: .55; box-shadow: none; }
.btn[disabled]:not([aria-busy="true"]) { cursor: not-allowed; }
.btn[aria-busy="true"] { cursor: progress; }

.btn .spinner { display: none; }
.btn[aria-busy="true"] .spinner { display: block; }
.btn[aria-busy="true"] .btn-icon { display: none; }

.spinner {
  width: 14px;
  height: 14px;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: spin .6s linear infinite;
}

.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--ink-faint);
  cursor: pointer;
}

.icon-btn:hover { border-color: var(--line-strong); background: var(--surface); color: var(--bad); }
.icon-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* A row you can open. Used on the knowledge-base document list, so a manager
   can see the passages a document became without leaving the page. */
.expand-btn {
  padding: 3px 8px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--ink-soft);
  font: inherit;
  font-size: .78rem;
  font-weight: 550;
  white-space: nowrap;
  cursor: pointer;
}

.expand-btn:hover { background: var(--surface-sunken); color: var(--ink); }
.expand-btn[aria-expanded="true"] { border-color: var(--brand-strong); background: var(--accent-tint); color: var(--brand-ink); }
.expand-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.toggle {
  display: inline-flex;
  padding: 2px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  background: var(--surface-sunken);
}

.toggle button {
  padding: 4px 11px;
  border: 0;
  border-radius: 5px;
  background: transparent;
  color: var(--ink-soft);
  font: inherit;
  font-size: .8rem;
  font-weight: 550;
  cursor: pointer;
}

.toggle button.is-on { background: var(--surface); color: var(--ink); box-shadow: 0 1px 2px rgba(22, 21, 15, .08); }
.toggle button:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* ==========================================================================
   6  FIELDS

   A native select on Windows draws a grey system arrow that ignores the rest of
   the page. Replacing it is the difference between a form that was styled and a
   form that was left alone.
   ========================================================================== */

.stack { display: grid; gap: var(--s3); }
.field { display: grid; gap: 5px; }

.field > span {
  color: var(--ink-soft);
  font-size: .74rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .05em;
}

.input, .select, textarea.input {
  width: 100%;
  padding: 8px 11px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  font: inherit;
  color: var(--ink);
  background-color: var(--surface);
  transition: border-color .12s, box-shadow .12s;
}

.input::placeholder { color: var(--ink-faint); }
.input:hover:not(:disabled), .select:hover:not(:disabled) { border-color: var(--ink-faint); }

.input:focus, .select:focus, textarea.input:focus {
  outline: none;
  border-color: var(--brand-strong);
  box-shadow: var(--ring);
}

.select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 30px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2357543f' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 9px center;
}

/* The dense variants set padding wholesale and would put the arrow under the
   longest option's last letter. */
.scope-bar .select, .tag-grid .select, .tags-panel .select { padding-right: 27px; background-position: right 7px center; }

.filters { display: flex; flex-wrap: wrap; gap: var(--s3); align-items: end; }
.filters .field { min-width: 132px; }

/* A form of eight fields laid out by flex-wrap ends ragged: three on one row,
   one orphan on the next, each a different width. A grid gives every field the
   same width and every row the same height. */
.form-grid {
  display: grid;
  gap: 13px 14px;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  align-items: start;
}

.form-grid > .field-wide { grid-column: 1 / -1; }

.check {
  display: flex;
  align-items: flex-start;
  gap: var(--s2);
  color: var(--ink-soft);
  font-size: .87rem;
  line-height: 1.45;
  cursor: pointer;
}

.check input { width: 15px; height: 15px; margin-top: 1px; flex: none; accent-color: var(--brand-strong); }

/* Search with magnifier indicator */
.search { position: relative; display: flex; align-items: center; }
.search svg { position: absolute; left: 10px; color: var(--ink-faint); pointer-events: none; }
.search .input { padding-left: 32px; }
.search-count { margin-left: 10px; color: var(--ink-faint); font-size: .8rem; white-space: nowrap; }

/* The audit log is the one page where the filters are the point, so they are a
   panel of their own rather than a tinted strip that reads as page furniture. */
.audit-filters {
  display: flex;
  align-items: end;
  gap: 12px 16px;
  flex-wrap: wrap;
  padding: 15px 18px;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--shadow-1);
}

.audit-filters .filters { flex: 1; gap: var(--s3); align-items: end; }
.audit-filters .field { min-width: 148px; }
.audit-filters .field > span { font-size: .71rem; }
.audit-filters .input, .audit-filters .select { padding: 8px 10px; font-size: .86rem; }
.audit-filters .scope-note { align-self: center; }

/* Grants, on the admin user form */
.kb-checks {
  display: grid;
  gap: 7px;
  max-height: 210px;
  overflow: auto;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface-sunken);
}

.req { margin-left: 3px; color: var(--warn); font-style: normal; }

/* ==========================================================================
   7  TABLES

   A table read all day is read in the gutters, so the breathing room matters
   more than the borders.
   ========================================================================== */

.table-wrap { overflow: auto; }
.table-wrap.scroll { max-height: 440px; }

table { width: 100%; border-collapse: separate; border-spacing: 0; }
.table-wrap table { font-size: .875rem; }

th {
  position: sticky;
  top: 0;
  z-index: 1;
  padding: 10px 14px;
  text-align: left;
  white-space: nowrap;
  color: var(--ink-soft);
  font-size: .72rem;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: .07em;
  background: var(--surface-sunken);
  /* An inset line rather than a border: a sticky header keeps its edge while
     the rows scroll under it, which a collapsed border does not. */
  border-bottom: 0;
  box-shadow: inset 0 -1px 0 var(--line-strong);
}

td { padding: 11px 14px; border-bottom: 1px solid var(--line); vertical-align: middle; }
tbody tr:last-child td { border-bottom: 0; }
tbody tr td { transition: background .1s; }
tbody tr:hover td { background: var(--surface-sunken); }

.table-wrap th:first-child, .table-wrap td:first-child { padding-left: var(--s5); }
.table-wrap th:last-child, .table-wrap td:last-child { padding-right: var(--s5); }

.num { white-space: nowrap; }
.name { font-weight: 580; }
/* Second line under a filename: what was actually sent, when the two differ. */
.cell-note { display: block; margin-top: 2px; color: var(--ink-faint); font-size: .78rem; font-weight: 400; line-height: 1.4; }
.truncate { max-width: 300px; overflow-wrap: anywhere; }
.mono { font-family: var(--font-mono); font-size: .8rem; color: var(--ink-soft); }
.muted { color: var(--ink-soft); }

/* Row actions only earn their column when the pointer is on the row. */
.row-actions { display: flex; gap: 6px; align-items: center; justify-content: flex-end; white-space: nowrap; }

/* A path in a table cell: never wrapped mid-word into an unreadable column, and
   never allowed to set the column width either. */
.cell-path {
  display: block;
  margin-top: 2px;
  max-width: 340px;
  color: var(--ink-faint);
  font-family: var(--font-mono);
  font-size: .75rem;
  font-weight: 400;
  overflow-wrap: anywhere;
}

/* An empty pager still drew a 48px strip and a hairline under every table that
   fitted on one page — a footer for nothing, on six views. */
.pagination:empty { display: none; }

.pagination {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px var(--s5);
  border-top: 1px solid var(--line);
  border-radius: 0 0 var(--r-lg) var(--r-lg);
  background: var(--surface-sunken);
  color: var(--ink-soft);
  font-size: .81rem;
}

.pagination span { color: var(--ink-soft); }
.pagination .btn { min-width: 84px; justify-content: center; }

tr.detail-row > td { background: var(--surface-sunken); padding: 12px 14px; }
tr.detail-row .chunk-list { max-height: 340px; }
tr.is-muted td { color: var(--ink-faint); }
.edit-row > td { background: var(--surface-sunken); }

/* ==========================================================================
   8  SIGNALS — pills, banners, numbers, tips
   ========================================================================== */

.pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 2px 9px 2px 7px;
  border: 1px solid transparent;
  border-radius: 999px;
  font-size: .78rem;
  font-weight: 600;
  white-space: nowrap;
}

.pill .dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.pill-good { color: var(--good); background: var(--good-tint); border-color: var(--good-line); }
.pill-warn { color: var(--warn); background: var(--warn-tint); border-color: var(--warn-line); }
.pill-bad { color: var(--bad); background: var(--bad-tint); border-color: var(--bad-line); }
.pill-info { color: #6b5200; background: var(--accent-tint); border-color: #ffe07a; }
.pill-mute { color: var(--ink-soft); background: var(--surface-sunken); border-color: var(--line); }
.pill-warn .dot { animation: pulse 1.6s ease-in-out infinite; }

.banner {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  padding: 11px 13px;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  font-size: .88rem;
}

.banner-good { color: #14532d; background: var(--good-tint); border-color: var(--good-line); }
.banner-bad { color: var(--bad-ink); background: var(--bad-tint); border-color: var(--bad-line); }
.banner-warn { color: var(--warn-ink); background: var(--warn-tint); border-color: var(--warn-line); }
.banner-info { color: #5c4700; background: var(--accent-tint); border-color: #ffe07a; }
/* A banner already inside a padded container needs no margin of its own. */
.banner-flush { margin: 0; }

/* Auto-refresh indicator: a live page has to say it is live, or the numbers
   look stale and someone reloads on top of a running job. */
.live { display: inline-flex; align-items: center; gap: 6px; color: var(--ink-faint); font-size: .78rem; }
.live .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--good); animation: pulse 1.8s ease-in-out infinite; }

/* The overview band. Bigger numbers than a .stat, because these are the facts
   someone opens the page to check, and everything below them is the detail
   behind one of them. */
.hero {
  display: grid;
  gap: 1px;
  grid-template-columns: repeat(auto-fit, minmax(176px, 1fr));
  background: var(--line);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  overflow: hidden;
  box-shadow: var(--shadow-1);
}

.hero-cell { display: grid; gap: 3px; align-content: start; padding: 17px var(--s5) 18px; background: var(--surface); }
.hero-cell b { font-size: 1.9rem; font-weight: 650; line-height: 1; letter-spacing: -.03em; font-variant-numeric: normal; }

.hero-cell > span {
  color: var(--ink-faint);
  font-size: .74rem;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: .07em;
}

.hero-cell small { color: var(--ink-faint); font-size: .78rem; line-height: 1.4; }
.hero-cell.good b { color: var(--good); }
.hero-cell.warn b { color: var(--warn); }
.hero-cell.bad b { color: var(--bad); }
.hero-cell.is-zero b { color: var(--ink-faint); }

.stat-grid {
  display: grid;
  gap: 1px;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  background: var(--line);
  border-bottom: 1px solid var(--line);
}

/* The same band used on its own — the chat-logs page opens with it — has to
   supply the frame the card would have given it. */
.page > .stat-grid,
.view > .stat-grid {
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  overflow: hidden;
  box-shadow: var(--shadow-1);
}

.stat { padding: 15px var(--s5) var(--s4); background: var(--surface); }
.stat b { display: block; font-size: 1.6rem; font-weight: 660; line-height: 1.1; letter-spacing: -.025em; font-variant-numeric: normal; }
.stat span { display: flex; align-items: center; gap: 5px; margin-top: 3px; color: var(--ink-soft); font-size: .78rem; }
.stat.good b { color: var(--good); }
.stat.warn b { color: var(--warn); }
.stat.bad b { color: var(--bad); }
.stat.is-zero b { color: var(--ink-faint); }

.progress-track { height: 5px; background: var(--line); border-radius: 999px; overflow: hidden; }

.progress-fill {
  height: 100%;
  background: linear-gradient(90deg, var(--brand-strong), var(--brand));
  border-radius: 999px;
  transition: width .2s;
}

/* Hover tooltip indicator */
.tip { position: relative; display: inline-flex; color: var(--ink-faint); cursor: help; }

.tip::after {
  content: attr(data-tip);
  position: absolute;
  bottom: calc(100% + 7px);
  left: 50%;
  z-index: 30;
  width: max-content;
  max-width: 250px;
  padding: 7px 9px;
  transform: translateX(-50%) scale(.96);
  border-radius: var(--r-sm);
  background: var(--ink);
  color: #fff;
  font-size: .78rem;
  font-weight: 400;
  line-height: 1.35;
  text-transform: none;
  letter-spacing: 0;
  box-shadow: var(--shadow-3);
  opacity: 0;
  pointer-events: none;
  transition: opacity .12s, transform .12s;
}

.tip:hover::after, .tip:focus-visible::after { opacity: 1; transform: translateX(-50%) scale(1); }

.kbd {
  padding: 1px 5px;
  border: 1px solid var(--line-strong);
  border-bottom-width: 2px;
  border-radius: 4px;
  background: var(--surface);
  font-family: var(--font-mono);
  font-size: .74rem;
}

/* ==========================================================================
   9  DOCUMENTS — upload, review, chunks, retrieval
   ========================================================================== */

/* --- Knowledge-base folders ---------------------------------------------- */

/* Dify calls them datasets and shows them as cards; the same shape here means a
   manager who has seen Dify recognises the page. */
.kb-grid { display: grid; gap: 14px; grid-template-columns: repeat(auto-fill, minmax(268px, 1fr)); }

.kb-card {
  display: grid;
  gap: 10px;
  align-content: start;
  padding: 17px 18px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-1);
  color: inherit;
  text-decoration: none;
  transition: box-shadow .13s, border-color .13s, transform .13s;
}

a.kb-card:hover { border-color: var(--brand-strong); box-shadow: var(--shadow-2); transform: translateY(-2px); }
a.kb-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.kb-card-top { display: flex; align-items: flex-start; gap: 11px; }
.kb-card-top > span { min-width: 0; display: grid; gap: 2px; }
.kb-card strong { font-size: 1rem; font-weight: 640; overflow-wrap: anywhere; }
.kb-card small { color: var(--ink-faint); font-size: .78rem; }
.kb-card .pill { align-self: start; }

/* The folder glyph on a tinted tile: at 30px on white it read as a stray line
   drawing rather than as the card's subject. */
.kb-card-top .folder {
  width: 36px;
  height: 36px;
  padding: 6px;
  border-radius: 9px;
  background: var(--accent-tint);
  color: var(--brand-strong);
}

.kb-desc {
  color: var(--ink-soft);
  font-size: .85rem;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.kb-facts {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 14px;
  padding-top: 10px;
  border-top: 1px solid var(--line);
  color: var(--ink-soft);
  font-size: .8rem;
}

.kb-facts b { color: var(--ink); font-weight: 620; }

/* The knowledge base description, editable in place by the manager who owns it.
   Dify reads it when choosing which knowledge base answers a question, so it
   sits with the summary rather than buried in settings. */
.kb-description { display: grid; gap: var(--s2); justify-items: start; }
.kb-description form { display: grid; gap: var(--s2); width: 100%; }
.kb-description .row-actions { justify-content: flex-start; align-items: center; white-space: normal; }

.kb-description textarea {
  width: 100%;
  min-width: min(48rem, 100%);
  padding: 10px 12px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: inherit;
  font: inherit;
  resize: vertical;
}

.kb-description textarea:focus { outline: none; border-color: var(--brand-strong); box-shadow: var(--ring); }

/* Choosing which knowledge base a file goes to. Radio cards rather than a
   dropdown: the destination is the one decision on this page that cannot be
   undone from here, so it is not allowed to hide inside a closed control. */
.kb-picker { display: grid; gap: var(--s2); }

.kb-choice {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  padding: 10px 12px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  background: var(--surface);
  cursor: pointer;
  transition: border-color .13s, background .13s;
}

.kb-choice:hover { background: var(--surface-sunken); }
.kb-choice:has(input:checked) { border-color: var(--brand-strong); background: var(--accent-tint); }
.kb-choice:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 1px; }
.kb-choice input { margin-top: 3px; accent-color: var(--brand-strong); }
.kb-choice > span { min-width: 0; display: grid; gap: 2px; }
.kb-choice strong { font-weight: 600; overflow-wrap: anywhere; }
.kb-choice small { color: var(--ink-soft); font-size: .8rem; }

/* --- Picking files ------------------------------------------------------- */

.filebox {
  display: grid;
  gap: var(--s2);
  place-items: center;
  padding: 26px 18px;
  text-align: center;
  border: 1.5px dashed var(--line-strong);
  border-radius: var(--r);
  background: var(--surface-sunken);
  cursor: pointer;
  transition: border-color .13s, background .13s;
}

.filebox:hover, .filebox.is-active { border-color: var(--brand-strong); background: var(--accent-tint); }
.filebox input[type=file] { position: absolute; width: 1px; height: 1px; opacity: 0; }
.filebox strong { font-weight: 600; }
.filebox span { color: var(--ink-soft); font-size: .85rem; }
.filebox svg { color: var(--brand-strong); }

/* A file Dify will not take: warn while it can still be converted, refuse when
   it cannot. Both states own the border so the picker itself reads as the
   problem, rather than a notice sitting somewhere below it. */
.filebox.is-warn { border-color: var(--warn); background: var(--warn-tint); }
.filebox.is-warn svg { color: var(--warn); }
.filebox.is-bad { border-color: var(--bad); background: var(--bad-tint); }
.filebox.is-bad svg { color: var(--bad); }

.filebox-total { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; color: var(--ink-soft); font-size: .84rem; }
.filebox-total .grow { flex: 1; }

/* The second way in: a directory picker beside the file picker. A browser only
   reports a path when a folder was chosen, so this cannot be hidden away. */
.picker-alt { display: flex; align-items: center; gap: 11px; flex-wrap: wrap; }
.picker-alt > label { position: relative; flex: none; }
.picker-alt .muted { flex: 1; min-width: 200px; font-size: .81rem; }

/* A chosen-file list rather than a bare "3 files selected". A manager handing
   over a folder has to see which file is the one Dify will refuse, and take it
   out without starting the selection again. */
.filelist {
  display: grid;
  gap: 1px;
  max-height: 300px;
  overflow: auto;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--line);
}

.filerow { display: flex; align-items: center; gap: 10px; padding: var(--s2) 10px; background: var(--surface); font-size: .87rem; }
.filerow.is-warn { background: var(--warn-tint); }
.filerow.is-bad { background: var(--bad-tint); }
.filerow.is-good { background: var(--good-tint); }
.filerow > span:first-of-type { flex: 1; min-width: 0; display: grid; gap: 2px; }
.filerow strong { font-weight: 550; overflow-wrap: anywhere; }
.filerow small { color: var(--ink-soft); font-size: .79rem; overflow-wrap: anywhere; }
.filerow .pill { flex: none; }

/* Where a file came from — monospace, because a path is read character by
   character, not as a phrase. */
.filerow-path,
.outcomes em.filerow-path {
  display: block;
  color: var(--ink-faint);
  font-family: var(--font-mono);
  font-size: .76rem;
  overflow-wrap: anywhere;
}

.filerow.is-bad .filerow-path { color: #8a3a3a; }
.filerow.is-warn .filerow-path { color: #8a4b26; }

.convert {
  display: grid;
  gap: 9px;
  padding: 12px 14px;
  border: 1px solid var(--warn-line);
  border-left: 3px solid var(--warn);
  border-radius: var(--r-sm);
  background: var(--warn-tint);
  font-size: .87rem;
  color: var(--warn-ink);
}

.convert strong { font-weight: 600; }
.convert .check { color: var(--warn-ink); font-weight: 550; }
.convert-drops { color: #8a4b26; font-size: .83rem; }

/* --- Destructive confirmations ------------------------------------------- */

/* The one action that deletes from Dify. Deliberately loud: it is the only
   irreversible thing this platform can do. */
.danger {
  display: grid;
  gap: 10px;
  padding: 14px var(--s4);
  border: 1px solid var(--bad-line);
  border-left: 3px solid var(--bad);
  border-radius: var(--r-sm);
  background: var(--bad-tint);
  color: var(--bad-ink);
  font-size: .88rem;
}

.danger ul { margin: 0; padding-left: 18px; display: grid; gap: 4px; }
/* Row-level destructive actions that do not deserve a whole red panel but must
   not be one unguarded click either. */
.confirm-inline {
  display: flex;
  align-items: center;
  gap: var(--s2);
  flex-wrap: wrap;
  padding: 9px 11px;
  border: 1px solid var(--bad-line);
  border-left: 3px solid var(--bad);
  border-radius: var(--r-sm);
  background: var(--bad-tint);
  color: var(--bad-ink);
  font-size: .85rem;
}

.confirm-inline .grow { flex: 1; min-width: 160px; }

/* One row per document, tone by outcome, scrollable because a full run is
   dozens of lines and must not push the page down. */
.outcomes {
  display: grid;
  gap: 1px;
  max-height: 320px;
  overflow: auto;
  margin: 0;
  padding: 0;
  list-style: none;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--line);
}

.outcomes li { display: flex; align-items: baseline; gap: 10px; padding: var(--s2) 11px; background: var(--surface); font-size: .85rem; }
.outcomes li > .pill { flex: none; }
.outcomes li > span { min-width: 0; overflow-wrap: anywhere; }
.outcomes li b { font-weight: 600; }
.outcomes li em { display: block; color: var(--ink-soft); font-style: normal; font-size: .82rem; }

/* --- Review -------------------------------------------------------------- */

.review { display: grid; grid-template-columns: minmax(300px, 400px) 1fr; gap: 18px; align-items: start; }

/* Reading the original: the queue is dead weight next to a page of A4. */
.review.is-wide { grid-template-columns: 1fr; }
.review.is-wide .queue { display: none; }

.queue { position: sticky; top: 76px; }
.queue-scroll { overflow: auto; max-height: calc(100vh - 260px); }

.queue-item {
  display: grid;
  gap: 4px;
  width: 100%;
  padding: 11px 14px;
  text-align: left;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--line);
  border-left: 3px solid transparent;
  font: inherit;
  cursor: pointer;
}

.queue-item:hover { background: var(--surface-sunken); }
.queue-item.is-active { background: var(--accent-tint); border-left-color: var(--brand); }
.queue-item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.queue-item strong { font-size: .87rem; font-weight: 600; overflow-wrap: anywhere; }
.queue-item .queue-meta { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.queue-item .queue-meta small { color: var(--ink-soft); font-size: .76rem; }
.queue-item.is-decided strong { color: var(--ink-soft); }

.queue-item .queue-verdict {
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--brand);
  color: var(--brand-ink);
  font-size: .7rem;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: .03em;
}

/* Verdict buttons, notes and metadata are one decision, so they read as one
   panel instead of three stacked bands in three different shades. */
.verdicts {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  align-items: center;
  padding: 13px 18px 11px;
  border-top: 1px solid var(--line);
  background: var(--surface-sunken);
}

.verdict {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  background: var(--surface);
  font: inherit;
  font-size: .84rem;
  font-weight: 550;
  cursor: pointer;
  transition: background .13s, border-color .13s, color .13s;
}

.verdict:hover { border-color: var(--brand-strong); background: var(--accent-tint); }
.verdict.is-set { color: var(--brand-ink); background: var(--brand); border-color: var(--brand-strong); }
.verdict.is-set .kbd { color: var(--brand-ink); background: rgba(255, 255, 255, .55); }
.verdict:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* Its own row: sharing one with six verdict buttons squeezed both. */
.verdict-note { display: flex; gap: 7px; flex-basis: 100%; }
.verdict-note .input { font-size: .85rem; }

/* Governing metadata */
.tags { padding: var(--s1) 18px 13px; background: var(--surface-sunken); border-bottom: 1px solid var(--line); }
.tags .tags-head { margin-bottom: var(--s2); }

/* Shared by the review page's metadata panel and the upload tag panel. */
.tags-head { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.tags-head > span:last-child { margin-left: auto; }
.tags-head .grow { flex: 1; }

.tags-head > span:first-child {
  color: var(--ink-soft);
  font-size: .78rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .03em;
}

.tag-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(148px, 1fr)); gap: 9px; }
.tag-grid .input, .tag-grid .select { padding: 6px 9px; font-size: .84rem; }
.tag-grid .field > span { font-size: .72rem; }
.tag-grid .field.is-missing .input,
.tag-grid .field.is-missing .select { border-color: var(--warn); background: var(--warn-tint); }

/* Tagging at upload. Required facets always visible; the rest fold away,
   because a form of thirteen fields is a form nobody fills in. */
.tags-panel { display: grid; gap: 11px; }
.tags-panel .tag-grid { grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); }
.tags-panel .field > span { font-size: .73rem; }
.tags-panel .input, .tags-panel .select { padding: 6px 9px; font-size: .84rem; }

#tags-summary { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
#tags-summary .muted { font-size: .82rem; }

.tag-facet { display: grid; gap: 6px; }

.tag-facet-name {
  color: var(--ink-faint);
  font-size: .72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .09em;
}

.tag-optional { display: grid; gap: 11px; padding-top: var(--s1); }

.tag-chip {
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
  padding: 2px var(--s2);
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  font-size: .75rem;
  color: var(--ink-soft);
}

.tag-chip b { color: var(--ink-faint); font-weight: 600; font-size: .68rem; text-transform: uppercase; letter-spacing: .04em; }

/* --- Reading the original ------------------------------------------------ */

.file-frame,
.doc-render {
  width: 100%;
  height: calc(100vh - 210px);
  min-height: 560px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface-sunken);
}

.doc-render { overflow: auto; }
.doc-render .docx-wrapper { padding: 18px; background: transparent; }
.doc-render .docx { box-shadow: var(--shadow-2); }

.sheet-tabs {
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  gap: 4px;
  overflow-x: auto;
  padding: 7px var(--s2);
  border-bottom: 1px solid var(--line);
  background: var(--surface);
}

.sheet-tabs button {
  padding: 4px 10px;
  border: 1px solid transparent;
  border-radius: 6px;
  background: transparent;
  color: var(--ink-soft);
  font: inherit;
  font-size: .8rem;
  font-weight: 550;
  white-space: nowrap;
  cursor: pointer;
}

.sheet-tabs button.is-on { border-color: var(--line-strong); background: var(--surface-sunken); color: var(--ink); }

.sheet { padding: var(--s3); overflow-x: auto; }
.sheet table { border-collapse: collapse; font-size: .82rem; background: var(--surface); }
.sheet td, .sheet th { padding: 4px var(--s2); border: 1px solid var(--line); white-space: nowrap; }

/* --- Chunks and passages ------------------------------------------------- */

.preview-head { display: grid; gap: 3px; }
.preview-head h2 { overflow-wrap: anywhere; }

.chunks { display: grid; gap: 10px; }

.chunk {
  display: grid;
  gap: 6px;
  padding: 11px 13px;
  background: var(--surface-sunken);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
}

.chunk-head,
.chunk-row header {
  display: flex;
  align-items: center;
  gap: 9px;
  color: var(--ink-faint);
  font-size: .73rem;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: .05em;
}

.chunk-head .grow, .chunk-row header .grow { flex: 1; }

.chunk p, .chunk-row p, .passage p {
  margin: 0;
  font-size: .87rem;
  line-height: 1.6;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.chunk.is-empty p { color: var(--bad); font-style: italic; }

.chunk-list { display: grid; gap: var(--s2); max-height: 420px; overflow: auto; }

.chunk-row {
  display: grid;
  gap: 5px;
  padding: 9px 11px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface);
}

.chunk-row.is-empty { border-color: var(--bad-line); background: var(--bad-tint); }

/* Asking the knowledge base a question. The one honest test of an upload: a
   document can index perfectly and never surface for the question it was
   uploaded to answer. */
.ask { display: flex; gap: 9px; align-items: center; flex-wrap: wrap; }

/* Direct child only: the question is the one control that should take the
   slack. The passage count below is a fixed-width part of the row, and as a
   descendant it was inheriting this 220px minimum and shoving the row apart. */
.ask > .input { flex: 1; min-width: 220px; }

/* One labelled control in a row of unlabelled ones. Stacking the label the way
   .field does elsewhere would sit its input a line below everything beside it,
   so here the label goes alongside and the row keeps to a single line. */
.ask .field {
  display: flex;
  align-items: center;
  gap: 7px;
  flex: none;
}

.ask .field > span { white-space: nowrap; }
.ask .field .input { width: 64px; min-width: 0; flex: none; }

#results:not([hidden]) { margin-top: 15px; }

.ask-summary {
  display: flex;
  align-items: center;
  gap: 9px;
  flex-wrap: wrap;
  /* Always the same gap under it: it is a heading for the list that follows. */
  margin: 0 0 9px;
  font-size: .85rem;
  color: var(--ink-soft);
}

.ask-summary b { color: var(--ink); }

.passages { display: grid; gap: 10px; margin: 0; padding: 0; list-style: none; }

.passage {
  display: grid;
  gap: 7px;
  padding: 11px 13px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--brand);
  border-radius: var(--r-sm);
  background: var(--surface-sunken);
}

.passage.is-empty { border-left-color: var(--bad); background: var(--bad-tint); }
.passage.is-empty p { color: var(--bad); font-style: italic; }
.passage-head { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.passage-head b { color: var(--ink-faint); font-size: .76rem; }
.passage-head .grow { flex: 1; }
.passage-doc { font-weight: 600; font-size: .87rem; overflow-wrap: anywhere; }
.passage-tags { display: flex; flex-wrap: wrap; gap: 5px; }

/* Match score. The number alone means little; next to the one above it, a lot. */
.score { display: inline-flex; align-items: center; gap: 7px; flex: none; }
.score-track { width: 62px; height: 5px; border-radius: 999px; background: var(--line); overflow: hidden; }
.score-track i { display: block; height: 100%; }
.score-track .is-good { background: var(--good); }
.score-track .is-warn { background: var(--warn); }
.score-track .is-bad { background: var(--bad); }
.score small { color: var(--ink-soft); font-size: .74rem; }

/* ==========================================================================
   10  CHAT

   A test harness for the knowledge bases rather than a product surface, so it
   stays deliberately plain: the interesting part is the source list under each
   answer, not the chrome around it.
   ========================================================================== */

.chat { display: grid; gap: 14px; min-height: 320px; align-content: start; }

.chat-empty { display: grid; gap: 10px; padding: 26px var(--s1); }
.chat-empty h2 { font-size: 1.08rem; }
.chat-empty .sub { max-width: 62ch; }
.chat-suggestions { display: flex; flex-wrap: wrap; gap: var(--s2); margin-top: 6px; }
.chat-suggestions .btn { border-radius: 999px; font-size: .85rem; font-weight: 500; }

.chat-turn {
  max-width: 84%;
  padding: 11px 14px;
  border-radius: var(--r);
  font-size: .93rem;
  box-shadow: var(--shadow-1);
}

/* The question is the quieter of the two: you wrote it, you know what it says. */
.chat-you { justify-self: end; background: var(--accent-tint); border: 1px solid #f2e3ae; color: var(--ink); }
.chat-bot { justify-self: start; background: var(--surface); border: 1px solid var(--line); }
.chat-turn p { margin: 0; line-height: 1.55; }
.chat-thinking { color: var(--ink-faint); }

/* Sources. Collapsed by default so a page of answers stays readable, and open
   in one click when an answer looks wrong and you need to know why. */
.chat-sources { margin-top: 10px; font-size: .84rem; }
.chat-sources summary { color: var(--ink-soft); cursor: pointer; font-weight: 550; }
.chat-sources ul { display: grid; gap: 10px; margin: 10px 0 0; padding: 0; list-style: none; }
.chat-sources li { padding: 9px 11px; border-left: 2px solid var(--brand-strong); background: var(--surface-sunken); border-radius: var(--r-sm); }
.chat-sources li strong { font-weight: 600; }
.chat-sources li small { display: block; margin-top: 2px; color: var(--ink-faint); }
.chat-sources li p { margin-top: 6px; color: var(--ink-soft); font-size: .82rem; line-height: 1.5; }

.chat-score {
  margin-left: 7px;
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--accent-tint);
  color: var(--brand-ink);
  font-size: .74rem;
  font-weight: 650;
}

/* No sources means the model answered from itself, which on a knowledge-base
   test is the failure worth seeing without expanding anything. */
.chat-sources-none {
  margin-top: 10px;
  padding: var(--s2) 11px;
  border-left: 2px solid var(--warn);
  border-radius: var(--r-sm);
  background: var(--warn-tint);
  color: var(--warn-ink);
  font-size: .82rem;
}

.chat-compose { display: flex; gap: 10px; align-items: stretch; margin-top: 18px; }
.chat-compose .field { flex: 1; }
.chat-compose textarea { resize: vertical; min-height: 46px; }
.chat-compose .btn { align-self: flex-end; padding: 11px 18px; }
.chat-hint { margin-top: 7px; color: var(--ink-faint); font-size: .78rem; }

/* ==========================================================================
   11  ADMIN — ingestion, lifecycle, profiles, keys
   ========================================================================== */

/* The one-line summary a manager sees. Informative, not adjustable. */
.ingestion-note {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  padding: 10px 13px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--brand-strong);
  border-radius: var(--r-sm);
  background: var(--surface-sunken);
  font-size: .85rem;
  color: var(--ink-soft);
}

.ingestion-note svg { color: var(--brand-strong); margin-top: 1px; }
.ingestion-note b { color: var(--ink); font-weight: 600; }
.ingestion-note span { min-width: 0; }
.ingestion-note em { display: block; margin-top: 3px; font-style: normal; color: var(--ink-faint); font-size: .82rem; }

.setting-row { display: grid; gap: 10px; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); align-items: start; }
.setting-help { color: var(--ink-faint); font-size: .79rem; line-height: 1.45; }

.applies-when {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 1px var(--s2);
  border-radius: 999px;
  font-size: .71rem;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: .04em;
}

.applies-when.on-upload { color: var(--warn); background: var(--warn-tint); border: 1px solid var(--warn-line); }

/* A retired document in a table: struck through, because it is still there and
   no longer answering, and that is exactly what strike-through means. */
.is-retired-name { color: var(--ink-faint); text-decoration: line-through; text-decoration-thickness: 1px; }

/* Offered on a result row when an upload turns out to have earlier namesakes. */
.version-note {
  display: flex;
  align-items: center;
  gap: 9px;
  flex-wrap: wrap;
  margin-top: 7px;
  padding: var(--s2) 10px;
  border: 1px solid var(--warn-line);
  border-left: 3px solid var(--warn);
  border-radius: var(--r-sm);
  background: var(--warn-tint);
  color: var(--warn-ink);
  font-size: .83rem;
}

.version-note .grow { flex: 1; min-width: 150px; }

/* An issued API key, shown once. Monospace and selectable in one gesture,
   because it is copied by hand exactly once and mistyping it fails silently
   later as an unexplained 401. */
.key-value {
  margin: var(--s3) 0 0;
  padding: var(--s3) 14px;
  overflow-x: auto;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  background: var(--surface-sunken);
  font-family: var(--font-mono);
  font-size: .85rem;
  user-select: all;
}

/* Editing a profile in the row it belongs to, rather than on a separate page:
   what is being corrected is usually one field somebody can see is wrong from
   the row above it. */
.edit-form {
  display: grid;
  gap: var(--s3);
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  align-items: end;
  padding: 6px 2px;
}

.edit-form .row-actions { justify-content: flex-start; }
/* The knowledge base ticks need the full width of the row rather than one
   column of the grid, or twelve of them stack into a single narrow strip. */
.edit-form .edit-grants { grid-column: 1 / -1; }

/* ==========================================================================
   12  SIGN-IN

   Two panels: the brand on the left, the form on the right. The brand half is
   the one place with a large field of dark, and it is where the rail's palette
   comes from — sign in and the app are the same product.

   Nearly everybody arrives through Feishu, so yellow marks that one and the
   password form keeps the dark second voice: plainly available, plainly not the
   default.
   ========================================================================== */

.signin-page { height: 100%; background: var(--surface); }

.signin {
  display: grid;
  grid-template-columns: minmax(0, 1.08fr) minmax(0, 1fr);
  min-height: 100vh;
  /* Old iOS and any browser without dvh keeps the line above. */
  min-height: 100dvh;
}

.signin-brand {
  display: grid;
  min-width: 0;
  place-items: center;
  padding: 56px 48px;
  background:
    radial-gradient(90% 70% at 18% 8%, rgba(255, 200, 0, .16), transparent 62%),
    linear-gradient(155deg, #241f14 0%, var(--accent) 55%, #0e0d08 100%);
  color: var(--rail-strong);
}

/* The content is a column centred in the panel rather than pinned to its left
   edge, which on a wide monitor left it stranded a third of a screen from the
   form it belongs to. */
.signin-inner {
  display: grid;
  min-width: 0;
  grid-template-rows: auto auto auto 1fr;
  align-content: start;
  gap: 34px;
  width: 100%;
  max-width: 27rem;
}

.signin-brand-top { display: flex; align-items: center; gap: 14px; }

.signin-mark {
  width: 52px;
  height: 52px;
  border-radius: 13px;
  object-fit: cover;
  box-shadow: 0 0 0 3px rgba(255, 200, 0, .22);
}

.signin-wordmark { color: var(--brand); font-size: .78rem; font-weight: 800; letter-spacing: .22em; }

.signin-pitch { display: grid; gap: 14px; }

.signin-pitch h1 {
  font-family: var(--font-display);
  font-size: clamp(2.1rem, 3.1vw, 2.9rem);
  font-weight: 700;
  line-height: 1.08;
  letter-spacing: -.025em;
}

/* The product name under the company name: this is the platform, Starlight is
   the company. A yellow rule ties it to the brand without another block of it. */
.signin-eyebrow {
  padding-left: var(--s3);
  border-left: 3px solid var(--brand);
  color: var(--brand);
  font-size: .95rem;
  font-weight: 620;
}

.signin-pitch p { max-width: 38ch; color: rgba(253, 251, 244, .72); font-size: 1.02rem; line-height: 1.6; }

.signin-points { display: grid; gap: 11px; margin: 0; padding: 0; list-style: none; }

.signin-points li {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  color: rgba(253, 251, 244, .88);
  font-size: .95rem;
  font-weight: 500;
  line-height: 1.45;
}

.signin-points svg { margin-top: 3px; color: var(--brand); }
.signin-foot { align-self: end; color: rgba(253, 251, 244, .45); font-size: .78rem; }

.signin-panel { display: grid; min-width: 0; place-items: center; padding: 44px 32px; background: var(--surface); }
.signin-form { display: grid; min-width: 0; gap: var(--s5); width: 100%; max-width: 392px; }
.signin-form-head { display: grid; gap: 6px; }
.signin-form-head h2 { font-size: 1.7rem; font-weight: 680; letter-spacing: -.02em; }
.signin-form-head p { color: var(--ink-soft); font-size: .93rem; }

.signin-sso {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  padding: 12px 14px;
  font-size: .97rem;
  font-weight: 620;
  text-decoration: none;
}

/* Comfortably larger than the app's dense table filters: this is the one form
   people type into before they are awake. */
.signin-form .input {
  padding: 11px 12px;
  font-size: .97rem;
  background: var(--surface-sunken);
  transition: border-color .13s, background .13s, box-shadow .13s;
}

.signin-form .input:hover { border-color: var(--ink-faint); }
.signin-form .input:focus { outline: none; background: var(--surface); border-color: var(--brand-strong); box-shadow: var(--ring); }
.signin-form .field > span { font-size: .74rem; letter-spacing: .06em; }

.signin-divider { display: flex; align-items: center; gap: var(--s3); margin: 0; color: var(--ink-faint); font-size: .78rem; }
.signin-divider::before, .signin-divider::after { content: ''; flex: 1; border-top: 1px solid var(--line); }

.signin-submit { margin-top: 0; padding: 12px 14px; font-size: .97rem; font-weight: 600; }
.signin-help { margin-top: 2px; padding-top: 14px; border-top: 1px solid var(--line); color: var(--ink-faint); font-size: .8rem; line-height: 1.5; }

/* Password field with a reveal control sitting inside it. */
.input-reveal { position: relative; display: block; }
.input-reveal .input { padding-right: 42px; }

.reveal {
  position: absolute;
  top: 50%;
  right: 5px;
  transform: translateY(-50%);
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  border-radius: var(--r-sm);
  background: none;
  color: var(--ink-faint);
  cursor: pointer;
}

.reveal:hover { background: var(--surface-sunken); color: var(--ink); }
.reveal:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* ==========================================================================
   13  UTILITIES

   Each of these replaced an inline style that had appeared in three or more
   places. A one-off stays inline; a pattern becomes a class, or the next person
   to change the value has to find every copy of it.
   ========================================================================== */

.grow { flex: 1; }
/* A select that should be its own width rather than the column's. */
.select-auto { width: auto; }
/* Small muted text: the note beside a heading, the hint under a control. */
.note { color: var(--ink-soft); font-size: .82rem; }
/* A number in a table that carries a verdict. Weight as well as colour, because
   colour alone is not a signal everybody receives. */
.num-good { color: var(--good); font-weight: 600; }
.num-warn { color: var(--warn); font-weight: 600; }
.num-bad { color: var(--bad); font-weight: 600; }
/* A spinner that is always shown, rather than only while its button is busy. */
.spinner-on { display: block; }

/* ==========================================================================
   14  RESPONSIVE

   The shell collapses to a drawer. Between a phone and a desktop the work is
   card headers whose actions overflow, filter rows that stay four columns wide
   at 700px, and tables at full desktop padding on a screen that cannot afford
   it.
   ========================================================================== */

@media (max-width: 1100px) {
  .shell { grid-template-columns: 232px 1fr; }
  .topbar { padding: 14px 22px; }
  .content > .page { padding-left: 22px; padding-right: 22px; }
}

@media (max-width: 980px) {
  .shell { grid-template-columns: 1fr; }

  .sidebar {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    z-index: 40;
    width: 260px;
    transform: translateX(-100%);
    transition: transform .16s ease-out;
    box-shadow: var(--shadow-3);
  }

  .sidebar.is-open { transform: none; }
  .sidebar-toggle { display: inline-flex; }

  /* Tapping the page closes the drawer, which is what everyone tries first. */
  .scrim { position: fixed; inset: 0; z-index: 30; background: rgba(22, 21, 15, .42); }
}

@media (max-width: 900px) {
  .review { grid-template-columns: 1fr; }
  .queue { position: static; }
  .queue-scroll { max-height: 300px; }
}

@media (max-width: 860px) {
  .card-head { flex-direction: column; align-items: stretch; }
  .card-head .head-actions { margin-left: 0; }
  .head-actions .search, .head-actions .search .input { flex: 1; min-width: 0; }
  .hero { grid-template-columns: repeat(auto-fit, minmax(148px, 1fr)); }
  .hero-cell b { font-size: 1.6rem; }
  .audit-filters .field { min-width: 132px; }

  /* One column on anything narrow. The brand becomes a band across the top
     rather than disappearing, so the page still looks like it belongs to
     somebody, and the form takes the rest of the screen. */
  .signin { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto 1fr; }
  .signin-brand { padding: 26px 24px 30px; }
  .signin-inner { grid-template-rows: auto auto; gap: 18px; max-width: none; }
  .signin-pitch h1 { font-size: 1.7rem; }
  .signin-pitch p { font-size: .95rem; }
  /* The selling points and the legal line are the first things to go: on a
     phone the form is the whole reason the page was opened. */
  .signin-points, .signin-foot { display: none; }
  .signin-panel { padding: 30px 22px 48px; place-items: start stretch; }
  /* A fixed max-width plus panel padding overflowed a 430px screen by a hair,
     which clipped the inputs. On one column the form takes the width it is
     given. */
  .signin-form { max-width: none; }
}

@media (max-width: 640px) {
  .topbar, .scope-bar { padding-left: 14px; padding-right: 14px; }
  .topbar-title h1 { font-size: 1.15rem; }
  .content > .page { padding-left: 14px; padding-right: 14px; }
  .page { padding-left: 14px; padding-right: 14px; }
  .table-wrap th:first-child, .table-wrap td:first-child { padding-left: 14px; }
  .table-wrap th:last-child, .table-wrap td:last-child { padding-right: 14px; }
  .card-head, .card-body { padding-left: 15px; padding-right: 15px; }
  .pagination { padding-left: 15px; padding-right: 15px; }
  .pagination .btn { min-width: 0; }
  .chat-turn { max-width: 100%; }
  .form-grid { grid-template-columns: 1fr; }
  .filters { flex-direction: column; align-items: stretch; }
  .audit-filters .field, .filters .field { min-width: 100%; }
  .audit-filters .btn, .filters .btn { width: 100%; justify-content: center; }
  .truncate { max-width: 180px; }
}

/* The review page keeps its own two-column layout, but its sticky queue was
   offset for a sticky document header that no longer exists. Inside .content,
   sticky is measured from the top of the scroll region. */
.content .queue { top: 0; }
.content .queue-scroll { max-height: calc(100vh - 220px); }
.content .file-frame,
.content .doc-render { height: calc(100vh - 190px); }

@media (prefers-reduced-motion: reduce) {
  .btn, .input, .select, .kb-card, .nav-item, tbody tr td { transition: none; }
  .btn:active:not([disabled]) { transform: none; }
  a.kb-card:hover { transform: none; }
  .sidebar { transition: none; }
  .pill-warn .dot, .live .dot, .shell-sync[aria-busy="true"] svg { animation: none; }
}
