/* Yatsar — shared styles.
 *
 * All color comes from the semantic tokens below — never hardcode a color in
 * a rule, an inline style, or JS. Light theme = the :root defaults; the dark
 * theme overrides the same tokens under html[data-theme="dark"] (set by the
 * admin-console toggle, see theme.js). Per-Agent Themes (future) override
 * these tokens as inline custom properties on <html> of the published page,
 * which wins over both scopes. Token list + scopes: docs/app/frontend.md.
 */
:root {
  color-scheme: light;

  /* actions */
  --primary: #6a3fcc;          /* primary buttons, user bubble, active toggle */
  --primary-hover: #5a33b0;
  --on-primary: #ffffff;       /* text/icons on --primary */
  --secondary: #5f5973;        /* metadata chrome: badges, tool chips */

  /* surfaces */
  --surface: #f4f5f7;          /* page / chat background */
  --surface-raised: #ffffff;   /* panels, cards, bubbles, inputs */
  --surface-hover: #f8f9fb;

  /* content */
  --text: #211c33;
  --text-muted: #5f5973;
  --border: #e5e3ec;
  --border-strong: #d5d2e0;

  /* accent (violet): links, selection, activity */
  --accent: #6a3fcc;
  --accent-surface: #efe9fb;   /* selection wash */
  --accent-text: #5b2bb0;      /* readable accent text on --accent-surface */
  /* focus (#401): NEUTRAL, Theme-aware — the visible focus ring is a quiet grey,
     not the violet brand accent. Dark-on-light here, light-on-dark in dark mode. */
  --focus-ring: #484553;

  /* status (--success-surface ratified by #421 per Decision 75's rule-of-two;
     soft-green wash re-expressed from prototype #393 --wf-success-soft).
     --warning/--warning-surface landed with the Tasks triage list (#498): a
     deadline is a THIRD status, not a member of the Skill identity family — a
     Theme repainting Skill gold must not repaint "Due today". The values are the
     amber the system already ships; the roles are now Themeable like the rest.
     --warning-border ratified by #540 (Decision 75's rule-of-two): #538's Explorer
     needs-attention badge and the Full API read-write pill are two bordered warning
     consumers, and the value is exactly the mix #538 wrote inline —
     color-mix(in srgb, --warning 35%, --border) — re-expressed as a token so the
     border is Themeable with the pair it belongs to. */
  --success: #1f8a4c;
  --success-surface: #e6f3eb;
  --warning: #8a5a00;
  --warning-surface: #fbf1d8;
  --warning-border: #c5b399;
  --danger: #c23934;
  --danger-surface: #fbeae9;
  --danger-border: #f1c7c5;

  /* semantic identity families (#401, migration brief #396) — Agent green,
     Skill gold, File blue, Notification rose. base = tint/text/icon hue;
     -surface = quiet fill wash; -text = readable label on -surface; -strong =
     AA-safe solid fill behind white (Notification has none). Themeable values,
     stable roles; part of the theme.py -> style.css -> dom-utils.js ->
     frontend.md parity contract (tests/test_token_parity.py). The -strong pair
     is dark enough for white text in BOTH modes, so it carries one value.
     Connector stays NEUTRAL — never a fifth identity family.
     2026-08-12 (#658): Tasks and Notifications swapped dress — the rose family
     below now paints TASKS (token names keep the parity contract), while
     Notification surfaces ride the violet accent/primary. */
  --agent: #1f8a4c; --agent-surface: #e6f4ec; --agent-text: #14663a; --agent-strong: #1a7a43;
  --skill: #c68a1a; --skill-surface: #fbf1d8; --skill-text: #8a5a00; --skill-strong: #9a6600;
  --file: #2f74c0; --file-surface: #e8f1fb; --file-text: #245a96; --file-strong: #265988;
  --notification: #b14f7b; --notification-surface: #f8e8ef; --notification-text: #87375c; --notification-strong: #a8476f;

  /* type */
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;

  /* ----- internal scale tokens (#65) — design constants, NOT themeable -----
     A Theme is colors only (docs/design/theming.md); these are tokenized for
     OUR consistency and never set by any Theme. Ported value-preserving from
     docs/design/design-system.css; new components use these from the start,
     existing rules migrate off raw px per-surface in later slices. */

  /* spacing rhythm */
  --space-1: 2px;  --space-2: 4px;  --space-3: 6px;  --space-4: 8px;
  --space-5: 10px; --space-6: 12px; --space-7: 14px; --space-8: 18px;
  --space-9: 24px; --space-10: 36px;

  /* radius */
  --radius-sm: 6px;
  --radius: 8px;            /* default: buttons, inputs, toggles, sidebar items */
  --radius-md: 10px;        /* cards, popovers */
  --radius-card: 14px;      /* agent card, dialog */
  --radius-pill: 999px;
  /* chat-bubble tail — deliberate asymmetry, shared so a new bubble surface
     can't diverge (backlog L3): user/assistant/form all compose these. */
  --bubble-radius: 14px;
  --bubble-tail: 4px;

  /* type ramp */
  --fs-eyebrow: 11px; --fs-meta: 12px; --fs-body-sm: 13px; --fs-body: 14px;
  --fs-h2: 15px; --fs-title: 16px; --fs-brand: 18px; --fs-h1: 22px;
  /* display: the Workspace greeting question ("What can we work on?") — larger
     than a page h1, fluid to the viewport (new-design-base / #388 wf-hero). */
  --fs-display: clamp(25px, 3vw, 36px);
  --fw-normal: 400; --fw-emph: 600; --fw-bold: 700;

  /* elevation — one color method: color-mix on --text (theme-aware).
     Elevation rule (#401): ordinary shell, Card, Row, and Panel surfaces stay
     FLAT. --shadow-focal is the one restrained lift reserved for the single
     focal surface (the new-chat composer); temporary layers use --shadow-popover
     (dropdowns/popovers) and --shadow-modal (dialogs). */
  --shadow-focal: 0 14px 36px color-mix(in srgb, var(--text) 22%, transparent);
  --shadow-popover: 0 6px 24px color-mix(in srgb, var(--text) 18%, transparent);
  --shadow-modal: 0 12px 40px color-mix(in srgb, var(--text) 25%, transparent);
  --scrim: color-mix(in srgb, var(--text) 35%, transparent);

  /* motion */
  --motion-fast: .12s ease;
  --motion-slide: .22s ease;

  /* z-index ladder (--z-tooltip: hover hints float above open temporary
     layers, below the modal) */
  --z-takeover: 5; --z-dropdown: 20; --z-popover: 30; --z-tooltip: 60;
  --z-modal: 100;

  /* state */
  --opacity-disabled: .5;
}

html[data-theme="dark"] {
  color-scheme: dark;

  --primary: #8b63e6;
  --primary-hover: #9d78ee;
  --on-primary: #ffffff;
  --secondary: #bbb8c4;

  /* Dark surfaces: neutral CHARCOAL (#401 / #386) — replaces the retired
     purple-black family. Neutral greys so the violet brand accent and the four
     identity families read against a quiet base. */
  --surface: #17171a;
  --surface-raised: #202023;
  --surface-hover: #2c2c30;

  --text: #f0eff3;
  --text-muted: #bbb8c4;
  --border: #39383f;
  --border-strong: #54515d;

  --accent: #a47ff0;
  --accent-surface: #2e2350;
  --accent-text: #c9b3f7;
  --focus-ring: #e1dde8;   /* neutral focus, light-on-dark */

  --success: #4ade80;
  --success-surface: #263b31;
  --warning: #e6b84a;
  --warning-surface: #342a10;
  --warning-border: #766543;
  --danger: #f87171;
  --danger-surface: #3a1a1a;
  --danger-border: #602c2c;

  /* identity families (dark) — Themeable hues, stable roles. -strong keeps its
     single value (dark enough for white in both modes). */
  --agent: #4ade80; --agent-surface: #123322; --agent-text: #6ee7a0; --agent-strong: #1a7a43;
  --skill: #e6b84a; --skill-surface: #342a10; --skill-text: #f0cf6b; --skill-strong: #9a6600;
  --file: #75b7ff; --file-surface: #142d49; --file-text: #9acaff; --file-strong: #265988;
  --notification: #ee86b1; --notification-surface: #442033; --notification-text: #f4adca; --notification-strong: #a8476f;
}

* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0; background: var(--surface); color: var(--text);
  font-family: var(--sans); font-size: 14px; line-height: 1.5;
}
button {
  font: inherit; cursor: pointer; border: 1px solid var(--border);
  background: var(--surface-raised); color: var(--text);
  border-radius: 8px; padding: 5px 12px;
}
button:hover { border-color: var(--border-strong); background: var(--surface-hover); }
button.primary { background: var(--primary); color: var(--on-primary); border-color: var(--primary); }
button.primary:hover { background: var(--primary-hover); border-color: var(--primary-hover); }
button.danger { color: var(--danger); }
button:disabled { opacity: var(--opacity-disabled); cursor: default; }
/* Visible keyboard focus on every button (#401): the NEUTRAL, Theme-aware
   --focus-ring is the resolved standard for focus everywhere (it replaced the
   violet --accent ring; the accent brand colour is no longer a focus signal). */
button:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
/* number/date/email/search share the base control styling (backlog H3) so
   #cfg-budget and the chat Form inputs stop getting raw user-agent chrome. */
input[type=text], input[type=password], input[type=number], input[type=date],
input[type=email], input[type=search], select, textarea {
  font: inherit; border: 1px solid var(--border); border-radius: 8px;
  padding: 6px 10px; background: var(--surface-raised); color: var(--text);
}
/* Text-input focus is RESTRAINED (#401): a neutral border + caret rather than
   the full focus ring, so composing does not sit inside a loud outline. */
input[type=text]:focus, input[type=password]:focus, input[type=number]:focus,
input[type=date]:focus, input[type=email]:focus, input[type=search]:focus,
select:focus, textarea:focus {
  outline: none; border-color: var(--focus-ring); caret-color: var(--text);
}
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

/* Selectable list row (#65, backlog H1) — ONE primitive behind what were six
 * bespoke rows. The canonical selectable-row look: flex, the .sel selected
 * state, --surface-hover hover, an optional .ico slot. The validator list,
 * the attach-a-Form list and the field-designer list compose
 * it (and the FileTree, #63); variant classes carry only true deltas.
 * Defined early so a variant's later rules win on equal specificity. */
.list-row {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-3) var(--space-5); border-radius: var(--radius-sm);
  cursor: pointer; color: var(--text);
}
.list-row:hover { background: var(--surface-hover); }
.list-row.sel { background: var(--accent-surface); color: var(--accent-text); }
.list-row:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -1px; }
.list-row .ico { width: var(--space-8); text-align: center; flex: none; }

/* A registry glyph is a BLOCK by default (CJ, 2026-08-06; backlog DR37).
 *
 * WHY this is the default rather than a list of controls that opt in: an SVG is
 * an inline box, so it sits on a text baseline and its host reserves descender
 * space BELOW the glyph. A `place-items: center` control then centers that
 * TALLER box rather than the mark, and an 18px glyph reads ~3px high. It bites
 * whenever a control wraps its icon in a NESTED span — which is how almost every
 * icon host in this codebase is built. The bug therefore recurred per component,
 * and #599's fix (one `:is(...)` list of every icon-only control) still had to be
 * JOINED by hand — a list #599 itself forgot to join in the same branch. So the
 * rule belongs at the source: `icons.js` stamps every glyph `.ico`, and every
 * `.ico` is a block. Nothing to remember.
 *
 * `svg.ico` is deliberately narrow — it matches the REGISTRY's output only.
 * Hand-inlined SVGs (canvas-shell's 16x16 Canvas-core marks, workspace.js's
 * agent/skill spark marks) carry no `.ico`, so hosts that mix registry and
 * hand-cut glyphs keep their own rule; those are marked where they appear.
 *
 * A glyph set inline WITHIN A TEXT RUN is the one real exception: it needs its
 * baseline back, so it re-declares `display: inline` alongside its
 * `vertical-align` (see .ws-greet .gr-kw). Positioning the HOST instead — an
 * inline-block wrapper with the vertical-align on it, .ws-kind-word's shape — is
 * the better pattern and needs no exception at all. */
svg.ico { display: block; }

/* Unified sidebar nav (#41). sidebar.js wraps each admin page's body into
 * .app-content and prepends #app-sidebar; body becomes a flex row. */
/* #49: override the base `html, body { height: 100% }` so the body grows with
 * content — that gives the sticky sidebar (its containing block) room to stick;
 * otherwise the body is capped at one viewport and the nav scrolls away. */
body.has-sidebar { display: flex; min-height: 100vh; height: auto; margin: 0; }
.app-content { flex: 1; min-width: 0; display: flex; flex-direction: column; }
#app-sidebar {
  flex: none; width: 252px; display: flex; flex-direction: column; gap: 2px;
  padding: 0; background: var(--surface-raised);
  border-right: 1px solid var(--border);
  /* #49: pin the nav and let IT scroll (not the page) when its own content
   * exceeds the screen — 100dvh so mobile browser chrome never clips the
   * bottom item; overflow-y:auto shows a scrollbar only when needed. */
  position: sticky; top: 0; height: 100dvh; overflow: visible;
  transition: width var(--motion-fast);
}
#app-sidebar.collapsed { width: 64px; }
.sb-rail-head { flex: none; min-height: 64px; display: flex; align-items: center;
  gap: 10px; padding: 10px; }
.sb-brand { min-width: 0; display: flex; align-items: center; gap: 10px;
  color: var(--text); text-decoration: none; overflow: hidden; }
.sb-brand:hover { text-decoration: none; }
.sb-mark { width: 36px; height: 36px; display: grid; place-items: center;
  flex: none; border-radius: var(--radius-md); color: var(--surface-raised);
  background: var(--text); font-size: 16px; font-weight: 800; }
.sb-brand-copy { min-width: 0; }
.sb-brand-copy strong, .sb-brand-copy span { display: block; white-space: nowrap; }
.sb-brand-copy strong { font-size: 16px; }
.sb-brand-copy span { color: var(--text-muted); font: 10px/1.35 var(--mono);
  letter-spacing: .05em; }
.sb-collapse { width: 34px; height: 34px; margin-left: auto; padding: 0;
  display: grid; place-items: center; flex: none; color: var(--text-muted);
  background: transparent; border-color: transparent; }
.sb-collapse:hover { color: var(--text); background: var(--surface-hover); }
.sb-nav-scroll { min-height: 0; flex: 1 1 auto; overflow-y: auto;
  padding: 2px 10px 12px; }
.sb-nav { display: flex; flex-direction: column; gap: 2px; }
/* The Builder rail sections (.sb-builder / .sb-sep, #44/#55/#137) are GONE as of
   2026-08-08 (CJ). Every Module's sub-menu had been reduced to a single item
   naming the place you already were, so the Agent (#609), Skill and Validator
   Builders all dropped theirs and mark their Module current in the main rail
   instead. With no consumers the relocation mechanism went too, and these rules
   with it. `.sb-item` / `.sb-ico` / `.sb-label` stay — the main rail and the
   Workspace settings rail are their live users. */
/* Nav item (#65, M7): ONE definition of the sidebar + Settings sub-nav row
   look. The prefixed classes alias it; new navs use .nav-item directly. */
.nav-item, .sb-item, .settings-subnav button {
  display: flex; align-items: center; gap: var(--space-5);
  min-height: 38px; padding: 8px 9px; border-radius: var(--radius);
  font-size: var(--fs-body); color: var(--text); text-decoration: none;
  white-space: nowrap; overflow: hidden;
}
.nav-item:hover, .sb-item:hover,
.settings-subnav button:hover {
  background: var(--surface-hover); text-decoration: none;
}
.nav-item.active, .sb-item.active, .settings-subnav button.active {
  background: var(--accent-surface); color: var(--accent-text); font-weight: var(--fw-emph);
}
.sb-item:focus-visible,
.sb-collapse:focus-visible, .sb-brand:focus-visible, .sb-profile:focus-visible {
  outline: 2px solid var(--focus-ring); outline-offset: 1px;
}
.sb-ico { width: 22px; display: grid; place-items: center; flex: none; }
.sb-ico svg { width: 18px; height: 18px; }
.sb-rail-foot { flex: none; position: relative; padding: 8px 10px 10px;
  border-top: 1px solid var(--border); }
.sb-profile { width: 100%; min-height: 40px; display: flex; align-items: center;
  gap: 9px; padding: 6px; border-color: transparent; border-radius: var(--radius);
  background: transparent; color: var(--text); text-align: left; }
.sb-profile:hover { background: var(--surface-hover); }
.sb-avatar { width: 30px; height: 30px; display: grid; place-items: center;
  flex: none; border-radius: 50%; color: var(--accent-text);
  background: var(--accent-surface); font-weight: var(--fw-emph); }
.sb-profile-copy { min-width: 0; flex: 1; }
.sb-profile-copy strong, .sb-profile-copy span { display: block; overflow: hidden;
  white-space: nowrap; text-overflow: ellipsis; }
.sb-profile-copy strong { font-size: var(--fs-body-sm); }
.sb-profile-copy span { color: var(--text-muted); font-size: var(--fs-meta); }
.sb-profile-more { display: grid; place-items: center; color: var(--text-muted); }
.sb-profile-menu { position: absolute; left: calc(100% + 8px); bottom: 10px;
  z-index: var(--z-popover); min-width: 190px; padding: 6px;
  border: 1px solid var(--border-strong); border-radius: var(--radius);
  background: var(--surface-raised); box-shadow: var(--shadow-popover); }
.sb-profile-menu[hidden] { display: none; }
.sb-menu-item { width: 100%; display: block; padding: 8px 9px; border: 0;
  border-radius: var(--radius-sm); background: transparent; color: var(--text);
  text-align: left; text-decoration: none; }
.sb-menu-item:hover { background: var(--surface-hover); text-decoration: none; }
.sb-menu-item.danger { color: var(--danger); }
.sb-menu-divider { height: 1px; margin: 5px 4px; background: var(--border); }
.sb-menu-form { margin: 0; }
#app-sidebar.collapsed .sb-label,
#app-sidebar.collapsed .sb-brand-copy,
#app-sidebar.collapsed .sb-profile-copy,
#app-sidebar.collapsed .sb-profile-more,
#app-sidebar.collapsed .sb-collapse { display: none; }
#app-sidebar.collapsed .sb-rail-head,
#app-sidebar.collapsed .sb-profile { justify-content: center; }
#app-sidebar.collapsed .sb-brand { justify-content: center; }
#app-sidebar.collapsed .sb-nav-scroll,
#app-sidebar.collapsed .sb-rail-foot { padding-inline: 10px; }
#app-sidebar.collapsed .sb-item { justify-content: center; padding-inline: 8px; }

.topbar {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 10px 18px; background: var(--surface-raised);
  border-bottom: 1px solid var(--border);
}
/* display:flex above out-specifies the UA [hidden] rule — restore it so a
 * .topbar can be hidden (e.g. the skills page topbar in the full-screen edit
 * view, #64). */
.topbar[hidden] { display: none; }
.topbar .brand { font-weight: 700; letter-spacing: .02em; }
.topbar .brand a { color: var(--text); }
.topbar .brand .dot { color: var(--accent); }
/* per-Agent Theme logo (#30) — replaces the Yatsar mark when set */
.topbar .brand-logo { display: block; height: 26px; max-width: 180px; object-fit: contain; }
.topbar .spacer { flex: 1; }
.flex-spacer { flex: 1; }
.login-panel { max-width: 360px; }
.login-form { display: flex; flex-direction: column; gap: var(--space-3); }
.chat-root { flex: 1; min-height: 0; display: flex; }
/* A pill does not wrap — the base anatomy, promoted out of the Hub's Unclaimed
   queue by the 2026-08-11 design review; see `.badge` in the spec sheet
   (`shared/design-system/design/design-system.css`) for the argument. A no-op
   on every badge this file paints today: all sixteen are short topbar page
   labels in a wide flex bar. */
.badge {
  font-family: var(--mono); font-size: 11px; color: var(--secondary);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 999px; padding: 2px 9px; white-space: nowrap;
}
/* Shared eyebrow micro-label (backlog L1): the uppercase-muted section-label
   recipe that was hand-repeated 4× (dash-table headers, takeover-sub,
   publish-panel section labels, fd-ctl-label). New labels use .eyebrow; the
   existing instances migrate to it as their surfaces are next touched. */
.eyebrow {
  font-size: var(--fs-eyebrow); text-transform: uppercase;
  letter-spacing: .04em; color: var(--text-muted);
}

/* Screen-reader-only text (#270) — the standard clip pattern. For labels that
   must be announced by AT but not painted (users.html connector selects,
   icon-button names). Not display:none — that hides from AT too. */
.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip: rect(0 0 0 0); clip-path: inset(50%);
  overflow: hidden; white-space: nowrap;
}

/* ---------- admin ---------- */
/* Tier-2 content shell (#66): full-width, one gutter, no left-pin. The console's
   content pages (Agents, Skills, Validators, Dashboard) share one width rule so
   moving between them feels like one app, not four. */
.admin-wrap { max-width: none; margin: 24px 0; padding: 0 36px; }
.admin-wrap h1 { font-size: 22px; margin: 0 0 4px; }
.admin-wrap .sub { color: var(--text-muted); margin: 0 0 22px; }
/* Module-list header (#142): the h1 + sub on the left, the page's primary
   "Create a …" action on the right — the unified Module-page header row. */
.module-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-6); }
.module-head > button { flex: none; }
.new-agent { display: flex; gap: 8px; margin-bottom: 26px; }
.new-agent input { flex: 1; }
/* The two action-row lists (Agents, Skills, #66) tile as a responsive card grid
   so the action buttons ride their own card instead of stranding from the name. */
.card-grid,
#list, #skill-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 12px; }
.agent-card {
  position: relative;
  background: var(--surface-raised); border: 1px solid var(--border);
  border-radius: 12px; padding: 14px 16px;
  display: flex; flex-direction: column; align-items: stretch; gap: 10px;
}
/* status overlay: published-version + validator badges ride the top-right
   corner, overlapping the border, so they never crowd or wrap the name. The
   surface fill lets a pill sit cleanly across the hairline. */
.card-status {
  position: absolute; top: -11px; right: 14px;
  display: flex; gap: 6px;
}
.card-status .pub-badge { margin: 0; background: var(--surface-raised); }
.agent-card .name { font-weight: 600; font-size: 15px; }
.agent-card .meta { color: var(--text-muted); font-size: 12px; }
.agent-card .grow { flex: 1; }
.agent-card .warn { color: var(--warning); font-size: 12px; }
/* the card's action buttons, on their own row at the foot of the tile */
.card-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.empty-note { color: var(--text-muted); text-align: center; padding: 40px 0; }
/* compact variant (backlog L7): the one empty-state for narrow panes — e.g. the
   grants library. (.editor-placeholder stays reserved for the file-editor
   "pick a file" affordance.) */
.empty-note.compact { padding: var(--space-5) 0; font-size: var(--fs-body-sm); }

/* ---------- builder layout ---------- */
.builder {
  display: grid; height: 100vh;
  /* The topbar is OPTIONAL — the Builder UI surface can render without the
     global chrome (e.g. a standalone Theme takeover, or a reference mockup).
     Default to a single full-height content row; add the auto topbar row only
     when a `.topbar` child is present. Hardcoding `auto 1fr` collapsed the
     content to 0 height when no topbar filled the auto row. (:has() is already
     used in this file — see .attach-check.) */
  grid-template-rows: 1fr;
  /* ONE column that may shrink below its content. Without this the implicit
     column is `auto` (= minmax(auto, max-content)), whose `auto` MINIMUM is the
     content's min-content width — so the column floored at whatever the widest
     row inside needed and the whole page scrolled sideways, clipping the Admin
     sidebar and the command bar's last control. It also made the 980px bench
     query inert: the narrow tracks were applied and then handed a container
     wider than the viewport, so they never took effect. minmax(0, 1fr) lets the
     column track the viewport and pushes the compressing down to the regions
     that own it (.bench-work's tracks, .bench-command's fields). */
  grid-template-columns: minmax(0, 1fr);
}
.builder:has(> .topbar) { grid-template-rows: auto 1fr; }
/* `display: grid` above out-specifies the UA [hidden] rule — restore it so a
   page that toggles a `.builder` editor view via the hidden attribute (the
   Connector/Validator Editors) actually hides it (same pattern as
   .topbar[hidden] / .mode-toggle[hidden]). */
.builder[hidden] { display: none; }
/* Mode-switching body (#55): the persistent topbar above, then this 1fr row
 * that holds the Agent Editor and the full-area takeovers/overlays. The body
 * is the --surface page the editor cards float on (gutter padding below). */
#builder-body { position: relative; min-height: 0; background: var(--surface); padding: var(--space-8); }
/* `.pane` is the loose card vocabulary still used by the Connector wizard and
   Workspace surfaces; the Builders (Agent/Skill/Validator) now compose over the
   shared Context Bench (.bench-*, #422/#423) instead of the old .builder-main /
   .skill-build-grid tracks + .pane-resize gutters, which were removed with the
   migration. */
.pane { min-height: 0; display: flex; flex-direction: column; background: var(--surface-raised); }
.pane-head {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 12px; border-bottom: 1px solid var(--border);
  font-size: 12px; color: var(--text-muted); flex-wrap: nowrap;
}
.pane-head .title { font-weight: 600; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Three-zone center-pane head (#139; hardened #151, DESIGN.md Decision 40): equal-flex
   side zones flank the centered toggle, so the filename + Rename (or the draft-agent
   title) read top-left and the actions top-right while the toggle stays dead-center.
   The pattern serves EVERY editor pane-head toggle — Editor⇄Agent (Agent) and
   Editor⇄Config (Connector). Equal flex:1 1 0 zones keep it centered; the side zones
   may shrink (min-width:0) and the head NEVER wraps (flex-wrap:nowrap), so a too-narrow
   pane keeps all three zones on one row and the filename ellipsizes (.title above)
   instead of dropping the actions to a second line. The .mode-toggle keeps its
   intrinsic width (flex:0 0 auto) so it never squeezes. NB: do NOT give .ed-controls a
   display here — it would out-specify the .ed-controls[hidden] rule and break the
   toggle's show/hide. (The bench Work head carries the same three zones — see
   .bench-panel-head .ph-side — and the Agent Builder centers Source/Preview there;
   the floating .pane-editor > .view-toggle is the other posture.) */
.pane-head .ph-side { display: flex; align-items: center; gap: 8px; flex: 1 1 0; min-width: 0; }
.pane-head .mode-toggle { flex: 0 0 auto; }
/* The actions zone never shrinks below its buttons (min-width:max-content), so
   on a narrow editor pane the right zone keeps its full width instead of
   overflowing its equal-flex box leftward over the centered toggle — the
   filename (left zone, min-width:0) ellipsizes to make room instead (#155/#159
   density: Save·Revert·Delete). Still truly centered when the pane is wide. */
.pane-head .ph-side-right { justify-content: flex-end; min-width: max-content; }

/* file tree */
/* The file tree (#63): rows are the shared .list-row + an .ico glyph slot
   (retiring the old .tree .row / .skill-files .row — backlog H1). The tree
   only tightens density, styles its icon column, and ellipsizes long names. */
.tree { overflow: auto; flex: 1; padding: var(--space-4) var(--space-3); font-size: var(--fs-body-sm); }
.tree .node { border-radius: var(--radius-sm); }
.tree .list-row { padding: var(--space-2) var(--space-4); gap: var(--space-3); }
.tree .list-row .ico { width: var(--space-7); color: var(--text-muted); }
.tree .list-row.sel .ico { color: var(--accent-text); }
.tree .tree-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tree .tree-empty { color: var(--text-muted); cursor: default; }
.tree .kids { margin-left: var(--space-7); }
/* …except in the Agent / Skill editor's File Explorer, whose rows ARE the
   Workspace File Explorer's rows (`.ws-fe-*`, the Utility block far below) —
   same checkbox, chevron, file glyphs and File-blue selected treatment, because
   the two are one surface wearing two hosts. `.tree` there supplies only the
   scroll box and the compact rail's type size; the `.list-row` rules above are
   left for the Canvas Folder core (`.ws-fx`), which still composes them.
   Net-new is the one row that Explorer has no equivalent for: the placeholder
   an expanded-but-empty folder shows, so an open folder never reads as a
   failed fetch. It is a row, so it indents with the rest — but it is not
   selectable, so it takes no hover. */
.tree .ws-fe-empty-row { color: var(--text-muted); font-size: var(--fs-meta);
  cursor: default; }
.tree .ws-fe-empty-row:hover { color: var(--text-muted); background: none; }

/* editor */
.pane-editor { position: relative; }
/* Form picker (#37): a small dropdown under a pane-head. Its one instance is the
   First Interaction pane's Attach button (#609 edit 9 retired the editor's own
   Insert Form picker), so the posture that used to live in .fi-form-pane's
   override is folded in here — one instance, one rule. */
.if-picker {
  position: absolute; top: 46px; right: var(--space-5); z-index: 20;
  min-width: 180px; max-height: 50%; overflow: auto;
  display: flex; flex-direction: column; gap: 2px; padding: 6px;
  background: var(--surface-raised); border: 1px solid var(--border-strong);
  border-radius: 8px; box-shadow: var(--shadow-popover);
}
.if-picker[hidden] { display: none; }
/* .list-row delta: a single-line <button> action (no .sel) — reset chrome. */
.if-item {
  display: block; width: 100%; text-align: left; border: none; background: none;
  font-size: var(--fs-body-sm);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.if-empty { font-size: 13px; color: var(--text-muted); padding: 6px 8px; }
.if-empty a { color: var(--accent-text); }
.editor-area { flex: 1; display: flex; min-height: 0; }
.editor-area textarea {
  flex: 1; resize: none; border: 0; border-radius: 0; padding: 14px 16px;
  font-family: var(--mono); font-size: 13px; line-height: 1.55;
  background: var(--surface-raised); tab-size: 2;
}
/* Text-input focus is RESTRAINED (#401): the full-bleed borderless editor
   shows the neutral border on its enclosing bench panel + the caret — never a
   full ring (the .ws-code:focus-within container-tint precedent). */
.editor-area textarea:focus { outline: none; caret-color: var(--text); }
.bench-panel:has(.editor-area textarea:focus) { border-color: var(--focus-ring); }

/* Change-highlight overlay (#154, PRD #151): a transparent-text textarea over a
   pixel-aligned backdrop (.ed-highlight) that paints added/replaced words with
   the --accent-surface wash. The backdrop's box model MUST match the textarea
   (padding/font/line-height/tab-size) so glyphs align; scroll is JS-mirrored.
   .ed-highlight is also reused standalone (#159 Preview, #160 First Interaction). */
.ed-highlight {
  flex: 1; min-height: 0; margin: 0; padding: 14px 16px; overflow: auto;
  white-space: pre-wrap; word-break: break-word;
  font-family: var(--mono); font-size: 13px; line-height: 1.55; tab-size: 2;
  background: var(--surface-raised); color: var(--text);
}
.ed-highlight mark {
  background: var(--accent-surface); color: inherit; border-radius: 3px;
  padding: 0 1px; box-decoration-break: clone; -webkit-box-decoration-break: clone;
}
.ed-overlay { position: relative; flex: 1; min-height: 0; }
.ed-overlay textarea {
  position: absolute; inset: 0; background: transparent; color: transparent;
  caret-color: var(--text);
}
.ed-overlay .ed-highlight {
  position: absolute; inset: 0; overflow: hidden; pointer-events: none;
}

/* Source / Preview (#159): the read-only rendered Markdown view (highlights
   preserved via .preview-body mark) + its toggle. Two postures: the Agent
   Builder docks it in the CENTERED head zone (Decision 43 revised — the bench
   command bar owns Editor/Agent, so the head's centre zone is free), while the
   remaining editors keep the FLOATING variant docked bottom-right of the editor
   body (.pane-editor > .view-toggle; .pane-editor is already position:relative). */
.preview-body {
  flex: 1; min-height: 0; overflow: auto; padding: 16px 20px;
  background: var(--surface-raised); font-size: 13.5px; line-height: 1.55;
}
.preview-body h1 { font-size: 1.4em; margin: 10px 0 8px; }
.preview-body h2 { font-size: 1.18em; margin: 16px 0 6px; }
.preview-body p, .preview-body li { margin: 6px 0; }
.preview-body ul { margin: 6px 0; padding-left: 20px; }
.preview-body code {
  font-family: var(--mono); font-size: .92em; background: var(--surface);
  padding: 1px 4px; border-radius: 4px;
}
.preview-body mark {
  background: var(--accent-surface); color: inherit; border-radius: 3px;
  padding: 0 1px; box-decoration-break: clone; -webkit-box-decoration-break: clone;
}
/* The floating variant: a DIRECT child of the editor pane (the head-hosted one
   is a child of .bench-panel-head, so it stays in flow).
   The float adds only the docking + the lift; it no longer re-declares a
   background, so its track is the same recessed --surface as every other
   segmented toggle (§ Segmented toggle) — this rule out-specifies that one. */
.pane-editor > .view-toggle {
  position: absolute; bottom: var(--space-5); right: var(--space-5); z-index: 5;
  box-shadow: 0 2px 8px color-mix(in srgb, var(--text) 14%, transparent);
}
/* .mode-toggle's display:inline-flex out-specifies the UA [hidden] rule — restore
   hide-on-[hidden] (same pattern as .ed-controls[hidden]). Keyed to the TOGGLE
   classes, not to the .view-toggle posture: every instance that hides today also
   happens to carry .view-toggle, so a posture-keyed reset only worked by
   coincidence and the next bare .mode-toggle to take `hidden` would render
   visible-but-inert. That exact trap cost this batch a debugging round on
   .bench-tree-tools — attribute state is not visual state in this stylesheet. */
.mode-toggle[hidden], .bench-mode-group[hidden] { display: none; }
/* Connector raw-JSON editor (#153): an inset danger ring on invalid JSON +
   a .json-error bar below — the edit is kept, not dropped, on a bad switch. */
.editor-area textarea.invalid { outline: 2px solid var(--danger-border); outline-offset: -2px; }
.json-error {
  color: var(--danger); background: var(--danger-surface);
  border: 1px solid var(--danger-border); border-radius: var(--radius);
  padding: var(--space-4) var(--space-5); font-size: var(--fs-meta); margin: var(--space-4);
}
.editor-placeholder {
  flex: 1; display: flex; align-items: center; justify-content: center;
  color: var(--text-muted);
}
.dirty-dot { color: var(--accent); font-size: 16px; line-height: 1; }

/* Pending-change dot (#151): a changed file in the Files tree, bubbling to its
   ancestor folders. Reuses the dirty-dot accent; margin-left:auto pins it to
   the row's trailing edge. */
.tree .pending-dot, .sb-item .pending-dot {
  color: var(--accent); font-size: 13px; line-height: 1; margin-left: auto;
  padding-left: var(--space-3); flex: none;
}

/* First Interaction Form pane (CJ, 2026-08-07): the Work canvas splits for
   FIRST_INTERACTION.md alone. The editor keeps the top; this is the bottom, and
   it renders the file's page-load Form with chat's own renderer. Fixed
   proportion by design — the bench owns dragging, and a horizontal resizer here
   would be a second resize idiom for one file.

   `position: relative` anchors the Attach picker to THIS pane: .if-picker is
   absolute, so without this it would resolve against .pane-editor and fly to the
   top of the canvas, nowhere near the button that opened it. */
.fi-form-pane {
  position: relative; flex: 0 0 38%; min-height: 180px; min-width: 0;
  display: flex; flex-direction: column;
  border-top: 1px solid var(--border); background: var(--surface-raised);
}
.fi-form-pane[hidden] { display: none; }
/* The pane's head IS .bench-panel-head (same anatomy, same height, same button
   scale as every other head on the bench) — this only turns the divider around,
   since the border above it is the pane's own. */
.fi-form-head { border-bottom: 1px solid var(--border); }
.fi-form-state {
  font-family: var(--mono); font-size: var(--fs-meta); color: var(--text-muted);
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fi-form-state-invalid { color: var(--danger); }
.fi-form-body { flex: 1; min-height: 0; overflow: auto; padding: var(--space-6) var(--space-7); }
/* The advisory line above a render, and the calm empty/why-nothing-rendered
   state. One treatment: nothing here is a failure the author must fix now —
   Save accepts these bytes either way (the .fd-region precedent). */
.fi-form-note { color: var(--text-muted); font-size: var(--fs-meta); font-style: italic; margin-bottom: var(--space-4); }
.settings-field-wide { max-width: 340px; }
.bench-chat-host { flex: 1; min-height: 0; display: flex; }
/* A mounted YatsarChat adds `.yc` (display:flex) to this same element, which is
   equal-specificity and LATER in this file — so a single-class hide would lose to
   it and the chat would render stacked under the editor body (CJ UAT). The
   attribute selector out-specifies `.yc`, so `hidden` is the one hide switch. */
.bench-chat-host[hidden] { display: none; }
.cfg-panel { padding: var(--space-7); display: flex; flex-direction: column; gap: var(--space-6); overflow: auto; }
/* These carry an explicit display, which would otherwise beat the `hidden`
   attribute — restore hide-on-[hidden] so the rail switches cleanly (#160). */
.pane-head[hidden], .cfg-panel[hidden] { display: none; }
/* The editor pane-head's inline-rename input (`.ed-path-input`) went out with the
   head's Rename + Delete buttons on 2026-08-07 (CJ): renaming, moving and
   deleting a file are File Explorer tools acting on the checked row, so the head
   holds only what is about the buffer — the filename, the dirty dot, Revert and
   Save. Those still carry no size delta: every action in an editor head reads at
   the one base button size, so the head never mixes two button scales (CJ UAT). */

/* Inline Validator Test strip (#142): pick a published Agent, Run, see the
   verdict — sits under the prompt textbox at the foot of the Work canvas. The
   Validator Editor is now the single-file (.no-nav) Context Bench tier (#423);
   its layout track comes from .bench-work.no-nav, not a bespoke grid. */
.test-strip {
  display: flex; align-items: center; gap: var(--space-4);
  padding: var(--space-4) var(--space-5); border-top: 1px solid var(--border);
  flex-wrap: wrap; background: var(--surface-raised);
}
.test-strip .test-label {
  font-size: var(--fs-meta); color: var(--text-muted);
  text-transform: uppercase; letter-spacing: .04em;
}
.test-strip select { flex: 1; min-width: 160px; }

/* ---------- Segmented toggle — ONE look for every view/mode switch ----------
   CJ's ruling (2026-08-07) on the Agent Editor's three toggles: the bench
   command bar's Editor⇄Agent (.bench-mode-group), the Work head's
   Source⇄Preview and the form's Editor/JSON/Preview (.mode-toggle) read as one
   control. The .bench-mode-group anatomy wins — a recessed --surface track
   under the WHOLE group, borderless transparent segments, the selected one
   inlaid a step — and the two divergences close on it: the selected segment
   takes .mode-toggle's --primary/--on-primary fill (it was the odd
   --surface-raised "white" chip), and an unselected segment now answers the
   pointer by LIFTING to --surface-raised (it had no hover at all; the base
   button:hover is same-specificity and lost to the transparent background).
   Both selectors share every declaration rather than one aliasing the other —
   neither name is the primitive yet (consolidation-backlog DR18 still owes the
   .seg/.seg-btn extraction, and .ws-cbar-seg / .dm-group are outside this pass).
   .on comes AFTER :hover at equal specificity so a hovered selected segment
   keeps its fill. No font-size delta: one button scale per head (Decision 100).
   The INLAY is the point (CJ, 2026-08-07): a filled segment must read as a chip
   sitting INSIDE the track, so the gutter and the inter-segment gap both go to
   --space-2 (4px, from 2px) and each segment gives back exactly what the track
   took (5px 9px → 3px 7px). The grey frame around the chip widens; the control
   does not grow. Height is unchanged to the pixel (2+5 = 4+3), which is what
   matters — the heads it sits in (.bench-command, .bench-panel-head) are
   height-tuned. Width lands 2-4px narrower depending on the segment count (exact
   parity for both 2- and 3-segment toggles isn't solvable — the gap scales with
   N while the gutter doesn't), and every host centres or right-docks it, so
   nothing shifts. Keep gutter and segment padding in LOCKSTEP on any future
   tweak: shrinking the segment alone shrinks the whole control. */
.mode-toggle, .bench-mode-group {
  display: inline-flex; align-items: center; gap: var(--space-2);
  padding: var(--space-2); background: var(--surface); border-radius: var(--radius);
}
.mode-toggle button, .bench-mode-group button {
  border: 0; background: transparent; padding: 3px 7px;
  color: var(--text-muted); border-radius: var(--radius-sm);
}
.mode-toggle button:hover, .bench-mode-group button:hover {
  background: var(--surface-raised); color: var(--text);
}
.mode-toggle button.on, .bench-mode-group button.on {
  background: var(--primary); color: var(--on-primary); font-weight: var(--fw-bold);
}
/* Inset ring — a base offset:2px ring would bleed outside the track. */
.mode-toggle button:focus-visible, .bench-mode-group button:focus-visible {
  outline: 2px solid var(--focus-ring); outline-offset: -2px;
}
/* Editor / Agent mode control groups in the center pane-head (#139): each lays
   its buttons out inline and follows the active mode. display:flex out-specifies
   the UA [hidden] rule, so restore it (same pattern as .topbar[hidden]).
   min-width:0 because this group is an INTERMEDIATE flex item: it sits between
   the shrinkable head zone (.ph-side, min-width:0) and the title that carries the
   ellipsis (.bench-title). A flex item defaults to min-width:auto — i.e. it
   refuses to shrink below its content — so without this the zone shrank to 0
   while this group kept its full intrinsic width and spilled out over the
   centered Source/Preview toggle, and the title's ellipsis could never fire
   because nothing ever narrowed it. The Skill Builder's head hangs its
   .bench-title directly off .ph-side and was always correct; the shrink has to
   reach the title through every wrapper in between (see .bench-panel-head). */
.ed-controls, .agent-controls {
  display: flex; align-items: center; gap: var(--space-3); min-width: 0;
}
.ed-controls[hidden], .agent-controls[hidden] { display: none; }

/* ---------- Builder Context Bench (#422, prototype #389 "Variant A —
   Context bench", locked b45316e) ----------
   The reusable Navigator | Work | Co-Pilot composition every Module Builder
   shares (context-bench.js wires the mechanics; the page authors the anatomy).
   Re-expressed near-literally from the locked prototype through the design-
   system tokens — the prototype's --wf-* sandbox palette normalizes to the
   production surface/border/identity roles, and the flat-surface tenet (#386)
   holds: bench panels carry no ornamental elevation. */
.bench {
  height: 100%; min-width: 0; min-height: 0;
  display: flex; flex-direction: column; gap: var(--space-4);
}
/* Command bar — the working-mode group + artifact breadcrumb + save-status. */
.bench-command {
  min-height: 42px; flex: none; display: flex; align-items: center;
  /* ONE row, always. Wrapping was tried first and is the wrong tool: flexbox
     breaks lines BEFORE it shrinks, so a bar that overflows drops its last
     control (Publish) onto a second line at any width — 1280px included —
     instead of tightening. */
  flex-wrap: nowrap;
  gap: var(--space-4); padding: 5px var(--space-4);
  background: var(--surface-raised); border: 1px solid var(--border);
  border-radius: var(--radius-md);
}
.bench-command .spacer { flex: 1; }
/* .bench-mode-group's own look lives with .mode-toggle's — one segmented
   toggle, one rule set (see § Segmented toggle above). */
.bench-breadcrumb {
  color: var(--text-muted); font-family: var(--mono);
  font-size: var(--fs-eyebrow); line-height: 1.4;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Save-status pill — Baseline (quiet) / Pending (calm accent, NOT danger, #414)
   / Saved (success). */
.bench-status {
  display: inline-flex; align-items: center; gap: var(--space-3); min-height: 25px;
  padding: 3px var(--space-4); border-radius: var(--radius-pill);
  background: var(--surface); border: 1px solid var(--border);
  color: var(--text-muted); font-size: var(--fs-eyebrow); white-space: nowrap;
}
.bench-status.dirty { color: var(--accent-text); background: var(--accent-surface); border-color: transparent; }
.bench-status.saved { color: var(--success); background: var(--success-surface); border-color: transparent; }
/* No control's own text wraps on the command row. The row used to also carry
   label+field pairs (the Agent Editor's Display name / Agent model), which
   needed their own shrink floor to survive a narrow bar; those fields are Config
   now, so the bar is buttons only and the floor machinery went with them. */
.bench-command button { white-space: nowrap; }

/* Work area — the persistent three-region track. Navigator + Co-Pilot widths
   are CSS-var driven so the resizers persist them; the Work canvas flexes. */
.bench-work {
  min-width: 0; min-height: 0; flex: 1; display: grid;
  grid-template-columns:
    var(--bench-nav-w, 230px) var(--space-4) minmax(320px, 1fr)
    var(--space-4) var(--bench-copilot-w, 330px);
}
/* Single-file tier (e.g. Validator, #423): no Navigator column or its resizer. */
.bench-work.no-nav { grid-template-columns: minmax(320px, 1fr) var(--space-4) var(--bench-copilot-w, 360px); }
.bench-work.no-nav .bench-nav,
.bench-work.no-nav [data-bench-resize="nav"] { display: none; }

.bench-panel {
  min-width: 0; min-height: 0; background: var(--surface-raised);
  border: 1px solid var(--border); border-radius: var(--radius-md);
  overflow: hidden; display: flex; flex-direction: column;
}
.bench-panel-head {
  min-height: 43px; flex: none; display: flex; align-items: center;
  gap: var(--space-4); padding: 7px var(--space-5); flex-wrap: nowrap;
  border-bottom: 1px solid var(--border);
}
.bench-panel-head[hidden] { display: none; }
/* Three-zone Work head (Decision 43, carried onto the bench): equal-flex side
   zones flank the centered .mode-toggle, so the filename + Rename read top-left
   and the actions top-right while the toggle stays dead-centre. The head never
   wraps; a too-narrow pane ellipsizes the filename (.bench-title above +
   min-width:0) instead of dropping the actions to a second row, and the actions
   zone keeps its intrinsic width so it never overruns the toggle. That ellipsis
   only fires if EVERY wrapper between this zone and .bench-title can shrink —
   .ed-controls carries its own min-width:0 for exactly that reason (see there).
   NB: do NOT give .ed-controls a display here — it would out-specify
   .ed-controls[hidden]. */
.bench-panel-head .ph-side { display: flex; align-items: center; gap: var(--space-3); flex: 1 1 0; min-width: 0; }
.bench-panel-head .ph-side-right { justify-content: flex-end; min-width: max-content; }
.bench-panel-head .mode-toggle { flex: 0 0 auto; }
.bench-panel-head .bench-title, .bench-panel-head strong {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-weight: 600; color: var(--text);
}
.bench-panel-body { min-width: 0; min-height: 0; flex: 1; overflow: auto; }
.bench-panel-body[hidden] { display: none; }
/* A panel's inline failure line (#414), on its own row UNDER the head — #539's
   .ws-util-notice idiom, and what keeps the three-zone head's promise. A whole
   refusal sentence inside the actions zone (min-width: max-content) shoves the
   actions past a .bench-panel that hides its overflow and squeezes the filename
   to nothing; here it wraps instead. flex:none because .bench-panel is a column
   flexbox whose body takes the rest, and the padding lines the sentence up
   under the head's own. */
.bench-head-notice {
  flex: none; min-width: 0; padding: var(--space-2) var(--space-5);
  line-height: 1.5;
}
.bench-head-notice[hidden] { display: none; }

/* Resizers — draggable + keyboard-operable separators between the regions. */
.bench-resize { position: relative; min-width: var(--space-4); min-height: var(--space-4); cursor: col-resize; }
.bench-resize::after {
  content: ""; position: absolute; inset: 50% auto auto 50%;
  width: 3px; height: 32px;
  border-left: 1px solid var(--border-strong); border-right: 1px solid var(--border-strong);
  transform: translate(-50%, -50%);
}
.bench-resize:hover::after, .bench-resize.dragging::after {
  border-left-color: var(--accent); border-right-color: var(--accent);
}
.bench-resize:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; border-radius: var(--radius-sm); }

/* Navigator tree — a Project-files group (the shared FileTree mounts here) and a
   Project-context group whose rows open the FI / Config panels in the Work
   canvas. A pending change bubbles a .pending-dot onto its row. */
.bench-nav .bench-panel-body { display: flex; flex-direction: column; }
.bench-tree { padding: 9px 7px var(--space-6); }
.bench-tree-group {
  color: var(--text-muted); font-size: 10px; font-weight: 800; letter-spacing: .11em;
  text-transform: uppercase; padding: 9px var(--space-4) 5px;
}
.bench-tree-row {
  width: 100%; display: flex; align-items: center; gap: 7px; border: 0;
  background: transparent; text-align: left; padding: var(--space-3) 7px;
  color: var(--text-muted); border-radius: var(--radius-sm);
}
.bench-tree-row:hover { background: var(--surface-hover); color: var(--text); }
.bench-tree-row.sel { background: var(--file-surface); color: var(--file-text); font-weight: 700; }
.bench-tree-row:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; }
.bench-tree-row .ico { flex: none; color: var(--text-muted); }
.bench-tree-row.sel .ico { color: var(--file-text); }
/* All five file tools, at the TOP of the tree they act on — the same bar the
   Workspace File Explorer carries (.ws-fe-tools): same position, same order, the
   same icon+label create pair and icon-only Rename / Move / Delete, so a person
   who has used one file surface already knows this one. They used to sit pinned to
   the foot of the panel, which put the controls a whole Project-context group away
   from the files; Rename and Delete then spent a while on the editor head, which
   put naming a file somewhere other than where files are named (CJ, 2026-08-07).
   The icon-only + danger treatments are shared with .ws-fe-tools, below. */
.bench-tree-tools {
  flex: none; display: flex; align-items: center; flex-wrap: wrap; gap: 5px;
  padding: 2px var(--space-2) var(--space-3); margin-bottom: 6px;
  border-bottom: 1px solid var(--border);
}
.bench-tree-tools button {
  display: inline-flex; align-items: center; gap: 5px; min-height: 31px;
  font-size: var(--fs-body-sm); padding: 5px 10px; line-height: 1;
  border: 1px solid transparent; border-radius: var(--radius-sm);
  background: none; color: var(--text);
}
/* :not(:disabled), because Rename / Move / Delete sit here greyed out until a row
   is checked and a dead control must not light up under the pointer. The dim
   itself is the base `button:disabled` rule — nothing bespoke. */
.bench-tree-tools button:hover:not(:disabled) { background: var(--surface-hover); border-color: var(--border); }
.bench-tree-tools button:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }
.bench-tree-tools .ico { flex: none; display: inline-flex; color: var(--text-muted); }
/* The delete tool's glyph takes the button's own danger color instead of the bar's
   muted icon color — the red IS the warning, and a muted trash inside a red button
   reads as disabled. The Workspace bar gets this for free (its `.ws-fe-ico` sets no
   color, so it inherits); here the muted rule above has to be undone. */
.bench-tree-tools .ws-fe-del .ico { color: inherit; }

/* The narrow tracks: the Co-Pilot narrows but stays present — the bench never
   drops its persistent Co-Pilot region.
   TWO breakpoints, because the two tiers have genuinely different floors and one
   number cannot serve both (backlog DR40). The THREE-region tier carries a
   Navigator, so its floor is 252 rail + ~36 chrome + (220 + 320 + 380 + gaps) =
   about 1210px; it swaps here, where the wide tracks actually stop fitting.
   Sharing the Admin 980px breakpoint left a ~230px band that ran the wide tracks
   in a window too small for them, and the page scrolled sideways instead. The
   TWO-region `.no-nav` tier (the single-file Skill/Validator editors) needs about
   996px for the same reason inverted — no Navigator to pay for — so 980px was
   always about right for it and it keeps that. */
@media (max-width: 1210px) {
  .bench-work { grid-template-columns: 190px var(--space-4) minmax(280px, 1fr) var(--space-4) 270px; }
}
@media (max-width: 980px) {
  .bench-work.no-nav { grid-template-columns: minmax(280px, 1fr) var(--space-4) 270px; }
}

/* ---------- chat widget ---------- */
/* root is a row: yc-main (the chat column) | yc-panel (slide-out artifact viewer) */
.yc { display: flex; flex-direction: row; min-height: 0; flex: 1; background: var(--surface); }
.yc-main { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
.yc-messages { flex: 1; overflow-y: auto; padding: 16px 14px; display: flex; flex-direction: column; gap: 12px; }
.yc-msg { max-width: 92%; }
.yc-msg.user { align-self: flex-end; }
.yc-msg.user .yc-bubble {
  background: var(--primary); color: var(--on-primary);
  border-radius: var(--bubble-radius) var(--bubble-radius) var(--bubble-tail) var(--bubble-radius);
  padding: 8px 13px; white-space: pre-wrap; overflow-wrap: break-word;
}
.yc-msg.assistant { align-self: flex-start; width: 100%; max-width: 100%; }
.yc-md {
  background: var(--surface-raised); border: 1px solid var(--border);
  border-radius: var(--bubble-tail) var(--bubble-radius) var(--bubble-radius) var(--bubble-radius); padding: 4px 14px;
  overflow-wrap: break-word; overflow-x: auto;
}
.yc-md > :first-child { margin-top: 8px; } .yc-md > :last-child { margin-bottom: 8px; }
.yc-md pre { background: var(--surface); border-radius: 8px; padding: 10px; overflow-x: auto; font-size: 12.5px; }
.yc-md code { font-family: var(--mono); font-size: .92em; background: var(--surface); padding: 1px 4px; border-radius: 4px; }
.yc-md pre code { background: none; padding: 0; }
.yc-md table { border-collapse: collapse; margin: 8px 0; font-size: 13px; }
.yc-md th, .yc-md td { border: 1px solid var(--border); padding: 4px 9px; text-align: left; }
.yc-md th { background: var(--surface); }
.yc-md blockquote { border-left: 3px solid var(--border); margin: 8px 0; padding: 0 12px; color: var(--text-muted); }
/* the Markdown core's editable friendly view (canvas-fixes #3): a border cue
   when the formatted preview is contenteditable, matching the #352 'border not
   ring' focus pattern. Scoped to [contenteditable] so read-only chat-bubble
   .yc-md is untouched. */
.yc-md[contenteditable="true"] { outline: none; border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm); padding: 10px 12px; min-height: 4em; }
.yc-md[contenteditable="true"]:focus-visible { border-color: var(--focus-ring); }

/* Tool-activity chips in the message timeline (Working-mark→✓/✗). Scoped to .yc-messages
 * so they never collide with the inline /skill chips in .yc-input (#56), which
 * reuse the same .yc-chip class for a very different look. */
.yc-messages .yc-chip {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--mono); font-size: 11.5px; color: var(--secondary);
  background: var(--surface-raised); border: 1px dashed var(--border);
  border-radius: 999px; padding: 2px 10px; margin: 3px 6px 3px 0;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.yc-messages .yc-chip.sub { margin-left: 22px; border-style: dotted; }
.yc-messages .yc-chip .st { flex: none; }
.yc-messages .yc-chip.ok .st { color: var(--success); }
.yc-messages .yc-chip.err .st { color: var(--danger); }
.yc-messages .yc-chip.run .st { color: var(--accent); animation: ycpulse 1s infinite; }
@keyframes ycpulse { 50% { opacity: .35; } }
/* the shared "running" pulse (design-system state class) — the Thinking
   indicator's mark slot wears it (#290) */
.is-running { color: var(--accent); animation: ycpulse 1s infinite; }
/* the Working-mark sizes to whatever text it sits beside */
.yc-y { display: block; }   /* kill the inline-SVG baseline gap */
.yc-messages .yc-chip .st .yc-y { width: 1em; height: 1em; }

.yc-error {
  color: var(--danger); background: var(--danger-surface); border: 1px solid var(--danger-border);
  border-radius: 10px; padding: 8px 12px; font-size: 13px;
}
.yc-notice { color: var(--text-muted); font-size: 12px; font-style: italic; }
/* Status notice (#65, M5): the one lightweight admin status line — used by the
   settings note() and validators flash() helpers via window.Notice. */
.notice { font-size: var(--fs-meta); }
/* #414 sweep: placement for the inline routine-failure notices that replaced
 * the routine-API-failure Dialog.alerts (the color comes from .notice-*). */
.tree-notice { margin-top: var(--space-2); }
.ws-fx-notice { margin: var(--space-2) 0; }
/* #539: the same placement rule for an open-file Utility's own failure line (a Save the
   server refused) and for a core's read-only banner over a truncated read — under the
   bar that Utility owns, above its content. */
.ws-util-notice { margin: var(--space-2) 0; padding: 0 var(--space-1); }
.ws-core-note { margin: 0 0 var(--space-2); line-height: 1.5; }
.notice-ok { color: var(--success); }
.notice-err { color: var(--danger); }
.notice-info { color: var(--text-muted); font-style: italic; }
/* graceful refusal card (#13): budget cap / rate limit — calm, not an error */
.yc-soft-notice {
  color: var(--text-muted); background: var(--surface-raised);
  border: 1px solid var(--border); border-radius: 10px;
  padding: 8px 12px; font-size: 13px;
}

/* ---------- chat Forms (issue #21) ---------- */
/* Rendered inline in the timeline by YatsarForms.render (chat-core.js) —
   the studio form designer reuses the same classes for its live preview. */
.yc-form {
  background: var(--surface-raised); border: 1px solid var(--border);
  border-radius: var(--bubble-tail) var(--bubble-radius) var(--bubble-radius) var(--bubble-radius); padding: 12px 14px; margin: 6px 0;
  display: flex; flex-direction: column; gap: 10px; max-width: 480px;
}
.yc-form.disabled { opacity: var(--opacity-disabled); }
.yc-form.disabled .yc-form-escape { display: none; }
.yc-form-head { display: flex; align-items: center; gap: 8px; }
.yc-form-title { font-weight: 600; font-size: 14px; }
.yc-form-badge {
  font-size: 11px; color: var(--success);
  border: 1px solid var(--border); border-radius: 999px; padding: 1px 8px;
}
.yc-form-desc { color: var(--text-muted); font-size: 13px; }
.yc-form-field { display: flex; flex-direction: column; gap: 4px; }
/* inputs inherit the base control styling (text/number/date/textarea all
   matched there now, backlog H3) — only the textarea resize affordance is a delta. */
.yc-form-field textarea { resize: vertical; }
.yc-form-label { font-size: 12px; font-weight: 600; color: var(--text); }
.yc-form-req { color: var(--danger); }
.yc-form-opts { display: flex; flex-direction: column; gap: 3px; }
.yc-form-opt {
  display: flex; align-items: center; gap: 7px;
  font-size: 13px; cursor: pointer;
}
.yc-form-opt input { accent-color: var(--accent); margin: 0; }
.yc-form-filemeta { font-size: 12px; color: var(--text-muted); }
.yc-form-err { color: var(--danger); font-size: 12px; }
.yc-form-actions { display: flex; align-items: center; gap: 10px; }
.yc-form-actions button { align-self: flex-start; }
.yc-form-escape { color: var(--text-muted); font-size: 12px; font-style: italic; }

.yc-inputbar {
  display: flex; gap: 8px; padding: 10px 12px; position: relative;
  background: var(--surface-raised); border-top: 1px solid var(--border); align-items: flex-end;
}
.yc-inputbar textarea {
  flex: 1; resize: none; max-height: 130px; min-height: 38px;
  border-radius: 10px; padding: 8px 12px;
}
/* contenteditable chat input (#56): grows with content, capped + scrollable;
 * hosts inline /skill chips. */
.yc-input {
  flex: 1; min-height: 38px; max-height: 130px; overflow-y: auto;
  border: 1px solid var(--border-strong); border-radius: 10px;
  padding: 8px 12px; font: inherit; line-height: 1.4; background: var(--surface-raised);
  color: var(--text); white-space: pre-wrap; word-break: break-word;
}
.yc-input:focus { outline: none; border-color: var(--focus-ring); }
.yc-input:empty:before {
  content: attr(data-placeholder); color: var(--text-muted); pointer-events: none;
}
/* Composer held while a Durable Turn runs (#465, ADR-0017 D5): a disabled cue so
 * an attached device reads as not-editable rather than fully live. Mirrors the
 * button:disabled dim + a not-allowed cursor; contentEditable=false already
 * blocks input, this just shows it. */
.yc-input.is-disabled { opacity: var(--opacity-disabled); cursor: not-allowed; }
/* sized to sit on the text line, not stretch it (#56): font slightly below the
 * surrounding text, and the negative block margin pulls the inline-flex box back
 * into the line box so a leading chip keeps the input at its one-line height. */
.yc-input .yc-chip {
  display: inline-flex; align-items: center; gap: 2px;
  vertical-align: middle; margin: -2px 0;
  /* a /skill chip carries the skill category gold, like the docked-skill
     chips + greeting keyword — never the purple --accent tint (#246 category
     system: Skills gold everywhere a category is colored). */
  background: var(--skill-surface); color: var(--skill-text);
  border-radius: 6px; padding: 0 2px 0 6px; font-size: 0.9em; line-height: 1.2;
  user-select: none;
}
.yc-input .yc-chip-name { font-family: var(--mono); }
.yc-input .yc-chip-x {
  border: 0; background: none; color: var(--skill-text); cursor: pointer;
  padding: 0 3px; height: auto; min-height: 0; font-size: 1.1em; line-height: 1;
}
.yc-input .yc-chip-x:hover { background: none; color: var(--danger); }
/* Skill launchers (#388): the row of one-click Skill shortcuts above the
   composer — the ONE Skill-insertion interaction (the obsolete "/"-typed
   picker was removed in #412). */
.yc-dock {
  display: flex; flex-wrap: wrap; gap: 6px; padding: 6px 12px 0;
}
.yc-dock[hidden] { display: none; }
.yc-dock-btn {
  display: inline-flex; align-items: center; gap: 4px;
  font-family: var(--mono); font-size: 12px; padding: 3px 9px; height: auto;
  border-radius: 999px; background: var(--accent-surface);
  color: var(--accent-text); border-color: var(--accent);
  transition: color var(--motion-fast), background var(--motion-fast),
    border-color var(--motion-fast);
}
/* Attached-file chips (#253/#255, retinted File-blue by #417 — prototype #390
   Variant B): removable File-blue chat-context chips under a "Chat context"
   label, above the skill row; hover reveals the remove ×. Live references,
   resolved server-side per turn. Removal is chip-owned; the File Explorer
   paperclip mirrors the same state.
   (.yc-attach-row, NOT .yc-attach — that's the Connector Co-Pilot's 📎.) */
.yc-attach-row { display: flex; align-items: center; flex-wrap: wrap; gap: 7px;
  padding: 6px 12px 0; }
.yc-attach-row[hidden] { display: none; }
.ws-hero-inner .yc-attach-row { justify-content: center; padding: 0; }
.yc-attach-label { display: inline-flex; align-items: center; gap: 5px;
  color: var(--text-muted); font-family: var(--mono); font-size: var(--fs-eyebrow);
  text-transform: uppercase; letter-spacing: .05em; }
.yc-attach-label svg { width: 12px; height: 12px; }
.yc-attach-clip { display: inline-flex; }
.yc-attach-clip svg { width: 12px; height: 12px; }
.yc-attach-chip {
  display: inline-flex; align-items: center; gap: 4px; font-family: var(--mono);
  font-size: 12px; padding: 3px 9px; border-radius: 999px;
  background: var(--file-surface); color: var(--file-text);
  border: 1px solid color-mix(in srgb, var(--file) 40%, var(--border));
  max-width: 240px;
}
.yc-attach-chip .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.yc-attach-chip .x { border: 0; background: none; color: inherit; cursor: pointer;
  padding: 0; width: 14px; font-size: 13px; line-height: 1; opacity: 0; flex: none; }
.yc-attach-chip:hover .x { opacity: 1; }
/* The uploading state (#539): a connected file's chip while its reach is being
   confirmed. Dashed border + a muted status word — the same "in flight, not yet
   promised" vocabulary the rest of the surface uses, and legible without the
   animation, so `prefers-reduced-motion` needs no special case. The × stays: a
   person must always be able to take a chip back. */
.yc-attach-chip.uploading { border-style: dashed; }
.yc-attach-chip.uploading .nm { opacity: 0.7; }
.yc-attach-chip .st { font-size: var(--fs-meta); color: var(--text-muted);
  flex: none; }

/* Chat "+" attach button + menu (#343): the "+" leads the input bar, mirroring
   the Send box; its menu floats above the bar as a popover.
   "Add from Files" is disabled this stream (opens in the Canvas — a later
   stream), shown greyed with a "Canvas · soon" tag. */
.yc-plus { flex: none; width: 38px; height: 38px; padding: 0;
  border: 1px solid var(--border-strong); border-radius: var(--radius-md); background: var(--surface-raised);
  color: var(--text-muted); cursor: pointer; display: grid; place-items: center; }
.yc-plus:hover { border-color: var(--accent); color: var(--accent-text); }
.yc-plus-menu { position: absolute; left: 12px; bottom: 100%; margin-bottom: 6px;
  min-width: 236px; z-index: var(--z-popover); padding: var(--space-3);
  background: var(--surface-raised); border: 1px solid var(--border-strong);
  border-radius: var(--radius-md); box-shadow: var(--shadow-popover);
  display: flex; flex-direction: column; gap: 1px; }
.yc-plus-menu[hidden] { display: none; }
.yc-plus-menu button { display: flex; align-items: center; gap: var(--space-4); width: 100%;
  text-align: left; border: 0; background: none; color: var(--text); font-size: var(--fs-body-sm);
  padding: 8px 10px; border-radius: var(--radius-sm); cursor: pointer; }
.yc-plus-menu button:hover { background: var(--surface-hover); }
.yc-plus-menu button:disabled { color: var(--text-muted); cursor: default; }
.yc-plus-menu button:disabled:hover { background: none; }
.yc-plus-menu .soon { margin-left: auto; font-size: var(--fs-meta); color: var(--text-muted);
  background: var(--surface-hover); border-radius: var(--radius-pill); padding: 1px 8px; }

/* Drag-drop upload target (#343/#345): a dashed-accent frame + tint while a
   file hovers — shared by the chat window (drag-into-chat) and the Files panel
   (drop-to-upload). */
.ws-fdrop { position: relative; }
.ws-fdrop.drag { outline: 2px dashed var(--accent); outline-offset: -3px;
  background: var(--accent-surface); border-radius: var(--radius-md); }
/* the Send button only — NOT the chip's × button nested in .yc-input (#56) */
.yc-inputbar > button { height: 38px; flex: none; }
/* Morphing Send/Stop control (#469, ADR-0017): every YatsarChat surface uses
   this one icon-only native button. The arrow/square transforms and the
   fill→danger color shift all run on --motion-fast — shape and color share
   one clock. Idle is a FILLED control (CJ, 2026-07-24): accent fill + white
   glyph, inverse of the outline draft; --primary/--agent-strong carry their
   own dark-theme values so both modes resolve through the tokens. Agent
   conversations only replace the idle fill; Stop is always semantic
   danger-red. */
button.yc-sendstop {
  --yc-sendstop-fill: var(--primary);
  --yc-sendstop-fill-hover: var(--primary-hover);
  width: 38px; height: 38px; padding: 0;
  display: inline-grid; place-items: center;
  color: var(--on-primary);
  background: var(--yc-sendstop-fill);
  border-color: var(--yc-sendstop-fill);
  border-radius: var(--radius);
  transition: color var(--motion-fast), background var(--motion-fast),
    border-color var(--motion-fast);
}
button.yc-sendstop:hover {
  color: var(--on-primary);
  background: var(--yc-sendstop-fill-hover);
  border-color: var(--yc-sendstop-fill-hover);
}
.yc-sendstop.is-agent,
.yc-sendstop.ws-send-agent {
  --yc-sendstop-fill: var(--agent-strong);
  --yc-sendstop-fill-hover: color-mix(in srgb, var(--agent-strong) 86%, black);
}
button.yc-sendstop.is-stop {
  color: var(--danger);
  background: var(--danger-surface);
  border-color: var(--danger-border);
}
button.yc-sendstop.is-stop:hover {
  color: var(--danger);
  background: color-mix(in srgb, var(--danger) 16%, var(--surface-raised));
  border-color: var(--danger);
}
/* Component-owned morph geometry, not a general-purpose icon: the arrow and
   square are the button's two pseudo-elements, so the Home and mounted-widget
   anatomies cannot drift and no one-off SVG bypasses the icon registry. */
button.yc-sendstop::before,
button.yc-sendstop::after {
  content: ""; grid-area: 1 / 1;
  display: block; background: currentColor;
  transform-origin: center;
  transition: opacity var(--motion-fast), transform var(--motion-fast);
}
button.yc-sendstop::before {
  width: 16px; height: 16px;
  clip-path: polygon(42% 0, 100% 42%, 84% 58%, 59% 36%, 59% 100%,
    41% 100%, 41% 36%, 16% 58%, 0 42%);
}
button.yc-sendstop::after {
  width: 10px; height: 10px; border-radius: 1px;
  opacity: 0; transform: scale(.45) rotate(-45deg);
}
button.yc-sendstop.is-stop::before {
  opacity: 0; transform: scale(.55) rotate(45deg);
}
button.yc-sendstop.is-stop::after {
  opacity: 1; transform: scale(1) rotate(0);
}
/* ---------- Thinking indicator (PRD #288) ---------- */
/* The composite full-turn line: Working-mark · timer · phrase. */
.yc-thinking {
  color: var(--text-muted); font-size: 12px; padding: 0 4px;
  display: inline-flex; align-items: center; gap: 7px;
}
.yc-thinking .yc-work { display: inline-flex; font-size: 15px; line-height: 0; }
.yc-thinking .yc-sep { opacity: .5; }
/* tabular digits: the counting timer never nudges the phrase left/right */
.yc-thinking .yc-elapsed { font-variant-numeric: tabular-nums; }
/* the rotator's cross-fade (#291) — frozen by the reduced-motion kill-switch */
.yc-thinking .yc-phrase { font-style: italic; transition: opacity .3s ease; }
.yc-thinking .yc-phrase.fade { opacity: 0; }

/* ---------- artifact / file card (issue #24; #417 File identity) ----------
   A file artifact reads in the File identity (File-blue surface/text), per #390
   AC6; the approved dark-blue/white Open action is the shared .ws-file-open. */
.yc-artifact {
  display: flex; align-items: center; gap: 10px; cursor: pointer;
  background: var(--file-surface); color: var(--file-text);
  border: 1px solid color-mix(in srgb, var(--file) 48%, var(--border));
  border-radius: 10px; padding: 9px 12px; margin: 6px 0; max-width: 400px;
}
.yc-artifact:hover { border-color: var(--file); }
.yc-artifact:focus-visible { outline: 2px solid var(--focus-ring); border-color: var(--focus-ring); }
.yc-artifact-ico { color: var(--file-text); flex: none; display: flex; }
.yc-artifact-meta { display: flex; flex-direction: column; flex: 1; min-width: 0; }
.yc-artifact-meta .fn {
  font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.yc-artifact-meta .sub {
  font-size: 11.5px; color: var(--text-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.yc-artifact-dl, .yc-panel-dl {
  flex: none; font-size: 12px; color: var(--text);
  border: 1px solid var(--border); border-radius: 8px; padding: 4px 10px;
  background: var(--surface-raised);
}
.yc-artifact-dl:hover, .yc-panel-dl:hover {
  border-color: var(--border-strong); background: var(--surface-hover);
}

/* ---------- slide-out artifact viewer (issue #25) ---------- */
.yc-panel {
  flex: none; width: 0; overflow: hidden; display: flex; flex-direction: column;
  background: var(--surface-raised);
  transition: width var(--motion-slide);
}
.yc.yc-panel-open .yc-panel { width: min(440px, 55%); border-left: 1px solid var(--border); }
.yc-panel-head {
  display: flex; align-items: center; gap: 8px; padding: 10px 12px;
  border-bottom: 1px solid var(--border);
}
.yc-panel-title {
  font-weight: 600; flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.yc-panel-close {
  border: none; background: none; padding: 0 4px;
  font-size: 18px; line-height: 1; color: var(--text-muted);
}
.yc-panel-close:hover { border: none; background: none; color: var(--text); }
.yc-panel-body {
  flex: 1; overflow-y: auto; padding: 14px 18px;
  font-size: 13.5px; overflow-wrap: break-word;
}
.yc-panel-body h1 { font-size: 1.35em; margin: 14px 0 8px; }
.yc-panel-body h2 { font-size: 1.2em; margin: 13px 0 7px; }
.yc-panel-body h3, .yc-panel-body h4 { font-size: 1.05em; margin: 12px 0 6px; }
.yc-panel-body p { margin: 7px 0; }
.yc-panel-body p:empty { min-height: 1em; }
.yc-panel-body table { border-collapse: collapse; margin: 8px 0; font-size: 13px; width: 100%; }
.yc-panel-body td { border: 1px solid var(--border); padding: 4px 9px; text-align: left; vertical-align: top; }

/* ---------- end-user chat page ---------- */
.chatpage { display: flex; flex-direction: column; height: 100vh; }
.chatpage .yc { max-width: 860px; width: 100%; margin: 0 auto; }

/* ---------- Admin Settings Index (#419, spec #400, prototype #391 "Variant A —
   Inset index", locked 3538127) ----------
   Platform Settings = the fixed/collapsible Admin rail (sidebar.js) + one stable
   inset section index beside a composed form surface, exactly one section
   visible at a time. The page locks to the viewport so the index runs full
   height and the content scrolls inside it; both column widths are
   content-independent, so switching sections never shifts the index. Coherent
   configuration groups are flat Cards; each Card's actions sit beside its
   contextual feedback. Below 680px the index collapses to a scrollable section
   bar above the content. Prototype 391's --wf-* palette is re-expressed here
   through the design-system tokens. */
.settings-section { display: flex; flex-direction: column; gap: 12px; }

body.settings-page.has-sidebar { height: 100vh; overflow: hidden; }
.settings-page .app-content { height: 100vh; min-height: 0; }
.settings-wrap {
  max-width: none; margin: 0; padding: 0;
  flex: 1; min-height: 0; display: flex; align-items: stretch;
}
.settings-index {
  flex: none; width: 226px; box-sizing: border-box;
  display: flex; flex-direction: column; gap: 3px;
  padding: 18px 12px; overflow-y: auto;
  background: var(--surface); border-right: 1px solid var(--border);
}
.settings-index-head { padding: 1px 10px 13px; }
.settings-index-head strong { display: block; font-size: var(--fs-title); }
.settings-index-head span { display: block; color: var(--text-muted); font-size: var(--fs-eyebrow); }
.settings-index-list { display: flex; flex-direction: column; gap: 3px; }
.settings-index-btn {
  width: 100%; display: grid; grid-template-columns: 20px minmax(0, 1fr);
  align-items: center; gap: 8px; min-height: 42px; padding: 8px 9px;
  color: var(--text-muted); text-align: left; font-family: inherit;
  background: none; border: 1px solid transparent; border-radius: var(--radius);
  cursor: pointer;
}
.settings-index-btn > span:last-child { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.settings-index-btn:hover { color: var(--text); background: var(--surface-hover); }
.settings-index-btn.active { color: var(--text); background: var(--accent-surface); font-weight: var(--fw-bold); }
.settings-index-btn.active .ico { color: var(--primary); }
.settings-index-btn:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.settings-stage { flex: 1; min-width: 0; overflow-y: auto; padding: 28px clamp(20px, 4vw, 48px) 120px; }
.settings-sec { width: min(960px, 100%); }
.settings-actions { display: flex; gap: 8px; align-items: center; margin-top: 8px; flex-wrap: wrap; }

/* page-head per section: kicker + heading + description */
.settings-page-head { margin-bottom: 20px; }
.settings-kicker { color: var(--primary); font-size: var(--fs-eyebrow); font-weight: var(--fw-bold); letter-spacing: .1em; text-transform: uppercase; }
.settings-page-head h1 { margin: 2px 0 4px; font-size: var(--fs-h1); }
.settings-page-head p { margin: 0; max-width: 640px; color: var(--text-muted); }

/* flat configuration Card — an ordinary settings surface stays FLAT (#401): no
   ornamental elevation; only the composer focal surface carries --shadow-focal. */
.settings-card {
  padding: 16px; margin-bottom: 13px;
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--surface-raised);
}
.settings-card-head { display: flex; align-items: flex-start; gap: 10px; margin-bottom: 13px; }
.settings-card-ico { flex: none; margin-top: 1px; color: var(--primary); display: inline-grid; place-items: center; }
.settings-card-head > div { min-width: 0; }
.settings-card-head strong { display: block; }
.settings-card-head span { display: block; margin-top: 2px; color: var(--text-muted); font-size: var(--fs-meta); }

/* contextual feedback beside the affected form, for what the Card's Save button
   cannot say (#649): a genuine failure, a sentence the page supplies about the
   accepted configuration, and the routine states on a Card with no single Save
   button of its own. #414 layering + severity reservation: dirty is a CALM
   attention state (Skill-gold), saving is File-blue, saved is
   success/Agent-green, and only the genuine-failure error state carries
   danger-red — never routine save flow. */
.settings-feedback {
  display: flex; align-items: center; gap: 9px; margin-top: 14px;
  padding: 10px 11px; border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface); color: var(--text-muted); font-size: var(--fs-body-sm);
}
.settings-feedback strong { display: block; color: var(--text); }
.settings-feedback span { display: block; color: var(--text-muted); font-size: var(--fs-meta); }
.settings-feedback .ico { flex: none; }
.settings-feedback.dirty { color: var(--skill-text); background: var(--skill-surface); border-color: transparent; }
.settings-feedback.dirty strong { color: var(--skill-text); }
.settings-feedback.saving { color: var(--file-text); background: var(--file-surface); border-color: transparent; }
.settings-feedback.saving strong { color: var(--file-text); }
.settings-feedback.saved { color: var(--agent-text); background: var(--agent-surface); border-color: transparent; }
.settings-feedback.saved strong { color: var(--agent-text); }
.settings-feedback.error { color: var(--danger); background: var(--danger-surface); border-color: var(--danger-border); }
.settings-feedback.error strong { color: var(--danger); }
/* `display: flex` above out-specifies the UA [hidden] rule — restore it so a
   Card at rest hides its strip rather than showing an empty box (same pattern
   as .topbar[hidden] / .ed-controls[hidden]). */
.settings-feedback[hidden] { display: none; }

/* the Save button AS the save indicator (#649): disabled until an edit stages
   the Card, then "Saving…", then a soft-green "Saved" that holds until the next
   edit. Saved is the STATUS axis (--success/--success-surface, the ratified soft
   wash of #421) rather than the identity green the strip borrows — a deliberate
   press that landed is status, not a kind of thing. It keeps full contrast
   while disabled: the dim is for controls you cannot use yet, and this one is
   reporting, not resting. */
.settings-card button.saved,
.settings-card button.saved:disabled {
  opacity: 1; color: var(--success); background: var(--success-surface);
  border-color: transparent;
}
.settings-spinner { width: 14px; height: 14px; flex: none; border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%; animation: settings-spin .7s linear infinite; }
@keyframes settings-spin { to { transform: rotate(360deg); } }
/* `.settings-note` — the muted sentence on a Card's actions row (the Yatsar Hub
   panel prints it beside Disconnect). Descends from `.settings-save-note`, whose
   Cmd/Ctrl-S consumer #649 deleted; renamed because the row holds no save. */
.settings-note { color: var(--text-muted); font-size: var(--fs-meta); }
.settings-action-spacer { flex: 1; }

@media (max-width: 680px) {
  body.settings-page.has-sidebar { height: auto; overflow: visible; }
  .settings-page .app-content { height: auto; }
  .settings-wrap { flex-direction: column; }
  .settings-index {
    width: auto; flex-direction: row; flex-wrap: wrap; gap: 6px;
    align-items: center; overflow-x: auto;
    border-right: 0; border-bottom: 1px solid var(--border);
  }
  .settings-index-head { display: none; }
  .settings-index-list { flex-direction: row; flex-wrap: wrap; gap: 6px; }
  .settings-index-btn { min-width: max-content; min-height: 36px; grid-template-columns: 18px auto; }
  .settings-stage { padding: 20px 14px 120px; }
  .settings-sec { width: 100%; }
  /* User Workspace Settings replacement rail (#420): below the breakpoint the
     rail becomes a wrapped / horizontally scrollable section bar rather than a
     full-height column, so it never forces horizontal overflow. */
  .ws-shell #app-sidebar #ws-rail-settings {
    flex: none; flex-direction: row; flex-wrap: wrap; gap: 6px;
    align-items: center; align-content: flex-start; overflow-x: auto;
  }
  #ws-rail-settings .ws-nav-title { flex-basis: 100%; }
  #ws-rail-settings .ws-back,
  #ws-rail-settings .sb-item { min-width: max-content; }
  .ws-settings .ws-set-sec { padding: 20px 16px 120px; }
}
.settings-provider {
  border: 1px solid var(--border); border-radius: 10px;
  background: var(--surface-raised); padding: 12px; display: flex;
  flex-direction: column; gap: 10px;
}
.settings-provider-head { display: flex; align-items: baseline; gap: 10px; }
.settings-status { font-size: 12px; color: var(--text-muted); }
.settings-status.ok { color: var(--success); }
.settings-status.env { color: var(--accent-text); }
.settings-status.err { color: var(--danger); }
.settings-keyrow { display: flex; gap: 8px; flex-wrap: wrap; margin: 0; }
.settings-keyrow input { flex: 1 1 180px; min-width: 0; }
.settings-keyrow button { flex: none; }
/* Connector enroll form (ADR-0012): the two URL fields need full width and a
   persistent label, so it is a labelled COLUMN rather than the inline keyrow. */
.connector-form { display: flex; flex-direction: column; gap: 12px; margin: 0; }
.connector-field { display: flex; flex-direction: column; gap: 4px; }
.connector-field input { width: 100%; }
.connector-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.settings-tier { display: flex; flex-direction: column; gap: 4px; }
.settings-tier-name { font-size: 12px; color: var(--text-muted); }
/* A tier's control row: the stock picker, the "Other…" free-text model id it
   reveals, and the unpriced badge. Wraps so the id field keeps a usable width
   in the narrow Settings column. */
.settings-tier-field {
  display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap;
}
.settings-tier-field select { flex: 0 1 auto; min-width: 0; }
.settings-tier-other {
  flex: 1 1 200px; min-width: 0; max-width: 320px; font-family: var(--mono);
}
/* Quiet pill, deliberately NOT danger: the saved value is valid and running —
   only its cost is unknown. Same treatment as the other neutral status pills. */
.settings-tier-unpriced {
  flex: none; padding: 3px var(--space-4); border-radius: var(--radius-pill);
  background: var(--surface); border: 1px solid var(--border);
  color: var(--text-muted); font-size: var(--fs-eyebrow); white-space: nowrap;
}
.settings-section > button { align-self: flex-start; }

/* ---------- Task retention card (#506, prototype tasks-admin-projects A) ------
   The Projects & Tasks page's one control, on the shipped .settings-card. Three
   pieces are its own: the checkbox row, the days row it GATES, and the fixed
   reassurance strip.

   .tr-dayrow.off is the gate's visible half — the row stays legible and in place
   (the reader can see the policy they would be switching on) and reads as
   unavailable through the canonical --opacity-disabled, the cue every disabled
   control in the system uses. The <input> is really `disabled` too; the class
   dims the words around it. */
.tr-card { max-width: 470px; }
.tr-optrow {
  display: flex; align-items: center; gap: var(--space-4);
  font-size: var(--fs-body-sm);
}
.tr-optrow input[type="checkbox"] { accent-color: var(--primary); flex: none; }
.tr-hint { color: var(--text-muted); }
/* Indented under the checkbox it hangs off, so the gating relationship is
   readable before anyone clicks anything. */
.tr-dayrow {
  display: flex; align-items: center; gap: var(--space-4);
  padding-left: var(--space-9); font-size: var(--fs-body-sm);
}
.tr-dayrow input { width: 76px; }
.tr-dayrow.off { opacity: var(--opacity-disabled); }
/* Calm, never-scary: a muted strip on the card's recessed surface carrying the
   success glyph — a reassurance, not a warning. */
.tr-assure {
  display: flex; align-items: flex-start; gap: var(--space-4);
  margin-top: var(--space-6); padding: var(--space-5) var(--space-6);
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface); color: var(--text-muted);
  font-size: var(--fs-meta);
}
.tr-assure-ico { flex: none; color: var(--success); display: inline-grid; }

/* ---------- dark mode 3-state (issue #42) ---------- */
/* (The Master Theme editor + its live-preview/swatch-legend CSS were removed
   with the module in #402; Organization Theme Preset authoring is the .tp-*
   surface at the end of this file — #426.) */
/* align-self stops the inline-flex pill from stretching to the full width of
   its .settings-section flex-column parent (the same fix the section's buttons
   get) — otherwise the border framed an empty full-width band beside it. */
.dm-group { display: inline-flex; align-self: flex-start; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
/* the options are icon glyphs (moon/sun/monitor) — flex centers the svg */
.dm-opt { border: none; border-radius: 0; background: var(--surface-raised); padding: 6px 14px; display: inline-flex; align-items: center; justify-content: center; }
.dm-opt + .dm-opt { border-left: 1px solid var(--border); }
.dm-opt:hover { background: var(--surface); }
.dm-opt.active { background: var(--primary); color: var(--on-primary); }
.dm-opt:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; }

/* ---------- cost dashboard (issues #6/#27) ---------- */
/* The dashboard route uses the shared .admin-wrap content shell (#394). The
   .dash-panel/.dash-table primitives back the shared ledger (TablePager).
   (.dash-card/.dash-cards were deleted 2026-08-08 with dash-render.js's
   dashCard(); the Dashboard's own stat tiles are .metric-card, a different
   thing that was never this.) */
.dash-panel {
  background: var(--surface-raised); border: 1px solid var(--border);
  border-radius: 10px; margin-bottom: 18px; overflow: hidden;
}
.dash-panel-head {
  display: flex; align-items: baseline; gap: 10px;
  padding: 10px 16px; border-bottom: 1px solid var(--border);
}
.dash-panel-head .title { font-weight: 600; }
.dash-panel-head .hint { color: var(--text-muted); font-size: 12px; }
.dash-panel .empty-note { padding: 28px 0; }
.dash-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.dash-table th, .dash-table td { padding: 7px 16px; text-align: left; }
.dash-table th {
  color: var(--text-muted); font-size: 11px; font-weight: 600;
  text-transform: uppercase; letter-spacing: .04em;
  border-bottom: 1px solid var(--border);
}
.dash-table td { border-bottom: 1px solid var(--border); }
.dash-table tbody tr:last-child td { border-bottom: 0; }
.dash-table tbody tr:hover td { background: var(--surface-hover); }
.dash-table .num { text-align: right; font-variant-numeric: tabular-nums; }
.dash-table a { color: var(--accent); }
/* ---------- form designer (issue #23) ---------- */
/* Owned confirmation dialog (#48) — sits above every overlay (--z-modal = 100). */
.dialog-overlay {
  position: fixed; inset: 0; z-index: 100;
  background: color-mix(in srgb, var(--text) 35%, transparent);
  display: flex; align-items: center; justify-content: center; padding: 28px;
}
.dialog-overlay[hidden] { display: none; }
.dialog {
  background: var(--surface-raised); color: var(--text);
  border: 1px solid var(--border-strong); border-radius: 14px;
  width: min(440px, 100%); padding: 22px 22px 18px;
  box-shadow: 0 12px 40px color-mix(in srgb, var(--text) 25%, transparent);
}
.dialog-title { font-size: 16px; font-weight: 700; margin: 0 0 8px; }
.dialog-title[hidden] { display: none; }
.dialog-message { font-size: 14px; line-height: 1.5; color: var(--text); white-space: pre-wrap; }
.dialog-message[hidden] { display: none; }
.dialog-confirm { display: block; margin-top: 14px; }
.dialog-confirm[hidden] { display: none; }
.dialog-hint { display: block; font-size: 12px; color: var(--text-muted); margin-bottom: 5px; }
.dialog-confirm input {
  width: 100%; box-sizing: border-box; padding: 7px 10px;
  border: 1px solid var(--border-strong); border-radius: 8px;
  background: var(--surface); color: var(--text); font-family: var(--mono); font-size: 13px;
}
.dialog-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 18px; }
.dialog-actions button.danger { background: var(--danger); color: var(--on-primary); border-color: var(--danger); }
.dialog-actions button.danger:hover:not(:disabled) { filter: brightness(.92); }
.dialog-actions button[hidden] { display: none; }
body.dialog-open { overflow: hidden; }

/* Credential Manager + the create/edit popover (#207). The popover reuses .dialog
   (themed) but is wider to host a form; the manager is a metadata-only table. */
.cred-popover { width: min(520px, 100%); max-height: 86vh; overflow-y: auto; }
.cred-pop-form { display: flex; flex-direction: column; gap: var(--space-6); margin-top: var(--space-6); }
.cred-pop-form .cn-field { display: flex; flex-direction: column; gap: var(--space-3); }
.cred-pop-fields, .cred-pop-exts { display: flex; flex-direction: column; gap: var(--space-5); }
.cred-pop-ext-row { display: flex; gap: var(--space-4); align-items: center; }
.cred-pop-ext-row input { flex: 1; min-width: 0; }
.cred-pop-msg { margin-top: var(--space-5); font-size: var(--fs-body-sm); }

/* Workspace non-OAuth Connect form (#321, ADR-0022) — the pasted-credential
   dialog beside the OAuth redirect. Reuses .dialog + .cred-popover + the .cn-*
   field primitives; overrides .cn-body's section top border (standalone in a
   dialog, not stacked under a section head). */
.cred-popover .cn-connect-help { margin: var(--space-4) 0 var(--space-5); }
/* The instructions are authored Markdown (#507 UAT): give its blocks the
   dialog's own rhythm rather than the page defaults. */
.cn-connect-help p { margin: 0 0 var(--space-3); }
.cn-connect-help p:last-child { margin-bottom: 0; }
.cn-connect-help ol, .cn-connect-help ul {
  margin: 0 0 var(--space-3); padding-left: var(--space-8);
}
.cred-popover > .cn-body { border-top: none; padding: var(--space-5) 0 0; }
.cred-popover .cn-account-label { width: 100%; }

/* The Service-key Context Canvas's selectable rows went at #728 with the key
   ledger itself — the Platform's credential is the deployment unit's env token,
   so there are no per-consumer keys to select into a canvas. (The `.cred-table`
   family had gone the same way at #547: the Credentials ledger is the shared
   YatsarLedger in the Connectors area, so the hand-rolled table had no consumer
   left either.) */
/* The Context Canvas "used by" list — connectors/tenants that bind a
   Credential, or the consumer/scope of a key. */
.ctx-uses { display: grid; gap: var(--space-4); margin: 0; }
.ctx-use {
  display: flex; align-items: center; gap: var(--space-5);
  padding: var(--space-5) var(--space-6);
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--surface);
}
.ctx-use .ico { flex: none; color: var(--text-muted); }
.ctx-use-name { min-width: 0; }
.ctx-use-name .l-name { font-weight: var(--fw-emph); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ctx-use-name .l-sub { color: var(--text-muted); font-size: var(--fs-eyebrow); }
.ctx-use-name .l-sub.mono { font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, monospace); }

/* The takeover POSITIONING shell (.builder-takeover, #55) and its title-only
 * .takeover-head were deleted 2026-08-08: #609 retired the last of the five
 * Agent Builder takeovers, leaving both with no consumer at all. What survives
 * here is what still has one — .takeover-body (workspace.html) and the
 * .takeover-bar family below, which YatsarDom.takeoverHeader builds for the
 * organization-side Preset authoring page. Git holds the deleted rules if a
 * pane-scoped takeover is ever wanted again. */
.takeover-body { padding: 20px 24px; }
.takeover-body .sub { color: var(--text-muted); font-size: 13px; margin: 0 0 18px; }
/* padded content area inside a .dash-panel (panels are edge-to-edge so tables
   reach the border; non-table content needs its own padding) */
.dash-panel .panel-pad { padding: 14px 16px; }
.settings-field { display: flex; flex-direction: column; gap: 4px; max-width: 220px; }
.settings-field > span { font-size: 12px; color: var(--text-muted); }
.settings-form-narrow { display: flex; flex-direction: column; gap: var(--space-5);
  max-width: 360px; }

/* The Builder section header + its first-letter collapse (#55 / #137 / PRD #136
   US7) went with the sections themselves on 2026-08-08 — see the note by
   .sb-nav above. The active-item rule below is the MAIN rail's and stays. */
#app-sidebar .sb-item.active {
  background: var(--accent-surface); color: var(--accent-text);
}

/* Studio-takeover top bar (#65, M2; #425): the Theme editor's bar (it was shared
   with the Form designer's, itself once the byte-for-byte .fd-head twin, until
   the Forms rail item folded into the Agent Editor). The locked #389
   full-area head — a kicker eyebrow stacked over the title on the left, then the
   buffer-status pill + Revert + Save on the right — is OPT-IN via the .full-area
   modifier (dom-utils adds it when a consumer uses the kicker/Revert/setStatus
   contract). A pre-#425 consumer (the Theme editor, until #426 migrates it)
   keeps the original inline title + plain status text. */
.takeover-bar {
  display: flex; align-items: center; gap: var(--space-6); padding: var(--space-5) var(--space-7);
  background: var(--surface-raised); border-bottom: 1px solid var(--border);
}
.takeover-bar .spacer { flex: 1; }
/* legacy head: .takeover-titles is only a grouping node — title + sub lay out
   inline in the bar exactly as they did before #425 */
.takeover-titles { display: contents; }
.takeover-bar.full-area .takeover-titles {
  display: flex; flex-direction: column; gap: 2px; min-width: 0;
}
/* Kicker delta on the shared .eyebrow (the takeover head composes .eyebrow
   rather than forking its recipe): the locked #389 prototype's wider tracking. */
.takeover-kicker { letter-spacing: .12em; }
.takeover-title { font-weight: var(--fw-bold); }
.takeover-sub { font-family: var(--mono); font-size: var(--fs-eyebrow); color: var(--secondary); }
/* legacy head: the pre-#425 plain status text (theme-editor.js) */
.takeover-status { font-size: var(--fs-meta); color: var(--accent); }
/* Full-area buffer-status pill — the same calm Baseline / Pending / Saved
   treatment the Context Bench uses (.bench-status), so a full-area takeover
   reads its buffer state identically to the bench. Pending/Saved stay calm,
   never danger (#414). */
.takeover-bar.full-area .takeover-status {
  display: inline-flex; align-items: center; gap: var(--space-3); min-height: 25px;
  padding: 3px var(--space-4); border-radius: var(--radius-pill);
  background: var(--surface); border: 1px solid var(--border);
  color: var(--text-muted); font-size: var(--fs-eyebrow); white-space: nowrap;
}
.takeover-bar.full-area .takeover-status.dirty { color: var(--accent-text); background: var(--accent-surface); border-color: transparent; }
.takeover-bar.full-area .takeover-status.saved { color: var(--success); background: var(--success-surface); border-color: transparent; }
/* Form views in the Work canvas: the field editor and the live preview, each a
   flex child of the editor area beside the JSON textarea, with the head toggle
   showing exactly one. The locked #389 Forms | Fields | Live-preview three-card
   grid went with the Forms rail item — the File Explorer is the form list now,
   so the two remaining views get the whole canvas instead of a third of it, and
   the takeover's own breakpoint collapse goes with the grid (the bench track
   already narrows the canvas for them). */
.fd-view { flex: 1; min-height: 0; overflow: auto; padding: var(--space-6) var(--space-7); }
.fd-view[hidden] { display: none; }
.fd-editor { display: flex; flex-direction: column; gap: 10px; max-width: 760px; }
.fd-preview { background: var(--surface-raised); }
/* Readiness advisory (#414 Region layer): what still stands between this
   definition and a form the runtime can serve — beside the fields, never a modal
   Dialog. It is CALM (accent, never danger): it was a save-blocking validation
   failure while the Form designer owned its own Save button, but the Work head's
   Save now accepts the bytes either way, exactly as it does for every file. */
.fd-region {
  margin-bottom: var(--space-4); padding: var(--space-4) var(--space-5);
  border-radius: var(--radius-sm); font-size: var(--fs-body-sm);
  background: var(--accent-surface); border: 1px solid transparent;
  color: var(--accent-text); white-space: pre-line;
}
.fd-region[hidden] { display: none; }
.fd-ctl { display: flex; flex-direction: column; gap: 3px; }
.fd-ctl-label { font-size: 11px; font-weight: 600; color: var(--text-muted); }
.fd-fields { display: flex; flex-direction: column; gap: 10px; }
.fd-field {
  background: var(--surface-raised); border: 1px solid var(--border);
  border-radius: 10px; padding: 10px 12px;
  display: flex; flex-direction: column; gap: 8px;
}
.fd-field-head { display: flex; align-items: center; justify-content: space-between; }
.fd-field-type {
  font-family: var(--mono); font-size: 11px; color: var(--secondary);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 999px; padding: 2px 9px;
}
.fd-field-tools { display: flex; gap: 4px; }
.fd-field-tools button { font-size: 11px; padding: 2px 7px; }
.fd-field-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.fd-required { font-size: 13px; display: flex; align-items: center; gap: 6px; cursor: pointer; }
.fd-required input { accent-color: var(--accent); margin: 0; }
.fd-addrow { display: flex; gap: 8px; align-items: center; padding-top: 2px; }
.fd-preview-note { color: var(--text-muted); font-size: 12px; font-style: italic; margin-bottom: 8px; }
/* ---------- Skill Library page + studio Skill grants (wt/skills, #15/#16) ---------- */
/* Edit view: the Skill Builder now composes over the shared Context Bench
   (.bench-*, #423) — the Navigator (the shared FileTree, mounted at #skill-files,
   which the .tree rules style), the Work canvas (the file editor), and the
   persistent Co-Pilot. The old full-screen .skill-build / .skill-build-grid
   surface + its bespoke resize track were removed with the migration; only the
   Co-Pilot chat mount keeps a skill-specific rule. */
.skill-builder-chat { flex: 1; min-height: 0; display: flex; }
.skill-builder-chat .yc { flex: 1; min-width: 0; }
#skill-list[hidden] { display: none; }

/* Grant + dock a Skill to Agents (#64). This was a page of its own until
   2026-08-08 (CJ); it is the Skill Builder's Config panel now, so the page
   chrome went (.attach-head and its .attach-skill slug pill) and what is left is
   the two blocks' internals, hosted by the .cfg-attach fieldsets — which carry
   only these internals, composing .field-group for the box itself (DR42). */
.attach-globals { display: flex; flex-direction: column; }
.attach-global-row { display: flex; gap: 10px; align-items: flex-start; padding: 12px 0; cursor: pointer; }
.attach-global-row + .attach-global-row { border-top: 1px solid var(--border); }
.attach-global-row > input { margin: 2px 0 0; accent-color: var(--accent); flex: none; width: 15px; height: 15px; }
.attach-global-name { font-weight: 600; font-size: 13px; }
.attach-global-row .desc { display: block; color: var(--text-muted); font-size: 12px; margin-top: 2px; }
.attach-global-row > input:disabled ~ .attach-global-text { opacity: .5; }
.attach-table .attach-col { width: 96px; text-align: center; padding: 0; }
.attach-table .attach-name { display: inline-block; font-family: var(--mono); font-size: 12.5px; cursor: pointer; }
/* the checkbox sits in a full-cell <label> so the whole cell is a hit target */
.attach-check { display: flex; align-items: center; justify-content: center; padding: 10px 0; cursor: pointer; }
.attach-check:has(input:disabled) { cursor: default; }
.attach-table input[type=checkbox] { accent-color: var(--accent); width: 15px; height: 15px; cursor: inherit; vertical-align: middle; margin: 0; }

/* Skill-grant chrome. The Builder's Granted Skills takeover (#55) is retired —
   it was the Skill editor's Attach table transposed, over the same /grants
   writes — so its .grants-toolbar / .grants-panel wrapper went with it. What
   remains belongs to the Users & groups console's Skill canvas: the "Global —
   grant to every Agent" switch and the mono name in a grant roster row. */
.grants-all { display: flex; gap: 9px; align-items: center; margin: 0; font-size: 13px; cursor: pointer; }
.grants-all input { accent-color: var(--accent); width: 15px; height: 15px; margin: 0; flex: none; }
.grant-name { font-family: var(--mono); font-size: 12.5px; }

/* ---------- Users & groups console (#71 UI) ---------- */
/* The Owner-only people console. Reuses .dash-panel + .dash-table + the
   .attach-* cell chrome (shared with Access/Skill grants) so it reads like the
   rest of the admin surface; only the few people-specific bits live here. */
.user-create { display: flex; flex-wrap: wrap; gap: var(--space-4); margin-bottom: var(--space-7); }
.user-create input { flex: 1 1 150px; min-width: 0; }
.user-create button { flex: none; }
.people-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-5); margin-bottom: var(--space-7); }
.role-select { min-width: 110px; }
/* A user/group source pill — local vs the external identity providers. Neutral
   across every source: the label ("Entra"/"Local") carries the distinction, so
   the accent stays reserved for activity/selection (DESIGN.md character rule 1). */
.src-badge {
  display: inline-block; font-size: var(--fs-eyebrow); line-height: 1; padding: 3px 7px;
  border: 1px solid var(--border); border-radius: var(--radius-pill); color: var(--text-muted);
  text-transform: capitalize;
}
.settings-status.off { color: var(--text-muted); }

/* ---------------------------------------------------------------------------
   The Users page's DIRECTORY IMPORT surface (#543, spec #524 §H) — one Import
   card per Users & Groups Connector, and the run report they share.

   The report itself is the shared RUN-REPORT CHECKLIST below (.run-checks /
   .run-chk) — the same card whether the run was a Preview or a real import,
   because they are one report shape.
   --------------------------------------------------------------------------- */
.import-card {
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--surface-raised); padding: var(--space-8);
  display: grid; gap: var(--space-6); margin-bottom: var(--space-8);
}
.import-card-head { display: flex; align-items: flex-start; gap: var(--space-5); }
.import-card-title { flex: 1; min-width: 0; }
.import-card-title h2 { margin: 0; font-size: var(--fs-title); }
.import-card-meta { margin: 0; color: var(--text-muted); font-size: var(--fs-meta); }
.import-empty { text-align: left; padding: var(--space-9) 0; }
.import-empty p { margin: 0 0 var(--space-5); }

/* group selection — a plain checkbox list, deliberately flat (v1 keeps every
   group picker flat; nesting is a resolution-layer fact, not a tree here). */
.import-groups > summary { cursor: pointer; font-weight: var(--fw-emph); }
.import-groups-body { padding-top: var(--space-6); display: grid; gap: var(--space-6); }
.import-group-list {
  display: grid; gap: var(--space-2); max-height: 320px; overflow-y: auto;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: var(--space-4);
}
.import-group {
  display: flex; align-items: center; gap: var(--space-5); justify-content: flex-start;
  padding: var(--space-3) var(--space-4); border-radius: var(--radius-sm);
}
.import-group:hover { background: var(--surface-hover); }
.import-group-name { display: flex; align-items: center; gap: var(--space-4);
  flex-wrap: wrap; min-width: 0; }
.import-proposals { display: grid; gap: var(--space-3); }

/* the run report */
.import-report {
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface);
}
.import-report .card-head {
  display: flex; align-items: baseline; gap: var(--space-5);
  padding: var(--space-5) var(--space-6); border-bottom: 1px solid var(--border);
}
.import-report .card-head .title { font-weight: var(--fw-emph); font-size: var(--fs-body-sm); }
.import-report .card-head .hint { color: var(--text-muted); font-size: var(--fs-meta); }
.import-report .ctx-section-title,
.import-report .sub,
.import-report .import-proposals { margin: var(--space-5) var(--space-6); }

/* the checklist lives in the shared RUN-REPORT block below (`.run-checks`/`.run-chk`).
   #543 namespaced it under the import card to stop a second run-report inheriting
   `.checks`/`.chk` by accident; #544 is that second report and it wants the same
   language on purpose, so the primitive was promoted rather than copied — one status
   glyph, one label, one detail line, wherever a run reports itself. */

/* ---------------------------------------------------------------------------
   THE RUN-REPORT CHECKLIST (#543 · #544) — the settled checklist from the #517
   prototype (its focal element), re-expressed through the design system's tokens
   and shared by every surface that reports a run: the Users page's directory
   import, and the Connector Builder's sandbox card + tag-contract publish
   checklist.

   The prototype's --warning/--warning-soft pair is this system's
   --warning/--warning-surface, its --file-soft is --file-surface, and its
   hardcoded 8px/12px paddings sit on the spacing ramp (--space-4/--space-6). Its
   19px status disc does NOT (#543 review P6) — see the note on `.run-chk .st`.

   One status glyph, one label, one detail line per fact. Five states: the three a
   run MEASURES (pass · warn · fail), one it DERIVES (pend — the mapping moved
   under a check, so what it measured describes a binding that no longer exists),
   and one the AUTHOR asserts (ack — "works in production"). Ack is deliberately
   not green: it is the --file family, which is the same distance from success the
   prototype put it at, because an acknowledged gap is a gap somebody decided to
   live with rather than one anybody closed.
   --------------------------------------------------------------------------- */
.run-checks { display: grid; }
.run-chk {
  display: flex; gap: var(--space-5); align-items: flex-start;
  padding: var(--space-4) var(--space-6); border-top: 1px solid var(--border);
  font-size: var(--fs-body-sm);
}
.run-checks .run-chk:first-child { border-top: 0; }
/* The status disc is the ONE measurement in this block that is not on a ramp: 19px is
   the #517 prototype's own value and the spacing ramp has no step at it (--space-8 is
   18px, --space-9 is 24px). It ships raw rather than being nudged onto a neighbour and
   called tokenized (#543 review P6). Everything else here — the padding, the gaps, the
   radius, the type sizes and every color — is on the tokens. */
.run-chk .st {
  flex: none; display: grid; place-items: center; width: 19px; height: 19px;
  border-radius: var(--radius-pill); margin-top: 1px; font-size: var(--fs-meta);
}
.run-chk.pass .st { color: var(--success); background: var(--success-surface); }
.run-chk.fail .st { color: var(--danger);  background: var(--danger-surface); }
.run-chk.warn .st { color: var(--warning); background: var(--warning-surface); }
.run-chk.ack  .st { color: var(--file-text); background: var(--file-surface); }
.run-chk.pend .st {
  color: var(--text-muted); background: var(--surface);
  border: 1px dashed var(--border-strong);
}
.run-chk .what { display: grid; gap: 1px; min-width: 0; flex: 1; }
.run-chk .what .lbl { font-weight: var(--fw-emph); }
.run-chk .what .det { color: var(--text-muted); font-size: var(--fs-meta); }
.run-chk.fail .what .lbl { color: var(--danger); }
.run-chk.warn .what .lbl { color: var(--warning); }
.run-chk.ack  .what .lbl { color: var(--file-text); }
/* The two exits from a failure, offered on the failing row itself: apply the
   Co-Pilot's fix, or say it works in production. They sit at the row's trailing
   edge and wrap, because a row whose detail is a provider sentence must not squeeze
   the affordances off it. */
.run-chk .acts {
  display: flex; gap: var(--space-4); flex: none; flex-wrap: wrap;
  justify-content: flex-end;
}
/* The acknowledgement, named on the label itself so the state travels with the
   words rather than only with the glyph — it has to read as acknowledged in the
   publish checklist too, where there are no buttons beside it. */
.run-ack-pill {
  font-family: var(--mono); font-size: var(--fs-eyebrow); line-height: 1.6;
  text-transform: uppercase; letter-spacing: .07em;
  color: var(--file-text); background: var(--file-surface);
  border-radius: var(--radius-pill); padding: 0 var(--space-4);
  white-space: nowrap;
}

/* the Canvas profile editor — the two directory-authoritative fields, locked on
   an imported row so provenance and edit rights are legible side by side. */
.ctx-profile { display: grid; gap: var(--space-5); margin-top: var(--space-6); }
.ctx-field { display: grid; gap: var(--space-2); }
.ctx-field-label { font-size: var(--fs-eyebrow); color: var(--text-muted); }
.ctx-field input:disabled { color: var(--text-muted); cursor: not-allowed; }

/* ===== the standalone Single Sign On provider page (#548, prototype #522) =====
   Sign-on is not a Connector, not a Credential Profile and not a ledger: it is a
   short repo-shipped FORM per provider, read-only until Edit, with the secret in
   the Service's Vault. The prototype's anatomy, re-expressed through the tokens:
   a card whose head carries the provider name, its protocol hint and a status,
   over a body of two two-up field rows and the surface's own actions. */
.sso-providers { display: grid; gap: var(--space-7); align-content: start; }
.sso-card-head {
  display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap;
  margin-bottom: var(--space-6);
}
.sso-card-head .title { font-weight: var(--fw-bold); font-size: var(--fs-body-sm); }
.sso-card-head .hint { color: var(--text-muted); font-size: var(--fs-meta); }
.sso-card-head .spacer { flex: 1; }
.sso-card-body { display: grid; gap: var(--space-5); }
.sso-row { display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap; }
.sso-row.fields { align-items: flex-start; }
/* A field is its `<label>` wrapping its control (#548 review ST-2) — the house
   convention `overlay_shell.labeled` already uses, so the accessible name has no
   `for`/id pairing to drift out of sync. `.sso-ctl` is the label; `.sso-label` is
   its text, carrying the meta type so the control inside keeps the body face; the
   `.sso-hint` stays a SIBLING of the label, since guidance about a field is not
   part of that field's name. */
.sso-field { display: grid; gap: var(--space-2); flex: 1; min-width: 0; }
.sso-ctl { display: grid; gap: var(--space-2); }
.sso-label { font-size: var(--fs-meta); font-weight: var(--fw-emph); }
.sso-field .opt { font-weight: var(--fw-normal); color: var(--text-muted); }
.sso-field input { min-width: 0; }
.sso-field .sso-row input { flex: 1; }

/* The provider's status, as the prototype's dot: **configured** (the Service holds
   the app registration and this directory is on the login allowlist) or not. It
   never claims sign-in *works* — nothing on this page has measured that, and
   Test sign-in is the affordance that does. */
.sso-status {
  display: inline-flex; align-items: center; gap: var(--space-3);
  font-size: var(--fs-meta); color: var(--text-muted);
}
.sso-status::before {
  content: ""; width: var(--space-4); height: var(--space-4);
  border-radius: var(--radius-pill); background: var(--text-muted);
}
.sso-status.ok::before { background: var(--success); }

/* "More providers arrive as reviewed releases" — a stated boundary, drawn as the
   prototype's dashed note so it never reads as a card somebody forgot to fill. */
.sso-more {
  border: 1px dashed var(--border-strong); border-radius: var(--radius-md);
  background: var(--surface); padding: var(--space-5) var(--space-6);
  font-size: var(--fs-meta); color: var(--text-muted);
}

/* The break-glass card — "You can't be locked out". Deliberately NOT a warning:
   it states a guarantee the Platform enforces, so it is calm surface, never
   danger (#414's severity reservation). */
.sso-breakglass {
  display: flex; gap: var(--space-4); align-items: flex-start;
  padding: var(--space-5) var(--space-6); border-radius: var(--radius-md);
  background: var(--surface); border: 1px solid var(--border);
  color: var(--text-muted); font-size: var(--fs-meta);
}
.sso-breakglass .ico { flex: none; color: var(--text-muted); margin-top: 1px; }
.sso-breakglass strong { color: var(--text); }

/* …and its two NOT-calm states (#548 review F2). The calm treatment above is
   earned by naming a live administrator; the card must not wear it while it is
   saying something else. `.unknown` is the card that could not read the roster —
   a caution, because the guarantee still holds and only this view of it failed.
   `.alarm` is the roster genuinely EMPTY, which is the invariant already
   violated: nobody can sign in when the provider breaks. That is the one case on
   this page where danger is the honest severity, and #414's reservation is what
   makes it read as danger — it is spent nowhere else here. */
.sso-breakglass.unknown {
  border-color: var(--warning); background: var(--warning-surface);
}
.sso-breakglass.unknown .ico { color: var(--warning); }
.sso-breakglass.alarm {
  border-color: var(--danger); background: var(--danger-surface);
  color: var(--text);
}
.sso-breakglass.alarm .ico, .sso-breakglass.alarm strong { color: var(--danger); }

/* The `.tenant-rows` / `.tenant-head` / `.tenant-row` grid died with the Settings
   tenant editor (#548). The provider form is a card of `.sso-field` rows now — a
   per-provider FORM, not a table of tenants — so a four-column grid with column
   headers has nothing left to lay out, and keeping it would offer the next author
   a second, contradicting answer to what this page looks like. */

/* Entra setup walkthrough, inside the provider form since #548: the Redirect URI
   to register (copyable) + the field-to-Azure mapping + the Value-vs-Secret-ID
   caution, so the two things operators reliably get wrong are answered beside the
   fields they are about. */
.sso-help {
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--surface); padding: var(--space-7) var(--space-8);
  display: flex; flex-direction: column; gap: var(--space-5);
}
.sso-help h3 { font-size: var(--fs-body); margin: 0; }
.sso-help ol {
  margin: 0; padding-left: var(--space-8);
  display: flex; flex-direction: column; gap: var(--space-3);
  color: var(--text-muted); font-size: var(--fs-body-sm);
}
.sso-help ol b { color: var(--text); font-weight: var(--fw-emph); }
.sso-hint { margin: 0; color: var(--text-muted); font-size: var(--fs-meta); }
.sso-warn { margin: 0; color: var(--danger); font-size: var(--fs-body-sm); }
.sso-redirect { display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap; }
.sso-redirect-label {
  font-size: var(--fs-eyebrow); text-transform: uppercase; letter-spacing: .04em;
  color: var(--text-muted);
}
.sso-redirect code {
  font-family: var(--mono); font-size: var(--fs-body-sm);
  background: var(--surface-raised); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: var(--space-2) var(--space-5);
  user-select: all; word-break: break-all;
}
.sso-redirect button { flex: none; }

/* Login page single sign-on (#71 UI): an "or" divider + a stacked button per
   configured tenant, below the local password form. */
.sso { margin-top: var(--space-8); }
.sso-divider { display: flex; align-items: center; gap: var(--space-5); color: var(--text-muted); font-size: var(--fs-meta); margin: var(--space-3) 0 var(--space-7); }
.sso-divider::before, .sso-divider::after { content: ""; flex: 1; height: 1px; background: var(--border); }
.sso-btn { width: 100%; margin-bottom: var(--space-4); }

/* ---------- .field-group — the titled fieldset box (DR42, 2026-08-08) --------
   THE PRIMITIVE. One box — a hairline-bordered <fieldset> + an .eyebrow <legend>
   — grouping controls that WRITE ON CHANGE, with an .fs-note caption saying so.
   It had drifted into three names (.access-levels · .cfg-theme · .cfg-attach),
   each maintained in five hand-written selector lists scattered ~950 lines
   apart, so a fourth fieldset meant five more edits and a fifth name. The three
   now COMPOSE this class in markup and carry only their own internals below
   (radios · a preset grid · switches + a grant table).

   UNPREFIXED, and the name is not `.cfg-fieldset` as DR42 first proposed:
   DESIGN.md's rule is that a reusable primitive is unprefixed and a page prefix
   is only for surface-specific chrome, and two of this box's four surfaces are
   not Config panels at all (the #409 Users & groups canvas, the #347 Workspace
   file-sort preference). `cfg-` would have been a lie on half of them.
   It sits with the spec's `.field` / `.choice-set` form vocabulary on purpose —
   when DR4 finally promotes `.access-levels` onto `.choice-set`, that fieldset
   composes THIS box and brings only its radios. */
.field-group { border: 1px solid var(--border); border-radius: 8px; padding: 14px 16px; margin: 0 0 18px; }
/* .eyebrow (in markup) supplies the uppercase/letter-spacing/muted recipe + 11px;
   keep only the legend-specific deltas here (backlog L1: new labels reuse .eyebrow). */
.field-group legend { padding: 0 6px; font-weight: 600; }
/* The caption (#347) — a muted explanatory line stating what the group's
   liveness means. It lived ~950 lines down under the Workspace Preferences
   heading until DR42, scoped one host at a time, which is how #609 could give
   Theme the same caption and have it render at body size. */
.field-group .fs-note { color: var(--text-muted); font-size: var(--fs-meta); margin: 0 0 12px; line-height: 1.5; }
.field-group .fs-note strong { color: var(--text); }
/* Seated in a Builder's Config panel: the panel's own flex gap owns the spacing
   below (kill the box's bottom margin), and the measure is capped so the
   descriptions stay readable in a wide Work canvas. Wider than
   .settings-field-wide's 340px — these rows are sentences, not inputs. This one
   stays keyed on .cfg-panel: it is the HOST's business, not the box's, which is
   why the two non-Config surfaces correctly never took it. */
.cfg-panel .field-group { margin: 0; max-width: 520px; }

/* ---------- Access level (#99) — .field-group internals ---------- */
/* The per-Agent Access-level selector — the design system's canonical
   pick-one-of-N control (Decision 72), now shared by three surfaces: the
   Builder's Config panel, the Users & groups Agent-access canvas, and the
   Workspace file-sort preference. The Builder's takeover host (.access-panel)
   and its Restricted grant-roster tables (#access-grants) went out with the
   takeover (2026-08-07) — the rosters are the Users & groups console's, which is
   where users and groups are managed. The box, the legend and the caption are
   .field-group's above; what is left here is the radios. */
.access-levels label { display: flex; gap: 9px; align-items: flex-start; padding: 7px 0; font-size: 13px; cursor: pointer; }
.access-levels input { accent-color: var(--accent); width: 15px; height: 15px; margin: 2px 0 0; flex: none; }
.access-levels .desc { display: block; color: var(--text-muted); font-size: 11px; margin-top: 2px; }
/* Trailing lines under the radios: the Restricted grant summary and the write
   failure. .fs-note is authored as a LEADING caption (bottom margin), so the
   grant note flips its margin rather than forking the class. The `p.` qualifier
   is belt-and-braces since DR42 moved the caption rule ABOVE this one (it beats
   `.field-group .fs-note` on specificity now, and would win on source order even
   without it) — kept so the flip does not silently depend on rule order. */
.access-levels p.access-grant-note { margin: 12px 0 0; }
.access-levels .access-notice { margin-top: 10px; }
/* Theme fieldset internals. The preset grid is the SHARED .ws-preset-* picker
   (YatsarDom.presetGrid) — untouched here; at this 520px measure its
   auto-fill/minmax(150px) track yields three cards a row, against the Workspace
   Appearance card's wider run. Below it, the logo row and the chrome checkbox
   keep the .te-* classes the Workspace shares, so only the spacing between the
   three blocks is authored here. */
.cfg-theme .ws-preset-grid { margin-top: 0; }
/* The archived-selection disclosure sits UNDER the cards it explains, so it
   trails them — .fs-note is authored as a leading caption (bottom margin), the
   same flip .access-grant-note makes a few rules up, and element-qualified for
   the same belt-and-braces reason. */
.cfg-theme p.cfg-theme-fallback { margin: 12px 0 0; }
.cfg-theme .cfg-theme-logo { margin-top: var(--space-6); display: flex; flex-direction: column; gap: var(--space-3); }
.cfg-theme .cfg-theme-label { font-size: 11px; color: var(--text-muted); }
.cfg-theme .te-check { margin-top: var(--space-6); }
.cfg-theme .notice { margin-top: var(--space-5); }
/* Attach fieldset internals (2026-08-08). The Global switches and the grant
   table come over from the retired Attach page unchanged; what the fieldset
   host needs is only that they sit flush to its own 16px padding, rather than
   carrying the .panel-pad / .dash-table insets they had inside a .dash-panel —
   otherwise the rows step in from the caption above them. */
.cfg-attach .attach-globals > .attach-global-row:first-child { padding-top: 0; }
.cfg-attach .attach-table th, .cfg-attach .attach-table td { padding-left: 0; padding-right: 0; }
.cfg-attach .attach-table th:first-child, .cfg-attach .attach-table td:first-child { padding-right: var(--space-5); }
/* Trailing lines under the table: the grant tally and the load failure — the
   same flip .access-grant-note makes, for the same reason (.fs-note is authored
   as a LEADING caption). */
.cfg-attach p.cfg-attach-count { margin: var(--space-6) 0 0; }
.cfg-attach .notice { margin-top: var(--space-5); }
/* The empty note keeps .compact's smaller type but takes the fieldset's own top
   inset — it sits under the caption with no table above it to space it. */
.cfg-attach .empty-note { padding: var(--space-8) 0 0; }


/* ---------- Publish (wt/publish, #11/#12) ---------- */
/* Publish popover (markup in builder.html, logic in publish-panel.js); admin
   agent-card status badges. Tokens only. The button moved into the Context
   Bench command bar, and the popover hangs from the viewport, so the offset
   clears BOTH bands: the topbar (44) + the page gutter (18) + the command
   bar's 47px, plus a 5px gap. ONE declared offset — the scroll ceiling derives
   from it, so the two can't drift apart. */
.publish-panel {
  --pub-top: 114px;
  position: absolute; top: var(--pub-top); right: 12px; z-index: 30;
  background: var(--surface-raised); border: 1px solid var(--border-strong);
  border-radius: 10px; padding: 12px 14px; width: 320px;
  box-shadow: var(--shadow-popover);
  font-size: 13px;
  /* Long Version histories / expanded findings scroll WITHIN the panel
     instead of running off the bottom of the page. */
  max-height: calc(100vh - var(--pub-top) - 12px); overflow-y: auto;
}
.publish-panel h3 { margin: 0 0 8px; font-size: 13px; }
.publish-panel h4 {
  margin: 12px 0 4px; font-size: 11px; text-transform: uppercase;
  letter-spacing: .04em; color: var(--text-muted);
}
.publish-panel .pub-status { color: var(--text-muted); margin-bottom: 10px; }
.publish-panel #pub-now { width: 100%; }
.publish-panel .pub-note { color: var(--text-muted); font-size: 11px; margin-top: 8px; }
.publish-panel .pub-row {
  display: flex; align-items: center; gap: 8px;
  padding: 5px 0; border-top: 1px solid var(--border);
}
.publish-panel .pub-row:first-of-type { border-top: 0; }
.publish-panel .pub-v { font-weight: 600; min-width: 30px; }
.publish-panel .pub-when { color: var(--text-muted); flex: 1; }
.publish-panel .pub-by { color: var(--text-muted); font-size: 11px; }
.publish-panel .pub-current { color: var(--success); font-weight: 600; font-size: 11px; }
.publish-panel .pub-row button { padding: 2px 9px; font-size: 12px; }

/* admin list: draft/published status next to the agent name */
.pub-badge {
  margin-left: 8px; padding: 1px 8px; border-radius: 999px;
  font-size: 11px; font-weight: 500; vertical-align: 2px;
  border: 1px solid var(--border); white-space: nowrap;
}
.pub-badge.live { color: var(--success); border-color: var(--success); }
.pub-badge.draft { color: var(--text-muted); }
/* validator flag (#18): the served Version has failed Validators */
.pub-badge.flagged { color: var(--danger); border-color: var(--danger); }
/* validator warning (#52): "needs attention", advisory not a flag. On the
   warning family since #628/DR43 — "caution" is a synonym for warn, and its
   `.live`/`.flagged`/`.passing` siblings all take their own status hue. */
.pub-badge.caution { color: var(--warning); border-color: var(--warning); }
/* all-clear: a published Version whose validators all pass (#corner-badges) */
.pub-badge.passing { color: var(--success); border-color: var(--success); }
/* a classifying badge, not a status one (#546): the Connector canvas's exposure
   tag — what surface this Connector exposes. Accent-washed so the classification
   reads as the canvas's own subject rather than as another verdict. */
.pub-badge.tag { color: var(--accent-text); background: var(--accent-surface); border-color: transparent; }

/* publish panel: per-Version validator results (#18) */
.publish-panel .pub-flag-note { color: var(--danger); font-size: 12px; margin: 6px 0 8px; }
.publish-panel .pub-caution-note { color: var(--accent-text); font-size: 12px; margin: 6px 0 8px; }
.publish-panel .pub-validators {
  display: flex; flex-wrap: wrap; gap: 4px; padding: 0 0 6px 30px;
}
.publish-panel .pub-val {
  font-size: 11px; padding: 0 7px; border-radius: 999px;
  border: 1px solid var(--border);
}
.publish-panel .pub-val.pass { color: var(--success); border-color: var(--success); }
.publish-panel .pub-val.fail { color: var(--danger); border-color: var(--danger); }
.publish-panel .pub-val.warn { color: var(--warning); border-color: var(--warning); }
.publish-panel .pub-val.err { color: var(--text-muted); border-style: dashed; }
.publish-panel .pub-reval-label { font-size: 11px; color: var(--text-muted); }
.publish-panel .pub-findings {
  margin: 0 0 8px 30px; padding: 6px 9px; font-size: 12px;
  color: var(--danger); background: var(--danger-surface);
  border: 1px solid var(--danger-border); border-radius: 8px;
}
.publish-panel .pub-findings div { margin-top: 4px; }
/* a warning's box reads as caution, not danger (#52) — on the warning family
   since #628/DR43; it borrowed accent only because #52 predated --warning */
.publish-panel .pub-findings.warn {
  color: var(--warning); background: var(--warning-surface);
  border-color: var(--warning-border);
}
.publish-panel .pub-suggestion { font-weight: 600; }
/* collapsed by default: only the clickable summary shows until expanded */
.publish-panel details.pub-findings > summary {
  cursor: pointer; font-weight: 600; list-style: none;
}
.publish-panel details.pub-findings > summary::-webkit-details-marker {
  display: none;
}
.publish-panel details.pub-findings > summary::before { content: "▸ "; }
.publish-panel details.pub-findings[open] > summary::before { content: "▾ "; }

/* live publish progress (#18): validators running → verdicts, in place */
.publish-panel .pub-progress { margin: 10px 0 2px; }
.publish-panel .pub-progress-head {
  font-size: 12px; font-weight: 600; margin-bottom: 6px;
}
.publish-panel .pub-progress-head.fail { color: var(--danger); }
.publish-panel .pub-progress .pub-validators { padding-left: 0; }
.publish-panel .pub-val.running {
  color: var(--accent); border-color: var(--accent);
  animation: ycpulse 1s infinite;
}

/* ---------- Theme controls (issue #31) ---------- */
/* The full-area Theme TAKEOVER is gone (CJ, 2026-08-08) and so are its shell
   rules (.te-takeover / .te-body / .te-controls / .te-ctl) and every per-token
   swatch rule (.te-token*). An Agent selects an Organization Theme Preset from
   the Config panel now, through the shared .ws-preset-* picker below.
   What survives is what BOTH the Config panel's Theme fieldset and the
   Workspace's Appearance card still render: the logo row, the chrome checkbox,
   and the .te-group label wrapper. */
.te-logo-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.te-logo-meta { font-size: 11px; color: var(--text-muted); font-family: var(--mono); }
.te-check { display: flex; align-items: center; gap: 8px; font-size: 13px; cursor: pointer; }
.te-check input { accent-color: var(--accent); margin: 0; }
.te-group { margin-bottom: 10px; }
.te-group-name { font-size: 11px; font-weight: 600; color: var(--text-muted); margin-bottom: 2px; }
/* Workspace Appearance preset picker (#402): each preset is a selectable box
   showing its sample color scheme (a swatch strip) plus the preset name. */
.ws-preset-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: var(--space-4); margin-top: var(--space-2);
}
.ws-preset-card {
  display: flex; flex-direction: column; gap: var(--space-4);
  align-items: stretch; text-align: left; padding: var(--space-4);
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--surface-raised); cursor: pointer;
}
.ws-preset-card:hover { border-color: var(--border-strong); background: var(--surface-hover); }
.ws-preset-card.sel { border-color: var(--primary); box-shadow: inset 0 0 0 1px var(--primary); }
.ws-preset-card:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.ws-preset-swatch {
  display: flex; height: 28px; border-radius: var(--radius-sm); overflow: hidden;
  border: 1px solid var(--border);
}
.ws-preset-swatch span { flex: 1; }
.ws-preset-name { font-size: var(--fs-body-sm); font-weight: 600; color: var(--text); }
/* .te-stage is the WORKSPACE Appearance card's preview now, and only that
   (#ws-te-stage). The Agent Builder's mock of the published surface went with
   the token swatches it existed to explain: a preset card's swatch strip already
   samples the palette, so a second preview of a skin the author didn't author
   had nothing left to add. Tokens are still set inline on the stage, which is
   why it renders its own palette rather than the console's. */
.te-stage {
  flex: 1; min-height: 0; display: flex; flex-direction: column;
  border: 1px solid var(--border-strong); border-radius: 10px; overflow: hidden;
  /* Drop shadow lifts the published-page preview off the editor surface (owner
     change, 2026-06-25). --text-derived color-mix so it tracks the theme, per
     the elevation story; a one-off value between --shadow-popover and
     --shadow-modal (flag for /design-review whether to tokenize on a 2nd use). */
  box-shadow: 0 10px 30px color-mix(in srgb, var(--text) 20%, transparent);
  background: var(--surface); color: var(--text);
}
.te-stage .te-chat { flex: 1; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
/* (The Theme editor's all-colors swatch legend was removed by design — the
   preview is just the mock published surface now. The shared .token-swatch
   legend primitive was removed with its last consumer in the #402 Master Theme
   cleanup, #427.) */

/* ---------- published fallback pages (unavailable / resting) ---------- */
/* Served at /a/{slug} for an unpublished agent (#11) or one over its budget
   cap (#13): anonymous, static, zero JS. Was duplicated inline in both pages
   (backlog L5); centralized here so they differ only by glyph + copy. */
.unavailable-wrap {
  height: 100%; display: flex; align-items: center; justify-content: center;
  padding: 24px;
}
.unavailable-card {
  max-width: 420px; text-align: center;
  background: var(--surface-raised); border: 1px solid var(--border);
  border-radius: var(--radius-card); padding: 36px 32px;
}
.unavailable-card .mark { font-size: 30px; line-height: 1; }
.unavailable-card h1 { font-size: 17px; margin: 14px 0 6px; }
.unavailable-card p { margin: 0; color: var(--text-muted); }

/* Reduced motion (a11y, DESIGN.md principle 7): honor the OS preference —
   neutralize the running-chip pulse and the collapse/slide transitions while
   keeping everything operable. Animations resolve instantly; nothing moves. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ---------------------------------------------------------------- Connectors Module (#119/#143, ADR-0013) */
/* The in-Platform management surface for Connectors on the headless Connector
   Service. The cards/list/editor now reuse the shared .card-grid + .builder pane
   shell (#143). The API-keys panel that used to be styled here (`.connector-key-new`,
   `.connector-key-row`, `.key-reveal`) went at #728 with the key ledger; what
   remains below is the Connector Builder Co-Pilot panel. `.connector-form` /
   `.connector-field` / `.connector-actions` survive as the **Hub** settings
   panel's labelled-column form (`hub-settings.js`) — that is now their only
   consumer, the Connector connect form they were named for having been deleted. */

/* The catalog page's Blueprints section (#309/#310, `connectors.html`
   #blueprint-section): the secret-less specs listed below the Connector grid.
   Its own rhythm — the section label needs a wider gap than the page's default
   to read as a new section rather than a caption on the grid above it, and the
   grid clears its own label. These two rules moved here from the retired
   `.cw-onramp` block: the Builder's Blueprint on-ramp is `.cb-tag` cards now,
   but the CATALOG still composes them. The label's old 28px literal rides the
   nearest scale token on the way back in. */
.blueprint-section-label { margin-top: var(--space-9); }
.blueprint-grid { margin-top: var(--space-4); }

/* Connector Builder (#121): the Co-Pilot + Custom build panel. */
.connector-build {
  margin: 18px 0; border: 1px solid var(--border); border-radius: 12px;
  padding: 16px; background: var(--surface-raised);
}
.connector-build-head { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; flex-wrap: wrap; }
.connector-build-head h3 { margin: 0; font-size: 15px; }
.connector-build-head .meta { color: var(--text-muted); }
#connector-copilot-chat { min-height: 340px; display: flex; }

/* ===== Connector parity Builder — the .cn-* vocabulary (PRD #165, #177–#180) =====
   Reviewed 2026-06-28 (/design-review): kept SCOPED chrome, deliberately not promoted —
   its parts fold onto the spec .field / .choice-row / .config-section, the
   takeoverHeader()/.takeover-bar pairing (.builder-takeover, named here until
   #611 deleted it, had no consumer left), and shared ValidatorVerdict on the
   next Connectors touch
   (consolidation-backlog DR4–DR8; DESIGN.md Decision 46). Don't hand-fork further.
   Reuses the existing .list-row / .tree / .pane-editor / .mode-toggle. */

/* Connector Validator verdict chip (#176/#180) — pass/warn/fail token treatment. */
.cn-verdict {
  display: inline-flex; align-items: center; gap: var(--space-3);
  font-size: var(--fs-body-sm); font-weight: var(--fw-emph);
  padding: var(--space-2) var(--space-5); border-radius: var(--radius-pill);
  border: 1px solid var(--border); color: var(--text-muted); background: var(--surface-raised);
}
/* display:inline-flex out-specifies the UA [hidden] rule — restore it (the
   .topbar[hidden] pattern), so a verdict slot waiting for its first result is
   really absent rather than an empty pill outline. */
.cn-verdict[hidden] { display: none; }
.cn-verdict.pass { color: var(--success); border-color: var(--success); }
.cn-verdict.warn { color: var(--warning); border-color: var(--warning); }
.cn-verdict.fail { color: var(--danger);  border-color: var(--danger); }

.cn-form {
  flex: 1; min-height: 0; overflow: auto; padding: var(--space-8);
  display: flex; flex-direction: column; gap: var(--space-6); background: var(--surface-raised);
}

/* Three-tier surface nesting: white form → light-purple section → white sub. */
.cn-section { border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--accent-surface); overflow: hidden; flex: none; }
.cn-section.disabled { background: var(--surface); }
.cn-section.disabled > .cn-head .cn-title { color: var(--text-muted); }
.cn-head { display: flex; align-items: center; gap: var(--space-4); padding: var(--space-6); cursor: pointer; list-style: none; }
.cn-head::-webkit-details-marker { display: none; }
.cn-head:hover { background: color-mix(in srgb, var(--accent) 10%, var(--accent-surface)); }
.cn-head:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; }
.cn-head .caret { flex: none; width: 12px; font-size: 10px; color: var(--text-muted); transition: transform var(--motion-fast); }
details.cn-section[open] > .cn-head .caret { transform: rotate(90deg); }
.cn-title { font-weight: var(--fw-emph); font-size: var(--fs-body); }
.cn-enable { accent-color: var(--accent); width: 15px; height: 15px; margin: 0; flex: none; cursor: pointer; }
.cn-body { padding: var(--space-6); border-top: 1px solid color-mix(in srgb, var(--accent) 18%, var(--border)); display: flex; flex-direction: column; gap: var(--space-6); }
.cn-body[hidden], details.cn-section:not([open]) > .cn-body { display: none; }
.cn-field { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
/* display:flex out-specifies the UA [hidden] rule — restore it (the
   .topbar[hidden] pattern), so a controller hiding a field really hides it
   (the Builder's Connect-instructions field is hidden in System mode). */
.cn-field[hidden] { display: none; }
.cn-label { display: flex; align-items: center; gap: var(--space-3); font-size: var(--fs-meta); font-weight: var(--fw-emph); color: var(--text-muted); }
.cn-label .opt { font-weight: var(--fw-normal); font-style: italic; font-size: var(--fs-meta); color: var(--text-muted); }
/* The field's under-caption — the house hint idiom (muted, --fs-meta, no margin),
   the same voice `.cb-card-head .hint` and `.ws-b-note` speak in. Both credential
   popovers have always emitted it; it had no rule, so every hint they wrote rendered
   as full-size body text under the control. */
.cn-hint { margin: 0; color: var(--text-muted); font-size: var(--fs-meta); }
/* The credential popup's hint when the Test gate CANNOT run (#547 review P7): the
   Credentials ledger's New has nothing to prove the credential against, so Save
   stores it untested — and that is a caution the author reads before the form, in
   the established caution treatment (#52), not danger. Nothing is broken; a check
   simply does not exist yet. */
.cn-hint.caution {
  color: var(--accent-text); background: var(--accent-surface);
  border: 1px solid var(--accent); border-radius: var(--radius-md);
  padding: var(--space-5) var(--space-6);
}
.cn-field > input, .cn-field > select, .cn-field > textarea { width: 100%; }
.cn-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-6); }
@media (max-width: 560px) { .cn-grid { grid-template-columns: 1fr; } }

/* Co-Pilot attach (📎) — the one Co-Pilot conversation's file attach (#179). */
.yc-attach { border: 1px solid var(--border); background: var(--surface-raised); border-radius: var(--radius); padding: var(--space-3) var(--space-5); cursor: pointer; color: var(--text-muted); flex: none; }
.yc-attach:hover { border-color: var(--border-strong); background: var(--surface-hover); }

/* ---------- table mechanics: per-column filters + pager (#224/#225/#226) ---------- */
.th-filter {
  display: inline-flex; align-items: center; gap: var(--space-3);
  position: relative; vertical-align: middle;
}
.attach-col .th-filter { justify-content: center; }
.col-ico-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; padding: 0; flex: none;
  border: 1px solid transparent; border-radius: var(--radius-sm);
  background: none; color: var(--text-muted); cursor: pointer;
}
.col-ico-btn:hover { background: var(--surface-hover); border-color: var(--border); color: var(--text); }
.col-ico-btn:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }
.col-filter-btn.on { background: var(--accent-surface); border-color: var(--accent); color: var(--accent-text); }
.col-clear-btn { color: var(--danger); }
.col-clear-btn:hover { color: var(--danger); border-color: var(--danger-border); background: var(--danger-surface); }
.col-clear-btn[hidden] { display: none; }
.col-filter-pop {
  position: absolute; top: calc(100% + var(--space-3)); left: 0; z-index: var(--z-popover);
  min-width: 210px; padding: var(--space-5);
  background: var(--surface-raised); border: 1px solid var(--border-strong);
  border-radius: var(--radius-md); box-shadow: var(--shadow-popover);
  text-transform: none; letter-spacing: 0;
}
.col-filter-pop.to-right { left: auto; right: 0; }
.col-filter-pop[hidden] { display: none; }
.col-filter-pop input, .col-filter-pop select { width: 100%; }
.pager {
  display: flex; align-items: center; gap: var(--space-5);
  padding: var(--space-5) 16px; border-top: 1px solid var(--border);
}
.pager[hidden] { display: none; }
.pager-status { color: var(--text-muted); font-size: var(--fs-meta); font-variant-numeric: tabular-nums; }
.pager-spacer { flex: 1; }
.pager-btn[disabled] { opacity: var(--opacity-disabled); cursor: default; }
.table-count { color: var(--text-muted); font-size: var(--fs-meta); font-variant-numeric: tabular-nums; }
.table-count strong { color: var(--text); font-weight: var(--fw-emph); }
.page-size {
  display: flex; align-items: center; gap: var(--space-3);
  color: var(--text-muted); font-size: var(--fs-meta); margin: 0; cursor: pointer;
}
.page-size select { padding: var(--space-2) var(--space-4); min-width: 0; }

/* ============================ Admin Browse (#404) ============================
 * The canonical Admin Module status-ledger composition (wayfinder #387
 * Variant C), established here through the Agents ledger as its tracer-bullet
 * consumer. Reuses the shared Ledger/filter/pager mechanics above
 * (.dash-panel / .dash-table / .th-filter / .pager, driven by TablePager);
 * adds the module heading band, the optional summary metric strip, the ledger
 * identity/status cells with isolated row actions, and the push-aside Context
 * Canvas — selecting a row slides an inset, rounded detail Canvas in from the
 * right and pushes the ledger aside. The Admin rail (sidebar.js, owned by
 * #398/#386) stays the sole Module switcher; there are no in-view tabs. */
.module-head {
  display: flex; align-items: flex-start; gap: var(--space-9);
  margin: 0 0 var(--space-9);
}
.module-head .module-heading { min-width: 0; }
.module-head .eyebrow {
  /* size comes from the .eyebrow primitive; this override recolors + weights it */
  color: var(--accent); font-weight: 700; letter-spacing: .12em;
}
.module-head h1 { margin: 2px 0 4px; }
.module-head .sub { margin: 0; color: var(--text-muted); max-width: 640px; }
.module-head .primary { margin-left: auto; white-space: nowrap; }
/* When a Module leads with more than one header action (Skills: Import + Create,
 * #405), the action cluster — not a lone primary — takes the trailing edge. */
.module-head .module-head-actions {
  margin-left: auto; display: flex; gap: var(--space-4); align-items: center;
}
.module-head .module-head-actions .primary { margin-left: 0; }
.module-head .module-head-actions button { white-space: nowrap; }

/* Multi-ledger surface switching (#408 / #392 Variant A). A Module that owns
   more than one ledger (Users · Groups · Directory · the three grant ledgers)
   now adopts the Settings inset-index composition wholesale (#419): the shared
   .settings-wrap / .settings-index / .settings-stage layout, one section visible
   at a time, each carrying its own .settings-page-head. Module switching still
   lives only in the Admin rail; this is intra-Module surface switching. Only the
   panel hide rule is Module-specific — the rail, its head/list/buttons, the
   stage, the page-head, and the narrow-width collapse all come from the shared
   Settings rules above (which key on .settings-page / .settings-*). */
.section-panel[hidden] { display: none; }

.admin-browse {
  display: grid; grid-template-columns: minmax(0, 1fr) 0; gap: 0;
  align-items: start; transition: grid-template-columns 220ms ease, gap 220ms ease;
}
.admin-browse.inspector-open { grid-template-columns: minmax(0, 1fr) 360px; gap: var(--space-8); }
.browse-main { min-width: 0; }
.ctx-slot { min-width: 0; overflow: hidden; }

/* A ledger NEVER compresses when a Context Canvas opens — it scrolls
   horizontally inside its card (spec #524 §J, prototype #522). The card scrolls
   always; the never-compress half is **opt-in**, because it is not right for
   every ledger (the Error Log's Message column is long free text that must wrap).
   A surface opts in with two things, exactly as the prototype pins them:

     .ledger-uncompressed   — cells stop wrapping, since a column that wraps its
                              content into stacked fragments IS a compressed one;
     --ledger-min-width     — the width its own columns actually need, so the
                              table has a floor to scroll below rather than
                              redistributing padding into a cramped fit.

   **The summary strip is part of the ledger, so it gets the same treatment**
   (#546 review Sp4): it was still compressing and wrapping its labels while the
   table beside it refused to, which is the same defect on the same surface —
   "Total connectors" broken over two lines above a table that held its width. So
   an opted-in surface's strip takes the same column floor inside its own
   scroller, and its labels stop wrapping.

   (The paragraph above was appended AFTER this comment's terminator in #546, so
   the loose prose ran on into the first selector and the browser threw the card's
   scroller away — the rule the whole never-compress ruling rests on. #547.)

   The Connectors area's other ledgers (#547/#548) adopt the same pair. */
.dash-panel .table-wrap { overflow-x: auto; }
.dash-table { min-width: var(--ledger-min-width, 0); }
.ledger-uncompressed .dash-table th,
.ledger-uncompressed .dash-table td { white-space: nowrap; }
.ledger-uncompressed .ledger-summary-wrap { overflow-x: auto; }
.ledger-uncompressed .ledger-summary { min-width: var(--ledger-min-width, 0); }
.ledger-uncompressed .summary-cell span,
.ledger-uncompressed .summary-cell strong { white-space: nowrap; }
/* The Connector catalog: Name · Tag · Context · State · Connections · Updated +
   the two-button action cell, none of which reads at a squeezed width. */
#connector-browse { --ledger-min-width: 840px; }
/* The Credentials ledger: Display name + slug · Kind · Secret · Updated + its
   action cell — narrower than the catalog because it carries no Connector
   column (the bindings live in the canvas, #547). */
#credential-browse { --ledger-min-width: 660px; }
/* The Users ledger (#549 review S10) — the never-compress rule is not the
   Connectors area's, it is every ledger with a push-aside Canvas, and this one
   grew a Canvas of its own. The widest of the three: User (name over username) ·
   Source (badge + an origin badge naming a Connector) · Sign-in · Role (an
   in-cell select) · Connections · Status + its two-button action cell. */
#users-browse { --ledger-min-width: 900px; }

/* A ledger's GROUP HEADER row (#547, prototype #522) — the Credentials ledger
   groups by Credential Profile, and the header carries profile · type · count.
   It is a row of the same table rather than a stack of tables, so the columns
   below it stay one grid and the whole thing scrolls as one. The eyebrow
   treatment is the ledger's own column-header voice, one step darker, so a group
   reads as a divider inside the ledger and never as another data row. */
/* The group header is STRUCTURE, not a row — the prototype's own two values
   (#522 `tr.grp td`, #547 review P8): a quiet band that does not collide with the
   hover wash a real row uses to say "you are over me", and a text step fainter
   than `--text-muted`, so the header labels the group without competing with the
   credentials under it.

   Rule-scoped rather than added to `:root` on purpose. The Theme contract's token
   list is a stability promise for stored `theme.json` files and every name in it
   is user-repaintable (app/theme.py, docs/design/theming.md rule 9); Decision 75's
   rule-of-two says a value earns a token on its SECOND consumer, and this is the
   first. When a second surface wants the same soft band or the same faint text,
   `--surface-soft` / `--text-faint` graduate — with the theming rule-9 dance and
   the preset values that go with it — and these two locals retire into them. */
.ledger-group td {
  --group-band: #f9f9fb;
  --group-label: #858290;
  background: var(--group-band);
  padding: var(--space-5) var(--space-7);
  font-family: var(--mono); font-size: var(--fs-eyebrow);
  text-transform: uppercase; letter-spacing: .1em; color: var(--group-label);
}
html[data-theme="dark"] .ledger-group td {
  --group-band: #252528;
  --group-label: #8f8c98;
}
.ledger-group .group-meta { text-transform: none; letter-spacing: 0; }

/* Summary metric strip — the optional leading metric row of the composition. */
.ledger-summary {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-8); margin-bottom: var(--space-8);
}
.summary-cell {
  background: var(--surface-raised); border: 1px solid var(--border);
  border-radius: var(--radius-md); padding: var(--space-8) var(--space-9);
}
.summary-cell span { display: block; color: var(--text-muted); font-size: var(--fs-meta); }
.summary-cell strong { display: block; font-size: var(--fs-h1); letter-spacing: -.02em; font-variant-numeric: tabular-nums; }

/* Ledger identity cell + selectable rows + isolated actions. */
.ledger-identity { display: flex; align-items: center; gap: var(--space-6); min-width: 200px; }
.identity-chip {
  width: 30px; height: 30px; flex: 0 0 auto; display: grid; place-items: center;
  border-radius: var(--radius-sm);
}
.identity-chip.agent { color: var(--agent-text); background: var(--agent-surface); }
.identity-chip.skill { color: var(--skill-text); background: var(--skill-surface); }
/* Validators are admin-only and outside the four Workspace identity families,
   so — like Connectors (#393) and per the "never a fifth identity family" rule
   (#401) — their ledger chip is a NEUTRAL object mark, not a fifth hue. The
   shield-check glyph carries the recognition. (#406) */
.identity-chip.validator { color: var(--text-muted); background: var(--surface-hover); border: 1px solid var(--border-strong); }
/* People surfaces (Users / Groups / Directory, #408) are identity/access
   management, not one of the four Workspace identity families — so, like
   Validators and Connectors, their ledger chip is a NEUTRAL object mark (the
   person/people glyph carries the recognition), never a fifth hue (#401). */
/* Connectors AND Blueprints (#393/#410) share this neutral object mark — a
   Connector never becomes a fifth identity family, and a Blueprint (a
   secret-less spec/template) is a peer neutral object, never a Connector
   category. Semantic status, focus, and primary-action roles carry the color. */
/* Credentials and Service access keys (#432) are secret/access objects managed
   in Settings, not one of the four Workspace identity families — so their
   Context Canvas identity chip is the same NEUTRAL object mark (the archive /
   lock glyph carries the recognition), never a fifth hue (#401). */
/* An error-log entry (#478) is a diagnostic record, not one of the four
   Workspace identity families — and NOT a danger surface either: a log the
   Owner reads calmly must not paint the whole ledger red (danger styling stays
   reserved for a failure the person is being asked to act on). Same NEUTRAL
   object mark; the alert glyph carries the recognition. */
.identity-chip.user, .identity-chip.group, .identity-chip.connector,
.identity-chip.blueprint, .identity-chip.credential,
.identity-chip.alert,
.identity-chip.key { color: var(--text-muted); background: var(--surface-hover); border: 1px solid var(--border-strong); }
.ledger-identity .l-name { font-weight: var(--fw-emph); }
.ledger-identity .l-slug { color: var(--text-muted); font-size: var(--fs-eyebrow); font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, monospace); }
.dash-table .ledger-row { cursor: pointer; }
.dash-table .ledger-row[aria-selected="true"] td { background: var(--accent-surface); }
.dash-table .ledger-row[aria-selected="true"] td:first-child { box-shadow: inset 3px 0 var(--accent); }
.dash-table .ledger-row:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; }
.ledger-status { display: inline-flex; gap: var(--space-3); flex-wrap: wrap; }
.ledger-actions { display: flex; gap: var(--space-3); justify-content: flex-end; }
.ledger-actions button { min-height: 0; padding: var(--space-3) var(--space-6); }
.dash-table td.actions-cell { text-align: right; }

/* Bulk selection (#408 / #392 Variant A) — an additive leading checkbox column
   and a bulk-action bar above the ledger. Only surfaces that opt in (Users,
   Groups) carry these; the other ledgers are untouched. */
.dash-table th.check, .dash-table td.check { width: 34px; text-align: center; padding-left: var(--space-6); padding-right: 0; }
.dash-table .check input { accent-color: var(--accent); width: 15px; height: 15px; margin: 0; cursor: pointer; }
/* Protected rows keep the checkbox visible-but-disabled (#392 self-protection);
   its title/aria-label carry the explanation. */
.dash-table .check input:disabled { opacity: var(--opacity-disabled); cursor: default; }
.ledger-bulk {
  display: flex; align-items: center; gap: var(--space-6);
  padding: var(--space-5) var(--space-8); margin-bottom: var(--space-6);
  background: var(--accent-surface); border: 1px solid var(--accent);
  border-radius: var(--radius-md); font-size: var(--fs-body-sm);
}
.ledger-bulk[hidden] { display: none; }
.ledger-bulk .bulk-count { color: var(--accent-text); }
.ledger-bulk .bulk-note { color: var(--text-muted); font-size: var(--fs-meta); }
.ledger-bulk .spacer { flex: 1; }
.ledger-bulk select { min-width: 130px; }
.ledger-bulk button { min-height: 0; padding: var(--space-3) var(--space-6); }

/* The push-aside Context Canvas (the Admin inspector — Context Canvas
 * terminology outside the Workspace, per #400). Inset, rounded, and it slides
 * in from the right when a row is selected. */
.ctx-canvas {
  position: sticky; top: var(--space-8);
  border: 1px solid var(--border); border-radius: var(--radius-card);
  background: var(--surface-raised); overflow: auto;
  max-height: calc(100vh - 132px);
  opacity: 0; transform: translateX(28px);
  transition: opacity 160ms ease, transform 220ms ease;
}
.admin-browse.inspector-open .ctx-canvas { opacity: 1; transform: none; }
.ctx-head {
  position: sticky; top: 0; z-index: 1; display: flex; align-items: center;
  gap: var(--space-4); padding: var(--space-7) var(--space-8);
  background: var(--surface-raised); border-bottom: 1px solid var(--border);
}
.ctx-head strong { font-size: var(--fs-body-sm); }
.ctx-close { margin-left: auto; width: 30px; height: 30px; display: grid; place-items: center; padding: 0; }
.ctx-body { padding: var(--space-8); }
.ctx-body .identity-chip { width: 44px; height: 44px; margin-bottom: var(--space-6); }
.ctx-body .identity-chip .ico { width: 22px; height: 22px; }
.ctx-title { margin: 0 0 2px; font-size: var(--fs-brand); }
.ctx-desc { color: var(--text-muted); margin: var(--space-6) 0; }
.ctx-detail-list {
  display: grid; grid-template-columns: auto 1fr; gap: var(--space-5) var(--space-8);
  padding: var(--space-7) 0; margin: 0 0 var(--space-7);
  border-top: 1px solid var(--border); border-bottom: 1px solid var(--border);
}
/* Canvas-body variants (#408): the status-pill row under the title, and a
   left-aligned action row. 16px has no exact ramp stop (--space-7 is 14px,
   --space-8 is 18px), so the value stays literal — but in CSS, not inline JS. */
.ctx-body .ctx-pill-row { margin-top: var(--space-6); }
/* Breathing room between the status-chip row and the detail-list separator. */
.ctx-body .ledger-status { margin-bottom: var(--space-6); }
.ctx-body .ctx-actions { justify-content: flex-start; margin-top: 16px; }
.ctx-detail-list dt { color: var(--text-muted); }
.ctx-detail-list dd { margin: 0; font-weight: var(--fw-emph); text-align: right; }
/* Free-form text in the Canvas body (#478 Error Logging): a block of context
   written by some other subsystem, shown verbatim. Monospace + pre-wrap because
   the writer may have put newlines, ids, or JSON-ish fragments in it, and the
   reader is diagnosing — but flat and hairlined like every other Canvas block,
   never a code editor. */
.ctx-detail-text {
  padding: var(--space-6) var(--space-7);
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--surface);
  font-family: var(--mono); font-size: var(--fs-meta); line-height: 1.55;
  white-space: pre-wrap; overflow-wrap: anywhere;
}
.ctx-section { margin: var(--space-8) 0; }
.ctx-section-title {
  margin-bottom: var(--space-5); color: var(--text-muted); font-size: 10px;
  font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
}
.ctx-metric-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-4); }
.ctx-metric {
  min-width: 0; padding: var(--space-6) var(--space-7);
  border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--surface);
}
.ctx-metric span { display: block; color: var(--text-muted); font-size: 10px; line-height: 1.3; }
.ctx-metric strong { display: block; margin-top: var(--space-2); font-size: 17px; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
/* `.ctx-use.link` — a "used by" row that CROSS-NAVIGATES (#546, spec §J;
   prototype #522's own modifier). The Connector canvas's linked Credentials —
   and, when #547 lands, the credential canvas's Used-by — carry you to the other
   surface with the other object selected. A **modifier on the existing
   `.ctx-use`**, never a second family: the two lists are the two ends of one
   relationship, and a fresh cut of the anatomy would have them read differently
   on the two surfaces it connects. The row is a real `<button>`, so it is
   focusable and announced as a control (the house `button` rules already give it
   `font: inherit` and the neutral focus ring); these add only the affordance. */
.ctx-use.link { width: 100%; text-align: left; cursor: pointer; }
.ctx-use.link:hover { background: var(--surface-hover); border-color: var(--border-strong); }
.ctx-use.link .l-name { color: var(--accent); }
/* The Usage panel's honesty line: the counters are not tracked yet, and the
   panel says so rather than showing a number nothing measured. */
.ctx-usage-note { margin: var(--space-5) 0 0; color: var(--text-muted); font-size: var(--fs-meta); }

.ctx-reliability { display: grid; gap: var(--space-4); margin: 0; }
.ctx-reliability > div { display: flex; align-items: baseline; gap: var(--space-6); }
.ctx-reliability dt { color: var(--text-muted); }
.ctx-reliability dd { margin: 0 0 0 auto; font-weight: var(--fw-emph); font-variant-numeric: tabular-nums; }

/* Validator findings in the Context Canvas (#18/#29): click-to-open
   disclosures mirroring the studio publish panel's .pub-findings, so a
   failure/warning/error the row only counted is readable here. */
.ctx-findings {
  margin: 0 0 var(--space-4); padding: var(--space-4) var(--space-6);
  font-size: var(--fs-eyebrow); color: var(--danger);
  background: var(--danger-surface); border: 1px solid var(--danger-border);
  border-radius: var(--radius-md);
}
.ctx-findings div { margin-top: var(--space-3); }
.ctx-suggestion { font-weight: var(--fw-emph); }
/* a warning or an un-runnable validator reads as caution, not danger (#52) —
   on the warning family since #628/DR43 (see .pub-findings.warn) */
.ctx-findings.warn, .ctx-findings.err {
  color: var(--warning); background: var(--warning-surface);
  border-color: var(--warning-border);
}
.ctx-findings > summary {
  cursor: pointer; font-weight: var(--fw-emph); list-style: none;
}
.ctx-findings > summary::-webkit-details-marker { display: none; }
.ctx-findings > summary::before { content: "▸ "; }
.ctx-findings[open] > summary::before { content: "▾ "; }

/* Allow-only grant rosters in the Context Canvas (#409). A stack of rows, each
   a name/detail pair beside a full-cell grant checkbox — the same allow-only
   affordance the directory permission cell and the Skill attach table use, so
   Agent access, Skill grants, and directory selection read identically. */
.grant-roster { display: grid; gap: var(--space-3); margin: 0 0 var(--space-7); }
.grant-row {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center;
  gap: var(--space-5); padding: var(--space-4) var(--space-6);
  border: 1px solid var(--border); border-radius: var(--radius-md);
}
.grant-row:has(.grant-check input:checked) {
  border-color: var(--accent); background: var(--accent-surface);
}
.grant-row .l-name { font-weight: var(--fw-emph); font-size: var(--fs-body); }
.grant-row .l-slug { color: var(--text-muted); font-size: var(--fs-eyebrow); }
.grant-check { padding: 0; }
.grant-check input { accent-color: var(--accent); width: 16px; height: 16px; margin: 0; flex: none; cursor: pointer; }
.grant-check input:disabled { cursor: default; }
/* The Restricted grant block reuses .access-levels chrome; keep its rosters
   from inheriting the fieldset's radio spacing. */
.access-grants { margin-top: var(--space-6); }

/* Honest states — loading skeleton, empty, and error share one centered card. */
.browse-state {
  min-height: 320px; border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--surface-raised); display: grid; place-items: center;
  padding: var(--space-9); text-align: center;
}
.browse-state h2 { margin: 0 0 var(--space-4); }
.browse-state p { color: var(--text-muted); max-width: 400px; margin: 0 0 var(--space-8); }
.browse-skeleton { width: 100%; display: grid; gap: var(--space-6); }
.browse-skeleton .sk-row {
  height: 44px; border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: linear-gradient(100deg, var(--surface) 20%, var(--surface-hover) 36%, var(--surface) 52%);
  background-size: 220% 100%; animation: browse-shimmer 1.4s linear infinite;
}
@keyframes browse-shimmer { to { background-position-x: -220%; } }

@media (max-width: 900px) {
  .admin-browse, .admin-browse.inspector-open { grid-template-columns: 1fr; }
  .ctx-canvas { position: static; max-height: none; }
}
@media (prefers-reduced-motion: reduce) {
  .admin-browse, .ctx-canvas { transition: none; }
  .browse-skeleton .sk-row { animation: none; }
}

/* ======================== Overview canvas (#407) ============================
 * The operational dashboard's approved composition (wayfinder #394 Variant A):
 * a leading Metric-Card row, an owned cost/run chart Panel co-present with a
 * Needs-attention Panel + spend-concentration Panel, then the shared Admin
 * Browse Status ledger for detailed Per-Agent comparison. Metric Card / chart
 * Panel / attention Row are this composition's reusable vocabulary. Colours are
 * tokens only: the prototype's amber "warn" maps to the design system's accent
 * caution treatment (matching the established unpriced-run convention) — the
 * locked token contract has no amber status role, and danger red is reserved
 * for genuine failure. */
.metric-grid {
  display: grid; grid-template-columns: repeat(5, minmax(120px, 1fr));
  gap: var(--space-6); margin-bottom: var(--space-8);
}
.metric-card {
  min-width: 0; background: var(--surface-raised); border: 1px solid var(--border);
  border-radius: var(--radius-md); padding: var(--space-7) var(--space-8);
}
.metric-label {
  color: var(--text-muted); font-size: var(--fs-eyebrow); font-weight: var(--fw-bold);
  letter-spacing: .06em; text-transform: uppercase;
}
.metric-value {
  margin-top: var(--space-3); font-size: var(--fs-h1); font-weight: var(--fw-bold);
  letter-spacing: -.02em; font-variant-numeric: tabular-nums;
}
.metric-delta { margin-top: var(--space-2); color: var(--text-muted); font-size: var(--fs-eyebrow); }
.metric-delta.good { color: var(--success); }
.metric-delta.warn { color: var(--warning); }

/* Overview grid: the owned trend Panel (spans two rows) beside the attention +
   concentration Panels. */
.overview-grid {
  display: grid; grid-template-columns: minmax(0, 1.55fr) minmax(280px, .65fr);
  gap: var(--space-8); margin-bottom: var(--space-8); align-items: start;
}
.overview-grid .trend-panel { grid-row: span 2; }

/* Flat Panel primitive (the loose visual container, #400 terminology). */
.panel {
  background: var(--surface-raised); border: 1px solid var(--border);
  border-radius: var(--radius-md); overflow: hidden; min-width: 0;
}
.panel-head {
  min-height: 50px; display: flex; align-items: center; gap: var(--space-5);
  padding: var(--space-5) var(--space-8); border-bottom: 1px solid var(--border);
}
.panel-head .spacer { flex: 1; }
.panel-title { font-weight: var(--fw-emph); }
.panel-hint { color: var(--text-muted); font-size: var(--fs-meta); }
.panel .panel-pad { padding: var(--space-8); }
.panel .panel-pad p.sub { margin: var(--space-3) 0 0; }
.panel .panel-pad button { margin-top: var(--space-6); }

/* Chart legend. Cost = accent (the bars), Runs = agent green (the line). */
.legend { display: flex; align-items: center; gap: var(--space-7); color: var(--text-muted); font-size: var(--fs-eyebrow); }
.legend span { display: inline-flex; align-items: center; gap: var(--space-3); }
.legend .dot { width: var(--space-4); height: var(--space-4); border-radius: var(--radius-pill); background: var(--accent); }
.legend .dot.runs { background: var(--agent); }

/* Needs-attention Rows (the reusable attention vocabulary). */
.insight-list { display: grid; }
.insight {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  gap: var(--space-5); align-items: center;
  padding: var(--space-6) var(--space-7); border-bottom: 1px solid var(--border);
}
.insight:last-child { border-bottom: 0; }
.insight-mark {
  width: 30px; height: 30px; border-radius: var(--radius-sm); display: grid;
  place-items: center; background: var(--accent-surface); color: var(--accent-text);
}
.insight-mark.good { background: var(--surface-hover); color: var(--success); }
.insight-copy strong, .insight-copy span { display: block; }
.insight-copy span { color: var(--text-muted); font-size: var(--fs-eyebrow); }
.insight-value { font-variant-numeric: tabular-nums; font-weight: var(--fw-emph); }

/* Owned SVG chart — cost bars + run-volume line. No chart library. */
.chart-wrap { padding: var(--space-7) var(--space-8) var(--space-4); }
.chart { display: block; width: 100%; height: auto; overflow: visible; }
.chart .grid { stroke: var(--border); stroke-width: 1; }
.chart .axis-label { fill: var(--text-muted); font-size: 10px; font-family: var(--sans); }
.chart .bar { fill: var(--accent-surface); }
.chart .bar:hover, .chart .bar:focus-visible { fill: var(--accent); }
.chart .bar:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: var(--space-1); }
.chart .line { fill: none; stroke: var(--agent); stroke-width: 2.5; }

/* Heading band above the reused Per-Agent ledger. */
.dash-section-head {
  display: flex; align-items: baseline; gap: var(--space-5);
  margin: var(--space-8) 0 var(--space-5);
}
/* The Overview supplies its own leading Metric-Card row, so the ledger's own
   optional summary strip is suppressed when a surface passes no cells. */
.ledger-summary:empty { display: none; margin: 0; }

@media (max-width: 1050px) {
  .overview-grid { grid-template-columns: 1fr; }
  .overview-grid .trend-panel { grid-row: auto; }
  .metric-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
  .metric-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ================================================================== Workspace
 * The end-user Workspace shell (#234, PRD #233) — the third page family, at /.
 * New end-user chrome carries the ws- prefix and composes the existing
 * primitives (#app-sidebar, .cn-section, .topbar, .yc-*). Built to the
 * approved mockups (docs/mockups/explore/end-user-workspace/). */

/* Semantic identity families (#401): Agent green, Skill gold, File blue, plus
 * the rose family — a third semantic axis alongside the violet accent + the
 * status green/red. Since the #658 swap the rose family paints TASKS (the
 * token names stay --notification* for the parity contract), while
 * Notification surfaces ride the violet accent/primary. As of #401 these are
 * FULL members of the four-place parity contract (theme.py TOKEN_GROUPS
 * "Identity" + the :root/dark blocks at the top of this file + dom-utils.js +
 * frontend.md), so they Theme and validate like every other role. The -strong
 * pair is the WHITE-BEARING solid-fill variant: the identity bases are light
 * in dark mode (for tints/text on dark), so white text on them fails WCAG AA —
 * -strong is dark enough for white in BOTH modes (one value); rose's -strong
 * carries the Tasks badge and the Tasks-context primary buttons. Identity
 * tints/text/icons use the base/-surface/-text roles; only solid fills behind
 * white use -strong. Connector stays neutral — never a fifth family. */

/* -- rail (#256/#257) -- the shell is height-locked: the page NEVER
   scrolls; each pane scrolls itself. The rail pins its brand at the top
   and the profile block at the bottom — only the middle nav scrolls. */
.ws-shell.has-sidebar { height: 100vh; overflow: hidden; }
/* The work zone (#413, prototype #398): a full-height COLUMN right of the left
   nav — the header row on top, the work row filling the rest. Splitting the
   header off is what pins New chat top-right: it spans the full width ABOVE the
   Center + right Utility Rail/Canvas, so opening a Utility only shrinks the
   Center and never displaces the header. */
.ws-shell .ws-workzone { flex: 1; min-width: 0; height: 100vh; display: flex;
  flex-direction: column; overflow: hidden; }
.ws-shell .ws-topbar { flex: none; }
/* The work row: the Center (.app-content) + the Utility Canvas + the Utility
   Rail, side by side BELOW the header. It fills the zone under the header. */
.ws-shell .ws-workrow { flex: 1; min-width: 0; min-height: 0; display: flex;
  overflow: hidden; }
/* the Center now fills the work row (no longer a 100vh body-flex child); its
   height comes from the row, its width from flex:1 (base .app-content rule). */
.ws-shell .app-content { min-height: 0; }
.ws-shell #app-sidebar { width: 252px; position: relative; height: 100vh; overflow: hidden; }
.ws-shell #app-sidebar .sb-nav { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
/* the pinned ends never squeeze — .sb-brand's overflow:hidden zeroes its
   automatic min-height, so an overlong nav would crush the wordmark */
.ws-shell #app-sidebar .sb-brand { flex: none; }
.ws-shell #app-sidebar .ws-profile { flex: none; }
/* sidebar-tuned .cn-section disclosure (reused from the Connector Builder) */
/* .sb-nav is display:flex — restore the UA [hidden] rule so the Settings
 * sub-nav can REPLACE the main rail (#243) */
.sb-nav[hidden] { display: none; }
.ws-shell .sb-nav .cn-section { background: transparent; border: 0; border-radius: 0; margin: 2px 0; overflow: visible; }
.ws-shell .sb-nav .cn-head { padding: 6px 8px; gap: 6px; cursor: pointer; }
.ws-shell .sb-nav .cn-head:hover { background: transparent; }
.ws-shell .sb-nav .cn-title { font-size: var(--fs-meta); letter-spacing: .04em; text-transform: uppercase; color: var(--text-muted); font-weight: 700; }
.ws-shell .sb-nav .cn-body { padding: 2px 0 8px; background: transparent; display: block; }
.ws-shell .sb-nav .caret { color: var(--text-muted); }
.ws-shell .sb-nav .sb-item { padding: 5px 10px; }
/* the user's OWN items read as filled icons (#262); org stays hollow */
.sb-ico.filled svg { fill: currentColor; }
/* Kept after DR37: this host also carries the hand-cut agent/skill SPARK marks
   (16x16, no .ico), which the registry default cannot reach. */
.sb-ico svg { display: block; }

/* ---------- User Workspace Settings surface (#420, prototype #391) ----------
   Keeps the replacement-rail shell; the personal sections reuse the shared flat
   configuration Card anatomy (.settings-card + .settings-feedback + Revert +
   Cmd/Ctrl-S) — NO Admin chrome, NO inset index. One section visible at a time
   in a comfortable centered content column. */
.ws-settings .ws-set-sec { flex: 1; min-height: 0; overflow-y: auto; padding: 26px 32px 120px; }
.ws-settings .ws-set-sec > * { max-width: 880px; margin-inline: auto; }
.ws-settings .settings-page-head { margin-top: 4px; }
/* Appearance: the live Workspace preview seated inside the flat Card. */
.ws-te-preview { margin-top: 14px; min-height: 190px; display: flex; }
.ws-te-preview .te-stage { flex: 1; min-height: 170px; }

/* FILES row/folder overflow menu (#346): the ⋯ trigger + the net-new
   .ws-rowmenu popover (rendered fixed-positioned in the body to escape the
   rail's overflow clip — JS sets position/left/top). */
.ws-frow-more { font-size: 16px; opacity: 0; }
.node:hover > .list-row .ws-frow-more,
.list-row:hover .ws-frow-more,
.list-row.menu-open .ws-frow-more { opacity: 1; }
.list-row.menu-open { background: var(--surface-hover); border-radius: var(--radius-sm); }
.ws-rowmenu { min-width: 176px; padding: var(--space-3); background: var(--surface-raised);
  border: 1px solid var(--border-strong); border-radius: var(--radius-md);
  box-shadow: var(--shadow-popover); z-index: var(--z-popover);
  display: flex; flex-direction: column; gap: 1px; }
.ws-rowmenu button { display: flex; align-items: center; gap: var(--space-4); width: 100%;
  text-align: left; border: 0; background: none; color: var(--text);
  font-size: var(--fs-body-sm); padding: 6px 9px; border-radius: var(--radius-sm); cursor: pointer; }
.ws-rowmenu button:hover { background: var(--surface-hover); }
.ws-rowmenu button.danger { color: var(--danger); }
.ws-rowmenu button.danger:hover { background: var(--danger-surface); }

/* The Preferences File-sort caption (#347) moved to the .field-group primitive
   it belongs to (DR42) — the box, its legend and its caption are one object and
   are now written in one place. */

/* file-row + add-to-chat (#237, reworked by #252/#253): purple and always
   visible — the primary file-to-chat affordance */
.ws-frow-act { margin-left: auto; display: flex; gap: 2px; }
.ws-frow-act button { width: 22px; height: 22px; padding: 0; border: 1px solid transparent;
  border-radius: var(--radius-sm); background: none; color: var(--text-muted); cursor: pointer; line-height: 1; }
.ws-frow-act button:hover { background: var(--surface-hover); border-color: var(--border); color: var(--text); }
.ws-frow-act .ws-add-file { color: var(--accent-text); font-weight: var(--fw-bold); font-size: 14px; }
.ws-frow-act .ws-add-file:hover { background: var(--accent-surface); border-color: var(--accent); color: var(--accent-text); }

/* owned file-type icons (#237): md is the m↓ text glyph, the rest inline SVG */
/* Kept after DR37: file-tree.js still draws Unicode carets here, and this slot
   takes caller-supplied glyphs on some hosts — not provably registry-only. */
.tree .list-row .ico svg { display: block; }
.ico-md { display: inline-flex; align-items: baseline; font-weight: 700; font-size: 12px; line-height: 1; }
.ico-md .md-arr { font-size: 10px; margin-left: -0.5px; }

/* rail profile footer */
.ws-profile { display: flex; align-items: center; gap: 8px; padding: 8px 10px; border-top: 1px solid var(--border); }
.ws-avatar { width: 28px; height: 28px; border-radius: 50%; flex: none; background: var(--accent-surface);
  color: var(--accent-text); display: flex; align-items: center; justify-content: center; font-weight: 600; font-size: 13px; }
.ws-profile .who { min-width: 0; }
.ws-profile .who .nm { font-size: var(--fs-body-sm); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ws-profile .who .em { font-size: var(--fs-meta); color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#app-sidebar.collapsed .ws-profile .who { display: none; }

/* Settings full-screen (#243): ← Back + section label in the swapped rail */
.ws-back { display: flex; align-items: center; gap: 8px; width: 100%; padding: 6px 10px; border: 0;
  background: none; color: var(--text-muted); cursor: pointer; font: inherit; text-align: left; border-radius: var(--radius-sm); }
.ws-back:hover { background: var(--surface-hover); color: var(--text); }
.ws-nav-title { font-size: var(--fs-meta); text-transform: uppercase; letter-spacing: .04em;
  color: var(--text-muted); font-weight: 700; padding: 12px 10px 4px; }

/* connection monogram (#243): the Connector Builder wizard's brand-signature
 * monograms (placeholders for the real logos, which will ship inside each
 * connector's definition). Brand hex is a sanctioned exception to token-only —
 * flagged for /design-review. The Workspace Connections lanes (#421) use the
 * NEUTRAL .ws-conn-mark below instead — a Connector is never a fifth identity
 * colour (#393/#401). */
.ws-conn-ico { width: 34px; height: 34px; border-radius: var(--radius-sm); background: var(--accent-surface);
  color: var(--accent-text); display: flex; align-items: center; justify-content: center; font-size: 16px; flex: none; font-weight: 700; }
.ws-conn-ico.conn-o365 { background: #EB3C00; color: #fff; }
.ws-conn-ico.conn-monday { background: #FF3D57; color: #fff; }
.ws-conn-ico.conn-gdrive { background: #1A73E8; color: #fff; }
.ws-conn-ico.conn-slack { background: #4A154B; color: #fff; }
/* Workspace Connections lifecycle lanes (#421, prototype #393 "Variant A —
   Lifecycle surfaces", locked 9f33294). Three headed lanes — Connected / System
   account / Available — of flat neutral cards, reproducing the locked prototype
   anatomy through the design-system tokens (prototype --wf-* → semantic roles;
   -soft names normalize to -surface). The Connector object mark is NEUTRAL (no
   fifth identity colour, #393/#401); status/focus/the primary Connect action
   carry the semantic colour. */
.ws-conn-section { margin-top: var(--space-9); }
.ws-conn-section:first-of-type { margin-top: 0; }
.ws-conn-section[hidden] { display: none; }
.ws-conn-section-title { display: flex; align-items: center; gap: var(--space-4);
  margin-bottom: var(--space-5); }
.ws-conn-section-title h2 { margin: 0; font-size: var(--fs-title);
  letter-spacing: -.01em; }
.ws-conn-hint { color: var(--text-muted); font-size: var(--fs-meta); }

/* the count / status chip — a neutral pill; .ok carries the semantic success
   role (the ratified --success-surface soft wash, #421/Decision 75) for the
   affirmative Connected / System Account states. */
.ws-conn-chip { display: inline-flex; align-items: center; min-height: 22px;
  padding: var(--space-1) var(--space-4); border: 1px solid var(--border);
  border-radius: var(--radius-pill); color: var(--text-muted);
  background: var(--surface-hover); font-size: var(--fs-eyebrow);
  white-space: nowrap; }
.ws-conn-chip.ok { color: var(--success); border-color: transparent;
  background: var(--success-surface); }

/* the lane grid + the flat card (no ornamental elevation). */
.ws-conn-cards { display: grid; gap: var(--space-5);
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.ws-conn-card { min-width: 0; display: grid; gap: var(--space-6);
  padding: var(--space-7); border: 1px solid var(--border);
  border-radius: var(--radius-card); background: var(--surface-raised);
  text-align: left; }
.ws-conn-card-top { display: flex; align-items: center; gap: var(--space-4); }
.ws-conn-card-top .ws-conn-chip { margin-left: auto; }
.ws-conn-mark { width: 36px; height: 36px; flex: none; display: grid;
  place-items: center; border-radius: var(--radius-md);
  border: 1px solid var(--border-strong); background: var(--surface-hover);
  color: var(--text); font-weight: var(--fw-bold); }
.ws-conn-copy { min-width: 0; }
.ws-conn-copy strong, .ws-conn-copy small { display: block; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.ws-conn-copy small { color: var(--text-muted); }

/* the connected account row + the available actions row share a trailing-control
   layout — the account/hint grows, the button trails. */
.ws-conn-account, .ws-conn-actions { display: flex; align-items: center;
  gap: var(--space-5); padding-top: var(--space-5);
  border-top: 1px solid var(--border); }
.ws-conn-account button, .ws-conn-actions button { margin-left: auto; flex: none; }
.ws-conn-who { min-width: 0; }
.ws-conn-who strong { display: block; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }
.ws-conn-who code { color: var(--text-muted); font-size: var(--fs-meta); }

/* -- the Connect-time folder picker (#535, spec #524 §I) --------------------
   No prototype exists for this on purpose — the spec governs, and the spec says
   design-system modal + lazy tree + breadcrumb of the current selection + ONE
   primary action ("Use this folder"), folders only, no search box in v1. So the
   chrome is borrowed whole rather than re-cut: the owned `.dialog-overlay` /
   `.dialog` (scroll lock, Esc/backdrop dismiss, theming) widened by the same
   `-popover` idea the credential form uses, over rows shaped like the shipped
   FileTree's. What is genuinely new is only the three things this surface has
   and no other does: a breadcrumb of the *selection*, a node that can be opened
   but not picked, and a per-node loading state.
   Nothing here knows a provider: a node is unpickable because the mapping said
   `selectable: false`, and all the CSS does with that is dim it. */
.ws-pick { width: min(560px, 100%); max-height: 86vh; display: flex;
  flex-direction: column; }
.ws-pick-body { flex: 1; min-height: 0; display: flex; flex-direction: column;
  gap: var(--space-5); }
/* the breadcrumb of the CURRENT SELECTION (not of the open node) — what
   "Use this folder" is about to bind, spelled out in friendly names. */
.ws-pick-crumbs { display: flex; align-items: baseline; flex-wrap: wrap;
  gap: var(--space-2); min-height: 22px; color: var(--text-muted);
  font-size: var(--fs-meta); }
.ws-pick-crumb { overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  max-width: 16ch; }
.ws-pick-crumb-sep { color: var(--border-strong); }
.ws-pick-crumb-current { color: var(--text); font-weight: var(--fw-emph);
  font-family: var(--mono); max-width: none; }
/* the lazy tree — one bounded call per expansion, so a node is `.loading` while
   its children are in flight and `.ws-pick-kids` only exists once they land. */
.ws-pick-tree { flex: 1; min-height: 180px; overflow: auto;
  border: 1px solid var(--border); border-radius: var(--radius-md);
  padding: var(--space-3); }
.ws-pick-kids { margin-left: var(--space-6);
  border-left: 1px solid var(--border); padding-left: var(--space-3); }
.ws-pick-node { width: 100%; display: flex; align-items: center;
  gap: var(--space-3); padding: var(--space-2) var(--space-3);
  border: 1px solid transparent; border-radius: var(--radius-sm);
  background: none; color: var(--text); text-align: left; font: inherit; }
.ws-pick-node:hover { background: var(--surface-hover); }
.ws-pick-node.sel { border-color: var(--accent);
  background: var(--accent-surface); }
/* a container you may OPEN but not PICK (a SharePoint site is the case this
   exists for — and the rule that dims it is `selectable`, never a name). */
.ws-pick-node.unpickable .ws-pick-name { color: var(--text-muted); }
.ws-pick-caret { flex: none; width: 14px; color: var(--text-muted);
  font-size: var(--fs-meta); }
.ws-pick-name { min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }
.ws-pick-node.loading .ws-pick-caret { opacity: var(--opacity-disabled); }
.ws-pick-empty { padding: var(--space-4); color: var(--text-muted);
  font-size: var(--fs-meta); }
/* the paged listing's continuation: a quiet link-weight row at the END of the
   page it continues, never a control that competes with the one primary action. */
.ws-pick-more { display: block; width: 100%; text-align: left;
  padding: var(--space-2) var(--space-3); border: 0; background: none;
  color: var(--accent); font-size: var(--fs-meta); cursor: pointer; }
.ws-pick-more:hover { text-decoration: underline; }
.ws-pick-more:disabled { color: var(--text-muted); cursor: default;
  text-decoration: none; }

/* the needs-setup Connection's recovery card (#535 AC5) — an unfinished Connect
   is recoverable, so the state reads as a to-do with a primary action rather
   than as an error. */
.ws-conn-setup { display: flex; align-items: center; gap: var(--space-5);
  padding-top: var(--space-5); border-top: 1px solid var(--border); }
.ws-conn-setup button { margin-left: auto; flex: none; }
.ws-conn-setup small { color: var(--text-muted); font-size: var(--fs-meta); }
.ws-conn-scope { display: block; color: var(--text-muted);
  font-size: var(--fs-meta); overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }

/* -- the Connection card's freshness controls (#536, spec #524 §F) ----------
   Two buttons now share the account row's trailing slot (Refresh · Disconnect),
   so `margin-left: auto` moves off the buttons and onto the group — two autos
   split the free space and open a gap BETWEEN them.

   In flight, Refresh is disabled and *spins the shared Working-mark* (§
   Iconography): the system retired the `◌` spinner onto one glyph, so this
   surface borrows it rather than minting a second. `.is-running` already carries
   the accent + the `ycpulse` breathe (and is already frozen under reduced
   motion); all that is owed here is the mark's size and a stable button width so
   the row does not jump when the label leaves.

   Three controls can share the slot since #537 (Change folder · Refresh ·
   Disconnect), so the group WRAPS and stays right-aligned when it does — the
   alternative on a narrow card is a row that overflows it. */
.ws-conn-buttons { display: flex; align-items: center; gap: var(--space-4);
  margin-left: auto; flex: none; flex-wrap: wrap; justify-content: flex-end; }
.ws-conn-account .ws-conn-buttons button { margin-left: 0; }
.ws-conn-synced { font-variant-numeric: tabular-nums; }
.ws-conn-refresh { min-width: 6.5rem; }
.ws-conn-refresh.is-running { display: flex; justify-content: center; }
.ws-conn-refresh .yc-y { width: 1em; height: 1em; }

/* Appearance tab host (#244): the te-takeover renders in-flow inside the
 * Settings main (not absolutely over a builder body) */
.ws-takeover-host { position: relative; flex: 1; min-height: 0; }
.ws-takeover-host[hidden] { display: none; }

/* -- Canvas (#238): the Center work column. The right-side working surface is
   now the fixed Utility Rail + single-owner Utility Canvas (#413), further
   down; opening a Utility pushes this Center aside as a flex sibling. -- */
.ws-content { flex: 1; min-height: 0; display: flex; }
.ws-content[hidden] { display: none; }
.ws-work { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; }

/* The standalone /canvas pop-out window (#366/#413, ADR-0016 D5). It owns ONE
   Utility popped out of the pane and mounts the shared module full-window: no
   Workspace rail/header, .ws-canvas.solo fills the viewport. */
.ws-canvas { display: flex; flex-direction: column; min-height: 0;
  background: var(--surface-raised); }
.ws-canvas-page { height: 100vh; overflow: hidden; display: flex; }
.ws-canvas.solo { flex: 1; width: auto; min-width: 0; max-width: none; border-left: 0; }
/* the popped-out window's empty state, before the owned Utility is sent over */
.ws-canvas-empty { flex: 1; display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 10px; text-align: center; color: var(--text-muted);
  padding: 24px; }
.ws-canvas-empty h3 { margin: 0; color: var(--text); font-size: var(--fs-title);
  font-weight: var(--fw-emph); }
.ws-canvas-empty p { margin: 0; max-width: 340px; font-size: var(--fs-body-sm); line-height: 1.6; }

/* The standalone /canvas window's toolbar bar (the single owned Utility's core
   declares its controls into .ws-cbar-left). The in-page pane's open-file
   Utilities carry their own .ws-util-toolbar (#413). */
.ws-canvas-bar { display: flex; align-items: center; gap: 10px; padding: 6px 12px;
  border-bottom: 1px solid var(--border); background: var(--accent-surface); }
.ws-canvas-bar .spacer { flex: 1; }
.ws-cbar-dl { padding: 4px 10px; }
.ws-cbar-icon { border: 0; background: none; color: var(--text-muted); font-size: 20px; line-height: 1;
  padding: 2px 6px; border-radius: 8px; cursor: pointer; }
.ws-cbar-icon:hover { color: var(--text); background: color-mix(in srgb, var(--accent) 16%, transparent); }
.ws-cbar-icon.danger-hover:hover { color: var(--danger); background: var(--danger-surface); }
.ws-canvas-body { flex: 1; min-height: 0; overflow: auto; padding: 16px 22px; }
.ws-canvas-body textarea { width: 100%; height: 100%; resize: none; border: 1px solid var(--border);
  border-radius: var(--radius-sm); background: var(--surface-raised); color: var(--text);
  font-family: var(--mono); font-size: var(--fs-body-sm); padding: 10px 12px; }
.ws-canvas-body textarea:focus { outline: none; border-color: var(--focus-ring); }

/* Canvas framework (#362, PRD #352) — the core-declared toolbar controls (a
   segmented toggle + an active toolbar-button read) + the fallback core's calm
   empty state. The flexible bar slot is display:contents so a core's declared
   controls lay out as direct children of the toolbar bar. */
.ws-cbar-left { display: contents; }
.ws-cbar-dl.active { background: var(--accent-surface); color: var(--accent-text);
  border-color: var(--accent); }
/* Shared segmented control — mirrors the Admin Appearance Light/Dark selector
   (.dm-group/.dm-opt): a solid raised track, a strong-fill selected segment, and
   hover + focus-visible states, sized down for the compact Workspace utility
   context. Notifications is its consumer, on the accent family (#658 swap);
   Tasks builds its own neutral .ws-tasks-tabs instead (see workspace-tasks).
   Keep the :hover rule before the .active rule so a hovered
   selected segment keeps its fill (equal specificity → source order wins). */
.ws-cbar-seg { display: inline-flex; border: 1px solid var(--border);
  border-radius: var(--radius-sm); overflow: hidden; }
.ws-cbar-seg button { border: 0; border-radius: 0; background: var(--surface-raised);
  color: var(--text-muted); font-size: var(--fs-body-sm); padding: 3px 11px;
  cursor: pointer; line-height: 1.6;
  transition: background var(--motion-fast), color var(--motion-fast); }
.ws-cbar-seg button + button { border-left: 1px solid var(--border); }
.ws-cbar-seg button:hover { background: var(--surface); color: var(--text); }
.ws-cbar-seg button.active { background: var(--primary); color: var(--on-primary);
  font-weight: var(--fw-emph); }
.ws-cbar-seg button:focus-visible { outline: 2px solid var(--focus-ring);
  outline-offset: -2px; }
.ws-core-empty { height: 100%; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 14px; text-align: center;
  color: var(--text-muted); padding: 24px; }
.ws-core-empty .ce-ico { color: var(--border-strong); }
/* Kept after DR37: canvas-shell.js hand-inlines this 16x16 mark (no .ico). */
.ws-core-empty .ce-ico svg { display: block; }
.ws-core-empty h3 { margin: 0; color: var(--text); font-size: var(--fs-body);
  font-weight: var(--fw-emph); }
.ws-core-empty p { margin: 0; max-width: 320px; font-size: var(--fs-body-sm);
  line-height: 1.6; }
.ws-core-empty code { font-family: var(--mono); color: var(--accent-text); }

/* File Explorer / Folder core (#365, ADR-0016 D8) — a CRUD menu bar over a
   ROOMY nested tree. The .tree/.node/.list-row/.tree-name primitives are reused
   verbatim; only the menu bar, the roomy sizing, and the expand/collapse caret
   are net-new (flagged for /design-review). */
.ws-fx { display: flex; flex-direction: column; height: 100%; }
.ws-fx-menubar { flex: none; display: flex; gap: var(--space-2); flex-wrap: wrap;
  padding: 2px 2px 10px; border-bottom: 1px solid var(--border); margin-bottom: 6px; }
.ws-fx-menubar button { display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--fs-body-sm); padding: 5px 10px; border: 1px solid transparent;
  border-radius: var(--radius-sm); background: none; color: var(--accent-text);
  cursor: pointer; line-height: 1; }
.ws-fx-menubar button:hover { background: var(--accent-surface); border-color: var(--accent); }
.ws-fx-menubar button:disabled { color: var(--text-muted); cursor: default; }
.ws-fx-menubar button:disabled:hover { background: none; border-color: transparent; }
.ws-fx-menubar button.danger-hover:hover { color: var(--danger);
  background: var(--danger-surface); border-color: var(--danger-border); }
.ws-fx-menubar .sep { width: 1px; background: var(--border); margin: 2px 4px; }
/* Kept after DR37: FX_ICON.* are hand-inlined 16x16 marks (no .ico). */
.ws-fx-menubar svg { display: block; }
/* the roomy tree: bigger type + a hair more row height than the compact rail */
.ws-fx .tree { font-size: var(--fs-body); padding: 4px 2px; }
.ws-fx .tree .list-row { padding: 5px 8px; gap: 9px; }
.ws-fx .tree .list-row .ico { width: 20px; }
.ws-fx .caret-t { display: inline-flex; width: 14px; color: var(--text-muted);
  font-size: var(--fs-eyebrow); justify-content: center; flex: none; }
.ws-fx .caret-t.leaf { visibility: hidden; }

/* Persistent File Explorer Utility (#417, PRD #400, prototype #390 Variant B —
   locked bc12ec5). The fixed root's contents as an EXPANDABLE tree above the Utility
   Rail divider: a tools bar (Add file/folder · Rename · Move · Delete + a live
   selection count), isolated selection checkboxes, a chevron that OPENS a file
   in its own Canvas + a file-only paperclip that toggles the chat attachment,
   in File identity tokens. The synthetic root row and explanatory note stay hidden. */
.ws-fe { display: flex; flex-direction: column; height: 100%; min-height: 0; }
.ws-fe-tools { flex: none; display: flex; align-items: center; flex-wrap: wrap;
  gap: 5px; padding: 2px 2px 10px; border-bottom: 1px solid var(--border);
  margin-bottom: 6px; }
.ws-fe-tools button { display: inline-flex; align-items: center; gap: 5px;
  min-height: 31px; font-size: var(--fs-body-sm); padding: 5px 10px;
  border: 1px solid transparent; border-radius: var(--radius-sm);
  background: none; color: var(--text); cursor: pointer; line-height: 1; }
.ws-fe-tools button:hover { background: var(--surface-hover); border-color: var(--border); }
/* The icon-only and danger treatments serve BOTH file surfaces' tools bars — this
   one and the Agent/Skill Editor's `.bench-tree-tools`, which took Rename / Move /
   Delete back off the editor head on 2026-08-07 (CJ). One definition listed for
   both parents rather than a copy in each, because a copy is exactly where the two
   bars would start diverging. The parent has to be named on each side: a bare
   class (0,1,0) loses to `.bench-tree-tools button` (0,1,1) on padding and to its
   `:hover` (0,2,1) on the danger background. Extracting one base rule for the
   whole bar is backlog DR18's job, not this edit's. */
.ws-fe-tools .ws-fe-tool-icon, .bench-tree-tools .ws-fe-tool-icon {
  aspect-ratio: 1; padding: 0; justify-content: center; }
.ws-fe-tools .ws-fe-del, .bench-tree-tools .ws-fe-del { color: var(--danger);
  border-color: var(--danger-border); }
.ws-fe-tools .ws-fe-del:hover:not(:disabled),
.bench-tree-tools .ws-fe-del:hover:not(:disabled) {
  background: var(--danger-surface); border-color: var(--danger); }
.ws-fe-count { margin-left: auto; color: var(--text-muted); white-space: nowrap;
  font-family: var(--mono); font-size: var(--fs-meta); }

.ws-fe-tree { flex: 1; min-height: 0; overflow: auto; padding: var(--space-3); }
.ws-fe-row { display: flex; align-items: center; gap: 5px; min-height: 34px;
  padding: 2px 5px; padding-left: calc(var(--space-2) + var(--fe-depth, 0) * 18px);
  color: var(--text-muted); border: 1px solid transparent;
  border-radius: var(--radius-sm); }
.ws-fe-row:hover { color: var(--text); background: var(--surface-hover); }
.ws-fe-row.selected, .ws-fe-row.checked { color: var(--file-text);
  background: var(--file-surface);
  border-color: color-mix(in srgb, var(--file) 40%, var(--border)); }
.ws-fe-check { width: 15px; height: 15px; margin: 0; flex: none;
  accent-color: var(--file); cursor: pointer; }
.ws-fe-check:disabled { cursor: default; }
.ws-fe-main { flex: 1; min-width: 0; min-height: 29px; display: flex;
  align-items: center; gap: 7px; padding: 3px 2px; text-align: left;
  color: inherit; background: none; border: 0; cursor: pointer; }
.ws-fe-main:hover { background: none; border-color: transparent; }
.ws-fe-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }
.ws-fe-caret { display: inline-flex; width: 14px; flex: none; color: var(--text-muted);
  transition: transform var(--motion-fast, .12s ease); }
.ws-fe-caret.collapsed { transform: rotate(-90deg); }
.ws-fe-main .ico { width: 18px; flex: none; }
.ws-fe-fileacts { display: flex; align-items: center; gap: 2px; flex: none; }
.ws-fe-attach, .ws-fe-open { width: 27px; height: 27px; flex: none; display: grid;
  place-items: center; padding: 0; color: var(--text-muted); background: none;
  border: 1px solid transparent; border-radius: var(--radius-sm); cursor: pointer;
  opacity: .72; }
.ws-fe-row:hover .ws-fe-attach, .ws-fe-row:hover .ws-fe-open,
.ws-fe-attach:focus-visible, .ws-fe-open:focus-visible { opacity: 1; }
.ws-fe-attach:hover, .ws-fe-open:hover { color: var(--file-text);
  background: var(--file-surface);
  border-color: color-mix(in srgb, var(--file) 40%, var(--border)); }
/* the checked File-blue attached state — a filled clip + a corner check badge */
.ws-fe-attach.attached { color: var(--file-text); opacity: 1;
  background: var(--file-surface);
  border-color: color-mix(in srgb, var(--file) 45%, var(--border)); position: relative; }
.ws-fe-attach.attached::after { content: "✓"; position: absolute; right: -3px;
  bottom: -3px; width: 12px; height: 12px; display: grid; place-items: center;
  color: var(--on-primary); background: var(--file); border: 2px solid var(--surface-raised);
  border-radius: 50%; font: 700 7px/1 system-ui, sans-serif; }
.ws-fe-empty { padding: 10px 8px; color: var(--text-muted); font-size: var(--fs-meta); }

/* Connected sources in the Explorer (#538). A mounted Connection root's
   needs-attention badge: the bound folder went wrong upstream and the rows below it
   are the last ones we pulled. WARNING tokens, not danger — nothing is broken and
   nothing is lost, there is something to fix, and the fix is on the Connection card. */
.ws-fe-badge { flex: none; margin-left: var(--space-3); padding: 1px 7px;
  font-size: var(--fs-eyebrow); font-weight: var(--fw-emph); line-height: 1.6;
  color: var(--warning); background: var(--warning-surface);
  border: 1px solid var(--warning-border);
  border-radius: var(--radius-pill); white-space: nowrap; }
/* The PER-ROW write-failure state: a connected write goes provider-first and can
   fail, with the mirror untouched — so the row still shows the old value and has to
   say why. The row tints, and the reason sits directly under it, where a long
   provider message wraps instead of squeezing the name out of its own row. */
.ws-fe-row.failed { color: var(--danger); background: var(--danger-surface);
  border-color: var(--danger-border); }
/* …and it is still an ORDINARY ROW. Hover and the selected/checked treatment are
   re-declared over the failed tint rather than being buried by it — the failed rule
   sits after both and would otherwise win at equal specificity. A row you cannot see
   yourself hovering, or cannot see is selected, reads as disabled; a failed row is
   exactly the one somebody is about to select and retry, and the retry is what clears
   the state. The danger text colour carries through all three. */
.ws-fe-row.failed:hover {
  background: color-mix(in srgb, var(--danger-surface) 60%, var(--surface-hover)); }
.ws-fe-row.failed.selected, .ws-fe-row.failed.checked {
  background: color-mix(in srgb, var(--danger-surface) 50%, var(--file-surface));
  border-color: color-mix(in srgb, var(--file) 40%, var(--border)); }
.ws-fe-failure { margin: 0 var(--space-2) var(--space-2);
  padding: var(--space-2) calc(var(--space-2) + 20px);
  color: var(--danger); font-size: var(--fs-meta); line-height: 1.45; }

/* The approved dark-blue/white File "Open" action (#390 AC6): a filled
   File-strong control opening a file artifact into its Canvas. Reusable.
   On-color follows the identity-strong precedent (--on-primary over the
   --agent-strong/--skill-strong step fills; --file-strong is white-bearing
   AA in both modes per tokens.md). */
.ws-file-open { display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 12px; min-height: 32px; color: var(--on-primary); background: var(--file-strong);
  border: 1px solid var(--file-strong); border-radius: var(--radius-sm);
  font-weight: var(--fw-bold); cursor: pointer; }
.ws-file-open:hover { color: var(--on-primary);
  background: color-mix(in srgb, var(--file-strong) 88%, var(--text));
  border-color: var(--file-strong); }

/* Code / IDE core (#367, ADR-0016 D3/D8) — ONE core parameterized by language.
   A line-number gutter beside a syntax-highlighted backdrop (.ws-code-src, the
   .tok-* spans) UNDER a transparent editing textarea (.ws-code-input): the
   overlay pattern (cf. highlight-overlay.js), so one surface both shows and edits
   the source. The backdrop + textarea MUST share an identical box model
   (font/padding/line-height/tab-size/white-space) so glyphs align; the textarea
   owns scrolling and JS mirrors it onto the gutter + backdrop. Net-new chrome +
   the --code-* palette are FLAGGED for /design-review. */
/* code-token palette — SCOPED to the Code core (not the global theme blocks: it
   is not a themeable palette token, like the scale tokens). FLAGGED for
   /design-review: a highlighter needs >1 hue, which strains the one-accent /
   status-only rule. Low-chroma + PROVISIONAL. kw/com/punc ride existing theme
   tokens so they adapt to dark for free; only the 3 literal hues get a dark
   override below. */
.ws-code { --code-kw: var(--accent-text); --code-str: #2e7d57; --code-num: #9a5b00;
  --code-fn: #6b4bd6; --code-com: var(--text-muted); --code-punc: var(--text-muted); }
html[data-theme="dark"] .ws-code { --code-str: #6fce9f; --code-num: #d8a35e;
  --code-fn: #b79cf0; }
.ws-code { display: flex; height: 100%; font-family: var(--mono);
  font-size: var(--fs-body-sm); line-height: 1.7; background: var(--surface-raised);
  border: 1px solid var(--border); border-radius: var(--radius-sm); overflow: hidden; }
.ws-code-gutter { flex: none; padding: 12px 12px 12px 16px; text-align: right;
  color: var(--text-muted); background: var(--surface);
  border-right: 1px solid var(--border); user-select: none; white-space: pre;
  overflow: hidden; }
.ws-code-body { position: relative; flex: 1; min-width: 0; }
.ws-code-src, .ws-code-input { margin: 0; padding: 12px 18px; border: 0;
  font-family: var(--mono); font-size: var(--fs-body-sm); line-height: 1.7;
  tab-size: 2; white-space: pre; }
.ws-code-src { position: absolute; inset: 0; color: var(--text);
  pointer-events: none; overflow: hidden; }
.ws-code-input { position: absolute; inset: 0; width: 100%; height: 100%;
  background: transparent; color: transparent; caret-color: var(--text);
  border-radius: 0; resize: none; overflow: auto; }
/* the input is a transparent overlay — show focus on the wrapper's hairline
   (matching .ws-canvas-body textarea:focus) instead of a ring (#352 a11y). */
.ws-code-input:focus { outline: none; }
.ws-code:focus-within { border-color: var(--accent); }
.ws-code .tok-kw { color: var(--code-kw); }
.ws-code .tok-str { color: var(--code-str); }
.ws-code .tok-num { color: var(--code-num); }
.ws-code .tok-fn { color: var(--code-fn); }
.ws-code .tok-com { color: var(--code-com); font-style: italic; }
.ws-code .tok-punc { color: var(--code-punc); }

/* Language picker (#367) — a compact native <select> in the Code core's toolbar
   slot. A styled native control (accessible + keyboard-navigable, no custom
   dropdown JS); own-the-UI means no reskinned product, not no-native-elements. */
.ws-cbar-select { font-size: var(--fs-body-sm); color: var(--text);
  background: var(--surface-raised); border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm); padding: 3px 8px; cursor: pointer; }

/* Toolbar tag (#368) — a non-interactive status pill (cloned from .soon) a core
   declares into the flexible bar: the HTML core's "Sandboxed" indicator makes the
   security guarantee visible. Net-new chrome, FLAGGED for /design-review. */
.ws-cbar-tag { display: inline-flex; align-items: center; gap: 5px;
  font-size: var(--fs-meta); color: var(--text-muted);
  background: var(--surface-hover); border-radius: var(--radius-pill);
  padding: 3px 10px; }
/* success-tinted variant — the ratified --success-surface soft wash (#421 per
   Decision 75's rule-of-two; this pill was the first consumer, DR19). */
.ws-cbar-tag.ok { color: var(--success);
  background: var(--success-surface); }
/* Kept after DR37: the tag's glyph is caller-supplied markup, not the registry. */
.ws-cbar-tag svg { display: block; }

/* HTML / dashboard core (#368, ADR-0016 D6) — the sandbox frame around the
   iframe. .ws-html-frame IS the sandboxed <iframe> (sandbox="allow-scripts", no
   same-origin, strict CSP); it fills the core and carries the framed look. The
   generated page brings its own styling — the platform theme does not cross the
   sandbox boundary. Net-new chrome, FLAGGED for /design-review. */
.ws-html-core { height: 100%; display: flex; flex-direction: column; }
.ws-html-frame { flex: 1; min-height: 0; width: 100%; display: block;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface-raised); }

/* PDF core (#371, ADR-0016 D8) — VIEWER ONLY. The browser's own native PDF
   viewer renders the file: .ws-pdf-frame IS the <iframe> pointed at the inline
   byte seam (/api/workspace/file/pdf), so the reading chrome — page-nav, zoom —
   is the browser's, not ours (self-contained: no vendored viewer, no CDN).
   Mirrors the HTML core's native .ws-html-frame. Net-new chrome, FLAGGED for
   /design-review. */
.ws-pdf { height: 100%; display: flex; flex-direction: column; }
.ws-pdf-frame { flex: 1; min-height: 0; width: 100%; display: block;
  border: none; background: var(--surface); }

/* DOCX core (#369, ADR-0016 D8) — a Word document on a centered PAGE SURFACE
   with a basic-edit B/I/H group. "Basic edit" is resolved as this B/I/H group
   over a contentEditable page (round-trip-through-MD level, #369 mockup). Net-new
   chrome (.ws-doc / .ws-doc-page / .ws-cfmt), FLAGGED for /design-review. */
.ws-doc { height: 100%; overflow: auto; }
.ws-doc-page { width: 100%; background: var(--surface-raised);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 4px 14px; }
/* the page IS the edit surface — on keyboard focus swap the hairline to accent
   (the chat-input border pattern) instead of a ring, so edit mode reads (#352 a11y). */
.ws-doc-page:focus { outline: none; }
.ws-doc-page:focus-visible { border-color: var(--focus-ring); }
.ws-doc-page h1 { font-size: var(--fs-title); margin: 0 0 14px; }
.ws-doc-page h2 { font-size: var(--fs-h2); margin: 22px 0 8px; }
.ws-doc-page h3 { font-size: var(--fs-body); margin: 18px 0 6px; }
.ws-doc-page p { margin: 0 0 12px; line-height: 1.7; font-size: var(--fs-body); }
.ws-doc-page ul { margin: 0 0 12px; padding-left: 22px; }
.ws-doc-page li { margin: 2px 0; line-height: 1.7; font-size: var(--fs-body); }
/* the basic-edit formatting group shown in edit mode */
.ws-cfmt { display: inline-flex; gap: 1px; border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm); overflow: hidden; }
.ws-cfmt button { border: 0; background: none; color: var(--text); min-width: 30px;
  padding: 3px 6px; cursor: pointer; font-size: var(--fs-body-sm); }
.ws-cfmt button + button { border-left: 1px solid var(--border); }
.ws-cfmt button:hover { background: var(--surface-hover); }
.ws-cfmt button:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; }
.ws-cfmt .b { font-weight: 800; }
.ws-cfmt .i { font-style: italic; }  /* system font — the system carries no serif */

/* XLSX core (#370, ADR-0016 D8) — a read-only spreadsheet grid: a column-letter
   header row, a row-number gutter (.ws-sheet-rowh), tabular-num cells, hairline
   token borders. Both the header row and the gutter are sticky so they stay
   visible as the grid scrolls. Viewer only in v1 (no edit chrome). Net-new
   chrome (.ws-sheet*), FLAGGED for /design-review. */
.ws-sheet { height: 100%; overflow: auto; border: 1px solid var(--border);
  border-radius: var(--radius-sm); background: var(--surface-raised); }
.ws-sheet-grid { border-collapse: collapse; font-size: var(--fs-body-sm);
  font-variant-numeric: tabular-nums; }
.ws-sheet-grid th, .ws-sheet-grid td { border: 1px solid var(--border);
  padding: 5px 12px; text-align: left; white-space: nowrap; }
.ws-sheet-grid thead th, .ws-sheet-grid .ws-sheet-rowh {
  background: var(--surface); color: var(--text-muted); font-weight: 600;
  text-align: center; position: sticky; }
.ws-sheet-grid thead th { top: 0; }
.ws-sheet-grid .ws-sheet-rowh { left: 0; width: 34px; }
.ws-sheet-grid td.num { text-align: right; }
.ws-sheet-grid .corner { background: var(--surface); }

/* main column */
.ws-main { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
/* display:flex out-specifies the UA [hidden] rule — restore it so the chat
 * main and the Settings main (#243) actually swap */
.ws-main[hidden] { display: none; }
.ws-main .yc { flex: 1; min-height: 0; }
/* the chat host (#236): fills the column; its child .yc row stretches */
.ws-chat { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.ws-chat[hidden] { display: none; }
.ws-chat > div { flex: 1; min-height: 0; display: flex; }
.ws-main .yc:not(.yc-panel-open) .yc-main { max-width: 840px; width: 100%; margin: 0 auto; }
.ws-shell .topbar { display: flex; align-items: center; gap: 10px; }

/* Ordinary chat input rows stay flat. Home upgrades the same input contract to
   #388's one elevated focal composer below. */
.ws-shell .yc-inputbar { background: transparent; border-top: 0; }
.ws-focal-composer {
  width: 100%; padding: 9px; display: flex; flex-direction: column; gap: 8px;
  border: 1px solid var(--border-strong); border-radius: var(--radius-card);
}
.ws-shell .ws-focal-composer {
  background: var(--surface-raised); box-shadow: var(--shadow-focal); }
.ws-shell .ws-focal-composer .yc-input {
  width: 100%; min-height: 72px; border: 0; border-radius: var(--radius);
  padding: 8px; background: var(--surface-raised);
}
.ws-shell .ws-focal-composer .yc-input:focus { border-color: transparent; }
/* Home's footer controls are ONE size — the mic's 34px (CJ, 2026-08-06). The
   shared .yc-plus / .yc-sendstop keep their 38px in the mounted chat, where the
   short input bar pairs them with no mic. */
.ws-focal-composer .yc-plus,
.ws-focal-composer button.yc-sendstop { width: 34px; height: 34px; }
/* The attach menu opens DOWNWARD on Home. The shared `bottom: 100%` is measured
   off the .yc-inputbar containing block, which in the mounted chat is a short
   one-row bar (the menu lands right above the "+") but on Home is the whole tall
   focal card — so the menu flew ~95px above the "+" and clear of the card. On
   Home the tools ROW is the containing block and the menu drops below it; the
   chat keeps the upward popover (CJ, 2026-08-06). */
.ws-focal-composer .ws-composer-tools { position: relative; }
.ws-focal-composer .yc-plus-menu { top: 100%; bottom: auto; left: 0;
  margin-top: 6px; margin-bottom: 0; }
.ws-composer-tools {
  width: 100%; display: flex; align-items: center; gap: 8px;
  padding-top: 0; border-top: 0;
}
.ws-composer-spacer { flex: 1; }
.ws-composer-icon {
  width: 34px; height: 34px; padding: 0; display: inline-grid; place-items: center;
}
/* the mic's glyph centers via the shared icon-only-control rule near .list-row */
.ws-composer-icon.listening {
  border-color: var(--accent); background: var(--accent-surface); color: var(--accent-text);
}

/* new-chat landing */
.ws-hero { flex: 1; min-height: 0; display: flex; flex-direction: column; align-items: center;
  justify-content: flex-start; gap: 18px; padding: var(--space-9); overflow-y: auto; }
/* display:flex above out-specifies the UA [hidden] rule — restore it (the
 * .topbar[hidden] pattern) so the home hero actually hides during a chat */
.ws-hero[hidden] { display: none; }
/* Home is a two-level vertical layout: the compose cluster grows to fill and
   centers in the space above a bottom-anchored Tasks card. As height falls,
   that flexible whitespace disappears first; neither cluster nor Tasks
   compresses below its content, so .ws-hero becomes the outer scroller. */
.ws-hero-inner { --ws-home-focal-width: 720px;
  width: 100%; max-width: 850px; min-height: 100%; flex: none;
  display: flex; flex-direction: column; gap: var(--space-8); }
.ws-hero-compose { width: 100%; max-width: var(--ws-home-focal-width); margin: 0 auto;
  flex: 1 0 auto; display: flex; flex-direction: column;
  justify-content: center; gap: var(--space-7); padding-block: var(--space-8); }
/* greeting + skills hug the input from above; agents from below */
.ws-hero-top { flex: none; display: flex; flex-direction: column; gap: var(--space-7); }
/* the agent dock sits its own stacking layer so it renders on top of the focal
   composer's --shadow-focal, which spills down onto it (buttons over the shadow) */
.ws-hero-bot { flex: none; display: flex; flex-direction: column; position: relative; z-index: 1; }
.ws-greet { text-align: center; font-size: var(--fs-h1); font-weight: 600; margin: 0 0 4px; }
/* the Home center column (new-design-base / #388 wf-hero): a mono eyebrow, the
   display-size greeting question, and a sans subtitle. The builder's Describe
   screen (#723) IS this pattern, so its #ws-b-greet takes the same display
   recipe. Type via tokens only. */
.ws-hero-top .ws-eyebrow { font-family: var(--mono); letter-spacing: .1em;
  margin: 0 0 7px; text-align: center; }
.ws-hero-top .ws-greet,
#ws-b-step1 .ws-greet { font-size: var(--fs-display); font-weight: var(--fw-emph); letter-spacing: -.03em; line-height: 1.1; margin: 0 0 6px; }
.ws-greet-sub { text-align: center; font-size: var(--fs-body); color: var(--text-muted); margin: 0; }
/* The keyword stays PLAIN INLINE TEXT so it shares the sentence's baseline. As
   an inline-flex it did not: a flex container takes its baseline from its first
   item — the icon — so the icon's bottom edge landed on the paragraph baseline
   and lifted the whole word ~2px above the surrounding copy. Only the icon is
   offset now, never the text; -0.18em centers the 1em glyph on the x-height.
   (CJ, 2026-08-06 — alignment only: same glyph size, same colors.) */
.ws-kind-word { font-weight: var(--fw-bold); white-space: nowrap; }
.ws-kind-word .ws-kind-icon { display: inline-block; vertical-align: -0.18em;
  margin-right: var(--space-2); }
.ws-kind-word .ws-kind-icon svg {
  width: var(--fs-body); height: var(--fs-body); }
.ws-kind-word.chat { color: var(--accent-text); }
.ws-kind-word.agent { color: var(--agent-text); }
.ws-kind-word.skill { color: var(--skill-text); }
/* greeting keywords carry their category color + icon (#246) */
.ws-greet .gr-kw { font-weight: var(--fw-emph); }
/* In-text glyph: re-declares `display: inline` so its vertical-align still
   means something against the registry's block default (DR37). */
.ws-greet .gr-kw svg { display: inline; vertical-align: -1px; }
.ws-greet .gr-chat { color: var(--accent-text); }
.ws-greet .gr-agent { color: var(--agent-text); }
.ws-greet .gr-skill { color: var(--skill-text); }
.ws-quick { display: flex; flex-direction: column; gap: 6px; }
.ws-quick .yc-dock { padding: 0; justify-content: center; }
/* launcher docks: skills gold, agents green */
.ws-dock-skill .yc-dock-btn { background: var(--skill-surface); color: var(--skill-text); border-color: var(--skill); }
.ws-dock-agent .yc-dock-btn { background: var(--agent-surface); color: var(--agent-text); border-color: var(--agent); }
.ws-dock-skill .yc-dock-btn:hover { background: color-mix(in srgb, var(--skill) 18%, var(--skill-surface));
  border-color: var(--skill-text); }
.ws-dock-agent .yc-dock-btn:hover { background: color-mix(in srgb, var(--agent) 18%, var(--agent-surface));
  border-color: var(--agent-text); }

/* The Home Tasks card (#415 data seam · #388 wf-tasks / new-design-base): a
   flat bordered card below the Agent launchers. Head = task mark + title +
   subtitle, then the filter chips + compact Add action (rendered by card-mode
   controller); rows are the shared .ws-task-row anatomy. Full-width, capped so
   it reads as a card, not a page band. */
/* The card's four-row scroll viewport is sized off ONE row height. A v2 row is
   two lines (title over its meta pills), so the height rose with it — and it is a
   FLOOR rather than a fixed height: a long title that wraps must grow its row,
   not be clipped by the viewport's arithmetic. */
.ws-tasks-card { --ws-task-card-row-height: calc(var(--space-9) + var(--space-9)
    + var(--space-4));
  width: 100%; max-width: var(--ws-home-focal-width); margin: 0 auto; flex: none;
  border: 1px solid var(--border); border-radius: var(--radius-card);
  background: var(--surface-raised); overflow: hidden; text-align: left; }
.ws-tasks-card-head { display: flex; align-items: center; gap: 10px;
  padding: 11px 14px; border-bottom: 1px solid var(--border); }
.ws-tasks-card-ico { width: 32px; height: 32px; display: grid;
  place-items: center; flex: none; border-radius: var(--radius);
  color: var(--notification); background: var(--notification-surface); }
.ws-tasks-card-copy { min-width: 0; }
.ws-tasks-card-copy h3 { margin: 0; font-size: var(--fs-body); font-weight: var(--fw-emph); }
.ws-tasks-card-copy p { margin: 1px 0 0; color: var(--text-muted); font-size: var(--fs-meta); }
.ws-tasks-card-actions { display: flex; align-items: center; gap: 8px;
  margin-left: auto; flex: none; }
.ws-tasks-card-body { padding: 4px 6px 6px; }
.ws-tasks-card .ws-task-row { min-height: var(--ws-task-card-row-height); }
/* the card head is a narrow slot, so its tab strip sizes to its labels rather
   than stretching, and the compose row it has no room for stays absent. */
.ws-tasks-card-actions .ws-tasks-tabs { flex: none; }
.ws-tasks-card-actions .ws-tasks-tabs button { flex: none; min-height: 24px;
  font-size: var(--fs-eyebrow); }
.ws-tasks-card .ws-tasks-list {
  max-height: calc(var(--ws-task-card-row-height) + var(--ws-task-card-row-height)
    + var(--ws-task-card-row-height) + var(--ws-task-card-row-height)
    + var(--space-3) + var(--space-3) + var(--space-3));
  overflow-y: auto; overscroll-behavior: contain; }
/* card-mode compact Add beside the filter segment */
.ws-tasks-card-actions .ws-tasks-add { min-height: 28px; padding: 3px 10px; font-size: var(--fs-meta); }

/* -- agent context (#239): the strip + the green chrome shift -- */
/* the topbar's swappable context area (#249): agent name + scoping subtitle
   while in agent context, or the open takeover's title — one header, ever */
.topbar .tb-context { display: flex; align-items: center; gap: 10px; }
.topbar .tb-context[hidden] { display: none; }
.tb-context .ag-ico { width: 28px; height: 28px; border-radius: var(--radius-sm); background: var(--agent-surface);
  color: var(--agent); display: flex; align-items: center; justify-content: center; flex: none; font-size: 15px; }
.tb-context .ag-ico svg { vertical-align: -3px; }
/* The identity mark carries the CATEGORY of what the header is about (#682).
   Green is the default because two of its three cases are an Agent — being
   inside one, and building one; a Personal SKILL build is the gold case, and
   workspace.js swaps the glyph (SPARK → BOLT) with this class. */
.tb-context.ws-cat-skill .ag-ico { background: var(--skill-surface); color: var(--skill); }
.tb-context .ag-nm { font-weight: 600; }
.tb-context .ag-sub { color: var(--text-muted); font-size: var(--fs-meta); }
.topbar .tb-title { font-weight: var(--fw-emph); font-size: 16px; }
/* The Workspace-surface header greeting (#398/new-design-base): a bold
   time-of-day line over a muted subtitle, top-left in the header row. Owns the
   header on Home; hidden on the Settings surface (the plain badge shows there)
   and while an Agent context / Browse takeover own the header. */
.ws-tb-greet { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ws-tb-greet[hidden] { display: none; }
.ws-tb-greet > strong { font-size: var(--fs-h1); font-weight: var(--fw-emph);
  letter-spacing: -.02em; line-height: 1.1; }
.ws-tb-greet .ws-tb-greet-sub { color: var(--text-muted); font-size: var(--fs-body-sm); }
/* Inside an AGENT context the chat shifts purple → green: user bubble +
 * in-chat icons + Send (the flagged color-tenet override — the mechanism
 * ships here; ratification is /design-review's). */
/* Scoped under .app-content (#258). A Co-Pilot is a platform conversation,
   always purple — the builder's only green chat is the agent test chat (its own
   .ws-cat-agent rule below). Since #670/#682 BOTH personal builders render
   INSIDE .app-content, so the scoping is no longer what holds them apart:
   openBuilder leaves the agent context on the way in — you cannot be in an
   Agent and building one at the same time — and that is what keeps a builder's
   Co-Pilot purple. State, not DOM position. */
.ws-in-agent .app-content .yc-msg.user .yc-bubble { background: var(--agent-strong); border-color: var(--agent-strong); }
.ws-in-agent .app-content .yc-artifact-ico { color: var(--agent); }
/* in-chat interactive accents follow the context too (#254): the agent
   first-interaction form's Submit goes green inside agent context */
.ws-in-agent .app-content .yc-form-actions .primary { background: var(--agent-strong); border-color: var(--agent-strong); }
.ws-in-agent .app-content .yc-form-actions .primary:hover { background: color-mix(in srgb, var(--agent-strong) 86%, black); border-color: color-mix(in srgb, var(--agent-strong) 86%, black); }
.ws-shell button.ws-send-agent {
  --yc-sendstop-fill: var(--agent-strong);
  --yc-sendstop-fill-hover: color-mix(in srgb, var(--agent-strong) 86%, black);
}
/* the agent builder's test chat is an agent conversation (#254): its user
   bubbles go green; the skill builder's test chat stays a normal session */
.ws-cat-agent #ws-b-test .yc-msg.user .yc-bubble { background: var(--agent-strong); border-color: var(--agent-strong); }

/* -- browse takeover (#239): Build CTA · search · sections · Pin --
 * (its title lives in the ONE topbar since #249 — no takeover head) */
.ws-pin { border: 1px solid var(--border); background: var(--surface-raised); border-radius: var(--radius-sm);
  cursor: pointer; color: var(--text-muted); font-size: 13px; padding: 3px 8px; }
.ws-pin:hover { border-color: var(--border-strong); color: var(--text); }
.ws-pin.pinned { background: var(--accent-surface); border-color: var(--accent); color: var(--accent-text); }
/* Inline mutation-failure notice (#282): rendered by mutate()/showActionError
 * right after the triggering control — a failed create/save/pin/delete must
 * never read as a dead button. */
.ws-action-err {
  color: var(--danger); background: var(--danger-surface);
  border: 1px solid var(--danger-border); border-radius: var(--radius-sm);
  font-size: var(--fs-meta); padding: var(--space-2) var(--space-4);
  margin-top: var(--space-2); max-width: 100%;
}
.agent-card .card-dia { color: var(--agent); display: inline-flex; align-items: center; }
.agent-card .card-dia.skill { color: var(--skill); }
/* personal items fill the category icon (the .sb-ico.filled twin, #262) */
.card-dia.filled svg { fill: currentColor; }
.ws-shell .card-dia svg { vertical-align: -3px; }
.build-card { flex-direction: row; align-items: center; gap: 16px; background: var(--agent-surface);
  border-color: var(--agent); margin-bottom: 18px; }
.build-card.skill { background: var(--skill-surface); border-color: var(--skill); }
.build-card .grow { flex: 1; }
.build-card .card-actions { flex: none; }
.ws-search { position: relative; max-width: 440px; margin: 0 0 22px; }
.ws-search .mag { position: absolute; left: 11px; top: 50%; transform: translateY(-50%);
  color: var(--text-muted); display: flex; pointer-events: none; }
.ws-search input[type=search] { width: 100%; padding-left: 32px; }
.section-title { font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
  color: var(--text-muted); margin: 0 0 12px; }
.section-title + .card-grid { margin-bottom: 26px; }
/* Browse: primary action + pin toggle carry the category color; the header
 * New chat stays purple (scoped to .takeover-body). AA note: white on solid
 * gold is borderline — flagged for /design-review. */
.ws-cat-agent .takeover-body .card-actions .primary, .ws-cat-agent .build-card .primary { background: var(--agent-strong); border-color: var(--agent-strong); }
.ws-cat-agent .takeover-body .card-actions .primary:hover, .ws-cat-agent .build-card .primary:hover { background: color-mix(in srgb, var(--agent-strong) 86%, black); border-color: color-mix(in srgb, var(--agent-strong) 86%, black); }
.ws-cat-agent .ws-pin { background: var(--agent-surface); border-color: var(--agent); color: var(--agent-text); }
.ws-cat-skill .takeover-body .card-actions .primary, .ws-cat-skill .build-card .primary { background: var(--skill-strong); border-color: var(--skill-strong); }
.ws-cat-skill .takeover-body .card-actions .primary:hover, .ws-cat-skill .build-card .primary:hover { background: color-mix(in srgb, var(--skill-strong) 86%, black); border-color: color-mix(in srgb, var(--skill-strong) 86%, black); }
.ws-cat-skill .ws-pin { background: var(--skill-surface); border-color: var(--skill); color: var(--skill-text); }
/* the browse takeover rides the ws-takeover-host in-flow */
.ws-browse { display: flex; flex-direction: column; min-height: 0; height: 100%; background: var(--surface); }
.ws-browse[hidden] { display: none; }
.ws-browse .takeover-body { flex: 1; overflow: auto; padding: 22px 28px; }


/* -- the guided PERSONAL builder (#241), RE-HOMED into the Workspace shell for
 * BOTH personal kinds (#670 Agents, #682 Skills; specs #668/#680) --
 * 1 Describe (Co-Pilot only, centered) → 2 Test & tune (test chat ⅔ | model
 * selector + New chat over a small Co-Pilot) → 3 Save/Request-to-publish.
 * agent=green / skill=gold on CTAs + step markers (the category tokens).
 *
 * Approved prototype `builder-rehomed.html`, variant A (#666), and its focal
 * composition is what these rules reproduce through the tokens: the
 * builder is not a frame but the shell's PADDED FIELD in the Center — 14px of
 * padding, 8px gaps, every region its own 10px rounded card on it (the
 * prototype's `.field` = --space-7 / --space-4 / --radius-md, and its
 * --bg/--surface pair = our --surface/--surface-raised). It was a fixed
 * full-screen overlay for as long as the SKILL kind still wore the wizard
 * strip; #682 re-homed that kind too, and the overlay went with the strip. */
.ws-build { height: 100%; display: flex; flex-direction: column;
  padding: var(--space-7); gap: var(--space-4); background: var(--surface); }
.ws-build[hidden] { display: none; }

.ws-b-left { display: flex; align-items: center; gap: 12px; min-width: 0; }
/* The band's far-left slot is the working-mode group's and stands EMPTY until
   Editor mode fills it — and an empty flex child still costs the band one 8px
   gap, which slides the centered indicator half of that off center. The band
   hides it while it holds nothing (its own `display: flex` would otherwise beat
   the UA sheet's [hidden]). */
.ws-b-left[hidden] { display: none; }
.ws-b-right { display: flex; align-items: center; gap: 8px; }
/* The head's OWN controls — Cancel and Back. The exclusion is
   the working-mode group's segments (#672): they are a design-system component
   nested in this head, and this rule (0,2,1) outranks `.bench-mode-group button`
   (0,1,1), so without it the band would re-derive a segmented control into five
   bordered chips. Excluding them from the match is what leaves the ONE shared
   segmented-toggle rule set in charge. */
.ws-build-head button:not(.primary, .bench-mode-group button) { border: 1px solid var(--border); background: var(--surface-raised);
  color: var(--text); border-radius: var(--radius-sm); padding: 5px 11px; font-size: var(--fs-body-sm); cursor: pointer; white-space: nowrap; }
/* `:not(:disabled)` — the same guard `.bench-tree-tools button:hover` carries,
   and for the same reason: the band renders Back DISABLED on step 1 (#671)
   rather than hiding it, and a dead control must neither keep the pointer
   cursor nor light up under it. The dim itself is the base `button:disabled`
   rule, nothing bespoke; the resting fill and border are restated here only
   because the local `background`/`border` above outrank the global
   `button:hover` and would otherwise decide what a hover does. */
.ws-build-head button:not(.primary, .bench-mode-group button):hover:not(:disabled) { border-color: var(--border-strong); }
.ws-build-head button:not(.primary, .bench-mode-group button):disabled,
.ws-build-head button:not(.primary, .bench-mode-group button):disabled:hover {
  cursor: default; border-color: var(--border); background: var(--surface-raised); }
/* The category mark on the Save step's summary card — the one place the builder
   still paints an artifact glyph of its own (the band names nothing, and the
   top bar's mark is the header's). */
.ws-cat-agent .bt-ico { background: var(--agent-surface); color: var(--agent); }
.ws-cat-skill .bt-ico { background: var(--skill-surface); color: var(--skill); }
/* ONLY explicit CTAs carry the category color — in-chat Send stays context-driven. */
.ws-cat-agent .ws-b-cta { background: var(--agent-strong); border-color: var(--agent-strong); }
.ws-cat-agent .ws-b-cta:hover { background: color-mix(in srgb, var(--agent-strong) 86%, black); border-color: color-mix(in srgb, var(--agent-strong) 86%, black); }
.ws-cat-skill .ws-b-cta { background: var(--skill-strong); border-color: var(--skill-strong); }
.ws-cat-skill .ws-b-cta:hover { background: color-mix(in srgb, var(--skill-strong) 86%, black); border-color: color-mix(in srgb, var(--skill-strong) 86%, black); }

.ws-build-body { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.ws-build-body > [hidden] { display: none; }
.ws-build .pane-head .hint,
.ws-build .bench-panel-head .hint { color: var(--text-muted); font-size: var(--fs-meta); font-weight: 400; margin-left: 8px; }
.ws-build .yc { flex: 1; min-height: 0; }

/* The Describe screen (#250; reworked #723) — the Home pattern on the
   Home-grey field: display greeting + subtext over the one raised composer
   card, no card chrome anywhere else; the transcript stays hidden (Send
   advances to the working screen where the same conversation continues in the
   side pane). The cluster anchors ~1/3 from the top — the two flexible
   spacers split the free height 1:2, so the position scales with the window
   instead of using a fixed offset. */
.ws-b-solo { flex: 1; min-height: 0; display: flex; flex-direction: column; align-items: center; padding: var(--space-3); background: var(--surface); }
.ws-b-solo::before { content: ""; flex: 1; }
.ws-b-solo::after { content: ""; flex: 2; }
.ws-b-hero { width: 100%; max-width: 720px; flex: none; display: flex; flex-direction: column; gap: 14px; }
#ws-b-step1 .yc-messages, #ws-b-step1 .yc-dock { display: none; }
/* The Describe composer is the focal-composer anatomy (#723) over the ONE
   Co-Pilot widget's own input bar: the tall input across the top, then
   Add files at left and microphone · Send at right on the row beneath. Grid,
   because the bar's children are the widget's — rearranged, not re-authored. */
#ws-b-step1 .yc-inputbar {
  display: grid; grid-template-columns: auto 1fr auto auto; gap: 8px;
  padding: 9px; border: 1px solid var(--border-strong);
  border-radius: var(--radius-card);
  background: var(--surface-raised); box-shadow: var(--shadow-focal);
}
#ws-b-step1 .yc-inputbar .yc-input {
  grid-column: 1 / -1; min-height: 132px;
  border: 0; border-radius: var(--radius); background: var(--surface-raised);
}
#ws-b-step1 .yc-inputbar .yc-input:focus { border-color: transparent; }
/* the Home footer-control size — ONE size for all three (CJ, 2026-08-06) */
#ws-b-step1 .yc-inputbar .yc-plus { grid-row: 2; grid-column: 1; width: 34px; height: 34px; }
#ws-b-step1 .yc-inputbar .ws-b-mic { grid-row: 2; grid-column: 3; }
#ws-b-step1 .yc-inputbar .yc-sendstop { grid-row: 2; grid-column: 4; width: 34px; height: 34px; }
/* The mic rides inside the widget's bar, so it travels to the working screen
   with it — where it does not belong. The Describe screen is the ONLY place
   it renders. */
.ws-b-mic { display: none; }
#ws-b-step1 .yc-inputbar .ws-b-mic { display: inline-grid; }

/* step 2 — test chat | grab strip | Co-Pilot.
   A GRID since #739, in the bench's own shape rather than a flex pair: the middle
   column IS the grab strip, so the Co-Pilot's width is a var a resizer writes
   instead of a flex ratio nothing can move. The var is shared with the Editor
   mode's Co-Pilot (`--ws-b-cop-w`, written on #ws-b-step2 by workspace.js) —
   that sharing is the whole of "it keeps its width across a mode switch". */
.ws-b-tt { flex: 1; min-height: 0; display: grid; padding: var(--space-3); background: var(--surface);
  grid-template-columns: minmax(0, 1fr) var(--space-4) var(--ws-b-cop-w); }
.ws-b-tt > .test-pane { min-width: 0; border: 1px solid var(--border); border-radius: var(--radius-md); overflow: hidden; display: flex; flex-direction: column; background: var(--surface-raised); }
.ws-b-tt > .side-copilot { min-width: 0; min-height: 0; border: 1px solid var(--border); border-radius: var(--radius-md); overflow: hidden; display: flex; flex-direction: column; background: var(--surface-raised); }
/* the widget hosts stretch their pane (#250) so .yc-messages (flex:1) keeps
   the input bar pinned to the pane bottom, transcript scrolling above */
#ws-b-copilot1 { display: flex; flex-direction: column; }
#ws-b-copilot2, #ws-b-copilot3, #ws-b-test { flex: 1; min-height: 0; display: flex; flex-direction: column; }
/* `.ws-b-testbar` / `.ws-b-mlbl` were here: the bar above the Test screen's
   Co-Pilot that carried a Model selector and New chat. Both went in #739 — New
   chat into the Co-Pilot's own head (where the Admin Agent Editor has always kept
   it), and the selector away entirely, so a test runs the model the agent is
   configured with. */
/* Both of the Test screen's heads push their right-hand controls out: the test
   chat's (New chat + the stale notice) and the Co-Pilot's (New chat, #739). */
.ws-b-tt .pane-head .spacer { flex: 1; }
.ws-b-newchat { border: 1px solid var(--border); background: var(--surface-raised); color: var(--text);
  border-radius: var(--radius-sm); padding: 5px 10px; font-size: var(--fs-body-sm); cursor: pointer; white-space: nowrap; }
.ws-b-newchat:hover { border-color: var(--border-strong); }
.ws-b-refresh-needed[hidden], .ws-b-test-newchat[hidden] { display: none; }
/* The test chat's New chat is the ARTIFACT's reset and takes the open kind's
   category fill, so it reads as the matched pair of the Co-Pilot's violet one
   below rather than a different sort of control (Decision 95). Both kinds show
   it since #680 §14 — the gold twin landed with the kind, not after it. */
.ws-cat-agent .ws-b-test-newchat { background: var(--agent-strong); border-color: var(--agent-strong); color: var(--on-primary); }
.ws-cat-agent .ws-b-test-newchat:hover { background: color-mix(in srgb, var(--agent-strong) 86%, var(--text)); border-color: color-mix(in srgb, var(--agent-strong) 86%, var(--text)); }
.ws-cat-skill .ws-b-test-newchat { background: var(--skill-strong); border-color: var(--skill-strong); color: var(--on-primary); }
.ws-cat-skill .ws-b-test-newchat:hover { background: color-mix(in srgb, var(--skill-strong) 86%, var(--text)); border-color: color-mix(in srgb, var(--skill-strong) 86%, var(--text)); }
.ws-b-copilot-newchat { background: var(--primary); border-color: var(--primary); color: var(--on-primary); }
.ws-b-copilot-newchat:hover { background: var(--primary-hover); border-color: var(--primary-hover); }

/* step 3 — save / request-to-publish */
.ws-b-scroll { flex: 1; min-height: 0; overflow: auto; padding: 26px 32px; }
.ws-b-scroll h1 { font-size: var(--fs-h1); margin: 0 0 4px; }
.ws-b-scroll .sub { color: var(--text-muted); margin: 0 0 20px; font-size: var(--fs-body-sm); }
.ws-b-summary { display: flex; align-items: center; gap: 14px; padding: 16px; margin: 0 0 18px;
  border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--surface-raised); }
.ws-b-summary .grow { flex: 1; min-width: 0; }
.ws-b-summary .name { font-weight: 600; }
/* the Save step's final name is an editable field (#251) */
.ws-b-summary input.name { font: inherit; font-weight: 600; width: 100%; max-width: 320px;
  border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 3px 8px;
  background: var(--surface); color: var(--text); margin: 0 0 2px; }
.ws-b-summary .meta { color: var(--text-muted); font-size: var(--fs-body-sm); }
.ws-b-actions { display: flex; align-items: center; gap: 10px; margin: 0 0 4px; flex-wrap: wrap; }
.ws-b-btn2 { border: 1px solid var(--border); background: var(--surface-raised); color: var(--text);
  border-radius: var(--radius-sm); padding: 8px 14px; font-size: var(--fs-body-sm); cursor: pointer; }
.ws-b-btn2:hover { border-color: var(--border-strong); }
.ws-b-btn2:disabled { opacity: var(--opacity-disabled); cursor: default; }
.ws-b-note { display: block; color: var(--text-muted); font-size: var(--fs-meta); margin-top: 10px; max-width: 640px; }

/* -- the ONE command bar, the band (#671) --
   Its card is `.bench-command`'s (the head wears that class, authored), so
   nothing about the card is restated here. What follows is only what the band's
   OWN anatomy adds on top of it, near-literally from the approved prototype's
   `.band` through the tokens:

     [ working-mode slot ] ··· 1 Describe · 2 Test & tune · 3 Save ··· Cancel · Back · CTA

   The far-left slot is `.ws-b-left`, which holds the working-mode group on
   step 2 and is hidden while it holds nothing — the two `.spacer` gaps around
   the indicator are what center it between the groups, and they are
   `.bench-command`'s too. */

/* Cancel is the band's QUIET control (the prototype's `.quiet`): no border, no
   fill, a muted label. It is the way out, not a third choice offered beside
   Back and the primary. */
.ws-build-head.bench-command button.ws-b-quiet {
  border-color: transparent; background: transparent; color: var(--text-muted);
}
.ws-build-head.bench-command button.ws-b-quiet:hover:not(:disabled) {
  border-color: transparent; background: var(--surface-hover); color: var(--text);
}

/* The Describe screen is not a step (#723): the band stands down to a bare
   row — no card, no border — so quiet Cancel floats alone on the Home-grey
   field, exactly as Home's own chrome does. */
.ws-build-head.bench-command.bare {
  background: transparent; border: 0; box-shadow: none; padding-inline: 0;
}
/* the Save screen is a single rounded card on the same field; the Describe
   screen lost its card with the step model (#723) */
.ws-b-scroll {
  background: var(--surface-raised);
  border: 1px solid var(--border); border-radius: var(--radius-md);
}
/* step 2's panels already carry the card treatment — the field owns the padding
   now, and since #739 the 8px between them is the resizer's own column rather
   than a gap, exactly as it is on the Admin bench. */
.ws-b-tt { padding: 0; background: transparent; }

/* Step 3's INTERIOR, from the prototype's `.savebody`: a centered column about
   as wide as the summary card, with the heading and the sub centered over it —
   not the wizard's left-aligned full-width form, which on a rounded card in the
   middle of a padded field reads as an unfinished page.
   The prototype's 24px side padding and 14px gap map exactly (--space-9 /
   --space-7); its 40px top has no token, so the scale's top step --space-10
   (36px) stands in. The 480px column stays a literal — it is a measure, and the
   scale holds none — shared by the card and the note as the prototype shares
   it. */
.ws-b-scroll {
  display: grid; justify-content: center; align-content: start;
  gap: var(--space-7); padding: var(--space-10) var(--space-9);
}
/* ...and a `display` of its own has to say [hidden] again, or the step body
   stays on screen through the two steps that are not it: `.ws-build-body >
   [hidden]` is an attribute selector, so it ties this rule on specificity and
   loses on order. (`.ws-b-left` carries the same guard.) */
.ws-b-scroll[hidden] { display: none; }
.ws-b-scroll h1 { text-align: center; margin: 0; }
.ws-b-scroll .sub { text-align: center; margin: 0; max-width: 480px; }
.ws-b-summary { width: 480px; max-width: 88vw; margin: 0; }
.ws-build .ws-b-actions { justify-content: center; margin: 0; }
.ws-build .ws-b-note { max-width: 480px; text-align: center; margin: 0; }

/* -- step 2's TWO working modes (#672, spec #668; #683, spec #680) --
   The ARTIFACT mode (Agent or Skill) is that kind's own step 2, untouched;
   Editor mode is the Admin bench's composition over the same files the Co-Pilot
   writes. Both personal kinds wear the pair. The step body is now the
   frame that holds one of them at a time — each already flexes to fill it, so
   this only has to be the column they fill. Its own `display` means the two
   modes need the [hidden] guard restated: a `display` on `.ws-b-tt` /
   `.bench-work` (0,1,0) beats the UA sheet, and these are not children of
   `.ws-build-body`, so its guard cannot reach them. */
.ws-b-step2 { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.ws-b-step2 > [hidden] { display: none; }

/* The two resizable widths live HERE, on the frame that holds both modes, not on
   either mode's own row (#739) — that is what lets the Co-Pilot be ONE width
   across a mode switch, and it is where workspace.js writes the Member's dragged
   value (an inline var on this element, which out-specifies both rules below).
   The resting values are the approved prototype's, and the narrow tier mirrors
   `.bench-work`'s own 1210px step for the same reason it has one. */
.ws-b-step2 { --ws-b-files-w: 232px; --ws-b-cop-w: 340px; }
@media (max-width: 1210px) {
  .ws-b-step2 { --ws-b-files-w: 190px; --ws-b-cop-w: 270px; }
}

/* The Editor bench: File Explorer | Work canvas | Co-Pilot, on the SAME padded
   field as the band. Every card is `.bench-panel` and the row is `.bench-work` —
   the design system's own, reused rather than re-derived. The ONE thing restated
   is the track, and since #739 that is only which VARS it reads: the bench's five
   columns — three panels, two draggable gutters — are now this surface's shape
   too, because a Member correcting their own agent's files is doing the same work
   an Author does and wants the same room for it.
   The vars are the BUILDER's, not `.bench-work`'s: both are declared on
   `.ws-b-step2` above (the frame that holds both modes), so `--ws-b-cop-w` is the
   same width the Test screen's Co-Pilot wears and a drag in either mode holds in
   both. */
.ws-b-ed {
  grid-template-columns:
    var(--ws-b-files-w) var(--space-4) minmax(0, 1fr)
    var(--space-4) var(--ws-b-cop-w);
}

/* The working-mode group wears the OPEN builder's own category scheme (the
   approved prototype's `.mode-group button.on { background: var(--agent) }`),
   exactly as production scopes the category: whichever mode segment is active
   is filled in that builder's color — Agent-green for an Agent, Skill-gold for
   a Skill (#683) — while the Co-Pilot beside it keeps accent purple and the
   tree keeps File-blue. Everything else about the control is the shared
   segmented toggle's. */
.ws-cat-agent .ws-build-head.bench-command .bench-mode-group button.on {
  background: var(--agent-strong); color: var(--on-primary);
}
.ws-cat-skill .ws-build-head.bench-command .bench-mode-group button.on {
  background: var(--skill-strong); color: var(--on-primary);
}
/* ---------- Connector Builder wizard (#300, PRD #295) ---------- */
/* ==========================================================================
   The in-bounds Connector Builder (#526, spec #524 §B; prototype #508
   `builder-in-bounds.html`, authoritative for this composition).

   The Builder is no longer a full-screen `.ws-build` overlay: it is a routed
   admin page inside the normal shell, with the sidebar asked down to its icon
   rail (`data-rail="icons"`, sidebar.js). Content is a **gray field of rounded
   cards with no divider rules** — nothing separates panels but the gap:

     .cb-shell            the gray field: [ .cb-lcol | .cb-cop ]
       .cb-lcol           steps card (pinned) over the scrolling .cb-body
       .cb-cop            the full-height Co-Pilot card — absent on Start,
                          where .cb-shell.solo goes full width

   The prototype's `--bg`/`--surface`/`--primary-soft`/`--radius-lg` map 1:1 onto
   `--surface`/`--surface-raised`/`--accent-surface`/`--radius-card`; its 13px
   field rhythm rides the nearest scale token (--space-7).
   ========================================================================== */

/* The page is viewport-locked (the .settings-page precedent): the left column
   scrolls INSIDE the field, so the steps card stays pinned and the Co-Pilot card
   is really full height rather than as tall as its transcript. The precedent
   includes its RELEASE — see the narrow-viewport block at the end of this
   section: a locked page whose columns stop fitting has no way to reach what
   fell below the fold. */
body.cb-page.has-sidebar { height: 100vh; overflow: hidden; }
.cb-page .app-content { height: 100vh; min-height: 0; }

/* Three tracks: the field, then the resize HANDLE — which IS the column gap
   (column-gap 0, a --space-7-wide track), so dragging is grabbing the gap and
   the field's rhythm never widens for it — then the Co-Pilot. The rail's track
   is `auto`: the card carries its own width (--cb-cop-width, written live by
   pane-resize.js setupSingle), because a var set on the card cannot reach a
   track list declared here. Row gap stays --space-7 for the narrow-viewport
   stack at the end of this section. */
.cb-shell { flex: 1; min-height: 0; background: var(--surface);
  padding: var(--space-7); display: grid; gap: var(--space-7) 0;
  grid-template-columns: minmax(0, 1fr) var(--space-7) auto; }
/* Start has no Co-Pilot (#508): the launcher's field is one column. */
.cb-shell.solo { grid-template-columns: minmax(0, 1fr); }
/* The handle wears the Workspace Canvas resizer's anatomy (.ws-canvas-resize):
   no resting grip — a 2px line down the gap's center, transparent until
   hover/focus/drag paints it the accent color. */
.cb-cop-resize { cursor: col-resize; position: relative; z-index: 3; }
.cb-cop-resize[hidden] { display: none; }
.cb-cop-resize::after { content: ""; position: absolute;
  inset: var(--space-6) auto var(--space-6) 50%; width: 2px;
  margin-left: -1px; background: transparent; }
/* the grab target is wider than the gap itself */
.cb-cop-resize::before { content: ""; position: absolute; inset: 0 -5px; }
.cb-cop-resize:hover::after, .cb-cop-resize:focus-visible::after,
.cb-cop-resize.dragging::after { background: var(--accent); }
.cb-cop-resize:focus-visible { outline: 2px solid var(--focus-ring);
  outline-offset: -2px; }
.cb-lcol { display: grid; grid-template-rows: auto minmax(0, 1fr);
  gap: var(--space-7); min-height: 0; }
.cb-body { overflow: auto; display: grid; gap: var(--space-7);
  align-content: start; min-height: 0; padding-right: var(--space-1); }
.cb-body > [hidden] { display: none; }
.cb-step { display: grid; gap: var(--space-7); align-content: start; }
.cb-step[hidden] { display: none; }

/* the card vocabulary on the gray field */
.cb-card { border: 1px solid var(--border); border-radius: var(--radius-card);
  background: var(--surface-raised); }
.cb-card-head { display: flex; align-items: center; gap: var(--space-4);
  padding: var(--space-6) var(--space-7) 0; }
.cb-card-head .title { font-weight: var(--fw-bold); font-size: var(--fs-body-sm); }
/* The Builder's muted caption, one recipe. `.cb-qscope .cb-note-text` (the
   interview's scratch-scope card, #545) joins the selector rather than
   re-declaring the same two properties a third time — the parallel-caption smell
   consolidation-backlog DR4 already tracks, not a new family. */
.cb-card-head .hint,
.cb-qscope .cb-note-text { color: var(--text-muted); font-size: var(--fs-meta); }
.cb-card-head .spacer { flex: 1; }
.cb-card-body { padding: var(--space-6) var(--space-7) var(--space-7);
  display: grid; gap: var(--space-5); }
.cb-lead { margin: 0; color: var(--text-muted); font-size: var(--fs-body-sm);
  max-width: 720px; }

/* ---- the steps card: pinned top-left, pills not a rule ----
   The step controls (Cancel / Back / Continue) share this card, right of the
   steps they move between — not in the topbar (UAT ruling, 2026-08-03). */
.cb-steps-card { padding: var(--space-4) var(--space-5); display: flex;
  align-items: center; gap: var(--space-4); flex-wrap: wrap; }
.cb-steps { display: flex; align-items: center; gap: var(--space-2);
  margin: 0; padding: 0; list-style: none; flex-wrap: wrap; flex: 1; }
.cb-nav { display: flex; align-items: center; gap: var(--space-3);
  flex: none; margin-left: auto; }
.cb-steps li { display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-3) var(--space-5); border-radius: var(--radius-pill);
  color: var(--text-muted); font-size: var(--fs-body-sm); white-space: nowrap; }
/* `display:flex` above out-specifies the UA [hidden] rule. Restore it so a
   route profile removes a step from the visible and accessible sequence. */
.cb-steps li[hidden] { display: none; }
.cb-steps li.active { background: var(--accent-surface); color: var(--accent-text);
  font-weight: var(--fw-bold); }
.cb-steps li .n { display: grid; place-items: center; width: 17px; height: 17px;
  border-radius: var(--radius-pill); border: 1px solid var(--border-strong);
  font: var(--fs-eyebrow)/1 var(--mono); flex: none; }
/* A Connector takes no identity hue: the step markers + CTA draw the platform
   Accent — `--primary`/`--on-primary`, NOT `--accent` (decisions.md #66,
   tokens.md). The two diverge in dark mode, where `--accent` is the lighter
   link/selection violet and would not carry white. */
.cb-steps li.active .n { border-color: var(--primary); background: var(--primary);
  color: var(--on-primary); }
.cb-steps li.done .n { border-color: var(--success);
  background: var(--success-surface); color: var(--success); }
/* Jump-to-any-step (edit re-entry, #311): the controller arms role=button +
   tabindex on every marker, so each is a real keyboard control (tenet #7/#63). */
.cb-steps li[role=button] { cursor: pointer; }
.cb-steps li[role=button]:not(.active):hover { background: var(--surface-hover);
  color: var(--text); }
.cb-steps li[role=button]:focus-visible { outline: 2px solid var(--focus-ring);
  outline-offset: 2px; }

/* ---- the Co-Pilot: a rounded card, full height, one conversation ---- */
/* The card owns its width (the shell's rail track is `auto`): the default 320px
   until the resize handle writes --cb-cop-width, clamped by pane-resize.js. */
.cb-cop { display: flex; flex-direction: column; min-height: 0; overflow: hidden;
  width: var(--cb-cop-width, 320px); }
/* display:flex out-specifies the UA [hidden] rule — restore it (the
   .topbar[hidden] pattern) so Start really has no Co-Pilot card. */
.cb-cop[hidden] { display: none; }
.cb-cop-head { display: flex; align-items: center; gap: var(--space-4);
  padding: var(--space-6); border-bottom: 1px solid var(--border); }
/* the prototype's `.cop-head` leads with the chat glyph */
.cb-cop-head .cb-cop-ico { display: flex; color: var(--text-muted); flex: none; }
.cb-cop-head .title { font-weight: var(--fw-bold); font-size: var(--fs-body-sm); }
.cb-cop-head .hint { color: var(--text-muted); font-size: var(--fs-meta); }
/* YatsarChat mounts ON the host (the host itself becomes `.yc`), and `.yc` is a
   ROW: [ yc-main | yc-panel ]. Forcing column here stacked the closed artifact
   panel (width 0, but its own height) UNDER the composer as a dead strip. */
.cb-cop-host { flex: 1; min-height: 0; display: flex; }
.cb-cop-host .yc-messages { padding: var(--space-7); }

/* ---- the plain-language interview's ANSWER-CARDS (#545, the #517 prototype's
   `.qcard` / `.qopt` / `.rec`) ----------------------------------------------

   They sit IN the message log, between the Co-Pilot's turns, because a refinement
   question is part of what it is saying — not a panel beside the conversation.

   The prototype hardcodes its values; each one below rides the nearest token, and
   the rounding is named where it moves a pixel:

     .qcard    padding 9px 11px → --space-5/--space-6 (10/12px)
               gap 7px          → --space-4 (8px)
               --primary-soft   → --accent-surface (the same role, this system's name)
     .qcard .q 12.5px           → --fs-body-sm (13px); weight 650 → --fw-emph
     .qopt     padding 7px 9px  → --space-4/--space-5 (8/10px)
               gap 8px          → --space-5 (10px)
               12px             → --fs-meta (12px, exact)

   The card carries the QUESTION and the ANSWERS and nothing else. An option knows
   the Streams it would bind — that is how choosing it changes the mapping — but it
   never renders them: the whole reason the question is written in consequences is
   that the technical detail lives in the mapping card. */
.cb-qcard {
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--surface-raised); padding: var(--space-5) var(--space-6);
  display: grid; gap: var(--space-4);
  /* An option question is a <fieldset> (#545 review ST-2) so the radios are one
     labelled group; these two zero what the UA gives a fieldset and nothing else,
     so the prototype's card is unchanged. */
  margin: 0; min-width: 0;
}
.cb-qcard .cb-q { font-weight: var(--fw-emph); font-size: var(--fs-body-sm); }
.cb-qopt {
  display: flex; gap: var(--space-5); align-items: flex-start;
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  cursor: pointer; font-size: var(--fs-meta);
}
.cb-qopt:hover { border-color: var(--border-strong); background: var(--surface-hover); }
.cb-qopt.on { border-color: var(--primary); background: var(--accent-surface);
  color: var(--primary); }
.cb-qopt:focus-within { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.cb-qopt input { width: 14px; height: 14px; accent-color: var(--primary);
  flex: none; margin-top: 1px; }
.cb-qopt .cb-qopt-label { flex: 1; min-width: 0; }
/* The card's save-status line. Clicking an option IS the submission (there is no
   Submit button by design), so the card itself says what happened to the answer —
   saving, saved, or refused — rather than leaving the author to infer it from a
   note scrolling by in the chat. Muted like every caption; the refused state
   borrows the danger color because it needs acting on. */
.cb-qstate { font-size: var(--fs-meta); color: var(--text-muted); }
.cb-qstate.err { color: var(--danger); }
/* The scratch-scope card — the one question the Builder asks rather than the
   Co-Pilot, because where the harness may write is a fact about this installation.
   It has no options: it has the Connect-time picker (the prototype's `picker: true`
   card), and once bound it reads as the answer it is. */
.cb-qscope { display: flex; align-items: center; gap: var(--space-5);
  flex-wrap: wrap; }
/* …and its caption rides the Builder's one muted-caption recipe, up beside
   `.cb-card-head .hint`. */
/* The recommendation chip. It is the answer for the author who has no opinion, so
   it reads as an endorsement (the --success family) rather than as a neutral tag:
   --success on --success-surface, the prototype's --success/--success-soft pair
   under this system's names, pill radius, mono, uppercase, .07em — all verbatim.

   TWO MEASURED DIVERGENCES from the prototype's `.rec`, both of them the same
   trade — the token wins where the prototype's raw value has a token equivalent:

     font-size  9px    → --fs-eyebrow (11px)
     padding    0 6px  → 0 var(--space-4) (0 8px)

   Together they cost a long option label one extra wrapped line in the 320px rail
   (68px → 90px on "Ask OneDrive what changed since the last check — fast"), and
   the 2px of extra side padding is most of why the chip crosses the wrap point.
   Taken anyway, because #544's `.run-ack-pill` is the same prototype's micro-label
   on the same rail and already rides --fs-eyebrow with --space-4 sides: two
   micro-labels a few pixels apart, in one column, would read as a mistake rather
   than as a distinction. 19px in `.run-chk .st` was the case where the prototype's
   value had no token equivalent and the raw value stayed. */
.cb-qrec {
  font-family: var(--mono); font-size: var(--fs-eyebrow); line-height: 1.6;
  text-transform: uppercase; letter-spacing: .07em;
  color: var(--success); background: var(--success-surface);
  border-radius: var(--radius-pill); padding: 0 var(--space-4);
  white-space: nowrap; flex: none;
}

/* ---- the Builder topbar: the Connector's identity only — the step controls
        live on the steps card (.cb-nav) ---- */
.cb-topbar .cb-ident { display: flex; align-items: center; gap: var(--space-3);
  font-weight: var(--fw-emph); min-width: 0; }
.cb-topbar .cb-ident .ws-conn-ico { width: 20px; height: 20px; font-size: 11px;
  flex: none; }
.cb-topbar .cb-ident .nm { overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }

/* ---- Start: the tag cards + the access-mode reveal ---- */
.cb-cards { display: grid; gap: var(--space-5);
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }
/* The card's radio and the hanging indent under it are ONE measurement: the body
   copy and the pin chips line up with the label, not with the control. Declared
   once ON THE PAGE — the control's size, and the indent derived from it (control +
   the `.t` row gap) — so every surface that lines copy up under a radio reads the
   same two numbers: Start's tag and access cards, Save & Publish's confirmation,
   and the reveals that hang below it as SIBLINGS of the option they belong to
   (which is why the declaration cannot live on the card itself). */
.cb-page { --cb-radio: 15px;
  --cb-hang: calc(var(--cb-radio) + var(--space-4)); }
.cb-tag, .cb-opt {
  display: grid; gap: var(--space-3); padding: var(--space-6);
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--surface-raised); cursor: pointer; }
.cb-tag:hover, .cb-opt:hover { border-color: var(--border-strong); }
.cb-tag.on, .cb-opt.on { border-color: var(--primary); background: var(--accent-surface); }
.cb-tag:focus-within, .cb-opt:focus-within { outline: 2px solid var(--focus-ring);
  outline-offset: 2px; }
.cb-tag .t, .cb-opt .t { display: flex; align-items: center; gap: var(--space-4);
  font-weight: var(--fw-bold); font-size: var(--fs-body-sm); }
.cb-tag input, .cb-opt input { width: var(--cb-radio); height: var(--cb-radio);
  accent-color: var(--primary); flex: none; margin: 0; }
.cb-tag .d, .cb-opt .d { font-size: var(--fs-meta); color: var(--text-muted);
  padding-left: var(--cb-hang); }
.cb-tag.on .d, .cb-opt.on .d { color: var(--accent-text); }
.cb-tag .pins { padding-left: var(--cb-hang); display: flex; gap: var(--space-3);
  flex-wrap: wrap; }
/* A Blueprint card's action sizes to its label — a grid child would otherwise
   stretch the full card width and out-shout the card it belongs to. */
.cb-tag > button { justify-self: start; }
.cb-pin { font: var(--fs-eyebrow)/1.5 var(--mono); border-radius: var(--radius-pill);
  padding: 1px var(--space-3); border: 1px solid var(--border-strong);
  color: var(--text-muted); background: var(--surface-raised); }
.cb-tag.on .cb-pin { border-color: var(--primary); color: var(--accent-text); }
/* Full API Access pins no contract and no access mode — the dashed, full-width
   card reads as the open option until it is chosen (prototype .tag-card.full). */
.cb-tag.full { border-style: dashed; }
.cb-tag.full.on { border-style: solid; }

/* ---- the consequence note: what the choice just locked in, or why it can't
   advance (prototype .locknote / .locknote.block) ---- */
.cb-note { display: flex; gap: var(--space-4); align-items: flex-start;
  padding: var(--space-5) var(--space-6); border-radius: var(--radius-md);
  background: var(--accent-surface); color: var(--accent-text);
  font-size: var(--fs-meta); transition: box-shadow var(--motion-fast); }
.cb-note[hidden] { display: none; }
.cb-note .cb-note-ico { color: var(--primary); flex: none; }
/* A refusal is calm, never alarming (tenet #9): the zero-tag block is the
   warning wash, not danger red. */
.cb-note.block { background: var(--warning-surface); color: var(--warning); }
.cb-note.block .cb-note-ico { color: var(--warning); }
/* Pressing Continue on an untagged Start repaints the SAME sentence, so the
   press needs its own evidence: a transient ring, re-triggered per press through
   the `.ws-note-badge` bump mechanism (remove → reflow → add → timer removes).
   The controller also clears and repaints the live region, so the reason is
   re-announced as well as re-drawn. */
.cb-note.nudge { box-shadow: 0 0 0 2px var(--warning); }
@media (prefers-reduced-motion: reduce) {
  /* the ring still appears and goes — it just does not fade */
  .cb-note { transition: none; }
}

/* ---- Describe: the description the author writes, or a Blueprint pre-fills ----
   The #508 prototype's Describe field is the ORDINARY bordered textarea at a
   comfortable prose height (its `min-height: 92px` override) — the platform's own
   control, not the Builder's borderless `.cw-json` pane editor, and not mono: a
   description is prose, and the prototype only reached for its JSON utility class
   because that was the one textarea rule it had. */
.cb-describe { width: 100%; min-height: 92px; resize: vertical;
  line-height: 1.55; }

/* ---- "Researching and Creating Connector": the holding state (#527) ----
   ONE state, two homes — the Co-Pilot rail while the research/authoring turn
   runs, and the Credential step's body while the Credential Profile is missing —
   so the two surfaces can never disagree about whether the Builder is working.
   It reads as the accent wash the Builder already uses for "what this step
   already knows", never as a warning: nothing is wrong, work is under way. */
.cb-hold { display: flex; gap: var(--space-5); align-items: flex-start;
  padding: var(--space-5) var(--space-6); border-radius: var(--radius-md);
  background: var(--accent-surface); color: var(--accent-text); }
.cb-hold[hidden] { display: none; }
/* the shared Working-mark slot (§ Iconography, Decision 65): the mark sizes to
   the title beside it and wears `.is-running` only while the turn is live */
.cb-hold .cb-hold-mark { display: inline-flex; flex: none; line-height: 0;
  font-size: 15px; margin-top: 1px; }
.cb-hold .cb-hold-text { display: grid; gap: var(--space-3); min-width: 0; }
.cb-hold .cb-hold-text .t { font-weight: var(--fw-bold);
  font-size: var(--fs-body-sm); }
.cb-hold .cb-hold-text .d { font-size: var(--fs-meta); }
/* Stalled — the turn ended without a Profile, so the Builder is waiting on the
   author, not working. The mark stops breathing and the wash goes calm-neutral:
   an unanswered question is not an error (tenet #9). */
.cb-hold.stalled { background: var(--surface); color: var(--text-muted);
  border: 1px solid var(--border); }
.cb-hold.stalled .cb-hold-mark { color: var(--text-muted); }
/* The rail's copy sits between the Co-Pilot head and the transcript. */
.cb-cop > .cb-hold { margin: var(--space-6) var(--space-6) 0; }
/* The step surface the hold stands in for, hidden as ONE thing. It re-declares
   the step's own stack (a step body is a grid of cards) so wrapping the cards to
   make them hideable does not collapse the gap between them. */
.cb-held { display: grid; gap: var(--space-7); align-content: start; }
.cb-held[hidden] { display: none; }

/* ---- Credential (#529): the slot cards ----
   One card per admin credential slot, so the host is just the step's own stack.
   Inside a slot, a control sometimes travels with a status pill (a saved secret) or
   a second control (the pick list and New), which is the prototype's `.row`. */
.cb-cred-slots { display: grid; gap: var(--space-7); align-content: start; }
.cb-cred-row { display: flex; align-items: center; gap: var(--space-4);
  flex-wrap: wrap; }
.cb-cred-row > input, .cb-cred-row > select { flex: 1; min-width: 0; }
.cb-cred-row > button { flex: none; }
/* The saved pill beside a write-only secret is the RATIFIED status chip itself
   (`.ws-conn-chip.ok`, Decision 75's `--success-surface`) — this step mints no
   near-copy of that recipe; it only pins the pill's width in the row. */
.cb-cred-row > .ws-conn-chip { flex: none; }
/* Every field on a slot card is DISPLAY-ONLY (select-or-New — the step never edits
   a credential), so the control has to look it. A muted value on the hover surface
   reads "stored, not yours to change here" while keeping the placeholder legible,
   which a flat --opacity-disabled wash would not — the documented exception to the
   ratified disabled treatment (patterns.md § UI states). */
.cb-cred-row > input:disabled { color: var(--text-muted);
  background: var(--surface-hover); cursor: default; }

/* ---- Stream configuration (#530 owns the merge; this is the prototype's
   two-column field: the Streams rail card beside the stacked pane cards) ---- */
.cb-cfg { display: grid; grid-template-columns: 250px minmax(0, 1fr);
  gap: var(--space-7); align-items: start; }
.cb-cfg[hidden] { display: none; }
.cb-cfg > div { display: grid; gap: var(--space-7); align-content: start;
  min-width: 0; }
/* The column div is itself a grid, so the CARDS are grid items too — without
   their own min-width release, a rail card sizes to its widest row's
   min-content (name + pill, both nowrap) and paints out of the 250px track,
   UNDER the pane cards beside it (UAT: the rail overlapped the panes whenever
   a Stream name plus its state pill outgrew 250px). */
.cb-cfg > div > * { min-width: 0; }
/* In the narrow rail a Stream is its NAME and its state — the description would
   only truncate mid-word (the prototype's rail carries name + tag). */
.cb-cfg .cw-srow .desc { display: none; }
/* …and in 250px the NAME has priority over the tail: the tail shrinks rather than
   squeezing the Stream's name down to "b…" — which row you are looking at is the
   one thing the rail must never lose. (The pills are terse for the same reason;
   their detail is in the title.) Priority means the name does NOT shrink —
   proportional flex shrink still shaved fractions off a tight-fitting name and
   tripped its ellipsis — capped so a runaway name leaves the lock a sliver of
   tail and still cannot push the row out of the rail. */
.cb-cfg .cw-srow .nm { flex: 1 0 auto; max-width: 85%; }
.cb-cfg .cw-srow .tail { flex: 0 1 auto; min-width: 0; }
/* A squeezed pill ellipsizes its own label inside its border. inline-block, not
   the pill's inline-flex: text-overflow cannot ellipsize a flex container's
   anonymous text box, so inline-flex clipped the pill mid-border instead. */
.cb-cfg .cw-srow .cw-state { display: inline-block; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; }

/* ---- narrow viewports: the shared Admin breakpoint (980px — the same one the
   bench rail, the Files takeover and the Preset authoring surface use), NOT a
   one-off. Two things have to give together, or the page traps its own content:
   the 320px Co-Pilot rail stacks under the body, and the viewport lock is
   RELEASED (the .settings-page precedent) so the stacked column scrolls with the
   page instead of inside a field that is no longer tall enough for it. ---- */
@media (max-width: 980px) {
  body.cb-page.has-sidebar { height: auto; overflow: visible; }
  .cb-page .app-content { height: auto; }
  .cb-shell { grid-template-columns: minmax(0, 1fr); }
  .cb-lcol { grid-template-rows: auto auto; }
  .cb-body { overflow: visible; }
  /* stacked, the Co-Pilot has no full-height field to fill — its transcript
     would otherwise collapse to nothing, so it keeps a usable floor. Its
     dragged width is a desktop fact: stacked, it goes full width again, and
     the handle has no column to resize. */
  .cb-cop { min-height: 420px; width: auto; }
  .cb-cop-resize { display: none; }
  .cb-cfg { grid-template-columns: 1fr; }
}
/* An unticked Stream stays listed with its definition intact: struck through and
   hatched, so nothing ever silently disappears from a Connector's surface
   (prototype .srow.off). */
.cw-srow.off .nm { color: var(--text-muted); text-decoration: line-through; }
.cw-srow.off { background: repeating-linear-gradient(135deg,
  transparent 0 6px, color-mix(in srgb, var(--text) 3%, transparent) 6px 12px); }
/* ---- the tag's pins (#530; prototype .srow.pinned / .srow.unavail) ----
   A pinned Stream carries a LOCK where the checkbox would be: the tag's contract
   requires it, so there is no choice to offer and the row is not a toggle. */
.cw-srow.pinned { cursor: default; }
.cw-srow .cw-lockico { color: var(--primary); flex: none; display: inline-flex;
  align-items: center; }
/* A Stream no tag requires, on a Connector whose tag does not open the set: the
   control STAYS (disabled) rather than vanishing, because the omission is an
   access-control decision an author has to be able to see. The canonical disabled
   wash — the same cue every other unavailable control wears. */
.cw-srow.unavail { opacity: var(--opacity-disabled); cursor: not-allowed; }
/* The rail leads with the Stream's NAME (prototype .srow .nm): a contract Stream
   may have no definition — and so no endpoint — yet, and the endpoint is in the
   pane hint. Truncates rather than wrapping in the 250px rail. */
.cw-srow .nm { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }
/* The state pill rides .cn-verdict (the system's pill) — bordered + muted for a
   state that closes the Stream off ("needs full access" / "excluded"), and the
   accent wash for the tag that holds it open, which is a fact about the contract
   rather than a warning. The uppercase micro-label recipe is the shared .eyebrow,
   composed in the MARKUP (backlog L1 — no sixth forked copy, no novel tracking);
   only the mono face and the nowrap are deltas, plus the size re-assert that
   .cn-verdict's own body-sm would otherwise win on equal specificity. */
.cw-srow .cw-state { font-family: var(--mono); font-size: var(--fs-eyebrow);
  white-space: nowrap; }
.cw-srow .cw-state.contract { color: var(--accent-text);
  background: var(--accent-surface); border-color: transparent; }
/* A pinned contract Stream the Co-Pilot has not mapped yet (#530 review): DASHED,
   the same "declared but not yet real" cue .cb-tag.full and the pane stub carry,
   with the row's NAME muted because there is nothing behind it to read yet. It
   keeps full opacity — unlike .unavail it is not disabled, it is unfinished. */
.cw-srow .cw-state.pending { border-style: dashed; }
.cw-srow.unmapped .nm { color: var(--text-muted); }
/* The rail's `read-write` pill (#540) in the REAL warning role — `--warning` on
   `--warning-surface`, bordered with `--warning-border`. It first shipped on
   `.cn-verdict.warn`, which is the ACCENT hue: that made the one row-level fact with a
   security consequence ("the chat can change somebody's data here") pixel-identical to
   the `contract` pin pill sitting beside it, which means the opposite — a fact about
   what the tag requires (#540 review, S8). Two things that mean different things must
   not look the same, and this is the one of the two that carries the consequence. Its
   border is what promoted `--warning-border` into the token contract as the second
   bordered warning consumer (tokens.md, Decision 75's rule-of-two). */
.cw-srow .cw-state.write { color: var(--warning);
  background: var(--warning-surface); border-color: var(--warning-border); }
/* The write toggle's own pane block (#540, spec §E): between the stub and the JSON
   editor, with the consequence spelled out under the control. A checkbox row rather
   than a segmented control because it is one binary with a safe default — and the
   rail carries the terse `read-write` pill above, in the warning role. */
.cb-wmode { margin: var(--space-5) var(--space-5) 0; display: grid;
  gap: var(--space-2); }
.cb-wmode label { display: flex; align-items: center; gap: var(--space-4);
  cursor: pointer; }
.cb-wmode input[type=checkbox] { accent-color: var(--accent); width: 15px;
  height: 15px; flex: none; }
.cb-wmode .hint { margin: 0; }
/* Why this pane looks the way it does (prototype .stub): a bounded dashed callout
   between the card head and the JSON editor, so it reads as the editor's preamble
   rather than as a muted paragraph that could pass for the pane's own content. */
.cb-pane-stub { margin: var(--space-5) var(--space-5) 0;
  border: 1px dashed var(--border-strong); border-radius: var(--radius);
  background: var(--surface); padding: var(--space-4) var(--space-5);
  font-size: var(--fs-meta); color: var(--text-muted); }
/* …and in a card BODY the grid gap already spaces it — the pane's own inset would
   double up (the Save & Publish contract region, #531). */
.cb-card-body > .cb-pane-stub { margin: 0; }

/* ---- Save & Publish: two columns (#531; the prototype's `.pubcols`) ----
   The access-mode confirmation reads at a fixed, narrow measure — it is a settled
   answer, not a workspace — while the grant table takes the room a people list
   needs. The release is the sanctioned 1050px (patterns.md's breakpoint
   vocabulary — the same threshold the Overview metrics use): below it the columns
   stack rather than crush the table. The prototype's own 1120 was measured in a
   mock with no admin sidebar and no Co-Pilot rail, so it was never this page's
   number, and a one-off threshold 70px from a registered one buys nothing. */
.cb-pubcols { display: grid; align-items: start; gap: var(--space-7);
  grid-template-columns: minmax(0, 390px) minmax(0, 1fr); }
.cb-pubcols > div { display: grid; gap: var(--space-7); align-content: start;
  min-width: 0; }
/* Same release the .cb-cfg columns need: the cards are items of the column's own
   grid, and without it the grant table's min-content could carry its card over
   the 390px column's edge. */
.cb-pubcols > div > * { min-width: 0; }
@media (max-width: 1050px) {
  .cb-pubcols { grid-template-columns: minmax(0, 1fr); }
}
/* The review summary IS a card (prototype `.card.summary`): a mark, the growing
   middle, a badge — never a bordered box nested inside another one. */
.cb-summary { padding: var(--space-5) var(--space-6); }
.cb-prow { display: flex; align-items: center; gap: var(--space-5); }
.cb-prow > .grow { flex: 1; min-width: 0; display: grid; gap: var(--space-2); }
.cb-prow .name { font-weight: var(--fw-bold); font-size: var(--fs-body-sm); }
.cb-prow .meta { color: var(--text-muted); font-size: var(--fs-meta); }
/* The final name is the prototype's borderless editable TITLE, not a form field
   parked in a summary: it already reads as the Connector's name, and boxing it
   made the one editable thing on the step look like the least important. Editable
   without a box still has to be reachable, so the focus ring is the affordance. */
.cb-prow input.name { font: inherit; font-weight: var(--fw-bold);
  font-size: var(--fs-body); width: 100%; border: 0; padding: 0;
  background: transparent; color: var(--text); }
.cb-prow input.name:focus-visible { outline: 2px solid var(--focus-ring);
  outline-offset: 2px; border-radius: var(--radius-sm); }
/* The read-only exposure tag riding the summary line: the same chip Start's pins
   use, so "settled, not editable" looks the same wherever it is confirmed. */
.cb-prow .cb-pin { flex: none; }
/* The confirmation pair (`--cb-radio`/`--cb-hang` come from `.cb-page`, so the
   reveals that sit BESIDE this group hang off the same measurement the option copy
   above them does). */
.cb-modes { display: grid; gap: var(--space-5); }
/* A confirmation is not a control: no pointer, no hover invitation, and the mode
   that was NOT chosen stays legible rather than dimmed away — "System XOR User" is
   the fact being stated, so both halves have to be readable. */
.cb-opt.confirm { cursor: default; }
.cb-opt.confirm:hover { border-color: var(--border); }
.cb-opt.confirm.on:hover { border-color: var(--primary); }
/* A reveal is a SIBLING of the radiogroup, never a child (#531 review S7): it
   carries live controls — a checkbox and a textarea — and a live control inside an
   `aria-readonly` group is a contradiction assistive tech has no way to resolve.
   The hanging indent is what keeps it reading as the settled option's rider. */
.cb-reveal { display: grid; gap: var(--space-5); padding-left: var(--cb-hang); }
.cb-reveal[hidden] { display: none; }
/* The grant table is what the card HOLDS, edge-to-edge (the prototype's
   `padding: 0 0 2px` body): a `.dash-panel` frame drawn a pixel inside the card's
   own is one border too many — raised on raised — and the body's ~32px inset left
   the rows floating in dead space instead of running the width the card promises.
   Everyone mode swaps the table for a stub, so the ordinary inset comes back with
   it; keying that off which surface is showing means the two cannot disagree. */
.cb-grant-body > .dash-panel { margin: 0; border: 0; border-radius: 0;
  background: transparent; }
.cb-grant-body:has(> .dash-panel:not([hidden])) { padding: 0 0 var(--space-1); }

/* Branded OAuth sign-in button (#302) — the oauth2_authcode context's control;
   build-time Step 2 AND the end-user Connect (#312) share it. */
.cw-signin { display: inline-flex; align-items: center; gap: var(--space-4);
  padding: var(--space-3) var(--space-6); border: 1px solid var(--border-strong);
  background: var(--surface-raised); color: var(--text); border-radius: var(--radius);
  font-size: var(--fs-body); font-weight: var(--fw-emph); cursor: pointer; }
.cw-signin:hover { border-color: var(--accent); }
.cw-signin .ws-conn-ico { width: 22px; height: 22px; font-size: 12px; }
/* Stream rows (#303, #530) — a .list-row composition: the control (checkbox, or a
   lock on a pinned Stream), the Stream's NAME, a muted description, and the
   trailing state / scope / verdict cluster (#304/#305/#530). The mono `.ep`
   endpoint the row used to lead with is gone: a contract Stream may have no
   definition yet, and the endpoint reads in the pane hint instead. */
.cw-streams { display: flex; flex-direction: column; padding: var(--space-3) 0;
  overflow: auto; }
.cw-srow { gap: var(--space-4); align-items: center; }
.cw-srow input[type=checkbox] { accent-color: var(--accent); width: 15px; height: 15px;
  margin: 0; flex: none; }
.cw-srow .desc { color: var(--text-muted); font-size: var(--fs-body-sm);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cw-srow .tail { margin-left: auto; display: flex; align-items: center;
  gap: var(--space-4); flex: none; }
/* The raw-JSON power-edit surface filling a card body: a borderless mono
   textarea under the card head (#306; re-hosted onto the gray field at #526,
   where the enclosing .cb-card supplies the border and radius). */
.cw-json { width: 100%; min-height: 118px; border: 0; border-radius: 0;
  resize: vertical; font-family: var(--mono); font-size: 12.5px; line-height: 1.55;
  padding: var(--space-5); background: var(--surface-raised); color: var(--text); }
/* Text-input focus is RESTRAINED (#401), but a borderless editor still needs
   a visible indicator (tenet #7): the card-filling JSON editors tint their
   enclosing card border + show the caret (the .ws-code:focus-within
   container-tint precedent, same as .editor-area textarea:focus); the
   instructions variant below carries its own border instead. */
.cw-json:focus { outline: none; caret-color: var(--text); }
.cb-card:has(.cw-json:focus) { border-color: var(--focus-ring); }
/* #cw-connect-instructions rides .cw-json for the mono look but sits in a
   form-field stack, not a bordered pane: a transparent resting border becomes
   the restrained neutral border on focus (border-box — no layout shift). */
.cw-json.cn-instructions { border: 1px solid transparent; }
.cw-json.cn-instructions:focus { border-color: var(--focus-ring); }
/* The "Already connected" report line (#313): a repeat sign-in adds no card — a
   quiet .cn-verdict warn under the Connections lanes, not an error. The migrated
   #421 lanes carry their own card anatomy (.ws-conn-card &c. above); .cw-connline
   now backs only this report line. */
.cw-connline { display: flex; align-items: center; gap: var(--space-3);
  margin-top: var(--space-3); }
/* display:flex above out-specifies the UA [hidden] rule — restore it so the
   report line hides cleanly (same pattern as .topbar[hidden]). */
.cw-connline[hidden] { display: none; }

/* ==========================================================================
   Workspace Atlas — Home-first Stacked navigation (#411, prototype #398
   Variant A). The left rail owns durable route navigation + categorized
   recents; the fixed right Utility Rail owns Tasks, Notifications, the File
   Explorer, and open documents. Reproduces the locked prototype geometry,
   hierarchy, action placement, response-status cue, collapse rules, and focus
   behavior through the design-system tokens (prototype --wf-* → semantic
   roles; -soft names normalize to -surface). New chat is the top-right
   primary; Settings + Log out are profile-owned.
   ========================================================================== */

/* --- rail shell ---------------------------------------------------------- */
#app-sidebar.ws-rail { display: flex; flex-direction: column; overflow: visible; }
.ws-nav-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 2px 10px 12px; }

.ws-section-label { margin: 12px 9px 5px; color: var(--text-muted);
  font: 10px/1 var(--mono); letter-spacing: .09em; text-transform: uppercase;
  opacity: .85; }

/* --- shared nav row skin (Home + group triggers + profile) --------------- */
.ws-nav-item, .ws-group-trigger, .ws-profile {
  width: 100%; min-height: 38px; display: flex; align-items: center; gap: 10px;
  padding: 8px 9px; border-radius: var(--radius); border-color: transparent;
  background: transparent; color: var(--text-muted); text-align: left;
  text-decoration: none; }
.ws-nav-item:hover, .ws-group-trigger:hover, .ws-profile:hover {
  color: var(--text); background: var(--surface-hover); border-color: transparent; }
.ws-nav-item.active { color: var(--accent-text); background: var(--accent-surface);
  font-weight: 650; }
.ws-nav-ico { width: 22px; display: grid; place-items: center; flex: none; }
.ws-label { min-width: 0; flex: 1; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; font-size: var(--fs-body-sm); }
.ws-caret { display: grid; place-items: center; flex: none;
  transition: transform var(--motion-fast); }
.ws-group.open > .ws-group-caret .ws-caret { transform: rotate(90deg); }
.ws-group-trigger.chat { color: var(--accent-text); font-weight: var(--fw-emph); }
.ws-group-trigger.agent { color: var(--agent-text); font-weight: var(--fw-emph); }
.ws-group-trigger.skill { color: var(--skill-text); font-weight: var(--fw-emph); }
.ws-group-trigger.file { color: var(--file-text); font-weight: var(--fw-emph); }
.ws-group-trigger.chat:hover { color: var(--accent-text); }
.ws-group-trigger.agent:hover { color: var(--agent-text); }
.ws-group-trigger.skill:hover { color: var(--skill-text); }
.ws-group-trigger.file:hover { color: var(--file-text); }

/* --- per-group quick action = the ROW; expand = the caret alone (#737) ---
   Each Recent group carries ONE direct verb: Chats → new chat, Agents/Skills →
   their Browse takeover, Files → the File Explorer Utility. The ROW fires it —
   the name is the verb — so the action needs no target of its own and shows
   only as a trailing glyph, plain and hover-less, inside the trigger.
   Expand is the trailing CHEVRON alone. A button cannot nest inside a button,
   so the caret is a sibling laid over the reserved .ws-caret-slot at the end of
   the row, and that slot is what keeps the label's ellipsis honest. Its offset
   is the row's own geometry: the 26px caret sits at the trigger's own 9px
   padding, flush with the row's right edge. */
.ws-group { position: relative; }
.ws-group-act-ico { width: 22px; display: grid; place-items: center; flex: none; }
.ws-caret-slot { width: 26px; flex: none; }
.ws-group-caret { position: absolute; top: 6px; right: 9px; z-index: 1;
  width: 26px; height: 26px; display: grid; place-items: center; padding: 0;
  border: 0; border-radius: var(--radius-sm); color: var(--text-muted);
  background: transparent; cursor: pointer;
  transition: background var(--motion-fast), color var(--motion-fast); }
/* Hovering the CARET still washes the whole row — it is one menu item — while
   the caret itself takes the group's own identity tint on top, so the two
   targets read apart without the action ever needing a hover of its own. */
.ws-group:has(> .ws-group-caret:is(:hover, :focus-visible)) > .ws-group-trigger {
  background: var(--surface-hover); }
.ws-group.chat .ws-group-caret:is(:hover, :focus-visible) {
  color: var(--accent-text); background: var(--accent-surface); }
.ws-group.agent .ws-group-caret:is(:hover, :focus-visible) {
  color: var(--agent-text); background: var(--agent-surface); }
.ws-group.skill .ws-group-caret:is(:hover, :focus-visible) {
  color: var(--skill-text); background: var(--skill-surface); }
.ws-group.file .ws-group-caret:is(:hover, :focus-visible) {
  color: var(--file-text); background: var(--file-surface); }

.ws-group-list { padding: 2px 0 6px 26px; }
/* the recents list sits inside a .ws-flyout wrapper (collapsed pop-out host);
   in the expanded rail the wrapper is transparent, so hide the closed group's
   list by descendant, not direct child (#398 collapsed flyout). */
.ws-group:not(.open) .ws-group-list { display: none; }
.ws-recent-empty { padding: 6px 8px 10px; color: var(--text-muted);
  font-size: var(--fs-meta); }

/* --- recent rows (category identity + processing/completion cue) -------- */
.ws-recent-row { --row-identity: var(--accent); position: relative; width: 100%;
  min-height: 32px; display: flex; align-items: center; gap: 2px; padding: 2px;
  color: var(--text-muted); border-radius: var(--radius); }
.ws-recent-row.identity-agent { --row-identity: var(--agent); }
.ws-recent-row.identity-skill { --row-identity: var(--skill); }
.ws-recent-row.identity-file { --row-identity: var(--file); }
.ws-recent-main { min-width: 0; flex: 1; min-height: 30px; display: flex;
  align-items: center; gap: 8px; padding: 4px 5px; color: inherit;
  border: 0; border-radius: var(--radius-sm); background: transparent;
  text-align: left; cursor: pointer; }
.ws-recent-main.is-static { cursor: default; }
.ws-recent-main .ws-label { font-size: var(--fs-body-sm); }
.ws-recent-icon-wrap { position: relative; width: 25px; height: 25px; flex: none;
  display: grid; place-items: center; }
.ws-recent-icon { width: 25px; height: 25px; display: grid; place-items: center;
  color: var(--row-identity); border-radius: 50%; }
.ws-recent-icon.identity-agent { color: var(--agent); }
.ws-recent-icon.identity-skill { color: var(--skill); }
.ws-recent-icon.identity-file { color: var(--file); }
/* filled glyph = the user's own (Personal) item; hollow = Organization (#398) */
.ws-recent-icon.personal svg { fill: currentColor; }
/* While processing, the shared Working-mark replaces category identity. */
.ws-recent-icon.processing { color: var(--accent); animation: ycpulse 1s infinite; }
.ws-recent-icon.processing .yc-y { width: 17px; height: 17px; }
/* Unread completion: purple dot immediately left of the restored identity. */
.ws-recent-ready-dot { position: absolute; left: -5px; top: 50%;
  width: 6px; height: 6px; transform: translateY(-50%);
  border-radius: 50%; background: var(--accent); }
.ws-recent-action { width: 27px; height: 27px; padding: 0; display: grid;
  place-items: center; flex: none; color: inherit; border-color: transparent;
  background: transparent; opacity: 0; cursor: pointer; }
.ws-recent-row:hover .ws-recent-action,
.ws-recent-row:focus-within .ws-recent-action { opacity: 1; }
/* recents have no general selected state — only a category-tinted hover (#398) */
.ws-group.chat .ws-recent-row:hover, .ws-group.chat .ws-recent-row:focus-within {
  color: var(--accent-text); background: var(--accent-surface); }
.ws-group.agent .ws-recent-row:hover, .ws-group.agent .ws-recent-row:focus-within {
  color: var(--agent-text); background: var(--agent-surface); }
.ws-group.skill .ws-recent-row:hover, .ws-group.skill .ws-recent-row:focus-within {
  color: var(--skill-text); background: var(--skill-surface); }
.ws-group.file .ws-recent-row:hover, .ws-group.file .ws-recent-row:focus-within {
  color: var(--file-text); background: var(--file-surface); }

/* --- profile button + menu ---------------------------------------------- */
.ws-profile { padding-inline: 6px; border-top: 0; }
.ws-menu { position: fixed; bottom: auto; }

/* --- collapsed rail (Y is the sole expand control) ---------------------- */
.ws-shell #app-sidebar.ws-rail.collapsed { width: 64px; }
#app-sidebar.ws-rail.collapsed .ws-label,
#app-sidebar.ws-rail.collapsed .ws-section-label,
#app-sidebar.ws-rail.collapsed .ws-group-caret,
#app-sidebar.ws-rail.collapsed .ws-caret-slot,
#app-sidebar.ws-rail.collapsed .ws-group-act-ico,
#app-sidebar.ws-rail.collapsed .ws-group-list,
#app-sidebar.ws-rail.collapsed .ws-recent-action { display: none; }
#app-sidebar.ws-rail.collapsed :is(.ws-nav-item, .ws-group-trigger, .ws-profile) {
  justify-content: center; padding-inline: 8px; }

/* --- collapsed tooltips (accessible names preserved) -------------------- */
.ws-tip { position: relative; }
.ws-tooltip { display: none; pointer-events: none; position: absolute;
  left: calc(100% + 8px); top: 50%; transform: translateY(-50%); z-index: var(--z-tooltip);
  padding: 5px 8px; border-radius: var(--radius-sm); color: var(--surface-raised);
  background: var(--text); box-shadow: var(--shadow-popover); white-space: nowrap;
  font-size: var(--fs-meta); }
#app-sidebar.ws-rail.collapsed .ws-tip:hover > .ws-tooltip,
#app-sidebar.ws-rail.collapsed .ws-tip:focus-within > .ws-tooltip { display: block; }

/* --- collapsed category flyout (pop-out of a group's recents, #398) ------
   Each Recent group (Chats/Agents/Skills/Files) pops its entries out to the
   right on hover/focus while collapsed — the same rendered rows the expanded
   list holds, so opening/navigating them works identically. The Y is still the
   sole expand control; this only surfaces the recents that collapse would
   otherwise bury. Escape sets data-dismissed (cleared on mouse/focus leave). */
.ws-flyout-head { display: none; }
#app-sidebar.ws-rail.collapsed .ws-group { position: relative; }
#app-sidebar.ws-rail.collapsed .ws-nav-scroll { overflow: visible; }
#app-sidebar.ws-rail.collapsed .ws-flyout {
  display: none; position: absolute; left: calc(100% + 8px); top: 0;
  z-index: var(--z-popover); width: 252px; padding: 9px;
  border: 1px solid var(--border-strong); border-radius: var(--radius-md);
  background: var(--surface-raised); box-shadow: var(--shadow-popover); }
/* hover bridge: an invisible strip spanning the 8px gap between the strip and
   the flyout, so crossing the gap never drops :hover and closes it mid-gesture. */
#app-sidebar.ws-rail.collapsed .ws-flyout::before { content: "";
  position: absolute; left: -8px; top: 0; bottom: 0; width: 8px; }
#app-sidebar.ws-rail.collapsed .ws-group:hover > .ws-flyout,
#app-sidebar.ws-rail.collapsed .ws-group:focus-within > .ws-flyout { display: block; }
#app-sidebar.ws-rail.collapsed .ws-group[data-dismissed="true"] > .ws-flyout { display: none; }
#app-sidebar.ws-rail.collapsed .ws-flyout .ws-group-list { display: block; padding: 0; }
/* the flyout is a full pop-out, so its rows keep their names — undo the strip's
   label hide inside it (the recents would otherwise be icon-only and unreadable). */
#app-sidebar.ws-rail.collapsed .ws-flyout .ws-label { display: block; }
#app-sidebar.ws-rail.collapsed .ws-flyout-head { display: flex; align-items: center;
  gap: 7px; padding: 3px 7px 8px; color: var(--text); }
#app-sidebar.ws-rail.collapsed .ws-flyout-head strong { flex: 1;
  font-size: var(--fs-body-sm); }
/* the head's "Recent" micro-label is the shared .eyebrow recipe; the prototype
   pins only its 10px size, so that's the sole local override. */
#app-sidebar.ws-rail.collapsed .ws-flyout-head .eyebrow { font-size: 10px; }
/* a Recent group's own flyout head already names it, so the hover tooltip the
   other collapsed rows use would just duplicate that label over the pop-out —
   suppress it on the group triggers only (Home/collapse/profile keep theirs). */
#app-sidebar.ws-rail.collapsed .ws-group-trigger.ws-tip:hover > .ws-tooltip,
#app-sidebar.ws-rail.collapsed .ws-group-trigger.ws-tip:focus-within > .ws-tooltip {
  display: none; }

/* --- fixed right Utility Rail + single Utility Canvas -------------------- */
.ws-util-rail { width: 54px; flex: none; display: flex; flex-direction: column;
  align-items: center; gap: 7px; padding: 12px 8px; border-left: 1px solid var(--border);
  background: var(--surface-raised); }
.ws-util-btn { width: 38px; height: 38px; position: relative; display: grid;
  place-items: center; padding: 0; color: var(--text-muted); background: transparent;
  border-color: transparent; cursor: pointer; }
/* the rail marks center via the shared icon-only-control rule near .list-row */
/* Rail items carry their identity-family colour AT REST (locked #386, hues
   swapped by #658: File blue, Tasks rose, My Workspace/Notifications accent)
   — not just on hover — so the rail reads as identity, not a neutral icon
   strip (new-design-base). Hover / active add the matching surface wash. */
.ws-util-btn.file { color: var(--file); }
.ws-util-btn.task { color: var(--notification); }
.ws-util-btn.notification, .ws-util-btn.workspace { color: var(--accent-text); }
.ws-util-btn.file:hover, .ws-util-btn.file.active {
  color: var(--file); background: var(--file-surface); }
.ws-util-btn.file.active { border-color: var(--file); }
.ws-util-btn.notification:hover, .ws-util-btn.notification.active,
.ws-util-btn.workspace:hover, .ws-util-btn.workspace.active {
  color: var(--accent-text); background: var(--accent-surface); }
.ws-util-btn.notification.active,
.ws-util-btn.workspace.active { border-color: var(--accent); }
.ws-util-btn.task:hover, .ws-util-btn.task.active {
  color: var(--notification); background: var(--notification-surface); }
.ws-util-btn.task.active { border-color: var(--notification); }
/* Rail flyout labels (File Explorer, filenames, Notifications, …): the shared
   .ws-tooltip primitive, restyled as a Platform popover (surface-raised body,
   hairline border, popover shadow — not the base black OS-tooltip look) and
   surfaced on MOUSE HOVER ONLY. Focus is deliberately NOT a trigger: clicking a
   rail button (or the focus that lands on File Explorer after a file closes)
   must never flash a label. Keyboard/AT users get the button's aria-label.
   The rail sits on the window's right edge, so a label slides out to the LEFT,
   emerging from behind the rail edge: at rest it is nudged right (toward the
   rail) and hidden, then slides left into place — and slides back on leave
   (the transition lives on the resting rule, so it animates both ways). It is
   purely informational — pointer-events:none (inherited) means moving the
   pointer left onto the label leaves the button and dismisses it (no menu
   latch). Immediate on hover-in (no delay); prefers-reduced-motion is honored
   by the global transition clamp. The base primitive centers vertically with
   translateY(-50%), so both transforms carry it. */
.ws-util-rail .ws-tip > .ws-tooltip {
  display: block; left: auto; right: calc(100% + 6px);
  visibility: hidden; opacity: 0;
  transform: translateX(10px) translateY(-50%);
  color: var(--text); background: var(--surface-raised);
  border: 1px solid var(--border); box-shadow: var(--shadow-popover);
  transition: opacity var(--motion-fast), transform var(--motion-slide),
    visibility var(--motion-slide); }
.ws-util-rail .ws-tip:hover > .ws-tooltip {
  visibility: visible; opacity: 1;
  transform: translateX(0) translateY(-50%); }
.ws-util-sep { width: 28px; height: 1px; margin: 2px 0; background: var(--border); }
.ws-canvas-resize { width: 14px; flex: none; position: relative; z-index: 3;
  cursor: col-resize; background: transparent; }
.ws-canvas-resize[hidden] { display: none; }
/* No resting grip — the affordance is the Canvas border itself. The ::after is a
   full-height line aligned to that border (vertically inset to match the canvas
   margins), transparent until hover/focus/drag paints it the accent color. */
.ws-canvas-resize::after { content: ""; position: absolute;
  inset: var(--space-6) 3px var(--space-6) auto; width: 2px;
  background: transparent; }
.ws-canvas-resize::before { content: ""; position: absolute; inset: 0 -5px; }
.ws-canvas-resize:hover::after, .ws-canvas-resize:focus-visible::after,
.ws-canvas-resize.dragging::after { background: var(--accent); }
.ws-canvas-resize:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; }
/* The fallback matches the `min` pane-resize.js clamps to (workspace.js), so an
   un-dragged Canvas opens at its minimum whichever side sets the width first. */
.ws-util-canvas { width: var(--ws-canvas-width, 375px); flex: none;
  display: flex; flex-direction: column; min-height: 0;
  margin: var(--space-6) var(--space-6) var(--space-6) 0;
  overflow: hidden; border: 1px solid var(--border-strong);
  border-radius: var(--radius-card); background: var(--surface-raised); }
.ws-util-canvas[hidden] { display: none; }
/* Open/close = the Admin "Agent Details" mechanism, ported to this flex row.
   Agent Details animates its grid COLUMN width (.admin-browse grid-template-
   columns 0 → 360px, 220ms ease) so the neighbouring table reflows continuously
   in lockstep. Here the Canvas is a flex-none child: we animate its own WIDTH
   (and the rail-gap margin + a light opacity, like .ctx-canvas) 0 ⇄ target, and
   the chat column (flex:1) fills the freed space smoothly, both directions. The
   Rail is a separate flex sibling to the right that keeps its own space, so the
   Canvas never overlaps it. Content reflows during the slide exactly as the
   Admin table does. */
.ws-util-canvas:not(.is-open) { width: 0; min-width: 0; margin-right: 0; opacity: 0; }
/* Transition ONLY during the open/close window (.ws-anim, added/removed by
   canvas-shell.js). Always-on width transitions would make dragging the resize
   handle — which writes --ws-canvas-width live — lag behind the pointer. */
.ws-util-canvas.ws-anim { transition: width var(--motion-slide),
  margin-right var(--motion-slide), opacity var(--motion-fast); }
.ws-util-head { flex: none; min-height: 52px; display: flex; align-items: center;
  gap: 9px; padding: 8px 10px; border-bottom: 1px solid var(--border); }
.ws-util-head-ico { width: 32px; height: 32px; display: grid;
  place-items: center; flex: none; border-radius: var(--radius); }
.ws-util-head-ico[data-identity="workspace"] {
  color: var(--accent-text); background: var(--accent-surface); }
.ws-util-head-ico[data-identity="task"] {
  color: var(--notification); background: var(--notification-surface); }
.ws-util-head-ico[data-identity="file"] {
  color: var(--file); background: var(--file-surface); }
.ws-util-head-ico[data-identity="notification"] {
  color: var(--accent-text); background: var(--accent-surface); }
.ws-util-title { min-width: 0; flex: 1; }
.ws-util-title strong, .ws-util-title span { display: block; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.ws-util-title span { margin-top: 2px; color: var(--text-muted); font-size: var(--fs-meta); }
.ws-util-close, .ws-util-act { width: 32px; height: 32px; padding: 0; display: grid;
  place-items: center; flex: none; color: var(--text-muted); background: transparent;
  border-color: transparent; cursor: pointer; }
.ws-util-act[hidden] { display: none; }
.ws-util-close:hover, .ws-util-act:hover { color: var(--text); background: var(--surface-hover); }
.ws-util-act.active { color: var(--file); background: var(--file-surface); }
.ws-util-body { flex: 1; min-height: 0; overflow-y: auto;
  padding: var(--space-6); background: var(--surface-raised); }
.ws-util-canvas[data-utility="workspace"] {
  gap: var(--space-6); border: 0; border-radius: 0; background: var(--surface); }
.ws-util-canvas[data-utility="workspace"] .ws-util-head {
  border: 1px solid var(--border); border-radius: var(--radius-card);
  background: var(--surface-raised); }
.ws-util-canvas[data-utility="workspace"] .ws-util-body {
  overflow: hidden; padding: 0; background: var(--surface); }
.ws-util-panel[hidden] { display: none; }
.ws-util-empty { display: grid; place-items: center; min-height: 160px;
  padding: 24px; color: var(--text-muted); text-align: center; font-size: var(--fs-body-sm); }

/* -- the combined "My Workspace" Utility (new-design-base) ------------------ */
/* A dashboard stacking a Notifications region over a File Explorer region.
   Region bodies host the reused Notifications feed and
   File Explorer tree (single source of truth). */
.ws-ws-combo { height: 100%; min-height: 0; display: flex;
  flex-direction: column; gap: var(--space-6); }
.ws-ws-region { border: 1px solid var(--border); border-radius: var(--radius-card);
  background: var(--surface-raised); overflow: hidden; }
.ws-ws-region.notification { flex: 0 1 auto; max-height: 48%; display: flex;
  flex-direction: column; }
.ws-ws-region.file { flex: 1; min-height: 0; display: flex;
  flex-direction: column; }
.ws-ws-region-head { display: flex; align-items: center; gap: 9px;
  padding: 9px 11px; border-bottom: 1px solid var(--border); }
.ws-ws-region-ico { width: 32px; height: 32px; display: grid;
  place-items: center; flex: none; border-radius: var(--radius); }
.ws-ws-region-ico.notification { color: var(--accent-text);
  background: var(--accent-surface); }
.ws-ws-region-ico.file { color: var(--file); background: var(--file-surface); }
.ws-ws-region-copy { min-width: 0; }
.ws-ws-region-copy strong { display: block; font-size: var(--fs-body-sm); font-weight: var(--fw-emph); }
.ws-ws-region-copy span { display: block; margin-top: 1px; color: var(--text-muted); font-size: var(--fs-meta); }
.ws-ws-count { flex: none; padding: 2px 8px; border-radius: var(--radius-pill);
  background: var(--accent-surface); color: var(--accent-text); font-size: var(--fs-meta); }
.ws-ws-count[hidden] { display: none; }
.ws-ws-region-body { padding: 7px 9px; }
.ws-ws-region.notification .ws-ws-region-body,
.ws-ws-region.file .ws-ws-region-body { min-height: 0; overflow-y: auto; }
.ws-ws-region.file .ws-ws-region-body { flex: 1; }

/* -- Tasks Utility v2, the triage list (#498, spec #496) --------------------- */
/* Re-expressed from prototype `tasks-utility-v2.html` variant B, which is the
   near-literal authority here. The Utility fills the shared single-owner host
   body: compose row, the three triage tabs, then the list. Identity glyph in the
   header stays the shared task glyph, and Tasks wear the ROSE family / neutral
   (the #658 swap; still no fifth identity family — the Connector precedent,
   Decisions 66/76 — Tasks borrow the rose tokens Notifications vacated).

   The amber a due-today pill and a `high` priority flag read in is the system's
   `--warning` / `--warning-surface` STATUS pair, promoted to a first-class token
   in the four-place contract beside success/danger (`:root` + the dark block,
   app/theme.py TOKEN_GROUPS, YatsarDom.TOKEN_GROUPS). It could not ride the Skill
   identity family — a Theme repainting Skill gold must not repaint a deadline —
   and a scoped hex copy would make it a colour this block owns rather than a role
   the Theme system can honour, so it lives where the other status hues live. */
.ws-tasks { display: flex; flex-direction: column; gap: var(--space-5); }
.ws-tasks-compose { display: flex; flex-wrap: wrap; gap: var(--space-3); }
.ws-tasks-input { flex: 1; min-width: 0; padding: var(--space-4) var(--space-5);
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface); color: var(--text); font-size: var(--fs-body-sm); }
.ws-tasks-input:focus { outline: none; border-color: var(--focus-ring); }
.ws-tasks-compose .primary { flex: none; }
/* Tasks-context primaries (quick-row Add, drill-in Create/Save) wear the rose
   family (#658) — the same identity-tinted-primary pattern as the agent/skill
   takeovers: the white-bearing -strong fill (DR25), darkened on hover. */
.ws-tasks-compose .primary, .ws-task-ffoot .primary {
  background: var(--notification-strong);
  border-color: var(--notification-strong); }
.ws-tasks-compose .primary:hover, .ws-task-ffoot .primary:hover {
  background: color-mix(in srgb, var(--notification-strong) 86%, black);
  border-color: color-mix(in srgb, var(--notification-strong) 86%, black); }
/* the escalation out of the quick row and into the full form (#503): the daily
   case is a title and Enter, and a hand-off is never that. Quiet, so the primary
   stays the one obvious thing to press. */
.ws-tasks-new { flex: none; white-space: nowrap; }
/* The drill-in HIDES the list chrome rather than covering it, and `display: flex`
   above out-specifies the UA [hidden] rule — restore it, or the form renders under
   a list that is still on screen (the same pattern as .topbar[hidden]). */
.ws-tasks-compose[hidden],
.ws-tasks-tabs[hidden],
.ws-tasks-list[hidden] { display: none; }
.ws-tasks-notice { font-size: var(--fs-meta); }
.ws-tasks-list { display: flex; flex-direction: column; gap: var(--space-3); }

/* the triage tab strip: Active / Completed / Canceled as one control — a raised
   selected tab on a recessed track, full width — rather than three buttons
   floating over the feed. It is deliberately NOT the shared `.ws-cbar-seg`
   segmented control the #415 filter used: a segment's solid primary fill reads
   as a pressed toggle, and these are three places to be. */
.ws-tasks-tabs { display: flex; gap: var(--space-1); padding: var(--space-1);
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--surface); }
.ws-tasks-tabs button { flex: 1; min-height: 27px; padding: 0 var(--space-4);
  border: 0; border-radius: var(--radius-sm); background: transparent;
  color: var(--text-muted); font-size: var(--fs-meta); cursor: pointer;
  transition: background var(--motion-fast), color var(--motion-fast); }
.ws-tasks-tabs button:hover { color: var(--text); }
.ws-tasks-tabs button.active { background: var(--surface-raised); color: var(--text);
  font-weight: var(--fw-bold);
  box-shadow: 0 1px 3px color-mix(in srgb, var(--text) 10%, transparent); }
.ws-tasks-tabs button:focus-visible { outline: 2px solid var(--focus-ring);
  outline-offset: -2px; }

/* a triage row: status mark · title over its meta line · the category, right.
   PRIORITY IS THE LEFT EDGE-STRIPE — a 3px border every row carries, so the
   whole list's urgency is one vertical scan and normal priority needs no pill at
   all. The scale is the Tasks IDENTITY family (rose since the #658 swap; the
   owner's 2026-07-26 ruling put priority on identity, not status): urgent is
   the full rose stripe over a rose-surface wash across the whole row — the
   only row with a background of its own — high the same stripe on the ordinary
   surface, normal a lightened rose, and low the quiet hairline gray. Red stays
   reserved for overdue (a deadline fact), which is why priority may not
   borrow it. The row is clickable in full (it
   opens the Task's edit page) — and therefore a real keyboard control
   (role="button" + tabindex + Enter/Space in workspace-tasks.js), so it carries
   a visible focus ring like any other. */
.ws-task-row { display: flex; align-items: flex-start; gap: var(--space-5);
  padding: var(--space-4) var(--space-5); border: 1px solid var(--border);
  border-left: 3px solid var(--border); border-radius: var(--radius-md);
  background: var(--surface-raised); cursor: pointer;
  transition: background var(--motion-fast), border-color var(--motion-fast); }
.ws-task-row:hover { border-color: var(--border-strong); background: var(--surface-hover); }
.ws-task-row:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }
.ws-task-row.p-urgent, .ws-task-row.p-urgent:hover { border-left-color: var(--notification);
  background: var(--notification-surface); }
.ws-task-row.p-urgent:hover {
  background: color-mix(in srgb, var(--notification) 8%, var(--notification-surface)); }
.ws-task-row.p-high, .ws-task-row.p-high:hover { border-left-color: var(--notification); }
.ws-task-row.p-normal, .ws-task-row.p-normal:hover {
  border-left-color: color-mix(in srgb, var(--notification) 38%, var(--surface-raised)); }
.ws-task-row.p-low, .ws-task-row.p-low:hover { border-left-color: var(--border); }

/* the status mark: the one gesture that moves a category from the row itself
   (complete a live Task, reopen a closed one). Quiet until hovered, then the
   green of the thing it is about to do. */
.ws-task-st { width: 22px; height: 22px; flex: none; display: grid;
  place-items: center; margin-top: 1px; padding: 0; min-height: 0;
  border: 0; background: none; color: var(--text-muted); cursor: pointer; }
.ws-task-st:hover { color: var(--success); background: none; }
.ws-task-row.done .ws-task-st, .ws-task-row.done .ws-task-st:hover {
  color: var(--success); }
.ws-task-row.dead .ws-task-st, .ws-task-row.dead .ws-task-st:hover {
  color: var(--text-muted); }
.ws-task-st:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }

.ws-task-body { min-width: 0; flex: 1; display: grid; gap: var(--space-1); }
.ws-task-title { min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; color: var(--text); font-size: var(--fs-body-sm);
  font-weight: var(--fw-emph); }
/* closed work is visually SETTLED: struck and muted either way. Canceled adds no
   second treatment here — its ✕ mark is what says "will not be done". */
.ws-task-row.done .ws-task-title, .ws-task-row.dead .ws-task-title {
  color: var(--text-muted); font-weight: var(--fw-normal);
  text-decoration: line-through; text-decoration-color: var(--border-strong); }
.ws-task-meta { display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--space-3); color: var(--text-muted); font-size: var(--fs-eyebrow); }
.ws-task-ico { display: inline-flex; flex: none; }
.ws-task-ico svg { display: block; }

/* the due pill: plain while the deadline is far off, AMBER on the day itself,
   RED and counting once it has passed. An undated Task carries no pill. */
.ws-task-due { display: inline-flex; align-items: center; gap: var(--space-2);
  padding: 1px var(--space-3); border-radius: var(--radius-pill);
  background: var(--surface); color: var(--text-muted);
  font-size: var(--fs-eyebrow); font-weight: var(--fw-emph); }
.ws-task-due.today { background: var(--warning-surface); color: var(--warning); }
.ws-task-due.over { background: var(--danger-surface); color: var(--danger); }
/* the priority flag names what the stripe showed — for everything but `normal`,
   and in the stripe's own rose family (urgent's deeper -text reads on the
   row's rose-surface wash). */
.ws-task-pri { display: inline-flex; align-items: center; gap: var(--space-1);
  font-size: var(--fs-eyebrow); font-weight: var(--fw-bold); }
.ws-task-pri.urgent { color: var(--notification-text); }
.ws-task-pri.high { color: var(--notification); }
.ws-task-pri.low { color: var(--text-muted); font-weight: var(--fw-normal); }

/* the right column: the category, which on the Active tab is the block marker a
   flat triage list has instead of headings. */
.ws-task-right { flex: none; display: grid; gap: var(--space-1);
  justify-items: end; align-content: start; }
.ws-task-state { font-size: var(--fs-eyebrow); font-weight: var(--fw-bold);
  letter-spacing: .06em; text-transform: uppercase; }
.ws-task-state.in_progress { color: var(--notification); }
.ws-task-state.open { color: var(--text-muted); }

/* the assigned-by line: the server-resolved sentence, then when it was filed —
   the ONE view-only fact on the Task edit page. */
.ws-task-prov { display: inline-flex; flex-wrap: wrap; align-items: baseline;
  gap: var(--space-2); color: var(--text-muted); }
.ws-task-dot::before { content: "\00b7"; }
.ws-task-prov time { color: inherit; font-size: inherit; }

/* the shared labelled-field anatomy both drill-ins build from. Humans author raw
   Markdown in v1 (ADR-0018 D9), so the description gets a real textarea rather
   than a one-line prompt. */
.ws-task-efield, .ws-task-pfield { display: grid; gap: var(--space-2); }
.ws-task-elabel { color: var(--text-muted); font-size: var(--fs-eyebrow);
  font-weight: var(--fw-emph); }
.ws-task-efield input, .ws-task-efield textarea, .ws-task-efield select {
  width: 100%; font-size: var(--fs-body-sm); }
.ws-task-efield textarea { min-height: 7em; resize: vertical;
  font-family: var(--sans); line-height: 1.5; }
.ws-task-efield input[type="date"] { max-width: 12em; }
.ws-task-efield select { max-width: 12em; }
/* the quiet clarifier beside a label (the create form's Markdown note and its
   "you are pre-added"): part of the field, so it lives inside the label and reads
   as ordinary weight against the label's own emphasis. */
.ws-task-ehint { margin-left: var(--space-2); color: var(--text-muted);
  font-weight: var(--fw-normal); }

/* -- the drilled-in create/assign form (#503) -------------------------------- */
/* Re-expressed from prototype `tasks-assign-form.html` variant A, the near-literal
   authority. A DRILL-IN, not a modal: it takes the Utility body while the list
   chrome is hidden, so there is one surface at a time and nothing to dismiss. The
   host shell owns the Canvas head, so the form carries its own head row — which is
   the prototype's `.util-head` expressed where this module can own it. */
.ws-tasks-form { display: grid; gap: var(--space-5); }
.ws-tasks-fhead { display: flex; align-items: center; gap: var(--space-4); }
.ws-tasks-fhead .ws-tasks-fback { min-height: 26px; padding: 2px var(--space-4);
  display: inline-flex; align-items: center; }
.ws-tasks-ftitle { font-size: var(--fs-body); font-weight: var(--fw-emph); }
.ws-tasks-fnotice { font-size: var(--fs-meta); }
.ws-task-new, .ws-task-editpage { display: grid; gap: var(--space-6); }
/* two short fields that answer one question between them share a line: due +
   priority, and — on the edit page — status + the read-only Assigned fact */
.ws-task-frow2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-5); }
.ws-task-frow2 .ws-task-efield input[type="date"],
.ws-task-frow2 .ws-task-efield select { max-width: none; }
/* the footer: the fan-out contract, then the one control that acts on it */
.ws-task-ffoot { display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--space-5); border-top: 1px solid var(--border);
  padding-top: var(--space-6); }
.ws-task-fanout { flex: 1; min-width: 12em; color: var(--text-muted);
  font-size: var(--fs-meta); }
.ws-task-fanout strong { color: var(--text); font-weight: var(--fw-bold); }
.ws-task-fanout small { display: block; color: var(--text-muted);
  font-size: var(--fs-eyebrow); }
.ws-task-ffoot .primary { flex: none; }
/* the edit page's footer spacer: Save leads, Delete keeps the right edge */
.ws-task-ffoot-tail { flex: 1; }
/* the read-only Assigned fact, labelled like the fields it sits among — a
   sentence, not a control, so it takes the meta size. */
.ws-task-efact { align-content: start; font-size: var(--fs-meta); }

/* the org-user autocomplete multi-select: chips and a query field share one
   field-shaped box, so the whole control reads as one input rather than a list
   beside one. It is `cursor: text` for the same reason — the box IS the field. */
.ws-task-picker { position: relative; display: flex; flex-wrap: wrap;
  gap: var(--space-3); padding: var(--space-3);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface); cursor: text; }
.ws-task-picker:focus-within { outline: 2px solid var(--focus-ring);
  outline-offset: 1px; }
.ws-task-pinput { flex: 1; min-width: 8em; padding: var(--space-1) var(--space-2);
  border: 0; background: transparent; color: var(--text);
  font-size: var(--fs-body-sm); }
.ws-task-pinput:focus { outline: none; }
/* a named person. The reader's OWN chip is the quiet neutral one — it is the
   default, not a choice they made — and every chip removes. Person chips and
   the .ws-task-av avatar mark PEOPLE, not the Task identity, so they keep the
   accent — deliberately outside the #658 Tasks-rose swap. */
.ws-task-pchip { display: inline-flex; align-items: center; gap: var(--space-2);
  padding: var(--space-1) var(--space-2) var(--space-1) var(--space-4);
  border-radius: var(--radius-pill); background: var(--accent-surface);
  color: var(--accent-text); font-size: var(--fs-meta);
  font-weight: var(--fw-emph); }
.ws-task-pchip.self { background: var(--surface-hover); color: var(--text-muted); }
.ws-task-pdrop { min-height: 0; padding: 0 var(--space-2); border: 0;
  background: none; color: inherit; font-size: var(--fs-body-sm); line-height: 1;
  cursor: pointer; }
.ws-task-pdrop:hover { background: none; color: var(--danger); }
.ws-task-pdrop:focus-visible { outline: 2px solid var(--focus-ring);
  outline-offset: 1px; }

/* the popover: the candidate set, anchored under the field it filters */
.ws-task-pop { position: absolute; top: calc(100% + var(--space-2)); left: 0;
  right: 0; z-index: var(--z-popover); max-height: 15em; overflow-y: auto;
  border: 1px solid var(--border-strong); border-radius: var(--radius-md);
  background: var(--surface); box-shadow: var(--shadow-popover); }
.ws-task-pop button { display: flex; align-items: center; gap: var(--space-4);
  width: 100%; min-height: 0; border: 0; border-radius: 0;
  background: transparent; padding: var(--space-4) var(--space-5);
  text-align: left; }
.ws-task-pop button:hover { background: var(--surface-hover); }
.ws-task-pop button:focus-visible { outline: 2px solid var(--focus-ring);
  outline-offset: -2px; }
.ws-task-av { width: 26px; height: 26px; flex: none; display: grid;
  place-items: center; border-radius: 50%; background: var(--accent-surface);
  color: var(--accent-text); font-size: var(--fs-eyebrow);
  font-weight: var(--fw-bold); }
.ws-task-pnm { min-width: 0; display: grid; color: var(--text);
  font-size: var(--fs-body-sm); }
/* the disambiguator — the username or email the assignable-users read carries so
   two same-named colleagues are two choices rather than one made twice */
.ws-task-pnm small { color: var(--text-muted); font-size: var(--fs-eyebrow); }
.ws-task-pnone { padding: var(--space-5) var(--space-6); color: var(--text-muted);
  font-size: var(--fs-meta); }

/* Region-layer states: a calm loading/empty line and a genuine error card with
   Retry (danger reserved for a real failure, per the #414 layered contract). */
.ws-tasks-state { padding: var(--space-8); text-align: center;
  color: var(--text-muted); font-size: var(--fs-body-sm); }
.ws-tasks-error { display: flex; flex-direction: column; align-items: center;
  gap: var(--space-5); padding: var(--space-8); text-align: center;
  color: var(--danger); font-size: var(--fs-body-sm); }

/* -- personal Notifications inbox + its live arrival cues (#416/#483) --------- */
/* Layer 4 of the layered-feedback contract: durable outcomes, one accent-violet
   Notification identity (#658 swap — rose moved to Tasks; the glyph varies by
   kind, the mark stays accent — a Notification is one identity, not four
   semantic colors). The inbox
   fills the shared single-owner Utility host; live delivery's two cues (the
   sticky arrival pill and the in-feed slide-in) are at the end of this block,
   where the retired transient toast stack used to be. */
.ws-notes { display: flex; flex-direction: column; gap: var(--space-4); }
/* Notifications recolors the shared control to the accent family: same track,
   sizing, hover + focus as Tasks, but the selected segment reads as the gentle
   accent wash rather than the solid primary fill (a quieter grade of the same
   violet). :hover before .active so a hovered selected segment keeps its
   accent fill; font-weight is inherited from the base .active rule. */
/* the tab bar (#482, prototype #456): the status filter with Mark all read at its
   RIGHT end. The filter keeps the right-hand seat it shipped in and the flush sits
   at the end of the row, so the bar reads as one cluster of tab-level controls
   rather than two things floating over the feed. */
.ws-notes-bar { display: flex; align-items: center; justify-content: flex-end;
  gap: var(--space-4); }
/* quiet by design: a backlog flush is not the loudest thing on the panel */
.ws-notes-markall { min-height: 26px; padding: 3px var(--space-4);
  font-size: var(--fs-meta); color: var(--text-muted); }
.ws-notes-markall:hover { color: var(--accent-text);
  border-color: color-mix(in srgb, var(--accent) 30%, var(--border)); }
.ws-notes-markall[hidden] { display: none; }
.ws-notes-filter {
  border-color: color-mix(in srgb, var(--accent) 30%, var(--border)); }
.ws-notes-filter button:hover { color: var(--accent-text);
  background: color-mix(in srgb, var(--accent) 14%, var(--surface-raised)); }
.ws-notes-filter button.active { color: var(--accent-text);
  background: var(--accent-surface); }
.ws-notes-notice { font-size: var(--fs-meta); }
.ws-notes-feed { display: flex; flex-direction: column; }

/* one feed item (#480, prototype #451 variant A + #456): accent kind-mark · body
   (title · 2-line-clamped summary · more/less · provenance meta line · the one
   named Action) · the Read/Unread toggle at the right edge. The body is a plain
   container — it expands the summary in place and never navigates. A read item
   dims as a whole rather than restyling its parts. */
.ws-note { display: grid; grid-template-columns: 34px minmax(0, 1fr) auto;
  gap: var(--space-4); align-items: start; width: 100%; text-align: left;
  padding: var(--space-4) var(--space-3); border: 0; border-radius: var(--radius);
  background: transparent; color: var(--text); }
.ws-note + .ws-note { border-top: 1px solid var(--border); border-radius: 0; }
.ws-note:hover { background: var(--surface-hover); }
.ws-note-body { min-width: 0; display: grid; gap: var(--space-1);
  justify-items: start; }
/* only a row with text still hidden behind the clamp reads as clickable */
.ws-note-body.tappable { cursor: pointer; }
.ws-note-state:focus-visible, .ws-note-more:focus-visible,
.ws-note-chat:focus-visible, .ws-note-action:focus-visible {
  outline: none; box-shadow: 0 0 0 2px var(--focus-ring); }
.ws-note-state { align-self: start; padding: var(--space-2) var(--space-5);
  color: var(--accent-text); background: var(--accent-surface);
  border-color: color-mix(in srgb, var(--accent) 30%, var(--border));
  font-size: var(--fs-meta); cursor: pointer; }
.ws-note-state:hover { border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 16%, var(--surface-raised)); }
.ws-note-state:disabled { opacity: .6; cursor: wait; }
.ws-note-mark { width: 34px; height: 34px; display: grid; place-items: center;
  flex: none; border-radius: var(--radius);
  color: var(--accent-text); background: var(--accent-surface); }
.ws-note-title { max-width: 100%; font-size: var(--fs-body-sm); font-weight: 650;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* the clamp: two lines until the reader opens it, then the full text */
.ws-note-summary { color: var(--text-muted); font-size: var(--fs-meta);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; }
.ws-note-summary.expanded { display: block; }
/* the subdued more/less toggle — present only when the clamp hides text */
.ws-note-more { min-height: 20px; padding: 0 var(--space-1); border: 0;
  background: transparent; color: var(--text-muted); font-size: var(--fs-eyebrow);
  text-decoration: underline; text-underline-offset: 2px; cursor: pointer; }
.ws-note-more:hover { color: var(--text); background: transparent; }
/* the provenance meta line: who · via which Agent · from your chat · when */
.ws-note-meta { display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--space-3); color: var(--text-muted); font-size: var(--fs-eyebrow); }
.ws-note-dot::before { content: "\00b7"; }
.ws-note-meta time { color: inherit; font-size: inherit; }
.ws-note-chat { min-height: 0; padding: 0; border: 0; background: transparent;
  color: var(--text-muted); font-size: inherit; text-decoration: underline;
  text-underline-offset: 2px; cursor: pointer; }
.ws-note-chat:hover { color: var(--text); background: transparent; }
/* the exemption cue (#482, prototype #456): this row was deliberately kept unread,
   so the next open leaves it alone. An accent pill in the meta line — the one place
   a row states its own standing instruction. */
.ws-note-kept { padding: 0 var(--space-3); border-radius: var(--radius-pill);
  background: var(--accent-surface); color: var(--accent-text);
  font-size: var(--fs-eyebrow); font-weight: 700; }
/* the one named typed Action — the row's only navigation path. Since #500 the slot
   holds one of TWO grades: this Action Button, or the shared demoted external link
   (`.ws-extlink` + `.ws-exthost`, defined with the Action System's own treatments) for
   a URL no trusted_origins provider vouched for. The old `.ws-note-origin` host hint
   retired with it: one host disclosure, defined once, in both renderings — and a
   VOUCHED link needs none, since promotion is what replaces the disclosure. */
.ws-note-acts { display: flex; align-items: center; gap: var(--space-4);
  margin-top: var(--space-2); }
.ws-note-action { display: inline-flex; align-items: center; gap: var(--space-3);
  min-height: 26px; padding: 3px var(--space-5); font-size: var(--fs-meta);
  cursor: pointer; }
.ws-note-act-ico { display: grid; place-items: center; }
/* The Action Button in a window whose host cannot dispatch its verb (#496 security
   review F2, ADR-0018's fail-closed doctrine): the popped-out Canvas has no
   `ws:open-chat`/`ws:open-canvas` listener, so the control keeps the row's geometry
   and gives up every promise of being one. It is a <span>, so there is no keyboard
   affordance to suppress — only the button chrome. `.ws-ia-inert` carries the muted
   ink and the default cursor, exactly as it does for a refused Inline Action; what
   this adds is dropping the button's own fill and border. */
.ws-note-action.ws-ia-inert, .ws-note-action.ws-ia-inert:hover {
  background: none; border: 0; box-shadow: none; }
.ws-note.read { opacity: .62; }

/* the paging foot (#481, prototype #456): one Show more centered above a subdued
   "Showing 10 of 12 · 2 older" count. It sits outside the feed — it is about the
   feed, not a row in it — and renders nothing at all for a tab that never needed
   paging, so a short inbox carries no dead chrome. */
.ws-notes-foot { display: grid; gap: var(--space-3); justify-items: center;
  padding: var(--space-2) var(--space-6) var(--space-8); }
/* the foot is built once and shown/hidden (so paging never drops keyboard focus),
   so its own [hidden] needs to beat the display:grid above */
.ws-notes-foot[hidden] { display: none; }
.ws-notes-more { min-width: 140px; }
.ws-notes-count { color: var(--text-muted); font-size: var(--fs-eyebrow); }

/* the empty state: calm inbox glyph, no alarm (nothing is wrong). */
.ws-notes-empty-ico { color: var(--text-muted); margin-bottom: var(--space-3); }
.ws-util-empty p { margin: var(--space-2) 0 0; color: var(--text-muted); }

/* Region-layer states (mirror the Tasks bodies): calm loading, real error. */
.ws-notes-state { padding: var(--space-8); text-align: center;
  color: var(--text-muted); font-size: var(--fs-body-sm); }
.ws-notes-error { display: flex; flex-direction: column; align-items: center;
  gap: var(--space-5); padding: var(--space-8); text-align: center;
  color: var(--danger); font-size: var(--fs-body-sm); }

/* the rail unread cue (#480, prototype #456): a real numeric badge at the
   Notifications rail button's top-right — 99+ cap, hidden at zero, a brief bump
   when the count changes. It is aria-hidden; the count lives in the button's
   accessible name, so the cue reads once. Replaces the shipped edge dot.
   (The containing block is the rail button's own `position: relative` in the
   Utility Rail block above — the retired `.ws-util-dot` re-declared it here; a
   second copy is not a second guarantee.) */
/* ONE rail-badge geometry, two identity fills (#498, families swapped by #658):
   the accent-violet unread count and the rose Tasks outstanding count are the
   same object in two families, so the shape is declared once and each family
   supplies only its own fill. Both bear white, so both take a fill that is dark
   enough for it in BOTH modes — `--primary`, the token every white-on-violet
   fill in the Platform already uses, and `--notification-strong` (DR25 /
   Decision 103). Never `--accent`, which is light in dark mode and would
   repeat the contrast failure DR25 fixed. */
.ws-note-badge, .ws-task-badge { position: absolute; top: 1px; right: 1px;
  min-width: 16px; height: 16px; padding: 0 var(--space-2);
  border-radius: var(--radius-pill); display: grid; place-items: center;
  color: var(--on-primary); font: 700 10px/1 var(--mono); }
.ws-note-badge[hidden], .ws-task-badge[hidden] { display: none; }
.ws-note-badge { background: var(--primary);
  transition: transform var(--motion-fast); }
.ws-note-badge.bump { transform: scale(1.35); }
.ws-task-badge { background: var(--notification-strong); }

/* live delivery (#483, spec #476, prototype #455 variant C) — the shipped
   transient toast stack is RETIRED with it: it announced only what the same tab
   had just done and then took itself away again, which is the opposite of a
   durable outcome. Two cues replace it, and which one appears is decided by
   whether Notifications are already on screen.

   1. THE STICKY WHISPER ARRIVAL PILL, in the Workspace header immediately left
   of the New chat primary: the latest title plus "+N", no Actions, and no
   auto-dismiss — it waits for the reader instead of vanishing. It slides DOWN
   from the header top, which is why the header clips: the pill animates within
   the header and never over the work.

   The clip is scoped to the Workspace shell like every other `.ws-topbar` rule
   (the sibling `flex: none` in the Workspace layout block), not applied to the
   bare class: `overflow: hidden` on a header is a real constraint — anything
   later anchored in this header that must escape it (a dropdown, a popover)
   would be clipped no matter its `z-index` — so it stays a stated property of
   the *Workspace* header rather than of the class anywhere it lands. */
.ws-shell .ws-topbar { overflow: hidden; }
.ws-arrival-slot { position: relative; display: flex; align-items: center;
  min-height: 32px; }
.ws-arrival { display: inline-flex; align-items: center; gap: var(--space-4);
  max-width: 340px; min-height: 30px;
  padding: var(--space-3) var(--space-6);
  border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--border));
  border-radius: var(--radius-pill); background: var(--accent-surface);
  color: var(--accent-text); cursor: pointer;
  transform: translateY(-46px); opacity: 0;
  transition: transform var(--motion-slide), opacity var(--motion-slide); }
.ws-arrival.shown { transform: translateY(0); opacity: 1; }
/* ...and back up the way it came, which is why the node is kept for the length of
   this transition rather than removed on the spot. It stops being a control the
   moment it is dismissed: a click landing on a pill already on its way out must
   not open the Utility a second time. */
.ws-arrival.leaving { transform: translateY(-46px); opacity: 0;
  pointer-events: none; }
.ws-arrival:hover { border-color: var(--accent);
  background: color-mix(in srgb, var(--accent-surface) 80%,
                        var(--accent) 20%); }
.ws-arrival:focus-visible { outline: none;
  box-shadow: 0 0 0 2px var(--focus-ring); }
.ws-arrival-title { font-size: var(--fs-meta); font-weight: 650;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ws-arrival-count { flex: none; padding: 0 var(--space-3);
  border-radius: var(--radius-pill); background: var(--primary);
  color: var(--on-primary); font: 700 10px/1.6 var(--mono); }

/* ...and the slot's other, quieter occupant: the viewing session's CLOSE REPORT
   (#482) — "3 moved to Read". It rides the pill's own slide so the header keeps one
   motion, but it is not a pill: no accent, no pointer, no focus ring and no
   dismiss, because nothing durable happened and there is nowhere for a click to go.
   Muted text on the plain raised surface stops a receipt from ever reading as loud
   as an arrival — and an arrival wins the slot outright (the JS drops this on the
   spot). It fades itself after a few seconds. */
.ws-arrival-report { display: inline-flex; align-items: center;
  max-width: 340px; min-height: 30px;
  padding: var(--space-3) var(--space-6);
  border: 1px solid var(--border); border-radius: var(--radius-pill);
  background: var(--surface-raised); color: var(--text-muted);
  font-size: var(--fs-meta); white-space: nowrap;
  transform: translateY(-46px); opacity: 0;
  transition: transform var(--motion-slide), opacity var(--motion-slide); }
.ws-arrival-report.shown { transform: translateY(0); opacity: 1; }
.ws-arrival-report.leaving { transform: translateY(-46px); opacity: 0; }

/* 2. THE IN-FEED SLIDE-IN, used INSTEAD of the pill when a Notifications feed is
   already on screen: the row arrives from the RIGHT (the Canvas-open motion,
   inside the feed) while the rows below slide down, then flashes accent briefly to
   mark where it landed. `.entered` is entrance scaffolding and the row drops it
   once the slide is over (settleEntrance): its max-height is what makes the rows
   below travel, and a row that KEPT it could never grow, so a later in-place
   summary expand would be clipped at this ceiling. */
.ws-note.enter { transform: translateX(110%); opacity: 0; max-height: 0;
  padding-top: 0; padding-bottom: 0; overflow: hidden; }
.ws-note.entered { transform: translateX(0); opacity: 1; max-height: 240px;
  transition: transform var(--motion-slide), opacity var(--motion-slide),
    max-height var(--motion-slide), padding var(--motion-slide); }
.ws-note.flash { animation: ws-note-arrive 1.6s ease; }
@keyframes ws-note-arrive {
  from { background: var(--accent-surface); }
  to { background: transparent; }
}

/* -- Inline Action chips (#499, ADR-0018 D2, prototype tasks-detail-affordances) */
/* The Action System's inline rendering: a small chip that FLOWS with the sentence
   it was written into, in the composer `/skill` chip's visual language — a
   per-type icon plus the link text. Re-expressed near-literally from the approved
   prototype's `.iachip` through the design system's own tokens: the prototype's
   --file-soft/--agent-soft/--primary-soft pairs are this system's
   --file-surface/--agent-surface/--accent-surface, each with the readable -text
   role on top of it, so a Theme repaints a chip the way it repaints everything
   else in that identity family.

   Shared vocabulary, not a Notifications detail: the summary is the first carrier
   and the Task description is the next, so it lives outside both their sections.
   Colored per identity axis (File blue, Agent green, chat and task accent-violet —
   Tasks stay accent-violet, no fifth identity family), with chat and task
   separating by ICON only, which is why the icon is not optional chrome.

   `vertical-align: baseline` and the em-relative size are what make it read as
   part of the prose rather than a control dropped into it; a real <button> is what
   makes it reachable (see actions.js on why not the prototype's styled <a>).

   The axis tint rides two SCOPED properties rather than a background per state:
   the chip is a <button>, and the global `button:hover` would otherwise repaint it
   `--surface-hover` the moment a pointer touched it. One tint, declared once per
   axis, re-asserted on hover. (The `.ws-code` treatment, #498.) */
.ws-iachip { --ia-tint: var(--surface-hover); --ia-ink: var(--text);
  display: inline-flex; align-items: center; gap: var(--space-2);
  padding: 1px var(--space-3) 1px var(--space-2); border: 0;
  border-radius: var(--radius-sm); background: var(--ia-tint);
  color: var(--ia-ink); font-size: 0.93em; font-weight: var(--fw-emph);
  line-height: 1.3; vertical-align: baseline; cursor: pointer; }
.ws-iachip.file { --ia-tint: var(--file-surface); --ia-ink: var(--file-text); }
.ws-iachip.agent { --ia-tint: var(--agent-surface); --ia-ink: var(--agent-text); }
.ws-iachip.chat, .ws-iachip.task {
  --ia-tint: var(--accent-surface); --ia-ink: var(--accent-text); }
/* Hover is a brightness nudge rather than a second colour per axis: four families
   times two modes is eight more tokens to keep honest, and the tint IS the
   identity — a chip that changed hue on hover would change what it claims to be. */
.ws-iachip:hover { background: var(--ia-tint); filter: brightness(0.95); }
html[data-theme="dark"] .ws-iachip:hover { filter: brightness(1.15); }
.ws-iachip:focus-visible { outline: 2px solid var(--focus-ring);
  outline-offset: 1px; }
.ws-iachip-ico { flex: none; display: flex; }

/* The staged-instruction disclosure (#496 security review V2, ADR-0018 D3 as
   amended 2026-07-26). A spawn/launch control stages words its READER did not
   write, behind a label their author chose — so the payload is shown, quoted,
   beside the label, with the full text on `title` and in the accessible name.

   Deliberately the same kind of statement as the demoted link's `.ws-exthost`
   below ("here is what this control will actually do"), so it borrows that
   treatment rather than inventing a second vocabulary for it. Lighter and a hair
   smaller than the label, because the label is still the primary words — the
   disclosure is additive, never a replacement. Reduced opacity on the chip's own
   `--ia-ink` rather than `--text-muted`: inside a tinted chip a neutral grey reads
   as disabled. */
.ws-iachip-staged, .ws-note-action .ws-iachip-staged {
  font-weight: var(--fw-normal); font-size: 0.92em; opacity: 0.75;
  max-width: 22em; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }

/* An Action the promotion pass refused — an unlearned type, a malformed shape.
   It keeps its words and loses its destination (actions.js strips the href), so it
   reads as the emphasis of a link with none of the promise of one: no pointer, no
   underline, nothing to click. A future Platform chips it; today it is text. */
.ws-ia-inert, .ws-ia-inert:hover { color: var(--text-muted);
  text-decoration: none; cursor: default; }

/* The fourth Action grade — a **trusted-origin URL** promoted to chip grade (#500,
   ADR-0018 D4). Neutral rather than a fifth identity family: an external
   destination belongs to no Module, so it borrows the hover wash plus a hairline
   instead of an axis tint (the prototype's `.iachip.url`). The `popout` icon is what
   says "this leaves the Platform" — and it is the only thing that may, since a
   promoted link is otherwise a chip like any other. */
.ws-iachip.url { --ia-tint: var(--surface-hover); --ia-ink: var(--text-muted);
  border: 1px solid var(--border); }

/* -- the demoted external URL (#500, ADR-0018 D4, prototype tasks-detail-affordances) */
/* The third Action grade: an `http(s)` link no `trusted_origins` provider vouched
   for. It stays a real link — clickable, new tab, `noopener` — and is deliberately
   NOT chip-shaped, because the tier IS "clickable but never dressed up as a platform
   control": it reads as prose with an underline rather than as a control with a
   fill. Re-expressed from the prototype's `.extlink` / `.exthost` pair through the
   design system's own tokens.

   Both roles land on `--text-muted` — the system carries no third, fainter text
   token (the prototype's `--text-soft` / `--text-faint` split has one home here) —
   so the em-relative step down is what separates the host from the words. Relative
   rather than `--fs-eyebrow`: this flows inside a sentence and has to scale with the
   prose around it, not with the chrome. */
.ws-extlink { color: var(--text-muted); text-decoration: underline;
  text-underline-offset: 2px; }
.ws-extlink:hover { color: var(--text); }
.ws-extlink:focus-visible { outline: 2px solid var(--focus-ring);
  outline-offset: 1px; }
/* The host disclosure. `nowrap` because the host IS the anti-masquerade control: a
   line break inside `(status.vendor.com)` leaves the reader to reassemble a
   destination, and a bare `evil.example` starting a line reads as prose.
   `unicode-bidi: isolate` for the same reason, one layer down: this span sits
   between two runs of author-supplied text (the link's own words, and whatever
   labeled the Action), and without isolation an RTL run next to it can reorder the
   host *visually* while the DOM is untouched — the disclosure would still say
   `evil.example` to a screen reader and something else to the eye. Both write seams
   strip the bidi class already; this is the defense that does not depend on that. */
.ws-exthost { color: var(--text-muted); font-size: 0.86em; white-space: nowrap;
  unicode-bidi: isolate; }
.ws-exthost-ico { display: inline-flex; vertical-align: -1px;
  margin-left: var(--space-1); }

/* -- single-owner Utility Canvas mechanics (#413, prototype #390 Variant B) -- */
/* Maximize fills the region between the left navigation and the Utility Rail
   (a flex sibling growing to fill, so it is correct at any nav width) and hides
   the Center; Restore/Escape return it to the docked inset size. The chat DOM
   is only hidden, never unmounted, so its state survives. */
.ws-util-canvas.maximized { flex: 1 1 auto; width: auto; min-width: 0;
  max-width: none; margin: 0; border-radius: 0; border-left: 1px solid var(--border); }
body.ws-shell:has(.ws-util-canvas.maximized) .app-content { display: none; }
body.ws-shell:has(.ws-util-canvas.maximized) .ws-canvas-resize { display: none; }

/* per-file rail entries below the divider — each open file owns one; a hover/
   focus close (×) and an unsaved mini-dot. Active takes the File identity. */
.ws-util-files { display: flex; flex-direction: column; align-items: center; gap: 7px; width: 100%; }
.ws-railfile { position: relative; width: 38px; height: 38px; }
/* open-file Utilities read File-blue at rest (locked #390 File family), like the
   File Explorer above them. */
.ws-railfile-open { width: 38px; height: 38px; display: grid; place-items: center;
  padding: 0; color: var(--file); background: transparent; border-color: transparent;
  cursor: pointer; }
.ws-railfile-open:hover, .ws-railfile-open.active {
  color: var(--file); background: var(--file-surface); }
.ws-railfile-open.popped { color: var(--file); border-color: var(--file); border-style: dashed; }
.ws-railfile .ws-mini-dot { position: absolute; right: 4px; top: 4px; width: 7px; height: 7px;
  border-radius: 50%; background: var(--file); border: 1px solid var(--surface-raised); }
.ws-railfile-close { position: absolute; z-index: 2; top: -3px; right: -3px; width: 17px; height: 17px;
  display: grid; place-items: center; padding: 0; line-height: 1; opacity: 0; color: var(--text-muted);
  background: var(--surface-raised); border: 1px solid var(--border-strong); border-radius: 50%;
  cursor: pointer; transition: opacity var(--motion-fast); }
.ws-railfile:hover .ws-railfile-close, .ws-railfile:focus-within .ws-railfile-close { opacity: 1; }
.ws-railfile-close:hover { color: var(--danger); }
/* "Close All": a rail entry shown only while files are open (canvas-shell.js
   renders it after the per-file entries). Sized/shaped like the rail buttons,
   muted at rest with a danger wash on hover/focus to signal it dismisses
   everything. Carries the shared .ws-tip/.ws-tooltip "Close All" flyout. */
.ws-railfile-closeall { width: 38px; height: 38px; position: relative; display: grid;
  place-items: center; padding: 0; color: var(--text-muted); background: transparent;
  border-color: transparent; cursor: pointer; }
.ws-railfile-closeall:hover, .ws-railfile-closeall:focus-visible {
  color: var(--danger); background: var(--danger-surface); }

/* an open-file Utility panel: its own flexible toolbar bar + the core body.
   height:100% gives the panel a definite height off .ws-util-body (a
   flex-sized, block-layout container) so cores that fill their host — the
   iframe viewers .ws-pdf / .ws-html-core (height:100%) — resolve to the full
   Canvas height instead of collapsing to the iframe's intrinsic default. */
.ws-util-filepanel { display: flex; flex-direction: column; min-height: 0; flex: 1;
  height: 100%; }
.ws-util-filepanel[hidden] { display: none; }
.ws-util-toolbar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 6px 4px 9px; border-bottom: 1px solid var(--border); }
.ws-util-core { flex: 1; min-height: 0; overflow: auto; padding-top: 9px; }

/* the docked ownership marker shown while a Utility is popped out (no mirror) */
.ws-util-detached { display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 10px; min-height: 200px; padding: 24px; text-align: center;
  color: var(--text-muted); }
.ws-util-detached h3 { margin: 0; color: var(--text); font-size: var(--fs-title);
  font-weight: var(--fw-emph); }
.ws-util-detached p { margin: 0; max-width: 260px; font-size: var(--fs-body-sm); line-height: 1.6; }
.ws-util-detached-acts { display: flex; gap: 8px; }

/* the standalone /canvas window's owned-Utility title in its toolbar */
.ws-canvas-solotitle { font-weight: var(--fw-emph); color: var(--text); }

@media (prefers-reduced-motion: reduce) {
  .ws-caret, .ws-collapse, .ws-nav-item, .ws-group-trigger { transition: none; }
  /* no slide — the Canvas shows/hides in place. JS also takes an instant
     open/close path under reduced motion (never adds .ws-anim, hides
     synchronously), so no transitionend is awaited and it can't get stuck. */
  .ws-util-canvas, .ws-util-canvas.ws-anim { transition: none; }
  /* the unread badge changes its number, not its size — an instant jump-scale
     is worse than no bump at all. */
  .ws-note-badge { transition: none; }
  .ws-note-badge.bump { transform: none; }
  /* an arrival still ARRIVES under reduced motion — it just does not travel: the
     pill and the new row appear in place, and the accent flash is dropped. A
     dismissed pill likewise goes at once rather than lingering, visible and
     inert, for the length of a slide it is not performing. */
  .ws-arrival { transition: none; transform: none; opacity: 1; }
  .ws-arrival.leaving { transform: none; opacity: 0; }
  /* the close report likewise appears and goes in place — it is a line of text
     that says something true for a few seconds, and travel adds nothing to it. */
  .ws-arrival-report { transition: none; transform: none; opacity: 1; }
  .ws-arrival-report.leaving { transform: none; opacity: 0; }
  .ws-note.enter { transform: none; opacity: 1; max-height: none;
    padding-top: var(--space-4); padding-bottom: var(--space-4); }
  .ws-note.entered { transition: none; }
  .ws-note.flash { animation: none; }
}

/* narrow viewports: the Utility Canvas yields, the rails stay usable (#398).
   No min-width here — a floor would stop the width from collapsing to 0 during
   the open/close animation; the resize controller (pane-resize.js) clamps the
   real minimum in JS. */
@media (max-width: 900px) {
  .ws-canvas-resize { display: none; }
  .ws-util-canvas { width: min(420px, 60vw); }
}

/* ===================================================================
   Organization Theme Preset authoring (#426, spec #400; prototype #389
   Theme-authoring geometry, locked b45316e). The owner-gated surface:
   a shared full-area head (takeoverHeader) over a [preset list · author ·
   resizer · live Workspace preview] body, inside the persistent Admin
   rail. Every color is a semantic token; only the scoped .tp-pw preview
   stage carries the *authored* palette (set inline by theme-presets.js) —
   the surrounding Admin chrome stays fixed Yatsar (AC2). Layout tracks are
   structural px like the bench/Form takeover; colors are never hardcoded.
   =================================================================== */
.tp-root { position: relative; display: flex; flex-direction: column;
  height: calc(100vh - var(--topbar-h, 48px)); min-height: 0; overflow: hidden; }
.tp-body {
  --tp-author: 42%;
  flex: 1; min-height: 0; display: grid;
  grid-template-columns: 250px minmax(360px, var(--tp-author)) 8px minmax(420px, 1fr);
}
.tp-presets, .tp-author, .tp-preview { min-width: 0; min-height: 0; display: flex; flex-direction: column; }
.tp-presets { border-right: 1px solid var(--border); background: var(--surface-raised); overflow-y: auto; }
.tp-author { border-right: 1px solid var(--border); }
.tp-preview { background: var(--surface); }

/* --- preset list + lifecycle (#397) --- */
.tp-presets-head, .tp-author-head, .tp-preview-head {
  display: flex; align-items: center; gap: var(--space-4);
  padding: var(--space-5) var(--space-5); border-bottom: 1px solid var(--border);
  background: var(--surface-raised);
}
.tp-preset-list { display: flex; flex-direction: column; padding: var(--space-4); gap: var(--space-3); }
.tp-preset-row {
  display: block; text-align: left; padding: var(--space-4) var(--space-5);
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--surface); cursor: pointer;
}
.tp-preset-row.sel { border-color: transparent; background: var(--accent-surface); color: var(--accent-text); }
.tp-preset-main { display: flex; flex-direction: column; gap: var(--space-3); }
.tp-preset-meta { display: flex; flex-wrap: wrap; gap: var(--space-3); }
.tp-chip {
  font-size: var(--fs-eyebrow); padding: 1px var(--space-3); border-radius: var(--radius-pill);
  background: var(--surface-hover); color: var(--text-muted); border: 1px solid var(--border);
}
.tp-chip.published { color: var(--success); background: var(--success-surface); border-color: transparent; }
.tp-chip.draft { color: var(--accent-text); background: var(--accent-surface); border-color: transparent; }
.tp-chip.archived { color: var(--text-muted); }
.tp-chip.default { color: var(--on-primary); background: var(--primary); border-color: transparent; }
.tp-lifecycle {
  margin-top: auto; display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3);
  padding: var(--space-5); border-top: 1px solid var(--border); background: var(--surface-raised);
}
.tp-rollback { display: inline-flex; align-items: center; gap: var(--space-3); }

/* --- author pane (#389: Manual / Co-Pilot) --- */
.tp-author-body { flex: 1; min-height: 0; overflow-y: auto; padding: var(--space-5); }
.tp-toggle { display: inline-flex; border: 1px solid var(--border); border-radius: var(--radius-md); overflow: hidden; }
.tp-toggle button { border: 0; border-radius: 0; background: transparent; color: var(--text-muted); padding: var(--space-3) var(--space-4); }
.tp-toggle button.on { background: var(--surface-hover); color: var(--text); font-weight: var(--fw-emph); }
.tp-manual { display: grid; gap: var(--space-5); }
.tp-section { border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--surface-raised); overflow: hidden; }
.tp-section-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--space-4) var(--space-5); border-bottom: 1px solid var(--border); font-weight: var(--fw-emph);
}
.tp-section-body { display: grid; gap: var(--space-4); padding: var(--space-5); }
.tp-field { display: flex; flex-direction: column; gap: var(--space-3); }
.tp-field > span { font-size: var(--fs-meta); color: var(--text-muted); }
.tp-name-static { display: flex; flex-direction: column; gap: 2px; font-weight: var(--fw-emph); }
.tp-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-4); }
.tp-muted { color: var(--text-muted); font-size: var(--fs-meta); }
.tp-groups { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-3); }
.tp-groups button {
  min-width: 0; border: 1px solid transparent; background: var(--surface); color: var(--text-muted);
  padding: var(--space-3); border-radius: var(--radius-sm); font-size: var(--fs-meta);
}
.tp-groups button.on { background: var(--surface-raised); color: var(--text); box-shadow: inset 0 0 0 1px var(--border); font-weight: var(--fw-emph); }
.tp-groups button small { display: block; color: var(--text-muted); }
.tp-token-grid { display: grid; gap: var(--space-3); }
.tp-token {
  display: grid; grid-template-columns: minmax(110px, 1fr) 34px 96px 30px;
  gap: var(--space-3); align-items: center;
  padding: var(--space-3) var(--space-4); border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.tp-token-name { min-width: 0; }
.tp-token-name strong, .tp-token-name small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tp-token-name small { color: var(--secondary); font-family: var(--mono); font-size: var(--fs-eyebrow); }
.tp-token input[type=color] { width: 34px; height: 30px; padding: 2px; }
.tp-token input[type=text] { width: 96px; padding-inline: var(--space-3); font: var(--fs-eyebrow) var(--mono); }
.tp-token input.bad { border-color: var(--danger); }
.tp-token-reset { width: 30px; padding: var(--space-2); }
.tp-token-reset:disabled { visibility: hidden; }

/* --- Co-Pilot (#389 wf-theme-copilot) --- */
.tp-copilot { display: flex; flex-direction: column; gap: var(--space-4); height: 100%; }
.tp-copilot-log { display: flex; flex-direction: column; gap: var(--space-4); }
.tp-msg { padding: var(--space-4) var(--space-5); border-radius: var(--radius-md); background: var(--surface-raised); border: 1px solid var(--border); }
.tp-msg.user { background: var(--accent-surface); color: var(--accent-text); border-color: transparent; align-self: flex-end; }
.tp-suggestions { display: flex; flex-wrap: wrap; gap: var(--space-3); }
.tp-suggestions button { color: var(--accent-text); background: var(--accent-surface); border-color: transparent; }
.tp-composer { display: flex; gap: var(--space-3); margin-top: auto; }
.tp-composer textarea { flex: 1; min-height: 60px; resize: vertical; }

/* --- live Workspace preview (#398 rail · #388 Center · #390 Utilities) --- */
.tp-preview-note { color: var(--text-muted); font-size: var(--fs-meta); }
.tp-preview-mode { font-size: var(--fs-eyebrow); color: var(--text-muted); }
.tp-pw-spacer { flex: 1; }
.tp-pw {
  flex: 1; min-height: 0; margin: var(--space-5); overflow: hidden;
  display: grid; grid-template-columns: 150px 1fr 160px;
  border: 1px solid var(--border); border-radius: var(--radius-card);
  /* the mock renders ENTIRELY through the authored tokens set inline (AC2) */
  background: var(--surface); color: var(--text);
}
.tp-pw-rail, .tp-pw-utility { padding: var(--space-4); display: flex; flex-direction: column; gap: var(--space-3); background: var(--surface-raised); }
.tp-pw-rail { border-right: 1px solid var(--border); }
.tp-pw-utility { border-left: 1px solid var(--border); }
.tp-pw-brand { display: flex; align-items: center; gap: var(--space-3); font-weight: var(--fw-bold); }
.tp-pw-mark {
  display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px;
  border-radius: var(--radius-sm); background: var(--primary); color: var(--on-primary); font-weight: var(--fw-bold);
}
.tp-pw-nav { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-3); border-radius: var(--radius-sm); color: var(--text-muted); font-size: var(--fs-body-sm); }
.tp-pw-nav.on { background: var(--accent-surface); color: var(--accent-text); }
.tp-pw-nav.agent { color: var(--agent-text); }
.tp-pw-nav.skill { color: var(--skill-text); }
.tp-pw-nav.file { color: var(--file-text); }
.tp-pw-nav svg { width: 16px; height: 16px; }
.tp-pw-profile { margin-top: auto; display: flex; flex-direction: column; font-size: var(--fs-meta); color: var(--text-muted); }
.tp-pw-main { display: flex; flex-direction: column; min-width: 0; overflow: hidden; }
.tp-pw-top { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-4) var(--space-5); border-bottom: 1px solid var(--border); }
.tp-pw-primary { background: var(--primary); color: var(--on-primary); border-color: transparent; }
.tp-pw-center { flex: 1; min-height: 0; overflow: auto; padding: var(--space-6); }
.tp-pw-eyebrow { font-size: var(--fs-eyebrow); letter-spacing: .1em; text-transform: uppercase; color: var(--text-muted); }
.tp-pw-muted { color: var(--text-muted); }
.tp-pw-docks { display: flex; flex-wrap: wrap; gap: var(--space-3); margin: var(--space-4) 0; }
.tp-pw-chip { display: inline-flex; align-items: center; gap: var(--space-2); padding: var(--space-2) var(--space-3); border-radius: var(--radius-pill); font-size: var(--fs-meta); background: var(--surface-hover); }
.tp-pw-chip.agent { background: var(--agent-surface); color: var(--agent-text); }
.tp-pw-chip.skill { background: var(--skill-surface); color: var(--skill-text); }
.tp-pw-chip svg { width: 14px; height: 14px; }
.tp-pw-composer { border: 1px solid var(--border); border-radius: var(--radius-md); padding: var(--space-4); background: var(--surface-raised); box-shadow: var(--shadow-focal); }
.tp-pw-composer-actions { display: flex; align-items: center; gap: var(--space-3); margin-top: var(--space-3); }
.tp-pw-icon { padding: var(--space-2) var(--space-3); }
.tp-pw-card { border: 1px solid var(--border); border-radius: var(--radius-sm); padding: var(--space-4); background: var(--surface); font-size: var(--fs-meta); }
.tp-pw-card.notification strong { color: var(--accent-text); }
.tp-pw-card.file strong { color: var(--file-text); }

/* --- resizer (shared neutral focus, #401) --- */
.tp-resize { cursor: col-resize; background: var(--border); }
.tp-resize:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; }

/* --- toast (Region/Notification-adjacent transient, #414) --- */
.tp-toast {
  position: fixed; left: 50%; bottom: var(--space-6); transform: translateX(-50%);
  background: var(--text); color: var(--surface); padding: var(--space-4) var(--space-6);
  border-radius: var(--radius-pill); font-size: var(--fs-body-sm); z-index: var(--z-popover);
}
.tp-toast[hidden] { display: none; }
/* #414: routine lifecycle failures ride the same toast, danger-tinted —
 * genuine failure keeps the danger reservation, never a modal Dialog. */
.tp-toast.err {
  background: var(--danger-surface); color: var(--danger);
  border: 1px solid var(--danger-border);
}
.btn-link { display: inline-flex; align-items: center; text-decoration: none; }

/* Below the shared Admin breakpoint the four-column authoring surface stacks so
   it contains itself rather than overflowing (mirrors the Form takeover, AC "responsive"). */
@media (max-width: 980px) {
  .tp-body { grid-template-columns: 1fr; grid-auto-rows: minmax(200px, auto); }
  .tp-resize { display: none; }
  .tp-pw { grid-template-columns: 1fr; }
}

/* ==========================================================================
   The Yatsar Hub panel — Settings' fifth section (#645, spec #631 §8,
   prototype #625). Everything structural on this panel is production
   vocabulary (.settings-card / .field-group / .connector-form /
   .settings-keyrow / .settings-actions / .pub-badge / Dialog.confirm). These
   four blocks are what a *status panel about a remote party* needs and no
   shipped surface has yet. Design-system candidates on the rule of two —
   deliberately NOT promoted, since this is the first and only consumer.

   Spacing is the published --space-1..-9 rhythm and corners are --radius
   (DESIGN.md rules 1 and 8). Three values here are genuinely OFF that scale
   and stay literals rather than being rounded to the nearest token: the 16px
   block rhythm between the panel's major sections (--space-7 is 14px and
   --space-8 is 18px), the 5px gap inside the .hub-privacy list, and the 3px
   .hub-backup left rule — a border WIDTH, which the spacing scale does not
   speak to at all.
   ========================================================================== */

/* --- 1. .hub-state — badge, what it means, when it was last confirmed, and
   the one control that goes out. `.check-result` acknowledges the PRESS: in
   the unrecoverable state Check now deliberately stamps nothing, so the age
   line correctly cannot move and the button must answer for itself. */
.hub-state { display: flex; align-items: center; gap: var(--space-5); flex-wrap: wrap; margin-bottom: var(--space-6); }
.hub-state .pub-badge { margin-left: 0; }
.hub-state .reason { font-weight: var(--fw-emph); }
.hub-state .stamp { color: var(--text-muted); font-size: var(--fs-meta); }
.hub-state .check-result { color: var(--text-muted); font-size: var(--fs-meta); }
/* The row WRAPS at ordinary desktop widths and the approved card must survive
   it. `.settings-action-spacer` only right-aligns Check now while everything
   fits on one line: at 1280px with the rail open the Card is ~672px, the spacer
   ends line one and the button lands alone on line two — flush LEFT, under the
   badge, against the right-aligned Disconnect row directly beneath it. Four of
   the nine states carry a reason sentence long enough to trip it (unclaimed,
   unreachable, rejected, and any state after a Check now stamps its answer).
   `margin-left: auto` is inert on the unwrapped line, where the spacer has
   already eaten the free space (2026-08-11 design review). */
.hub-state > button { margin-left: auto; }
.hub-body { margin: 0 0 16px; color: var(--text-muted); max-width: 68ch; line-height: 1.55; }
.hub-body strong { color: var(--text); }
.hub-body code { font-family: var(--mono); font-size: var(--fs-meta); }

/* --- 2. .hub-lanes / .hub-lane — the two-lane fence, inverted. The Hub
   console fences what it observed from what a Deployment claims; here the
   Platform fences what YATSAR said from what this machine knows. Settings
   makes no network call to Yatsar on load, so every field in the Yatsar lane
   is by construction cached — the lane says when it was last confirmed rather
   than dressing it up as live. */
.hub-lanes { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 16px; margin-bottom: 16px; }
.hub-lane { border: 1px solid var(--border); border-radius: var(--radius); padding: var(--space-6) var(--space-7); }
.hub-lane .eyebrow { display: block; margin-bottom: var(--space-5); }
.hub-lane dl { display: grid; grid-template-columns: auto 1fr; gap: var(--space-3) var(--space-7); margin: 0; }
.hub-lane dt { color: var(--text-muted); font-size: var(--fs-meta); }
.hub-lane dd { margin: 0; font-size: var(--fs-body-sm); }
.hub-lane dd.mono { font-family: var(--mono); }
.hub-lane .lane-note { margin: var(--space-5) 0 0; color: var(--text-muted); font-size: var(--fs-meta); line-height: 1.5; }
.hub-lane .caution { color: var(--warning); font-weight: var(--fw-emph); }

/* --- 3. .hub-privacy — what leaves this machine, framed on its own because it
   is the one thing on this panel an org admin will actually read. Its placement
   is load-bearing and lives in the DOM, not here: `hub-settings.js` appends it
   BEFORE the register form, so the disclosure precedes the decision. Nothing
   here may reorder it — no `order`, no `column-reverse` — and
   `hub_settings_panel.test.js` asserts that order outright. */
.hub-privacy { border: 1px solid var(--border); border-radius: var(--radius); padding: var(--space-6) var(--space-7); margin: 0 0 16px; background: var(--surface); }
.hub-privacy > strong { display: block; margin-bottom: var(--space-4); }
.hub-privacy ul { margin: 0; padding-left: var(--space-8); display: grid; gap: 5px; color: var(--text-muted); font-size: var(--fs-body-sm); line-height: 1.5; }
.hub-privacy li strong { color: var(--text); }

/* --- 4. .hub-backup — the backup-critical note: persistent, never a toast,
   and with no download button anywhere near it. `.hub-hint` / `.hub-err` /
   `.hub-refusal` / `.hub-quote` are the panel's field- and refusal-level
   trim, kept here rather than promoted for the same rule-of-two reason.
   `.hub-refusal.calm` is the 401: a revoked Deployment re-registering is a
   billing notice, so it takes the caution family and never danger red. */
.hub-backup { border: 1px solid var(--border); border-left: 3px solid var(--primary); border-radius: var(--radius); padding: var(--space-6) var(--space-7); margin: 0 0 16px; }
.hub-backup > strong { display: block; margin-bottom: var(--space-3); }
.hub-backup p { margin: 0 0 var(--space-4); color: var(--text-muted); font-size: var(--fs-body-sm); line-height: 1.55; }
.hub-backup p:last-child { margin-bottom: 0; }
.hub-backup p strong { color: var(--text); }
.hub-backup code { font-family: var(--mono); font-size: var(--fs-meta); }
.hub-hint { color: var(--text-muted); font-size: var(--fs-meta); line-height: 1.5; margin-top: var(--space-1); display: block; }
.hub-err { color: var(--danger); font-size: var(--fs-meta); margin-top: var(--space-2); display: block; }
/* the author `display` above outranks the browser's own [hidden] rule, so the
   attribute needs saying again — the house pattern (.topbar[hidden] et al).
   `test_hub_settings_panel.py` pins this line: deleting it leaves every JS test
   green, because the node lane has no CSS engine to see it with. The panel also
   EMPTIES a refusal it hides, so the behaviour does not rest on this line alone. */
.hub-err[hidden] { display: none; }
.hub-refusal { border: 1px solid var(--danger-border); background: var(--danger-surface); color: var(--danger); border-radius: var(--radius); padding: var(--space-5) var(--space-6); margin: 0 0 var(--space-7); font-size: var(--fs-body-sm); line-height: 1.5; }
.hub-refusal strong { display: block; }
.hub-refusal.calm { border-color: var(--warning-border); background: var(--warning-surface); color: var(--warning); }
.hub-quote { display: flex; align-items: center; gap: var(--space-4); margin-top: var(--space-5); margin-bottom: var(--space-7); font-size: var(--fs-body-sm); }
.hub-quote code { font-family: var(--mono); }
