/* ========================================================================
   Alchemic — ChatGPT-clean, dual theme (light default, dark via [data-theme])
   ======================================================================== */

[hidden] { display: none !important; }

:root,
[data-theme="light"] {
  --bg: #ffffff;
  --bg-soft: #f9f9f9;
  --bg-elevated: #ffffff;
  --bg-input: #f4f4f5;
  --bg-input-focus: #ececef;
  --bg-hover: #ececef;
  --bg-sidebar: #f9f9f9;
  --line: #e5e5e7;
  --line-strong: #d1d1d6;

  --fg: #0d0d0d;
  --fg-muted: #5d5d63;
  --fg-subtle: #9b9ba0;

  --brand: #1d6db8;
  --brand-strong: #1558a0;
  --brand-soft: rgba(29, 109, 184, 0.10);

  --send-bg: #0d0d0d;
  --send-fg: #ffffff;
  --send-hover: #2a2a2a;

  --shadow-sm: 0 1px 2px rgba(0,0,0,0.04);
  --shadow-md: 0 6px 24px rgba(0,0,0,0.08);
  --shadow-lg: 0 16px 48px rgba(0,0,0,0.18);

  --avatar-bg: var(--brand);

  --label-PUBLIC: #1e8e3e;
  --label-SYNTHETIC: #8b5cf6;
  --label-SANITIZED: #1d6db8;
  --label-CONFIDENTIAL: #d97706;
  --label-RESTRICTED: #dc2626;
}

[data-theme="dark"] {
  --bg: #212121;
  --bg-soft: #2f2f2f;
  --bg-elevated: #2f2f2f;
  --bg-input: #2f2f2f;
  --bg-input-focus: #3a3a3a;
  --bg-hover: #3a3a3a;
  --bg-sidebar: #181818;
  --line: #3a3a3a;
  --line-strong: #4a4a4a;

  --fg: #ececf1;
  --fg-muted: #a8a8b0;
  --fg-subtle: #7a7a82;

  --brand: #4ab3f0;
  --brand-strong: #6cc4f5;
  --brand-soft: rgba(74, 179, 240, 0.16);

  --send-bg: #ececf1;
  --send-fg: #0d0d0d;
  --send-hover: #ffffff;

  --shadow-sm: 0 1px 2px rgba(0,0,0,0.4);
  --shadow-md: 0 6px 24px rgba(0,0,0,0.4);
  --shadow-lg: 0 16px 48px rgba(0,0,0,0.6);

  --avatar-bg: var(--brand);

  --label-PUBLIC: #6fc191;
  --label-SYNTHETIC: #b09be0;
  --label-SANITIZED: #6cc4f5;
  --label-CONFIDENTIAL: #f0b649;
  --label-RESTRICTED: #ee6f5b;
}

:root {
  --sidebar-w: 64px;
  --sidebar-w-expanded: 260px;
  --sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --mono: "JetBrains Mono", ui-monospace, Menlo, Consolas, monospace;
  --ease: cubic-bezier(0.32, 0.72, 0.16, 1);
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  /* No ``position: fixed`` here — that anchors the body so
     aggressively that iOS Safari's "scroll focused input into
     view" handling ends up shoving the entire canvas above the
     viewport when the soft keyboard opens. ``overflow: hidden``
     alone is enough: the document has no scroll axis, the
     visualViewport listener (in app.js) updates ``--vvh`` so the
     canvas height tracks the area above the keyboard. */
  overflow: hidden;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  transition: background 0.2s, color 0.2s;
}

button {
  font-family: inherit;
  font-size: inherit;
  background: none;
  border: none;
  color: inherit;
  cursor: pointer;
  padding: 0;
}
button:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; border-radius: 6px; }

textarea, input { font-family: inherit; }
::selection { background: var(--brand-soft); }

.icon-sun, .icon-moon { transition: opacity 0.2s; }
[data-theme="light"] .icon-sun { display: none; }
[data-theme="light"] .icon-moon { display: inline-block; }
[data-theme="dark"] .icon-sun { display: inline-block; }
[data-theme="dark"] .icon-moon { display: none; }

/* ========================================================================
   Left sidebar (collapsible)
   ======================================================================== */

.sidebar {
  position: fixed;
  top: 0; left: 0; bottom: 0;
  width: var(--sidebar-w);
  background: var(--bg-sidebar);
  border-right: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  padding: 8px;
  z-index: 40;
  overflow: hidden;
  transition: width 0.28s var(--ease), transform 0.28s var(--ease), background 0.2s, border-color 0.2s;
}
.sidebar.expanded { width: var(--sidebar-w-expanded); }

.sidebar-top, .sidebar-bottom {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.sidebar-bottom { margin-top: auto; }

.sidebar-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  height: 46px;
  padding: 0 10px;
  border-radius: 10px;
  color: var(--fg);
  text-align: left;
  white-space: nowrap;
  overflow: hidden;
  transition: background 0.15s, color 0.15s;
  position: relative;
}
.sidebar-row:hover { background: var(--bg-hover); color: var(--fg); }
/* Collapsed sidebar: the tooltip portal already names the row; the
   hover-shade square reads as redundant chrome. Drop the background
   on hover (keep the colour shift so the icon lights up to --fg).
   Expanded state keeps the full-width hover plate because the visible
   label benefits from the affordance. */
.sidebar:not(.expanded) .sidebar-row:hover { background: transparent; }
.sidebar-row .sidebar-icon-wrap {
  width: 30px; height: 30px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  /* Brand-colour every icon column. Theme-adaptive: --brand resolves
     to a slightly different blue on light vs. dark sidebars so contrast
     stays readable. */
  color: var(--brand);
}
/* Mask-image-driven sidebar icon: paints the SVG silhouette in
   currentColor so an external SVG asset (instead of inline markup)
   stays colour-consistent with the rest of the sidebar. The
   silhouette's alpha channel is what matters; the fill in the
   source SVG is overwritten by `background: currentColor`. */
.sidebar-row .sidebar-icon-mask {
  display: block;
  width: 26px;
  height: 26px;
  background: currentColor;
  -webkit-mask-position: center;
          mask-position: center;
  -webkit-mask-size: contain;
          mask-size: contain;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
}
.sidebar-row .cloud-storage-mask {
  -webkit-mask-image: url("/static/cloud-storage.svg");
          mask-image: url("/static/cloud-storage.svg");
}
.sidebar-row .sidebar-label {
  font-size: 16px;
  font-weight: 500;
  letter-spacing: -0.15px;
  text-align: left;
  opacity: 0;
  transform: translateX(-4px);
  transition: opacity 0.16s var(--ease), transform 0.18s var(--ease);
  pointer-events: none;
}
.sidebar.expanded .sidebar-row .sidebar-label {
  opacity: 1;
  transform: none;
  pointer-events: auto;
  transition-delay: 0.06s;
}

/* Expandable groups (Chats, Projects, Workflows, Connectors) */
.sidebar-group { display: flex; flex-direction: column; }
.sidebar-row .sidebar-label { flex: 1; }
.sidebar-chevron {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  color: var(--fg-subtle);
  opacity: 0;
  transition: opacity 0.16s var(--ease), transform 0.18s var(--ease);
  pointer-events: none;
  flex-shrink: 0;
}
.sidebar.expanded .sidebar-chevron { opacity: 1; transition-delay: 0.06s; }
.sidebar-group.open .sidebar-chevron { transform: rotate(90deg); color: var(--brand); }
.sidebar-group.open > .sidebar-row {
  background: var(--bg-hover);
  color: var(--fg);
}

/* Inline sublist using grid-template-rows for smooth height animation */
.sidebar-sublist {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.22s var(--ease);
}
.sidebar.expanded .sidebar-group.open .sidebar-sublist { grid-template-rows: 1fr; }
.sidebar-sublist-inner {
  overflow: hidden;
  min-height: 0;
}
.sidebar:not(.expanded) .sidebar-sublist { display: none; }

/* Sub-items align flush with parent rows: same 8px outer pad + 32px icon column. */
.sidebar-sub-items {
  list-style: none;
  margin: 0;
  padding: 2px 0 6px;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.sub-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  height: 40px;
  padding: 0 10px;
  border-radius: 10px;
  font-size: 15px;
  font-weight: 500;
  color: var(--fg);
  text-align: left;
  cursor: default;
  white-space: nowrap;
  overflow: hidden;
  transition: background 0.15s, color 0.15s;
}
.sub-item.available { cursor: pointer; }
.sub-item.available:hover { background: var(--bg-hover); color: var(--fg); }
.sub-icon-wrap {
  width: 30px;
  height: 30px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  color: var(--brand);
}
/* Sub-item icon stays brand on hover; only the row background shifts. */
.sub-item:hover .sub-icon-wrap { color: var(--brand); }
.sub-title {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  text-align: left;
}
.sub-pill {
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--bg-input);
  color: var(--fg-subtle);
  flex-shrink: 0;
}
.sub-pill.pill-live { background: var(--brand-soft); color: var(--brand); }
.sub-pill.pill-blocked { background: rgba(220, 38, 38, 0.1); color: var(--label-RESTRICTED); }

/* Indent submodule rows nested under a parent sidebar group
   (e.g., Computing / Experimentation under Connectors). Opt-in
   modifier on ``.sub-item`` so flush sub-items elsewhere are
   unaffected. */
.sub-item-indent { padding-left: 22px; }
.sub-item-indent:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.sub-empty {
  font-size: 13px;
  font-style: italic;
  color: var(--fg-subtle);
  padding: 8px 12px 8px 48px;
  white-space: normal;
  line-height: 1.45;
  text-align: left;
}
.sub-footer {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  height: 40px;
  padding: 0 10px;
  font-size: 14px;
  color: var(--brand);
  border-radius: 10px;
  text-align: left;
  transition: background 0.12s;
}
.sub-footer .sub-icon-wrap { color: var(--brand); }
.sub-footer:hover:not(.disabled):not(:disabled) { background: var(--brand-soft); }
.sub-footer.disabled, .sub-footer:disabled {
  color: var(--fg-subtle);
  cursor: not-allowed;
}

/* Override for ``+ New project`` so its "+" lines up under every
   project row's chevron. Math:
     .pm-row padding-left = 12px
     .pm-row-main padding-left = 12px
     chevron width = 14px (centered at 12+12+7 = 31px from .pm-row left)
   The button is a sibling of the .pm-row list — same left edge —
   so we match left-padding 24px + a 14px icon wrap + 8px gap so
   the "+" centers at 31px and the label "New project" begins at
   the same x as each project's name. */
#new-project-inline {
  padding-left: 24px;
  padding-right: 12px;
  gap: 8px;
}
#new-project-inline .sub-icon-wrap {
  width: 14px;
  height: 14px;
}
#new-project-inline .sub-icon-wrap svg {
  width: 14px;
  height: 14px;
}

/* -----------------------------------------------------------------
   Projects sidebar — two-line entries with the linked folder path
   in a smaller muted line below the project name. An edit pencil
   surfaces on hover; clicking the row body also opens the edit
   modal (no detail page exists yet).
   ----------------------------------------------------------------- */
.sidebar-sub-items .pm-row,
.entity-list .pm-row {
  display: flex;
  flex-wrap: wrap;             /* lets pm-children drop to a new line */
  align-items: stretch;
  gap: 4px;
  padding: 0 8px 0 12px;
  border-radius: 10px;
}
.pm-children {
  flex-basis: 100%;
  min-width: 0;                /* let the chat-title ellipsis engage (see below) */
  padding: 0 0 6px 24px;       /* indent under the chevron */
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.pm-children-list {
  list-style: none;
  margin: 0;
  padding: 0;
  min-width: 0;                /* propagate the shrink so a long title truncates,
                                  not widens the card — the ellipsis lives on
                                  .pm-chat-title but needs every flex ancestor
                                  to opt out of the default min-width:auto */
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.pm-children-empty {
  font-size: 12px;
  font-style: italic;
  color: var(--fg-subtle);
  padding: 6px 4px;
}
.pm-chat-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 8px;
  border-radius: 8px;
  font-size: 13px;
  color: var(--fg);
  cursor: pointer;
  min-width: 0;          /* allow the title to shrink inside the row */
  overflow: hidden;      /* never let a long title spill the panel */
  transition: background 0.12s;
}
.pm-chat-row:hover { background: var(--bg-hover); }
/* Active conversation in the projects sidebar gets a tinted
   background + brand-coloured glyph so the user always knows
   which thread is loaded. (No inset left-stripe shadow: on a
   border-radius row it bends around the corners and renders as
   a blue smear instead of a straight accent line.) */
.pm-chat-row.is-active {
  background: var(--bg-hover);
  color: var(--fg);
}
.pm-chat-row.is-active .pm-chat-glyph { color: var(--brand); }
.pm-chat-row .pm-chat-glyph { color: var(--fg-muted); flex-shrink: 0; }
.pm-chat-title {
  flex: 1;
  min-width: 0;          /* lets the flex child shrink so ellipsis engages */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.pm-chat-edit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  flex-shrink: 0;
  border: none;
  background: none;
  color: var(--fg-muted);
  border-radius: 6px;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.12s, color 0.12s, background 0.12s;
}
.pm-chat-row:hover .pm-chat-edit { opacity: 1; }
.pm-chat-edit:hover { color: var(--brand); background: var(--brand-soft); }

/* Archived disclosure — a single muted row at the bottom of the
   projects list (below "+ New project") that expands to show
   archived projects. Chevron alignment matches every project
   row's chevron: padding-left = .pm-row (12) + .pm-row-main (12)
   so the ">" sits at the same x. */
.pm-archived-toggle {
  list-style: none;
  margin-top: 4px;
}
.pm-archived-btn {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 6px 4px 6px 24px;
  border: none;
  background: none;
  border-radius: 8px;
  cursor: pointer;
  font: inherit;
  font-size: 12.5px;
  color: var(--fg-muted);
  text-align: left;
}
.pm-archived-btn:hover { background: var(--bg-hover); color: var(--fg); }
.pm-archived-btn[aria-expanded="true"] .pm-row-chev { transform: rotate(90deg); }
.pm-archived-count {
  display: inline-flex;
  align-items: center;
  padding: 0 6px;
  margin-left: 4px;
  font-family: var(--mono);
  font-size: 11px;
  background: var(--bg-input);
  border-radius: 999px;
  color: var(--fg-muted);
}
.pm-new-chat-btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin-top: 4px;
  padding: 5px 10px;
  background: none;
  border: 1px dashed var(--line);
  border-radius: 8px;
  cursor: pointer;
  font: inherit;
  font-size: 12.5px;
  color: var(--brand);
  width: fit-content;
  transition: background 0.12s, border-color 0.12s;
}
.pm-new-chat-btn:hover {
  background: var(--brand-soft);
  border-color: var(--brand);
}
/* Per-element hover, not block-level. The user expects to be
   able to land separately on (a) the project header (toggles
   expansion), (b) any chat row, (c) the "+ New chat" button —
   each highlights independently. The previous whole-li hover
   greyed the entire block including the children, which made
   the child controls feel "stuck inside" the project tile. */
.pm-row-main:hover { background: var(--bg-hover); }
.pm-row-main:hover .pm-row-name { color: var(--fg); }
/* Drawer variant: bordered card-ish row to fit the entity-list
   styling, slightly more padding so it doesn't look cramped next
   to the chunkier drawer chrome. */
.entity-list .pm-row {
  background: var(--bg-soft);
  border: 1px solid var(--line);
  padding: 4px 8px 4px 12px;
  transition: border-color 0.12s, background 0.12s;
}
/* In the drawer, the row is bordered. Highlight only when hover
   lands on the project header — not when the user is interacting
   with a nested chat or the "+ New chat" button. */
.entity-list .pm-row:has(.pm-row-main:hover) { border-color: var(--brand); }
.entity-list .pm-row .pm-row-main { padding: 10px 4px 10px 4px; }
/* Drawer main has 4px left padding (vs 12 in the sidebar), so the
   under-title action cluster needs a matching shallower indent
   (4 + 14px chevron + 8px gap). */
.entity-list .pm-row .pm-row-actions { padding-left: 26px; }
.entity-list .pm-row .pm-row-edit { opacity: 0.55; }
/* Drawer keeps the pencil + archive icons accessible whenever the
   project row is hovered — including when the cursor reaches the
   icons themselves. (Tying visibility to ``.pm-row-main:hover``
   alone made the icons evaporate before the cursor could land on
   them, since the cursor has to leave .pm-row-main to reach the
   sibling buttons.) */
.entity-list .pm-row:hover .pm-row-edit { opacity: 1; }
.pm-row-main {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  padding: 8px 4px 8px 12px;
  background: none;
  border: none;
  text-align: left;
  cursor: pointer;
  font: inherit;
  color: inherit;
}
.pm-row-chev {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--fg-muted);
  width: 14px;
  transition: transform 0.15s ease;
  flex-shrink: 0;
}
.pm-row-main[aria-expanded="true"] .pm-row-chev { transform: rotate(90deg); }
.pm-row-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.pm-row-name {
  font-size: 14px;
  font-weight: 500;
  color: var(--fg);
  width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Digital Twin badge on Project sidebar rows. Cross-created
   Platform Projects carry ``linked_digital_twin_project_id``; the
   badge tells the user the project has a curated corpus that
   agents can query via technique_request. */
.pm-row-twin-badge {
  display: inline-block;
  margin-left: 6px;
  padding: 0 5px;
  font-family: var(--mono);
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.4px;
  background: var(--brand-soft);
  color: var(--brand);
  border-radius: 4px;
  vertical-align: 2px;
}
.pm-row-folder {
  font-size: 11.5px;
  color: var(--fg-muted);
  width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  margin-top: 2px;
}
.pm-row-folder-empty { font-style: italic; opacity: 0.7; }
/* Per-row actions (edit / archive / delete) sit on their own line
   UNDER the title + folder text, not floated to the right of the
   title where they used to overlap (and truncate) long project
   names. ``.pm-row`` is flex-wrap, so flex-basis:100% drops the
   cluster to the next line; the left padding lines the icons up
   under the title text (12px main padding + 14px chevron + 8px
   gap). Muted but always visible — hover-reveal on a separate
   line would make row heights jump as the cursor moves down the
   list. */
.pm-row-actions {
  flex-basis: 100%;
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 0 4px 4px 34px;
  margin-top: -4px;
}
.pm-row-edit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 22px;
  flex-shrink: 0;        /* keep the action cluster stable (edit/archive/term) */
  background: none;
  border: none;
  color: var(--fg-muted);
  opacity: 0.45;
  cursor: pointer;
  border-radius: 6px;
  transition: opacity 0.12s, color 0.12s, background 0.12s;
}
.sidebar-sub-items .pm-row:hover .pm-row-edit { opacity: 1; }
/* Keep the header subtly tinted while the cursor is on an action
   button so it doesn't visually "snap off" mid-aim. */
.sidebar-sub-items .pm-row:has(.pm-row-edit:hover) .pm-row-main { background: var(--bg-hover); }
.pm-row-edit:hover { color: var(--brand); background: var(--brand-soft); }
.pm-row-edit-danger:hover {
  color: var(--danger, #d95757);
  background: rgba(217, 87, 87, 0.12);
}

/* -----------------------------------------------------------------
   Project modal — name + folder summary; folder picker view shares
   the same modal box (the two views toggle via [hidden]).
   ----------------------------------------------------------------- */
.pm-folder-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.pm-folder-summary {
  flex: 1;
  font-size: 13px;
  color: var(--fg);
  background: var(--bg-input);
  border: 1px dashed var(--line);
  padding: 8px 12px;
  border-radius: 8px;
  word-break: break-word;
  line-height: 1.4;
  min-width: 200px;
}
.pm-folder-summary-empty {
  color: var(--fg-muted);
  font-style: italic;
}

/* Sync status line — sits between the Folder section and the
   action buttons. Quiet on success, red-tinted on error. */
.pm-sync-status {
  margin: 12px 0 6px;
  font-size: 12.5px;
  color: var(--fg-muted);
  line-height: 1.45;
}
.pm-sync-status-error {
  color: #b13434;
}

/* The server-rendered folder browser was replaced by Google's
   Drive Picker (a Google-hosted popup); the corresponding
   ``.pm-picker-*`` / ``.pm-crumb*`` rules used to live here and
   are gone. The Drive Picker brings its own chrome. */

.sidebar-row.brand-row {
  margin-bottom: 6px;
  color: var(--fg);
}
.sidebar-row.brand-row .brand-label {
  font-family: var(--sans);
  font-weight: 600;
  font-size: 16px;
  letter-spacing: -0.2px;
}

/* Sidebar header.
   Collapsed: only the toggle, centered in the icon column.
   Expanded:  wordmark image on the left, toggle on the right (same row). */
.sidebar-header {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  height: 40px;
  margin-bottom: 6px;
  gap: 8px;
}
.sidebar.expanded .sidebar-header {
  justify-content: space-between;
}
.sidebar:not(.expanded) .brand-mark { display: none; }
/* Collapsed: align the toggle's visual center with the icon
   column the rest of the sidebar rows use. `.sidebar-row` has
   `padding: 0 10px` and a 30px icon-wrap, so its icon centers
   sit at x ≈ 33 inside the column. The 40px toggle, when the
   header uses `justify-content: center`, centers at column
   midpoint (x ≈ 28 on the 56px-wide mobile sidebar) — visibly
   left of every other icon. Pin the toggle to the same x by
   left-justifying with a precise padding-left. Works at both
   the desktop (64px) and mobile (56px) sidebar widths because
   the inner-column math is the same. */
.sidebar:not(.expanded) .sidebar-header {
  justify-content: flex-start;
  padding-left: 5px;
}

.sidebar-toggle {
  width: 40px; height: 40px;
  border-radius: 10px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--brand);
  flex-shrink: 0;
  transition: background 0.15s, color 0.15s;
  position: relative;
}
/* Hover keeps the brand colour. The background plate is suppressed
   so the toggle reads consistently with the rest of the icon-only
   sidebar (see ``.sidebar:not(.expanded) .sidebar-row:hover``). The
   tooltip portal already names the action; the plate was redundant.
   The Alchemic wordmark (``.brand-mark``) keeps its plate because
   that's a clickable text label, not an icon. */
.sidebar-toggle:hover { background: transparent; }

/* ----- Tooltips ---------------------------------------------------------- */
/* All tooltips render through a single shared portal element appended at
   body level (`#tip-portal`), positioned by JS via getBoundingClientRect.
   This avoids the CSS-only ::after approach which would be clipped by the
   sidebar's `overflow: hidden`. JS sets the placement (right / above /
   below) based on which container the trigger lives in. */

.tip-portal {
  position: fixed;
  /* baseline position; JS overrides via inline style on show */
  top: 0;
  left: 0;
  background: var(--fg);
  color: var(--bg);
  padding: 6px 10px;
  border-radius: 8px;
  font-size: 12.5px;
  font-weight: 500;
  letter-spacing: 0.1px;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  z-index: 200;
  box-shadow: var(--shadow-md);
  transform: translate(0, 0);
  transition: opacity 0.14s var(--ease);
  /* Hidden until JS shows it. We set display:none through aria-hidden so
     it doesn't affect layout or accessibility tree when not in use. */
}
.tip-portal[aria-hidden="true"] { display: none; }
.tip-portal.show { opacity: 1; }

.sidebar-row { position: relative; }

/* ----- Brand mark (wordmark image, expanded only) ----------------------- */
.brand-mark {
  display: inline-flex;
  align-items: center;
  height: 40px;
  /* Left pad bumped from 10 → 14 so the "A" lines up with the visible left
     edge of the row icons below — those are 24px glyphs centered in a 30px
     icon-wrap, putting them ~4px in from the row's content edge. */
  padding: 0 10px 0 14px;
  border-radius: 10px;
  background: transparent;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s;
}
.brand-mark:hover { background: var(--bg-hover); }
/* Brand wordmark — single PNG with dark-blue "Al" and lighter-blue
   "chemic". Both tones are mid-saturation so it reads on light AND
   dark sidebars without any per-theme asset swap. */
.brand-wordmark {
  display: block;
  height: 28px;        /* slightly larger than row labels so it reads as the brand mark */
  width: auto;
  user-select: none;
  pointer-events: none;
  animation: wm-in 0.2s var(--ease) both;
  animation-delay: 0.06s;
}
@keyframes wm-in {
  from { opacity: 0; transform: translateX(-6px); }
  to { opacity: 1; transform: none; }
}

/* Recent chats inline list — visible only when expanded */
.sidebar-section {
  margin-top: 14px;
  padding: 0 4px;
  opacity: 0;
  transition: opacity 0.18s var(--ease);
  pointer-events: none;
}
.sidebar.expanded .sidebar-section {
  opacity: 1;
  pointer-events: auto;
  transition-delay: 0.1s;
}
.sidebar-section-label {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.7px;
  color: var(--fg-subtle);
  padding: 8px 8px 6px;
}
.sidebar-recent {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
  max-height: 50vh;
  overflow-y: auto;
}
.sidebar-recent li {
  font-size: 13px;
  color: var(--fg);
  padding: 8px 10px;
  border-radius: 8px;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: background 0.12s;
}
.sidebar-recent li:hover { background: var(--bg-hover); }
.sidebar-recent-empty {
  color: var(--fg-subtle) !important;
  font-style: italic;
  cursor: default !important;
  padding: 6px 10px !important;
}
.sidebar-recent-empty:hover { background: transparent !important; }

/* Edge zone: 14px strip on the very left for swipe-to-open */
.sidebar-edge {
  position: fixed;
  top: 0; left: 0; bottom: 0;
  width: 14px;
  z-index: 35;
  cursor: e-resize;
}

/* User row + avatar pill */
.sidebar-row.user-row .sidebar-icon-wrap { width: 32px; height: 32px; }
.avatar {
  width: 28px; height: 28px;
  border-radius: 50%;
  background: var(--avatar-bg);
  color: white;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.3px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: filter 0.15s;
  text-transform: uppercase;
}
.avatar:hover { filter: brightness(1.1); }
.avatar.small { width: 28px; height: 28px; font-size: 11px; }
/* When the slot holds a real headshot the JS sets `.has-photo`; the
   inner <img> fills the disc and the initials background is hidden. */
.avatar.has-photo { background: transparent; overflow: hidden; padding: 0; }
.avatar.has-photo > img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 50%;
  display: block;
}

/* Existing collapsed-state tooltips defined via the rule above (shared). */

/* ========================================================================
   Top bar
   ======================================================================== */

.topbar {
  position: fixed;
  top: 0;
  left: var(--sidebar-w);
  right: 0;
  height: 56px;
  display: flex;
  align-items: center;
  padding: 0 16px;
  background: var(--bg);
  z-index: 30;
  transition: left 0.28s var(--ease), background 0.2s;
}
body.sidebar-expanded .topbar { left: var(--sidebar-w-expanded); }

/* Desktop / tablet (>720px) — sidebar is always visible as the
   icon column, with its own internal toggle to expand/collapse.
   Topbar + canvas push aside to make room. The hamburger
   trigger in the topbar stays hidden because the sidebar's own
   toggle is reachable. */
.topbar .topbar-menu-btn { display: none; }
.canvas { margin-left: var(--sidebar-w); transition: margin-left 0.28s var(--ease); }
body.sidebar-expanded .canvas { margin-left: var(--sidebar-w-expanded); }
/* Current project indicator — top-left of the conversation window.
   Name on the first line, linked-folder breadcrumb under it, both
   ellipsized so a deep Drive path can't push the right-side icons. */
.topbar-project {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-width: 0;
  max-width: 38vw;
  gap: 1px;
}
.topbar-project-name {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--fg);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.topbar-project-folder {
  font-size: 11px;
  color: var(--fg-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.topbar-spacer { flex: 1; }
.topbar-right {
  display: flex;
  align-items: center;
  gap: 4px;
}

.topbar-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 14px;
  border-radius: 999px;
  border: 1px solid var(--line);
  color: var(--fg);
  font-size: 13px;
  font-weight: 500;
  transition: background 0.15s, border-color 0.15s;
}
.topbar-pill:hover { background: var(--bg-hover); }
.topbar-pill svg { color: var(--brand); }

.icon-btn {
  width: 36px; height: 36px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--brand);
  transition: background 0.15s, color 0.15s;
}
/* Hover keeps brand colour; only the background changes. */
.icon-btn:hover { background: var(--bg-hover); }

/* ========================================================================
   Canvas
   ======================================================================== */

.canvas {
  margin-left: var(--sidebar-w);
  padding-top: 56px;
  /* ``dvh`` is the *dynamic* viewport height — it shrinks when
     iOS Safari raises the soft keyboard, so the canvas (and its
     anchored composer at the bottom) resizes naturally above the
     keyboard rather than scrolling the hero content off the top
     of the screen. ``vh`` is the legacy fallback for browsers
     that don't understand dvh (very old WebKit). */
  height: 100vh;
  height: var(--vvh, 100dvh);
  display: flex;
  flex-direction: column;
  align-items: center;
  transition: margin-left 0.28s var(--ease);
}
body.sidebar-expanded .canvas { margin-left: var(--sidebar-w-expanded); }

.hero, .thread, .composer-zone {
  width: 100%;
  max-width: 880px;
  padding: 0 24px;
}

.hero {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding-top: 24px;
  padding-bottom: 40px;
  gap: 18px;
}
.canvas.has-thread .hero { display: none; }

.hero-emblem {
  width: 128px;
  height: 128px;
  object-fit: contain;
  display: block;
  filter: drop-shadow(0 6px 24px rgba(74, 179, 240, 0.28));
  animation: emblem-in 0.6s cubic-bezier(0.2, 0.8, 0.2, 1) both;
}
@keyframes emblem-in {
  from { opacity: 0; transform: translateY(8px) scale(0.94); }
  to { opacity: 1; transform: none; }
}

.hero-title {
  font-weight: 600;
  /* slimmer than the previous clamp(28, 4.2vw, 34) so the tagline reads
     as a quiet brand line rather than a primary headline */
  font-size: clamp(22px, 2.8vw, 26px);
  line-height: 1.25;
  letter-spacing: -0.4px;
  margin: 0;
  color: var(--fg);
}

.thread {
  flex: 1;
  overflow-y: auto;
  padding-top: 24px;
  /* Generous bottom padding so the latest reply has visible
     breathing room from the Chronicle pill / composer below.
     Without this, scrolling-to-bottom puts the message bubble
     directly against the composer and the chat feels crammed. */
  padding-bottom: 56px;
  display: none;
  flex-direction: column;
  /* Inter-turn gap — the breathing room BETWEEN conversational
     turns (one Q+R pair to the next). Within a turn the gap is
     tighter, set via the .msg.user + .msg.agent rule below.
     The 2:1 ratio (8px intra-turn / 22px inter-turn) is what
     makes the eye read Q+R as a unit. */
  gap: 22px;
  /* Match the bottom padding when scroll-anchoring to the latest
     bubble — keeps the in-view reply far enough from the composer
     even when scroll jumps for an auto-anchored append. */
  scroll-padding-bottom: 56px;
}

/* Conversational pairing: pull each agent reply close to the user
   question that prompted it so the Q+R reads as one turn rather
   than two evenly-spaced siblings. Without this override the
   uniform thread gap made every response equidistant from its
   parent question and the next question — readers couldn't tell
   which Q a given R was answering. The negative margin is safe
   because the agent's avatar sits in the LEFT gutter while the
   user message above ends in the RIGHT gutter (bubble + right-
   aligned actions); they never visually collide. */
.msg.user + .msg.agent {
  margin-top: -14px;
}
.canvas.has-thread .thread { display: flex; }

/* "View in Chronicle" deep-link for the active thread.
   Sits below the message list (above the composer) so the
   button reads as a footer for the conversation, with a clear
   breathing-room gap (28px) between the last reply and the pill.
   Stacks vertically so the inline error chip can sit above the
   button without overlapping the composer toast. */
.thread-footer {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 28px 0 18px;
}
/* Inline error display directly above the Chronicle button —
   keeps the failure message anchored to the button that produced
   it instead of as a viewport-bottom toast that overlaps the
   composer. */
.thread-footer-error {
  max-width: 520px;
  padding: 7px 14px;
  border-radius: 999px;
  font-size: 12.5px;
  background: var(--bg-soft);
  color: var(--fg-muted);
  border: 1px solid var(--line);
  text-align: center;
}
.thread-footer-error[data-tone="error"] {
  background: color-mix(in oklab, var(--bg-soft) 80%, #d24545 20%);
  border-color: color-mix(in oklab, var(--line) 40%, #d24545 60%);
  color: var(--fg);
}
.thread-footer-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 16px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--bg-soft);
  color: var(--fg-muted);
  font: 500 13px var(--ui-font, system-ui);
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.thread-footer-btn:hover {
  background: var(--bg);
  color: var(--fg);
  border-color: var(--fg-subtle);
}
.thread-footer-btn:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}
.thread-footer-btn svg { flex: 0 0 auto; }

.msg {
  display: flex;
  flex-direction: column;
  gap: 6px;
  animation: msg-in 0.22s ease both;
  position: relative;
}
/* Avatar gutter — 32px circle + 12px gap = 44px reserved on the side
   the avatar lives on. Agent left, user right. Errors don't get an
   avatar so they stay flush. */
.msg.agent { padding-left: 44px; }
.msg.user  { padding-right: 44px; }
.msg.error { padding-left: 0; padding-right: 0; }

.msg-avatar {
  position: absolute;
  top: 0;
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  user-select: none;
  /* No circle, no frame — the avatar is just the icon itself. The
     Alchemic logo and the user silhouette have their own shapes;
     framing them in a colored disc cropped the artwork at the edges. */
}
.msg.agent .msg-avatar { left: 0; }
.msg.user  .msg-avatar { right: 0; }

.msg-avatar-logo {
  /* The logo PNG has whitespace around the triangle, so a 28px render
     inside the 32px container reads at the same visual weight as the
     user-side line glyph. Bumping it back to 32px makes the agent feel
     dominant. */
  width: 28px;
  height: 28px;
  object-fit: contain;
  pointer-events: none;
}

/* User-side message-bubble glyph: a clean person silhouette stroked
   in --brand at the same visual weight as the agent logo (28px in
   the 32px container) and the sidebar icons (which also use
   --brand). The brand-coloured initials circle stays in place on
   the topbar / popover / profile-modal avatars (see `.avatar`
   styles) — that surface IS the user's identity. */
.msg-avatar-user-icon {
  color: var(--brand);
  width: 28px;
  height: 28px;
}
.msg.user .msg-avatar { color: var(--brand); }

/* When the user has a profile photo (e.g. from a Google / GitHub /
   Microsoft OAuth response), render it as a circular headshot. */
.msg-avatar.has-photo {
  border-radius: 50%;
  overflow: hidden;
}
.msg-avatar-photo {
  width: 32px;
  height: 32px;
  object-fit: cover;
  display: block;
}
@keyframes msg-in {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}

.msg-author {
  display: flex;
  align-items: baseline;
  gap: 10px;
  font-size: 14px;
  font-weight: 600;
  color: var(--fg-muted);
}
.msg.agent .msg-author { color: var(--brand); }
.msg.error .msg-author { color: var(--label-RESTRICTED); }
.msg-author .timestamp {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--fg-subtle);
  font-weight: 400;
}

.msg-content {
  font-size: 16px;
  line-height: 1.7;
  color: var(--fg);
  white-space: pre-wrap;
  word-wrap: break-word;
}
/* When the agent emits markdown the renderer wraps content in <p>/<ul>;
   pre-wrap is no longer wanted because the renderer added the breaks. */
.msg-content-md { white-space: normal; }
.msg-content-md p { margin: 0 0 8px; }
.msg-content-md p:last-child { margin-bottom: 0; }
.msg-content-md ul {
  margin: 4px 0 8px;
  padding-left: 22px;
  list-style: disc;
}
.msg-content-md li { margin: 2px 0; }
/* When the composer emits a "**Sources**" mini-heading after the
   bullets, give it a touch more breathing room so it reads as a new
   section rather than a stray bold line. */
.msg-content-md ul + p { margin-top: 10px; }
/* Inline links inside the answer body: neutral colour with a quiet
   underline so a mid-sentence link doesn't break the reading flow.
   The underline alone marks it clickable; brand-blue is reserved for
   buttons and chips, not prose. */
.msg-content-md a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-color: var(--fg-subtle);
}
.msg-content-md a:hover {
  color: var(--fg);
  text-decoration-color: var(--fg-muted);
}
/* ChatGPT-style domain pill. Replaces the underlined inline link.
   The pill is a small dark capsule showing the (truncated) hostname;
   the rest of the original link text is rendered as plain prose
   right before it. Inverts cleanly across themes by using fg/bg. */
.msg-content-md a.link-pill {
  display: inline-flex;
  align-items: center;
  height: 21px;
  padding: 0 10px;
  margin: 0 2px;
  border-radius: 999px;
  background: var(--fg);
  color: var(--bg);
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: 0.1px;
  text-decoration: none;
  vertical-align: 1px;
  white-space: nowrap;
  transition: opacity 0.15s ease;
}
.msg-content-md a.link-pill:hover {
  background: var(--fg);
  color: var(--bg);
  opacity: 0.82;
  text-decoration: none;
}

/* Hover card anchored to a `.link-pill`. Shown via JS — see
   `_showLinkCard` in app.js. Lives at `body` level so the absolute
   positioning isn't constrained by the message bubble's overflow.
   Light card on any theme: ChatGPT's preview is a solid card and
   reads consistently against either background. */
.link-card {
  position: absolute;
  z-index: 1500;
  width: min(420px, calc(100vw - 32px));
  padding: 14px 16px 16px;
  border-radius: 14px;
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--line);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18);
  font-size: 13.5px;
  line-height: 1.45;
  pointer-events: auto;
  opacity: 0;
  visibility: hidden;
  transform: translateY(2px);
  transition: opacity 120ms ease, transform 120ms ease;
}
.link-card.visible {
  opacity: 1;
  transform: none;
}
.link-card-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
}
.link-card-favicon {
  width: 18px;
  height: 18px;
  border-radius: 4px;
  flex-shrink: 0;
  object-fit: contain;
}
.link-card-host {
  color: var(--fg);
  font-size: 13px;
  font-weight: 500;
  word-break: break-all;
}
.link-card-title {
  font-size: 15px;
  font-weight: 600;
  color: var(--fg);
  margin-bottom: 6px;
  /* Cap to 2 lines so the card stays compact even on busy pages. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.link-card-desc {
  font-size: 13px;
  color: var(--fg-muted);
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.msg-content-md code {
  font-family: var(--mono);
  font-size: 13.5px;
  padding: 1px 5px;
  border-radius: 4px;
  background: var(--bg-input);
  color: var(--fg);
}
.msg-content-md em { color: var(--fg-muted); }
.msg.user { align-items: flex-end; }
.msg.user .msg-content {
  max-width: 80%;
  background: var(--bg-input);
  border-radius: 18px;
  padding: 10px 16px;
}
.msg.error .msg-content {
  background: rgba(220, 38, 38, 0.08);
  border: 1px solid rgba(220, 38, 38, 0.3);
  border-radius: 14px;
  padding: 12px 16px;
}

/* Provenance caption — single line, equally-divided into three
   columns (Agents | Skills | Tools). Each cell stays inside its
   1/3 width; the value text truncates with `…` when it can't fit
   while the "+N" disclosure chip stays visible after the ellipsis
   so the user always has a way to see the full list. Cells with
   no value show a conventional "none" rather than a bare dash. */
/* Provenance row — inline flow with `·` separators between the
   three sections. Each section sizes to its content; on narrow
   viewports the row wraps. The cdot has breathing room on both
   sides so the visual rhythm is `AGENTS: a, b  ·  SKILLS: none
   ·  TOOLS: x, y, z`. Long tool lists ellipsis-truncate within
   their own section and surface the full list via title tooltip. */
.msg-provenance {
  display: flex;
  flex-direction: column;
  row-gap: 4px;
  margin-top: 8px;
  /* Inherit the bubble's sans-serif body font so this row reads as
     part of the surrounding chrome (matches the output-tab bar
     above), not as a separate monospace footer. */
  font: inherit;
  font-size: 12.5px;
  color: var(--fg-muted);
}
.msg-provenance .prov-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  column-gap: 14px;
  row-gap: 4px;
}
.msg-provenance .prov-cell {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  min-width: 0;
}
.msg-provenance .prov-label {
  /* Labels read in the same weight as the tab labels above; the
     italic on .prov-value below visually distinguishes the list
     from its category. */
  color: var(--fg-muted);
  font-weight: 500;
  flex-shrink: 0;
}
.msg-provenance .prov-value {
  /* Same muted colour as the category label — visual distinction
     comes from the regular (non-bold) weight on the value. */
  color: var(--fg-muted);
  font-weight: 400;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
  /* Cap any single section so a long tool list can't push the
     others off-screen — overflow ellipsises within this cap and
     the title tooltip carries the full text. */
  max-width: 60ch;
}
.msg-provenance .prov-empty {
  font-style: italic;
  opacity: 0.75;
}
.msg-provenance .prov-sep {
  color: var(--fg-muted);
  opacity: 0.55;
  user-select: none;
}

.msg-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 12px;
  margin-top: 4px;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--fg-subtle);
}

/* Output tabs — sit between the meta caption and the action
   toolbar on agent messages. The tab bar IS the disclosure: click
   a tab to open its panel, click the active tab again to collapse.
   Today only the simulation agent populates artefacts; future tabs
   (Output, Data, Visualisation) drop in by adding keys to
   `agent_artifacts`. */
.output-tabs {
  display: flex;
  flex-direction: column;
  margin-top: 8px;
  border: 1px solid var(--line);
  border-radius: 10px;
  overflow: hidden;
  background: var(--bg-input);
  font-size: 13px;
}

/* Collapsed: hide the panels entirely. The tab bar stays visible
   so the user knows the artefacts exist and can click in. */
.output-tabs.collapsed .output-tab-panels { display: none; }

.output-tab-bar {
  display: flex;
  gap: 0;
  background: var(--bg);
  align-items: stretch;
}
/* Bottom border only appears once a tab is open, so the bar reads
   as a card by itself when collapsed. */
.output-tabs.expanded .output-tab-bar {
  border-bottom: 1px solid var(--line);
}

/* Live-status pill rendered on the tab bar while the underlying
   tool pipeline is running. Three dots after the label shimmer
   to make "in flight" obvious. Pill is right-aligned via
   `margin-left: auto`. */
.output-tabs-status {
  margin-left: auto;
  align-self: center;
  padding: 2px 10px;
  border-radius: 999px;
  background: var(--brand-soft);
  color: var(--brand);
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.02em;
  user-select: none;
  animation: output-tabs-status-pulse 1.4s ease-in-out infinite;
}
@keyframes output-tabs-status-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.55; }
}

/* Disclosure chevron — pinned to the LEFT edge of the tab bar.
   Always visible so first-time users have an explicit "this
   opens" affordance; the SVG rotates 180° when a panel is
   expanded so the same glyph reads as "collapse". */
.output-tabs-collapse {
  border: 0;
  background: transparent;
  color: var(--fg-muted);
  padding: 0 12px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  transition: color 120ms ease, background 120ms ease;
}
.output-tabs-collapse:hover {
  color: var(--fg);
  background: var(--bg-input);
}
.output-tabs-collapse svg {
  transition: transform 160ms ease;
}
.output-tabs.expanded .output-tabs-collapse svg {
  transform: rotate(180deg);
}
.output-tab-btn {
  border: 0;
  background: transparent;
  color: var(--fg-muted);
  padding: 8px 14px;
  font: inherit;
  font-size: 12.5px;
  font-weight: 500;
  cursor: pointer;
  border-bottom: 2px solid transparent;
  transition: color 120ms ease, border-color 120ms ease;
}
.output-tab-btn:hover { color: var(--fg); }
.output-tab-btn.active {
  color: var(--fg);
  border-bottom-color: var(--fg);
}
.output-tab-panels { position: relative; }
.output-tab-panel { display: none; }
.output-tab-panel.active { display: block; }

.output-tab-content { display: flex; flex-direction: column; }
.output-tab-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 6px 12px;
  border-bottom: 1px solid var(--line);
  background: var(--bg);
  font-size: 11.5px;
  color: var(--fg-subtle);
  text-transform: uppercase;
  letter-spacing: 0.4px;
}
.output-tab-lang { font-family: var(--mono); }
.output-tab-copy {
  border: 1px solid var(--line);
  background: var(--bg-input);
  color: var(--fg);
  font: inherit;
  font-size: 11px;
  padding: 3px 10px;
  border-radius: 6px;
  cursor: pointer;
  text-transform: none;
  letter-spacing: 0;
}
.output-tab-copy:hover { background: var(--bg); }
.output-tab-pre {
  margin: 0;
  padding: 12px 14px;
  background: var(--bg-input);
  color: var(--fg);
  font-family: var(--mono);
  font-size: 12.5px;
  line-height: 1.55;
  /* Cap the panel height so a long script (or stdout dump) scrolls
     INSIDE its own pre block instead of stretching the chat bubble
     to the full file length and pushing every later message off-
     screen. The chat surface stays compact; the user opens / scrolls
     the Code tab when they want to read the whole thing. */
  max-height: 420px;
  overflow: auto;
  white-space: pre;
  scrollbar-width: thin;
}
.output-tab-pre code { font: inherit; color: inherit; background: transparent; }

/* Inline image (PNG / SVG / JPG) rendered inside the Visualization
   tab when the viz payload is a ``data:image/...`` URL. Used by the
   general-purpose script_run tool. Bounded height so a large
   plot doesn't push the rest of the chat off-screen; the image keeps
   its native aspect via ``object-fit: contain``. */
.output-tab-image {
  display: block;
  width: 100%;
  max-width: 100%;
  max-height: 520px;
  object-fit: contain;
  background: var(--bg-input);
  border-radius: 6px;
  margin: 0;
}

/* 3D structure viewer (Visualization tab). 3Dmol.js mounts a
   <canvas> inside the stage; we just give it a fixed height + the
   bubble's own background so the viewer blends with the chat. */
.output-tab-viz .output-viz-stage {
  position: relative;
  width: 100%;
  height: 320px;
  background: var(--bg-input);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--fg-muted);
  font-size: 12.5px;
}
.output-tab-viz .output-viz-stage canvas {
  display: block;
  width: 100% !important;
  height: 100% !important;
}
.output-tab-viz .output-viz-toolbar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  padding: 6px 10px;
  border-bottom: 1px solid var(--line);
  background: var(--bg);
}
.output-tab-viz .output-viz-control-group {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px;
  min-width: 0;
}
.output-tab-viz .output-viz-control-group + .output-viz-control-group::before {
  content: "·";
  color: var(--fg-subtle);
  padding: 0 2px;
}
.output-tab-viz .output-viz-toolbar-label {
  color: var(--fg-subtle);
  font-size: 11px;
  white-space: nowrap;
}
.output-tab-viz .output-viz-toggle.active {
  background: var(--fg);
  border-color: var(--fg);
  color: var(--bg);
}
.output-tab-viz .output-tab-copy:disabled,
.output-tab-viz .output-viz-select:disabled {
  cursor: not-allowed;
  opacity: 0.48;
}
.output-tab-viz .output-viz-select {
  min-height: 24px;
  border: 1px solid var(--line);
  background: var(--bg-input);
  color: var(--fg);
  border-radius: 6px;
  font: inherit;
  font-size: 11px;
  padding: 3px 24px 3px 8px;
}
.output-tab-viz .output-viz-measurement {
  padding: 6px 10px;
  border-top: 1px solid var(--line);
  background: var(--bg);
  color: var(--fg-muted);
  font-size: 12px;
  line-height: 1.4;
}

/* Trajectory player — appears below the 3D stage when the viz
   carries multi-frame XYZ. Single-frame structures don't render
   this strip; the layer is dynamic and only shows what the
   artifact has. Sits flush under the stage in the same monospace
   family as the rest of the tab. */
.output-tab-viz .output-viz-player {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  border-top: 1px solid var(--border);
  background: var(--bg-input);
  font-size: 12px;
  color: var(--fg-muted);
}
.output-tab-viz .output-viz-play {
  appearance: none;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--fg);
  border-radius: 4px;
  width: 28px;
  height: 24px;
  font-size: 11px;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.output-tab-viz .output-viz-play:hover {
  background: var(--bg-hover);
}
.output-tab-viz .output-viz-slider {
  flex: 1;
  min-width: 80px;
  accent-color: var(--accent, currentColor);
}
.output-tab-viz .output-viz-frame-label {
  font-variant-numeric: tabular-nums;
  min-width: 56px;
  text-align: right;
}

/* Per-message action toolbar. Always visible on both roles so the
   action affordances (retry / edit / copy / delete + the date)
   read as part of the message rather than appearing on hover only
   — on mobile there is no hover, and the prior hide-until-hover
   pattern made the user bubble look like it had a large empty
   gap below it. The toolbar stays subdued (fg-subtle) so it
   doesn't compete with the content. */
.msg-actions {
  display: flex;
  align-items: center;
  gap: 2px;
  margin-top: 4px;
}
.msg.user .msg-actions {
  align-self: flex-end;
}
.msg-action {
  width: 32px;
  height: 32px;
  border-radius: 8px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  color: var(--fg-subtle);
  transition: background 0.12s, color 0.12s, transform 0.1s;
}
.msg-action:hover { background: var(--bg-hover); color: var(--fg); }
.msg-action:active { transform: scale(0.94); }
.msg-action.active { color: var(--brand); background: var(--brand-soft); }
.msg-action.flash { color: var(--brand); background: var(--brand-soft); }

.msg-date {
  font-size: 13px;
  color: var(--fg-subtle);
  padding: 0 8px 0 4px;
  align-self: center;
}
.msg-meta .meta-item { display: inline-flex; align-items: center; gap: 4px; }
/* Visible center-dot separator between meta items (".msg_meta_item · .msg_meta_item")
   so a reader can tell which token belongs to which slot. The dot is
   muted and styled into the gap; it doesn't appear before the first
   item. */
.msg-meta .meta-item + .meta-item::before {
  content: "·";
  margin-right: 8px;
  color: var(--fg-subtle);
  font-weight: 500;
}
.msg-meta .meta-dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  display: inline-block;
}
/* Underlined neutral link inside the meta line — used for the source
   record id (e.g. "mp-149"). No brand colour: the link is a quiet
   provenance pointer, not a callout, so it inherits the meta caption's
   muted foreground. */
.msg-meta .meta-link {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-color: var(--fg-subtle);
}
.msg-meta .meta-link:hover {
  color: var(--fg);
  text-decoration-color: var(--fg-muted);
}

/* "Thinking…" placeholder while a request is in flight. Reads as a
   quiet status line rather than a grey block — the three dots cycle
   through opacity in sequence so the user sees the platform is alive
   without the content area pretending to be a message bubble. */
.msg-thinking {
  display: inline-flex;
  align-items: baseline;
  gap: 1px;
  color: var(--fg-muted);
  font-size: 16px;
  line-height: 1.6;
}
.msg-thinking .thinking-text {
  margin-right: 1px;
}
.msg-thinking .thinking-dot {
  display: inline-block;
  width: 0.5em;
  text-align: center;
  opacity: 0.25;
  animation: thinking-dot 1.4s ease-in-out infinite;
  font-weight: 700;
}
.msg-thinking .thinking-dot:nth-of-type(1) { animation-delay: 0s;    }
.msg-thinking .thinking-dot:nth-of-type(2) { animation-delay: 0.18s; }
.msg-thinking .thinking-dot:nth-of-type(3) { animation-delay: 0.36s; }
@keyframes thinking-dot {
  0%, 80%, 100% { opacity: 0.25; transform: translateY(0); }
  40%           { opacity: 1.00; transform: translateY(-1px); }
}

/* Persistent trace block — shown above the answer when the chat handler
   identifies a tool path. While the request is in flight, one step is
   "active" (pulsing brand dot) and the rest are "pending" (subtle ring).
   When the response arrives every step transitions to "done" (brand
   check) and the trace stays visible as a record of what the platform
   did. The trace is intentionally compact and quiet so the answer
   below is the visual focus. */
.msg-trace {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 10px 14px;
  border-radius: 12px;
  background: var(--bg-input);
  border: 1px solid var(--line);
  font-size: 13px;
  color: var(--fg-muted);
  max-width: 80%;
}
.trace-step {
  display: flex;
  align-items: baseline;
  gap: 8px;
  line-height: 1.5;
}
.trace-marker {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  flex-shrink: 0;
  font-size: 12px;
  color: var(--fg-subtle);
  font-weight: 600;
}
.trace-step.trace-active .trace-marker {
  color: var(--brand);
  animation: trace-pulse 1.2s ease-in-out infinite;
}
.trace-step.trace-done .trace-marker { color: var(--brand); }
.trace-step.trace-active .trace-label,
.trace-step.trace-done .trace-label   { color: var(--fg); font-weight: 500; }
.trace-detail {
  color: var(--fg-muted);
  font-size: 12.5px;
}
@keyframes trace-pulse {
  0%, 100% { opacity: 0.45; transform: scale(0.85); }
  50%      { opacity: 1.00; transform: scale(1.10); }
}

/* Spacing between the trace block and the answer that lands
   underneath it on tool-routed messages. Apply the gap only when
   the trace is actually expanded — collapsed traces are
   `display:none`, so without this scope the 12px margin would still
   push the answer down and the avatar (pinned to top:0) would look
   misaligned. */
.msg-answer { margin-top: 0; }
.msg.show-trace .msg-answer { margin-top: 12px; }

/* The trace is shown while the request is in flight, then collapses on
   response arrival. The user reveals it on demand by clicking the
   "Trace" pill in the action bar — which toggles `.show-trace` on
   the bubble. */
.msg-trace.trace-collapsed { display: none; }
.msg.show-trace .msg-trace.trace-collapsed {
  display: flex;
  animation: trace-fade-in 0.18s var(--ease);
}
@keyframes trace-fade-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: none; }
}

/* Active state for the trace toggle: when the trace panel is open we
   tint the action button so the user can see which control opened it.
   Same tint the other action buttons use on `.active` / `.flash`. */
.msg.show-trace .msg-action[data-action="trace"] {
  color: var(--brand);
  background: var(--brand-soft);
}
/* ========================================================================
   Composer
   ======================================================================== */

.composer-zone {
  flex-shrink: 0;
  padding-bottom: 20px;
  position: relative;  /* anchor for .agent-popover */
}

/* Agent popover — opened by the #open-agent composer button.
   Per-agent model picker; persists per-thread to localStorage.
   Same visual idiom the user's eye is already trained for from
   the legacy model-menu. */
.agent-popover {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 56px;
  /* Widened so the agent name (one line) and the model picker pill
     (also one line, no truncation) fit side-by-side with comfortable
     breathing room. Capped at min(viewport - 24px, 560px) so it
     doesn't overflow narrow windows. */
  width: min(calc(100vw - 32px), 560px);
  /* Tall enough that a 9–12-row Workflows catalog fits without
     clipping on a typical laptop viewport (≥800px tall); the
     internal list still scrolls if the user is on a short screen. */
  max-height: min(85vh, 740px);
  display: flex;
  flex-direction: column;
  background: var(--bg-elevated);
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow: var(--shadow-lg);
  z-index: 50;
  overflow: hidden;
}
.agent-popover-head {
  /* Three-column grid: a left-side spacer mirrors the strategy
     pill's right-side slot so the centered "Agents" title stays
     visually centered regardless of how wide the pill grows when
     a long strategy name is picked.
     The agent popover overrides this below (title left, pill
     right); other popovers (workflows / skills / tools) keep the
     centered shape. */
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 10px;
  padding: 10px 16px 6px;
}
/* Agent popover header — title on the left, strategy pill on the
   right. Scoped by #agent-popover so the sibling popovers stay
   centered. The padding-left and padding-right are explicitly
   equal so the gap from popover-left to "Agents" matches the gap
   from the strategy pill's right border to popover-right. */
#agent-popover > .agent-popover-head {
  grid-template-columns: auto 1fr auto;
  padding-left: 16px;
  padding-right: 16px;
}
#agent-popover > .agent-popover-head > .agent-popover-title {
  text-align: left;
  justify-self: start;
  margin: 0;
  padding: 0;
}
#agent-popover > .agent-popover-head > .agent-popover-strategy {
  margin: 0;
  padding: 0;
  /* Pin to the third grid column. Without this, CSS auto-placement
     drops the slot into column 2 (the ``1fr`` middle track) — only
     two children are declared in the markup, so column 3 stays
     empty and the pill ends up far from the popover's right edge.
     With ``grid-column: 3`` plus ``justify-self: end``, the pill's
     right border sits exactly ``padding-right: 16px`` from the
     popover edge, mirroring the title's left offset. */
  grid-column: 3;
  justify-self: end;
}
.agent-popover-head-spacer {
  /* Mirrors the strategy pill column so the title centers. The
     spacer collapses to a 0-height inline element but keeps the
     grid track active. */
  display: block;
  min-width: 1px;
}
.agent-popover-title {
  font-weight: 600;
  font-size: 13.5px;
  color: var(--fg);
  text-align: center;
}
.agent-popover-strategy {
  display: inline-flex;
  align-items: center;
  justify-self: end;
}
.agent-popover-strategy:empty { display: none; }
.agent-popover-list {
  list-style: none;
  margin: 0;
  padding: 0;
  overflow-y: auto;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.agent-popover-row {
  display: grid;
  /* Two columns now: avatar + meta. The model picker pill lives
     inside the meta cell, on the same flex row as the agent name,
     so a long model label (Nemotron 3 Super 120B (free)) pushes
     the name to ellipsis instead of stealing space from the role
     line below. */
  grid-template-columns: 56px 1fr;
  align-items: center;
  gap: 14px;
  padding: 12px 14px;
  border-bottom: 1px solid var(--line);
}
.agent-meta-top {
  display: flex;
  align-items: center;
  gap: 10px;
  /* Picker is pushed to the right edge; the name takes the
     remaining space and ellipses if absurd. */
  justify-content: space-between;
}
.agent-meta-top .name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.agent-meta-top .agent-model-trigger {
  /* Let the pill take its natural width — the operator needs to
     see the full model name so they know exactly which model is
     active. The popover itself is wide enough to fit it next to
     the agent name with breathing room. Override the 220px cap
     baked into ``.agent-model-trigger`` below. */
  flex-shrink: 0;
  max-width: none;
}
.agent-meta-top .agent-model-note {
  /* Static "Tool broker — no LLM" label that replaces the model
     pill on the Hermes row. Same vertical rhythm as the pill so
     the agent rows stay aligned. Muted color + italic to signal
     "informational, not interactive." */
  font-size: 12px;
  color: var(--fg-muted);
  font-style: italic;
  padding: 4px 8px;
  flex-shrink: 0;
  cursor: help;
}
.agent-meta-top .agent-model-label {
  /* Same idea: no ellipsis on the label text inside the pill.
     Truncating "Nemotron 3 Super 120B (free)" to "Nemotron 3 …"
     defeats the purpose of showing the active model. */
  overflow: visible;
  text-overflow: clip;
  white-space: nowrap;
}
/* Strategy pill — agentic-collaboration card picker. Sized to sit
   under the role title in the agent row; the dropdown menu floats
   on top via position:fixed and is appended to <body> so the
   parent popover's overflow:hidden doesn't clip it. */
.agent-strategy-trigger {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  /* Vertical pad nudged so the pill height matches the "Agents"
     title baseline now that the font is the same size. */
  padding: 4px 10px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--bg-soft);
  color: var(--fg-muted);
  font-family: inherit;
  /* Match the .agent-popover-title font-size so the strategy
     picker reads as a peer of "Agents" — it's the most important
     control in the popover, not a footnote. */
  font-size: 13.5px;
  font-weight: 600;
  cursor: pointer;
  max-width: 100%;
  white-space: nowrap;
}
.agent-strategy-trigger:hover {
  border-color: color-mix(in oklab, var(--line) 60%, var(--fg-muted) 40%);
  color: var(--fg);
}
.agent-strategy-trigger[aria-expanded="true"] {
  border-color: var(--accent, color-mix(in oklab, var(--fg) 30%, var(--bg) 70%));
  color: var(--fg);
}
.agent-strategy-icon, .agent-strategy-chev {
  display: inline-flex;
  align-items: center;
  color: currentColor;
  opacity: 0.85;
}
.agent-strategy-label {
  overflow: hidden;
  text-overflow: ellipsis;
}
.agent-strategy-menu {
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 4px 0;
  box-shadow: 0 6px 24px rgba(0,0,0,0.18);
  z-index: 1000;
  max-height: 380px;
  overflow-y: auto;
}
.agent-strategy-heading {
  font-family: var(--mono);
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--fg-muted);
  padding: 6px 10px 2px;
}
.agent-strategy-divider {
  height: 1px;
  background: var(--line);
  margin: 4px 0;
}
.agent-strategy-row {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  padding: 6px 10px;
  border: 0;
  background: transparent;
  color: var(--fg);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.agent-strategy-row:hover {
  background: var(--bg-soft);
}
.agent-strategy-row.current {
  /* Use the brand colour at a low mix so the selected row reads
     clearly without dominating. (Earlier this used var(--accent),
     but --accent isn't defined globally — it fell back to --fg
     which made the tint invisible against --bg-soft.) */
  background: color-mix(in oklab, var(--bg-soft) 70%, var(--brand) 18%);
}
.agent-strategy-row-main {
  font-size: 12.5px;
  font-weight: 500;
}
.agent-strategy-row-sub {
  font-size: 10.5px;
  color: var(--fg-muted);
  white-space: normal;
  line-height: 1.35;
}
/* Section header rows — "User-defined strategies" and "Legacy
   Strategies". Same visual footprint as an Auto row (bold main +
   muted sub) so the picker reads as a uniform list. The legacy
   variant is expandable; the chevron rotates on aria-expanded.
   The user-defined variant is non-interactive (no hover, default
   cursor) since it's just a section label. */
.agent-strategy-section {
  /* Inherits .agent-strategy-row base layout (column flex,
     padding, etc.); we just style the inner head row so the
     chevron sits on the right of the main label. */
  cursor: default;
}
.agent-strategy-section:hover { background: transparent; }
.agent-strategy-section-toggle { cursor: pointer; }
.agent-strategy-section-toggle:hover { background: var(--bg-soft); }
.agent-strategy-section-head {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
}
.agent-strategy-section-chev {
  display: inline-flex;
  align-items: center;
  color: var(--fg-muted);
  transition: transform 0.15s ease;
}
.agent-strategy-section-toggle[aria-expanded="true"] .agent-strategy-section-chev {
  transform: rotate(180deg);
}
/* Items belonging to a section (user-defined cards listed under
   their header, legacy cards under the expanded toggle) get a
   small left indent so the grouping reads visually. */
.agent-strategy-section-item {
  padding-left: 22px;
}

/* "+ Add a strategy…" row at the bottom of the strategy menu.
   Inherits the row layout but reads as an action, not an option. */
.agent-strategy-add {
  color: var(--brand, var(--fg));
  font-weight: 600;
  font-size: 12px;
  display: flex;
  align-items: center;
  gap: 6px;
}
.agent-strategy-add::before {
  content: "+";
  font-size: 15px;
  line-height: 1;
}
/* Modal: "Add a strategy" — reuses .modal / .modal-box vocabulary.
   New surfaces below carry the strategy-modal- prefix so they
   don't bleed into other modals. */
/* Selector deliberately doubled (.modal-box.strategy-modal-box)
   so this beats the mobile-media-query .modal-box rule that sets
   padding-top. The head provides its own internal padding. */
.modal-box.strategy-modal-box {
  /* Pinned header + scrollable body + pinned actions footer.
     Only ``.strategy-modal-body`` scrolls; the title, tabs, and
     Save/Cancel buttons stay visible while the user fills in a
     long form. Override the base ``.modal-box`` (which scrolls
     as one unit). */
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding: 0;
}
.strategy-modal-head {
  flex-shrink: 0;
  padding: 8px 22px 0;
  background: var(--bg);
  border-bottom: 1px solid var(--line);
}
.strategy-modal-head .modal-header { margin-bottom: 8px; }
.strategy-modal-head .strategy-modal-tabs {
  margin-bottom: -1px;  /* tabs sit on the head's bottom border */
  border-bottom: 0;
}
.strategy-modal-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 18px 22px 8px;
}
.strategy-modal-box .modal-actions {
  flex-shrink: 0;
  margin-top: 0;
  padding: 14px 22px;
  background: var(--bg);
}
.strategy-modal-tabs {
  display: flex;
  gap: 4px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 14px;
}
.strategy-modal-tab {
  padding: 8px 14px;
  font-size: 13px;
  font-weight: 500;
  color: var(--fg-muted);
  background: transparent;
  border: 0;
  border-bottom: 2px solid transparent;
  cursor: pointer;
  margin-bottom: -1px;
}
.strategy-modal-tab.active {
  color: var(--fg);
  border-bottom-color: var(--brand, var(--fg));
}
.strategy-modal-tab:hover { color: var(--fg); }
.strategy-modal-panel { display: none; }
.strategy-modal-panel.active { display: block; }
.strategy-modal-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 12px;
}
.strategy-modal-field label {
  font-size: 12px;
  font-weight: 600;
  color: var(--fg);
}
.strategy-modal-field .help {
  font-size: 11px;
  color: var(--fg-muted);
  margin-top: 2px;
}
.strategy-modal-field input,
.strategy-modal-field textarea,
.strategy-modal-field select {
  font: inherit;
  font-size: 13px;
  color: var(--fg);
  background: var(--bg-soft);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 8px 10px;
  width: 100%;
}
.strategy-modal-field textarea {
  min-height: 84px;
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.55;
  resize: vertical;
}
.strategy-modal-phase {
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 10px 12px;
  margin-bottom: 10px;
  background: var(--bg-soft);
}
.strategy-modal-phase-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 8px;
}
.strategy-modal-phase-head strong {
  font-size: 12px;
}
.strategy-modal-phase-remove {
  background: transparent;
  border: 0;
  color: var(--fg-muted);
  cursor: pointer;
  font-size: 12px;
  padding: 2px 6px;
}
.strategy-modal-phase-remove:hover { color: var(--fg); }
.strategy-modal-add-phase {
  background: transparent;
  border: 1px dashed var(--line);
  border-radius: 8px;
  padding: 8px;
  width: 100%;
  font-size: 12px;
  color: var(--fg-muted);
  cursor: pointer;
}
.strategy-modal-add-phase:hover {
  color: var(--fg);
  border-color: var(--fg-muted);
}
.strategy-modal-error {
  background: color-mix(in oklab, var(--bg-soft) 70%, #c0392b 30%);
  color: #fff;
  border-radius: 8px;
  padding: 8px 10px;
  font-size: 12px;
  margin: 10px 0;
  white-space: pre-wrap;
  word-break: break-word;
}
.strategy-modal-templates {
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px dashed var(--line);
  font-size: 12px;
  color: var(--fg-muted);
}
.strategy-modal-templates a {
  color: var(--brand, var(--fg));
  text-decoration: none;
  margin-right: 10px;
}
.strategy-modal-templates a:hover { text-decoration: underline; }
.strategy-modal-file {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.strategy-modal-file input[type="file"] { font-size: 12px; }
/* Chip-set for multi-select enum fields (agents, allowed_inputs,
   required_outputs). Click toggles selection; visually clear so
   the form stays scannable without external libs. */
.strategy-modal-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.strategy-modal-chip {
  border: 1px solid var(--line);
  background: var(--bg);
  color: var(--fg-muted);
  border-radius: 999px;
  padding: 3px 9px;
  font-size: 11px;
  cursor: pointer;
}
.strategy-modal-chip.selected {
  background: var(--brand, var(--fg));
  color: var(--bg);
  border-color: transparent;
}
/* Per-agent avatar — two PNGs per agent, one shown per active
   theme. The artwork is the foreground; we deliberately don't
   crop with a circle, so the full silhouette + scientific-prop
   detail (vials, instruments) reads at popover scale. */
.agent-avatar {
  width: 56px;
  height: 56px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.agent-avatar img {
  width: 100%;
  height: 100%;
  object-fit: contain;  /* don't crop the artwork */
  display: block;
}
[data-theme="dark"] .agent-avatar .avatar-light { display: none; }
[data-theme="light"] .agent-avatar .avatar-dark { display: none; }
/* Default to dark variant when neither attribute is set (the
   platform's default theme matches the OS). */
.agent-avatar .avatar-dark { display: block; }
.agent-avatar .avatar-light { display: none; }
[data-theme="light"] .agent-avatar .avatar-light { display: block; }
[data-theme="light"] .agent-avatar .avatar-dark { display: none; }

.agent-meta {
  min-width: 0;  /* permits ellipsis on overflow inside the grid cell */
}
.agent-popover-row:last-child { border-bottom: 0; }
.agent-popover-row .name {
  font-size: 13px; font-weight: 600; color: var(--fg);
  display: flex; align-items: center; gap: 6px;
}
.agent-popover-row .role { font-size: 11.5px; color: var(--fg-muted); margin-top: 2px; }
.agent-popover-row .badge {
  font-family: var(--mono); font-size: 9.5px; font-weight: 600;
  letter-spacing: 0.04em; padding: 1px 5px; border-radius: 999px;
  background: var(--bg-soft); color: var(--fg-muted);
  border: 1px solid var(--line); text-transform: uppercase;
}
.agent-popover-row .badge.block {
  color: #d24545;
  border-color: color-mix(in oklab, var(--line) 50%, #d24545 50%);
}
/* Per-agent model picker — custom dropdown so we control vendor
   headers (small-caps accent text, not the OS grey-italic), the
   row hover / current / disabled states, and the vision badge.
   The picker is two pieces: a trigger pill (always visible on
   the right of the agent name) and an absolutely-positioned
   menu that drops beneath the pill on click. The wrapper is
   ``position: relative`` so the menu anchors to the pill, not
   to the page. */
.agent-model-pill {
  position: relative;
  justify-self: end;          /* hug the right edge of the grid cell */
}

.agent-model-trigger {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  max-width: 220px;            /* keep the row tidy when display names are long */
  padding: 5px 10px 5px 12px;
  background: var(--bg-soft, var(--bg));
  border: 1px solid var(--line);
  border-radius: 999px;
  cursor: pointer;
  color: var(--fg);
  font: inherit;
  font-size: 12px;
  font-weight: 500;
  white-space: nowrap;
  transition: border-color 0.15s ease, background 0.15s ease;
}
/* Gate hover styling to devices with real hover capability so iOS
   taps don't leave a sticky highlight on the picked-model pill. */
@media (hover: hover) {
  .agent-model-trigger:hover {
    border-color: var(--fg-subtle);
    background: var(--bg);
  }
}
.agent-model-trigger[aria-expanded="true"] {
  border-color: var(--brand, currentColor);
  background: var(--bg);
}
.agent-model-label {
  overflow: hidden;
  text-overflow: ellipsis;
}
.agent-model-chev {
  display: inline-flex;
  align-items: center;
  color: var(--fg-muted);
  transition: transform 0.15s ease;
}
.agent-model-trigger[aria-expanded="true"] .agent-model-chev {
  transform: rotate(180deg);
  color: var(--fg);
}

/* The dropdown menu itself. Right-aligned to the trigger so the
   menu hangs off the right edge of the picker (and not off the
   side of the popover). */
.agent-model-menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 80;
  min-width: 240px;
  max-height: 360px;
  overflow-y: auto;
  padding: 6px 0;
  background: var(--bg, #fff);
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18),
              0 2px 6px rgba(0, 0, 0, 0.10);
  font-family: var(--font, inherit);
}
[data-theme="dark"] .agent-model-menu {
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.55),
              0 2px 6px rgba(0, 0, 0, 0.35);
}

.agent-model-section {
  padding: 4px 0 4px;
}
.agent-model-sep {
  height: 1px;
  background: var(--line);
  margin: 4px 12px;
  opacity: 0.6;
}

/* Vendor header — small caps, accent colour, definitely NOT the
   greyed-italic that <optgroup> gave us. */
.agent-model-vendor {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--brand, var(--fg));
  padding: 6px 14px 4px;
  user-select: none;
}

/* One row per model. Bullet marker prefixed via ::before so
   available vs disabled is scannable at a glance:
   * available rows  → solid bullet
   * disabled rows   → hollow ring + muted text
   * current row     → no separate brand-colour text (would clash
                       with the vendor header's brand colour);
                       a subtle filled background + a tick on the
                       right carry the "this is the active pick"
                       signal without conflating with the header. */
.agent-model-option {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 7px 14px 7px 28px;
  position: relative;
  background: transparent;
  border: 0;
  text-align: left;
  font: inherit;
  font-size: 13px;
  color: var(--fg);
  cursor: pointer;
}
.agent-model-option::before {
  content: "";
  position: absolute;
  left: 14px;
  top: 50%;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  transform: translateY(-50%);
  background: var(--fg-muted);
  opacity: 0.85;
}
.agent-model-option.is-disabled::before {
  background: transparent;
  border: 1px solid var(--fg-muted);
  opacity: 0.45;
}
.agent-model-option:hover:not(:disabled) {
  background: var(--bg-soft, rgba(0, 0, 0, 0.04));
}
[data-theme="dark"] .agent-model-option:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.05);
}
.agent-model-option.is-current {
  /* Subtle filled background + the trailing tick are the cues;
     text stays default-colour so it doesn't compete with the
     brand-coloured vendor headers. */
  background: color-mix(in oklab, var(--brand, #6b7280) 8%, transparent);
  font-weight: 600;
}
.agent-model-option.is-current::before {
  background: var(--brand, currentColor);
  opacity: 1;
}
.agent-model-option.is-disabled {
  color: var(--fg-muted);
  cursor: not-allowed;
  opacity: 0.6;
}
.agent-model-option-name {
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.agent-model-tick {
  flex: 0 0 auto;
  color: var(--brand, currentColor);
  display: inline-flex;
  align-items: center;
}

/* Small "vision" tag — typeset to fit the row, no emoji clutter. */
.agent-model-tag {
  flex: 0 0 auto;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 1px 6px;
  border-radius: 4px;
  background: color-mix(in oklab, var(--brand, #6b7280) 14%, transparent);
  color: var(--brand, var(--fg));
}
.agent-popover-foot {
  display: flex; align-items: center; justify-content: space-between;
  padding: 8px 14px; font-size: 11.5px; color: var(--fg-muted);
}
.agent-popover-reset {
  font-size: 11.5px; background: transparent; border: 0;
  color: var(--fg-muted); cursor: pointer;
  padding: 4px 8px; border-radius: 6px;
}
.agent-popover-reset:hover { background: var(--bg-soft); color: var(--fg); }

/* Skills + Tools + Workflows popovers reuse the .agent-popover
   frame; their per-row layout differs (Skills by agent, Tools by
   category, Workflows by Run Plan) but the visual pattern is
   shared. */
.skills-popover,
.tools-popover,
.workflows-popover {
  width: 380px;
}

/* ----- Workflows popover polish — chrome, scrollbar, animation -----
   Targeted at .workflows-popover only so the agents / skills / tools
   popovers keep their existing visual idiom. */
.workflows-popover {
  width: 392px;
  border-radius: 16px;
  box-shadow:
    0 24px 60px -20px color-mix(in oklab, #000 60%, transparent),
    0 4px 14px -4px color-mix(in oklab, #000 30%, transparent),
    var(--shadow-lg);
  animation: workflows-popover-in 0.18s ease-out;
}
@keyframes workflows-popover-in {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
/* Header — same shape as #agent-popover's header: title pinned
   left, search/filter pinned right via grid-column: 3, padding
   explicitly equal on both sides so the gap from popover-left to
   the title text matches the gap from the search's right border
   to popover-right. */
.workflows-popover .agent-popover-head {
  grid-template-columns: auto 1fr auto;
  padding-left: 16px;
  padding-right: 16px;
}
.workflows-popover .agent-popover-head > .agent-popover-title {
  text-align: left;
  justify-self: start;
  margin: 0;
  padding: 0;
}
.workflows-popover .agent-popover-head > .agent-popover-search {
  grid-column: 3;
  justify-self: end;
  margin: 0;
}
/* List — softer dividers, tighter scrollbar, smooth-scroll. */
.workflows-popover .agent-popover-list {
  border-top: 1px solid color-mix(in oklab, var(--line) 60%, transparent);
  border-bottom: 1px solid color-mix(in oklab, var(--line) 60%, transparent);
  scrollbar-width: thin;
  scrollbar-color: color-mix(in oklab, var(--fg-subtle) 35%, transparent) transparent;
  scroll-behavior: smooth;
}
.workflows-popover .agent-popover-list::-webkit-scrollbar { width: 8px; }
.workflows-popover .agent-popover-list::-webkit-scrollbar-thumb {
  background: color-mix(in oklab, var(--fg-subtle) 30%, transparent);
  border-radius: 8px;
  border: 2px solid transparent;
  background-clip: padding-box;
}
.workflows-popover .agent-popover-list::-webkit-scrollbar-thumb:hover {
  background: color-mix(in oklab, var(--fg-subtle) 50%, transparent);
}
.workflows-popover .agent-popover-list::-webkit-scrollbar-track {
  background: transparent;
}

/* Header search — sits in the same right-side slot as the agent
   popover's strategy pill and uses the exact same chrome
   (.agent-strategy-trigger). Starts compact (icon-only); the
   input field expands smoothly on focus so the trigger reads as
   a pill at rest and a search field when in use. */
.agent-popover-search {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--bg-soft);
  color: var(--fg-muted);
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 600;
  cursor: text;
  max-width: 100%;
  white-space: nowrap;
}
.agent-popover-search:hover {
  border-color: color-mix(in oklab, var(--line) 60%, var(--fg-muted) 40%);
  color: var(--fg);
}
.agent-popover-search:focus-within {
  /* Same neutral active-state colour as the protocol pill's
     [aria-expanded="true"] — no brand-blue accent. */
  border-color: var(--accent, color-mix(in oklab, var(--fg) 30%, var(--bg) 70%));
  color: var(--fg);
}
.agent-popover-search input {
  background: transparent;
  border: 0;
  outline: none;
  color: inherit;
  font: inherit;
  font-weight: 500;
  /* Compact at rest — only the icon shows. Expands when the
     trigger is focused or already has a query. */
  width: 0;
  min-width: 0;
  padding: 0;
  transition: width 0.18s ease;
}
.agent-popover-search:focus-within input,
.agent-popover-search input:not(:placeholder-shown) {
  width: 110px;
}
.agent-popover-search input::placeholder { color: var(--fg-subtle); }
.agent-popover-search input::-webkit-search-cancel-button { display: none; }

/* Workflow row — same shape as the protocol picker: one text cell
   with name above blurb. Selected row gets a subtle background
   tint. */
.workflow-row {
  grid-template-columns: 1fr;
  cursor: pointer;
}
/* Selection tint — same formula as the protocol picker's
   .agent-strategy-row.current so the two pickers feel like the
   same component. */
.workflow-row.is-selected {
  background: color-mix(in oklab, var(--bg-soft) 70%, var(--brand) 18%);
}
.workflow-row .workflow-icon {
  /* Reuse .agent-avatar dimensions but draw a small bg/ring so the
     line-art SVG reads against the popover background, similar to
     the workflow icons' previous treatment. */
  background: var(--bg-input);
  border-radius: 12px;
  box-shadow: inset 0 0 0 1px var(--line);
  color: var(--brand);
}
.workflow-row .workflow-icon svg {
  width: 28px;
  height: 28px;
}
.workflow-row .workflow-meta {
  min-width: 0;
}
.workflow-row .workflow-meta .name {
  font-weight: 600;
  color: var(--fg);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.workflow-row .workflow-meta .role {
  font-size: 11.5px;
  color: var(--fg-muted);
  margin-top: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.workflow-row.workflow-empty {
  color: var(--fg-muted);
  font-style: italic;
  text-align: center;
  display: block;
  padding: 24px 12px;
  border-bottom: 0;
}

/* Section header row — same idiom as the protocol picker's
   .agent-strategy-section: bold main + muted sub on the left,
   rotating chevron on the right. Re-uses the .agent-popover-row
   skeleton so it sits flush with the regular workflow rows. */
.workflow-section {
  grid-template-columns: 1fr auto;
  cursor: pointer;
}
.workflow-row.workflow-section-toggle {
  grid-template-columns: 1fr auto;
}
.workflow-section:hover { background: var(--bg-soft); }
.workflow-section .workflow-section-meta {
  min-width: 0;
}
.workflow-section .workflow-section-meta .name {
  font-size: 13px;
  font-weight: 600;
  color: var(--fg);
}
.workflow-section .workflow-section-meta .role {
  font-size: 11.5px;
  color: var(--fg-muted);
  margin-top: 2px;
}
.workflow-section .workflow-section-chev,
.workflow-section-toggle .workflow-section-chev {
  display: inline-flex;
  align-items: center;
  color: var(--fg-muted);
  transition: transform 0.15s ease;
}
.workflow-section[aria-expanded="true"] .workflow-section-chev,
.workflow-section-toggle[aria-expanded="true"] .workflow-section-chev {
  transform: rotate(180deg);
}
/* (Section items used to carry an indent suggesting they were
   children of the selected row. Removed — workflows in an
   expanded section are peers of the section header, not
   subcategories of any other row.) */

/* "+ Add a new one…" footer row — same role as the protocol
   picker's add affordance. Brand-coloured, no icon, centred
   text — reads as an action, not an option. */
.workflow-row.workflow-add {
  display: block;
  text-align: center;
  color: var(--brand, var(--fg));
  font-weight: 600;
  font-size: 13px;
  padding: 12px;
  cursor: pointer;
}
.workflow-row.workflow-add:hover { background: var(--bg-soft); }
/* Position each composer popover above its own trigger button.
   The composer's left bar lays the icons out in order
   Attach → Agent → Protocols → Workflows → Skills → Tools, so each
   popover slides further right. .composer-icon-btn is 36px wide with a
   4px gap, so successive triggers are 40px apart.
   #protocols-popover shares the .workflows-popover class for its chrome,
   so its id selector overrides the class `left` to sit at its own (96px)
   button rather than Workflows' (136px). */
#protocols-popover { left: 96px; }
.workflows-popover { left: 136px; }
.skills-popover { left: 176px; }
.tools-popover { left: 216px; }

/* Skills / Tools popovers — same compact picker idiom as Workflows:
   header search pill, collapsible catalogue sections, text rows,
   add row, and reset-only footer. */
.tools-popover,
.skills-popover,
.workflows-popover {
  max-height: 520px;
  display: flex;
  flex-direction: column;
}
.tools-popover .agent-popover-head,
.skills-popover .agent-popover-head,
.workflows-popover .agent-popover-head { flex-shrink: 0; }

.skills-popover .agent-popover-head,
.tools-popover .agent-popover-head {
  grid-template-columns: auto 1fr auto;
  padding-left: 16px;
  padding-right: 16px;
}
.skills-popover .agent-popover-head > .agent-popover-title,
.tools-popover .agent-popover-head > .agent-popover-title {
  text-align: left;
  justify-self: start;
  margin: 0;
  padding: 0;
}
.skills-popover .agent-popover-head > .agent-popover-search,
.tools-popover .agent-popover-head > .agent-popover-search {
  grid-column: 3;
  justify-self: end;
  margin: 0;
}
.skills-popover .agent-popover-list,
.tools-popover .agent-popover-list {
  border-top: 1px solid color-mix(in oklab, var(--line) 60%, transparent);
  border-bottom: 1px solid color-mix(in oklab, var(--line) 60%, transparent);
  scrollbar-width: thin;
  scrollbar-color: color-mix(in oklab, var(--fg-subtle) 35%, transparent) transparent;
  scroll-behavior: smooth;
}
.skills-popover .agent-popover-list::-webkit-scrollbar,
.tools-popover .agent-popover-list::-webkit-scrollbar { width: 8px; }
.skills-popover .agent-popover-list::-webkit-scrollbar-thumb,
.tools-popover .agent-popover-list::-webkit-scrollbar-thumb {
  background: color-mix(in oklab, var(--fg-subtle) 30%, transparent);
  border-radius: 8px;
  border: 2px solid transparent;
  background-clip: padding-box;
}
.skills-popover .agent-popover-list::-webkit-scrollbar-thumb:hover,
.tools-popover .agent-popover-list::-webkit-scrollbar-thumb:hover {
  background: color-mix(in oklab, var(--fg-subtle) 50%, transparent);
}
.skills-popover .agent-popover-list::-webkit-scrollbar-track,
.tools-popover .agent-popover-list::-webkit-scrollbar-track {
  background: transparent;
}

.skill-row,
.tool-row {
  grid-template-columns: 1fr;
  cursor: default;
}
.skill-row .skill-meta,
.tool-row .tool-meta {
  min-width: 0;
}
.skill-row .skill-meta .name,
.tool-row .tool-meta .name {
  font-weight: 600;
  color: var(--fg);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.skill-row .skill-meta .role,
.tool-row .tool-meta .role {
  font-size: 11.5px;
  color: var(--fg-muted);
  margin-top: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.skill-empty,
.tool-empty {
  color: var(--fg-muted);
  font-style: italic;
  text-align: center;
  display: block;
  padding: 24px 12px;
  border-bottom: 0;
}

.skill-section,
.tool-section {
  grid-template-columns: 1fr auto;
  cursor: pointer;
}
.skill-row.skill-section-toggle,
.tool-row.tool-section-toggle {
  grid-template-columns: 1fr auto;
}
.skill-section:hover,
.tool-section:hover { background: var(--bg-soft); }
.skill-section .skill-section-meta,
.tool-section .tool-section-meta {
  min-width: 0;
}
.skill-section .skill-section-meta .name,
.tool-section .tool-section-meta .name {
  font-size: 13px;
  font-weight: 600;
  color: var(--fg);
}
.skill-section .skill-section-meta .role,
.tool-section .tool-section-meta .role {
  font-size: 11.5px;
  color: var(--fg-muted);
  margin-top: 2px;
}
.skill-section .skill-section-chev,
.skill-section-toggle .skill-section-chev,
.tool-section .tool-section-chev,
.tool-section-toggle .tool-section-chev {
  display: inline-flex;
  align-items: center;
  color: var(--fg-muted);
  transition: transform 0.15s ease;
}
.skill-section[aria-expanded="true"] .skill-section-chev,
.skill-section-toggle[aria-expanded="true"] .skill-section-chev,
.tool-section[aria-expanded="true"] .tool-section-chev,
.tool-section-toggle[aria-expanded="true"] .tool-section-chev {
  transform: rotate(180deg);
}
/* Items belonging to a section (legacy built-ins under their
   expanded toggle, user-defined items under their header) get a
   small left indent and a slightly muted name so the hierarchy
   between section header and section children is legible. Without
   this, every row in the picker has identical styling and the
   "Legacy" / "User-defined" headers blend in with their items.
   Same rule applied across tools, skills, and workflows pickers. */
.tool-row.tool-section-item,
.skill-row.skill-section-item,
.workflow-row.workflow-section-item {
  padding-left: 22px;
}
.tool-row.tool-section-item .tool-meta .name,
.skill-row.skill-section-item .skill-meta .name,
.workflow-row.workflow-section-item .workflow-meta .name {
  font-weight: 500;
}
.skill-row.skill-add,
.tool-row.tool-add {
  display: block;
  text-align: center;
  color: var(--brand, var(--fg));
  font-weight: 600;
  font-size: 13px;
  padding: 12px;
  cursor: pointer;
}
.skill-row.skill-add:hover,
.tool-row.tool-add:hover { background: var(--bg-soft); }

/* Workflows popover row — text-only row matching the protocol
   picker. Icons remain defined for catalog compatibility but are
   not rendered in this picker. */
.workflow-row {
  display: grid;
  grid-template-columns: 1fr;
  align-items: center;
  gap: 14px;
  padding: 11px 16px;
  border-bottom: 1px solid color-mix(in oklab, var(--line) 70%, transparent);
  cursor: pointer;
  transition: background 0.14s ease;
  position: relative;
}
.workflow-row:last-child { border-bottom: 0; }
.workflow-row:hover {
  background: color-mix(in oklab, var(--bg-soft) 65%, transparent);
}
.workflow-row.workflow-section-toggle {
  grid-template-columns: 1fr auto;
}
.workflow-row:hover .workflow-icon {
  /* Lift the icon a touch on hover — subtle, not selection-grade. */
  filter: brightness(1.06);
}
/* Icon: subtle duotone surface with a soft inset to give the
   popover a more polished, less utilitarian feel without crossing
   into "tinted = selected" territory. The brand colour appears
   only inside the SVG strokes, never on the surface. */
.workflow-row .workflow-icon {
  width: 48px;
  height: 48px;
  border-radius: 11px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(
    180deg,
    color-mix(in oklab, var(--bg-soft) 78%, var(--bg) 22%) 0%,
    var(--bg-soft) 100%
  );
  color: var(--brand);
  flex-shrink: 0;
  box-shadow:
    inset 0 0 0 1px color-mix(in oklab, var(--line) 80%, transparent),
    0 1px 0 color-mix(in oklab, var(--bg) 70%, transparent);
  transition: filter 0.14s ease;
}
.workflow-row .workflow-icon svg {
  width: 26px;
  height: 26px;
  stroke-width: 1.6;
}
.workflow-row .workflow-meta {
  min-width: 0;
}
/* Title line — name + small status chip inline so the chip never
   competes with the radio button on the right. */
.workflow-row .workflow-name-line {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.workflow-row .workflow-name {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--fg);
  letter-spacing: -0.005em;
}
.workflow-row .workflow-blurb {
  font-size: 11.5px;
  color: var(--fg-muted);
  margin-top: 3px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  line-height: 1.35;
}
/* Status chip — LIVE gets a leading dot to read as a heartbeat;
   PLANNED stays as a quiet uppercase caption so the catalogue
   reads as a roadmap, not a horse race. */
.workflow-row .workflow-status {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-family: var(--mono);
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.06em;
  padding: 2px 8px;
  border-radius: 999px;
  background: color-mix(in oklab, var(--bg-soft) 50%, transparent);
  color: var(--fg-muted);
  border: 1px solid color-mix(in oklab, var(--line) 80%, transparent);
  text-transform: uppercase;
}
.workflow-row .workflow-status::before {
  content: "";
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.6;
  flex-shrink: 0;
}
.workflow-row .workflow-status.live {
  color: color-mix(in oklab, var(--fg) 25%, var(--brand) 75%);
  border-color: color-mix(in oklab, var(--line) 40%, var(--brand) 55%);
  background: color-mix(in oklab, var(--bg-soft) 60%, var(--brand) 10%);
}
.workflow-row .workflow-status.live::before {
  background: var(--brand);
  opacity: 1;
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--brand) 22%, transparent);
  animation: workflow-status-pulse 1.8s ease-in-out infinite;
}
@keyframes workflow-status-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.55; }
}

/* Radio indicator — the ONE visual signal of selection per the
   "row stays neutral" contract. Smooth grow on activate. */
.workflow-radio {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 1.5px solid color-mix(in oklab, var(--fg-subtle) 90%, transparent);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--bg);
  flex-shrink: 0;
  transition: border-color 0.16s ease, background 0.16s ease, box-shadow 0.16s ease;
}
.workflow-row:hover .workflow-radio {
  border-color: color-mix(in oklab, var(--fg-subtle) 60%, var(--brand) 40%);
}
.workflow-radio::after {
  content: "";
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: transparent;
  transition: background 0.16s ease, transform 0.16s ease;
  transform: scale(0.4);
}
.workflow-row.selected .workflow-radio {
  border-color: var(--brand);
  background: color-mix(in oklab, var(--bg) 65%, var(--brand) 8%);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--brand) 14%, transparent);
}
.workflow-row.selected .workflow-radio::after {
  background: var(--brand);
  transform: scale(1);
}

/* Templates parent row — uses <details> like the Skills/Tools
   per-agent groups but with our taller workflow row inside the
   <summary> so the hierarchy reads visually consistent. */
.workflow-group {
  border-bottom: 1px solid var(--line);
}
.workflow-group:last-child { border-bottom: 0; }
.workflow-group > summary {
  list-style: none;
  list-style-type: none;
  cursor: pointer;
  padding: 0;
  /* Belt + suspenders: ``display: block`` suppresses the default
     ::marker on Firefox/Chrome, the explicit ``::marker { content:
     none }`` covers spec-strict engines, and the WebKit-specific
     pseudo-element rule kills the legacy Safari triangle. ``::before``
     is also forced off in case a parent rule injects one. The cdot
     the user reported was a leftover Safari/iOS marker that this
     stack now drops on every engine. */
  display: block;
}
.workflow-group > summary::-webkit-details-marker {
  display: none !important;
  content: none !important;
}
.workflow-group > summary::marker {
  content: none !important;
  display: none !important;
}
.workflow-group > summary::before { content: none !important; }
/* The chevron lives inside the radio column slot when the row is a
   group; rotates on open. */
.workflow-group > summary .workflow-row {
  border-bottom: 0;
  padding-right: 16px;
}
.workflow-group .workflow-chevron {
  width: 22px;
  height: 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--fg-subtle);
  transition: transform 0.18s ease, color 0.18s ease;
  border-radius: 50%;
}
.workflow-group[open] .workflow-chevron {
  transform: rotate(90deg);
  color: var(--fg);
  background: color-mix(in oklab, var(--bg-soft) 70%, transparent);
}
/* Children of the Templates group — quietly recessed background +
   a thin neutral left rail so the hierarchy reads clearly without
   competing with the brand colour reserved for selection. */
.workflow-group > .workflow-children {
  background: color-mix(in oklab, var(--bg-elevated) 90%, var(--bg-soft) 10%);
  border-top: 1px solid color-mix(in oklab, var(--line) 70%, transparent);
  position: relative;
}
.workflow-group > .workflow-children::before {
  content: "";
  position: absolute;
  left: 26px;
  top: 6px;
  bottom: 6px;
  width: 2px;
  border-radius: 2px;
  background: color-mix(in oklab, var(--line) 75%, transparent);
}
.workflow-group > .workflow-children .workflow-row {
  padding-left: 36px;  /* clears the left rail + breathes */
}
.workflow-group > .workflow-children .workflow-row:last-child {
  border-bottom: 0;
}
/* Subhead inside the Templates group separating "main" from
   "advanced" campaigns. Quiet caps caption with a hairline rule
   above so it reads as a section break, not a button. */
.workflow-subhead {
  padding: 8px 16px 6px 36px;
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--fg-subtle);
  background: transparent;
  border-top: 1px solid color-mix(in oklab, var(--line) 60%, transparent);
  border-bottom: 0;
}
.workflow-subhead:first-child {
  border-top: 0;
}

.agent-popover-group {
  border-bottom: 1px solid var(--line);
}
.agent-popover-group:last-child { border-bottom: 0; }
.agent-popover-group > summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 14px;
  font-weight: 600;
  font-size: 12.5px;
  color: var(--fg);
}
.agent-popover-group > summary::-webkit-details-marker { display: none; }
.agent-popover-group > summary::before {
  content: "▸";
  display: inline-block;
  width: 14px;
  color: var(--fg-subtle);
  transition: transform 0.15s ease;
}
.agent-popover-group[open] > summary::before { transform: rotate(90deg); }
.agent-popover-group > summary .group-meta {
  font-family: var(--mono);
  font-weight: 400;
  font-size: 11px;
  color: var(--fg-muted);
}
.agent-popover-group > summary .group-blurb {
  font-weight: 400;
  font-size: 11px;
  color: var(--fg-muted);
  margin-left: 8px;
  flex: 1 1 auto;
  text-align: right;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.agent-popover-leaf {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 8px;
  padding: 6px 14px 6px 32px;
  font-size: 12.5px;
  color: var(--fg);
}
.agent-popover-leaf input[type="checkbox"] {
  accent-color: var(--brand);
  cursor: pointer;
}
.agent-popover-leaf .leaf-id {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--fg);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.agent-popover-leaf.disabled {
  opacity: 0.5;
}
.agent-popover-leaf.disabled .leaf-id {
  text-decoration: line-through;
}

/* Agent-membership chips on the Tools popover rows. Each chip is
   the agent's display-name initial; tooltip carries the full name
   (set via the title attribute). */
.agent-chips {
  display: inline-flex;
  gap: 3px;
}
.agent-chip {
  font-family: var(--mono);
  font-size: 9.5px;
  font-weight: 600;
  width: 16px;
  height: 16px;
  border-radius: 999px;
  background: var(--bg-soft);
  border: 1px solid var(--line);
  color: var(--fg-muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.composer {
  background: var(--bg-input);
  border: 1px solid transparent;
  border-radius: 28px;
  padding: 14px 16px 10px;
  transition: background 0.15s, border-color 0.15s;
  box-shadow: var(--shadow-sm);
}
.composer:focus-within {
  background: var(--bg-input-focus);
  border-color: var(--line);
}

.composer-input {
  width: 100%;
  background: transparent;
  border: none;
  outline: none;
  resize: none;
  color: var(--fg);
  /* 16px is the iOS Safari threshold: any text input below 16px
     auto-zooms the viewport on focus, and the zoom doesn't reset
     cleanly on blur, leaving the whole page magnified. Hold the
     line at 16px so focus + blur are no-ops on the viewport
     scale. The earlier 14px sizing matched the placeholder
     better but the zoom side-effect is the worse defect. */
  font-size: 16px;
  line-height: 1.5;
  max-height: 220px;
  min-height: 22px;
  padding: 0;
  overflow-y: auto;
}
.composer-input::placeholder { color: var(--fg-subtle); }

/* Attached-files row sits above the textarea; renders one chip per
   uploaded evidence with a remove button. Hidden by default — the
   JS toggles `hidden` based on whether any chips exist. */
.attach-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 8px;
}
.attach-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 4px 4px 10px;
  border-radius: 999px;
  background: var(--brand-soft);
  color: var(--brand);
  font-size: 12.5px;
  max-width: 100%;
}
.attach-chip[data-status="failed"] {
  background: rgba(220, 38, 38, 0.10);
  color: var(--label-RESTRICTED);
}
.attach-chip[data-status="pending"] {
  opacity: 0.7;
}
.attach-chip-name {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 220px;
}
.attach-chip-size {
  font-size: 11px;
  opacity: 0.7;
  font-variant-numeric: tabular-nums;
}
.attach-chip-remove {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  color: inherit;
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
}
.attach-chip-remove:hover { background: rgba(0,0,0,0.10); }

/* Per-turn attachment chips inside a user bubble — rendered above the
   query text, same shape as the composer chips minus the remove
   button. User bubbles are right-aligned, so chips stack to the right
   to track the bubble edge. */
.msg-attachments {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 6px;
}
.msg.user .msg-attachments {
  justify-content: flex-end;
}
.msg-attach-chip {
  padding-right: 10px;
  cursor: default;
}

.composer-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 10px;
  gap: 8px;
}
.composer-left { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.composer-right { display: flex; align-items: center; gap: 4px; }

/* Round, transparent action buttons inside the composer (left + right). */
.composer-icon-btn {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--brand);
  background: transparent;
  transition: background 0.15s, color 0.15s;
  flex-shrink: 0;
}
/* Hover keeps the brand colour and only changes the background, so
   the icon doesn't desaturate while the user is interacting. */
.composer-icon-btn:hover { background: var(--bg-hover); }
.composer-icon-btn:active { transform: scale(0.95); }
/* Protocols button: a soft-brand pill behind the icon while a
   non-default collaboration protocol is selected. The icon is already
   brand-coloured, so the tinted background — not a colour change — is
   what signals "active", mirroring the terminal toggle. It replaces the
   old "Auto ▾" pill that used to name the choice in the Agents header. */
#open-protocols.is-active { background: var(--brand-soft); }
/* Active recording state for the dictate + voice buttons. A soft
   red tint + slow pulse so the user can tell at a glance which
   button is currently capturing audio. The pulse uses CSS only
   (no JS animation loop) so it costs nothing when no button is
   recording. */
.composer-icon-btn.is-recording {
  color: var(--danger, #ef4444);
  background: rgba(239, 68, 68, 0.12);
  animation: composer-rec-pulse 1.5s ease-in-out infinite;
}
@keyframes composer-rec-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.30); }
  50%      { box-shadow: 0 0 0 6px rgba(239, 68, 68, 0.00); }
}

/* Model picker — sits in composer-right, pill trigger + dropdown menu */
.model-picker { position: relative; }
.model-trigger {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 8px 6px 10px;
  border-radius: 999px;
  border: 1px solid var(--line);
  color: var(--fg-muted);
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.model-trigger:hover { background: var(--bg-hover); color: var(--fg); border-color: var(--line-strong); }
/* Trigger glyph is a swappable wrapper so JS can rewrite the inner SVG
   to match the currently-selected model (Auto/Local/Cloud). Brand-
   coloured to match the rest of the icon family. */
.model-trigger .model-trigger-glyph {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--brand);
}

.model-menu {
  position: absolute;
  bottom: calc(100% + 8px);
  right: 0;
  width: 320px;
  background: var(--bg-elevated);
  border: 1px solid var(--line);
  border-radius: 14px;
  padding: 6px;
  box-shadow: var(--shadow-lg);
  z-index: 50;
}
.model-menu-header {
  font-family: var(--mono);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.8px;
  color: var(--fg-subtle);
  padding: 8px 10px 4px;
}
.model-section-label {
  font-size: 11px;
  color: var(--fg-subtle);
  padding: 8px 10px 4px;
  letter-spacing: 0.2px;
}
.model-option {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 9px 10px;
  border-radius: 10px;
  text-align: left;
  transition: background 0.12s;
}
.model-option:hover { background: var(--bg-hover); }
/* Selected row uses the same neutral hover background instead of a
   brand tint, so the picker reads consistently with the rest of the
   composer. */
.model-option.selected { background: var(--bg-hover); }
.model-option .model-glyph {
  width: 28px; height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  color: var(--brand);
  border-radius: 8px;
  background: var(--bg-input);
}
.model-option.selected .model-glyph { color: var(--brand-strong); }
.model-option .opt-text { display: flex; flex-direction: column; gap: 1px; flex: 1; min-width: 0; }
.model-option .opt-title {
  font-size: 13px;
  color: var(--fg);
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.model-option .opt-desc {
  font-size: 11.5px;
  color: var(--fg-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.model-option.disabled { opacity: 0.55; cursor: not-allowed; }
.model-option.disabled:hover { background: transparent; }

.model-menu-divider {
  height: 1px;
  background: var(--line);
  margin: 6px 4px;
}
.model-option.model-configure .model-glyph { background: transparent; }

/* Transient toast for composer placeholder buttons. */
.composer-toast {
  position: fixed;
  bottom: 28px;
  left: 50%;
  transform: translate(-50%, 8px);
  background: var(--bg-elevated);
  color: var(--fg);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-lg);
  padding: 9px 14px;
  border-radius: 999px;
  font-size: 13px;
  opacity: 0;
  pointer-events: none;
  z-index: 200;
  transition: opacity 0.18s var(--ease), transform 0.18s var(--ease);
}
.composer-toast.show { opacity: 1; transform: translate(-50%, 0); }

.opt-text { display: flex; flex-direction: column; gap: 1px; }
.opt-title { font-size: 13px; color: var(--fg); font-weight: 500; }
.opt-desc { font-size: 11.5px; color: var(--fg-muted); }

.dot {
  width: 9px; height: 9px;
  border-radius: 50%;
  flex-shrink: 0;
  display: inline-block;
}
.dot.small { width: 7px; height: 7px; }
.dot-PUBLIC { background: var(--label-PUBLIC); }
.dot-SYNTHETIC { background: var(--label-SYNTHETIC); }
.dot-SANITIZED { background: var(--label-SANITIZED); }
.dot-CONFIDENTIAL { background: var(--label-CONFIDENTIAL); }
.dot-RESTRICTED { background: var(--label-RESTRICTED); }

/* Send button — small disc that hints "Enter to submit" but uses
   the same brand-colour glyph as the surrounding composer icons
   (mic / audio / video) so the row reads as one icon family. The
   disc itself is a soft neutral pill (`--bg-input`) so the brand
   arrow stands out against it. 24px disc inside the 36px button
   frame matches the topbar avatar's sizing. */
.send-btn {
  width: 28px; height: 28px;
  border-radius: 50%;
  background: var(--brand-soft);
  color: var(--brand);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s, color 0.15s, transform 0.1s;
  flex-shrink: 0;
}
.send-btn:hover:not(:disabled) { background: var(--brand); color: #fff; }
.send-btn:active:not(:disabled) { transform: scale(0.95); }
.send-btn:disabled { background: var(--bg-input); color: var(--fg-subtle); cursor: not-allowed; }
/* Stop mode while a request is in flight. Same shape; foreground
   colour swap reads as "active, click to cancel" without the
   alarm of red. */
.send-btn.send-btn-stop {
  background: var(--fg);
  color: var(--bg);
}
.send-btn.send-btn-stop:hover { background: var(--fg-muted); }

/* ========================================================================
   User menu (anchored bottom-left)
   ======================================================================== */

.popover {
  position: fixed;
  background: var(--bg-elevated);
  border: 1px solid var(--line);
  border-radius: 12px;
  box-shadow: var(--shadow-lg);
  z-index: 110;
  animation: pop-in 0.15s ease;
}
@keyframes pop-in {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}

.user-menu {
  top: 56px;
  right: 18px;
  width: 280px;
  padding: 8px;
}

/* Custom attach menu (composer paperclip → #attach-menu). Reuses
   the shared .popover + .menu-item system so it matches the avatar
   / agent menus in font, colour, and light/dark; JS sets top/left
   to anchor it above the paperclip. */
.attach-menu {
  width: 248px;
  padding: 6px;
}
.user-menu-header {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 6px;
}
.user-name { font-size: 14px; font-weight: 600; color: var(--fg); }
.user-tag { font-size: 11px; color: var(--fg-subtle); margin-top: 2px; }

.menu-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px 10px;
  border-radius: 8px;
  font-family: var(--sans);
  font-size: 14px;
  font-weight: 400;
  line-height: 1.4;
  color: var(--fg);
  transition: background 0.12s;
  text-align: left;
}
.menu-item:hover { background: var(--bg-hover); }
.menu-item svg { color: var(--fg-muted); flex-shrink: 0; }
.attach-menu .menu-item { font-weight: 600; }
.attach-menu .menu-item svg { color: var(--brand); }
.menu-item.danger { color: var(--label-RESTRICTED); }
.menu-item.danger svg { color: var(--label-RESTRICTED); }
.menu-sep { height: 1px; background: var(--line); margin: 6px 0; }

/* Slash-command skills menu (composer "/" → list + filter skills). Body-mounted
   above the textarea; JS sets left/bottom/width. Reuses .popover + .menu-item so
   it follows the theme; only the row layout + active highlight are new. */
.slash-menu { padding: 4px; max-height: 40vh; overflow-y: auto; }
.slash-menu ul { list-style: none; margin: 0; padding: 0; }
.slash-menu .menu-item {
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  cursor: pointer;
}
.slash-menu .slash-name { font-weight: 600; }
.slash-menu .slash-name .slash-id { color: var(--fg-muted); font-weight: 400; }
.slash-menu .slash-desc {
  color: var(--fg-muted);
  font-size: 12px;
  line-height: 1.35;
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.slash-menu .menu-item.active { background: var(--bg-hover); }
.slash-menu .slash-empty { padding: 8px 10px; color: var(--fg-muted); font-size: 13px; }

/* Expandable Help row + its submenu (anchored to the LEFT of the user menu). */
.menu-item-with-sub { position: relative; }
.menu-item-expandable .menu-item-label { flex: 1; }
.menu-item-expandable .menu-item-chev { color: var(--fg-subtle); transition: transform 0.15s; }
.menu-item-with-sub.open > .menu-item-expandable { background: var(--bg-hover); }
.menu-item-with-sub.open > .menu-item-expandable .menu-item-chev { color: var(--fg); }

.help-submenu {
  position: absolute;
  top: -8px;
  right: calc(100% + 8px);
  width: 240px;
  padding: 8px;
  background: var(--bg-elevated);
  border: 1px solid var(--line);
  border-radius: 12px;
  box-shadow: var(--shadow-lg);
  z-index: 110;
  animation: pop-in 0.15s var(--ease) both;
}

/* Top-right Sign in pill (logged out). */
.topbar-signin {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 7px 14px 7px 12px;
  border-radius: 999px;
  background: var(--bg-input);
  color: var(--fg);
  font-size: 14px;
  font-weight: 500;
  transition: background 0.15s, color 0.15s;
}
.topbar-signin:hover { background: var(--bg-hover); }
.topbar-signin svg { color: var(--brand); }
.topbar-signin:hover svg { color: var(--fg); }

/* Top-right avatar button (logged in). Styled like the other
   topbar `.icon-btn` controls (Theme, Share) — 36x36, transparent
   background, hover bg-hover — so the topbar reads as one
   coherent icon row. The inner `.avatar` slot holds either text
   initials (brand-coloured to match the surrounding icons) or a
   photo (filled disc, see `.avatar.has-photo`). */
.topbar-avatar-btn {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: transparent;
  transition: background 0.15s;
}
.topbar-avatar-btn:hover { background: var(--bg-hover); }
/* Inner identity disc — sharp brand-coloured circle with white
   initials, sized to align with the 20px glyphs in the sibling
   icon-btn controls. The 28px disc + 4px pad inside the 36px
   button matches the visual weight of the Theme / Share icons. */
.topbar-avatar-btn .avatar {
  width: 24px;
  height: 24px;
  font-size: 11px;
  font-weight: 600;
  background: var(--brand);
  color: #fff;
  letter-spacing: 0.3px;
}

/* ========================================================================
   Drawers (right + left)
   ======================================================================== */

.drawer-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.45);
  z-index: 80;
  animation: fade-in 0.2s ease;
}
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }

.drawer {
  position: fixed;
  top: 0; right: 0; bottom: 0;
  width: 420px;
  max-width: 100vw;
  background: var(--bg);
  border-left: 1px solid var(--line);
  z-index: 90;
  display: flex;
  flex-direction: column;
  animation: slide-in 0.25s cubic-bezier(0.2, 0.8, 0.2, 1);
  box-shadow: var(--shadow-lg);
}
.drawer.drawer-left {
  left: var(--sidebar-w);
  right: auto;
  border-left: none;
  border-right: 1px solid var(--line);
  width: 320px;
  animation: slide-in-left 0.25s cubic-bezier(0.2, 0.8, 0.2, 1);
  box-shadow: 10px 0 30px rgba(0,0,0,0.2);
}
@keyframes slide-in {
  from { transform: translateX(100%); }
  to { transform: translateX(0); }
}
@keyframes slide-in-left {
  from { transform: translateX(-100%); }
  to { transform: translateX(0); }
}

.drawer-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  padding: 18px 20px 14px;
  border-bottom: 1px solid var(--line);
}
.drawer-header h2 { margin: 0; font-size: 18px; font-weight: 600; }
.drawer-sub { margin: 4px 0 0; font-size: 12px; color: var(--fg-subtle); }
/* When the description sits under the Connectors drawer's tab
   switcher (acting as the active tab's subtitle) it needs its own
   block padding — it's no longer inside the header. */
.conn-mode-desc {
  padding: 10px 16px 14px;
  margin: 0;
  border-bottom: 1px solid var(--line);
}

.drawer-tabs {
  display: flex;
  flex-wrap: wrap;   /* 4th tab (File System) wraps instead of overflowing a narrow drawer */
  gap: 4px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--line);
}
.drawer-tab {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 14px;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 500;
  color: var(--fg-subtle);
  transition: background 0.15s, color 0.15s;
}
.drawer-tab:hover { color: var(--fg-muted); background: var(--bg-hover); }
.drawer-tab.active { background: var(--brand-soft); color: var(--brand); }
.audit-count {
  background: var(--bg-input);
  color: var(--brand);
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 500;
  padding: 1px 7px;
  border-radius: 999px;
}

.drawer-panel {
  flex: 1;
  overflow-y: auto;
  padding: 16px 18px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* File System panel (User Settings → "File System"). Reuses the
   .fs-card / .fs-empty styles from the old page but stacks them in a
   single column sized for the 420px drawer — the page's auto-fill grid
   has a 360px min that would overflow a full-width mobile drawer. */
.drawer-fs-intro {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--fg-muted);
}
.drawer-panel[data-panel="file-system"] .fs-cards {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.drawer-panel[data-panel="file-system"] .fs-empty {
  padding: 32px 18px;
}

.row-toggle {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding: 14px 16px;
  background: var(--bg-soft);
  border: 1px solid var(--line);
  border-radius: 12px;
}
.row-text { flex: 1; }
.row-title { font-size: 14px; font-weight: 600; color: var(--fg); }
.row-desc { font-size: 12px; color: var(--fg-muted); margin-top: 4px; }

.switch {
  position: relative;
  display: inline-block;
  width: 38px;
  height: 22px;
  flex-shrink: 0;
  margin-top: 2px;
}
.switch input { opacity: 0; width: 0; height: 0; }
.slider {
  position: absolute;
  cursor: pointer;
  inset: 0;
  background: var(--line-strong);
  border-radius: 999px;
  transition: background 0.18s;
}
.slider::before {
  content: "";
  position: absolute;
  height: 16px; width: 16px;
  left: 3px; top: 3px;
  background: white;
  border-radius: 50%;
  transition: transform 0.18s;
  box-shadow: 0 1px 3px rgba(0,0,0,0.2);
}
.switch input:checked + .slider { background: var(--brand); }
.switch input:checked + .slider::before { transform: translateX(16px); }

.theme-pills {
  display: inline-flex;
  background: var(--bg-input);
  border-radius: 999px;
  padding: 3px;
  gap: 0;
}
.theme-pills button {
  padding: 5px 12px;
  border-radius: 999px;
  font-size: 12px;
  color: var(--fg-muted);
  font-weight: 500;
  transition: background 0.15s, color 0.15s;
}
.theme-pills button:hover { color: var(--fg); }
.theme-pills button.active {
  background: var(--bg);
  color: var(--fg);
  box-shadow: var(--shadow-sm);
}

.card {
  padding: 14px 16px;
  background: var(--bg-soft);
  border: 1px solid var(--line);
  border-radius: 12px;
}
.card.subtle { background: transparent; border-style: dashed; }
.card-label {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--mono);
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.8px;
  color: var(--fg-subtle);
  margin-bottom: 6px;
}
.card-value { font-size: 14px; color: var(--fg); font-weight: 500; }
.card-value.mono { font-family: var(--mono); font-size: 13px; }
.card-foot { margin-top: 4px; font-size: 12px; color: var(--fg-muted); }

.legend { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.legend li { display: flex; align-items: center; gap: 10px; font-size: 12px; color: var(--fg-muted); font-family: var(--mono); }

.ghost-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 9px 14px;
  background: transparent;
  color: var(--brand);
  border: 1px solid var(--line);
  border-radius: 10px;
  font-size: 13px;
  font-weight: 500;
  align-self: flex-start;
  transition: background 0.15s, border-color 0.15s;
}
.ghost-btn:hover { background: var(--brand-soft); border-color: var(--brand); }
.ghost-btn.small { padding: 5px 10px; font-size: 11.5px; }

.audit-header { display: flex; align-items: center; justify-content: space-between; font-family: var(--mono); font-size: 11px; color: var(--fg-subtle); margin-bottom: 4px; }
.audit-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.audit-list li { padding: 10px 12px; background: var(--bg-soft); border: 1px solid var(--line); border-radius: 10px; font-family: var(--mono); font-size: 11px; }
.ev-type { font-size: 10px; font-weight: 600; letter-spacing: 0.5px; text-transform: uppercase; color: var(--fg-muted); margin-bottom: 4px; }
.ev-OUTBOUND_GATE_DENIED .ev-type { color: var(--label-RESTRICTED); }
.ev-OUTBOUND_GATE_CHECKED .ev-type { color: var(--label-PUBLIC); }
.ev-PROVIDER_FAILED .ev-type { color: var(--label-CONFIDENTIAL); }
.ev-PROVIDER_CALLED .ev-type { color: var(--label-SANITIZED); }
.ev-meta { color: var(--fg-subtle); }
.ev-payload { color: var(--fg-muted); margin-top: 4px; word-break: break-all; }
.audit-empty { color: var(--fg-subtle); font-style: italic; text-align: center; padding: 30px 10px; font-family: var(--sans); }

/* Chats list (left drawer) */
.chats-list { list-style: none; margin: 0; padding: 8px; overflow-y: auto; flex: 1; }
.chats-list li {
  padding: 10px 12px;
  border-radius: 10px;
  margin-bottom: 2px;
  cursor: pointer;
  font-size: 13.5px;
  color: var(--fg);
  transition: background 0.12s;
  display: flex;
  align-items: baseline;
  gap: 10px;
}
.chats-list li:hover { background: var(--bg-hover); }
.chats-list li .chat-title { flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.chats-list li .chat-time { font-family: var(--mono); font-size: 10.5px; color: var(--fg-subtle); }
.chats-empty { color: var(--fg-subtle) !important; font-style: italic; cursor: default !important; padding: 20px 12px !important; }
.chats-empty:hover { background: transparent !important; }

/* Entity lists (projects, workflows, connectors) */
.drawer-panel-static {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 14px 16px max(16px, env(safe-area-inset-bottom, 16px));
  flex: 1 1 0;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
}
.drawer-panel-static .ghost-btn {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.entity-list {
  list-style: none;
  margin: 0;
  padding: 10px 12px;
  overflow-y: auto;
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
/* Inside the drawer the parent panel is the scroll container; if
   the list also takes ``flex: 1; overflow: auto`` we end up with a
   nested scroller that traps touches on iOS and the user can't pan
   the panel to reach an expanded twin's body. Let the list size to
   its content and let the panel scroll. */
.drawer-panel-static .entity-list {
  padding: 0;
  flex: 0 0 auto;
  overflow: visible;
}
.entity-card {
  background: var(--bg-soft);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 12px 14px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  transition: border-color 0.12s, background 0.12s;
}
.entity-card.available { cursor: pointer; }
.entity-card.available:hover {
  border-color: var(--brand);
  background: var(--brand-soft);
}
.entity-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.entity-title { font-size: 13.5px; font-weight: 600; color: var(--fg); }
.entity-desc { font-size: 12px; color: var(--fg-muted); margin: 0; line-height: 1.45; }
.entity-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 6px;
  flex-wrap: wrap;
}
.entity-actions .btn-secondary {
  font-size: 12px;
  padding: 4px 10px;
  border-radius: 6px;
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--line);
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s;
}
.entity-actions .btn-secondary:hover:not(:disabled) {
  background: var(--brand-soft);
  border-color: var(--brand);
}
.entity-actions .btn-secondary:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
/* Accessible-software panel on an HPC connector card. */
.entity-software {
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid var(--line);
}
.entity-software-title {
  display: block;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--fg);
  margin-bottom: 6px;
}
.entity-software-hint { font-weight: 400; color: var(--fg-muted); }
.entity-software-list {
  list-style: none;
  margin: 0 0 6px;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.entity-software-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--mono);
  font-size: 11.5px;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 2px 6px;
}
.entity-software-item code { background: none; padding: 0; }
.entity-software-tag {
  font-family: var(--sans, inherit);
  color: var(--fg-muted);
  font-size: 11px;
}
.entity-software-remove {
  border: none;
  background: none;
  color: var(--fg-muted);
  cursor: pointer;
  font-size: 14px;
  line-height: 1;
  padding: 0 2px;
}
.entity-software-remove:hover { color: #b13434; }
.entity-software-empty {
  font-size: 11.5px;
  color: var(--fg-muted);
  line-height: 1.45;
}
.entity-software-add {
  display: flex;
  gap: 6px;
  align-items: center;
}
.entity-software-input {
  flex: 1;
  min-width: 0;
  font-family: var(--mono);
  font-size: 11.5px;
  padding: 4px 8px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--bg);
  color: var(--fg);
}
.entity-software-input:focus {
  outline: none;
  border-color: var(--brand);
}
.entity-software-addbtn { white-space: nowrap; }
.entity-meta {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--fg-muted);
}
.entity-status {
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--bg-input);
  color: var(--fg-subtle);
  white-space: nowrap;
}
.entity-status.status-live { background: var(--brand-soft); color: var(--brand); }
.entity-status.status-blocked { background: rgba(220, 38, 38, 0.1); color: var(--label-RESTRICTED); }
.entity-empty {
  color: var(--fg-subtle);
  font-style: italic;
  font-size: 13px;
  padding: 18px 6px;
  line-height: 1.5;
}

/* Empty state for the Experimentation panel — generic title +
   one-line description. Non-italic so it reads as informational,
   not as missing-data filler. */
.entity-empty-labs {
  font-style: normal;
  color: var(--fg);
}
.entity-empty-labs .empty-title {
  font-size: 14px;
  font-weight: 600;
  color: var(--fg);
  margin-bottom: 4px;
}
.entity-empty-labs .empty-desc {
  font-size: 13px;
  color: var(--fg-subtle);
  margin: 0;
  line-height: 1.5;
}

/* ------------------------------------------------------------------
   Digital Twin panel — Projects of curated data objects.

   The visual story: each project is a "vessel" of curated data. The
   row is intentionally restrained (matches Computing/Experimentation)
   but earns its character through (a) the dashed-axis paired-orb
   icon, (b) a monospaced count badge that reads like a meter, and
   (c) a collapsible body that reveals a stack of data-point cards
   with a left-rail rule echoing a "spine of records".
   ------------------------------------------------------------------ */

.twin-projects-list { gap: 8px; }

/* Project row — outer shell. Reuses .entity-card geometry so it
   sits flush with how Computing's HPC cards land in the same list. */
.twin-project {
  background: var(--bg-soft);
  border: 1px solid var(--line);
  border-radius: 10px;
  overflow: hidden;
  transition: border-color 0.15s, background 0.15s;
}
.twin-project:hover { border-color: var(--line-strong); }
.twin-project.is-open { border-color: var(--brand); }

/* Header row — name + count badge + chevron. Click toggles open. */
.twin-project-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  cursor: pointer;
  user-select: none;
  background: transparent;
  border: none;
  width: 100%;
  text-align: left;
  color: var(--fg);
  font: inherit;
}
.twin-project-head:hover { background: var(--bg-hover); }
.twin-project.is-open .twin-project-head { background: var(--brand-soft); }
/* Label block stacks name on top of the project id so the full
   id is visible (not truncated). Header row stays single-row, but
   THIS cell is a vertical column. */
.twin-project-label {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  overflow: hidden;
}
.twin-project-name {
  width: 100%;
  font-size: 13.5px;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Project id row below the name — id + copy control side by side.
   Keep the row shrink-wrapped so the icon stays visually attached
   to the code instead of drifting to the far edge of the header. */
.twin-project-id-row {
  display: flex;
  align-items: center;
  gap: 4px;
  width: fit-content;
  max-width: 100%;
}
.twin-project-id {
  flex: 0 1 auto;
  min-width: 0;
  font-family: var(--mono);
  font-size: 10.5px;
  line-height: 1.3;
  color: var(--fg-subtle);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  user-select: all;
}
/* Copy button — tiny clipboard icon next to the id. Swaps to a
   check for 1.2s on click. Sits flush against the id; uses the
   same hover surface as the rest of the row chrome. */
.twin-project-id-copy {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  padding: 0;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 4px;
  color: var(--fg-subtle);
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.twin-project-id-copy:hover {
  background: var(--bg-hover);
  color: var(--fg);
  border-color: var(--line);
}
.twin-project-id-copy.is-copied {
  color: var(--brand);
  border-color: color-mix(in srgb, var(--brand) 35%, var(--line));
}
.twin-project-id-copy .twin-id-copy-check { display: none; }
.twin-project-id-copy.is-copied .twin-id-copy-icon { display: none; }
.twin-project-id-copy.is-copied .twin-id-copy-check { display: inline-block; }
.twin-project-count {
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--bg-input);
  color: var(--fg-subtle);
  white-space: nowrap;
  flex-shrink: 0;
}
.twin-project.is-open .twin-project-count {
  background: var(--brand-soft);
  color: var(--brand);
}
.twin-project-chev {
  display: inline-flex;
  align-items: center;
  color: var(--fg-subtle);
  flex-shrink: 0;
  transition: transform 0.2s ease, color 0.15s;
}
.twin-project.is-open .twin-project-chev {
  transform: rotate(90deg);
  color: var(--brand);
}

/* Project body — only rendered when .is-open. The body is the
   twin's configuration card: summary strip, data-point list,
   inline test-query workshop. Sections are visually distinct so
   the operator can find each region at a glance.

   Geometry: 14px horizontal padding ties to the head's padding;
   sections separated by ~16px gap so they read as discrete blocks
   rather than a continuous list. */
.twin-project-body {
  border-top: 1px solid var(--line);
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

/* Summary strip — four key/value cells in a row, with a vertical
   rule between each. Reads at-a-glance like an instrument panel.
   On narrow widths it wraps to a 2x2 grid. */
.twin-card-summary {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1px;
  padding: 0;
  background: var(--line);
  border: 1px solid var(--line);
  border-radius: 8px;
  overflow: hidden;
}
.twin-summary-cell {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 8px 10px;
  background: var(--bg);
  min-width: 0;
}
.twin-summary-label {
  font-size: 9.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.55px;
  color: var(--fg-subtle);
}
.twin-summary-value {
  font-size: 13px;
  font-weight: 500;
  color: var(--fg);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.twin-summary-value.mono {
  font-family: var(--mono);
  font-size: 12px;
}
@media (max-width: 640px) {
  .twin-card-summary { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Card sections — each section (data points, test query) gets a
   header strip and a body. Keeps the body padded without nesting
   too many boxes. */
.twin-card-section {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.twin-card-section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.twin-card-section-head h4 {
  margin: 0;
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--fg-subtle);
}
.twin-card-section-head .ghost-btn {
  align-self: auto;
}
.twin-test-hint {
  font-size: 11px;
  color: var(--fg-subtle);
}
.twin-test-hint code {
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--fg-muted);
}

/* Test-query workshop — slight inset + tinted background so it
   reads as a "do something" zone separate from the inventory
   list above it. */
.twin-card-test {
  background: var(--bg-input);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 12px;
  min-width: 0;
}
/* Single-column on phones; two-column on >=520px wide cards. */
.twin-card-test .twin-test-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 10px;
  min-width: 0;
}
@media (min-width: 520px) {
  .twin-card-test .twin-test-grid {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }
  .twin-card-test .twin-test-intent {
    grid-column: 1 / -1;
  }
}
.twin-card-test .fs-form-row {
  min-width: 0;
}
.twin-card-test .fs-form-row label {
  font-size: 10px;
}
.twin-card-test select,
.twin-card-test input[type="text"] {
  width: 100%;
  box-sizing: border-box;
  min-width: 0;
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 9px 12px;
  font-size: 13px;
  font-family: inherit;
  appearance: none;
  -webkit-appearance: none;
}
.twin-card-test select {
  padding-right: 28px;
  text-overflow: ellipsis;
  white-space: nowrap;
  overflow: hidden;
}
.twin-card-test select:focus,
.twin-card-test input[type="text"]:focus {
  outline: none;
  border-color: var(--brand);
}
.twin-test-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 6px;
}
.twin-card-test .twin-test-run {
  align-self: flex-end;
}
.twin-card-test .twin-test-run[disabled] {
  opacity: 0.55;
  cursor: not-allowed;
}

/* Test result — verdict pill, kv strip, observation blockquote.
   Three colour states: ok / miss / error. */
.twin-card-test .twin-test-result {
  border-radius: 8px;
  border: 1px solid var(--line);
  background: var(--bg);
  padding: 10px 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  font-size: 12.5px;
  color: var(--fg);
}
.twin-card-test .twin-test-result.is-ok { border-color: color-mix(in srgb, var(--brand) 50%, var(--line)); }
.twin-card-test .twin-test-result.is-miss { border-color: color-mix(in srgb, var(--label-CONFIDENTIAL, var(--label-RESTRICTED)) 45%, var(--line)); }
.twin-card-test .twin-test-result.is-error { border-color: color-mix(in srgb, var(--label-RESTRICTED) 55%, var(--line)); }
.twin-card-test .twin-test-result.is-running { color: var(--fg-muted); }
.twin-test-verdict {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 14px;
}
.twin-verdict {
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  padding: 2px 8px;
  border-radius: 999px;
  white-space: nowrap;
}
.twin-verdict.ok { background: var(--brand-soft); color: var(--brand); }
.twin-verdict.miss { background: color-mix(in srgb, var(--label-CONFIDENTIAL, var(--label-RESTRICTED)) 15%, transparent); color: var(--label-CONFIDENTIAL, var(--label-RESTRICTED)); }
.twin-test-kvs {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  flex-wrap: wrap;
  font-size: 11.5px;
  color: var(--fg-subtle);
  font-family: var(--mono);
}
.twin-test-kvs .k { color: var(--fg-subtle); }
.twin-test-kvs strong { color: var(--fg); font-weight: 600; }
.twin-test-kvs .dot { opacity: 0.5; }
.twin-test-row {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.twin-test-row .k {
  font-size: 9.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--fg-subtle);
}
.twin-test-row .v {
  font-size: 12.5px;
  color: var(--fg);
  line-height: 1.45;
  overflow-wrap: anywhere;
}
.twin-test-observation {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.twin-test-observation .k {
  font-size: 9.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--fg-subtle);
}
.twin-test-observation blockquote {
  margin: 0;
  padding: 8px 10px;
  border-left: 3px solid var(--brand);
  background: var(--bg-input);
  border-radius: 4px;
  font-size: 13px;
  color: var(--fg);
  line-height: 1.5;
  overflow-wrap: anywhere;
}
.twin-test-reason {
  color: var(--label-CONFIDENTIAL, var(--label-RESTRICTED));
  font-size: 12.5px;
  line-height: 1.45;
}
.twin-test-status {
  color: var(--fg-muted);
  font-size: 12.5px;
}
.twin-test-status.is-error {
  color: var(--label-RESTRICTED);
}

/* Hidden file input pattern — the "Add data point" ghost button
   triggers a sibling <input type="file"> via a label. */
.twin-file-input { display: none; }

/* Data-point list — a stack of slim cards with a left-edge accent.
   Reads as a "spine of records" inside the project vessel. */
.twin-dp-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.twin-dp-empty {
  font-size: 12.5px;
  color: var(--fg-subtle);
  font-style: italic;
  padding: 8px 4px 4px;
}
.twin-dp {
  display: grid;
  grid-template-columns: 4px 1fr auto;
  align-items: stretch;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 8px;
  overflow: hidden;
  transition: border-color 0.12s;
}
.twin-dp:hover { border-color: var(--brand); }
.twin-dp-rail {
  background: var(--brand);
  opacity: 0.45;
}
.twin-dp:hover .twin-dp-rail { opacity: 1; }
.twin-dp-main {
  padding: 8px 10px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.twin-dp-id {
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--fg-muted);
  letter-spacing: 0.2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.twin-dp-q {
  font-size: 12.5px;
  color: var(--fg);
  line-height: 1.4;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Expanded variant — used inside the configuration card. Gives
   the central question more room and shows it in full instead of
   clamping to two lines. The mono id sits as a small tag above. */
.twin-dp-card {
  grid-template-columns: 4px 1fr auto;
  align-items: stretch;
}
.twin-dp-card .twin-dp-main {
  padding: 12px 14px;
  gap: 6px;
}
.twin-dp-card .twin-dp-id {
  font-size: 11px;
  color: var(--fg-subtle);
  font-weight: 500;
  text-transform: lowercase;
}
.twin-dp-card .twin-dp-q {
  font-size: 13.5px;
  color: var(--fg);
  line-height: 1.5;
  display: block;
  -webkit-line-clamp: unset;
  -webkit-box-orient: unset;
  overflow: visible;
}
.twin-dp-card .twin-dp-actions {
  padding: 10px 10px 10px 4px;
  align-items: flex-start;
}
.twin-dp-actions {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 6px 8px 6px 4px;
}
.twin-dp-act {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 6px;
  background: transparent;
  border: none;
  color: var(--fg-subtle);
  cursor: pointer;
  transition: background 0.12s, color 0.12s;
}
.twin-dp-act:hover { background: var(--bg-hover); color: var(--fg); }
.twin-dp-act.danger:hover { color: var(--label-RESTRICTED); }
.twin-dp-act svg { pointer-events: none; }

/* Project-level row actions — sit on the head when hovering. Small,
   muted, only visible on row hover so the row stays calm at rest. */
.twin-project-head-actions {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-left: 2px;
  opacity: 0;
  transition: opacity 0.12s;
}
.twin-project:hover .twin-project-head-actions,
.twin-project.is-open .twin-project-head-actions { opacity: 1; }
.twin-project-head-act {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: 5px;
  background: transparent;
  border: none;
  color: var(--fg-subtle);
  cursor: pointer;
  transition: background 0.12s, color 0.12s;
}
.twin-project-head-act:hover { background: var(--bg-hover); color: var(--fg); }
.twin-project-head-act.danger:hover { color: var(--label-RESTRICTED); }

/* Modal — "New project" flow + the data-point inspector. */
/* ===================================================================
   Digital Twin — Create modal (mobile-first rebuild).

   Frame:  three rows — sticky header, scrollable body, sticky footer.
           ``.twin-modal-frame`` replaces the default overflow:auto on
           .modal-box so we can pin the header + footer outside the
           scroll area.
   Inputs: every interactive control is 100% width on its parent and
           uses box-sizing:border-box so long option labels truncate
           instead of pushing the modal sideways.
   ================================================================== */

.modal-box.twin-modal.twin-modal-frame {
  width: min(560px, calc(100% - 24px));
  max-width: 560px;
  max-height: min(820px, calc(var(--vvh, 100vh) - 24px));
  padding: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.twin-modal-header {
  padding: 16px 18px 12px;
  margin: 0;
  border-bottom: 1px solid var(--line);
  flex-shrink: 0;
}

.twin-create-form {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}

.twin-modal-body {
  flex: 1 1 auto;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 16px 18px 12px;
  display: flex;
  flex-direction: column;
  gap: 18px;
  -webkit-overflow-scrolling: touch;
}

.twin-create-section {
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 0;
}

.twin-section-label {
  margin: 0;
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--fg-subtle);
  display: flex;
  align-items: baseline;
  gap: 8px;
  min-width: 0;
}
.twin-section-hint {
  font-size: 10.5px;
  font-weight: 500;
  text-transform: none;
  letter-spacing: 0.1px;
  color: var(--fg-subtle);
  opacity: 0.7;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Explanatory blurb under a section header — e.g. how to produce
   the vector-store upload file. Mono code chip for the CLI hint. */
.twin-section-help {
  margin: 8px 0 0;
  padding: 0;
  font-size: 11px;
  line-height: 1.45;
  color: var(--fg-subtle);
}
.twin-section-help code {
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--fg-muted);
  background: var(--bg-soft);
  padding: 1px 5px;
  border-radius: 3px;
  border: 1px solid var(--line);
  word-break: break-all;
}

/* Per-twin card: inline status of the loaded vector store. */
.twin-vs-status {
  margin: 8px 0 12px;
  font-size: 12px;
  color: var(--fg-subtle);
  word-break: break-word;
}
.twin-vs-status.is-pending { color: var(--fg-muted); }
.twin-vs-status.is-ok      { color: var(--brand); }
.twin-vs-status.is-error   { color: var(--label-RESTRICTED, #d9534f); }

/* "Check connection" row in the create modal — button + inline
   status, mirroring the HPC connection test pattern. */
.twin-check-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 10px;
  flex-wrap: wrap;
}
.twin-check-status {
  font-size: 11.5px;
  line-height: 1.4;
  color: var(--fg-subtle);
  flex: 1 1 0;
  min-width: 0;
  word-break: break-word;
}
.twin-check-status.is-pending { color: var(--fg-muted); }
.twin-check-status.is-ok      { color: var(--brand); }
.twin-check-status.is-error   { color: var(--label-RESTRICTED, #d9534f); }
/* Tighter chip variant for the per-twin card (limited horizontal
   room next to the existing Run-test button). */
.twin-test-actions .twin-check-status {
  margin-left: 4px;
}

/* Inputs + selects — share one geometry, full width, native height
   (~38-40px tap target). Padding-right on selects leaves room for
   the native chevron rendered by the OS. */
.twin-input,
.twin-select {
  width: 100%;
  box-sizing: border-box;
  min-width: 0;
  padding: 9px 12px;
  background: var(--bg-input);
  color: var(--fg);
  border: 1px solid var(--line);
  border-radius: 8px;
  font-family: inherit;
  font-size: 14px;
  line-height: 1.35;
  appearance: none;
  -webkit-appearance: none;
  transition: border-color 0.12s, background 0.12s;
}
.twin-input::placeholder {
  color: var(--fg-subtle);
}
.twin-input:focus,
.twin-select:focus {
  outline: none;
  border-color: var(--brand);
  background: var(--bg);
}
.twin-select {
  padding-right: 32px;
  cursor: pointer;
  text-overflow: ellipsis;
  white-space: nowrap;
  overflow: hidden;
}
.twin-select-wrap {
  position: relative;
  min-width: 0;
}
.twin-select-wrap::after {
  content: "";
  position: absolute;
  right: 12px;
  top: 50%;
  width: 8px;
  height: 8px;
  border-right: 1.5px solid var(--fg-subtle);
  border-bottom: 1.5px solid var(--fg-subtle);
  transform: translateY(-70%) rotate(45deg);
  pointer-events: none;
}

/* Field wrappers — vertical stack inside the test-query section. */
.twin-test-form {
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: 0;
}
.twin-field {
  display: flex;
  flex-direction: column;
  gap: 5px;
  min-width: 0;
}
.twin-field-label {
  font-size: 10.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--fg-subtle);
}

/* Drop zone */
.twin-drop-zone {
  border: 1px dashed var(--line-strong, var(--line));
  background: var(--bg-input);
  border-radius: 8px;
  padding: 14px;
  transition: border-color 0.12s, background 0.12s;
}
.twin-drop-zone.is-dragging {
  border-color: var(--brand);
  background: var(--bg-hover);
}
.twin-drop-main {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  min-width: 0;
}
.twin-drop-title {
  color: var(--fg-muted);
  font-size: 13px;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
.twin-staged-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.twin-staged-empty {
  color: var(--fg-subtle);
  font-size: 12px;
  padding: 2px 0;
}
.twin-staged-file {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 9px 10px;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--bg);
  min-width: 0;
}
.twin-staged-file.is-error { border-color: color-mix(in srgb, var(--label-RESTRICTED) 40%, var(--line)); }
.twin-staged-file.is-success { border-color: color-mix(in srgb, var(--brand) 42%, var(--line)); }
.twin-staged-main {
  min-width: 0;
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.twin-staged-name {
  color: var(--fg);
  font-size: 13px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.twin-staged-meta {
  color: var(--fg-subtle);
  font-family: var(--mono);
  font-size: 11px;
  overflow-wrap: anywhere;
}
.twin-staged-remove {
  width: 26px;
  height: 26px;
  flex: 0 0 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--fg-subtle);
  cursor: pointer;
}
.twin-staged-remove:hover {
  background: var(--bg-hover);
  color: var(--label-RESTRICTED);
}

/* Buttons — modal-local primaries/ghosts so they share the rest of
   the form's geometry (no inherited align-self quirks from .ghost-btn). */
.twin-btn-ghost,
.twin-btn-primary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 9px 16px;
  border-radius: 8px;
  font-size: 13.5px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  border: 1px solid transparent;
  transition: background 0.12s, color 0.12s, border-color 0.12s;
  min-height: 38px;
  white-space: nowrap;
}
.twin-btn-ghost {
  background: transparent;
  color: var(--brand);
  border-color: var(--line);
}
.twin-btn-ghost:hover:not(:disabled) {
  background: var(--brand-soft);
  border-color: var(--brand);
}
.twin-btn-ghost:disabled {
  color: var(--fg-subtle);
  cursor: not-allowed;
  opacity: 0.7;
}
.twin-btn-primary {
  background: var(--fg);
  color: var(--bg);
  border-color: var(--fg);
}
.twin-btn-primary:hover:not(:disabled) {
  background: var(--fg-muted);
  border-color: var(--fg-muted);
}
.twin-btn-primary:disabled {
  background: var(--fg-muted);
  border-color: var(--fg-muted);
  opacity: 0.6;
  cursor: not-allowed;
}
.twin-test-run-btn {
  align-self: flex-start;
}

/* Test result — verdict pill + meta strip + observation. Reuses
   the per-twin card's verdict idiom so the two surfaces feel like
   one product. */
.twin-test-result {
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 10px 12px;
  background: var(--bg-input);
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 0;
  font-size: 13px;
  color: var(--fg);
}
.twin-test-result.is-ok { border-color: color-mix(in srgb, var(--brand) 55%, var(--line)); }
.twin-test-result.is-miss { border-color: color-mix(in srgb, var(--label-CONFIDENTIAL, var(--label-RESTRICTED)) 45%, var(--line)); }
.twin-test-result.is-error { border-color: color-mix(in srgb, var(--label-RESTRICTED) 55%, var(--line)); }
.twin-test-result.is-running { color: var(--fg-muted); }
.twin-test-result .twin-test-status {
  color: var(--fg-muted);
  font-size: 13px;
}
.twin-test-result .twin-test-status.is-error {
  color: var(--label-RESTRICTED);
  word-break: break-word;
}
.twin-test-result .twin-verdict-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
}
.twin-test-result .twin-verdict {
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  padding: 2px 8px;
  border-radius: 999px;
  white-space: nowrap;
}
.twin-test-result .twin-verdict.ok {
  background: var(--brand-soft);
  color: var(--brand);
}
.twin-test-result .twin-verdict.miss {
  background: color-mix(in srgb, var(--label-CONFIDENTIAL, var(--label-RESTRICTED)) 15%, transparent);
  color: var(--label-CONFIDENTIAL, var(--label-RESTRICTED));
}
.twin-test-result .twin-kv-strip {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 4px 10px;
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--fg-subtle);
}
.twin-test-result .twin-kv-strip .k { color: var(--fg-subtle); }
.twin-test-result .twin-kv-strip strong { color: var(--fg); font-weight: 600; }
.twin-test-result .twin-kv-strip .dot { opacity: 0.5; }

/* "Judged by Hermes · model-id" attribution line. Only rendered
   when an LLM-judge matcher answered the query; the heuristic
   path emits no agent / model so the line is omitted. Mono on
   the model id so the slash-prefixed OpenRouter ids stay legible. */
.twin-judged-by {
  margin-top: 6px;
  font-size: 11px;
  color: var(--fg-subtle);
  display: flex;
  align-items: center;
  gap: 4px;
  flex-wrap: wrap;
}
.twin-judged-by strong {
  color: var(--brand);
  font-weight: 600;
}
.twin-judged-by code {
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--fg-muted);
  background: var(--bg-soft);
  padding: 1px 5px;
  border-radius: 3px;
  border: 1px solid var(--line);
}
.twin-test-result .twin-result-label {
  font-size: 9.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--fg-subtle);
  margin-bottom: 4px;
}
.twin-test-result .twin-result-matched {
  font-size: 12.5px;
  color: var(--fg);
  line-height: 1.45;
  overflow-wrap: anywhere;
}
.twin-test-result blockquote {
  margin: 0;
  padding: 8px 10px;
  border-left: 3px solid var(--brand);
  background: var(--bg);
  border-radius: 4px;
  font-size: 13px;
  color: var(--fg);
  line-height: 1.5;
  overflow-wrap: anywhere;
}
.twin-test-result .twin-test-reason {
  color: var(--label-CONFIDENTIAL, var(--label-RESTRICTED));
  font-size: 12.5px;
  line-height: 1.45;
  overflow-wrap: anywhere;
}

.twin-form-error {
  margin: 0;
  color: var(--label-RESTRICTED);
  font-weight: 500;
  font-size: 12.5px;
  overflow-wrap: anywhere;
}

.twin-modal-footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  padding: 12px 18px 14px;
  border-top: 1px solid var(--line);
  background: var(--bg);
  flex-shrink: 0;
  /* On iOS, account for the home-indicator inset when the modal
     sits at the bottom of the viewport. */
  padding-bottom: max(14px, env(safe-area-inset-bottom, 14px));
}

@media (max-width: 640px) {
  .modal {
    align-items: stretch;
    padding: 0;
  }
  .modal-box.twin-modal.twin-modal-frame {
    width: 100%;
    max-width: 100%;
    max-height: var(--vvh, 100vh);
    border-radius: 0;
    box-shadow: none;
  }
  .twin-modal-header,
  .twin-modal-body,
  .twin-modal-footer { padding-left: 14px; padding-right: 14px; }
}

/* Data-point JSON editor modal — wider than the create modal +
   the textarea is the dominant element. Mono font, generous line
   height so the JSON is comfortably scannable. */
.modal-box.twin-edit-modal {
  max-width: 760px;
}
.twin-edit-form {
  gap: 12px;
}
.twin-edit-hint {
  margin: 0;
  font-size: 12px;
  color: var(--fg-subtle);
  line-height: 1.45;
}
.twin-edit-hint code {
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--fg-muted);
  background: var(--bg-input);
  padding: 1px 5px;
  border-radius: 3px;
}
.twin-edit-textarea {
  width: 100%;
  min-height: 420px;
  max-height: 60vh;
  resize: vertical;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--bg-input);
  color: var(--fg);
  font-family: var(--mono);
  font-size: 12.5px;
  line-height: 1.55;
  tab-size: 2;
  white-space: pre;
}
.twin-edit-textarea:focus {
  outline: none;
  border-color: var(--brand);
  background: var(--bg);
}
@media (max-width: 640px) {
  .modal-box.twin-edit-modal { max-width: 100%; }
  .twin-edit-textarea { min-height: 320px; }
}

/* Inspector body — agent-visible content only. The h3 reads as a
   section caption above the long-form values; we never render gold
   or distractor sections (the API doesn't expose them). */
.twin-inspect-body {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding-top: 4px;
}
.twin-inspect-section {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.twin-inspect-label {
  font-size: 10.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--fg-subtle);
}
.twin-inspect-value {
  font-size: 13px;
  color: var(--fg);
  line-height: 1.5;
  white-space: pre-wrap;
}
.twin-inspect-value.mono {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--fg-muted);
  background: var(--bg-input);
  padding: 8px 10px;
  border-radius: 6px;
  word-break: break-all;
}
.twin-inspect-note {
  font-size: 11.5px;
  color: var(--fg-subtle);
  font-style: italic;
  margin-top: 4px;
  padding-top: 10px;
  border-top: 1px solid var(--line);
}

/* Sidebar inline badge (e.g. Audit count) */
.sidebar-badge {
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 500;
  background: var(--bg-input);
  color: var(--brand);
  padding: 1px 7px;
  border-radius: 999px;
  margin-left: 6px;
  vertical-align: 1px;
}

/* ========================================================================
   Modals
   ======================================================================== */

.modal {
  position: fixed; inset: 0;
  background: rgba(0,0,0,0.45);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
  animation: fade-in 0.18s ease;
}
.modal-box {
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 16px;
  padding: 22px;
  width: min(520px, calc(100% - 32px));
  max-height: calc(100vh - 64px);
  overflow-y: auto;
  box-shadow: var(--shadow-lg);
}
.modal-box.modal-search {
  width: min(560px, calc(100% - 32px));
  padding: 0;
  align-self: flex-start;
  margin-top: 12vh;
}
.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 14px;
}
.modal-header h2 { margin: 0; font-size: 18px; font-weight: 600; }
.modal-lead { margin: 0 0 16px; color: var(--fg-muted); font-size: 14px; }
/* Lead inside the trace modal sits right under the H2 in the
   modal-header grid, so it shouldn't carry the 16px bottom margin
   the standalone .modal-lead uses. */
.trace-modal-box .modal-lead { margin: 4px 0 0; font-size: 13px; }

/* Trace modal — reasoning trace popup opened from the agent
   message's Trace pill. Reads like a system-log viewer: chips for
   provenance, numbered list for steps, monospaced labels for
   tool / skill / agent ids. */
.trace-modal-box {
  max-width: 640px;
  width: min(640px, calc(100vw - 32px));
  display: flex;
  flex-direction: column;
  max-height: min(85vh, var(--vvh, 100vh));
}
.trace-modal-body {
  flex: 1;
  overflow-y: auto;
  padding-right: 4px;
  margin: 18px 0;
}
.trace-modal-section { margin-bottom: 22px; }
.trace-modal-section:last-child { margin-bottom: 0; }
.trace-modal-h3 {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--fg-subtle);
  margin: 0 0 10px;
}
.trace-modal-prov { display: flex; flex-direction: column; gap: 6px; }
.trace-modal-prov-row {
  display: flex;
  gap: 12px;
  align-items: baseline;
  font-size: 13px;
}
.trace-modal-prov-label {
  flex: 0 0 72px;
  color: var(--fg-subtle);
  text-transform: uppercase;
  font-size: 11px;
  letter-spacing: 0.4px;
}
.trace-modal-prov-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.trace-modal-chip {
  display: inline-block;
  padding: 2px 8px;
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 11.5px;
  background: var(--bg-input);
  border: 1px solid var(--line);
  border-radius: 4px;
  color: var(--fg);
}
/* Rich trace events — instrument-log layout.
   * Left rail: type-coded indicator (icon in a circle), connected by
     a thin vertical line to the next event so the sequence reads as a
     timeline.
   * Right body: agent attribution chip, action label, optional
     duration, then optional input + output blocks in mono type.
   Color accents differentiate event types without shouting. */
.trace-modal-step-list {
  margin: 0;
  padding: 0;
  list-style: none;
}
.trace-event {
  display: grid;
  grid-template-columns: 26px 1fr;
  gap: 0 12px;
  padding-bottom: 14px;
  position: relative;
}
.trace-event:not(:last-child)::before {
  /* Vertical timeline rail connecting indicator circles. */
  content: "";
  position: absolute;
  left: 12px;
  top: 22px;
  bottom: -4px;
  width: 2px;
  background: var(--line);
}
.trace-event-indicator {
  width: 26px;
  height: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--bg-input);
  border: 1px solid var(--line);
  color: var(--fg-subtle);
  position: relative;
  z-index: 1;
}
.trace-event-thinking .trace-event-indicator { color: var(--brand); }
.trace-event-handoff  .trace-event-indicator { color: #d49b3a; }
.trace-event-tool     .trace-event-indicator {
  color: #2aa198;
  background: color-mix(in oklab, var(--bg-input) 80%, #2aa198 20%);
  border-color: color-mix(in oklab, var(--line) 50%, #2aa198 50%);
}
.trace-event-composing .trace-event-indicator { color: var(--fg-muted); }
.trace-event[data-status="active"] .trace-event-indicator {
  animation: trace-event-pulse 1.4s ease-in-out infinite;
}
@keyframes trace-event-pulse {
  0%, 100% { opacity: 0.55; }
  50%      { opacity: 1; }
}
.trace-event-body {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding-top: 3px;
}
.trace-event-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
}
.trace-event-agent {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  padding: 1px 6px;
  border-radius: 4px;
  background: var(--brand-soft);
  color: var(--brand);
  flex-shrink: 0;
}
.trace-event-label {
  font-size: 13.5px;
  color: var(--fg);
  font-weight: 500;
}
.trace-event-label code {
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 12px;
  padding: 1px 6px;
  background: var(--bg-input);
  border: 1px solid var(--line);
  border-radius: 4px;
  color: var(--fg);
  font-weight: 400;
}
.trace-event-time {
  margin-left: auto;
  font-size: 11px;
  color: var(--fg-subtle);
  font-family: var(--font-mono, ui-monospace, monospace);
}
.trace-event-block {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.trace-event-block-label {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--fg-subtle);
}
.trace-event-block pre,
.trace-event-skill code {
  margin: 0;
  padding: 8px 10px;
  background: var(--bg-input);
  border: 1px solid var(--line);
  border-radius: 6px;
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 12px;
  line-height: 1.45;
  color: var(--fg);
  white-space: pre-wrap;
  word-break: break-word;
  overflow-x: auto;
  max-height: 220px;
  overflow-y: auto;
}
.trace-event-block-out pre { color: var(--fg); }
.trace-event-artifacts {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.trace-event-artifacts code {
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 11px;
  padding: 1px 6px;
  background: var(--bg-soft);
  border: 1px solid var(--line);
  border-radius: 4px;
  color: var(--fg-muted);
}
.trace-event-skill {
  display: flex;
  align-items: center;
  gap: 6px;
}
.trace-event-skill code {
  padding: 2px 8px;
  font-size: 11.5px;
}
.trace-modal-empty {
  color: var(--fg-muted);
  font-size: 13px;
  margin: 0;
  padding: 24px 0;
  text-align: center;
}
.trace-modal-footer {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}
.trace-modal-footer .primary-btn.flash {
  background: var(--brand-soft);
  color: var(--brand);
}
.modal-h3 { font-size: 13px; font-weight: 600; margin: 18px 0 8px; color: var(--brand); letter-spacing: 0.2px; }
.modal-box pre {
  background: var(--bg-soft);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 12px 14px;
  margin: 0;
  overflow-x: auto;
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.6;
  color: var(--fg);
}
.modal-note { margin-top: 16px; font-size: 12px; color: var(--fg-subtle); }
.modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 20px;
  padding-top: 16px;
  border-top: 1px solid var(--line);
}
.primary-btn {
  background: var(--send-bg);
  color: var(--send-fg);
  padding: 9px 20px;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 600;
}
.primary-btn:hover { background: var(--send-hover); }

.text-input {
  width: 100%;
  padding: 10px 14px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--bg-soft);
  color: var(--fg);
  font-size: 14px;
  outline: none;
  transition: border-color 0.15s;
}
.text-input:focus { border-color: var(--brand); }

/* Search modal */
.search-input-wrap {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--line);
}
.search-input-wrap svg { color: var(--fg-muted); flex-shrink: 0; }
.search-input-wrap input {
  flex: 1;
  background: transparent;
  border: none;
  outline: none;
  color: var(--fg);
  font-size: 16px;
}
.search-input-wrap input::placeholder { color: var(--fg-subtle); }
.search-input-wrap kbd {
  background: var(--bg-input);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 1px 6px;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--fg-muted);
}
.search-results {
  padding: 16px 18px 18px;
  max-height: 50vh;
  overflow-y: auto;
}
.search-results .modal-note { margin: 0; }
.search-hit {
  padding: 10px 12px;
  border-radius: 8px;
  margin-bottom: 4px;
  cursor: pointer;
  transition: background 0.12s;
}
.search-hit:hover { background: var(--bg-hover); }
.search-hit-head { font-size: 11px; color: var(--fg-subtle); margin-bottom: 4px; font-family: var(--mono); }
.search-hit-body { font-size: 13.5px; color: var(--fg); }
.search-hit mark { background: var(--brand-soft); color: var(--brand); padding: 0 2px; border-radius: 2px; }

/* ========================================================================
   Auth (top-right buttons + modal)
   ======================================================================== */

.auth-actions {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-right: 4px;
}

.btn-text {
  padding: 7px 14px;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 500;
  color: var(--fg);
  background: transparent;
  transition: background 0.15s;
}
.btn-text:hover { background: var(--bg-hover); }

.btn-primary {
  padding: 7px 16px;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 600;
  color: var(--send-fg);
  background: var(--send-bg);
  transition: background 0.15s;
}
.btn-primary:hover { background: var(--send-hover); }
.btn-primary:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

button.btn-secondary {
  padding: 6px 14px;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 500;
  color: var(--fg);
  background: var(--bg);
  border: 1px solid var(--line);
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
}
button.btn-secondary:hover:not(:disabled) {
  background: var(--brand-soft);
  border-color: var(--brand);
}
button.btn-secondary:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* ----- Auth modal ------------------------------------------------------- */

.modal-auth {
  width: min(440px, calc(100% - 32px));
  padding: 32px 32px 24px;
  position: relative;
  border-radius: 18px;
}
/* iOS keyboard fix: iOS does NOT shrink the layout viewport when the
   on-screen keyboard opens, so a `position:fixed; inset:0` modal centred
   against `100vh` ends up partly behind the keyboard — the login fields
   land below the fold and need manual scrolling (the big grey gap).
   Pin the auth modal to the visual viewport (``--vvh`` tracks
   visualViewport.height, maintained in app.js) so centring happens within
   the area ABOVE the keyboard, and cap the box to that height so it
   scrolls inside the visible region and iOS keeps the focused field in
   view. No-op on desktop, where --vvh equals the full window height. */
#auth-modal {
  top: 0;
  bottom: auto;
  height: var(--vvh, 100dvh);
}
#auth-modal .modal-box.modal-auth {
  max-height: calc(var(--vvh, 100dvh) - 24px);
}
.auth-close {
  position: absolute;
  top: 14px; right: 14px;
}

.auth-brand {
  text-align: center;
  margin-bottom: 18px;
}
.auth-logo {
  width: 44px;
  height: 44px;
  display: block;
  margin: 0 auto 12px;
  filter: drop-shadow(0 4px 16px rgba(74, 179, 240, 0.25));
}
.auth-title {
  margin: 0 0 6px;
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.3px;
  color: var(--fg);
}
.auth-sub {
  margin: 0;
  color: var(--fg-muted);
  font-size: 14px;
}

/* ----- Tabs ----- */
.auth-tabs {
  display: flex;
  gap: 0;
  margin-bottom: 18px;
  background: var(--bg-soft);
  border-radius: 10px;
  padding: 3px;
  position: relative;
}
.auth-tab {
  flex: 1;
  padding: 8px 14px;
  border-radius: 8px;
  font-size: 13.5px;
  font-weight: 500;
  color: var(--fg-muted);
  text-align: center;
  transition: color 0.18s var(--ease);
  position: relative;
  z-index: 1;
}
.auth-tab.active { color: var(--fg); }
.auth-tab:hover:not(.active) { color: var(--fg); }

.auth-tab-indicator {
  position: absolute;
  top: 3px;
  left: 3px;
  width: calc(50% - 3px);
  height: calc(100% - 6px);
  background: var(--bg);
  border-radius: 8px;
  box-shadow: var(--shadow-sm);
  transition: transform 0.22s var(--ease);
}
.auth-tabs[data-mode="signup"] .auth-tab-indicator { transform: translateX(100%); }

/* ----- SSO buttons ----- */
.sso-buttons {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.sso-btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 11px 16px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--bg);
  color: var(--fg);
  font-size: 14px;
  font-weight: 500;
  transition: background 0.15s, border-color 0.15s, transform 0.06s;
}
.sso-btn span { flex: 1; text-align: center; padding-right: 28px; /* offset the icon to keep text optically centered */ }
.sso-btn:hover { background: var(--bg-hover); border-color: var(--line-strong); }
.sso-btn:active { transform: scale(0.99); }
.sso-btn.sso-disabled, .sso-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  filter: grayscale(60%);
}
.sso-btn.sso-disabled:hover, .sso-btn:disabled:hover { background: var(--bg); border-color: var(--line); }
.sso-btn.sso-github {
  background: #24292e;
  color: #fff;
  border-color: #24292e;
}
.sso-btn.sso-github:hover { background: #1a1f24; border-color: #1a1f24; }
[data-theme="dark"] .sso-btn.sso-github {
  background: #fff;
  color: #24292e;
  border-color: #fff;
}
[data-theme="dark"] .sso-btn.sso-github:hover { background: #f0f0f0; border-color: #f0f0f0; }
.sso-btn.sso-github svg { color: inherit; }

/* ----- Divider ----- */
.auth-divider {
  display: flex;
  align-items: center;
  gap: 14px;
  margin: 18px 0 14px;
  color: var(--fg-subtle);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 1px;
}
.auth-divider::before,
.auth-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line);
}

/* ----- Form ----- */
.auth-form {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.auth-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.auth-field-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
}
.auth-field label {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--fg);
  letter-spacing: 0.1px;
}
.auth-field input {
  padding: 11px 14px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--bg);
  color: var(--fg);
  font-size: 14px;
  outline: none;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.auth-field input:focus {
  border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-soft);
}
.auth-field input::placeholder { color: var(--fg-subtle); }

.auth-hint {
  margin: 4px 0 0;
  font-size: 11.5px;
  color: var(--fg-subtle);
  line-height: 1.45;
}

.auth-link {
  background: transparent;
  color: var(--brand);
  font-size: 12px;
  font-weight: 500;
  text-decoration: none;
  padding: 0;
  border: none;
  cursor: pointer;
}
.auth-link:hover { text-decoration: underline; }

.auth-error {
  margin: 0;
  padding: 9px 12px;
  background: rgba(220, 38, 38, 0.08);
  border: 1px solid rgba(220, 38, 38, 0.25);
  border-radius: 8px;
  color: var(--label-RESTRICTED);
  font-size: 12.5px;
}

.auth-submit {
  margin-top: 4px;
  padding: 12px 18px;
  width: 100%;
  font-size: 14px;
  font-weight: 600;
}

.auth-tos {
  margin: 18px 0 0;
  text-align: center;
  font-size: 12px;
  color: var(--fg-subtle);
  line-height: 1.55;
}

.auth-note {
  margin: 14px 0 0;
  padding-top: 14px;
  border-top: 1px solid var(--line);
  font-size: 11.5px;
  color: var(--fg-subtle);
  line-height: 1.55;
}
.auth-note strong { color: var(--fg-muted); }

/* ========================================================================
   Responsive
   ======================================================================== */

@media (max-width: 720px) {
  :root { --sidebar-w: 56px; }
  /* iPhone PWA standalone mode draws the status bar over the
     page; the home indicator sits on the very bottom edge. Pad
     the topbar by the safe-area-inset-top PLUS a 14px floor so
     icons sit visibly below the clock, and pad the composer-zone
     by the safe-area-inset-bottom PLUS a 24px floor so the
     composer never touches the home bar. Both floors apply in
     normal Safari tabs too — env() returns 0 there and the
     fixed extra gives breathing room. */
  .topbar {
    /* Mobile-only: take the topbar OUT of position:fixed so it
       sits as the first flex child of .canvas. With position:fixed,
       iOS PWA standalone mode pushed the topbar above the visible
       area when the keyboard opened — the layout viewport shifts
       under the keyboard handler. As a flex child of the canvas
       (whose height tracks ``--vvh``), the topbar stays inside
       the visible area no matter what the keyboard does. */
    position: relative;
    flex-shrink: 0;
    width: 100%;
    align-self: stretch;
    /* LOCKED — top margin signed off by user on 2026-05-16.
       Do NOT change without an explicit instruction. */
    padding: calc((env(safe-area-inset-top, 0px) + 12px) / 2) 10px 0;
    height: calc(56px + (env(safe-area-inset-top, 0px) + 12px) / 2);
    left: 0;
    right: 0;
  }
  /* Canvas no longer needs the 56px padding-top reservation —
     the topbar lives inside the flex column on mobile. */
  .canvas { padding-top: 0; }
  .hero, .thread, .composer-zone { padding: 0 8px; }
  /* Let the hero (flex:1) actually shrink below its content size
     when the keyboard shrinks the canvas. Without ``min-height: 0``
     the emblem keeps its declared height and pushes other items
     off-screen. */
  .hero { min-height: 0; }
  .composer-zone {
    /* LOCKED — bottom margin signed off by user on 2026-05-16.
       Do NOT change without an explicit instruction. */
    padding-bottom: calc((env(safe-area-inset-bottom, 0px) + 12px) * 2 - 6px);
  }
  .drawer { width: 100vw; }
  .drawer.drawer-left { left: 0; width: 100vw; }
  /* The drawer pins to top:0 so on iOS its header sits behind the
     status bar — the H2 title and close X overlap the clock /
     battery glyphs. Inset the header by the safe-area-top so the
     drawer chrome clears the system UI. The body content below
     the header is unaffected. */
  .drawer-header {
    padding-top: calc(18px + env(safe-area-inset-top, 0px));
  }
  .hero-title { font-size: 17px; }
  /* Hero emblem scales down to suit the narrower viewport — the
     128px desktop size dominates a phone screen. Bumped from 88
     to 106 (~20%) so the brand mark reads stronger above the
     tagline; gap tightened so the tagline sits visually anchored
     to the emblem rather than floating below it. */
  .hero-emblem { width: 106px; height: 106px; }
  .hero { gap: 8px; }
  .topbar-pill span { display: none; }

  /* Phone-only sidebar lifecycle: hide the icon column entirely
     and surface a hamburger in the topbar. The user summons the
     full sidebar on demand; the canvas reclaims the full
     viewport. Tablet+ keeps the original always-visible
     icon-column shape from the rules above this block. */
  .sidebar:not(.expanded) {
    transform: translateX(-100%);
    border-right: 0;
  }
  /* Narrower expanded width on phones — 220px gives 170px+ of
     canvas dimmed behind, which reads as a drawer instead of a
     full-screen takeover. Also pad the top by the iOS safe-area
     inset so the brand wordmark clears the status bar. */
  .sidebar.expanded {
    width: 220px;
    padding-top: calc(8px + env(safe-area-inset-top, 0px) * 0.7);
    box-shadow: 4px 0 30px rgba(0,0,0,0.25);
  }
  body:not(.sidebar-expanded) .topbar { left: 0; }
  .canvas { margin-left: 0; }
  body.sidebar-expanded .topbar,
  body.sidebar-expanded .canvas { left: 0; margin-left: 0; }
  .topbar .topbar-menu-btn { display: inline-flex; }

  /* Mobile chat reclaims the full viewport — desktop constrains
     the trace bubble + user-message bubble to 80% so they don't
     stretch across a wide canvas, but on a 393px iPhone that
     wastes a fifth of the screen and the answer body crowds. */
  .msg-trace { max-width: 100%; }
  .msg.user .msg-content { max-width: 100%; }
  .msg.agent { padding-left: 38px; }
  .msg.user  { padding-right: 38px; }

  /* Composer footer: keep both groups on one line at every
     viewport — the icon set is fixed (Attach / Agent / Skills /
     Tools | Mic / Audio / Video / Send) so we don't need to
     accommodate growth. Tighten the gap so the eight icons
     comfortably fit a 360 px+ phone width without wrapping. */
  .composer-left,
  .composer-right { gap: 0; flex-wrap: nowrap; }
  .composer-icon-btn { width: 32px; height: 32px; }

  /* Center popovers anchored to composer icons (Agent, Skills,
     Tools, Workflows). Desktop anchors them to a single icon
     position (left: 56px) but on a phone the 560px-capped width
     overflows the right edge — center it instead so the whole
     card sits inside the viewport with a uniform side gutter. */
  .agent-popover {
    left: 16px;
    right: 16px;
    width: auto;
    max-width: none;
  }
  /* #protocols-popover pins left: 96px on desktop via an ID selector,
     which (ID > class) would otherwise beat the centering rule above and
     leave the Protocols card narrower + shifted right than the other
     pickers on a phone. Re-pin it to the shared 16px gutter at matching
     specificity so it gets the same full-width centered card. */
  #protocols-popover {
    left: 16px;
  }
  /* Modals (Add a strategy, etc.) — on mobile, drop the box BELOW
     the topbar so its top edge doesn't sit over the menu / theme /
     profile icons. The offset mirrors the locked topbar geometry
     (height = 56 + (env+12)/2) plus an 8px gap, so the modal sits
     just under the topbar regardless of notch size.
     The max-height is sized so the box's bottom edge stops just
     above the composer — the modal can scroll its body, but its
     pinned footer (Save/Cancel) is always visible above the chat
     input rather than overlapping it or running off-screen. */
  .modal {
    align-items: flex-start;
  }
  .modal-box {
    margin-top: calc(56px + (env(safe-area-inset-top, 0px) + 12px) / 2 + 8px);
    padding-top: 16px;
    max-height: calc(
      var(--vvh, 100vh)
      - 56px
      - (env(safe-area-inset-top, 0px) + 12px) / 2
      - 8px
      - env(safe-area-inset-bottom, 0px)
      - 70px
    );
  }
}

/* ========================================================================
   Profile modal — full account settings
   ======================================================================== */

.modal-profile {
  width: min(620px, calc(100vw - 32px));
  padding: 24px;
  position: relative;
  max-height: calc(100vh - 64px);
  overflow-y: auto;
}

.profile-header {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 18px;
}
.profile-header-avatar {
  width: 56px;
  height: 56px;
  font-size: 18px;
  cursor: default;
}
.profile-header-avatar:hover { filter: none; }
.profile-header-text { min-width: 0; }
.profile-header-name {
  margin: 0;
  font-size: 18px;
  font-weight: 600;
  color: var(--fg);
}
.profile-header-sub {
  margin: 2px 0 0;
  font-size: 13px;
  color: var(--fg-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.profile-tabs {
  display: flex;
  gap: 4px;
  position: relative;
  border-bottom: 1px solid var(--line);
  margin-bottom: 18px;
  /* Horizontal scroll fallback so a new tab never pushes the
     rightmost label off-screen. Visible scrollbar would be ugly
     under a tablist; hide it but keep wheel / touch panning. */
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.profile-tabs::-webkit-scrollbar { display: none; }
.profile-tab {
  border: none;
  background: transparent;
  color: var(--fg-muted);
  font: inherit;
  font-size: 13.5px;
  font-weight: 500;
  padding: 10px 14px;
  cursor: pointer;
  border-radius: 0;
  transition: color 0.15s ease;
  /* Prevent label wrapping inside one tab — labels stay on one
     line, the row scrolls when they collectively overflow. */
  white-space: nowrap;
  flex: 0 0 auto;
}
.profile-tab:hover { color: var(--fg); }
.profile-tab.active { color: var(--fg); }
.profile-tab.active::after {
  content: "";
  position: absolute;
  left: 0; right: 0;
  bottom: -1px;
  height: 2px;
  background: var(--fg);
  pointer-events: none;
  /* Per-tab indicator is positioned by the .active class via this
     pseudo. We sit it under just the active button using inset trick:
     it spans the whole strip but the parent's row layout means it
     draws under the active one because we replace it per-click. */
}
/* Replace the spanning pseudo with one anchored to the active tab. */
.profile-tab.active::after {
  left: 14px;
  right: auto;
  width: calc(100% - 28px);
}
.profile-tab-indicator { display: none; }

.profile-status {
  margin: 0 0 12px;
  padding: 8px 12px;
  border-radius: 8px;
  font-size: 13px;
  background: var(--bg-input);
  color: var(--fg);
  border: 1px solid var(--line);
}
.profile-status.err {
  border-color: var(--danger, #d33);
  color: var(--danger, #d33);
}

.profile-pane {
  animation: profile-pane-in 0.18s ease both;
}
@keyframes profile-pane-in {
  from { opacity: 0; transform: translateY(2px); }
  to { opacity: 1; transform: none; }
}

.profile-form { display: flex; flex-direction: column; gap: 14px; }
.profile-form-tight { gap: 10px; margin-top: 24px; }
.profile-h3 {
  margin: 0 0 6px;
  font-size: 14px;
  font-weight: 600;
  color: var(--fg);
}
.profile-field { display: flex; flex-direction: column; gap: 6px; }
.profile-field label {
  font-size: 12.5px;
  font-weight: 500;
  color: var(--fg-muted);
}
.profile-field input[type="text"],
.profile-field input[type="email"],
.profile-field input[type="url"],
.profile-field input[type="password"] {
  width: 100%;
  padding: 9px 12px;
  border-radius: 8px;
  border: 1px solid var(--line);
  background: var(--bg);
  color: var(--fg);
  font: inherit;
  font-size: 14px;
  transition: border-color 0.15s ease;
}
.profile-field input:focus {
  outline: none;
  border-color: var(--fg-muted);
}
.profile-hint {
  margin: 0;
  font-size: 12px;
  color: var(--fg-muted);
}

.profile-radio {
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 12px 14px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 0;
}
.profile-radio legend {
  padding: 0 4px;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--fg-muted);
}
.profile-radio label {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13.5px;
  color: var(--fg);
  cursor: pointer;
}

.profile-actions {
  display: flex;
  justify-content: flex-end;
  margin-top: 4px;
}

.profile-readonly {
  display: grid;
  grid-template-columns: 160px 1fr;
  gap: 8px 16px;
  margin: 0 0 4px;
  font-size: 13.5px;
}
.profile-readonly > div {
  display: contents;
}
.profile-readonly dt {
  color: var(--fg-muted);
}
.profile-readonly dd {
  margin: 0;
  color: var(--fg);
  word-break: break-word;
}

.profile-error {
  margin: 0;
  font-size: 12.5px;
  color: var(--danger, #d33);
}

.profile-note {
  margin: 0 0 12px;
  font-size: 13px;
  color: var(--fg-muted);
}

.profile-sessions { display: flex; flex-direction: column; gap: 8px; }
.profile-api-keys { display: flex; flex-direction: column; gap: 8px; }
.profile-api-key-row .api-key-prefix {
  margin-left: 8px;
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 11.5px;
  padding: 1px 6px;
  border-radius: 4px;
  background: var(--bg-input);
  color: var(--fg-muted);
  font-weight: 400;
}
.api-key-reveal {
  margin: 12px 0 18px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--bg-input);
}
.api-key-code {
  margin: 8px 0;
  padding: 10px 12px;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 8px;
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 12px;
  white-space: pre-wrap;
  word-break: break-all;
  overflow-x: auto;
}
.profile-help {
  margin-top: 18px;
  font-size: 13px;
}
.profile-help summary {
  cursor: pointer;
  color: var(--fg-muted);
  padding: 4px 0;
}
.profile-help > p, .profile-help > pre { margin-top: 8px; }
.profile-sessions-loading { color: var(--fg-muted); font-size: 13px; margin: 0; }
.profile-session-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--bg);
}
.profile-session-row.is-current {
  border-color: var(--fg-muted);
  background: var(--bg-input);
}
.profile-session-main { flex: 1; min-width: 0; }
.profile-session-ua {
  font-size: 13.5px;
  font-weight: 500;
  color: var(--fg);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.profile-session-meta {
  font-size: 12px;
  color: var(--fg-muted);
  margin-top: 2px;
}
.profile-session-tag {
  font-size: 11.5px;
  font-weight: 500;
  color: var(--fg-muted);
  text-transform: uppercase;
  letter-spacing: 0.4px;
  padding: 4px 8px;
  border-radius: 999px;
  border: 1px solid var(--line);
}
.profile-session-revoke { font-size: 12.5px; padding: 6px 12px; }

/* ---------------------------------------------------------------------------
   File Systems page (Track 15)

   The page replaces the chat hero/thread/composer when the URL is
   /file-systems. JS toggles `body[data-route="file-systems"]` so the
   chat surface gets `display:none` and the FS surface becomes
   visible. Three tabs match ImplementingFileSystems_CLAUDE.md.
   --------------------------------------------------------------------------- */
body[data-route="file-systems"] .hero,
body[data-route="file-systems"] .thread,
body[data-route="file-systems"] .composer-zone { display: none !important; }
/* The canvas keeps its 56px topbar offset (.canvas already has padding-top:56px).
   The fs-page is the scroll container — it owns the inner scrolling so the
   topbar stays fixed and clicking a tab doesn't blow up the layout. */
.fs-page {
  padding: 32px 40px 56px;
  max-width: 1080px;
  width: 100%;
  margin: 0 auto;
  box-sizing: border-box;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  align-self: stretch;
}
.fs-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 24px; margin-bottom: 24px; }
.fs-header h1 { margin: 0 0 6px; font-size: 26px; font-weight: 650; letter-spacing: -0.01em; }
.fs-subtitle { margin: 0; color: var(--fg-muted); font-size: 14px; }
.fs-add-btn {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 9px 14px; border-radius: 999px; border: 1px solid var(--line);
  background: var(--fg); color: var(--bg);
  font-size: 13.5px; font-weight: 600; cursor: pointer;
  white-space: nowrap;
}
.fs-add-btn:hover { background: var(--fg-muted); }

.fs-architectural-note {
  margin: -12px 0 24px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--bg-input);
  color: var(--fg-muted);
  font-size: 12.5px;
  line-height: 1.55;
  max-width: 760px;
}
.fs-tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--line); margin-bottom: 24px; }
.fs-tab {
  appearance: none; border: 0; background: transparent; cursor: pointer;
  padding: 10px 14px; font-size: 13.5px; color: var(--fg-muted);
  border-bottom: 2px solid transparent; margin-bottom: -1px;
  font-family: inherit;
}
.fs-tab:hover { color: var(--fg); }
.fs-tab.active { color: var(--fg); border-bottom-color: var(--fg); font-weight: 600; }

.fs-panel { animation: fs-fade 120ms ease-out; }
@keyframes fs-fade { from { opacity: 0; transform: translateY(2px); } to { opacity: 1; transform: none; } }

.fs-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); gap: 16px; }

/* Connector card — section-based layout: header (logo + name +
   status), then ACCESS + SYNC sections, then optional error
   banner, then a clean action footer. Status pill lives in its
   own grid slot so it can never overlap the title text. */
.fs-card {
  display: flex;
  flex-direction: column;
  padding: 18px 18px 14px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--bg-elevated);
}

/* Header: logo | (name + account) | status. CSS grid keeps the
   status pill anchored on the right and prevents the name from
   wrapping under it. */
.fs-card-head {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 12px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 14px;
}
/* Card-head logo container — holds the provider's official SVG
   logo at /static/logos/<provider>.svg. Sized by height so that
   wide wordmarks (Box: 3:1) render at the same visual scale as
   square marks (Google Drive: 1:1). No background tile — the
   official logos carry their own colour. */
.fs-card-logo {
  display: inline-flex;
  align-items: center;
  justify-content: flex-start;
  height: 32px;
  flex-shrink: 0;
}
.fs-card-logo img {
  display: block;
  height: 32px;
  width: auto;
  max-width: 96px;
  object-fit: contain;
}
.fs-card-title {
  min-width: 0;  /* allow ellipsis */
}
.fs-card-name {
  font-size: 14.5px;
  font-weight: 600;
  color: var(--fg);
  margin: 0 0 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.fs-card-account {
  font-size: 12.5px;
  color: var(--fg-muted);
  margin: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.fs-card-status {
  flex-shrink: 0;
  justify-self: end;
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 3px 9px;
  border-radius: 999px;
  border: 1px solid var(--line);
  color: var(--fg-muted);
  background: var(--bg-input);
}
.fs-card-status.is-connected {
  color: #1f7a3a;
  border-color: rgba(31, 122, 58, 0.25);
  background: rgba(31, 122, 58, 0.08);
}
.fs-card-status.is-error {
  color: #b13434;
  border-color: rgba(177, 52, 52, 0.25);
  background: rgba(177, 52, 52, 0.08);
}

/* Sections inside the card — vertical rhythm, no inner borders.
   Each section opens with a small-caps muted label, then key-value
   rows in default text. */
.fs-card-section {
  padding-bottom: 14px;
}
.fs-card-section + .fs-card-section {
  border-top: 1px solid var(--line);
  padding-top: 14px;
}
.fs-card-section-label {
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--fg-muted);
  margin: 0 0 10px;
}
.fs-card-kv {
  display: grid;
  grid-template-columns: 110px 1fr;
  gap: 6px 12px;
  margin: 0;
  font-size: 13px;
}
.fs-card-kv dt {
  color: var(--fg-muted);
  font-weight: 500;
}
.fs-card-kv dd {
  margin: 0;
  color: var(--fg);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Access line — pill + short statement that sits above the
   folder-scope KV. The pill carries the only access fact that
   matters in our model: agents have full read+write on whatever
   folders are in scope. We use a soft green tint so it reads as
   "fully granted" without screaming. */
.fs-card-access-line {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 0 10px;
  font-size: 13px;
  color: var(--fg);
  line-height: 1.4;
}
.fs-access-pill {
  display: inline-flex;
  align-items: center;
  padding: 2px 10px;
  border-radius: 999px;
  background: rgba(46, 160, 67, 0.14);
  color: #2ea043;
  border: 1px solid rgba(46, 160, 67, 0.32);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.01em;
  flex-shrink: 0;
}
.fs-access-line-text {
  color: var(--fg-muted);
}

/* One-liner under the folder-scope KV — explains the current
   folder situation (entire account vs. selected list) and points
   at the Choose-folders button. */
.fs-card-access-help {
  margin: 6px 0 8px;
  font-size: 12.5px;
  color: var(--fg-muted);
  line-height: 1.45;
}

/* Error banner — only rendered when last_error is set. Reads as a
   bordered red-tinted strip so the operator sees the failure
   without it overwhelming the rest of the card. */
.fs-card-error {
  margin-top: 4px;
  margin-bottom: 14px;
  padding: 9px 12px;
  border: 1px solid rgba(177, 52, 52, 0.25);
  background: rgba(177, 52, 52, 0.08);
  color: #b13434;
  border-radius: 8px;
  font-size: 12.5px;
  line-height: 1.4;
}

/* "+ Add another" tile that lives at the end of the fs-cards
   grid. Matches the connected-card shape so it visually belongs,
   but reads as a CTA via the dashed border + centered glyph +
   muted-by-default colour. Hover bumps the border + glyph back
   to the active foreground colour so the affordance is obvious. */
.fs-card-add {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 16px;
  min-height: 112px;
  border: 1px dashed var(--line);
  border-radius: 14px;
  background: transparent;
  color: var(--fg-muted);
  cursor: pointer;
  font: inherit;
  transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}
.fs-card-add:hover {
  border-color: var(--fg-subtle, var(--fg-muted));
  background: var(--bg-elevated);
  color: var(--fg);
}
.fs-add-card-glyph {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: 10px;
  background: var(--bg-input, transparent);
}
.fs-add-card-label {
  font-size: 13.5px;
  font-weight: 600;
  letter-spacing: 0.01em;
}
.fs-card-meta { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px 14px; font-size: 12.5px; color: var(--fg-muted); }
.fs-card-meta strong { color: var(--fg); font-weight: 500; }
.fs-card-actions { display: flex; gap: 8px; margin-top: 4px; }
.fs-btn-ghost, .fs-btn-primary, .fs-btn-danger {
  appearance: none; border: 1px solid var(--line); background: var(--bg);
  color: var(--fg); padding: 7px 12px; border-radius: 8px; font-size: 12.5px;
  cursor: pointer; font-family: inherit;
}
.fs-btn-ghost:hover { background: var(--bg-hover); }
.fs-btn-primary { background: var(--fg); color: var(--bg); border-color: var(--fg); font-weight: 600; }
.fs-btn-primary:hover { background: var(--fg-muted); }
.fs-btn-danger { color: #b13434; border-color: rgba(177, 52, 52, 0.4); }
.fs-btn-danger:hover { background: rgba(177, 52, 52, 0.08); }

.fs-empty {
  padding: 56px 24px; text-align: center; border: 1px dashed var(--line);
  border-radius: 16px; color: var(--fg-muted); display: flex; flex-direction: column;
  align-items: center; gap: 12px;
}
.fs-empty-glyph { color: var(--fg-subtle); }
.fs-empty h2 { margin: 0; font-size: 16px; color: var(--fg); }
.fs-empty p { margin: 0; max-width: 460px; font-size: 13.5px; line-height: 1.55; }

.fs-browse { display: grid; grid-template-columns: 220px 1fr; gap: 24px; }
.fs-browse-side h3 { margin: 0 0 8px; font-size: 12px; font-weight: 600; color: var(--fg-muted); text-transform: uppercase; letter-spacing: 0.4px; }
.fs-browse-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 4px; }
.fs-browse-list li { padding: 8px 10px; border-radius: 8px; cursor: pointer; font-size: 13.5px; color: var(--fg); }
.fs-browse-list li:hover { background: var(--bg-hover); }
.fs-browse-list li.active { background: var(--bg-input); font-weight: 600; }
.fs-browse-list li.empty { color: var(--fg-subtle); cursor: default; font-style: italic; }
.fs-browse-list li.empty:hover { background: transparent; }
.fs-browse-main { min-height: 240px; border: 1px solid var(--line); border-radius: 12px; padding: 20px; background: var(--bg-elevated); }
.fs-browse-empty { color: var(--fg-muted); font-size: 13.5px; }
.fs-file-row { display: flex; align-items: center; gap: 10px; padding: 8px 4px; border-bottom: 1px solid var(--line); font-size: 13.5px; }
.fs-file-row:last-child { border-bottom: 0; }
.fs-file-name { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fs-file-state { font-size: 11px; padding: 2px 8px; border-radius: 999px; background: var(--bg-input); color: var(--fg-muted); }

.fs-indexing { display: flex; flex-direction: column; gap: 16px; }
.fs-indexing-card { border: 1px solid var(--line); border-radius: 14px; padding: 16px 18px; background: var(--bg-elevated); }
.fs-indexing-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
.fs-indexing-name { font-size: 14.5px; font-weight: 600; }
.fs-indexing-counts { font-size: 12.5px; color: var(--fg-muted); }
.fs-perm-grid { display: grid; grid-template-columns: 1fr auto auto; gap: 8px 12px; align-items: center; font-size: 13px; }
.fs-perm-grid > .perm-head { font-size: 11.5px; font-weight: 600; color: var(--fg-muted); text-transform: uppercase; letter-spacing: 0.4px; }
.fs-perm-form { display: flex; gap: 8px; align-items: center; margin-top: 12px; flex-wrap: wrap; }
.fs-perm-form input, .fs-perm-form select {
  padding: 7px 10px; border: 1px solid var(--line); border-radius: 8px;
  background: var(--bg); color: var(--fg); font-size: 13px; font-family: inherit;
}
.fs-perm-form input { min-width: 140px; }

/* Add File System modal */
.modal-box.modal-fs { width: min(560px, 92vw); padding: 24px; }
.fs-modal-body { padding-top: 8px; }
/* HPC cluster preset picker — list-style tiles inside the Add
   HPC cluster modal. Each tile renders the preset's friendly
   label + vendor + canonical host. Two columns on the standard
   modal width so 6 presets fit on one screen. */
.hpc-section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 4px 8px;
}
.hpc-section-title {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--fg-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.hpc-preset-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
  margin-top: 14px;
}
.hpc-preset-tile {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--bg);
  color: var(--fg);
  cursor: pointer;
  font-family: inherit;
  text-align: left;
  transition: background 100ms, border-color 100ms;
}
.hpc-preset-tile:hover {
  background: var(--bg-hover);
  border-color: var(--brand);
}
.hpc-preset-name { font-size: 14px; font-weight: 600; }
.hpc-preset-vendor {
  font-size: 11.5px;
  color: var(--fg-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.hpc-preset-host {
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--fg-muted);
  word-break: break-all;
}

/* Public-key display block — readonly textarea with a Copy button
   to the right. Monospace + word-wrap so the OpenSSH one-liner
   reads cleanly. */
.hpc-key-block {
  position: relative;
  margin: 10px 0;
}
.hpc-key-block textarea {
  width: 100%;
  resize: vertical;
  padding: 10px 12px;
  font-family: var(--mono);
  font-size: 12px;
  background: var(--bg-input);
  color: var(--fg);
  border: 1px solid var(--line);
  border-radius: 8px;
  word-break: break-all;
}
.hpc-key-block button {
  position: absolute;
  top: 8px;
  right: 8px;
}
.hpc-form-row-double {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}
.hpc-form-row-double label {
  display: block;
  font-size: 12.5px;
  color: var(--fg-muted);
  margin-bottom: 4px;
}
.hpc-form-row-double input {
  width: 100%;
}

.fs-provider-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-top: 16px; }
.fs-provider-btn {
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  padding: 18px 10px; border: 1px solid var(--line); border-radius: 12px;
  background: var(--bg); color: var(--fg); cursor: pointer; font-family: inherit;
  transition: background 100ms, border-color 100ms;
}
.fs-provider-btn:hover { background: var(--bg-hover); border-color: var(--line-strong); }
/* Glyph slot — sizes vendor logos by height so square glyphs
   (Google Drive / Dropbox / OneDrive / S3) and wide wordmarks
   (Box "box" wordmark) read with the same visual weight. The
   width is flexible up to a generous cap so wordmarks can breathe.
   Fixed-size width/height attributes on the <img> are overridden
   here so the CSS shape wins.
*/
.fs-provider-glyph {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 40px;
  height: 36px;
}
.fs-provider-glyph img {
  height: 36px;
  width: auto;
  max-width: 110px;
  display: block;
  object-fit: contain;
  /* If the swapped-in logo SVG omits its own background colour
     (transparent + dark-on-light only), the dark-theme tile would
     swallow it. The platform always renders the modal on the
     elevated surface, so we don't need to invert — but we DO add
     a subtle rounded corner so transparent corners blend cleanly. */
  border-radius: 6px;
}
.fs-provider-name { font-size: 13px; font-weight: 500; }

.fs-modal-form { padding-top: 8px; display: flex; flex-direction: column; gap: 14px; }
.fs-form-row { display: flex; flex-direction: column; gap: 6px; }
.fs-form-row label { font-size: 12px; color: var(--fg-muted); font-weight: 600; text-transform: uppercase; letter-spacing: 0.3px; }
.fs-form-row input {
  padding: 10px 12px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--bg); color: var(--fg); font-size: 14px; font-family: inherit;
}
.fs-form-row input:focus { border-color: var(--fg-muted); outline: none; }
.fs-form-provider { font-size: 14px; font-weight: 500; padding: 8px 0; }
.fs-form-note { margin: 0; font-size: 12px; color: var(--fg-subtle); }
.fs-form-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 4px; }


/* ---------------------------------------------------------------------------
   Tools page (Track 16) — same layout language as the File Systems page.
   --------------------------------------------------------------------------- */
body[data-route="tools"] .hero,
body[data-route="tools"] .thread,
body[data-route="tools"] .composer-zone { display: none !important; }

.tools-page {
  padding: 24px 32px 40px;
  max-width: 1280px;
  width: 100%;
  margin: 0 auto;
  box-sizing: border-box;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  align-self: stretch;
  /* Mode palette — matches the Queue Jobs page tokens so the
     two pages read as siblings. */
  --tools-live: #1f7a3a;
  --tools-live-soft: rgba(31, 122, 58, 0.10);
  --tools-demo: #b3691f;
  --tools-demo-soft: rgba(179, 105, 31, 0.10);
  --tools-offline: #6e6e76;
  --tools-offline-soft: rgba(110, 110, 118, 0.10);
  --tools-local: var(--brand);
  --tools-local-soft: var(--brand-soft);
}
[data-theme="dark"] .tools-page {
  --tools-live: #6fc191;
  --tools-live-soft: rgba(111, 193, 145, 0.14);
  --tools-demo: #e9a55b;
  --tools-demo-soft: rgba(233, 165, 91, 0.14);
  --tools-offline: #9b9ba0;
  --tools-offline-soft: rgba(155, 155, 160, 0.12);
}

.tools-header {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 24px; margin-bottom: 18px;
}
.tools-header-text h1 { margin: 0 0 6px; font-size: 26px; font-weight: 650; letter-spacing: -0.01em; }
.tools-subtitle { margin: 0; color: var(--fg-muted); font-size: 14px; max-width: 760px; line-height: 1.55; }
.tools-header-stats {
  display: flex; flex-direction: column; align-items: flex-end;
  gap: 2px; flex-shrink: 0; padding: 8px 16px;
  background: var(--bg-input); border: 1px solid var(--line);
  border-radius: 10px;
}
.tools-stat-num {
  font-size: 22px; font-weight: 600; color: var(--fg);
  font-variant-numeric: tabular-nums; line-height: 1.1;
}
.tools-stat-label {
  font-size: 11px; color: var(--fg-muted);
  text-transform: uppercase; letter-spacing: 0.05em; font-weight: 600;
}

/* ----- Filter row ----- */
.tools-filters {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  margin-bottom: 18px; padding: 6px;
  background: var(--bg-soft); border: 1px solid var(--line);
  border-radius: 12px;
}
.tools-filter {
  appearance: none; cursor: pointer; font-family: inherit;
  display: inline-flex; align-items: center; gap: 7px;
  padding: 6px 12px; border-radius: 8px; border: 1px solid transparent;
  background: transparent; color: var(--fg-muted); font-size: 13px;
}
.tools-filter:hover { background: var(--bg-hover); color: var(--fg); }
.tools-filter.active {
  background: var(--bg-elevated); color: var(--fg);
  border-color: var(--line-strong); font-weight: 600;
  box-shadow: var(--shadow-sm);
}
.tools-filter-count {
  font-size: 11px; padding: 1px 7px; border-radius: 999px;
  background: var(--bg-input); color: var(--fg-muted); min-width: 18px;
  text-align: center; font-variant-numeric: tabular-nums;
}
.tools-filter.active .tools-filter-count { color: var(--fg); }
.tools-filter-spacer { flex: 1 1 auto; min-width: 12px; }
.tools-search {
  display: inline-flex; align-items: center; gap: 6px; padding: 0 10px;
  border: 1px solid var(--line); border-radius: 8px;
  background: var(--bg); color: var(--fg-muted);
  min-width: 240px; max-width: 300px;
}
.tools-search:focus-within { border-color: var(--brand); }
.tools-search input {
  flex: 1; appearance: none; border: 0; background: transparent;
  padding: 7px 0; font-size: 13px; color: var(--fg);
  font-family: inherit; min-width: 0;
}
.tools-search input::placeholder { color: var(--fg-subtle); }

/* ----- Category sections ----- */
.tools-content { display: flex; flex-direction: column; gap: 24px; }
.tools-section {
  display: flex; flex-direction: column; gap: 12px;
}
.tools-section-head {
  display: flex; align-items: baseline; gap: 10px;
  padding-bottom: 8px; border-bottom: 1px solid var(--line);
}
.tools-section-title {
  margin: 0; font-size: 15px; font-weight: 650; color: var(--fg);
  letter-spacing: -0.005em;
}
.tools-section-count {
  font-size: 12px; color: var(--fg-subtle);
  font-variant-numeric: tabular-nums;
}
.tools-section-desc {
  font-size: 12.5px; color: var(--fg-muted); margin-left: auto;
}

.tools-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
  gap: 12px;
}

.tool-card {
  display: flex; flex-direction: column; gap: 12px;
  padding: 16px 18px; border: 1px solid var(--line); border-radius: 12px;
  background: var(--bg-elevated);
  transition: border-color 120ms, box-shadow 120ms;
  min-width: 0;
}
.tool-card:hover {
  border-color: var(--line-strong);
  box-shadow: var(--shadow-sm);
}
.tool-card.has-demo-mode { background: var(--bg-soft); }
.tool-card-head { display: flex; align-items: flex-start; gap: 12px; min-width: 0; }
.tool-card-glyph {
  width: 36px; height: 36px; border-radius: 10px;
  background: var(--bg-input); color: var(--fg);
  display: grid; place-items: center; flex-shrink: 0;
}
.tool-card.mode-live .tool-card-glyph { color: var(--tools-live); background: var(--tools-live-soft); }
.tool-card.mode-demo .tool-card-glyph { color: var(--tools-demo); background: var(--tools-demo-soft); }
.tool-card.mode-offline .tool-card-glyph { color: var(--tools-offline); background: var(--tools-offline-soft); }
.tool-card.mode-local .tool-card-glyph { color: var(--tools-local); background: var(--tools-local-soft); }
.tool-card-title { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.tool-card-name {
  font-size: 14.5px; font-weight: 600; color: var(--fg); margin: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tool-card-id {
  font-family: var(--mono); font-size: 11px; color: var(--fg-subtle);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tool-card-mode {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 10.5px; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.04em;
  padding: 2px 8px; border-radius: 999px;
  background: var(--bg-input); color: var(--fg-muted);
  border: 1px solid var(--line); white-space: nowrap;
  flex-shrink: 0; align-self: flex-start;
}
.tool-card-mode::before {
  content: ""; width: 5px; height: 5px; border-radius: 50%;
  background: currentColor; flex-shrink: 0;
}
.tool-card.mode-live .tool-card-mode { color: var(--tools-live); background: var(--tools-live-soft); border-color: color-mix(in srgb, var(--tools-live) 28%, transparent); }
.tool-card.mode-demo .tool-card-mode { color: var(--tools-demo); background: var(--tools-demo-soft); border-color: color-mix(in srgb, var(--tools-demo) 28%, transparent); }
.tool-card.mode-offline .tool-card-mode { color: var(--tools-offline); background: var(--tools-offline-soft); border-color: color-mix(in srgb, var(--tools-offline) 25%, transparent); }
.tool-card.mode-local .tool-card-mode { color: var(--tools-local); background: var(--tools-local-soft); border-color: color-mix(in srgb, var(--tools-local) 28%, transparent); }
.tool-card-desc {
  font-size: 12.5px; color: var(--fg-muted); margin: 0; line-height: 1.55;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3;
  overflow: hidden;
}
.tool-card-meta {
  display: flex; flex-wrap: wrap; gap: 6px;
}
.tool-card-pill {
  font-size: 10.5px; color: var(--fg-muted);
  background: var(--bg-input); padding: 2px 7px;
  border-radius: 999px; border: 1px solid var(--line);
  font-variant-numeric: tabular-nums;
}
.tool-card-pill.risk-destructive,
.tool-card-pill.risk-external_egress,
.tool-card-pill.risk-lab_action {
  color: var(--tools-demo);
  background: var(--tools-demo-soft);
  border-color: color-mix(in srgb, var(--tools-demo) 25%, transparent);
}
.tool-card-pill.risk-read_only { color: var(--tools-live); background: var(--tools-live-soft); border-color: color-mix(in srgb, var(--tools-live) 25%, transparent); }
.tool-card-config {
  display: grid; grid-template-columns: max-content 1fr; gap: 3px 12px;
  font-size: 12px; color: var(--fg-muted);
  padding-top: 10px; border-top: 1px dashed var(--line);
}
.tool-card-config dt { font-weight: 500; color: var(--fg); }
.tool-card-config dd { margin: 0; }
.tool-card-config .ok { color: var(--tools-live); }
.tool-card-config .miss { color: var(--tools-demo); }

.tools-empty {
  padding: 40px 24px; text-align: center; color: var(--fg-muted);
  border: 1px dashed var(--line); border-radius: 12px;
}
.tools-empty h2 { margin: 0 0 6px; font-size: 15px; color: var(--fg); font-weight: 600; }
.tools-empty p { margin: 0; font-size: 13px; line-height: 1.55; }

.tool-try {
  display: flex; gap: 8px; align-items: center;
  border-top: 1px solid var(--line); padding-top: 12px; margin-top: 4px;
}
.tool-try input[type="text"] {
  flex: 1; padding: 8px 12px; border: 1px solid var(--line); border-radius: 8px;
  background: var(--bg); color: var(--fg); font-size: 13.5px; font-family: inherit;
}
.tool-try input[type="text"]:focus { border-color: var(--fg-muted); outline: none; }
.tool-try-results { font-size: 12.5px; color: var(--fg-muted); }
.tool-try-result-row {
  display: flex; flex-direction: column; gap: 2px;
  padding: 8px 0; border-bottom: 1px solid var(--line);
}
.tool-try-result-row:last-child { border-bottom: 0; }
.tool-try-result-title { font-size: 13px; font-weight: 600; color: var(--fg); }
.tool-try-result-title a { color: inherit; text-decoration: none; }
.tool-try-result-title a:hover { text-decoration: underline; }
.tool-try-result-snippet { color: var(--fg-muted); }
.tool-try-result-meta { font-size: 11.5px; color: var(--fg-subtle); }

/* ---------------------------------------------------------------------------
   Chronicle module — capture layer page (/chronicle).

   Same visual idiom as File Systems / Tools: hide the chat hero/thread/
   composer when this route is active; the page itself is a 280px-rail +
   workspace shell with tabbed panels for Overview / Timeline / Evidence /
   Notebook. Reuses the platform's shared tokens (--brand, --line, --bg,
   --bg-soft, --fg, --fg-muted) so light/dark toggles work for free.
   --------------------------------------------------------------------------- */
body[data-route="chronicle"] .hero,
body[data-route="chronicle"] .thread,
body[data-route="chronicle"] .composer-zone { display: none !important; }

.chronicle-page {
  padding: 24px 32px 40px;
  width: 100%;
  margin: 0;
  box-sizing: border-box;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  align-self: stretch;
}
.chronicle-header {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 24px; margin-bottom: 18px; max-width: 1200px;
}
.chronicle-header-text h1 { margin: 0 0 4px; font-size: 22px; font-weight: 700; }
.chronicle-subtitle { margin: 0; color: var(--fg-muted); font-size: 13px; max-width: 720px; }
.chronicle-header-actions { display: flex; gap: 8px; }

.chronicle-shell {
  display: grid;
  grid-template-columns: 280px 1fr;
  gap: 20px;
  align-items: stretch;
}

/* ----- left rail: projects + experiments ----- */
.chronicle-rail {
  background: var(--bg-soft);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 12px;
  min-height: 480px;
}
.chronicle-empty { text-align: center; padding: 24px 12px; }
.chronicle-empty h2 { margin: 0 0 6px; font-size: 14px; }
.chronicle-empty p { margin: 0 0 12px; color: var(--fg-muted); font-size: 12px; }

.chronicle-projects { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 4px; }
.chronicle-project { border-radius: 8px; }
.chronicle-project-head {
  display: flex; align-items: center; gap: 6px;
  width: 100%;
  padding: 6px 8px;
  background: transparent; border: 0; border-radius: 6px;
  color: var(--fg); cursor: pointer; text-align: left;
  font-size: 13px; font-weight: 600;
}
.chronicle-project-head:hover { background: var(--brand-soft); }
.chronicle-project-caret { font-size: 10px; color: var(--fg-muted); width: 10px; }
.chronicle-project-experiments { list-style: none; margin: 0 0 6px 16px; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.chronicle-experiment-empty { color: var(--fg-subtle); font-size: 12px; padding: 4px 8px; }
.chronicle-experiment-btn {
  display: flex; align-items: center; justify-content: space-between;
  width: 100%; padding: 6px 8px;
  background: transparent; border: 0; border-radius: 6px;
  color: var(--fg); cursor: pointer; text-align: left; font-size: 12.5px;
}
.chronicle-experiment-btn:hover { background: var(--brand-soft); }
.chronicle-experiment.active .chronicle-experiment-btn { background: var(--brand-soft); color: var(--brand); }
.chronicle-experiment-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 160px; }
.chronicle-experiment-status {
  font-family: var(--mono);
  font-size: 9.5px; text-transform: uppercase;
  color: var(--fg-muted);
  padding: 1px 5px;
  border: 1px solid var(--line);
  border-radius: 4px;
}
.chronicle-experiment-status.status-running,
.chronicle-experiment-status.status-completed { color: var(--brand); border-color: var(--brand); }
.chronicle-experiment-status.status-signed { color: #10a06b; border-color: #10a06b; }
.chronicle-add-experiment-btn {
  width: 100%; text-align: left; padding: 4px 8px;
  background: transparent; border: 0; color: var(--fg-muted);
  cursor: pointer; font-size: 12px;
}
.chronicle-add-experiment-btn:hover { color: var(--brand); }

/* ----- right pane ----- */
.chronicle-workspace {
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 24px;
  min-height: 480px;
  display: flex; flex-direction: column;
}
.chronicle-workspace-empty { text-align: center; padding: 64px 16px; color: var(--fg-muted); }
.chronicle-workspace-empty h2 { margin: 0 0 6px; color: var(--fg); }
.chronicle-exp-header { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; margin-bottom: 12px; }
.chronicle-exp-header h2 { margin: 0; font-size: 18px; }
.chronicle-exp-meta { margin: 4px 0 0; color: var(--fg-muted); font-size: 12px; }
.chronicle-exp-actions { display: flex; gap: 8px; align-items: center; }
.chronicle-status-select {
  border: 1px solid var(--line); border-radius: 6px;
  background: var(--bg); color: var(--fg);
  padding: 4px 8px; font-size: 12px;
}

.chronicle-tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--line); margin-bottom: 18px; }
.chronicle-tab {
  background: transparent; border: 0; padding: 10px 14px;
  font-size: 13px; color: var(--fg-muted); cursor: pointer;
  border-bottom: 2px solid transparent;
}
.chronicle-tab:hover { color: var(--fg); }
.chronicle-tab.active { color: var(--brand); border-bottom-color: var(--brand); font-weight: 600; }

.chronicle-panel { display: block; }

/* ----- overview ----- */
.chronicle-overview-grid { display: flex; flex-direction: column; gap: 12px; max-width: 720px; }
.chronicle-field { display: flex; flex-direction: column; gap: 4px; }
.chronicle-field span { font-size: 12px; font-weight: 600; color: var(--fg-muted); }
.chronicle-field input,
.chronicle-field textarea {
  border: 1px solid var(--line); border-radius: 6px;
  background: var(--bg); color: var(--fg);
  padding: 8px 10px; font-size: 13px; font-family: inherit;
  resize: vertical;
}
.chronicle-field input:focus,
.chronicle-field textarea:focus { outline: 2px solid var(--brand); outline-offset: -1px; border-color: transparent; }

.chronicle-quick-capture {
  margin-top: 28px;
  padding: 16px;
  border: 1px dashed var(--line);
  border-radius: 8px;
  background: var(--bg-soft);
  max-width: 720px;
}
.chronicle-quick-capture h3 { margin: 0 0 4px; font-size: 14px; }
.chronicle-quick-help { margin: 0 0 10px; font-size: 12px; color: var(--fg-muted); }
.chronicle-event-type, .chronicle-quick-text {
  width: 100%; box-sizing: border-box; font-size: 13px;
  border: 1px solid var(--line); border-radius: 6px;
  background: var(--bg); color: var(--fg); padding: 8px 10px;
  margin-bottom: 8px; font-family: inherit;
}

/* ----- timeline ----- */
.chronicle-timeline { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 6px; }
.chronicle-timeline-empty { color: var(--fg-muted); padding: 24px 0; text-align: center; }
.chronicle-timeline-row {
  display: grid;
  grid-template-columns: 200px 1fr 140px;
  gap: 12px; padding: 10px 12px;
  border: 1px solid var(--line); border-radius: 6px;
  background: var(--bg);
  align-items: start;
}
.chronicle-timeline-meta { display: flex; flex-direction: column; gap: 2px; }
.chronicle-timeline-time { font-size: 12px; color: var(--fg-muted); }
.chronicle-timeline-actor { font-size: 12px; color: var(--fg); font-weight: 500; }
.chronicle-timeline-body { display: flex; flex-direction: column; gap: 2px; }
.chronicle-timeline-type {
  font-family: var(--mono); font-size: 10.5px;
  color: var(--brand); text-transform: uppercase; letter-spacing: 0.04em;
}
.chronicle-timeline-summary { font-size: 13px; color: var(--fg); white-space: pre-wrap; word-break: break-word; }
.chronicle-timeline-hash {
  font-family: var(--mono); font-size: 10.5px;
  color: var(--fg-subtle); text-align: right;
}

/* ----- evidence ----- */
.chronicle-evidence-form {
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--bg-soft);
  margin-bottom: 18px;
  max-width: 720px;
  display: flex; flex-direction: column; gap: 8px;
}
.chronicle-evidence-form h3 { margin: 0 0 4px; font-size: 14px; }
.chronicle-evidence-form input,
.chronicle-evidence-form textarea,
.chronicle-evidence-form select {
  width: 100%; box-sizing: border-box;
  border: 1px solid var(--line); border-radius: 6px;
  background: var(--bg); color: var(--fg);
  padding: 8px 10px; font-size: 13px; font-family: inherit;
}
.chronicle-evidence-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 8px; }
.chronicle-evidence-empty { color: var(--fg-muted); padding: 16px 0; }
.chronicle-evidence-row {
  border: 1px solid var(--line); border-radius: 6px;
  padding: 10px 12px;
  background: var(--bg);
}
.chronicle-evidence-head { display: flex; justify-content: space-between; align-items: center; }
.chronicle-evidence-name { font-size: 13.5px; font-weight: 600; color: var(--fg); }
.chronicle-evidence-type {
  font-family: var(--mono); font-size: 10.5px;
  color: var(--fg-muted); text-transform: lowercase;
}
.chronicle-evidence-sub { font-size: 11.5px; color: var(--fg-muted); margin-top: 4px; }
.chronicle-evidence-preview {
  margin: 8px 0 0; padding: 8px 10px;
  background: var(--bg-soft); border-radius: 4px;
  font-family: var(--mono); font-size: 11.5px;
  color: var(--fg); white-space: pre-wrap; word-break: break-word;
}

/* ----- notebook ----- */
.chronicle-notebook-actions { display: flex; gap: 12px; align-items: center; margin-bottom: 16px; }
.chronicle-projection-meta { color: var(--fg-muted); font-size: 12px; }
.chronicle-notebook-body {
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 24px 32px;
  background: var(--bg);
  line-height: 1.55;
  max-width: 880px;
}
.chronicle-notebook-body h1 { font-size: 20px; margin: 0 0 12px; }
.chronicle-notebook-body h2 { font-size: 15px; margin: 18px 0 8px; color: var(--brand); }
.chronicle-notebook-body p { margin: 0 0 8px; }
.chronicle-notebook-body ul { margin: 0 0 12px 16px; padding: 0; }
.chronicle-notebook-body li { margin-bottom: 4px; font-size: 13.5px; }
.chronicle-notebook-body code {
  font-family: var(--mono); font-size: 12px;
  background: var(--bg-soft); padding: 1px 4px; border-radius: 3px;
}
.chronicle-notebook-empty { color: var(--fg-muted); }

/* ----- Chronicle Quality panel (spec §10.2 + §10.3) -------------------------- */
.chronicle-quality-score { margin-bottom: 24px; }
.chronicle-quality-score h3,
.chronicle-quality-gaps h3 { margin: 0 0 12px; font-size: 14px; }
.chronicle-score-row {
  display: flex; align-items: baseline; gap: 12px;
  margin-bottom: 14px;
}
.chronicle-score-number {
  font-size: 36px; font-weight: 700; color: var(--brand);
  font-variant-numeric: tabular-nums;
}
.chronicle-score-level {
  font-family: var(--mono);
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em;
  padding: 3px 8px; border-radius: 4px;
  background: var(--bg-soft); color: var(--fg-muted); border: 1px solid var(--line);
}
.chronicle-score-level[data-level="excellent"] { color: #10a06b; border-color: #10a06b; }
.chronicle-score-level[data-level="good"] { color: var(--brand); border-color: var(--brand); }
.chronicle-score-level[data-level="fair"] { color: #c97a06; border-color: #c97a06; }
.chronicle-score-level[data-level="poor"] { color: #c0382e; border-color: #c0382e; }
.chronicle-score-criteria { list-style: none; padding: 0; margin: 0; max-width: 720px; }
.chronicle-criterion {
  display: grid; grid-template-columns: 1fr auto;
  gap: 4px 12px;
  padding: 8px 12px; border: 1px solid var(--line); border-radius: 6px;
  margin-bottom: 6px; background: var(--bg);
}
.chronicle-criterion.ok { border-color: var(--brand); }
.chronicle-criterion.miss { opacity: 0.85; }
.chronicle-criterion-label { font-size: 13px; font-weight: 500; }
.chronicle-criterion-points {
  font-family: var(--mono); font-size: 12px;
  color: var(--fg-muted); text-align: right;
}
.chronicle-criterion-rationale {
  grid-column: 1 / -1;
  font-size: 12px; color: var(--fg-muted);
}

.chronicle-gap-list { list-style: none; padding: 0; margin: 0; max-width: 720px; }
.chronicle-gap-empty { color: var(--fg-muted); padding: 12px 0; }
.chronicle-gap {
  border: 1px solid var(--line); border-radius: 6px;
  padding: 10px 12px; margin-bottom: 8px; background: var(--bg);
}
.chronicle-gap-head { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
.chronicle-gap-severity {
  font-family: var(--mono); font-size: 10px;
  text-transform: uppercase; letter-spacing: 0.06em;
  padding: 2px 6px; border-radius: 3px;
}
.sev-blocking { background: #c0382e; color: white; }
.sev-warning { background: #c97a06; color: white; }
.sev-info { background: var(--fg-subtle); color: white; }
.chronicle-gap-code { font-family: var(--mono); font-size: 11px; color: var(--fg-muted); }
.chronicle-gap-desc { font-size: 13px; color: var(--fg); }
.chronicle-gap-action { font-size: 12px; color: var(--fg-muted); margin-top: 4px; font-style: italic; }

/* ---------------------------------------------------------------------------
   Queue Jobs dashboard — compute job queue surfacing the
   SimulationGateway state machine. Matches the File Systems / Tools /
   Chronicle pages' chrome: sidebar entry pushes the URL, body
   [data-route="queue-jobs"] hides the chat hero/thread/composer, and
   the page is its own scroll container so the topbar stays fixed.

   Status colour tokens are scoped to the page (--qj-*) instead of
   bleeding into the global palette: lots of pages have a "running"
   colour and we don't want them all coupled to this one.
   --------------------------------------------------------------------------- */
body[data-route="queue-jobs"] .hero,
body[data-route="queue-jobs"] .thread,
body[data-route="queue-jobs"] .thread-footer,
body[data-route="queue-jobs"] .composer-zone { display: none !important; }

.queue-jobs-page {
  padding: 24px 32px 40px;
  max-width: 1280px;
  width: 100%;
  margin: 0 auto;
  box-sizing: border-box;
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
  align-self: stretch;
  display: flex;
  flex-direction: column;
  /* Status palette. Picked to read clearly in both themes:
     queued = neutral blue-grey, running = brand, completed =
     muted green, failed = muted red, cancelled = subtle grey. */
  --qj-active: var(--brand);
  --qj-active-soft: var(--brand-soft);
  --qj-queued: #8a8a92;
  --qj-queued-soft: rgba(138, 138, 146, 0.14);
  --qj-running: var(--brand);
  --qj-running-soft: var(--brand-soft);
  --qj-completed: #1f7a3a;
  --qj-completed-soft: rgba(31, 122, 58, 0.10);
  --qj-failed: #b13434;
  --qj-failed-soft: rgba(177, 52, 52, 0.10);
  --qj-cancelled: #6e6e76;
  --qj-cancelled-soft: rgba(110, 110, 118, 0.10);
}
[data-theme="dark"] .queue-jobs-page {
  --qj-completed: #6fc191;
  --qj-completed-soft: rgba(111, 193, 145, 0.14);
  --qj-failed: #f08585;
  --qj-failed-soft: rgba(240, 133, 133, 0.14);
  --qj-queued: #a8a8b0;
  --qj-queued-soft: rgba(168, 168, 176, 0.12);
  --qj-cancelled: #9b9ba0;
  --qj-cancelled-soft: rgba(155, 155, 160, 0.12);
}

.qj-header {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 24px; margin-bottom: 16px;
}
.qj-header-text h1 { margin: 0 0 6px; font-size: 26px; font-weight: 650; letter-spacing: -0.01em; }
.qj-subtitle { margin: 0; color: var(--fg-muted); font-size: 14px; max-width: 760px; line-height: 1.55; }
.qj-header-actions { display: flex; align-items: center; gap: 12px; flex-shrink: 0; }
.qj-poll-status {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; color: var(--fg-muted); padding: 4px 10px;
  border-radius: 999px; background: var(--bg-input);
  border: 1px solid var(--line);
}
.qj-poll-dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--fg-subtle); transition: background 200ms;
}
.qj-poll-status[data-state="live"] .qj-poll-dot { background: var(--qj-running); box-shadow: 0 0 0 3px var(--qj-running-soft); }
.qj-poll-status[data-state="error"] .qj-poll-dot { background: var(--qj-failed); }

.qj-btn-ghost, .qj-btn-danger, .qj-btn-primary {
  appearance: none; cursor: pointer; font-family: inherit;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 12px; border-radius: 8px;
  border: 1px solid var(--line); background: var(--bg);
  color: var(--fg); font-size: 13px; font-weight: 500;
  transition: background 120ms, color 120ms, border-color 120ms;
}
.qj-btn-ghost:hover { background: var(--bg-hover); }
.qj-btn-primary { background: var(--fg); color: var(--bg); border-color: var(--fg); font-weight: 600; }
.qj-btn-primary:hover { background: var(--fg-muted); }
.qj-btn-danger { color: var(--qj-failed); border-color: color-mix(in srgb, var(--qj-failed) 35%, var(--line)); }
.qj-btn-danger:hover { background: var(--qj-failed-soft); }

/* ----- Filter chip row ----- */
.qj-filters {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  margin-bottom: 18px; padding: 6px;
  background: var(--bg-soft); border: 1px solid var(--line);
  border-radius: 12px;
}
.qj-filter {
  appearance: none; cursor: pointer; font-family: inherit;
  display: inline-flex; align-items: center; gap: 7px;
  padding: 6px 12px; border-radius: 8px; border: 1px solid transparent;
  background: transparent; color: var(--fg-muted); font-size: 13px;
}
.qj-filter:hover { background: var(--bg-hover); color: var(--fg); }
.qj-filter.active {
  background: var(--bg-elevated); color: var(--fg);
  border-color: var(--line-strong); font-weight: 600;
  box-shadow: var(--shadow-sm);
}
.qj-filter-count {
  font-size: 11px; padding: 1px 7px; border-radius: 999px;
  background: var(--bg-input); color: var(--fg-muted); min-width: 18px;
  text-align: center; font-variant-numeric: tabular-nums;
}
.qj-filter.active .qj-filter-count { background: var(--bg-input); color: var(--fg); }
.qj-filter-dot {
  width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0;
  background: var(--fg-subtle);
}
.qj-filter-dot.is-active   { background: var(--qj-running); box-shadow: 0 0 0 3px var(--qj-running-soft); }
.qj-filter-dot.is-queued   { background: var(--qj-queued); }
.qj-filter-dot.is-running  { background: var(--qj-running); box-shadow: 0 0 0 3px var(--qj-running-soft); }
.qj-filter-dot.is-completed{ background: var(--qj-completed); }
.qj-filter-dot.is-failed   { background: var(--qj-failed); }
.qj-filter-dot.is-cancelled{ background: var(--qj-cancelled); }

.qj-filter-spacer { flex: 1 1 auto; min-width: 12px; }
.qj-search {
  display: inline-flex; align-items: center; gap: 6px; padding: 0 10px;
  border: 1px solid var(--line); border-radius: 8px;
  background: var(--bg); color: var(--fg-muted);
  min-width: 220px; max-width: 280px;
}
.qj-search:focus-within { border-color: var(--brand); }
.qj-search input {
  flex: 1; appearance: none; border: 0; background: transparent;
  padding: 7px 0; font-size: 13px; color: var(--fg);
  font-family: inherit; min-width: 0;
}
.qj-search input::placeholder { color: var(--fg-subtle); }

/* ----- Two-pane shell ----- */
.qj-shell {
  display: grid; grid-template-columns: 360px 1fr;
  gap: 16px; flex: 1 1 auto; min-height: 0;
}
@media (max-width: 1024px) {
  .qj-shell { grid-template-columns: 1fr; }
}

/* ----- List pane ----- */
.qj-list-pane {
  border: 1px solid var(--line); border-radius: 12px;
  background: var(--bg-elevated); overflow: hidden;
  display: flex; flex-direction: column; min-height: 0;
}
.qj-list { list-style: none; padding: 6px; margin: 0; overflow-y: auto; flex: 1; }
.qj-list-row {
  display: flex; flex-direction: column; gap: 6px;
  padding: 10px 12px; border-radius: 9px; cursor: pointer;
  border: 1px solid transparent;
  transition: background 100ms, border-color 100ms;
}
.qj-list-row:hover { background: var(--bg-hover); }
.qj-list-row.selected {
  background: var(--brand-soft);
  border-color: color-mix(in srgb, var(--brand) 25%, transparent);
}
.qj-list-row + .qj-list-row { margin-top: 2px; }
.qj-list-row-head {
  display: flex; align-items: center; gap: 8px;
}
.qj-list-row-tool {
  flex: 1; min-width: 0; font-size: 13.5px; font-weight: 600; color: var(--fg);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.qj-list-row-meta {
  display: flex; gap: 8px; align-items: center;
  font-size: 11.5px; color: var(--fg-muted);
  font-variant-numeric: tabular-nums;
}
.qj-list-row-meta code {
  font-family: var(--mono); font-size: 10.5px;
  color: var(--fg-subtle); background: var(--bg-input);
  padding: 1px 6px; border-radius: 4px;
}

/* Mini progress sliver — for jobs that have started but aren't
   terminal, paint a soft brand bar that pulses; for terminal jobs,
   paint a static bar coloured by outcome. */
.qj-list-row-bar {
  height: 3px; border-radius: 2px; background: var(--bg-input);
  overflow: hidden; position: relative;
}
.qj-list-row-bar-fill {
  position: absolute; inset: 0; border-radius: 2px;
  background: var(--qj-completed);
}
.qj-list-row-bar-fill.is-active {
  background: linear-gradient(90deg,
    transparent 0%, var(--qj-running) 50%, transparent 100%);
  background-size: 200% 100%;
  animation: qj-progress-sweep 1.6s ease-in-out infinite;
}
.qj-list-row-bar-fill.is-failed   { background: var(--qj-failed); }
.qj-list-row-bar-fill.is-cancelled{ background: var(--qj-cancelled); }
.qj-list-row-bar-fill.is-queued   { background: var(--qj-queued); opacity: 0.55; }
@keyframes qj-progress-sweep {
  0%   { background-position: 100% 0%; }
  100% { background-position: -100% 0%; }
}

/* ----- Status pill (shared by list + detail) ----- */
.qj-status-pill {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 10.5px; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.04em;
  padding: 2px 8px; border-radius: 999px;
  background: var(--bg-input); color: var(--fg-muted);
  border: 1px solid var(--line);
  white-space: nowrap; flex-shrink: 0;
  font-variant-numeric: tabular-nums;
}
.qj-status-pill::before {
  content: ""; width: 5px; height: 5px; border-radius: 50%;
  background: currentColor; flex-shrink: 0;
}
.qj-status-pill[data-bucket="queued"]    { color: var(--qj-queued);    background: var(--qj-queued-soft);    border-color: color-mix(in srgb, var(--qj-queued) 25%, transparent); }
.qj-status-pill[data-bucket="running"]   { color: var(--qj-running);   background: var(--qj-running-soft);   border-color: color-mix(in srgb, var(--qj-running) 25%, transparent); }
.qj-status-pill[data-bucket="completed"] { color: var(--qj-completed); background: var(--qj-completed-soft); border-color: color-mix(in srgb, var(--qj-completed) 28%, transparent); }
.qj-status-pill[data-bucket="failed"]    { color: var(--qj-failed);    background: var(--qj-failed-soft);    border-color: color-mix(in srgb, var(--qj-failed) 28%, transparent); }
.qj-status-pill[data-bucket="cancelled"] { color: var(--qj-cancelled); background: var(--qj-cancelled-soft); border-color: color-mix(in srgb, var(--qj-cancelled) 25%, transparent); }
.qj-status-pill[data-bucket="running"]::before {
  animation: qj-pulse 1.4s ease-in-out infinite;
}
@keyframes qj-pulse {
  0%, 100% { box-shadow: 0 0 0 0 currentColor; opacity: 1; }
  50%      { box-shadow: 0 0 0 3px transparent; opacity: 0.55; }
}

/* ----- Detail pane ----- */
.qj-detail-pane {
  border: 1px solid var(--line); border-radius: 12px;
  background: var(--bg-elevated);
  display: flex; flex-direction: column; min-height: 0;
  overflow: hidden;
}
.qj-detail-empty {
  flex: 1; display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  text-align: center; padding: 48px 32px; gap: 12px;
  color: var(--fg-muted);
}
.qj-detail-empty-glyph { color: var(--fg-subtle); }
.qj-detail-empty h2 { margin: 0; font-size: 17px; color: var(--fg); font-weight: 600; }
.qj-detail-empty p { margin: 0; max-width: 380px; font-size: 13.5px; line-height: 1.55; }

.qj-detail {
  display: flex; flex-direction: column; min-height: 0; flex: 1;
}
.qj-detail-head {
  display: flex; gap: 16px; justify-content: space-between; align-items: flex-start;
  padding: 18px 22px 14px; border-bottom: 1px solid var(--line);
}
.qj-detail-head-text { min-width: 0; flex: 1; }
.qj-detail-title-row { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }
.qj-detail-title {
  margin: 0; font-size: 18px; font-weight: 650;
  color: var(--fg); letter-spacing: -0.005em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  min-width: 0;
}
.qj-detail-meta {
  display: flex; gap: 6px; align-items: center; flex-wrap: wrap;
  font-size: 12px; color: var(--fg-muted);
}
.qj-meta-sep { color: var(--fg-subtle); }
.qj-job-id {
  font-family: var(--mono); font-size: 11.5px; color: var(--fg);
  background: var(--bg-input); padding: 2px 8px; border-radius: 5px;
}
.qj-detail-actions { display: flex; gap: 8px; flex-shrink: 0; }
.qj-detail-error {
  margin: 12px 22px 0;
  padding: 10px 12px; border-radius: 8px;
  background: var(--qj-failed-soft); color: var(--qj-failed);
  border: 1px solid color-mix(in srgb, var(--qj-failed) 25%, transparent);
  font-size: 13px; line-height: 1.5;
}

/* ----- Status timeline ----- */
.qj-timeline {
  display: flex; align-items: center; gap: 4px;
  padding: 16px 22px 4px; overflow-x: auto;
}
.qj-timeline-step {
  display: inline-flex; flex-direction: column; align-items: center;
  gap: 4px; min-width: 0; flex-shrink: 0;
}
.qj-timeline-dot {
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--bg-input); border: 1.5px solid var(--line-strong);
  flex-shrink: 0;
}
.qj-timeline-label {
  font-size: 10px; color: var(--fg-subtle);
  text-transform: uppercase; letter-spacing: 0.05em;
  white-space: nowrap;
}
.qj-timeline-bar {
  flex: 1 1 18px; min-width: 18px; max-width: 36px;
  height: 1.5px; background: var(--line); margin: 0 0 14px;
}
.qj-timeline-step.is-reached .qj-timeline-dot {
  background: var(--qj-completed); border-color: var(--qj-completed);
}
.qj-timeline-step.is-reached .qj-timeline-label {
  color: var(--fg-muted);
}
.qj-timeline-step.is-current .qj-timeline-dot {
  background: var(--qj-running); border-color: var(--qj-running);
  box-shadow: 0 0 0 3px var(--qj-running-soft);
  animation: qj-pulse 1.6s ease-in-out infinite;
}
.qj-timeline-step.is-current .qj-timeline-label { color: var(--fg); font-weight: 600; }
.qj-timeline-step.is-failed  .qj-timeline-dot { background: var(--qj-failed); border-color: var(--qj-failed); }
.qj-timeline-step.is-failed  .qj-timeline-label { color: var(--qj-failed); font-weight: 600; }
.qj-timeline-step.is-cancelled .qj-timeline-dot { background: var(--qj-cancelled); border-color: var(--qj-cancelled); }
.qj-timeline-step.is-cancelled .qj-timeline-label { color: var(--qj-cancelled); font-weight: 600; }
.qj-timeline-bar.is-reached { background: var(--qj-completed); }

/* ----- Key facts grid ----- */
.qj-stats {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px 16px;
  padding: 12px 22px 16px; border-bottom: 1px solid var(--line);
}
@media (max-width: 1180px) { .qj-stats { grid-template-columns: repeat(2, 1fr); } }
.qj-stat { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.qj-stat-label {
  font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--fg-subtle); font-weight: 600;
}
.qj-stat-value {
  font-size: 13.5px; color: var(--fg); font-variant-numeric: tabular-nums;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* ----- Tabs ----- */
.qj-tabs {
  display: flex; gap: 2px; padding: 0 22px;
  border-bottom: 1px solid var(--line);
}
.qj-tab {
  appearance: none; border: 0; background: transparent; cursor: pointer;
  padding: 10px 14px; font-size: 13px; color: var(--fg-muted);
  border-bottom: 2px solid transparent; margin-bottom: -1px;
  font-family: inherit;
}
.qj-tab:hover { color: var(--fg); }
.qj-tab.active { color: var(--fg); border-bottom-color: var(--fg); font-weight: 600; }

.qj-tab-panels { flex: 1; min-height: 0; overflow: hidden; display: flex; flex-direction: column; }
.qj-tab-panel { display: none; flex: 1; min-height: 0; padding: 14px 22px 18px; overflow: hidden; flex-direction: column; }
.qj-tab-panel.active { display: flex; }

.qj-logs-controls {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 8px; font-size: 11.5px; color: var(--fg-muted);
}
.qj-checkbox {
  display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
}
.qj-checkbox input { accent-color: var(--brand); }
.qj-logs-meta { font-variant-numeric: tabular-nums; }
.qj-logs, .qj-code {
  margin: 0; padding: 12px 14px;
  background: var(--bg-input); border: 1px solid var(--line);
  border-radius: 8px; color: var(--fg);
  font-family: var(--mono); font-size: 12px; line-height: 1.55;
  flex: 1; overflow: auto; min-height: 0;
  white-space: pre-wrap; word-break: break-word;
}
.qj-logs:empty::before, .qj-code:empty::before {
  content: "—"; color: var(--fg-subtle);
}

.qj-artifacts { list-style: none; padding: 0; margin: 0; overflow-y: auto; flex: 1; }
.qj-artifacts li {
  padding: 8px 10px; border-radius: 6px;
  display: flex; align-items: center; gap: 10px;
  font-family: var(--mono); font-size: 12px; color: var(--fg);
  background: var(--bg-input); margin-bottom: 4px;
}
.qj-artifacts li::before {
  content: ""; width: 14px; height: 14px;
  background: var(--fg-subtle); flex-shrink: 0;
  -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'><path d='M14 3 H7 a2 2 0 0 0 -2 2 V19 a2 2 0 0 0 2 2 H17 a2 2 0 0 0 2 -2 V8 z'/><path d='M14 3 V8 H19'/></svg>") center/contain no-repeat;
          mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'><path d='M14 3 H7 a2 2 0 0 0 -2 2 V19 a2 2 0 0 0 2 2 H17 a2 2 0 0 0 2 -2 V8 z'/><path d='M14 3 V8 H19'/></svg>") center/contain no-repeat;
}
.qj-artifacts-empty {
  color: var(--fg-muted); font-size: 13px; padding: 12px 0;
}

/* ----- Empty list state ----- */
.qj-empty {
  padding: 40px 22px; text-align: center; color: var(--fg-muted);
  display: flex; flex-direction: column; align-items: center; gap: 10px;
}
.qj-empty-glyph { color: var(--fg-subtle); }
.qj-empty h2 { margin: 0; font-size: 15px; color: var(--fg); font-weight: 600; }
.qj-empty p { margin: 0; font-size: 13px; line-height: 1.55; max-width: 320px; }

/* ----- View toggle (Jobs vs Schedules) ----- */
.qj-views {
  display: flex; gap: 4px; margin-bottom: 16px;
  border-bottom: 1px solid var(--line);
}
.qj-view-tab {
  appearance: none; border: 0; background: transparent; cursor: pointer;
  font-family: inherit;
  display: inline-flex; align-items: center; gap: 7px;
  padding: 10px 14px; font-size: 13.5px; color: var(--fg-muted);
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.qj-view-tab:hover { color: var(--fg); }
.qj-view-tab.active { color: var(--fg); border-bottom-color: var(--fg); font-weight: 600; }
.qj-view-tab svg { opacity: 0.75; }
.qj-view-badge {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 18px; height: 18px; padding: 0 6px;
  font-size: 11px; font-weight: 600; color: var(--bg);
  background: var(--brand); border-radius: 999px;
  font-variant-numeric: tabular-nums;
}
.qj-view { display: flex; flex-direction: column; min-height: 0; flex: 1; }
.qj-view[hidden] { display: none; }

/* ----- Architectural note (Schedules header) ----- */
.qj-arch-note {
  margin: 0 0 16px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--bg-input);
  color: var(--fg-muted);
  font-size: 12.5px; line-height: 1.55;
  max-width: 760px;
}
.qj-arch-note em {
  font-style: normal; font-family: var(--mono); font-size: 12px;
  color: var(--fg); background: var(--bg);
  padding: 1px 6px; border-radius: 4px;
  border: 1px solid var(--line);
}

/* ----- Schedules list ----- */
.qj-sched-list {
  list-style: none; padding: 0; margin: 0;
  display: flex; flex-direction: column; gap: 10px;
  overflow-y: auto; flex: 1; min-height: 0;
}
.qj-sched-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 6px 16px;
  padding: 16px 18px;
  border: 1px solid var(--line); border-radius: 12px;
  background: var(--bg-elevated);
  transition: border-color 120ms, background 120ms;
}
.qj-sched-row:hover { border-color: var(--line-strong); }
.qj-sched-row.is-paused { background: var(--bg-soft); }
.qj-sched-row.is-failing { border-color: color-mix(in srgb, var(--qj-failed) 35%, var(--line)); }
.qj-sched-head {
  grid-column: 1 / -1;
  display: flex; align-items: center; gap: 10px;
  flex-wrap: wrap;
}
.qj-sched-tool {
  font-size: 15px; font-weight: 600; color: var(--fg);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  max-width: 360px;
}
.qj-sched-label {
  font-size: 12px; color: var(--fg-muted);
  padding: 2px 8px; border-radius: 999px;
  background: var(--bg-input); border: 1px solid var(--line);
}
.qj-sched-id {
  margin-left: auto;
  font-family: var(--mono); font-size: 11px; color: var(--fg-subtle);
}
.qj-sched-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 8px 24px;
  font-size: 13px;
}
@media (max-width: 880px) { .qj-sched-grid { grid-template-columns: repeat(2, 1fr); } }
.qj-sched-cell {
  display: flex; flex-direction: column; gap: 2px; min-width: 0;
}
.qj-sched-cell-label {
  font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--fg-subtle); font-weight: 600;
}
.qj-sched-cell-value {
  font-size: 13px; color: var(--fg);
  font-variant-numeric: tabular-nums;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.qj-sched-crontab {
  font-family: var(--mono); font-size: 12.5px;
  background: var(--bg-input); padding: 2px 8px;
  border-radius: 5px; display: inline-block;
}
.qj-sched-actions {
  grid-row: 2; grid-column: 2;
  display: flex; gap: 8px; align-items: end;
  align-self: end;
}
.qj-sched-last {
  display: inline-flex; align-items: center; gap: 6px;
}
.qj-sched-last-dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--fg-subtle); flex-shrink: 0;
}
.qj-sched-last-dot.is-ok { background: var(--qj-completed); }
.qj-sched-last-dot.is-err { background: var(--qj-failed); }
.qj-sched-failing-banner {
  grid-column: 1 / -1;
  display: flex; align-items: center; gap: 8px;
  padding: 6px 10px; border-radius: 7px;
  background: var(--qj-failed-soft); color: var(--qj-failed);
  font-size: 12px;
}

.qj-sched-empty {
  padding: 56px 24px; text-align: center; border: 1px dashed var(--line);
  border-radius: 16px; color: var(--fg-muted);
  display: flex; flex-direction: column; align-items: center; gap: 12px;
  background: var(--bg-elevated);
}
.qj-sched-empty-glyph { color: var(--fg-subtle); }
.qj-sched-empty h2 { margin: 0; font-size: 16px; color: var(--fg); font-weight: 600; }
.qj-sched-empty p { margin: 0; max-width: 460px; font-size: 13.5px; line-height: 1.55; }




/* ─── Knowledge Graph dashboard ─────────────────────────────────────────── */
/* Enterprise-grade layout inspired by Neo4j Bloom: top toolbar with
   summary metrics + search; left rail with schema browser (kinds +
   relationship types + result-set); centre force-directed graph
   canvas (D3 v7); right inspector pane; bottom chat bar ("Ask the
   graph…"). All four regions share one ``.kg-grid`` CSS grid; the
   chat bar pins to the page footer. Visual language reuses the
   platform's existing var(--bg/--fg/--muted/--accent) palette so
   light and dark themes work out of the box. */

.kg-page {
  display: none;
  flex-direction: column;
  height: 100%;
  /* The parent ``.canvas`` is ``align-items: center``, which makes
     non-stretching children (anything without an explicit width)
     collapse to their intrinsic content width. Force the dashboard
     to take the full available width regardless. */
  width: 100%;
  align-self: stretch;
  background: var(--bg);
  color: var(--fg);
  overflow: hidden;
}
[data-route="knowledge-graph"] .kg-page { display: flex; }

/* On the KG route, suppress the platform's main chat surface — hero
   (logo + tagline), thread, thread-footer, and composer — so the
   dashboard owns the viewport. Without this rule the chat hero
   leaks through above the dashboard. Same shape as the queue-jobs
   / chronicle / file-systems / tools rules. */
body[data-route="knowledge-graph"] .hero,
body[data-route="knowledge-graph"] .thread,
body[data-route="knowledge-graph"] .thread-footer,
body[data-route="knowledge-graph"] .composer-zone { display: none !important; }

/* ── Toolbar (top) ──────────────────────────────────────────────────── */

/* Two-row stack: row 1 is the centered "GALAXY · Knowledge & Data
   Graph" title; row 2 is the actions row (search bar flex-grown to
   fill the width, then scope chips, then view controls). */
.kg-toolbar {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 12px;
  padding: 14px 24px;
  border-bottom: 1px solid var(--border, rgba(0, 0, 0, 0.08));
  flex-shrink: 0;
  background: var(--bg);
}
/* Single-line title: "GALAXY · Knowledge & Data Graph". One size,
   one baseline; the brand carries the weight contrast, the
   separator + descriptor stay in muted. Inspired by Datadog /
   Neo4j / Linear's "Brand · Descriptor" header pattern. Centered
   above the actions row. */
.kg-toolbar-title {
  display: flex;
  align-items: baseline;
  justify-content: center;
  align-self: center;
  gap: 8px;
  margin: 0;
  font-size: 15px;
  font-weight: 400;
  line-height: 1.2;
  letter-spacing: 0;
  color: var(--fg);
  flex-shrink: 0;
  white-space: nowrap;
}
.kg-toolbar-title .kg-brand {
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--fg);
}
.kg-toolbar-sep {
  color: var(--muted);
  font-weight: 400;
  letter-spacing: 0;
}
.kg-toolbar-subtitle {
  font-size: inherit;
  font-weight: 400;
  color: var(--muted);
  letter-spacing: 0;
}
.kg-toolbar-actions {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-shrink: 0;
  flex-wrap: wrap;
  width: 100%;
}

/* ── Redesign (Phase B): search-under-title · canvas action bar ·
   inspector legend. The toolbar is a centred column so the search sits
   directly under the title; the graph's action buttons float at the
   top of the canvas; the node-label / relationship legend lives in the
   right inspector. ── */
.kg-toolbar > .kg-search-wrap {
  flex: 0 0 auto;
  width: 100%;
  max-width: 760px;
  align-self: center;
}
.kg-canvas-actions {
  position: absolute;
  top: 10px; left: 12px; right: 12px;
  z-index: 4;
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 8px;
  pointer-events: none;   /* graph pans in the gaps; groups re-enable */
}
.kg-canvas-actions-left,
.kg-canvas-actions-right {
  display: flex; align-items: center; gap: 4px;
  flex-wrap: wrap;
  pointer-events: auto;
  background: color-mix(in oklab, var(--bg) 80%, transparent);
  backdrop-filter: blur(7px);
  -webkit-backdrop-filter: blur(7px);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 3px 5px;
}
/* Text buttons in the left group (Schema + +Node/+Edge/Merge/Delete)
   need auto width; the icon-only Fit/Refresh keep the 30px square. */
.kg-canvas-actions-left .kg-btn-ghost,
.kg-canvas-actions-left .kg-schema-link {
  width: auto; padding: 0 9px; gap: 5px;
}
/* Don't collide with the show-rail chevron when the panel is collapsed. */
.kg-grid[data-rail-collapsed="true"] .kg-canvas-actions { left: 46px; }
.kg-inspector-legend {
  display: flex; flex-direction: column; gap: 7px;
  padding: 0 0 13px;
  margin-bottom: 6px;
  border-bottom: 1px solid var(--line);
}
.kg-inspector-legend .kg-legend-rels-title { margin-top: 5px; }
/* Scope selector — three-segment control (Enterprise / Project /
   Personal) that replaces the older search input. Active tab gets
   the accent fill and a subtle drop shadow inside the pill. */
.kg-scope {
  display: inline-flex;
  align-items: stretch;
  padding: 3px;
  background: var(--card-bg, rgba(0, 0, 0, 0.04));
  border: 1px solid var(--border, rgba(0, 0, 0, 0.08));
  border-radius: 999px;
  gap: 2px;
}
.kg-scope-tab {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 12px;
  border: none;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.01em;
  border-radius: 999px;
  cursor: pointer;
}
.kg-scope-tab svg { opacity: 0.85; }
.kg-scope-tab:hover { color: var(--fg); }
.kg-scope-tab[aria-selected="true"] {
  background: var(--bg);
  color: var(--fg);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08), 0 0 0 1px rgba(0, 0, 0, 0.04);
}
[data-theme="dark"] .kg-scope-tab[aria-selected="true"] {
  background: rgba(255, 255, 255, 0.1);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.45);
}

/* Project tab is a dropdown — wrapper anchors the menu, chevron
   rotates 180° when open. */
.kg-scope-project-wrap {
  position: relative;
  display: inline-flex;
}
.kg-chevron-down {
  margin-left: 2px;
  opacity: 0.7;
  transition: transform 0.18s ease;
}
.kg-scope-project[aria-expanded="true"] .kg-chevron-down {
  transform: rotate(180deg);
}
.kg-scope-menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  min-width: 220px;
  max-height: 320px;
  overflow-y: auto;
  background: var(--bg);
  border: 1px solid var(--border, rgba(0, 0, 0, 0.12));
  border-radius: 10px;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.18);
  padding: 6px;
  z-index: 50;
}
.kg-scope-menu-empty {
  padding: 10px 12px;
  color: var(--muted);
  font-size: 12.5px;
}
.kg-scope-menu-item {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 7px 10px;
  border: none;
  background: transparent;
  border-radius: 7px;
  font: inherit;
  font-size: 13px;
  color: var(--fg);
  cursor: pointer;
  text-align: left;
}
.kg-scope-menu-item:hover { background: rgba(0, 0, 0, 0.04); }
.kg-scope-menu-item[aria-current="true"] {
  background: rgba(91, 141, 239, 0.12);
  color: var(--accent, #5b8def);
  font-weight: 600;
}
.kg-scope-menu-item .kg-scope-menu-icon {
  flex-shrink: 0;
  opacity: 0.7;
}
[data-theme="dark"] .kg-scope-menu {
  background: var(--bg);
  border-color: rgba(255, 255, 255, 0.12);
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.6);
}
[data-theme="dark"] .kg-scope-menu-item:hover { background: rgba(255, 255, 255, 0.06); }

/* Per-row inline rename / delete affordances on the project menu.
   The row is a flex container so the icon buttons sit to the right
   of the main select button; they are hidden by default and fade
   in on row hover so the menu stays uncluttered when scanning. */
.kg-scope-menu-row {
  display: flex;
  align-items: center;
  border-radius: 7px;
  position: relative;
}
.kg-scope-menu-row .kg-scope-menu-item { flex: 1 1 auto; }
.kg-scope-menu-action {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  margin-right: 4px;
  border: none;
  background: transparent;
  color: var(--muted);
  border-radius: 5px;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.12s ease, background 0.12s ease, color 0.12s ease;
}
.kg-scope-menu-row:hover .kg-scope-menu-action { opacity: 0.85; }
.kg-scope-menu-action:hover {
  opacity: 1;
  background: rgba(0, 0, 0, 0.06);
  color: var(--fg);
}
.kg-scope-menu-delete:hover { color: #d95757; }
[data-theme="dark"] .kg-scope-menu-action:hover {
  background: rgba(255, 255, 255, 0.08);
}

.kg-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 30px;
  padding: 0 10px;
  background: transparent;
  border: 1px solid var(--border, rgba(0, 0, 0, 0.1));
  border-radius: 7px;
  color: var(--fg);
  cursor: pointer;
  font-size: 12px;
  font-weight: 500;
}
.kg-btn:hover { background: rgba(0, 0, 0, 0.03); }
.kg-btn-ghost { width: 30px; padding: 0; }
.kg-btn-tiny {
  height: 24px;
  font-size: 11px;
  padding: 0 8px;
  border-radius: 6px;
}

/* ── Grid body (3 columns × 1 row) ──────────────────────────────────── */

.kg-grid {
  flex: 1;
  display: grid;
  /* Inspector column is intentionally narrower than the canvas
     — roughly half its width. With a typical 1280px viewport the
     schema rail takes ~260px, leaving ~1020px for canvas +
     inspector; at minmax(240px, 280px) the inspector lands near
     ~270px and the canvas near ~750px, giving the canvas the
     dominant share. */
  grid-template-columns: minmax(240px, 280px) 1fr minmax(240px, 280px);
  gap: 0;
  min-height: 0;
}

/* Left rail — schema browser */
.kg-schema {
  display: flex;
  flex-direction: column;
  gap: 18px;
  padding: 18px 14px 18px 24px;
  overflow-y: auto;
  border-right: 1px solid var(--border, rgba(0, 0, 0, 0.08));
  position: relative;
}
/* Rail header — "Overview" label on the left, collapse chevron on
   the right. The collapse button used to be a free-floating
   absolute element overlapping section content; it now sits inside
   this header so it can never overlap a count or a pill chip. */
.kg-rail-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-bottom: 6px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--border, rgba(0, 0, 0, 0.05));
}
.kg-rail-title {
  margin: 0;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: -0.005em;
}
.kg-rail-toggle,
.kg-rail-show {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 6px;
  color: var(--muted);
  cursor: pointer;
}
.kg-rail-toggle:hover,
.kg-rail-show:hover {
  color: var(--fg);
  background: rgba(0, 0, 0, 0.05);
  border-color: var(--border, rgba(0, 0, 0, 0.08));
}
.kg-rail-show {
  position: absolute;
  top: 14px;
  left: 12px;
  z-index: 4;
  background: var(--card-bg, rgba(0, 0, 0, 0.04));
  border-color: var(--border, rgba(0, 0, 0, 0.1));
}
[data-theme="dark"] .kg-rail-toggle:hover,
[data-theme="dark"] .kg-rail-show:hover {
  background: rgba(255, 255, 255, 0.07);
  border-color: rgba(255, 255, 255, 0.12);
}
[data-theme="dark"] .kg-rail-show {
  background: rgba(255, 255, 255, 0.05);
  border-color: rgba(255, 255, 255, 0.1);
}

/* Collapsed-rail state: the rail is removed from layout entirely
   and the grid drops to two columns (canvas + inspector). The
   first column does NOT get a 0-width slot — CSS Grid with
   ``display: none`` removes the item from placement, so a 3-column
   template would push ``.kg-canvas`` into the 0-width slot and
   leave the third column empty (= blank dashboard, canvas with
   zero width). Two-column template keeps canvas at ``1fr`` and
   the show button at the canvas's top-left becomes reachable. */
.kg-grid[data-rail-collapsed="true"] {
  grid-template-columns: 1fr minmax(240px, 280px);
}
.kg-grid[data-rail-collapsed="true"] .kg-schema {
  display: none;
}
/* Collapsible right inspector — mirror of the left rail. Dropping the
   inspector column gives the canvas its width back; collapsing both
   rails leaves the canvas full-bleed. */
.kg-grid[data-inspector-collapsed="true"] {
  grid-template-columns: minmax(240px, 280px) 1fr;
}
.kg-grid[data-inspector-collapsed="true"] .kg-inspector { display: none; }
.kg-grid[data-rail-collapsed="true"][data-inspector-collapsed="true"] {
  grid-template-columns: 1fr;
}
/* Right-edge "show" button — reuses the .kg-rail-show base, flipped to
   the right edge and dropped below the top action row (fit/refresh). */
.kg-inspector-show { left: auto; right: 12px; top: 56px; }
/* Inspector header: back (left) · dynamic title (fills) · actions (right). */
.kg-inspector-titlebar { gap: 6px; }
.kg-inspector-title {
  flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.kg-inspector-titleactions { display: inline-flex; align-items: center; gap: 4px; flex: none; }
.kg-schema-block { display: flex; flex-direction: column; gap: 8px; }
.kg-section-title {
  margin: 0;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0;
  color: var(--muted);
}

/* ── Named-graph list (left panel) ─────────────────────────────
   The redesigned left panel lists first-class knowledge graphs,
   each a title with a scope-coloured dot + badge (enterprise =
   blue, project = green). Clicking one filters the dashboard to
   that graph (graph_id=); "+ New" opens the create/import flow. */
.kg-graphs-block { gap: 6px; }
/* Primary affordance at the top of the panel — a quiet dashed "add"
   row that hints the enterprise blue on hover. Replaces the old
   "Knowledge graphs" header + inline New button. */
.kg-add-graph {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  width: 100%; padding: 8px 10px; margin-bottom: 2px;
  border: 1px dashed var(--line); border-radius: 9px;
  background: transparent; color: var(--fg);
  font-size: 12px; font-weight: 500; cursor: pointer;
  transition: border-color .14s ease, background .14s ease, color .14s ease;
}
.kg-add-graph:hover {
  border-color: color-mix(in oklab, #5b9cf6 55%, var(--line));
  color: #5b9cf6;
  background: color-mix(in oklab, #5b9cf6 9%, transparent);
}
.kg-add-graph svg { flex: none; }
.kg-graphs-list {
  display: flex; flex-direction: column; gap: 1px; margin-top: 2px;
}
.kg-graph-item {
  display: flex; align-items: center; gap: 8px;
  width: 100%; padding: 7px 9px;
  border: 1px solid transparent; border-radius: 8px;
  background: transparent; color: var(--fg);
  font-size: 12.5px; text-align: left; cursor: pointer;
  transition: background .12s ease, border-color .12s ease;
}
.kg-graph-item:hover { background: rgba(0, 0, 0, 0.04); }
[data-theme="dark"] .kg-graph-item:hover { background: rgba(255, 255, 255, 0.05); }
.kg-graph-item.is-active {
  background: rgba(0, 0, 0, 0.05); border-color: var(--line);
}
[data-theme="dark"] .kg-graph-item.is-active { background: rgba(255, 255, 255, 0.07); }
/* Disclosure caret — rotates to point down when the graph is expanded.
   Hidden (but space-preserving) for the all-graphs row, which has no
   details to disclose, so dots + titles stay aligned. */
.kg-graph-caret {
  display: inline-flex; flex: none; color: var(--muted);
  transition: transform .15s ease;
}
.kg-graph-item.is-active .kg-graph-caret { transform: rotate(90deg); color: var(--fg); }
.kg-graph-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--muted); flex: none;
}
.kg-graph-dot[data-scope="enterprise"] { background: #5b9cf6; }
.kg-graph-dot[data-scope="project"] { background: #45c08a; }
.kg-graph-title {
  flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.kg-graph-scope {
  flex: none;
  font-size: 9px; text-transform: uppercase; letter-spacing: .04em;
  padding: 1px 5px; border-radius: 5px;
  color: var(--muted); border: 1px solid var(--line);
}
.kg-graph-scope[data-scope="enterprise"] {
  color: #5b9cf6; border-color: color-mix(in oklab, #5b9cf6 35%, transparent);
}
.kg-graph-scope[data-scope="project"] {
  color: #45c08a; border-color: color-mix(in oklab, #45c08a 35%, transparent);
}

/* Inline detail disclosure under the selected graph — a quiet facts
   list (size / scope / created) plus the bound schema rows, hung off a
   connector rail aligned roughly beneath the row's dot. */
.kg-graph-detail {
  margin: 2px 0 8px 25px;
  padding-left: 11px;
  border-left: 1px solid var(--line);
  display: flex; flex-direction: column; gap: 9px;
  animation: kgDetailIn .16s ease both;
}
@keyframes kgDetailIn {
  from { opacity: 0; transform: translateY(-3px); }
  to { opacity: 1; transform: none; }
}
.kg-graph-facts {
  margin: 0; display: grid; grid-template-columns: auto 1fr; gap: 4px 12px;
}
.kg-graph-facts > div { display: contents; }
.kg-graph-facts dt {
  font-size: 10px; text-transform: uppercase; letter-spacing: .05em;
  color: var(--muted); align-self: baseline; padding-top: 1px;
}
.kg-graph-facts dd {
  margin: 0; font-size: 12px; color: var(--fg);
  font-variant-numeric: tabular-nums;
}
.kg-graph-facts dd .kg-graph-scope { vertical-align: middle; }
.kg-detail-label {
  margin: 0 0 3px; font-size: 10px; text-transform: uppercase;
  letter-spacing: .05em; color: var(--muted);
}
.kg-detail-empty {
  margin: 0; font-size: 11.5px; color: var(--muted); font-style: italic;
}
/* Manage actions for the selected project graph (rename / delete). */
.kg-graph-actions {
  display: flex; gap: 6px; margin-top: 1px;
}
.kg-graph-action {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 10px; border-radius: 7px;
  border: 1px solid var(--line); background: transparent;
  color: var(--muted); font-size: 11px; cursor: pointer;
  transition: background .12s ease, border-color .12s ease, color .12s ease;
}
.kg-graph-action:hover {
  color: var(--fg);
  border-color: color-mix(in oklab, var(--fg) 22%, var(--line));
}
.kg-graph-action svg { flex: none; }
.kg-graph-action-danger:hover {
  color: var(--danger, #d95757);
  border-color: color-mix(in oklab, #d95757 45%, transparent);
  background: color-mix(in oklab, #d95757 10%, transparent);
}
/* A graph's bound schema(s). The detail wrapper already supplies the
   connector rail + indent, so reset the standalone list's own offsets. */
.kg-graph-schemas {
  list-style: none;
  margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: 1px;
}

/* Neo4j-Bloom-style pill chips for node labels and relationship
   types. Each pill carries a ``--pill-color`` CSS variable so the
   palette can colour the background, border, and active state from
   one source. Pills wrap; click-to-toggle filters the canvas. */
.kg-pill-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.kg-pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 11px;
  font: inherit;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0;
  border-radius: 999px;
  background: color-mix(in srgb, var(--pill-color, #9aa1ad) 18%, var(--bg));
  border: 1px solid color-mix(in srgb, var(--pill-color, #9aa1ad) 32%, var(--bg));
  color: var(--fg);
  cursor: pointer;
  white-space: nowrap;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}
.kg-pill:hover {
  background: color-mix(in srgb, var(--pill-color, #9aa1ad) 28%, var(--bg));
  border-color: color-mix(in srgb, var(--pill-color, #9aa1ad) 50%, var(--bg));
}
.kg-pill .kg-pill-x {
  /* Hidden until the chip is hovered. Click → delete all nodes
     of this kind. Tinted red to mark the destructive action. */
  display: none;
  margin-left: 4px;
  padding: 0 6px;
  border-radius: 999px;
  font-size: 14px;
  line-height: 1;
  color: #e15858;
  background: rgba(225, 88, 88, 0.12);
}
.kg-pill:hover .kg-pill-x { display: inline-flex; align-items: center; }
.kg-pill .kg-pill-x:hover {
  background: rgba(225, 88, 88, 0.28);
  color: #fff;
}
.kg-pill[aria-pressed="true"] {
  background: color-mix(in srgb, var(--pill-color, #9aa1ad) 45%, var(--bg));
  border-color: var(--pill-color, #9aa1ad);
  color: var(--fg);
}
.kg-pill-count {
  opacity: 0.7;
  font-variant-numeric: tabular-nums;
  font-weight: 400;
}
.kg-pill[data-tone="neutral"] {
  --pill-color: #9aa1ad;
  color: var(--muted);
}
.kg-pill[data-tone="neutral"][aria-pressed="true"] {
  color: var(--fg);
}
.kg-pill[data-tone="neutral"]:hover { color: var(--fg); }

/* Schemas list — registered schemas for the current scope. Click a
   row to swap the canvas from "data graph" to "schema view"
   (kinds + relationship types rendered as a small graph). The
   active schema is highlighted; clicking it again or "Back to
   graph" in the canvas banner returns to the data view. */
.kg-schemas-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.kg-schemas-item {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  padding: 7px 10px;
  border-radius: 8px;
  background: transparent;
  border: 1px solid transparent;
  color: var(--fg);
  font-size: 12.5px;
  cursor: pointer;
  text-align: left;
  width: 100%;
}
.kg-schemas-item:hover {
  background: var(--card-bg, rgba(0, 0, 0, 0.04));
}
.kg-schemas-item[aria-pressed="true"] {
  background: rgba(91, 141, 239, 0.12);
  border-color: rgba(91, 141, 239, 0.45);
  color: var(--fg);
}
.kg-schemas-name { font-weight: 600; }
.kg-schemas-counts {
  color: var(--muted);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* Row container that hosts the select button + inline rename
   action. Pencil icon stays muted until the row is hovered to keep
   the schemas list visually quiet. */
.kg-schemas-row {
  display: flex;
  align-items: center;
  gap: 4px;
}
.kg-schemas-row .kg-schemas-item { flex: 1 1 auto; }
.kg-schemas-action {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border: none;
  background: transparent;
  color: var(--muted);
  border-radius: 5px;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.12s ease, background 0.12s ease, color 0.12s ease;
}
.kg-schemas-row:hover .kg-schemas-action { opacity: 0.85; }
.kg-schemas-action:hover {
  opacity: 1;
  background: rgba(0, 0, 0, 0.06);
  color: var(--fg);
}
[data-theme="dark"] .kg-schemas-action:hover {
  background: rgba(255, 255, 255, 0.08);
}

/* Schema-view banner across the top of the canvas overlay. */
.kg-schema-banner {
  position: absolute;
  top: 10px;
  left: 50%;
  transform: translateX(-50%);
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px;
  background: var(--bg);
  border: 1px solid rgba(91, 141, 239, 0.45);
  border-radius: 999px;
  font-size: 12px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  z-index: 6;
  pointer-events: auto;
}
.kg-schema-banner[hidden] { display: none; }
.kg-schema-banner-label { color: var(--muted); }
.kg-schema-banner-name { font-weight: 600; color: var(--fg); }
.kg-schema-banner-back {
  margin-left: 4px;
  padding: 3px 8px;
  border-radius: 999px;
  border: none;
  background: rgba(91, 141, 239, 0.18);
  color: var(--accent, #5b8def);
  font-size: 11.5px;
  cursor: pointer;
}
.kg-schema-banner-back:hover { background: rgba(91, 141, 239, 0.28); }

/* Bottom-of-rail summary line, same energy as Neo4j Bloom's
   "Displaying X nodes, Y relationships." footer. */
.kg-rail-summary {
  margin-top: auto;
  padding-top: 14px;
  border-top: 1px solid var(--border, rgba(0, 0, 0, 0.06));
  font-size: 11.5px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* Centre canvas */
.kg-canvas {
  position: relative;
  background:
    radial-gradient(circle at 18% 24%, rgba(91, 141, 239, 0.05), transparent 55%),
    radial-gradient(circle at 82% 78%, rgba(91, 141, 239, 0.04), transparent 60%),
    var(--bg);
  overflow: hidden;
}
.kg-svg {
  width: 100%;
  height: 100%;
  display: block;
  cursor: grab;
  touch-action: none;
}
.kg-svg:active { cursor: grabbing; }

.kg-links line {
  /* ``stroke`` is set inline per-edge by drawGraph() so the rail
     legend pill and the rendered relationship line always agree
     on colour. Untyped edges fall back to the stylesheet stroke. */
  stroke: rgba(15, 18, 28, 0.22);
  stroke-width: 1.6;
  stroke-linecap: round;
  fill: none;
}
.kg-link-label {
  /* ``fill`` is set inline per-edge to the relationship-type
     colour, so the label reads like a coloured chip lying on the
     edge — Neo4j Bloom idiom. */
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-anchor: middle;
  pointer-events: none;
  user-select: none;
  paint-order: stroke;
  stroke: var(--bg, #fff);
  stroke-width: 3px;
  stroke-linejoin: round;
}

.kg-node-circle {
  stroke: rgba(255, 255, 255, 0.85);
  stroke-width: 1.5;
  cursor: pointer;
}
.kg-node-circle:hover { filter: brightness(1.06); }
.kg-node-group.selected .kg-node-circle {
  stroke: #ffae3a;
  stroke-width: 3;
}
.kg-node-label {
  font-size: 11px;
  font-weight: 500;
  fill: var(--fg);
  text-anchor: middle;
  pointer-events: none;
  user-select: none;
}
.kg-node-label-bg {
  fill: var(--bg);
  fill-opacity: 0.75;
  pointer-events: none;
}

.kg-canvas-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: center;
  pointer-events: none;
  padding: 12px;
}
.kg-canvas-empty {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
  font-size: 13px;
  pointer-events: none;
}
.kg-canvas-empty p { margin: 0; }
.kg-canvas-empty.hidden { display: none; }
.kg-canvas-hint {
  padding: 5px 11px;
  background: rgba(0, 0, 0, 0.55);
  color: #f6f6f6;
  border-radius: 999px;
  font-size: 11px;
  letter-spacing: 0.02em;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.25s ease;
}
.kg-canvas:hover .kg-canvas-hint { opacity: 0.85; }

.kg-canvas-tooltip {
  position: absolute;
  pointer-events: none;
  padding: 6px 10px;
  background: rgba(15, 18, 28, 0.95);
  color: #fafafa;
  border-radius: 6px;
  font-size: 12px;
  max-width: 240px;
  line-height: 1.45;
  z-index: 5;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);
}

/* NL → Cypher overlay card. Sits in the bottom-left of the canvas,
   above the hint chip. Becomes visible once /v1/kg/search returns a
   non-empty cypher block. */
.kg-cypher-card {
  position: absolute;
  left: 16px;
  bottom: 56px;
  width: min(440px, 46%);
  max-height: 60%;
  display: flex;
  flex-direction: column;
  background: rgba(15, 18, 28, 0.96);
  color: #f4f4f6;
  border-radius: 10px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
  border: 1px solid rgba(255, 255, 255, 0.08);
  z-index: 6;
  overflow: hidden;
  font-size: 12.5px;
}
.kg-cypher-card[data-collapsed="true"] .kg-cypher-card-body { display: none; }
.kg-cypher-card[data-collapsed="true"] { max-height: 38px; }
.kg-cypher-card-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  background: rgba(255, 255, 255, 0.04);
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.kg-cypher-card-title {
  flex: 1;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #d6d8e0;
}
.kg-cypher-card .kg-icon-btn {
  color: #d6d8e0;
  background: transparent;
  border: 0;
  cursor: pointer;
  padding: 3px;
  border-radius: 4px;
}
.kg-cypher-card .kg-icon-btn:hover { background: rgba(255, 255, 255, 0.08); }
.kg-cypher-card[data-collapsed="true"] .kg-cypher-collapse svg { transform: rotate(180deg); }
.kg-cypher-card-body {
  padding: 10px 12px 12px;
  overflow: auto;
}
.kg-cypher-fence-wrap {
  position: relative;
  background: rgba(255, 255, 255, 0.04);
  border-radius: 6px;
  padding: 8px 10px;
  margin-bottom: 8px;
}
.kg-cypher-fence {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  margin: 0;
  white-space: pre-wrap;
  word-break: break-word;
  color: #c8efe9;
}
.kg-cypher-copy {
  position: absolute;
  top: 6px;
  right: 6px;
  background: rgba(255, 255, 255, 0.08);
  border: 0;
  color: #f4f4f6;
  border-radius: 4px;
  padding: 2px 8px;
  font-size: 10.5px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  cursor: pointer;
}
.kg-cypher-copy:hover { background: rgba(255, 255, 255, 0.16); }
.kg-cypher-copy[data-state="ok"] { background: #2e7d32; }
.kg-cypher-answer {
  margin: 0 0 8px;
  line-height: 1.5;
  color: #ecedef;
}
.kg-cypher-rows-wrap {
  margin-top: 4px;
}
.kg-cypher-rows {
  width: 100%;
  border-collapse: collapse;
  font-size: 11.5px;
}
.kg-cypher-rows th,
.kg-cypher-rows td {
  text-align: left;
  padding: 4px 6px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
  vertical-align: top;
  max-width: 220px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.kg-cypher-rows th {
  font-weight: 600;
  color: #b8bac4;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: 10.5px;
}
.kg-cypher-rows-foot {
  margin: 6px 0 0;
  color: #a0a3b0;
  font-size: 10.5px;
}
.kg-cypher-error {
  margin: 6px 0 0;
  padding: 6px 8px;
  background: rgba(255, 80, 80, 0.12);
  border: 1px solid rgba(255, 80, 80, 0.35);
  border-radius: 6px;
  color: #ffbbbb;
  font-size: 11.5px;
}
.kg-tooltip-kind {
  display: inline-block;
  padding: 1px 6px;
  background: rgba(255, 255, 255, 0.12);
  border-radius: 999px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  margin-right: 6px;
}

/* Right rail — inspector, Neo4j Node-details style. */
.kg-inspector {
  display: flex;
  flex-direction: column;
  padding: 14px 16px 18px;
  overflow-y: auto;
  border-left: 1px solid var(--border, rgba(0, 0, 0, 0.08));
  font-size: 12.5px;
}
.kg-inspector-titlebar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-bottom: 8px;
  margin-bottom: 10px;
  border-bottom: 1px solid var(--border, rgba(0, 0, 0, 0.05));
}
.kg-inspector-title {
  margin: 0;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: -0.005em;
}
.kg-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: 6px;
  background: transparent;
  border: 1px solid transparent;
  color: var(--muted);
  cursor: pointer;
}
.kg-icon-btn:hover:not([disabled]) {
  color: var(--fg);
  background: rgba(0, 0, 0, 0.05);
  border-color: var(--border, rgba(0, 0, 0, 0.08));
}
.kg-icon-btn[disabled] { opacity: 0.4; cursor: not-allowed; }
.kg-icon-btn.kg-copied { color: var(--accent, #5b8def); }
[data-theme="dark"] .kg-icon-btn:hover:not([disabled]) {
  background: rgba(255, 255, 255, 0.07);
  border-color: rgba(255, 255, 255, 0.12);
}
body.edge-pick-mode .kg-canvas,
body.edge-pick-mode .kg-node-group { cursor: crosshair; }
/* Multi-select uses the same orange halo as single-select so the
   "this node is picked" affordance is consistent. Multi-select
   draws a slightly thicker stroke to differentiate at a glance
   when both states could coexist. */
.kg-node-group.kg-multi-selected .kg-node-circle {
  stroke: #ffae3a;
  stroke-width: 4px;
}

.kg-inspector-empty {
  margin: auto 0;
  text-align: center;
  color: var(--muted);
  font-size: 12.5px;
  line-height: 1.5;
  padding: 0 8px;
}
.kg-inspector-pillrow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 12px;
}
.kg-inspector-pill { cursor: default; }

/* Properties table — three columns: key / value / row-copy. Rows
   carry a thin bottom border; long values truncate with an inline
   "Show all" toggle. */
.kg-prop-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12px;
  margin-bottom: 14px;
}
.kg-prop-table thead th {
  text-align: left;
  font-weight: 600;
  font-size: 11px;
  color: var(--muted);
  letter-spacing: 0;
  padding: 4px 6px;
  border-bottom: 1px solid var(--border, rgba(0, 0, 0, 0.08));
}
.kg-prop-th-key { width: 30%; }
.kg-prop-th-act { width: 26px; }
.kg-prop-table tbody tr {
  border-bottom: 1px solid var(--border, rgba(0, 0, 0, 0.05));
}
.kg-prop-table tbody tr:last-child { border-bottom: none; }
.kg-prop-table td {
  padding: 7px 6px;
  vertical-align: top;
  overflow-wrap: anywhere;
}
.kg-prop-key {
  color: var(--fg);
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 11.5px;
}
.kg-prop-val {
  color: var(--fg);
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 11.5px;
  line-height: 1.45;
  word-break: break-word;
}
.kg-prop-val-truncated {
  display: -webkit-box;
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.kg-show-all {
  display: inline-block;
  margin-top: 4px;
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  font-size: 11px;
  color: var(--accent, #5b8def);
  cursor: pointer;
}
.kg-show-all:hover { text-decoration: underline; }
.kg-prop-act { text-align: right; padding-right: 2px !important; }
.kg-row-copy {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  background: transparent;
  border: none;
  color: var(--muted);
  border-radius: 5px;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.12s ease;
}
.kg-prop-table tr:hover .kg-row-copy { opacity: 1; }
.kg-row-copy:hover { color: var(--fg); background: rgba(0, 0, 0, 0.06); }
.kg-row-copy.kg-copied { color: var(--accent, #5b8def); opacity: 1; }
[data-theme="dark"] .kg-row-copy:hover { background: rgba(255, 255, 255, 0.08); }

.kg-inspector-section { margin-bottom: 14px; }
.kg-inspector-section h3 {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 8px;
  font-size: 11px;
  font-weight: 650;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
.kg-inspector-count {
  font-weight: 600;
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;
  padding: 1px 6px;
  background: var(--card-bg, rgba(0, 0, 0, 0.04));
  border-radius: 999px;
}
.kg-neighbor-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.kg-neighbor-item {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 8px;
  border-radius: 6px;
  font-size: 12px;
  cursor: pointer;
  border: 1px solid transparent;
}
.kg-neighbor-item:hover {
  background: rgba(91, 141, 239, 0.06);
  border-color: rgba(91, 141, 239, 0.15);
}
.kg-edge-chip {
  font-family: ui-monospace, monospace;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
  padding: 1px 6px;
  background: var(--card-bg, rgba(0, 0, 0, 0.04));
  border-radius: 4px;
  white-space: nowrap;
  flex-shrink: 0;
}
.kg-edge-chip-arrow {
  font-family: ui-monospace, monospace;
  color: var(--muted);
  font-size: 11px;
}
.kg-neighbor-label {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Relationship-type group header inside the Neighbours list — one
   pill-coloured row before each block of rows. Matches the rail
   legend's per-edge-type colour. */
.kg-neighbor-group {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  margin-top: 8px;
  margin-bottom: 2px;
  padding: 0;
  list-style: none;
}
.kg-neighbor-group:first-child { margin-top: 0; }
.kg-neighbor-group-bar {
  width: 3px;
  height: 12px;
  border-radius: 2px;
}
.kg-neighbor-group-label { flex: 1; }
.kg-neighbor-group-count {
  color: var(--muted);
  font-weight: 500;
  font-size: 10px;
}
/* Kind chip inside a neighbour row — small pill in the kind's
   colour. The text-only chip avoids two competing background
   colours (edge group + kind) inside one row. */
.kg-neighbor-kind {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  padding: 1px 6px;
  border-radius: 4px;
  border: 1px solid currentColor;
  white-space: nowrap;
  flex-shrink: 0;
  opacity: 0.85;
}
.kg-neighbor-ref {
  font-size: 10px;
  text-decoration: none;
  padding: 1px 6px;
  border: 1px solid var(--border, rgba(0, 0, 0, 0.12));
  border-radius: 4px;
  color: var(--accent, #5b8def);
}
.kg-neighbor-ref:hover { background: rgba(91, 141, 239, 0.08); }

.kg-inspector-label {
  flex: 1;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--fg);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.kg-inspector-actions {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  padding-top: 10px;
  margin-top: 6px;
  border-top: 1px solid var(--border, rgba(0, 0, 0, 0.06));
}
.kg-inspector-actions .kg-btn-tiny[aria-pressed="true"] {
  background: rgba(91, 141, 239, 0.12);
  color: var(--accent, #5b8def);
}

/* Shared kind tag (used in tooltip + chat citations). The inspector
   pill is now a real ``.kg-pill`` so its colour comes from the same
   ``--pill-color`` plumbing the schema rail uses. */
.kg-kind-tag {
  display: inline-block;
  padding: 2px 8px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.05em;
  border-radius: 999px;
  text-transform: uppercase;
  background: rgba(91, 141, 239, 0.12);
  color: var(--accent, #5b8def);
}

/* ── Search-in-graph input (top toolbar) ──────────────────────────── */

/* The toolbar's "Search in the graph…" input is NL → graph query →
   canvas mutation. It posts to /v1/kg/search; the response carries
   matched_node_ids the canvas highlights (.kg-match) while it dims
   the rest (.kg-dim), then auto-fits to the bounding box of the
   match. A small chip below the bar shows the "Matching: …" summary
   with an X to clear. No chat bubble — the canvas IS the answer. */
.kg-search-wrap {
  position: relative;
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  flex-direction: column;
  align-items: stretch;
  gap: 4px;
}
.kg-search-form {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 5px 6px 5px 10px;
  background: var(--card-bg, rgba(0, 0, 0, 0.035));
  border: 1px solid var(--border, rgba(0, 0, 0, 0.1));
  border-radius: 999px;
  width: 100%;
  min-width: 0;
}
.kg-search-form:focus-within {
  border-color: var(--accent, #5b8def);
  box-shadow: 0 0 0 2px rgba(91, 141, 239, 0.18);
}
.kg-search-prefix {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
}
.kg-search-input {
  flex: 1;
  background: transparent;
  border: none;
  outline: none;
  font: inherit;
  font-size: 13px;
  color: var(--fg);
  min-width: 0;
}
.kg-search-input::placeholder { color: var(--muted); opacity: 0.85; }
/* Hide the WebKit native "x" clear button — the chip-X is the
   canonical clear gesture, and the floating x clashes with the
   pill shape. */
.kg-search-input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
.kg-search-submit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 999px;
  border: none;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease;
}
.kg-search-submit:hover,
.kg-search-submit:focus-visible {
  background: rgba(91, 141, 239, 0.16);
  color: var(--accent, #5b8def);
  outline: none;
}
.kg-search-submit:disabled { opacity: 0.45; cursor: progress; }
.kg-search-input[aria-busy="true"] { color: var(--muted); }

/* The "Matching: <summary>" chip under the search bar. Floats
   absolutely so it doesn't reserve toolbar height when no query
   is active, but stays anchored to the search input column. */
.kg-search-chip {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 6px 4px 9px;
  background: var(--bg);
  border: 1px solid rgba(91, 141, 239, 0.4);
  border-radius: 999px;
  font-size: 11.5px;
  line-height: 1.25;
  color: var(--fg);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
  z-index: 5;
}
.kg-search-chip[hidden] { display: none; }
.kg-search-chip-icon { color: var(--accent, #5b8def); display: inline-flex; }
.kg-search-chip-label { color: var(--muted); font-weight: 500; }
.kg-search-chip-summary {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.kg-search-chip-clear {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 999px;
  border: none;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
}
.kg-search-chip-clear:hover {
  background: rgba(0, 0, 0, 0.08);
  color: var(--fg);
}

/* ── Search-highlight overlay on canvas nodes / edges ─────────────── */

/* Matching nodes pop with a bright accent ring; non-matching ones
   fade to ~30 % opacity. Edges between two matching endpoints
   stay solid (.kg-match), all others fade (.kg-dim). The classes
   are sticky across simulation ticks because we re-apply on every
   redraw (see _applySearchHighlight in app.js). */
.kg-node-group.kg-match .kg-node-circle {
  stroke: var(--accent, #5b8def);
  stroke-width: 3.5px;
  filter: drop-shadow(0 0 6px rgba(91, 141, 239, 0.6));
}
.kg-node-group.kg-match .kg-node-label,
.kg-node-group.kg-match .kg-node-label-bg {
  opacity: 1;
}
.kg-node-group.kg-dim { opacity: 0.28; }
.kg-node-group.kg-dim:hover { opacity: 0.6; }
.kg-link-group.kg-match line {
  stroke-opacity: 0.95;
  stroke-width: 1.6px;
}
.kg-link-group.kg-dim { opacity: 0.18; }

/* Dark mode tweaks */
[data-theme="dark"] .kg-toolbar { background: var(--bg); }
[data-theme="dark"] .kg-metric { background: rgba(255, 255, 255, 0.05); border-color: rgba(255, 255, 255, 0.1); }
[data-theme="dark"] .kg-schema-item:hover { background: rgba(255, 255, 255, 0.05); }
[data-theme="dark"] .kg-canvas {
  background:
    radial-gradient(circle at 18% 24%, rgba(91, 141, 239, 0.08), transparent 55%),
    radial-gradient(circle at 82% 78%, rgba(91, 141, 239, 0.06), transparent 60%),
    var(--bg);
}
[data-theme="dark"] .kg-links line { stroke: rgba(255, 255, 255, 0.28); }
[data-theme="dark"] .kg-node-circle { stroke: rgba(0, 0, 0, 0.55); }
[data-theme="dark"] .kg-node-label-bg { fill: var(--bg); fill-opacity: 0.85; }
[data-theme="dark"] .kg-edge-chip { background: rgba(255, 255, 255, 0.05); }
[data-theme="dark"] .kg-search-form { background: rgba(255, 255, 255, 0.04); border-color: rgba(255, 255, 255, 0.1); }

/* ── /galaxy/schema — multi-agent designer ─────────────────────────────
   Sub-page under GALAXY. Same SPA shell; body[data-route="galaxy-
   schema"] flips visibility and suppresses the chat surface. Layout
   is a two-column grid: left form, centre live progress + result. */

.kg-schema-page {
  display: none;
  flex-direction: column;
  height: 100%;
  width: 100%;
  align-self: stretch;
  background: var(--bg);
  color: var(--fg);
  overflow: hidden;
}
[data-route="galaxy-schema"] .kg-schema-page { display: flex; }

body[data-route="galaxy-schema"] .hero,
body[data-route="galaxy-schema"] .thread,
body[data-route="galaxy-schema"] .thread-footer,
body[data-route="galaxy-schema"] .composer-zone { display: none !important; }

.kg-schema-topbar {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 22px 12px;
  border-bottom: 1px solid var(--border);
}
.kg-schema-back {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  color: var(--muted);
  text-decoration: none;
  padding: 6px 10px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg-input);
}
.kg-schema-back:hover { color: var(--fg); }
.kg-schema-title {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 8px;
}
.kg-schema-topbar-spacer { flex: 1; }

.kg-schema-grid {
  flex: 1;
  display: grid;
  grid-template-columns: 360px 1fr;
  gap: 0;
  overflow: hidden;
}

/* Left form rail. */
.kg-schema-form {
  border-right: 1px solid var(--border);
  padding: 20px;
  overflow: auto;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.kg-schema-form-header { display: flex; flex-direction: column; gap: 6px; }
.kg-schema-form-title { margin: 0; font-size: 14px; font-weight: 600; }
.kg-schema-form-sub { margin: 0; color: var(--muted); font-size: 12px; line-height: 1.55; }
.kg-schema-form-body { display: flex; flex-direction: column; gap: 14px; }
.kg-schema-row { display: flex; gap: 10px; flex-wrap: wrap; }
.kg-schema-row .kg-schema-field { flex: 1 1 100px; min-width: 100px; }
.kg-schema-field { display: flex; flex-direction: column; gap: 4px; }
.kg-schema-label { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.06em; }
.kg-schema-hint { font-size: 11px; color: var(--muted); }
.kg-schema-field input,
.kg-schema-field textarea,
.kg-schema-field select {
  padding: 8px 10px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg-input);
  color: var(--fg);
  font: inherit;
  font-size: 13px;
}
.kg-schema-field textarea { resize: vertical; min-height: 64px; font-family: inherit; }
.kg-schema-form-footer { display: flex; gap: 10px; margin-top: 4px; }
.kg-schema-design-btn,
.kg-schema-cancel-btn,
.kg-schema-save-btn {
  padding: 9px 18px;
  border-radius: 9px;
  border: 1px solid var(--border);
  background: var(--accent);
  color: white;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
.kg-schema-cancel-btn { background: var(--bg-input); color: var(--fg); }
.kg-schema-design-btn:hover,
.kg-schema-save-btn:hover { filter: brightness(1.08); }
.kg-schema-design-btn:disabled,
.kg-schema-save-btn:disabled { opacity: 0.6; cursor: progress; }

/* Centre canvas. */
.kg-schema-canvas {
  position: relative;
  overflow: auto;
  padding: 22px;
  display: flex;
  flex-direction: column;
}
.kg-schema-empty {
  max-width: 560px;
  margin: 6vh auto 0;
  color: var(--muted);
  text-align: left;
}
.kg-schema-empty-title { margin: 0 0 8px; font-size: 18px; color: var(--fg); }
.kg-schema-empty p { line-height: 1.6; font-size: 13px; }
.kg-schema-stage-legend {
  list-style: none;
  padding: 0;
  margin: 18px 0 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 12px;
}
.kg-schema-stage-legend li { display: flex; gap: 8px; align-items: center; }
.kg-schema-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--muted);
}
.kg-schema-dot.dot-decompose { background: #6cb1ff; }
.kg-schema-dot.dot-architect { background: #6cd5ff; }
.kg-schema-dot.dot-experts { background: #7ee29d; }
.kg-schema-dot.dot-validate { background: #d5d278; }
.kg-schema-dot.dot-critic { background: #ffae3a; }
.kg-schema-dot.dot-refiner { background: #d289ff; }

/* Live progress timeline. */
.kg-schema-run { display: flex; flex-direction: column; gap: 22px; }
.kg-schema-progress {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-height: 50vh;
  overflow: auto;
}
.kg-schema-stage {
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 12px 14px;
  background: var(--bg-card, var(--bg-input));
}
.kg-schema-stage.error { border-color: #ff7a7a; background: rgba(255, 122, 122, 0.08); }
.kg-schema-stage.done { border-color: rgba(126, 226, 157, 0.5); }
.kg-schema-stage-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 6px;
}
.kg-schema-stage-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
  flex-shrink: 0;
}
.kg-schema-stage[data-stage="decompose"] .kg-schema-stage-dot { background: #6cb1ff; }
.kg-schema-stage[data-stage="architect"] .kg-schema-stage-dot { background: #6cd5ff; }
.kg-schema-stage[data-stage="experts"] .kg-schema-stage-dot { background: #7ee29d; }
.kg-schema-stage[data-stage="validate"] .kg-schema-stage-dot,
.kg-schema-stage[data-stage="assemble"] .kg-schema-stage-dot { background: #d5d278; }
.kg-schema-stage[data-stage="critic"] .kg-schema-stage-dot { background: #ffae3a; }
.kg-schema-stage[data-stage="refiner"] .kg-schema-stage-dot { background: #d289ff; }
.kg-schema-stage-title { font-weight: 600; font-size: 13px; }
.kg-schema-stage-sub { font-size: 11px; color: var(--muted); margin-left: 4px; }
.kg-schema-stage-body { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 4px; }
.kg-schema-stage-line {
  font-size: 12px;
  color: var(--fg);
  padding-left: 16px;
  position: relative;
  line-height: 1.5;
}
.kg-schema-stage-line::before {
  content: "·";
  position: absolute;
  left: 6px;
  color: var(--muted);
}
.kg-schema-stage-line.error { color: #ff7a7a; }
.kg-schema-stage-msg { display: block; }
.kg-schema-stage-extras {
  display: block;
  color: var(--muted);
  font-size: 11px;
  margin-top: 2px;
}

/* Result + tabs. */
.kg-schema-result {
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--bg-card, var(--bg-input));
  display: flex;
  flex-direction: column;
}
.kg-schema-result-head {
  padding: 14px 16px 10px;
  border-bottom: 1px solid var(--border);
}
.kg-schema-result-title { margin: 0 0 4px; font-size: 15px; font-weight: 600; }
.kg-schema-result-meta { font-size: 12px; color: var(--muted); }
.kg-schema-result-tabs {
  display: flex;
  gap: 6px;
  padding: 10px 16px 0;
  border-bottom: 1px solid var(--border);
}
.kg-schema-tab {
  padding: 6px 12px;
  border-radius: 9px 9px 0 0;
  border: 1px solid transparent;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}
.kg-schema-tab:hover { color: var(--fg); }
.kg-schema-tab.active {
  border-color: var(--border);
  border-bottom-color: transparent;
  color: var(--fg);
  background: var(--bg);
}
.kg-schema-tab-body { padding: 14px 16px; max-height: 50vh; overflow: auto; }
.kg-schema-json {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  white-space: pre;
  color: var(--fg);
  background: var(--bg);
  margin: 0;
}

/* Overview tab entity cards. */
.kg-schema-entity {
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  margin-bottom: 8px;
  background: var(--bg);
}
.kg-schema-entity-head { font-weight: 600; font-size: 13px; margin-bottom: 4px; }
.kg-schema-entity-desc { margin: 0 0 6px; color: var(--muted); font-size: 12px; line-height: 1.55; }
.kg-schema-entity-props { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 3px; }
.kg-schema-entity-props li {
  display: flex;
  gap: 8px;
  align-items: center;
  font-size: 12px;
}
.kg-schema-entity-props code {
  background: var(--bg-input);
  padding: 1px 6px;
  border-radius: 4px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px;
}
.kg-schema-prop-type { color: var(--muted); font-size: 11px; }
.kg-schema-prop-format,
.kg-schema-prop-enum {
  color: var(--muted);
  font-size: 11px;
  font-style: italic;
}

/* Trace tab rows. */
.kg-schema-trace-row {
  display: grid;
  grid-template-columns: 90px 1fr 1fr 70px 50px;
  gap: 8px;
  padding: 6px 0;
  border-bottom: 1px dashed var(--border);
  font-size: 12px;
  align-items: baseline;
}
.kg-schema-trace-row:last-child { border-bottom: none; }
.kg-schema-trace-stage { color: var(--muted); font-weight: 600; text-transform: uppercase; font-size: 10.5px; letter-spacing: 0.05em; }
.kg-schema-trace-agent { color: var(--fg); }
.kg-schema-trace-model { color: var(--muted); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; }
.kg-schema-trace-dur { color: var(--muted); text-align: right; }
.kg-schema-trace-iter { color: var(--muted); font-size: 11px; }
.kg-schema-trace-summary { grid-column: 1 / -1; color: var(--muted); line-height: 1.5; padding-left: 4px; }

/* Save footer. */
.kg-schema-result-footer {
  display: flex;
  gap: 10px;
  align-items: center;
  padding: 12px 16px;
  border-top: 1px solid var(--border);
  flex-wrap: wrap;
}
.kg-schema-save-name {
  flex: 1 1 220px;
  padding: 8px 10px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg-input);
  color: var(--fg);
  font: inherit;
  font-size: 13px;
  min-width: 0;
}
.kg-schema-save-status { color: var(--muted); font-size: 12px; }
.kg-schema-save-status code {
  background: var(--bg-input);
  padding: 1px 5px;
  border-radius: 4px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px;
}
.kg-schema-save-status a { color: var(--accent); text-decoration: none; }
.kg-schema-save-status a:hover { text-decoration: underline; }

/* "Schema" link button in the GALAXY toolbar. */
.kg-btn.kg-schema-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  text-decoration: none;
}

/* Narrow layouts — stack form above canvas. */
@media (max-width: 900px) {
  .kg-schema-grid {
    grid-template-columns: 1fr;
    grid-template-rows: auto 1fr;
  }
  .kg-schema-form {
    border-right: none;
    border-bottom: 1px solid var(--border);
    max-height: 50vh;
  }
}

/* ------------------------------------------------------------------
   Auth gate. Until a session is confirmed the platform is not visible:
   hide every top-level region except the login modal, and hide the
   modal close button (the login is non-dismissable while gated — see
   closeAuthModal() / applyAuthState() in app.js). `body.gated` ships in
   the HTML and is removed on auth, so an unauthenticated load never
   flashes the app.
   ------------------------------------------------------------------ */
body.gated > *:not(#auth-modal) { display: none !important; }
body.gated .auth-close { display: none !important; }
