/*
 * SKEMA workspace
 *
 * This stylesheet is deliberately self-contained.  It replaces the former
 * collection of route-specific overrides with the visual system specified in
 * SKEMA Workspace.dc.html: a 56px command bar, a paper-and-ink palette, and a
 * single 12-column content grid.  Legacy portal classes are intentionally not
 * styled here; the new workspace renderer owns the visible DOM.
 */

:root {
  --gs-bg: #f7f5f1;
  --gs-paper: #fffdfa;
  --gs-ink: #17140f;
  --gs-copy: #3a342c;
  --gs-muted: #6f6961;
  --gs-line: #e3ded4;
  --gs-soft-line: #ede9e2;
  --gs-panel: #f4f1ec;
  --gs-burgundy: #7e2230;
  --gs-green: #2e6448;
  --gs-amber: #8a5a12;
  --gs-danger: #8d2938;
  --gs-radius: 14px;
  --gs-mono: "Geist Mono", "SFMono-Regular", Consolas, "Liberation Mono", monospace;
  --gs-sans: "Geist", Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
    "Segoe UI", sans-serif;
}

*,
*::before,
*::after { box-sizing: border-box; }

/* WO-49: ONE global focus ring, in the workspace's own accent.
 *
 * Gate #31 found no control had a visible focus indicator, because four
 * local suppressions out-specified a perfectly good global one.
 * Deleting those four lines fixed every focus finding in the gate, and
 * `test_no_stylesheet_suppresses_the_focus_ring` exists so it cannot come
 * back. If a control needs a different indicator, RESTYLE this outline —
 * never remove it. A background or border change may accompany the ring
 * but must not replace it: `:focus-visible` already fires only when the
 * browser judges a visible indicator is warranted, so suppressing it here
 * is suppressing it for exactly the keyboard user it exists to serve.
 */
:focus-visible {
  outline: 2px solid rgb(126 34 48 / .48);
  outline-offset: 2px;
}

html { min-width: 320px; background: var(--gs-bg); }

/* ── EVERY DROPDOWN, ONE DESIGN ───────────────────────────────────────────
 * Selects were shipping with the browser's own control: a grey system
 * chevron and a system border, beside inputs that had been designed. They
 * appear on Ingest (datatype per field), Author (kind, expected type),
 * Maintain (module, term), the admin roster (role), Facts (question
 * shape) and the assignment picker - so "the dropdowns are undesigned"
 * was true of six screens at once, and styling them one class at a time
 * is how they drifted apart in the first place.
 *
 * `appearance: none` removes the native control, so the chevron has to be
 * drawn: an inline data: SVG, which `img-src data:` already permits (the
 * stylesheet embeds its other icons the same way).
 *
 * NOT `selects.js`. That parked module replaces the popup with a listbox
 * and would need its own keyboard model, its own ARIA and its own tests;
 * a native select styled properly keeps the platform's behaviour - type
 * to jump, arrow keys, mobile wheel - and is what "designed" means here.
 */
select {
  appearance: none;
  -webkit-appearance: none;
  min-height: 32px;
  padding: 0 30px 0 11px;
  border: 1px solid var(--gs-line);
  border-radius: 8px;
  background-color: var(--gs-paper);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%236f6961' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 11px center;
  background-size: 10px 6px;
  color: var(--gs-ink);
  font-size: 12.5px;
  line-height: 1.2;
}
select:hover { border-color: #cfc7ba; }
select:focus { border-color: #a49b8e; box-shadow: 0 0 0 3px rgba(126, 34, 48, .08); }
select:disabled { background-color: #f4f1ec; color: var(--gs-muted); }
/* The options list is the one part a page cannot style in most engines.
   Setting the colours at least stops a dark-mode browser inverting it
   against a light control. */
select option { background: var(--gs-paper); color: var(--gs-ink); }
/* These classes paint with the `background` SHORTHAND, which resets
   background-image and erased the chevron above - a class also
   out-specifies a bare `select`, so order could not fix it. They set
   `background-color` now; this line is the note explaining why, so the
   next person does not helpfully change them back. */

/* ── FOCUS ON A LINE-STYLE FIELD ──────────────────────────────────────────
 * The global ring is a 2px rectangle at 2px offset, which is right for a
 * button and wrong for an input drawn as a single underline: it painted a
 * detached box in mid-air around the Ingest domain field.
 *
 * RESTYLED, not removed - the rule above this file's focus block says so
 * in as many words, and `test_no_stylesheet_suppresses_the_focus_ring`
 * enforces it. The ring now hugs the control and the underline thickens,
 * so the indicator is stronger than it was, not weaker.
 */
/* No bespoke override any more: the field has a border, so the global
   ring sits where a ring belongs. */


body {
  min-width: 320px;
  margin: 0;
  background: var(--gs-bg);
  color: var(--gs-ink);
  font-family: var(--gs-sans);
  font-size: 14px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

body > .workspace-header,
body > header.workspace-header {
  position: sticky;
  top: 0;
  z-index: 80;
  display: flex;
  align-items: center;
  gap: 28px;
  width: 100%;
  height: 56px;
  padding: 0 28px;
  border-bottom: 1px solid var(--gs-line);
  background: rgba(247, 245, 241, 0.86);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}

a { color: var(--gs-burgundy); text-decoration: none; }
a:hover { color: #5c161f; }
button,
input,
select,
textarea { font: inherit; }
button { color: inherit; }
button:not(:disabled),
a[href] { cursor: pointer; }
button:disabled { cursor: not-allowed; opacity: 0.55; }
::selection { background: var(--gs-burgundy); color: var(--gs-bg); }

.skip-link {
  position: fixed;
  top: 8px;
  left: 8px;
  z-index: 300;
  padding: 8px 12px;
  border-radius: 8px;
  background: var(--gs-ink);
  color: var(--gs-paper);
  transform: translateY(-150%);
}
.skip-link:focus { transform: translateY(0); }

.gs-brand__logo {
  /* Sized off the wordmark so the two stay in proportion when the type
     scale changes, and aligned to its baseline rather than centred - a
     centred mark beside 15px type reads as slightly high. */
  width: 22px;
  height: 22px;
  flex: 0 0 auto;
  align-self: center;
}

.gs-brand {
  display: inline-flex;
  align-items: baseline;
  gap: 9px;
  flex: 0 0 auto;
  color: var(--gs-ink);
  text-decoration: none;
}
.gs-brand__mark {
  font-family: var(--gs-mono);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.02em;
}
.gs-brand__sub {
  color: var(--gs-muted);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.gs-nav {
  display: flex;
  align-items: center;
  /* 12px, not 2px. The focus ring is `outline: 2px` at `outline-offset:
   * 2px`, so it extends 4px beyond each item - and at a 2px gap the ring
   * around one destination overlapped the pill of the next by 4px.
   * Measured, at every width from 960 to 1440. Two highlighted neighbours
   * (an active tab beside a focused one) then read as a single smeared
   * control. The gap has to clear two rings, so it must exceed 8px. */
  gap: 12px;
  min-width: 0;
}
.gs-nav__item {
  height: 32px;
  padding: 0 13px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--gs-muted);
  font-size: 13px;
  line-height: 32px;
  text-decoration: none;
  white-space: nowrap;
}
.gs-nav__item:hover,
.gs-nav__item:focus-visible { background: #ede9e2; color: var(--gs-ink); outline: 2px solid rgb(126 34 48 / .48); outline-offset: 2px; }
.gs-nav__item.is-active { background: #ede9e2; color: var(--gs-ink); font-weight: 500; }

/* AN ICON AT EVERY WIDTH.
 *
 * This was 220px of chrome sitting between the navigation and the edge,
 * and the nav is the thing that must never be sacrificed - so the nav grew
 * (Graph, and three separators) and the search stopped fitting, burying
 * Settings underneath it. Collapsing it permanently returns ~188px and
 * removes a breakpoint that had to be re-measured every time the nav
 * changed. The accessible name is unchanged; the hint moves to a tooltip. */
.gs-search {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 32px;
  height: 32px;
  margin-left: auto;
  padding: 0;
  border: 1px solid var(--gs-line);
  border-radius: 8px;
  background: var(--gs-paper);
  color: var(--gs-muted);
  font-size: 12.5px;
  text-align: left;
}
.gs-search:hover,
.gs-search:focus-visible { border-color: #cfc7ba; color: #6c665c; outline: 2px solid rgb(126 34 48 / .48); outline-offset: 2px; }
.gs-search__icon { color: var(--gs-burgundy); font-family: var(--gs-mono); font-size: 13px; }
/* Hidden, not removed: the accessible name comes from aria-label, and the
   spans stay so a wider variant can bring them back without new markup. */
.gs-search__label,
.gs-search__key { display: none; }

/* The tooltip. `attr(data-tip)` so the sentence lives in shell.js beside
   the aria-label. Anchored right - the control is the last thing before
   the health chip, so it can never run off the edge. The header does not
   clip, so this is free of a portal. */
.gs-search::after {
  content: attr(data-tip);
  position: absolute;
  top: calc(100% + 9px);
  right: 0;
  z-index: 90;
  padding: 6px 10px;
  border-radius: 7px;
  background: var(--gs-ink);
  color: var(--gs-bg);
  font-size: 11.5px;
  white-space: nowrap;
  box-shadow: 0 8px 22px rgb(23 20 15 / .2);
  opacity: 0;
  transform: translateY(-3px);
  pointer-events: none;
  transition: opacity .16s ease, transform .16s ease;
}
.gs-search:hover::after,
.gs-search:focus-visible::after { opacity: 1; transform: none; }
.gs-search__key {
  padding: 1px 5px;
  border: 1px solid var(--gs-line);
  border-radius: 4px;
  background: #f2efe9;
  color: var(--gs-muted);
  font-family: var(--gs-mono);
  font-size: 10.5px;
}

.gs-health {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: 0 0 auto;
  color: var(--gs-green);
  font-family: var(--gs-mono);
  font-size: 11.5px;
  white-space: nowrap;
}
.gs-health__dot { width: 6px; height: 6px; border-radius: 50%; background: var(--gs-green); }
.gs-avatar {
  display: inline-grid;
  width: 26px;
  height: 26px;
  place-items: center;
  flex: 0 0 auto;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--gs-burgundy);
  color: var(--gs-paper);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .02em;
}

.gs-menu {
  position: fixed;
  z-index: 90;
  width: 216px;
  padding: 6px;
  border: 1px solid var(--gs-line);
  border-radius: 10px;
  background: var(--gs-paper);
  box-shadow: 0 14px 34px rgba(23, 20, 15, 0.12);
}
.gs-menu[hidden] { display: none; }
.gs-menu__item {
  display: flex;
  align-items: center;
  width: 100%;
  min-height: 34px;
  padding: 7px 9px;
  border: 0;
  border-radius: 7px;
  background: transparent;
  color: var(--gs-copy);
  font-size: 12.5px;
  text-align: left;
}
.gs-menu__item:hover,
.gs-menu__item:focus-visible { background: #f2efe9; color: var(--gs-ink); outline: 2px solid rgb(126 34 48 / .48); outline-offset: 2px; }

#tabs { display: none !important; }
#view { min-height: calc(100vh - 56px); }

.dc-page {
  position: relative;
  width: 100%;
  max-width: 1180px;
  margin: 0 auto;
  padding: 34px 32px 80px;
}
.dc-page--wide { max-width: 1320px; }
.dc-page--graph { max-width: 1400px; padding-top: 28px; }
.dc-page--narrow { max-width: 1000px; }
.dc-page--compact { max-width: 900px; }

.dc-page__hero {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
  margin-bottom: 26px;
}
.dc-page__intro { min-width: 0; }
.dc-eyebrow {
  margin-bottom: 8px;
  color: var(--gs-muted);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
}
.dc-page h1 {
  margin: 0;
  color: var(--gs-ink);
  font-size: 30px;
  font-weight: 600;
  letter-spacing: -0.028em;
  line-height: 1.1;
}
.dc-page__lede {
  max-width: 64ch;
  margin: 8px 0 0;
  color: #6c665c;
  font-size: 14px;
  text-wrap: pretty;
}
.dc-page__hero-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

/* THE PROVENANCE THREAD, PLACED.
 *
 * It was `min(48%, 640px)` over 260px — a near-square panel holding a
 * story that runs left to right, so evidence, weave, decision, governed
 * term and record were all squeezed into less than half a screen and
 * read as a tangle.
 *
 * It is now a wide, shallow band: the thread gets room to diverge and
 * converge, and the record trail reaches the page edge instead of
 * stopping in clear air. `right: 0` on a container that is already
 * centred with a max-width, so the trail ends where the content column
 * ends rather than under the window's edge.
 *
 * It sits BELOW the headline's baseline rather than at `top: 0`, because
 * the thread's own centre line should meet the hero's optical middle —
 * pinned to the top it hung above everything and read as a separate
 * object. */
.dc-motif {
  position: absolute;
  /* ABOVE THE HEADLINE'S LINE, not across it.
     At `top: 26px` over 186px the band was centred exactly on the
     headline's glyphs: measured, 5 marks sat on the letters at 1680px and
     28 at 760px. The thread's own centre line now runs above the cap
     height, so the drawing shares the hero's row without sharing its
     baseline. */
  top: 2px;
  right: 0;
  z-index: 0;
  width: min(74%, 1060px);
  height: 132px;
  overflow: visible;
  pointer-events: none;
  opacity: .26;
}
.dc-motif svg { display: block; width: 100%; height: 100%; }

/* THE BUILD SHELL HAD NO RULE AT ALL.
 *
 * `buildDocumentPage` renames the host to `ws-build-page__motif`, and
 * nothing styled that class beyond its animation — so on Ingest, Explore
 * and Library the ornament was a full-width block IN THE FLOW, pushing
 * the page's own heading down and drawing at whatever height its
 * intrinsic ratio gave it. It inherits the same placement now. */
.ws-build-page__motif {
  position: absolute;
  top: 2px;
  right: 0;
  z-index: 0;
  width: min(74%, 1060px);
  height: 132px;
  overflow: visible;
  pointer-events: none;
  opacity: .26;
}
.ws-build-page__motif svg { display: block; width: 100%; height: 100%; }

/* A CONTAINING BLOCK, OR THE ORNAMENT ESCAPES.
 *
 * `.dc-page` and `.ws-build-page` are already `position: relative`; the
 * Review and Schema shells were not. An absolutely positioned child then
 * resolves against the nearest positioned ancestor — the viewport — so
 * the thread pinned itself to the top-right of the WINDOW instead of the
 * content column, drifting as the page scrolled. That is the placement
 * bug, and it is a one-line fix in the right place rather than a special
 * case in the ornament. */
.ws-review-page, .ws-schema-page { position: relative; }

/* IT STEPS BACK BEFORE IT CROWDS.
   Below ~1180px the hero's headline wraps and takes the full row, so the
   thread narrows; below 1080 it goes entirely, because at that width
   there is no right-hand space left to be an ornament in. Measured
   rather than guessed: mark-on-glyph collisions rise from 5 to 28 across
   this range. */
@media (max-width: 1320px) {
  .dc-motif, .ws-build-page__motif { width: min(62%, 820px); }
}
@media (max-width: 1180px) {
  .dc-motif, .ws-build-page__motif { display: none; }
}
/* `position: relative` and NOTHING ELSE.
 *
 * This carried `z-index: 1` and that broke the Graph's change picker: a
 * positioned element with a z-index creates a STACKING CONTEXT, so the
 * dropdown panel's `z-index: 60` could only compete inside the page
 * head, and the graph card — a later sibling at the same z-index 1 —
 * painted over the whole head. The panel opened behind the canvas.
 *
 * `z-index: auto` creates no stacking context, so the panel escapes and
 * lands above later siblings as it is meant to. The ornament still sits
 * behind everything because it is the FIRST child at `z-index: 0`, and
 * positioned siblings at `auto` paint after it in document order — which
 * is all the layering this ever needed. */
.ws-review-page > :not(.dc-motif),
.ws-schema-page > :not(.dc-motif) { position: relative; }

/* The structure draws itself once, the first time a page paints it: a
   watermark that GREW rather than one that was always there. Same gesture
   the landing page uses when it drafts its plan, at a fifth of the
   contrast, and it never repeats - a corner ornament that loops is a
   corner ornament you end up watching. */
.dc-motif path, .ws-build-page__motif path {
  stroke-dasharray: 1; stroke-dashoffset: 0;
  /* Short. This runs again on every navigation, because each render builds
     a fresh motif - so it has to be finished before a reader has settled on
     the page, not still moving underneath them. */
  animation: dc-motif-grow 1.05s cubic-bezier(.22,.61,.36,1) backwards;
}
.dc-motif path:nth-of-type(1), .ws-build-page__motif path:nth-of-type(1) { animation-delay: .06s; }
.dc-motif path:nth-of-type(2), .ws-build-page__motif path:nth-of-type(2) { animation-delay: .2s; }
.dc-motif path:nth-of-type(3), .ws-build-page__motif path:nth-of-type(3) { animation-delay: .24s; }
.dc-motif path:nth-of-type(n+4), .ws-build-page__motif path:nth-of-type(n+4) { animation-delay: .34s; }
.dc-motif circle, .ws-build-page__motif circle {
  animation: dc-motif-settle .45s ease .8s backwards;
}
@keyframes dc-motif-grow { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }
@keyframes dc-motif-settle { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .dc-motif path, .dc-motif circle,
  .ws-build-page__motif path, .ws-build-page__motif circle { animation: none; }
}

.dc-card {
  min-width: 0;
  border: 1px solid var(--gs-line);
  border-radius: var(--gs-radius);
  background: var(--gs-paper);
  padding: 20px 22px;
}
.dc-card--flush { padding: 0; overflow: hidden; }
.dc-card--dark { border-color: var(--gs-ink); background: var(--gs-ink); color: var(--gs-paper); }
.dc-card__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 16px;
}
.dc-card__head h2,
.dc-section-title {
  margin: 0;
  color: var(--gs-muted);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
}
.dc-card__head a { font-size: 12.5px; }
.dc-card__caption { margin: 0 0 16px; color: #6c665c; font-size: 13px; }

.dc-grid-12 {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: 20px;
}
.dc-span-12 { grid-column: span 12; }
.dc-span-7 { grid-column: span 7; }
.dc-span-6 { grid-column: span 6; }
.dc-span-5 { grid-column: span 5; }
.dc-span-4 { grid-column: span 4; }
.dc-span-3 { grid-column: span 3; }

.dc-metric-band {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  margin-bottom: 28px;
  border-top: 1px solid var(--gs-line);
  border-bottom: 1px solid var(--gs-line);
}
.dc-metric {
  display: flex;
  flex-direction: column;
  gap: 5px;
  min-width: 0;
  padding: 16px 18px;
  border-right: 1px solid var(--gs-line);
  color: inherit;
}
.dc-metric:first-child { border-left: 1px solid var(--gs-line); }
.dc-metric[href]:hover { background: #f1eee8; color: inherit; }
.dc-metric__value {
  color: var(--gs-ink);
  font-family: var(--gs-mono);
  font-size: 26px;
  font-weight: 500;
  letter-spacing: -.03em;
  line-height: 1;
}
.dc-metric__label {
  color: var(--gs-muted);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: .1em;
  text-transform: uppercase;
}

.dc-pill,
.dc-chip,
.dc-stage,
.dc-state {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 24px;
  padding: 2px 8px;
  border: 1px solid transparent;
  border-radius: 999px;
  font-family: var(--gs-mono);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: .02em;
  line-height: 1.3;
  white-space: nowrap;
}
.dc-pill { height: 30px; padding: 0 12px; border-color: var(--gs-line); background: var(--gs-paper); color: #6c665c; font-size: 11.5px; }
.dc-pill--warning { border-color: #e8c9a8; background: #fdf3e7; color: var(--gs-amber); }
.dc-pill--warning::before { width: 5px; height: 5px; margin-right: 7px; border-radius: 50%; background: #b57a18; content: ""; }
.dc-chip--green,
.dc-state--applied,
.dc-state--accepted { border-color: #c8ded0; background: #e9f1ec; color: var(--gs-green); }
.dc-chip--amber,
.dc-state--validated,
.dc-state--modified { border-color: #ecd4b6; background: #fdf3e7; color: var(--gs-amber); }
.dc-chip--red,
.dc-state--rejected { border-color: #ebc9cf; background: #faecee; color: var(--gs-danger); }
.dc-chip--muted,
.dc-state--draft,
.dc-state--in_review { background: #f0ede6; color: #6c665c; }
.dc-stage { border-radius: 5px; background: #f0ede6; color: #6c665c; font-family: var(--gs-sans); font-size: 10px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; }

.dc-button,
.dc-button--primary,
.dc-button--soft,
.dc-button--danger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 32px;
  padding: 0 14px;
  border: 1px solid #cfc7ba;
  border-radius: 8px;
  background: var(--gs-paper);
  color: var(--gs-copy);
  font-size: 12.5px;
  font-weight: 500;
  text-decoration: none;
}
.dc-button:hover,
.dc-button:focus-visible { border-color: var(--gs-ink); color: var(--gs-ink); outline: 2px solid rgb(126 34 48 / .48); outline-offset: 2px; }
.dc-button--primary { border-color: var(--gs-ink); background: var(--gs-ink); color: var(--gs-bg); }
.dc-button--primary:hover,
.dc-button--primary:focus-visible { border-color: #2c271f; background: #2c271f; color: var(--gs-paper); }
.dc-button--soft { border-color: var(--gs-line); background: var(--gs-paper); }
.dc-button--danger { border-color: #6b2531; background: transparent; color: #a64553; }
.dc-button--danger:hover { background: #faecee; }

.dc-pipeline {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 10px;
  align-items: end;
}
.dc-pipeline__stage {
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: 0;
  color: inherit;
  text-decoration: none;
}
.dc-pipeline__stage:hover { color: inherit; }
.dc-pipeline__vessel { display: flex; align-items: flex-end; height: 96px; }
.dc-pipeline__bar { width: 100%; min-height: 4px; border-radius: 6px 6px 0 0; background: #d8d2c7; }
.dc-pipeline__bar--validated { background: #e8c9a8; }
.dc-pipeline__bar--review { background: #efd6b6; }
.dc-pipeline__bar--applied { background: #c9d8cf; }
.dc-pipeline__count { color: var(--gs-ink); font-family: var(--gs-mono); font-size: 20px; font-weight: 500; letter-spacing: -.02em; line-height: 1; }
.dc-pipeline__label { min-height: 30px; color: #6c665c; font-size: 11.5px; line-height: 1.3; }
.dc-pipeline__stall { min-height: 14px; color: var(--gs-amber); font-family: var(--gs-mono); font-size: 10.5px; }
.dc-pipeline__stage.is-empty .dc-pipeline__count,
.dc-pipeline__stage.is-empty .dc-pipeline__label { color: #b6b0a7; }

.dc-gauntlet {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 20px;
  padding-top: 16px;
  border-top: 1px solid var(--gs-soft-line);
}
.dc-gauntlet__title { margin-right: 2px; color: var(--gs-muted); font-size: 10.5px; font-weight: 600; letter-spacing: .1em; text-transform: uppercase; }
.dc-gauntlet__step { padding: 2px 8px; border-radius: 5px; background: #e9f1ec; color: var(--gs-green); font-family: var(--gs-mono); font-size: 11.5px; }
.dc-gauntlet__step--warning { background: #fdf3e7; color: var(--gs-amber); }
.dc-gauntlet__arrow { color: #cfc7ba; font-size: 11px; }
.dc-gauntlet__note { margin-left: 4px; color: var(--gs-muted); font-size: 11.5px; }

.dc-waiting__who { margin-bottom: 12px; color: var(--gs-muted); font-family: var(--gs-mono); font-size: 11px; }
.dc-waiting__list { display: flex; flex-direction: column; }
.dc-waiting__row {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0 -8px;
  padding: 11px 8px;
  border-top: 1px solid var(--gs-soft-line);
  color: inherit;
}
.dc-waiting__row:hover { border-radius: 7px; background: #f5f2ec; color: inherit; }
.dc-dot { width: 7px; height: 7px; border-radius: 50%; background: #b57a18; flex: 0 0 auto; }
.dc-dot--green { background: #3fbe89; }
.dc-dot--red { background: var(--gs-burgundy); }
.dc-waiting__main { min-width: 0; flex: 1; }
.dc-waiting__title { display: block; overflow: hidden; color: var(--gs-ink); font-size: 13.5px; font-weight: 500; text-overflow: ellipsis; white-space: nowrap; }
.dc-waiting__meta { display: block; margin-top: 2px; color: var(--gs-muted); font-size: 11.5px; }
.dc-waiting__note { color: var(--gs-amber); font-family: var(--gs-mono); font-size: 10.5px; white-space: nowrap; }
.dc-waiting__clear { margin: 12px 0 0; padding-top: 14px; border-top: 1px solid var(--gs-soft-line); color: #6c665c; font-size: 12.5px; }
.dc-waiting__foot { margin: 14px 0 0; padding-top: 12px; border-top: 1px solid var(--gs-soft-line); color: var(--gs-muted); font-size: 11.5px; }

.dc-health-main { display: flex; align-items: center; gap: 26px; flex-wrap: wrap; }
.dc-health-gauge { width: 104px; height: 104px; flex: 0 0 auto; }
.dc-health-gauge text { font-family: var(--gs-mono); }
.dc-health-cover { display: flex; flex: 1 1 260px; flex-direction: column; gap: 9px; }
.dc-health-rows { display: flex; flex-direction: column; gap: 9px; }
.dc-cover-row { display: grid; grid-template-columns: 78px minmax(0, 1fr) 38px; align-items: center; gap: 10px; }
.dc-cover-row__label { color: #6c665c; font-size: 12px; }
.dc-cover-row__track,
.dc-domain__track,
.dc-progress__track { display: block; height: 6px; overflow: hidden; border-radius: 999px; background: var(--gs-soft-line); }
.dc-cover-row__fill,
.dc-domain__fill,
.dc-progress__fill { display: block; height: 100%; border-radius: inherit; background: var(--gs-green); }
.dc-cover-row__fill--amber { background: #c08217; }
.dc-cover-row__value { color: var(--gs-copy); font-family: var(--gs-mono); font-size: 11.5px; text-align: right; }
.dc-health-breakdown { display: grid; grid-template-columns: 1fr 1fr; gap: 22px; margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--gs-soft-line); }
.dc-mini-title { margin-bottom: 10px; color: var(--gs-muted); font-size: 10.5px; font-weight: 600; letter-spacing: .1em; text-transform: uppercase; }
.dc-breakdown-row,
.dc-issue-row { display: flex; align-items: center; gap: 9px; padding: 5px 0; color: var(--gs-copy); font-size: 12.5px; }
.dc-breakdown-row__cost { width: 26px; color: var(--gs-burgundy); font-family: var(--gs-mono); font-size: 12px; }
.dc-breakdown-row__math { margin-left: auto; color: var(--gs-muted); font-family: var(--gs-mono); font-size: 11px; }
.dc-issue-row__label { flex: 1; }
.dc-issue-row__count { color: #6c665c; font-family: var(--gs-mono); font-size: 12px; }

.dc-decision-list,
.dc-domain-list { display: flex; flex-direction: column; }
.dc-decision-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 0;
  border-top: 1px solid var(--gs-soft-line);
}
.dc-decision-row__title { min-width: 0; flex: 1; overflow: hidden; color: var(--gs-ink); font-size: 13px; text-overflow: ellipsis; white-space: nowrap; }
.dc-decision-row__when { color: var(--gs-muted); font-family: var(--gs-mono); font-size: 10.5px; white-space: nowrap; }
.dc-domain { display: grid; grid-template-columns: 88px minmax(0, 1fr) 30px; align-items: center; gap: 12px; padding: 5px 0; color: inherit; }
.dc-domain:hover { color: inherit; opacity: .75; }
.dc-domain__name { overflow: hidden; color: var(--gs-copy); font-size: 12.5px; text-overflow: ellipsis; white-space: nowrap; }
.dc-domain__fill { background: var(--gs-burgundy); }
.dc-domain__count { color: #6c665c; font-family: var(--gs-mono); font-size: 11.5px; text-align: right; }

.dc-action-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
.dc-action {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  min-width: 0;
  padding: 15px 16px 16px;
  border: 1px solid var(--gs-line);
  border-radius: 12px;
  background: var(--gs-paper);
  color: inherit;
  transition: border-color .18s, transform .18s, box-shadow .18s;
}
.dc-action:hover { border-color: #cfc7ba; box-shadow: 0 6px 20px rgba(23,20,15,.06); color: inherit; transform: translateY(-2px); }
/* The icon is now DRAWN, not typed — see `ICON_PATHS` in workspace.js for
   why (one of the glyphs had an emoji presentation and rendered as a
   colour tile beside five line strokes). `color` still drives it, because
   the marks stroke in `currentColor`. */
.dc-action__icon { display: inline-flex; color: var(--gs-burgundy); }
.dc-action__icon .dc-icon { display: block; }
/* The mark leans in with the card rather than sitting inert on it. */
.dc-action { --dc-icon-shift: 0; }
.dc-action:hover .dc-icon { transform: translateX(1px); }
.dc-icon { transition: transform .18s; }
.dc-action__title { color: var(--gs-ink); font-size: 13.5px; font-weight: 500; }
.dc-action__copy { color: var(--gs-muted); font-size: 11.5px; line-height: 1.4; }

.dc-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 336px;
  gap: 20px;
  align-items: start;
}
.dc-split--detail { grid-template-columns: minmax(0, 1fr) 320px; gap: 22px; }
.dc-side { position: sticky; top: 76px; }

.dc-filterbar { display: flex; gap: 6px; flex-wrap: wrap; }
.dc-filter {
  height: 30px;
  padding: 0 11px;
  border: 1px solid var(--gs-line);
  border-radius: 7px;
  background: var(--gs-paper);
  color: var(--gs-muted);
  font-size: 11.5px;
  text-decoration: none;
}
.dc-filter:hover,
.dc-filter.is-active { border-color: #cfc7ba; background: #ede9e2; color: var(--gs-ink); }

.dc-table { overflow: hidden; }
.dc-table__head,
.dc-table__row {
  display: grid;
  grid-template-columns: minmax(160px, 1fr) 108px 130px 120px 74px;
  gap: 16px;
  align-items: center;
}
.dc-table__head {
  padding: 11px 20px;
  border-bottom: 1px solid var(--gs-line);
  background: var(--gs-panel);
  color: var(--gs-muted);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
}
.dc-table__head > :last-child { text-align: right; }
.dc-table__row {
  width: 100%;
  padding: 12px 20px;
  border: 0;
  border-bottom: 1px solid #f2efe9;
  background: transparent;
  color: inherit;
  text-align: left;
}
.dc-table__row:hover,
.dc-table__row.is-selected { background: #f5f2ec; }
.dc-table__title { display: block; overflow: hidden; color: var(--gs-ink); font-size: 13.5px; font-weight: 500; text-overflow: ellipsis; white-space: nowrap; }
.dc-table__uri { display: block; overflow: hidden; margin-top: 3px; color: var(--gs-muted); font-family: var(--gs-mono); font-size: 10.5px; text-overflow: ellipsis; white-space: nowrap; }
.dc-table__cell { overflow: hidden; color: #6c665c; font-size: 12px; text-overflow: ellipsis; white-space: nowrap; }
.dc-table__age { color: var(--gs-muted); font-family: var(--gs-mono); font-size: 10.5px; text-align: right; }

.dc-inspector { padding: 18px 20px 20px; }
.dc-inspector__eyebrow { margin-bottom: 7px; color: var(--gs-muted); font-family: var(--gs-mono); font-size: 10.5px; letter-spacing: .08em; }
.dc-inspector h2 { margin: 0 0 12px; font-size: 17px; font-weight: 600; letter-spacing: -.02em; line-height: 1.25; }
.dc-inspector__chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 16px; }
.dc-tier-list { display: flex; flex-direction: column; gap: 6px; margin: 0 0 16px; }
.dc-tier { display: flex; align-items: center; gap: 9px; padding: 4px 0; }
.dc-tier__label { flex: 1; color: var(--gs-copy); font-size: 12.5px; }
.dc-tier__status { color: var(--gs-muted); font-family: var(--gs-mono); font-size: 10.5px; }
.dc-meta-list { display: flex; flex-direction: column; gap: 7px; margin: 0 0 16px; padding-top: 14px; border-top: 1px solid var(--gs-soft-line); }
.dc-meta-list__row { display: flex; gap: 12px; color: var(--gs-copy); font-size: 12px; }
.dc-meta-list__key { width: 78px; flex: 0 0 auto; color: var(--gs-muted); }
.dc-meta-list__value { min-width: 0; flex: 1; overflow: hidden; font-family: var(--gs-mono); font-size: 11.5px; text-overflow: ellipsis; }

.dc-back { margin-bottom: 18px; }
.dc-detail-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 24px; flex-wrap: wrap; margin-bottom: 6px; }
.dc-detail-head h1 { font-size: 28px; }
.dc-detail-meta { margin-bottom: 26px; color: var(--gs-muted); font-family: var(--gs-mono); font-size: 11.5px; }
.dc-stack { display: flex; flex-direction: column; gap: 18px; min-width: 0; }
.dc-impact-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1px; overflow: hidden; margin-bottom: 16px; border: 1px solid var(--gs-soft-line); border-radius: 10px; background: var(--gs-soft-line); }
.dc-impact { padding: 13px 15px; background: var(--gs-paper); }
.dc-impact__n { color: var(--gs-ink); font-family: var(--gs-mono); font-size: 21px; font-weight: 500; letter-spacing: -.02em; }
.dc-impact__label { margin-top: 3px; color: var(--gs-muted); font-size: 11px; letter-spacing: .08em; text-transform: uppercase; }
.dc-code-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.dc-code { overflow: hidden; padding: 0; }
.dc-code__head { padding: 11px 16px; border-bottom: 1px solid var(--gs-soft-line); background: var(--gs-panel); color: var(--gs-muted); font-size: 10.5px; font-weight: 600; letter-spacing: .12em; text-transform: uppercase; }
.dc-code--proposal { border-color: #cfc7ba; }
.dc-code--proposal .dc-code__head { background: #f3eeea; color: var(--gs-burgundy); }
.dc-code pre,
.dc-export pre { margin: 0; overflow-x: auto; padding: 15px 16px; color: var(--gs-copy); font-family: var(--gs-mono); font-size: 11.5px; line-height: 1.65; white-space: pre-wrap; }
.dc-verdict-card { padding: 18px 20px; }
.dc-verdict-card h2 { margin: 0 0 4px; color: #8f887c; font-size: 11px; font-weight: 600; letter-spacing: .14em; text-transform: uppercase; }
.dc-verdict-card p { margin: 0 0 14px; color: #a9a296; font-size: 12px; line-height: 1.5; }
.dc-verdict-card input { width: 100%; margin-bottom: 10px; border-color: #3a342c; background: #221e18; color: var(--gs-paper); }
.dc-verdict-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.dc-verdict-actions .dc-button--primary { grid-column: span 2; background: var(--gs-paper); color: var(--gs-ink); }

.dc-ingest-top { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; margin-bottom: 22px; }
.dc-drop {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 154px;
  padding: 22px;
  border: 1px dashed #cfc7ba;
  border-radius: 12px;
  background: var(--gs-paper);
  text-align: center;
}
.dc-drop input { position: absolute; width: 1px; height: 1px; overflow: hidden; opacity: 0; }
.dc-drop__icon { margin-bottom: 8px; color: var(--gs-burgundy); font-family: var(--gs-mono); font-size: 18px; }
.dc-drop__title { color: var(--gs-ink); font-size: 13.5px; font-weight: 500; }
.dc-drop__sub { margin-top: 4px; color: var(--gs-muted); font-size: 11.5px; }
.dc-field-card { display: flex; flex-direction: column; gap: 6px; min-height: 154px; padding: 16px 18px; border: 1px solid var(--gs-line); border-radius: 12px; background: var(--gs-paper); }
.dc-field-card > span { color: var(--gs-muted); font-size: 11px; font-weight: 600; letter-spacing: .1em; text-transform: uppercase; }
.dc-field-card small { margin-top: 2px; color: var(--gs-muted); font-size: 11px; }
.dc-input,
.dc-select,
.dc-textarea {
  width: 100%;
  min-height: 32px;
  padding: 0 11px;
  border: 1px solid var(--gs-line);
  border-radius: 8px;
  background-color: var(--gs-paper);
  color: var(--gs-ink);
  font-size: 12.5px;
}
.dc-input:focus,
.dc-select:focus,
.dc-textarea:focus { border-color: #a49b8e; box-shadow: 0 0 0 3px rgba(126,34,48,.08); }
.dc-field-card .dc-input { min-height: 29px; padding: 5px 0; border: 0; border-bottom: 1px solid var(--gs-line); border-radius: 0; background: transparent; font-size: 14px; }
.dc-field-card .dc-input:focus { box-shadow: none; }
.dc-mono { font-family: var(--gs-mono); }
.dc-report { overflow: hidden; margin-bottom: 20px; padding: 0; }
.dc-report__head { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 14px 20px; border-bottom: 1px solid var(--gs-line); background: var(--gs-panel); }
.dc-report__title { color: var(--gs-ink); font-size: 13px; font-weight: 600; }
.dc-report__meta { margin-top: 3px; color: var(--gs-muted); font-family: var(--gs-mono); font-size: 11px; }
.dc-report__empty { padding: 42px 20px; color: var(--gs-muted); font-size: 13px; text-align: center; }
.dc-report-table { width: 100%; border-collapse: collapse; }
.dc-report-table th,
.dc-report-table td { padding: 11px 20px; border-bottom: 1px solid #f2efe9; color: var(--gs-copy); font-size: 12px; text-align: left; vertical-align: middle; }
.dc-report-table thead th { padding-top: 11px; padding-bottom: 11px; background: #f8f6f2; color: var(--gs-muted); font-size: 10px; font-weight: 600; letter-spacing: .12em; text-transform: uppercase; }
.dc-report-table td input,
.dc-report-table td select { min-width: 0; }
.dc-report-table td:last-child { color: var(--gs-muted); font-family: var(--gs-mono); font-size: 10.5px; }
.dc-run-report { margin-top: 20px; }

.dc-author-layout { display: grid; grid-template-columns: minmax(0, 1fr) 358px; gap: 20px; align-items: start; }
.dc-form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
.dc-form-field { display: flex; flex-direction: column; gap: 6px; }
.dc-form-field--full { grid-column: span 2; }
.dc-form-label { color: var(--gs-muted); font-size: 10.5px; font-weight: 600; letter-spacing: .1em; text-transform: uppercase; }
/* Sits under one field, explaining what that field means for the kind
   currently selected. Muted and small: it is a caption, not a second
   label competing with the one above the input. */
.dc-form-help { color: var(--gs-muted); font-size: 11px; line-height: 1.4; }
.dc-textarea { min-height: 84px; padding: 9px 11px; resize: vertical; }
.dc-marker-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
.dc-marker { min-height: 80px; padding: 12px; border: 1px solid var(--gs-line); border-radius: 10px; background: #fcfaf7; }
.dc-marker__title { color: var(--gs-ink); font-size: 12px; font-weight: 500; }
.dc-marker__copy { margin-top: 4px; color: var(--gs-muted); font-size: 11px; line-height: 1.45; }
.dc-marker input { margin-top: 8px; }
.dc-preview { position: sticky; top: 76px; }
.dc-preview__uri { margin-bottom: 8px; overflow: hidden; color: var(--gs-muted); font-family: var(--gs-mono); font-size: 10.5px; text-overflow: ellipsis; white-space: nowrap; }
.dc-preview__title { margin: 0 0 8px; color: var(--gs-ink); font-size: 18px; letter-spacing: -.02em; }
.dc-preview__copy { margin: 0 0 16px; color: #6c665c; font-size: 12.5px; line-height: 1.55; }
.dc-preview pre { max-height: 420px; overflow: auto; margin: 0; padding: 14px; border-radius: 8px; background: #f4f1ec; color: var(--gs-copy); font-family: var(--gs-mono); font-size: 10.5px; line-height: 1.55; white-space: pre-wrap; }

.dc-explore-search { display: flex; align-items: center; gap: 12px; padding: 15px 18px; margin-bottom: 20px; }
.dc-explore-search .dc-input { flex: 1; }
.dc-explore-search__hint { color: var(--gs-muted); font-size: 11.5px; white-space: nowrap; }
.dc-explore-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
.dc-term-card { display: block; min-width: 0; padding: 15px 16px; border: 1px solid var(--gs-line); border-radius: 12px; background: var(--gs-paper); color: inherit; }
.dc-term-card:hover { border-color: #cfc7ba; color: inherit; }
.dc-term-card__kind { color: var(--gs-burgundy); font-family: var(--gs-mono); font-size: 10.5px; }
.dc-term-card__title { display: block; margin-top: 7px; color: var(--gs-ink); font-size: 13.5px; font-weight: 500; }
.dc-term-card__meta { display: block; overflow: hidden; margin-top: 4px; color: var(--gs-muted); font-family: var(--gs-mono); font-size: 10.5px; text-overflow: ellipsis; white-space: nowrap; }
.dc-term-card__copy { display: block; margin-top: 8px; color: #6c665c; font-size: 11.5px; line-height: 1.45; }

.dc-library-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
.dc-library { padding: 18px 20px; }
.dc-library__title { margin: 0; color: var(--gs-ink); font-size: 15px; font-weight: 600; }
.dc-library__copy { min-height: 42px; margin: 8px 0 14px; color: #6c665c; font-size: 12.5px; line-height: 1.5; }
.dc-library__meta { margin-bottom: 12px; color: var(--gs-muted); font-family: var(--gs-mono); font-size: 10.5px; }
.dc-library__form { display: flex; gap: 8px; }
.dc-library__form .dc-input { flex: 1; }

.dc-duplicate { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 16px; padding: 16px 20px; border-bottom: 1px solid #f2efe9; }
.dc-duplicate__title { margin: 0; color: var(--gs-ink); font-size: 13.5px; font-weight: 500; }
.dc-duplicate__terms { margin-top: 6px; color: var(--gs-copy); font-family: var(--gs-mono); font-size: 11px; line-height: 1.55; }
.dc-duplicate__features { margin: 8px 0 0; color: var(--gs-muted); font-size: 11.5px; }
.dc-duplicate__actions { display: flex; align-items: center; gap: 8px; }

.dc-decisions-table .dc-table__head,
.dc-decisions-table .dc-table__row { grid-template-columns: 104px minmax(0,1fr) 92px 108px minmax(0,1.3fr) 88px; gap: 14px; }

.dc-test-summary { display: flex; align-items: center; gap: 14px; padding: 16px 20px; margin-bottom: 20px; }
.dc-test-summary__score { color: var(--gs-green); font-family: var(--gs-mono); font-size: 26px; font-weight: 500; letter-spacing: -.03em; }
.dc-test-summary__track { flex: 1; }
.dc-test-summary__copy { color: #6c665c; font-size: 12.5px; }
.dc-test-row { display: flex; align-items: flex-start; gap: 14px; padding: 14px 20px; border-bottom: 1px solid #f2efe9; }
.dc-test-row__copy { min-width: 0; flex: 1; }
.dc-test-row__title { display: block; color: var(--gs-ink); font-size: 13.5px; }
.dc-test-row__spec { display: block; margin-top: 4px; color: var(--gs-muted); font-family: var(--gs-mono); font-size: 10.5px; }

.dc-format-grid { display: flex; gap: 7px; flex-wrap: wrap; margin-bottom: 18px; }
.dc-format { width: 154px; min-height: 62px; padding: 10px 12px; border: 1px solid var(--gs-line); border-radius: 9px; background: var(--gs-paper); color: var(--gs-copy); text-align: left; }
.dc-format.is-active { border-color: #cfc7ba; background: #f3eeea; color: var(--gs-ink); }
.dc-format__label { display: block; font-size: 13px; font-weight: 500; }
.dc-format__sub { display: block; margin-top: 3px; color: var(--gs-muted); font-size: 10.5px; }

/* Build / Ingest and Build / Explore are direct translations of the supplied
 * workspace document. They deliberately do not inherit generic card or grid
 * presentation from the earlier portal renderer. */
.ws-build-page {
  position: relative;
  width: 100%;
  max-width: 1180px;
  margin: 0 auto;
  padding: 34px 32px 80px;
}
/* Same correction, same reason. This one is older than the ornament
   work: any dropdown, tooltip or popover inside a Build page's children
   was confined to that child's stacking context. */
.ws-build-page > :not(.ws-build-page__motif) { position: relative; }
.ws-build-page > .ws-build-page__motif { z-index: 0; }
.ws-build-page__motif {
  position: absolute;
  top: 0;
  right: 0;
  z-index: 0;
  width: min(52%, 620px);
  height: 215px;
  overflow: hidden;
  opacity: .23;
  pointer-events: none;
}
.ws-build-page__motif svg { display: block; width: 100%; height: 100%; }
.ws-build-page__intro { margin-bottom: 26px; }
.ws-build-page__eyebrow {
  margin-bottom: 8px;
  color: #6f6961;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
}
.ws-build-page h1 {
  margin: 0 0 8px;
  color: #17140f;
  font-size: 30px;
  font-weight: 600;
  letter-spacing: -.028em;
  line-height: 1.1;
}
.ws-build-page__lede {
  max-width: 64ch;
  margin: 0;
  color: #6c665c;
  font-size: 14px;
  text-wrap: pretty;
}
.ws-primary-button,
.ws-secondary-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 34px;
  padding: 0 16px;
  border-radius: 8px;
  font-size: 13px;
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
}
.ws-primary-button {
  border: 1px solid #17140f;
  background: #17140f;
  color: #f7f5f1;
}
.ws-primary-button:hover { background: #2c271f; color: #f7f5f1; }
.ws-secondary-button {
  border: 1px solid #cfc7ba;
  background: #fffdfa;
  color: #17140f;
}
.ws-secondary-button:hover { border-color: #17140f; color: #17140f; }

.ws-ingest-setup {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
  margin-bottom: 22px;
}
.ws-ingest-drop {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 154px;
  padding: 22px;
  border: 1px dashed #cfc7ba;
  border-radius: 12px;
  background: #fffdfa;
  cursor: pointer;
  text-align: center;
}
.ws-ingest-drop input {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  opacity: 0;
}
.ws-ingest-drop:focus-within { outline: 3px solid rgba(126, 34, 48, .12); outline-offset: 2px; }
.ws-ingest-drop__icon {
  margin-bottom: 8px;
  color: #7e2230;
  font-family: var(--gs-mono);
  font-size: 18px;
}
.ws-ingest-drop__title { color: #17140f; font-size: 13.5px; font-weight: 500; }
.ws-ingest-drop__sub { margin-top: 4px; color: #6f6961; font-size: 11.5px; }
.ws-ingest-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-height: 154px;
  padding: 16px 18px;
  border: 1px solid #e3ded4;
  border-radius: 12px;
  background: #fffdfa;
}
.ws-ingest-field__label {
  color: #6f6961;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
}
/* A FIELD WITH NO BOX GETS A BOX WHEN IT IS FOCUSED, AND THAT LOOKS WRONG.
 *
 * These were underline-only inputs: `border: 0` with a single bottom
 * rule. The global `:focus-visible` ring is a rectangle, so clicking the
 * Domain field drew a burgundy box in mid-air around something that had
 * no edges of its own - reported as "a weird box around domain".
 *
 * The ring is NOT the bug and is not removed (this file's focus block
 * says restyle rather than remove, and `test_accessibility.py` enforces
 * it). The bug is that Ingest was the only screen still drawing inputs
 * as underlines while Author, Maintain, Facts and the admin roster all
 * use the bordered `.dc-input` treatment. Given a real border, a focus
 * ring is unremarkable - and one input design across the product was
 * the point.
 */
.ws-ingest-line-input {
  width: 100%;
  min-height: 32px;
  padding: 0 11px;
  border: 1px solid var(--gs-line);
  border-radius: 8px;
  background-color: var(--gs-paper);
  color: var(--gs-ink);
  font-size: 13px;
}
.ws-ingest-line-input:hover { border-color: #cfc7ba; }
.ws-ingest-line-input:focus {
  border-color: #a49b8e;
  box-shadow: 0 0 0 3px rgba(126, 34, 48, .08);
}
.ws-ingest-line-input--mono { font-family: var(--gs-mono); font-size: 12.5px; }
.ws-ingest-field__help { margin-top: 2px; color: #6f6961; font-size: 11px; }
.ws-ingest-report {
  overflow: hidden;
  margin-bottom: 20px;
  border: 1px solid #e3ded4;
  border-radius: 14px;
  background: #fffdfa;
}
.ws-ingest-report__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 14px 20px;
  border-bottom: 1px solid #e3ded4;
  background: #f4f1ec;
}
.ws-ingest-report__title { color: #17140f; font-size: 13px; font-weight: 600; }
.ws-ingest-report__meta { margin-top: 3px; color: #6f6961; font-family: var(--gs-mono); font-size: 11px; }
.ws-ingest-report__head .ws-primary-button { height: 32px; padding: 0 15px; font-size: 12.5px; }
/* A3a: `.ws-ingest-row` is now a CONTAINER (grid line + facts + evidence),
 * so the grid moved to `.ws-ingest-row__line`. Leaving `display: grid`
 * here would have laid the three stacked parts out as columns. */
.ws-ingest-columns {
  padding: 10px 20px;
  border-bottom: 1px solid #ede9e2;
  color: #6f6961;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
}
.ws-ingest-columns__confidence { text-align: right; }
.ws-ingest-row {
  padding: 11px 0;
  border-bottom: 1px solid #f2efe9;
}
.ws-ingest-row:last-child { border-bottom: 0; }
.ws-ingest-row__line { padding: 0 20px; }
.ws-ingest-check {
  position: relative;
  display: block;
  width: 16px;
  height: 16px;
}
.ws-ingest-check input {
  position: absolute;
  width: 16px;
  height: 16px;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.ws-ingest-check span {
  display: flex;
  width: 16px;
  height: 16px;
  align-items: center;
  justify-content: center;
  border: 1px solid #cfc7ba;
  border-radius: 5px;
  color: transparent;
  font-size: 10px;
}
.ws-ingest-check input:checked + span { border-color: #17140f; background: #17140f; color: #f7f5f1; }
.ws-ingest-check input:checked + span::after { content: "✓"; }
.ws-ingest-check input:focus-visible + span { outline: 2px solid rgba(126, 34, 48, .35); outline-offset: 2px; }
.ws-ingest-row__field {
  overflow: hidden;
  color: #3a342c;
  font-family: var(--gs-mono);
  font-size: 12px;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ws-ingest-datatype {
  justify-self: start;
  min-width: 0;
  padding: 3px 8px;
  border: 1px solid #e3ded4;
  border-radius: 6px;
  appearance: none;
  background-color: #fffdfa;
  color: #6c665c;
  cursor: pointer;
  font-family: var(--gs-mono);
  font-size: 11.5px;
}
.ws-ingest-datatype:focus { border-color: #a49b8e; }
.ws-ingest-inline-input {
  width: 100%;
  min-width: 0;
  padding: 0;
  border: 0;
  background: transparent;
  color: #17140f;
  font-size: 12.5px;
}
.ws-ingest-inline-input:focus { box-shadow: inset 0 -1px 0 #a49b8e; }
.ws-ingest-row__confidence { text-align: right; font-family: var(--gs-mono); font-size: 12px; }
.ws-ingest-row__confidence.is-strong { color: #2e6448; }
.ws-ingest-row__confidence.is-medium { color: #8a5a12; }
.ws-ingest-row__confidence.is-weak { color: #6f6961; }
.ws-ingest-row__signal {
  overflow: hidden;
  color: #6f6961;
  font-family: var(--gs-mono);
  font-size: 10.5px;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* A3a — the extraction's own findings, made visible.
 *
 * Routing, the map-or-new verdict, deontic modality, markers, page
 * citations and nominated relationships all arrived in the payload and
 * none of them reached the screen. Everything below is in the
 * workspace's bone-and-oxblood tokens; no new palette. */

.ws-routing {
  margin-bottom: 14px;
  padding: 14px 18px;
  border: 1px solid var(--gs-line);
  border-left: 3px solid var(--gs-green);
  border-radius: 12px;
  background: var(--gs-paper);
}
.ws-routing.is-split { border-left-color: var(--gs-amber); }
.ws-routing.is-absent { border-left-color: var(--gs-line); }
.ws-routing__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
}
.ws-routing__title { color: var(--gs-ink); font-size: 13px; font-weight: 600; }
.ws-routing__note,
.ws-routing__why {
  margin-top: 3px;
  color: var(--gs-muted);
  font-family: var(--gs-mono);
  font-size: 11px;
}
.ws-routing__why { margin-top: 8px; font-style: italic; }
.ws-routing__chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.ws-routing__chip {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  padding: 3px 9px;
  border: 1px solid var(--gs-line);
  border-radius: 999px;
  background: var(--gs-panel);
  font-family: var(--gs-mono);
  font-size: 11px;
}
.ws-routing__chip.is-winner {
  border-color: rgb(46 100 72 / .4);
  background: rgb(46 100 72 / .09);
  color: var(--gs-green);
  font-weight: 600;
}
.ws-routing__chip-count { color: var(--gs-muted); font-size: 10px; }
.ws-routing__chip.is-winner .ws-routing__chip-count { color: inherit; }
.ws-routing__split {
  margin-top: 8px;
  color: var(--gs-amber);
  font-size: 11.5px;
}

.ws-coverage {
  margin-bottom: 14px;
  padding: 10px 16px;
  border: 1px solid rgb(138 90 18 / .3);
  border-radius: 10px;
  background: rgb(138 90 18 / .07);
  color: var(--gs-amber);
  font-size: 11.5px;
}

/* The grid gained a verdict column, so it must SCROLL rather than clip:
 * `.ws-ingest-report` is `overflow: hidden`, which would have silently
 * cut the last column off on a narrow viewport. */
.ws-ingest-scroll { overflow-x: auto; }
.ws-ingest-columns,
.ws-ingest-row__line {
  display: grid;
  grid-template-columns:
    34px minmax(110px, 1fr) 112px 124px 132px 58px minmax(140px, 1fr);
  gap: 12px;
  min-width: 830px;
}
.ws-ingest-row__line { align-items: center; }
.ws-ingest-row__facts {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  padding: 0 20px 10px 60px;
}
.ws-ingest-row__cite {
  padding: 0;
  border: 0;
  background: none;
  color: var(--gs-burgundy);
  cursor: pointer;
  font-size: 11px;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.ws-ingest-row__detail { display: none; padding: 0 20px 12px 60px; }
.ws-ingest-row__detail.is-open { display: block; }

/* The map-or-new fork. `is-absent` is its own state on purpose: the
 * predictor not running is not the same answer as "new". */
.ws-verdict {
  display: inline-flex;
  flex-direction: column;
  min-width: 0;
  padding: 3px 8px;
  border: 1px solid var(--gs-line);
  border-radius: 7px;
  background: var(--gs-panel);
  line-height: 1.25;
}
.ws-verdict__label {
  font-family: var(--gs-mono);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
}
.ws-verdict__target {
  overflow: hidden;
  color: var(--gs-muted);
  font-size: 10.5px;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ws-verdict.is-maps {
  border-color: rgb(46 100 72 / .38);
  background: rgb(46 100 72 / .09);
  color: var(--gs-green);
}
.ws-verdict.is-new { border-color: var(--gs-line); color: var(--gs-copy); }
.ws-verdict.is-unsure {
  border-color: rgb(138 90 18 / .38);
  background: rgb(138 90 18 / .08);
  color: var(--gs-amber);
}
.ws-verdict.is-trap {
  border-color: rgb(141 41 56 / .42);
  background: rgb(141 41 56 / .08);
  color: var(--gs-danger);
}
.ws-verdict.is-absent {
  border-style: dashed;
  background: none;
  color: var(--gs-muted);
  font-family: var(--gs-mono);
  font-size: 10.5px;
}

/* "should" is not "must" — X0e measured 11.6% vs 11.0% in the corpus, so
 * they must not read as the same obligation. */
.ws-modality {
  padding: 2px 8px;
  border-radius: 999px;
  font-family: var(--gs-mono);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .05em;
  text-transform: uppercase;
}
.ws-modality.is-must { background: rgb(126 34 48 / .1); color: var(--gs-burgundy); }
.ws-modality.is-should { background: rgb(138 90 18 / .12); color: var(--gs-amber); }
.ws-modality.is-may { background: var(--gs-panel); color: var(--gs-muted); }
.ws-modality.is-prohibited { background: rgb(141 41 56 / .12); color: var(--gs-danger); }
.ws-modality.is-exempt {
  border: 1px dashed var(--gs-line);
  color: var(--gs-muted);
}
.ws-marker {
  padding: 2px 8px;
  border: 1px solid var(--gs-line);
  border-radius: 999px;
  background: var(--gs-paper);
  color: var(--gs-copy);
  font-family: var(--gs-mono);
  font-size: 10px;
}

/* The predictor's own reasoning, e.g. "the closest was 'Product' at 0.00,
 * below the 0.45 floor". Showing the working is what lets a reviewer
 * overrule the verdict on sight rather than on faith. */
.ws-why {
  margin-bottom: 8px;
  color: var(--gs-muted);
  font-size: 11.5px;
  line-height: 1.5;
}
.ws-evidence {
  padding: 10px 12px;
  border-left: 2px solid var(--gs-line);
  background: var(--gs-panel);
  border-radius: 0 8px 8px 0;
}
.ws-evidence__item + .ws-evidence__item { margin-top: 8px; }
.ws-evidence__quote {
  display: block;
  color: var(--gs-copy);
  font-size: 12px;
  font-style: italic;
}
.ws-evidence__where {
  display: inline-block;
  margin-top: 4px;
  color: var(--gs-muted);
  font-family: var(--gs-mono);
  font-size: 10.5px;
}

/* Relationships: what turns a list of fields into a graph. Read-only
 * until the creation agent consumes them (A3b), and labelled as such. */
.ws-rels {
  margin-bottom: 20px;
  border: 1px solid var(--gs-line);
  border-radius: 14px;
  background: var(--gs-paper);
}
.ws-rels__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding: 14px 20px;
  border-bottom: 1px solid var(--gs-line);
  background: var(--gs-panel);
}
.ws-rels__title { color: var(--gs-ink); font-size: 13px; font-weight: 600; }
.ws-rels__note { margin-top: 3px; max-width: 62ch; color: var(--gs-muted); font-size: 11.5px; }
.ws-rels__count {
  flex: none;
  color: var(--gs-muted);
  font-family: var(--gs-mono);
  font-size: 11px;
}
.ws-rel { padding: 12px 20px; border-bottom: 1px solid var(--gs-soft-line); }
.ws-rel:last-child { border-bottom: 0; }
.ws-rel__head { display: flex; align-items: flex-start; gap: 12px; }
.ws-rel__triple {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}
.ws-rel__meta,
.ws-rel .ws-evidence { margin-left: 32px; }
.ws-rel__node {
  padding: 3px 9px;
  border: 1px solid var(--gs-line);
  border-radius: 7px;
  background: var(--gs-panel);
  color: var(--gs-ink);
  font-family: var(--gs-mono);
  font-size: 11.5px;
}
.ws-rel__edge { color: var(--gs-burgundy); font-family: var(--gs-mono); font-size: 11.5px; }
.ws-rel__arrow { color: var(--gs-muted); font-size: 11px; }
.ws-rel__meta { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.ws-rel__mediator {
  padding: 2px 8px;
  border: 1px solid rgb(126 34 48 / .3);
  border-radius: 999px;
  background: rgb(126 34 48 / .07);
  color: var(--gs-burgundy);
  font-family: var(--gs-mono);
  font-size: 10px;
}
.ws-rel__qualifier {
  padding: 2px 8px;
  border: 1px dashed var(--gs-line);
  border-radius: 999px;
  color: var(--gs-muted);
  font-size: 10.5px;
}
.ws-rel .ws-evidence { margin-top: 8px; }

.ws-run-report {
  padding: 20px 22px;
  border: 1px solid #e3ded4;
  border-radius: 14px;
  background: #fffdfa;
}
.ws-run-report__title {
  margin: 0 0 14px;
  color: #6f6961;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
}
.ws-run-report__metrics {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 1px;
  overflow: hidden;
  border: 1px solid #ede9e2;
  border-radius: 10px;
  background: #ede9e2;
}
.ws-run-report__metric { min-width: 0; padding: 13px 15px; background: #fffdfa; }
.ws-run-report__value {
  overflow: hidden;
  color: #17140f;
  font-family: var(--gs-mono);
  font-size: 21px;
  font-weight: 500;
  letter-spacing: -.02em;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ws-run-report__value.is-source { padding-top: 7px; font-size: 12px; }
.ws-run-report__value.is-warning { color: #8a5a12; }
.ws-run-report__label { margin-top: 3px; color: #6f6961; font-size: 10.5px; letter-spacing: .08em; text-transform: uppercase; }
.ws-run-report__footer { margin-top: 14px; color: #3a342c; font-size: 13px; }
.ws-inline-link { color: #7e2230; }

.ws-explore-search {
  display: flex;
  align-items: center;
  gap: 11px;
  height: 46px;
  margin-bottom: 22px;
  padding: 0 16px;
  border: 1px solid #cfc7ba;
  border-radius: 12px;
  background: #fffdfa;
}
.ws-explore-search__icon { color: #7e2230; font-size: 15px; }

/* The standing promise under the search box. Not an empty state - it is
   true when the box is empty, when it holds a query, and when results
   are on screen - so it keeps its own class and never borrows
   `.ws-ops-empty`, whose margins assume it is the only thing there. The
   rule below is the reason: it stays legible with content beneath it. */
.ws-explore-guarantee {
  margin: -8px 2px 16px;
  font-size: 13px;
  color: var(--gs-muted);
  line-height: 1.6;
  max-width: 72ch;
}
.ws-explore-search__input {
  min-width: 0;
  flex: 1;
  border: 0;
  background: transparent;
  color: #17140f;
  font-size: 15px;
}
.ws-explore-search__count { color: #6f6961; font-family: var(--gs-mono); font-size: 11px; white-space: nowrap; }
.ws-explore-block { margin-bottom: 24px; }
.ws-explore-block__title {
  margin: 0 0 12px;
  color: #6f6961;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
}
.ws-explore-list {
  overflow: hidden;
  border: 1px solid #e3ded4;
  border-radius: 14px;
  background: #fffdfa;
}
.ws-explore-row {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  padding: 15px 20px;
  border-bottom: 1px solid #f2efe9;
}
.ws-explore-row:last-child { border-bottom: 0; }
.ws-explore-row__score {
  width: 36px;
  flex: 0 0 36px;
  padding-top: 2px;
  font-family: var(--gs-mono);
  font-size: 13px;
}
.ws-explore-row__score.is-strong { color: #2e6448; }
.ws-explore-row__score.is-medium { color: #8a5a12; }
.ws-explore-row__score.is-weak { color: #6f6961; }
.ws-explore-row__body { min-width: 0; flex: 1; }
.ws-explore-row__title-line { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ws-explore-row__title { color: #17140f; font-size: 14px; font-weight: 500; }
.ws-explore-row__badge {
  display: inline-flex;
  align-items: center;
  min-height: 18px;
  padding: 2px 6px;
  border-radius: 4px;
  background: #e9f1ec;
  color: #2e6448;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.ws-explore-row__badge.is-external { background: #f3eeea; color: #7e2230; }
.ws-explore-row__meta {
  display: block;
  margin-top: 4px;
  overflow: hidden;
  color: #6f6961;
  font-family: var(--gs-mono);
  font-size: 10.5px;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ws-explore-row__description { display: block; margin-top: 6px; color: #6c665c; font-size: 12.5px; line-height: 1.5; }
.ws-explore-row__action {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 28px;
  flex: 0 0 auto;
  padding: 0 12px;
  border: 1px solid #e3ded4;
  border-radius: 7px;
  background: #fffdfa;
  color: #17140f;
  font-size: 12px;
  text-decoration: none;
}
.ws-explore-row__action:hover { border-color: #17140f; color: #17140f; }
.ws-explore-row__action--import { border-color: #17140f; background: #17140f; color: #f7f5f1; }
.ws-explore-row__action--import:hover { background: #2c271f; color: #f7f5f1; }
.ws-explore-empty { margin: 0; padding: 20px; color: #6f6961; font-size: 12.5px; }
.ws-explore-create {
  padding: 22px 24px;
  border: 1px solid #e3ded4;
  border-radius: 14px;
  background: #f4f1ec;
}
.ws-explore-create__title { margin: 0 0 6px; color: #17140f; font-size: 15px; font-weight: 600; letter-spacing: -.015em; }
.ws-explore-create__copy { max-width: 56ch; margin: 0 0 16px; color: #6c665c; font-size: 13px; }
.ws-explore-create__actions { display: flex; flex-wrap: wrap; gap: 10px; }

.ws-library-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
  margin-bottom: 26px;
}
.ws-library-card {
  display: flex;
  min-width: 0;
  flex-direction: column;
  gap: 8px;
  padding: 17px 19px;
  border: 1px solid #e3ded4;
  border-radius: 13px;
  background: #fffdfa;
}
.ws-library-card__head { display: flex; align-items: center; gap: 8px; min-width: 0; }
.ws-library-card__title {
  min-width: 0;
  overflow: hidden;
  color: #17140f;
  font-size: 14.5px;
  font-weight: 600;
  letter-spacing: -.01em;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ws-library-card__badge {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  min-height: 18px;
  padding: 2px 6px;
  border-radius: 4px;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.ws-library-card__badge.is-bundled { background: #f0ede6; color: #6c665c; }
.ws-library-card__badge.is-registered { background: #f3eeea; color: #7e2230; }
.ws-library-card__description {
  margin: 0;
  color: #6c665c;
  font-size: 12.5px;
  line-height: 1.5;
  text-wrap: pretty;
}
.ws-library-card__meta {
  color: #6f6961;
  font-family: var(--gs-mono);
  font-size: 10.5px;
  line-height: 1.45;
}
.ws-library-card__actions { display: flex; gap: 8px; margin-top: 4px; }
.ws-library-card__domain {
  min-width: 0;
  flex: 1;
  height: 30px;
  padding: 0 10px;
  border: 1px solid #e3ded4;
  border-radius: 7px;
  background: #f7f5f1;
  color: #17140f;
  font-family: var(--gs-mono);
  font-size: 11.5px;
}
.ws-library-card__domain:focus { border-color: #a49b8e; box-shadow: 0 0 0 3px rgba(126, 34, 48, .08); }
.ws-library-card__import {
  height: 30px;
  padding: 0 14px;
  border: 1px solid #17140f;
  border-radius: 7px;
  background: #17140f;
  color: #f7f5f1;
  font-size: 12px;
  white-space: nowrap;
}
.ws-library-card__import:hover { background: #2c271f; }
.ws-library-empty { grid-column: 1 / -1; margin: 0; color: #6f6961; }
.ws-library-register {
  padding: 20px 22px;
  border: 1px solid #e3ded4;
  border-radius: 14px;
  background: #fffdfa;
}
.ws-library-register__title {
  margin: 0 0 4px;
  color: #6f6961;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
}
.ws-library-register__copy { margin: 0 0 16px; color: #6f6961; font-size: 12.5px; }
.ws-library-form__grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 12px;
  margin-bottom: 12px;
}
.ws-library-form__field { display: flex; min-width: 0; flex-direction: column; gap: 5px; }
.ws-library-form__field > span { color: #6c665c; font-size: 11.5px; }
.ws-library-form__input {
  width: 100%;
  height: 32px;
  min-width: 0;
  padding: 0 11px;
  border: 1px solid #e3ded4;
  border-radius: 8px;
  background: #fffdfa;
  color: #17140f;
  font-size: 12.5px;
}
.ws-library-form__input:focus,
.ws-library-form__turtle:focus { border-color: #a49b8e; box-shadow: 0 0 0 3px rgba(126, 34, 48, .08); }
.ws-library-form__turtle {
  display: block;
  width: 100%;
  min-height: 92px;
  padding: 11px 13px;
  border: 1px solid #e3ded4;
  border-radius: 8px;
  background: #f7f5f1;
  color: #17140f;
  font-family: var(--gs-mono);
  font-size: 11.5px;
  line-height: 1.6;
  resize: vertical;
}
.ws-library-register__action { margin-top: 12px; }

@media (max-width: 860px) {
  .ws-build-page { padding: 28px 20px 64px; }
  .ws-build-page__motif { display: none; }
  .ws-ingest-setup { grid-template-columns: 1fr; }
  .ws-ingest-report { overflow-x: auto; }
  .ws-run-report__metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ws-library-form__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 560px) {
  .ws-explore-row { padding: 14px; gap: 10px; }
  .ws-explore-row__action { padding: 0 9px; }
  .ws-explore-row__description { font-size: 12px; }
  .ws-library-grid,
  .ws-library-form__grid { grid-template-columns: 1fr; }
}
.dc-export { overflow: hidden; padding: 0; }
.dc-export__head { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 12px 18px; border-bottom: 1px solid var(--gs-soft-line); background: var(--gs-panel); }
.dc-export__file { color: #6c665c; font-family: var(--gs-mono); font-size: 11.5px; }
.dc-export pre { min-height: 420px; padding: 20px 22px; font-size: 12px; line-height: 1.75; }

.dc-production { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; padding: 16px 20px; margin-bottom: 14px; }
.dc-production__label { display: inline-flex; align-items: center; gap: 8px; color: var(--gs-muted); font-size: 11px; font-weight: 600; letter-spacing: .1em; text-transform: uppercase; }
.dc-production__version { color: var(--gs-ink); font-family: var(--gs-mono); font-size: 17px; font-weight: 500; }
.dc-production__copy { flex: 1; color: var(--gs-amber); font-size: 12.5px; }
.dc-production__form { display: flex; gap: 8px; }
.dc-production__form .dc-input { width: 96px; font-family: var(--gs-mono); }
.dc-release-chain { margin: 0; padding: 0; list-style: none; }
.dc-release { display: grid; grid-template-columns: 22px minmax(0, 1fr); gap: 16px; }
.dc-release__spine { display: flex; flex-direction: column; align-items: center; padding-top: 20px; }
.dc-release__dot { width: 8px; height: 8px; border-radius: 50%; background: var(--gs-green); }
.dc-release__line { width: 1px; flex: 1; background: var(--gs-line); }
.dc-release__body { margin-bottom: 12px; padding: 16px 19px; }
.dc-release__head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 8px; }
.dc-release__version { color: var(--gs-ink); font-family: var(--gs-mono); font-size: 15px; font-weight: 600; }
.dc-release__meta { color: var(--gs-muted); font-family: var(--gs-mono); font-size: 10.5px; }
.dc-release__facts { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; color: #6c665c; font-family: var(--gs-mono); font-size: 11px; }
.dc-release__links { display: flex; gap: 14px; flex-wrap: wrap; font-size: 12px; }

.dc-people-row { display: flex; align-items: center; gap: 14px; padding: 14px 20px; border-bottom: 1px solid #f2efe9; }
.dc-person__avatar { display: grid; width: 28px; height: 28px; place-items: center; border-radius: 50%; background: #f0e3e5; color: var(--gs-burgundy); font-family: var(--gs-mono); font-size: 11px; font-weight: 600; }
.dc-person__main { flex: 1; }
.dc-person__name { display: block; color: var(--gs-ink); font-size: 13.5px; }
.dc-person__meaning { display: block; margin-top: 2px; color: var(--gs-muted); font-size: 12px; }
.dc-person-form { display: grid; grid-template-columns: 1fr 1fr 120px auto; gap: 10px; padding: 14px 20px; background: var(--gs-panel); }

.dc-pipeline-steps { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 8px; }
.dc-step { padding: 14px 13px; border: 1px solid var(--gs-line); border-radius: 11px; background: var(--gs-paper); }
.dc-step__n { margin-bottom: 7px; color: var(--gs-burgundy); font-family: var(--gs-mono); font-size: 10.5px; }
.dc-step__title { color: var(--gs-ink); font-size: 12.5px; font-weight: 600; line-height: 1.3; }
.dc-step__copy { margin-top: 5px; color: var(--gs-muted); font-size: 11px; line-height: 1.45; }
.dc-contrast-row { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; padding: 14px 20px; border-bottom: 1px solid #f2efe9; }
.dc-contrast-row__old { color: var(--gs-muted); font-size: 13px; text-decoration: line-through; text-decoration-color: #d8d2c7; }
.dc-contrast-row__now { color: var(--gs-ink); font-size: 13px; }
.dc-glossary { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
.dc-glossary__item { padding: 15px 17px; }
.dc-glossary__term { margin-bottom: 5px; color: var(--gs-ink); font-size: 13.5px; font-weight: 600; }
.dc-glossary__def { color: #6c665c; font-size: 12.5px; line-height: 1.55; }

.dc-graph-layout { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: 20px; align-items: start; }
.dc-graph-canvas { position: relative; overflow: hidden; padding: 0; }
.dc-graph-svg { display: block; width: 100%; min-height: 520px; background-image: radial-gradient(circle at 1px 1px, #ede9e2 1px, transparent 0); background-size: 22px 22px; }
.dc-graph-legend { display: flex; gap: 18px; flex-wrap: wrap; padding: 12px 18px; border-top: 1px solid var(--gs-soft-line); background: var(--gs-panel); color: #6c665c; font-size: 11.5px; }
.dc-graph-legend__item { display: inline-flex; align-items: center; gap: 7px; }
.dc-graph-legend__dot { width: 9px; height: 9px; border: 1px solid var(--gs-burgundy); border-radius: 50%; background: var(--gs-paper); }
.dc-graph-sidebar { padding: 18px 20px; }
.dc-graph-sidebar__uri { overflow: hidden; margin-bottom: 6px; color: var(--gs-muted); font-family: var(--gs-mono); font-size: 10.5px; text-overflow: ellipsis; white-space: nowrap; }
.dc-graph-sidebar h2 { margin: 0 0 8px; font-size: 17px; font-weight: 600; letter-spacing: -.02em; }
.dc-graph-sidebar p { color: #6c665c; font-size: 12.5px; line-height: 1.55; }
.dc-attr-list { display: flex; flex-direction: column; gap: 6px; margin-bottom: 16px; }
.dc-attr { display: flex; gap: 10px; align-items: baseline; color: var(--gs-copy); font-family: var(--gs-mono); font-size: 11.5px; }
.dc-attr__value { margin-left: auto; color: var(--gs-muted); font-size: 11px; }

.dc-empty { padding: 38px 20px; color: var(--gs-muted); font-size: 13px; text-align: center; }
.dc-note { color: var(--gs-muted); font-size: 12px; }
.dc-inline-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

#toast {
  position: fixed;
  right: 24px;
  bottom: 24px;
  z-index: 200;
  max-width: min(420px, calc(100vw - 48px));
  padding: 11px 14px;
  border: 1px solid #c8ded0;
  border-radius: 9px;
  background: #e9f1ec;
  color: var(--gs-green);
  box-shadow: 0 12px 30px rgba(23,20,15,.12);
  font-size: 12.5px;
}
#toast.error { border-color: #ebc9cf; background: #faecee; color: var(--gs-danger); }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0,0,0,0); clip-path: inset(50%); white-space: nowrap; }

.gs-palette {
  position: fixed;
  inset: 0;
  z-index: 220;
  display: flex;
  justify-content: center;
  align-items: flex-start;
  padding-top: 13vh;
  background: rgba(23,20,15,.28);
  backdrop-filter: blur(3px);
}
.gs-palette[hidden] { display: none; }
.gs-palette__panel { width: min(560px, 92vw); overflow: hidden; border: 1px solid var(--gs-line); border-radius: 16px; background: var(--gs-paper); box-shadow: 0 28px 70px rgba(23,20,15,.22); }
.gs-palette__bar { display: flex; align-items: center; gap: 11px; padding: 15px 18px; border-bottom: 1px solid var(--gs-soft-line); }
.gs-palette__input { flex: 1; min-width: 0; border: 0; background: transparent; color: var(--gs-ink); font-size: 15px; }
.gs-palette__list { max-height: min(48vh, 380px); overflow-y: auto; padding: 8px; }
.gs-palette__item { display: flex; align-items: center; gap: 12px; width: 100%; padding: 9px 11px; border: 0; border-radius: 9px; background: transparent; color: var(--gs-ink); text-align: left; }
.gs-palette__item:hover,
.gs-palette__item:focus-visible { background: #f2efe9; outline: 2px solid rgb(126 34 48 / .48); outline-offset: 2px; }
.gs-palette__glyph { display: grid; width: 26px; height: 26px; place-items: center; border-radius: 7px; background: #f3eeea; color: var(--gs-burgundy); font-family: var(--gs-mono); font-size: 12px; }
.gs-palette__item-title { display: block; font-size: 13.5px; }
.gs-palette__item-copy { display: block; color: var(--gs-muted); font-size: 11.5px; }
.gs-palette__group { margin-left: auto; color: var(--gs-muted); font-family: var(--gs-mono); font-size: 10px; letter-spacing: .08em; text-transform: uppercase; }
.gs-palette__foot { display: flex; gap: 16px; padding: 9px 16px; border-top: 1px solid var(--gs-soft-line); background: var(--gs-bg); color: var(--gs-muted); font-size: 11px; }

@keyframes gs-rise { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
/* `backwards`, NOT `both`.
 *
 * `both` keeps the animation FILLING after it ends, so the transform it
 * animates stays applied for the life of the page — and an element with a
 * transform carries a stacking context, permanently. Measured: every
 * direct child of `.dc-page` reported `matrix(1, 0, 0, 1, 0, 0)` seconds
 * after the 0.32s animation finished, `.dc-author-layout` among them. Any
 * popover inside one of those children was confined to it and could not
 * paint above a later sibling, which is the same trap that put the
 * Graph's change picker behind the canvas.
 *
 * `backwards` covers the delay before the animation starts — which is
 * what the stagger on `--i` needs — and then lets the element return to
 * its own styles. Visually identical: the keyframe ends on the values the
 * element already has. */
.dc-page > * { animation: gs-rise .32s cubic-bezier(.22,.61,.36,1) backwards; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { scroll-behavior: auto !important; animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; }
}

@media (max-width: 1100px) {
  .dc-grid-12 > .dc-span-7,
  .dc-grid-12 > .dc-span-5,
  .dc-grid-12 > .dc-span-6,
  .dc-grid-12 > .dc-span-4,
  .dc-grid-12 > .dc-span-3 { grid-column: span 12; }
  .dc-split,
  .dc-split--detail,
  .dc-author-layout,
  .dc-graph-layout { grid-template-columns: minmax(0, 1fr); }
  .dc-side,
  .dc-preview { position: static; }
  .dc-author-layout .dc-preview { order: -1; }
}

/* WORKSPACE HEADER, 921-1200px: the band where the nav was overlapped.
 *
 * `.gs-nav` is the only shrinkable item in the header (`min-width: 0`,
 * `nowrap`, visible overflow), so once brand + nav + search + health +
 * avatar + gaps exceeded the bar, the nav alone absorbed the shortfall and
 * its items spilled out UNDERNEATH the search box. Measured at 1152px: the
 * header needed 1172px against 1096 available, and the nav overflowed by
 * 78px - "Knowledge" was sliced in half and Operate and Settings were
 * completely buried and unclickable. The collapse rule that hides the nav
 * only fired at 920px, leaving ~260px of widths broken.
 *
 * Navigation is the last thing that should be sacrificed, so it is now the
 * one item that never shrinks, and the space comes from the search instead:
 * it drops to its icon. That is safe because `.gs-search` already carries
 * aria-label="Search or jump to a screen, term, or action", so the
 * accessible name survives the label being hidden, and the keyboard path
 * is unaffected. Below 920px the existing rule still hides the nav. */
/* Marker group headings ("Markers", "Characteristics (object properties)",
   "Relationships") are rows inside the marker grid, not cells - without this
   they land in the first free column and read as an empty card. */
.dc-marker-group {
  grid-column: 1 / -1;
  margin: 6px 0 -2px;
  font-size: 11px;
  letter-spacing: .1em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--gs-muted);
}
.dc-marker-group:first-child { margin-top: 0; }

/* 1520, not 1420. This threshold is a function of how wide the nav is,
 * and the nav got wider when its items were given room to stop
 * overlapping each other's focus rings. Measured at 1280 with the old
 * threshold: the last item's right edge was 74px INSIDE the search box -
 * the same spill this band was written to prevent, reintroduced by a
 * change three rules away. A breakpoint tuned to a measurement has to
 * move when the measurement does.
 *
 * Re-derived after the lifecycle nav landed: nine items and three kind
 * separators need ~841px, and the whole bar ~1487px with the search
 * already collapsed to its icon. 1420 fired 67px too late, so Settings
 * sat under the search on any 1440-1520 viewport - which is most laptops
 * at 125%%. 1520 gives the estimate ~33px of headroom. If you add a nav
 * item, add ~70px here. */
@media (max-width: 1520px) and (min-width: 1221px) {
  body > .workspace-header,
  body > header.workspace-header { gap: 16px; padding: 0 18px; }
  .gs-nav { flex: 0 0 auto; min-width: auto; gap: 11px; }
  /* Tighter PADDING rather than a tighter gap: shrinking the space
   * between items is what put the rings back on top of each other, and a
   * pill can lose a few pixels of its own without touching anything. */
  .gs-nav__item { padding: 0 8px; }
  .gs-search { flex: 0 0 auto; }
}

/* Tighter still: the brand's secondary line is the least load-bearing text
   in the bar, so it goes before any control does. */
/* THE SUBTITLE GOES AT 1300, NOT 1060.
 *
 * Measured on the SIGNED-IN header, which is the one a deployment has:
 * `body:has(.auth-chip) .gs-avatar { display: none }` swaps the 26px
 * avatar for a name-and-role chip, so the bar needs 1260px with the full
 * brand and 1214px without it. The subtitle was collapsing at 1060 —
 * below the width where it was already needed — so from 1250px down to
 * 921px the header ran off the screen and the page scrolled sideways.
 * Anonymous it starts at 1180; signed in it starts at 1250, and nobody
 * uses this signed out. */
@media (max-width: 1300px) and (min-width: 1221px) {
  .gs-brand__sub { display: none; }
}

/* NARROW: the nav moves to its own row rather than disappearing.
 *
 * `display: none` was the old answer, and it left the app with NO
 * navigation at all below 920px - no menu button, no drawer, nothing.
 * Measured before this rule: eight nav items, zero visible, at 820px,
 * 560px and 390px alike. The only way left to change screen was the
 * command palette, which is a search box a first-time visitor has no
 * reason to think is also the menu. The comment two rules above says
 * navigation is the last thing that should be sacrificed; this is that
 * sentence applied to the place it was not.
 *
 * A second row, scrolled horizontally, rather than a hamburger: the
 * destinations stay VISIBLE (a menu behind a button is a menu you have to
 * know about), it needs no JavaScript, no focus trap and no escape
 * handling, and it degrades to a swipe on a phone. The header stops being
 * a fixed 56px and becomes two stacked rows, which is why `height` gives
 * way to `min-height` here. */
/* 1220, NOT 920.
 *
 * The wrap below is the RIGHT behaviour and it was simply firing 300px
 * too late. One row needs 1214px signed in with the brand collapsed, so
 * everything from 1220 down belongs on two rows — and between 921 and
 * 1220 the old rules left it on one, with `gs-health` and the identity
 * chip past the viewport edge and a horizontal scrollbar on every page.
 *
 * Nothing is hidden by moving this: the nav keeps every destination
 * visible on its own scrollable row, which is what the note below argues
 * for. If you add a nav item, re-measure — this number is a measurement,
 * not a round figure. */
@media (max-width: 1220px) {
  body > .workspace-header,
  body > header.workspace-header {
    flex-wrap: wrap;
    gap: 14px;
    height: auto;
    min-height: 56px;
    padding: 0 18px;
  }
  .gs-nav {
    order: 10;
    /* `1 1 100%` and `min-width: 0`, not `1 0 100%` with negative margins.
     * The bleed-to-the-edge version measured 398px inside a 390px header
     * and pushed the whole document 4px sideways: a flex item that cannot
     * shrink, plus -18px margins, is wider than the line it sits on. The
     * rule inset by the header's own padding looks tidier anyway. */
    flex: 1 1 100%;
    min-width: 0;
    gap: 10px;
    margin: 0;
    padding: 6px 0;
    border-top: 1px solid var(--gs-line);
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    -ms-overflow-style: none;
  }
  .gs-nav::-webkit-scrollbar { display: none; }
  .gs-nav__item { flex: 0 0 auto; }
  .gs-search { flex: 1 1 120px; width: auto; min-width: 0; }
  .gs-search__key { display: none; }
}

@media (max-width: 860px) {
  .dc-page { padding: 28px 20px 64px; }
  .dc-metric-band { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .dc-metric:nth-child(4) { border-left: 1px solid var(--gs-line); }
  .dc-ingest-top,
  .dc-action-grid,
  .dc-explore-grid,
  .dc-library-grid { grid-template-columns: 1fr 1fr; }
  .dc-pipeline-steps { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

@media (max-width: 680px) {
  body > .workspace-header,
  /* `min-height`, not `height`. The nav wraps to its own row below 920px,
   * and a fixed height meant the header stayed 54px tall while the nav
   * rendered underneath it - straight over the page's first line. The
   * measurement that caught it: header bottom 56, content top 56, nav
   * occupying 56-94. */
  body > header.workspace-header { min-height: 54px; height: auto; padding: 0 14px; }
  .gs-brand__sub,
  .gs-health { display: none; }
  .gs-search { margin-left: auto; }
  .dc-page { padding: 24px 16px 56px; }
  .dc-page h1 { font-size: 28px; }
  .dc-page__hero { align-items: flex-start; margin-bottom: 20px; }
  .dc-page__hero-actions { width: 100%; }
  .dc-motif { display: none; }
  .dc-metric-band { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .dc-metric:nth-child(n) { border-left: 1px solid var(--gs-line); }
  .dc-grid-12 { gap: 14px; }
  .dc-card { padding: 17px; border-radius: 12px; }
  .dc-pipeline { gap: 6px; }
  .dc-pipeline__vessel { height: 72px; }
  .dc-pipeline__label { font-size: 10px; }
  .dc-pipeline__count { font-size: 17px; }
  .dc-gauntlet__note { margin-left: 0; width: 100%; }
  .dc-health-breakdown,
  .dc-code-grid,
  .dc-impact-grid,
  .dc-form-grid,
  .dc-marker-grid,
  .dc-glossary,
  .dc-contrast-row { grid-template-columns: 1fr; }
  .dc-form-field--full { grid-column: auto; }
  .dc-ingest-top,
  .dc-action-grid,
  .dc-explore-grid,
  .dc-library-grid { grid-template-columns: 1fr; }
  .dc-table__head { display: none; }
  .dc-table__row,
  .dc-decisions-table .dc-table__row { grid-template-columns: minmax(0,1fr) auto; gap: 8px; padding: 13px 16px; }
  .dc-table__row > :nth-child(2),
  .dc-table__row > :nth-child(3),
  .dc-table__row > :nth-child(4),
  .dc-decisions-table .dc-table__row > :nth-child(4),
  .dc-decisions-table .dc-table__row > :nth-child(5) { display: none; }
  .dc-table__age { grid-column: 2; grid-row: 1; }
  .dc-report-table { display: block; overflow-x: auto; }
  .dc-report-table th,
  .dc-report-table td { padding-right: 12px; padding-left: 12px; white-space: nowrap; }
  .dc-production { align-items: flex-start; }
  .dc-production__copy { flex-basis: 100%; }
  .dc-person-form { grid-template-columns: 1fr; }
  .dc-pipeline-steps { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Review / Queue, Duplicates, and Decisions are document-derived surfaces.
 * They intentionally do not inherit the earlier portal cards or review table
 * classes: their sizes, grid tracks, and information hierarchy mirror the
 * supplied workspace document directly. */
.ws-review-page,
.ws-review-page * { box-sizing: border-box; }
.ws-review-page {
  width: 100%;
  max-width: 1180px;
  margin: 0 auto;
  padding: 34px 32px 80px;
}
.ws-review-page--wide { max-width: 1320px; }
.ws-review-page__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
  margin-bottom: 24px;
}
.ws-review-page__intro { min-width: 0; }
.ws-review-page__eyebrow {
  margin-bottom: 8px;
  color: #6f6961;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
}
.ws-review-page h1 {
  margin: 0 0 8px;
  color: #17140f;
  font-size: 30px;
  font-weight: 600;
  letter-spacing: -.028em;
  line-height: 1.1;
}
/* Route announcements focus the non-interactive heading programmatically.
 * Keep that semantic focus target, but do not expose a browser-default ring
 * intended for keyboard-operable controls. */
.ws-review-page__lede {
  max-width: 62ch;
  margin: 0;
  color: #6c665c;
  font-size: 14px;
  text-wrap: pretty;
}
.ws-review-page--queue .ws-review-page__head { margin-bottom: 22px; }
.ws-review-page--queue h1 { margin: 0; }
.ws-review-page--queue .ws-review-page__lede { margin-top: 8px; }
.ws-review-page--duplicates .ws-review-page__head { margin-bottom: 20px; }
.ws-review-page--decisions .ws-review-page__head { margin-bottom: 24px; }
.ws-review-page__actions { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.ws-review-filter {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 30px;
  padding: 0 12px;
  border: 1px solid #e3ded4;
  border-radius: 8px;
  background: #fffdfa;
  color: #6c665c;
  font-size: 12.5px;
  text-decoration: none;
  white-space: nowrap;
}
.ws-review-filter:hover { border-color: #cfc7ba; color: #17140f; }
.ws-review-filter.is-active { border-color: #17140f; background: #17140f; color: #f7f5f1; }
.ws-review-primary-button,
.ws-review-secondary-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 32px;
  padding: 0 15px;
  border-radius: 8px;
  font: inherit;
  font-size: 12.5px;
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}
.ws-review-primary-button { border: 1px solid #17140f; background: #17140f; color: #f7f5f1; }
.ws-review-primary-button:hover { background: #2c271f; color: #f7f5f1; }
.ws-review-secondary-button { border: 1px solid #e3ded4; background: #fffdfa; color: #17140f; }
.ws-review-secondary-button:hover { border-color: #cfc7ba; color: #17140f; }
.ws-review-primary-button:disabled,
.ws-review-secondary-button:disabled { cursor: not-allowed; opacity: .55; }
.ws-review-filter:focus-visible,
.ws-review-primary-button:focus-visible,
.ws-review-secondary-button:focus-visible,
.ws-review-queue__row:focus-visible,
.ws-review-decisions__row:focus-visible { outline: 2px solid rgba(126, 34, 48, .48); outline-offset: 2px; }
.ws-review-badge {
  display: inline-flex;
  align-items: center;
  justify-self: start;
  min-height: 19px;
  padding: 2px 7px;
  border-radius: 5px;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .06em;
  line-height: 1.35;
  text-transform: uppercase;
  white-space: nowrap;
}
.ws-review-badge--green { background: #e9f1ec; color: #2e6448; }
.ws-review-badge--red { background: #f6ebec; color: #7e2230; }
.ws-review-badge--amber { background: #fdf3e7; color: #8a5a12; }
.ws-review-badge--muted { background: #f0ede6; color: #6c665c; }
.ws-review-dot {
  display: inline-block;
  width: 7px;
  height: 7px;
  flex: 0 0 7px;
  border-radius: 999px;
  background: #cfc7ba;
}
.ws-review-dot--green { background: #2e6448; }
.ws-review-dot--red { background: #7e2230; }
.ws-review-dot--amber { background: #c17d12; }
.ws-review-dot--muted { background: #cfc7ba; }

/* Queue */
.ws-review-queue {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 336px;
  gap: 20px;
  align-items: start;
}
.ws-review-queue__table {
  overflow: hidden;
  border: 1px solid #e3ded4;
  border-radius: 14px;
  background: #fffdfa;
}
.ws-review-queue__head,
.ws-review-queue__row {
  display: grid;
  grid-template-columns: minmax(160px, 1fr) 108px 130px 120px 74px;
  gap: 16px;
  align-items: center;
}
.ws-review-queue__head {
  padding: 11px 20px;
  border-bottom: 1px solid #e3ded4;
  background: #f4f1ec;
  color: #6f6961;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
}
.ws-review-queue__head-age { text-align: right; }
.ws-review-queue__row {
  width: 100%;
  padding: 13px 20px;
  border: 0;
  border-left: 2px solid transparent;
  border-bottom: 1px solid #ede9e2;
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.ws-review-queue__row:last-of-type { border-bottom: 0; }
.ws-review-queue__row:hover { background: #f5f2ec; }
.ws-review-queue__row.is-selected { border-left-color: #7e2230; background: #f5f2ec; }
.ws-review-queue__proposal { min-width: 0; }
.ws-review-queue__title {
  display: block;
  overflow: hidden;
  color: #17140f;
  font-size: 13.5px;
  font-weight: 500;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ws-review-queue__uri {
  display: block;
  margin-top: 3px;
  overflow: hidden;
  color: #6f6961;
  font-family: var(--gs-mono);
  font-size: 10.5px;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ws-review-queue__stage,
.ws-review-queue__reviewer {
  overflow: hidden;
  color: #3a342c;
  font-size: 12px;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ws-review-queue__reviewer { color: #3a342c; }
/* P006. An unassigned row is the backlog, and it should read as a gap
   rather than as a name - the column used to say "unassigned" when it
   meant "nobody has signed yet", which is a different fact. */
.ws-review-queue__reviewer.is-open { color: #9a938a; font-style: italic; }

/* P006 send-for-review panel. */
.ws-review-assign {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  margin: 6px 0 14px;
}
.ws-review-assign__who {
  color: #17140f;
  font-size: 13px;
  font-weight: 500;
}
.ws-review-assign__note {
  flex: 1 1 100%;
  color: #6f6961;
  font-size: 11.5px;
  line-height: 1.5;
}
/* The one outcome that is not a shortage of suggestions but a deployment
   that cannot reach quorum at all. It should not read like the others. */
.ws-review-assign__note--no_candidate {
  padding: 8px 10px;
  border-left: 2px solid #7e2230;
  border-radius: 0 6px 6px 0;
  background: #fbf3f2;
  color: #7e2230;
}
.ws-review-assign__select,
.ws-review-assign__reason {
  flex: 1 1 100%;
  padding: 7px 9px;
  border: 1px solid #ddd7cc;
  border-radius: 8px;
  background-color: #fffdfa;
  color: #17140f;
  font: inherit;
  font-size: 12.5px;
}
.ws-review-assign__actions {
  display: flex;
  flex: 1 1 100%;
  gap: 8px;
}
.ws-review-assign__button {
  padding: 6px 11px;
  border: 1px solid #ddd7cc;
  border-radius: 8px;
  background: #fffdfa;
  color: #3a342c;
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}
.ws-review-assign__button:hover { background: #f4f1ec; }
.ws-review-assign__button--primary {
  border-color: #7e2230;
  background: #7e2230;
  color: #fffdfa;
}
.ws-review-assign__button--primary:hover { background: #6b1c28; }
.ws-review-queue__age {
  color: #6f6961;
  font-family: var(--gs-mono);
  font-size: 11.5px;
  text-align: right;
}
.ws-review-queue__empty { margin: 0; padding: 20px; color: #6f6961; font-size: 12.5px; }
.ws-review-queue__inspector {
  position: sticky;
  top: 76px;
  padding: 18px 20px 20px;
  border: 1px solid #e3ded4;
  border-radius: 14px;
  background: #fffdfa;
}
.ws-review-queue__inspector-eyebrow {
  margin-bottom: 7px;
  color: #6f6961;
  font-family: var(--gs-mono);
  font-size: 10.5px;
  letter-spacing: .08em;
}
.ws-review-queue__inspector-title {
  margin: 0 0 12px;
  color: #17140f;
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -.02em;
  line-height: 1.25;
}
.ws-review-queue__inspector-empty { margin: 0; color: #6c665c; font-size: 12.5px; line-height: 1.5; }
.ws-review-badges { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 16px; }
.ws-review-mini-title {
  margin-bottom: 8px;
  color: #6f6961;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
}
.ws-review-tiers { display: flex; flex-direction: column; gap: 6px; margin-bottom: 16px; }
.ws-review-tier { display: flex; align-items: center; gap: 9px; min-width: 0; }
.ws-review-tier__label { min-width: 0; flex: 1; color: #3a342c; font-size: 12.5px; }
.ws-review-tier__state { color: #6f6961; font-family: var(--gs-mono); font-size: 10.5px; text-transform: lowercase; }
.ws-review-tier__state.is-green { color: #2e6448; }
.ws-review-tier__state.is-red { color: #7e2230; }
.ws-review-tier__state.is-amber { color: #8a5a12; }
.ws-review-meta-list {
  display: flex;
  flex-direction: column;
  gap: 7px;
  margin-bottom: 16px;
  padding-top: 14px;
  border-top: 1px solid #ede9e2;
}
.ws-review-meta-list__row { display: flex; gap: 12px; min-width: 0; font-size: 12px; }
.ws-review-meta-list__key { width: 78px; flex: 0 0 78px; color: #6f6961; }
.ws-review-meta-list__value {
  min-width: 0;
  flex: 1;
  overflow: hidden;
  color: #3a342c;
  font-family: var(--gs-mono);
  font-size: 11.5px;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ws-review-queue__open { display: flex; width: 100%; height: 34px; font-size: 13px; }

/* Duplicates */
.ws-review-duplicates { display: flex; flex-direction: column; gap: 12px; }
.ws-review-duplicate {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 172px;
  gap: 22px;
  padding: 18px 20px;
  border: 1px solid #e3ded4;
  border-radius: 14px;
  background: #fffdfa;
}
.ws-review-duplicate__main { min-width: 0; }
/* P007: the thread spans the whole card. It is the conversation ABOUT
   the pair, not a property of either column. */
.ws-review-duplicate > .ws-thread { grid-column: 1 / -1; }
.ws-review-duplicate__title-line { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 6px; }
.ws-review-duplicate__pair { color: #17140f; font-size: 15px; font-weight: 600; letter-spacing: -.015em; }
.ws-review-duplicate .ws-review-badge { border-radius: 4px; font-size: 9.5px; letter-spacing: .08em; }
.ws-review-duplicate__uris {
  display: flex;
  flex-direction: column;
  margin-bottom: 12px;
  overflow: hidden;
  color: #6f6961;
  font-family: var(--gs-mono);
  font-size: 10.5px;
  line-height: 1.7;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ws-review-duplicate__features { display: flex; flex-direction: column; gap: 5px; }
.ws-review-duplicate__feature { display: flex; align-items: center; gap: 9px; color: #3a342c; font-size: 12.5px; }
.ws-review-duplicate__side {
  display: flex;
  min-width: 0;
  flex-direction: column;
  align-items: flex-end;
  gap: 10px;
  padding-left: 20px;
  border-left: 1px solid #ede9e2;
}
.ws-review-duplicate__score {
  color: #8a5a12;
  font-family: var(--gs-mono);
  font-size: 27px;
  font-weight: 500;
  letter-spacing: -.03em;
  line-height: 1;
}
.ws-review-duplicate__score.is-strong { color: #7e2230; }
.ws-review-duplicate__winner { color: #6f6961; font-size: 11px; text-align: right; }
.ws-review-duplicate__winner > span { display: block; margin-top: 1px; color: #3a342c; font-family: var(--gs-mono); font-size: 11px; }
.ws-review-duplicate__actions { display: flex; gap: 7px; margin-top: auto; }
.ws-review-duplicate__button { height: 29px; padding: 0 12px; border-radius: 7px; font-size: 12px; }
.ws-review-duplicates__empty {
  margin: 0;
  padding: 20px;
  border: 1px solid #e3ded4;
  border-radius: 14px;
  background: #fffdfa;
  color: #6f6961;
  font-size: 12.5px;
}

/* Decisions */
.ws-review-decisions__table {
  overflow: hidden;
  margin-bottom: 26px;
  border: 1px solid #e3ded4;
  border-radius: 14px;
  background: #fffdfa;
}
.ws-review-decisions__head,
.ws-review-decisions__row {
  display: grid;
  grid-template-columns: 104px minmax(0, 1fr) 92px 108px minmax(0, 1.3fr) 88px;
  gap: 14px;
  align-items: center;
}
.ws-review-decisions__head {
  padding: 11px 20px;
  border-bottom: 1px solid #e3ded4;
  background: #f4f1ec;
  color: #6f6961;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
}
.ws-review-decisions__head-when { text-align: right; }
.ws-review-decisions__row {
  width: 100%;
  padding: 12px 20px;
  border-bottom: 1px solid #f2efe9;
  background: transparent;
  color: inherit;
  text-align: left;
  text-decoration: none;
}
.ws-review-decisions__row:last-of-type { border-bottom: 0; }
.ws-review-decisions__row:hover { background: #f5f2ec; }
.ws-review-decisions__proposal {
  overflow: hidden;
  color: #7e2230;
  font-family: var(--gs-mono);
  font-size: 11px;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ws-review-decisions__title,
.ws-review-decisions__reason,
.ws-review-decisions__reviewer {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ws-review-decisions__title { color: #17140f; font-size: 13px; }
.ws-review-decisions__reviewer { color: #6c665c; font-size: 12px; }
.ws-review-decisions__reason { color: #6f6961; font-size: 12px; }
.ws-review-decisions__when { color: #6f6961; font-family: var(--gs-mono); font-size: 11px; text-align: right; white-space: nowrap; }
.ws-review-decisions__row .ws-review-badge { border-radius: 4px; font-size: 10px; letter-spacing: .08em; }
.ws-review-decisions__empty { margin: 0; padding: 20px; color: #6f6961; font-size: 12.5px; }
.ws-review-changelog { padding: 20px 22px; border: 1px solid #e3ded4; border-radius: 14px; background: #fffdfa; }
.ws-review-changelog__head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 16px; }
.ws-review-changelog__head h2 {
  margin: 0;
  color: #6f6961;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
}
.ws-review-changelog__download { color: #7e2230; font-size: 12.5px; text-decoration: none; white-space: nowrap; }
.ws-review-changelog__download:hover { color: #5d1722; text-decoration: underline; }
.ws-review-changelog__entry { padding: 13px 0; border-top: 1px solid #f2efe9; }
.ws-review-changelog__title-line { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; margin-bottom: 4px; }
.ws-review-changelog__title { color: #17140f; font-size: 13.5px; font-weight: 500; }
.ws-review-changelog__entry .ws-review-badge { border-radius: 4px; font-size: 10px; letter-spacing: .08em; }
.ws-review-changelog__meta { margin-bottom: 6px; color: #6f6961; font-family: var(--gs-mono); font-size: 10.5px; }
.ws-review-changelog__added { color: #3a342c; font-size: 12.5px; }
.ws-review-changelog__why { margin-top: 4px; color: #6f6961; font-size: 12.5px; font-style: italic; line-height: 1.5; }
.ws-review-changelog__empty { margin: 0; color: #6f6961; font-size: 12.5px; }

@media (max-width: 1100px) {
  .ws-review-queue { grid-template-columns: minmax(0, 1fr); }
  .ws-review-queue__inspector { position: static; }
}
@media (max-width: 860px) {
  .ws-review-page { padding: 28px 20px 64px; }
  .ws-review-queue__table,
  .ws-review-decisions__table { overflow-x: auto; }
  .ws-review-queue__head,
  .ws-review-queue__row { min-width: 760px; }
  .ws-review-decisions__head,
  .ws-review-decisions__row { min-width: 760px; }
}
@media (max-width: 560px) {
  .ws-review-page { padding: 24px 16px 56px; }
  .ws-review-page__head { align-items: flex-start; }
  .ws-review-page__actions { width: 100%; }
  .ws-review-duplicate { grid-template-columns: minmax(0, 1fr); }
  .ws-review-duplicate__side {
    align-items: flex-start;
    padding-top: 14px;
    padding-left: 0;
    border-top: 1px solid #ede9e2;
    border-left: 0;
  }
  .ws-review-duplicate__winner { text-align: left; }
}

/* Schema / Graph and Competency Tests are direct document surfaces.  Their
 * canvas, inspector, and test run list do not inherit the generic portal
 * layouts so those routes stay faithful to the supplied workspace design. */
.ws-schema-page,
.ws-schema-page * { box-sizing: border-box; }
.ws-schema-page {
  width: 100%;
  max-width: 1180px;
  margin: 0 auto;
  padding: 34px 32px 80px;
}
.ws-schema-page--graph { max-width: 1400px; padding: 28px 32px 60px; }
.ws-schema-page--tests { max-width: 1080px; }
.ws-schema-page__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
  margin-bottom: 22px;
}
.ws-schema-page--graph .ws-schema-page__head { margin-bottom: 18px; }
.ws-schema-page__intro { min-width: 0; }
.ws-schema-page__eyebrow {
  margin-bottom: 8px;
  color: #6f6961;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
}
.ws-schema-page h1 {
  margin: 0 0 8px;
  color: #17140f;
  font-size: 30px;
  font-weight: 600;
  letter-spacing: -.028em;
  line-height: 1.1;
}
.ws-schema-page--graph h1 { margin: 0; }
.ws-schema-page__lede {
  max-width: 62ch;
  margin: 0;
  color: #6c665c;
  font-size: 14px;
  text-wrap: pretty;
}
.ws-schema-page__actions { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

/* Competency tests */
.ws-schema-tests__summary {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 20px;
  padding: 16px 20px;
  border: 1px solid #e3ded4;
  border-radius: 12px;
  background: #fffdfa;
}
.ws-schema-tests__score {
  color: #2e6448;
  font-family: var(--gs-mono);
  font-size: 26px;
  font-weight: 500;
  letter-spacing: -.03em;
  white-space: nowrap;
}
.ws-schema-tests__score.is-warning { color: #8a5a12; }
.ws-schema-tests__track { height: 7px; min-width: 0; flex: 1; overflow: hidden; border-radius: 999px; background: #ede9e2; }
.ws-schema-tests__fill { display: block; height: 100%; border-radius: inherit; background: #2e6448; }
.ws-schema-tests__summary-copy { color: #6c665c; font-size: 12.5px; white-space: nowrap; }
.ws-schema-tests__list { overflow: hidden; border: 1px solid #e3ded4; border-radius: 14px; background: #fffdfa; }
.ws-schema-test {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  padding: 14px 20px;
  border-bottom: 1px solid #f2efe9;
}
.ws-schema-test:last-of-type { border-bottom: 0; }
.ws-schema-test__dot { width: 7px; height: 7px; flex: 0 0 7px; margin-top: 5px; border-radius: 50%; background: #cfc7ba; }
.ws-schema-test__dot.is-pass { background: #2e6448; }
.ws-schema-test__dot.is-fail { background: #7e2230; }
.ws-schema-test__copy { min-width: 0; flex: 1; }
.ws-schema-test__question { display: block; color: #17140f; font-size: 13.5px; }
.ws-schema-test__spec {
  display: block;
  margin-top: 4px;
  overflow: hidden;
  color: #6f6961;
  font-family: var(--gs-mono);
  font-size: 10.5px;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ws-schema-test__state {
  display: inline-flex;
  align-items: center;
  min-height: 19px;
  padding: 2px 7px;
  border-radius: 5px;
  background: #f0ede6;
  color: #6c665c;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .08em;
  line-height: 1.35;
  text-transform: uppercase;
  white-space: nowrap;
}
.ws-schema-test__state.is-pass { background: #e9f1ec; color: #2e6448; }
.ws-schema-test__state.is-fail { background: #f6ebec; color: #7e2230; }
.ws-schema-tests__empty { margin: 0; padding: 20px; color: #6f6961; font-size: 12.5px; }

/* Graph */
.ws-schema-mode {
  height: 30px;
  padding: 0 14px;
  border: 1px solid #e3ded4;
  border-radius: 8px;
  background: #fffdfa;
  color: #6c665c;
  font: inherit;
  font-size: 12.5px;
  cursor: pointer;
}
.ws-schema-mode:hover { border-color: #cfc7ba; color: #17140f; }
.ws-schema-mode.is-active { border-color: #17140f; background: #17140f; color: #f7f5f1; }
.ws-schema-mode:focus-visible,
.ws-schema-graph__node:focus-visible { outline: 2px solid rgba(126, 34, 48, .48); outline-offset: 2px; }
.ws-schema-graph__delta-banner {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 14px;
  padding: 11px 16px;
  border: 1px solid #e8c9a8;
  border-radius: 10px;
  background: #fdf3e7;
  color: #8a5a12;
  font-size: 12.5px;
}
.ws-schema-graph__delta-dot { width: 6px; height: 6px; flex: 0 0 6px; border-radius: 50%; background: #b57a18; }
.ws-schema-graph {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 320px;
  gap: 20px;
  align-items: start;
}
.ws-schema-graph__canvas {
  overflow: hidden;
  border: 1px solid #e3ded4;
  border-radius: 14px;
  background: #fffdfa;
}
.ws-schema-graph__plot { position: relative; container-type: inline-size; overflow: hidden; }
.ws-schema-graph__svg {
  display: block;
  width: 100%;
  background: radial-gradient(circle at 1px 1px, #ede9e2 1px, transparent 0) 0 0 / 22px 22px;
}
.ws-schema-graph__node {
  position: absolute;
  z-index: 1;
  min-width: 0;
  padding: 0;
  border: 0;
  background: transparent;
  color: #17140f;
  font: inherit;
  font-size: clamp(10px, 1.41cqw, 15px);
  font-weight: 500;
  line-height: 1.15;
  text-align: center;
  transform: translate(-50%, -50%);
  white-space: nowrap;
  cursor: pointer;
}
.ws-schema-graph__node:hover,
.ws-schema-graph__node.is-selected { font-weight: 600; }
/* The single-scale legend these rules dressed is gone (P009). Its swatch
   classes - is-governed / is-upper / is-proposed / is-deprecated /
   is-object / is-subclass - encoded origin and lifecycle on one axis, and
   nothing now emits them. Removed rather than left behind: dead selectors
   are how a reader ends up believing the old encoding is still live. The
   replacement lives at the end of this file. */
.ws-schema-graph__inspector {
  position: sticky;
  top: 76px;
  padding: 18px 20px;
  border: 1px solid #e3ded4;
  border-radius: 14px;
  background: #fffdfa;
}
.ws-schema-graph__uri {
  margin-bottom: 6px;
  overflow: hidden;
  color: #6f6961;
  font-family: var(--gs-mono);
  font-size: 10.5px;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ws-schema-graph__inspector h2 { margin: 0 0 8px; color: #17140f; font-size: 17px; font-weight: 600; letter-spacing: -.02em; }
.ws-schema-graph__description { margin: 0 0 16px; color: #6c665c; font-size: 12.5px; line-height: 1.55; text-wrap: pretty; }
.ws-schema-graph__mini-title {
  margin-bottom: 8px;
  color: #6f6961;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
}
.ws-schema-graph__attribute-list,
.ws-schema-graph__relationship-list { display: flex; flex-direction: column; gap: 6px; margin-bottom: 16px; }
.ws-schema-graph__attribute { display: flex; align-items: baseline; gap: 10px; min-width: 0; }
.ws-schema-graph__attribute > :first-child { min-width: 0; flex: 1; overflow: hidden; color: #3a342c; font-family: var(--gs-mono); font-size: 11.5px; text-overflow: ellipsis; white-space: nowrap; }
.ws-schema-graph__attribute > :last-child { color: #6f6961; font-family: var(--gs-mono); font-size: 11px; white-space: nowrap; }
.ws-schema-graph__relationship { color: #3a342c; font-family: var(--gs-mono); font-size: 11.5px; line-height: 1.45; }
.ws-schema-graph__empty-row { color: #6f6961; font-size: 11.5px; }
.ws-schema-graph__audit { padding-top: 14px; border-top: 1px solid #ede9e2; color: #6f6961; font-size: 11.5px; line-height: 1.6; }

@media (max-width: 1100px) {
  .ws-schema-graph { grid-template-columns: minmax(0, 1fr); }
  .ws-schema-graph__inspector { position: static; }
}
@media (max-width: 860px) {
  .ws-schema-page,
  .ws-schema-page--graph { padding: 28px 20px 64px; }
  .ws-schema-tests__summary-copy { white-space: normal; }
}
@media (max-width: 560px) {
  .ws-schema-page,
  .ws-schema-page--graph { padding: 24px 16px 56px; }
  .ws-schema-page__head { align-items: flex-start; }
  .ws-schema-page__actions { width: 100%; }
  .ws-schema-tests__summary { align-items: flex-start; flex-wrap: wrap; }
  .ws-schema-tests__track { flex-basis: 100%; order: 3; }
  .ws-schema-test { padding: 14px 16px; }
  .ws-schema-test__state { margin-left: 4px; }
  .ws-schema-graph__legend { gap: 10px 14px; padding: 11px 14px; }
  .ws-schema-graph__node { font-size: 10px; }
}

/* Settings document surfaces ------------------------------------------------
 * Manual and People & access are direct translations of SKEMA Workspace.dc.html.
 * They intentionally do not share the legacy dc-page or dc-card selectors. */
.ws-settings-page,
.ws-settings-page * { box-sizing: border-box; }
.ws-settings-page {
  width: 100%;
  max-width: 1080px;
  margin: 0 auto;
  padding: 34px 32px 80px;
  color: #17140f;
}
.ws-settings-page--admin { max-width: 900px; }
.ws-settings-page__header { margin: 0 0 26px; }
.ws-settings-page__eyebrow {
  margin: 0 0 8px;
  color: #6f6961;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
}
.ws-settings-page h1 {
  margin: 0 0 8px;
  color: #17140f;
  font-size: 30px;
  font-weight: 600;
  letter-spacing: -.028em;
  line-height: 1.2;
}
.ws-settings-page h1[tabindex="-1"]:focus {
  outline: 2px solid #7e2230;
  outline-offset: 3px;
}
.ws-settings-page__lede {
  max-width: 62ch;
  margin: 0;
  color: #6c665c;
  font-size: 14px;
  line-height: 1.55;
  text-wrap: pretty;
}
.ws-settings-section-title {
  margin: 0 0 14px;
  color: #6f6961;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .14em;
  line-height: 1.25;
  text-transform: uppercase;
}
.ws-settings-control {
  width: 100%;
  min-width: 0;
  height: 31px;
  padding: 0 11px;
  border: 1px solid #e3ded4;
  border-radius: 8px;
  background-color: #fffdfa;
  color: #17140f;
  font: inherit;
  font-size: 12.5px;
}
.ws-settings-control:focus-visible {
  border-color: #7e2230;
  box-shadow: 0 0 0 3px rgb(126 34 48 / 13%);
}
.ws-settings-control:disabled {
  cursor: not-allowed;
  background: #f2efe9;
  color: #8b8479;
}
.ws-settings-button {
  min-width: max-content;
  height: 31px;
  padding: 0 15px;
  border-radius: 8px;
  font: inherit;
  font-size: 12.5px;
  cursor: pointer;
}
.ws-settings-button--primary {
  border: 1px solid #17140f;
  background: #17140f;
  color: #f7f5f1;
}
.ws-settings-button--primary:hover:not(:disabled) { background: #2c271f; }
.ws-settings-button--secondary {
  border: 1px solid #cfc7ba;
  background: #fffdfa;
  color: #17140f;
}
.ws-settings-button--secondary:hover:not(:disabled) { border-color: #17140f; }
.ws-settings-button:focus-visible {
  outline: 3px solid rgb(126 34 48 / 24%);
  outline-offset: 2px;
}
.ws-settings-button:disabled { cursor: not-allowed; opacity: .56; }
/* A third weight, for the row actions on the organization panel. Suspend
   and Remove are consequential but they are not the point of the screen,
   and a row of secondary buttons per member reads as a demand to act. */
.ws-settings-button--quiet {
  height: 27px;
  padding: 0 10px;
  font-size: 11.5px;
  border: 1px solid var(--gs-line);
  background: transparent;
  color: var(--gs-muted);
}
.ws-settings-button--quiet:hover:not(:disabled) {
  border-color: var(--gs-burgundy);
  color: var(--gs-burgundy);
}

/* People & access */
.ws-settings-admin__roster {
  overflow: hidden;
  margin-bottom: 22px;
  border: 1px solid #e3ded4;
  border-radius: 14px;
  background: #fffdfa;
}
.ws-settings-admin__person {
  display: flex;
  align-items: center;
  gap: 14px;
  min-width: 0;
  padding: 14px 20px;
  border-bottom: 1px solid #f2efe9;
}
.ws-settings-admin__avatar {
  display: flex;
  width: 28px;
  height: 28px;
  flex: 0 0 28px;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: #ede9e2;
  color: #6c665c;
  font-size: 10.5px;
  font-weight: 600;
}
.ws-settings-admin__avatar.is-owner { background: #7e2230; color: #f7f5f1; }
.ws-settings-admin__person-copy { min-width: 0; flex: 1; }
.ws-settings-admin__person-name {
  display: block;
  overflow: hidden;
  color: #17140f;
  font-size: 13.5px;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ws-settings-admin__person-meaning {
  display: block;
  margin-top: 2px;
  color: #6f6961;
  font-size: 12px;
  line-height: 1.35;
}
.ws-settings-admin__role {
  flex: 0 0 auto;
  padding: 3px 10px;
  border-radius: 6px;
  background: #f0ede6;
  color: #6c665c;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
}
.ws-settings-admin__role.is-owner { background: #f3eeea; color: #7e2230; }
.ws-settings-admin__role.is-reviewer { background: #e9f1ec; color: #2e6448; }
.ws-settings-admin__empty {
  padding: 18px 20px;
  color: #6f6961;
  font-size: 12.5px;
  line-height: 1.55;
}
.ws-settings-admin__new-person {
  display: grid;
  grid-template-columns: 1fr 1fr 120px auto;
  gap: 10px;
  padding: 14px 20px;
  background: #f4f1ec;
}
.ws-settings-admin__password {
  padding: 20px 22px;
  border: 1px solid #e3ded4;
  border-radius: 14px;
  background: #fffdfa;
}
.ws-settings-admin__password-form {
  display: grid;
  grid-template-columns: 1fr 1fr auto;
  gap: 10px;
  align-items: end;
}
.ws-settings-admin__field { display: flex; min-width: 0; flex-direction: column; gap: 5px; }
.ws-settings-admin__field-label { color: #6c665c; font-size: 11.5px; }
.ws-settings-admin__password .ws-settings-control,
.ws-settings-admin__password .ws-settings-button { height: 32px; }
.ws-settings-admin__warning {
  margin: 12px 0 0;
  color: #8a5a12;
  font-size: 11.5px;
  line-height: 1.45;
}

/* Manual */
.ws-settings-manual__section { margin-bottom: 30px; }
.ws-settings-manual__section:last-child { margin-bottom: 0; }
.ws-settings-manual__pipeline {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 8px;
}
.ws-settings-manual__step {
  min-width: 0;
  padding: 14px 13px;
  border: 1px solid #e3ded4;
  border-radius: 11px;
  background: #fffdfa;
}
.ws-settings-manual__step-index {
  margin-bottom: 7px;
  color: #7e2230;
  font-family: var(--gs-mono);
  font-size: 10.5px;
}
.ws-settings-manual__step-title {
  color: #17140f;
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1.3;
}
.ws-settings-manual__step-blurb {
  margin-top: 5px;
  color: #6f6961;
  font-size: 11px;
  line-height: 1.45;
}
.ws-settings-manual__contrast {
  overflow: hidden;
  border: 1px solid #e3ded4;
  border-radius: 14px;
  background: #fffdfa;
}
.ws-settings-manual__contrast-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
  padding: 14px 20px;
  border-bottom: 1px solid #f2efe9;
}
.ws-settings-manual__contrast-row:last-child { border-bottom: 0; }
.ws-settings-manual__contrast-old {
  color: #6f6961;
  font-size: 13px;
  text-decoration: line-through;
  text-decoration-color: #d8d2c7;
}
.ws-settings-manual__contrast-now { color: #17140f; font-size: 13px; }
.ws-settings-manual__glossary {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}
.ws-settings-manual__glossary-item {
  padding: 15px 17px;
  border: 1px solid #e3ded4;
  border-radius: 12px;
  background: #fffdfa;
}
.ws-settings-manual__glossary-term {
  margin-bottom: 5px;
  color: #17140f;
  font-size: 13.5px;
  font-weight: 600;
}
.ws-settings-manual__glossary-definition {
  color: #6c665c;
  font-size: 12.5px;
  line-height: 1.55;
  text-wrap: pretty;
}

@media (max-width: 900px) {
  .ws-settings-page { padding: 28px 20px 64px; }
  .ws-settings-manual__pipeline { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 620px) {
  .ws-settings-page { padding: 24px 16px 56px; }
  .ws-settings-page h1 { font-size: 28px; }
  .ws-settings-admin__new-person,
  .ws-settings-admin__password-form,
  .ws-settings-manual__glossary,
  .ws-settings-manual__contrast-row { grid-template-columns: 1fr; }
  .ws-settings-admin__new-person .ws-settings-button { width: 100%; }
  .ws-settings-manual__pipeline { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* -- Ask the record (PROBLEMS.md 3.2) -------------------------------------
 *
 * One column, one rule, no cards. The page has exactly three registers and
 * they are ranked by type, not by box: the CLAIM (largest), the RECEIPTS
 * (quiet, monospaced refs with the source sentence set as a real quote),
 * and the GAPS. The gaps deliberately sit at the same weight as the
 * receipts rather than being greyed out or collapsed - burying what an
 * answer cannot support is the failure mode this whole surface exists
 * against, so the stylesheet must not reintroduce it.
 */
.ws-schema-page--ask { max-width: 74ch; }

.ws-ask__question {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding-bottom: 22px;
  border-bottom: 1px solid var(--gs-line);
}
.ws-ask__shapes { display: flex; flex-wrap: wrap; gap: 6px; }
.ws-ask__shape {
  padding: 6px 12px;
  border: 1px solid var(--gs-line);
  border-radius: 999px;
  background: var(--gs-paper);
  color: var(--gs-muted);
  font: inherit;
  font-size: 12.5px;
  cursor: pointer;
  transition: color .12s ease, border-color .12s ease, background .12s ease;
}
.ws-ask__shape:hover { color: var(--gs-ink); border-color: #cfc8bc; }
.ws-ask__shape.is-active {
  border-color: var(--gs-burgundy);
  background: var(--gs-burgundy);
  color: #fffdfa;
}
.ws-ask__shape:focus-visible {
  outline: 2px solid var(--gs-burgundy);
  outline-offset: 2px;
}

.ws-ask__field { display: flex; align-items: baseline; gap: 10px; }
.ws-ask__field-label {
  color: var(--gs-muted);
  font-size: 12px;
  letter-spacing: .14em;
  text-transform: uppercase;
}
.ws-ask__subject {
  flex: 1;
  min-width: 0;
  padding: 4px 0;
  border: 0;
  border-bottom: 1px solid var(--gs-line);
  background: transparent;
  color: var(--gs-ink);
  font-family: var(--gs-sans);
  font-size: 21px;
  letter-spacing: -.02em;
}
/* The underline darkens on any focus; the RING stays for keyboard users.
 * A first draft here suppressed the outline and leaned on the underline
 * alone - precisely the WO-49 failure, where four local overrides
 * out-specified a good global rule and left no control with a visible
 * focus indicator. A different indicator means RESTYLING the ring, never
 * removing it, and `tests/test_accessibility.py` enforces that by scanning
 * for the declaration. Note it scans line by line and only strips a `/*`
 * that opens on the same line, so a continuation line of a block comment
 * naming that declaration also trips it. Left alone deliberately: an
 * over-eager guard shows up instantly, whereas teaching it to parse block
 * comments risks a false NEGATIVE, which is the direction that ships an
 * unusable control. */
.ws-ask__subject:focus { border-bottom-color: var(--gs-burgundy); }
.ws-ask__subject:focus-visible {
  outline: 2px solid var(--gs-burgundy);
  outline-offset: 3px;
}

.ws-ask__answer { padding-top: 26px; }
.ws-ask__idle { margin: 0; color: var(--gs-muted); font-size: 14px; max-width: 60ch; }

/* The answer itself. Nothing on the page is allowed to be larger. */
.ws-ask__claim {
  margin: 0 0 28px;
  color: var(--gs-ink);
  font-size: 25px;
  font-weight: 500;
  letter-spacing: -.024em;
  line-height: 1.32;
  text-wrap: pretty;
}
.ws-ask__claim--none { color: var(--gs-muted); font-weight: 400; }

.ws-ask__rubric {
  margin: 0 0 12px;
  color: var(--gs-muted);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
}
.ws-ask__receipts, .ws-ask__gaps, .ws-ask__fork { margin-bottom: 26px; }

.ws-ask__cites, .ws-ask__gap-list, .ws-ask__candidates {
  margin: 0;
  padding: 0;
  list-style: none;
}
.ws-ask__cite {
  padding: 10px 0 10px 16px;
  border-left: 2px solid var(--gs-soft-line);
}
.ws-ask__cite.is-evidence { border-left-color: var(--gs-burgundy); }
.ws-ask__cite-kind {
  margin-right: 8px;
  color: var(--gs-muted);
  font-size: 10.5px;
  letter-spacing: .12em;
  text-transform: uppercase;
}
.ws-ask__cite-ref {
  color: var(--gs-copy);
  font-family: var(--gs-mono);
  font-size: 12.5px;
  overflow-wrap: anywhere;
}
.ws-ask__cite-detail { margin-top: 4px; color: var(--gs-muted); font-size: 12.5px; }

/* Verbatim source text. Never truncated here - a quote edited for length
 * is no longer evidence, so it wraps rather than clipping. */
.ws-ask__quote {
  margin: 8px 0 0;
  color: var(--gs-ink);
  font-size: 15px;
  font-style: italic;
  line-height: 1.5;
  text-wrap: pretty;
}

.ws-ask__gap {
  padding: 9px 0 9px 16px;
  border-left: 2px solid var(--gs-amber);
  color: var(--gs-copy);
  font-size: 14px;
  line-height: 1.5;
}
.ws-ask__gap + .ws-ask__gap { margin-top: 4px; }

.ws-ask__candidate {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 7px 0;
  border-bottom: 1px solid var(--gs-soft-line);
}
.ws-ask__candidate-pick {
  padding: 0;
  border: 0;
  background: none;
  color: var(--gs-burgundy);
  font: inherit;
  font-size: 15px;
  text-align: left;
  cursor: pointer;
}
.ws-ask__candidate-pick:hover { text-decoration: underline; }
.ws-ask__candidate-pick:focus-visible {
  outline: 2px solid var(--gs-burgundy);
  outline-offset: 2px;
}
.ws-ask__candidate-domain {
  color: var(--gs-muted);
  font-family: var(--gs-mono);
  font-size: 11.5px;
}

@media (prefers-reduced-motion: reduce) {
  .ws-ask__shape { transition: none; }
}

/* ── Operate: Metrics (AUDIT Q375/Q376) ───────────────────────────────────
 * Tabular numerics throughout: a duration column that reflows as digits
 * change is unreadable when you are scanning it for an outlier, which is
 * the only reason anyone opens this page. */
.ws-ops-table { display: flex; flex-direction: column; }
.ws-ops-table__head,
.ws-ops-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 64px minmax(80px, 1.1fr) 74px 74px 74px;
  gap: 12px;
  align-items: center;
}
.ws-ops-table__head {
  padding: 8px 2px;
  border-bottom: 1px solid var(--gs-line);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--gs-muted);
  font-weight: 600;
}
.ws-ops-table__head > span:nth-child(2),
.ws-ops-table__head > span:nth-child(n + 4) { text-align: right; }
.ws-ops-row { padding: 11px 2px; border-bottom: 1px solid var(--gs-soft-line); }
.ws-ops-row__name {
  font-family: var(--gs-mono);
  font-size: 12.5px;
  color: var(--gs-ink);
  overflow: hidden;
  text-overflow: ellipsis;
}
.ws-ops-row__num {
  font-family: var(--gs-mono);
  font-size: 11.5px;
  text-align: right;
  color: var(--gs-copy);
  font-variant-numeric: tabular-nums;
}
.ws-ops-row__num.is-muted { color: var(--gs-muted); }
.ws-ops-bar {
  display: block;
  height: 6px;
  border-radius: 999px;
  background: var(--gs-soft-line);
  overflow: hidden;
}
.ws-ops-bar__fill { display: block; height: 100%; border-radius: 999px; background: var(--gs-green); }

.ws-ops-counters { display: flex; flex-direction: column; }
.ws-ops-counter {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 150px) 96px;
  gap: 12px;
  align-items: center;
  padding: 9px 0;
  border-top: 1px solid var(--gs-soft-line);
}
.ws-ops-counter__name {
  font-family: var(--gs-mono);
  font-size: 12px;
  color: var(--gs-ink);
  overflow: hidden;
  text-overflow: ellipsis;
}
.ws-ops-counter__labels { font-family: var(--gs-mono); font-size: 11.5px; color: var(--gs-muted); }
.ws-ops-counter__value {
  font-family: var(--gs-mono);
  font-size: 14px;
  text-align: right;
  color: var(--gs-ink);
  font-variant-numeric: tabular-nums;
}

.ws-ops-empty {
  margin: 0;
  padding: 18px 2px;
  font-size: 13px;
  color: var(--gs-muted);
  line-height: 1.6;
  max-width: 72ch;
}
.ws-ops-notes { display: flex; flex-direction: column; }
.ws-ops-note { padding: 10px 0; border-top: 1px solid var(--gs-soft-line); }
.ws-ops-note:first-child { border-top: 0; }
.ws-ops-note__title { display: block; font-size: 13px; font-weight: 500; color: var(--gs-ink); margin-bottom: 3px; }
.ws-ops-note__body { display: block; font-size: 12.5px; color: var(--gs-copy); line-height: 1.55; }

@media (max-width: 720px) {
  .ws-ops-table__head,
  .ws-ops-row { grid-template-columns: minmax(0, 1fr) 56px 64px 64px; }
  .ws-ops-table__head > span:nth-child(3),
  .ws-ops-row > .ws-ops-bar { display: none; }
}

/* ── Operate: Integrity · Continuity · Alerts (AUDIT Q092/Q093/Q367/Q374) ──
 * The three pages that answer "is the record intact", "has a backup ever
 * been restored" and "who gets told". They share the metrics page's table
 * above, and add the shapes those three need: a fact list, a callout, a
 * status chip and a stat row.
 *
 * Tone is carried by BOTH colour and a word — `is-warn` paints amber and
 * the string beside it says what is wrong. A page whose only warning is a
 * colour is a page that says nothing to a colour-blind operator, and these
 * three exist precisely to be read under pressure.
 *
 * The stat row deliberately reuses `.dc-metric`'s language (26px mono
 * numeral, hairline-separated, no card per figure) rather than four
 * rounded boxes — the standing ban on KPI furniture.
 */
.ws-schema-page--ops {
  display: flex;
  flex-direction: column;
  gap: 18px;
  max-width: 1080px;
}
.ws-schema-page--ops .ws-schema-page__head { margin-bottom: 0; }

/* -- fact list: dot · key · value · why it matters ------------------------ */
.ws-ops-facts { display: flex; flex-direction: column; }
.ws-ops-fact {
  display: grid;
  grid-template-columns: 7px minmax(0, 130px) minmax(0, 140px) minmax(0, 1fr);
  gap: 14px;
  align-items: start;
  padding: 11px 2px;
  border-top: 1px solid var(--gs-soft-line);
}
.ws-ops-fact:first-child { border-top: 0; }
.ws-ops-fact__dot {
  display: block;
  width: 7px;
  height: 7px;
  margin-top: 6px;
  border-radius: 50%;
  background: #c8c2b8;
}
.ws-ops-fact__dot.is-good { background: var(--gs-green); }
.ws-ops-fact__dot.is-warn { background: var(--gs-amber); }
.ws-ops-fact__dot.is-bad { background: var(--gs-danger); }
.ws-ops-fact__k { font-size: 12.5px; color: var(--gs-copy); }
.ws-ops-fact__v {
  font-family: var(--gs-mono);
  font-size: 12.5px;
  color: var(--gs-ink);
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}
.ws-ops-fact__note { font-size: 12px; color: var(--gs-muted); line-height: 1.5; }

/* -- callout: the state an operator must not scroll past ------------------ */
.ws-ops-callout {
  padding: 12px 14px;
  border: 1px solid var(--gs-line);
  border-left-width: 3px;
  border-radius: 8px;
  background: var(--gs-panel);
  color: var(--gs-copy);
  font-size: 12.5px;
  line-height: 1.55;
}
.ws-ops-callout.is-good {
  border-color: #c8ded0;
  border-left-color: var(--gs-green);
  background: #e9f1ec;
  color: var(--gs-green);
}
.ws-ops-callout.is-warn {
  border-color: #ecd4b6;
  border-left-color: var(--gs-amber);
  background: #fdf3e7;
  color: var(--gs-amber);
}
.ws-ops-callout.is-bad {
  border-color: #ebc9cf;
  border-left-color: var(--gs-danger);
  background: #faecee;
  color: var(--gs-danger);
}

.ws-ops-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  justify-self: start;
  min-height: 22px;
  padding: 2px 9px;
  border: 1px solid transparent;
  border-radius: 999px;
  background: #f0ede6;
  color: #6c665c;
  font-family: var(--gs-mono);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: .02em;
  line-height: 1.3;
  white-space: nowrap;
}
.ws-ops-chip.is-good { border-color: #c8ded0; background: #e9f1ec; color: var(--gs-green); }
.ws-ops-chip.is-warn { border-color: #ecd4b6; background: #fdf3e7; color: var(--gs-amber); }
.ws-ops-chip.is-bad { border-color: #ebc9cf; background: #faecee; color: var(--gs-danger); }

/* -- drill history --------------------------------------------------------- */
.ws-ops-drills { display: flex; flex-direction: column; }
.ws-ops-drill {
  display: grid;
  grid-template-columns: 7px minmax(0, 1fr);
  gap: 14px;
  align-items: start;
  padding: 12px 2px;
  border-top: 1px solid var(--gs-soft-line);
}
.ws-ops-drill:first-child { border-top: 0; }
.ws-ops-drill__body { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.ws-ops-drill__head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.ws-ops-drill__when {
  font-family: var(--gs-mono);
  font-size: 12px;
  color: var(--gs-ink);
  font-variant-numeric: tabular-nums;
}
.ws-ops-drill__detail { font-size: 12.5px; color: var(--gs-copy); line-height: 1.55; }

/* -- alert counters -------------------------------------------------------- */
.ws-ops-stats {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  border: 1px solid var(--gs-line);
  border-radius: var(--gs-radius);
  background: var(--gs-paper);
  overflow: hidden;
}
.ws-ops-stat {
  display: flex;
  flex-direction: column;
  gap: 5px;
  min-width: 0;
  padding: 16px 18px;
  border-left: 1px solid var(--gs-line);
}
.ws-ops-stat:first-child { border-left: 0; }
.ws-ops-stat__value {
  color: var(--gs-ink);
  font-family: var(--gs-mono);
  font-size: 26px;
  font-weight: 500;
  letter-spacing: -.03em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.ws-ops-stat__value.is-good { color: var(--gs-green); }
.ws-ops-stat__value.is-bad { color: var(--gs-danger); }
.ws-ops-stat__value.is-muted { color: var(--gs-muted); }
.ws-ops-stat__label {
  color: var(--gs-muted);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: .1em;
  text-transform: uppercase;
}

/* -- fineprint: the limits of what the page above just claimed ------------- */
.ws-ops-fineprint {
  margin: 0;
  max-width: 74ch;
  font-size: 12px;
  color: var(--gs-muted);
  line-height: 1.6;
}
.ws-ops-callout + .ws-ops-table,
.ws-ops-callout + .ws-ops-fineprint,
.ws-ops-table + .ws-ops-fineprint { margin-top: 14px; }

/* -- table variants: three columns, not the metrics page's six ------------- */
.ws-ops-table__head.is-anchors,
.ws-ops-row.is-anchors { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 88px; }
.ws-ops-table__head.is-anchors > span:nth-child(2) { text-align: left; }
.ws-ops-table__head.is-anchors > span:nth-child(3) { text-align: right; }
.ws-ops-row.is-anchors > .ws-ops-row__num.is-muted { text-align: left; }

.ws-ops-table__head.is-policy,
.ws-ops-row.is-policy { grid-template-columns: minmax(0, 1.2fr) 150px minmax(0, 1fr); }
.ws-ops-table__head.is-policy > span:nth-child(2) { text-align: left; }
.ws-ops-row.is-policy > .ws-ops-row__num { text-align: left; }

/* The migration table: model, role, coverage bar, vectors, and whether
   this PROCESS can embed with it - the last one is a separate fact from
   the role and gets its own column rather than a footnote. */
.ws-ops-table__head.is-models,
.ws-ops-row.is-models {
  grid-template-columns: minmax(0, 1.4fr) 92px minmax(60px, 1fr) 96px 108px;
}
.ws-ops-table__head.is-models > span:nth-child(2),
.ws-ops-table__head.is-models > span:nth-child(3) { text-align: left; }
.ws-ops-row.is-models > .ws-ops-chip { justify-self: start; }

/* A scored PAIR is two terms and two numbers. Both terms are equally the
   subject - this is a symmetric relation - so neither column is the
   "name" column and they get equal width. */
.ws-ops-table__head.is-pairs,
.ws-ops-row.is-pairs {
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 62px 62px;
}
.ws-ops-table__head.is-pairs > span:nth-child(2) { text-align: left; }

@media (max-width: 720px) {
  .ws-ops-fact { grid-template-columns: 7px minmax(0, 1fr) minmax(0, 110px); }
  .ws-ops-fact__note { grid-column: 2 / -1; }
  .ws-ops-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ws-ops-stat:nth-child(3) { border-left: 0; }
  .ws-ops-stat:nth-child(n + 3) { border-top: 1px solid var(--gs-line); }
  .ws-ops-table__head.is-anchors,
  .ws-ops-row.is-anchors { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 72px; }
  /* Policy keeps every column - which outcomes page you is the whole
     content of the row, so it wraps to a second line rather than hiding. */
  .ws-ops-table__head.is-policy,
  .ws-ops-row.is-policy { grid-template-columns: minmax(0, 1fr) 130px; }
  .ws-ops-table__head.is-policy > span:nth-child(3) { display: none; }
  .ws-ops-row.is-policy > .ws-ops-row__num { grid-column: 1 / -1; }
  /* The coverage BAR is the column that goes: it is a redrawing of the
     vectors count beside it, so dropping it loses no fact. "Client"
     stays - it is the one column with no other home on the page. */
  .ws-ops-table__head.is-models,
  .ws-ops-row.is-models { grid-template-columns: minmax(0, 1fr) 84px 92px; }
  .ws-ops-table__head.is-models > span:nth-child(3),
  .ws-ops-row.is-models > .ws-ops-bar { display: none; }
  .ws-ops-table__head.is-models > span:nth-child(4),
  .ws-ops-row.is-models > .ws-ops-row__num:not(:last-child) { display: none; }
  .ws-ops-table__head.is-pairs,
  .ws-ops-row.is-pairs { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 52px; }
  .ws-ops-table__head.is-pairs > span:nth-child(3) { display: none; }
  .ws-ops-row.is-pairs > .ws-ops-row__num.is-muted { display: none; }
}

/* ── Ingest: what this document is about (X4) ─────────────────────────────
 * The type the fields hang off. Sits above the field table because it is
 * the correction that changes what every row below it means. */
.ws-subjects-host:not(:empty) { margin-bottom: 20px; }
.ws-subject { padding: 11px 0; border-top: 1px solid var(--gs-soft-line); }
.ws-subject:first-child { border-top: 0; }
.ws-subject__head { display: flex; align-items: center; gap: 10px; }
.ws-subject__name { max-width: 260px; font-size: 13.5px; }

/* X6: the per-class domain sits beside the class name — narrower,
   because a module slug is short and a box the width of the name
   field reads as the more important of the two. */
.ws-subject__domain { max-width: 14ch; flex: 0 0 auto; }

/* X7: the parent class sits after the domain. Wider than the module slug
   because it holds a URI, and still narrower than the class name — the
   name is what the reader scans for. */
.ws-subject__parent { max-width: 24ch; flex: 0 1 auto; }
.ws-subject__conf {
  font-family: var(--gs-mono);
  font-size: 11.5px;
  color: var(--gs-muted);
  font-variant-numeric: tabular-nums;
}
.ws-subject__home {
  padding: 2px 8px;
  border-radius: 999px;
  background: #efece5;
  font-size: 10.5px;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--gs-copy);
}
.ws-subject__desc { margin-top: 4px; font-size: 12.5px; color: var(--gs-copy); }

/* ── Read a document: the upload row ──────────────────────────────────────
 * It reuses `.ws-ingest-setup` - the same three equal cells at the same
 * height Ingest uses. The bespoke layout that stood here put a 116px drop
 * zone beside two 32px inputs and left half the card empty; two screens
 * asking the same question (what is this file, and what is it for) had no
 * business having two proportions. */
.ws-readdoc__wanted {
  display: block;
  margin-top: 5px;
  font-family: var(--gs-mono);
  font-size: 11.5px;
  color: var(--gs-ink);
  line-height: 1.5;
}
.ws-readdoc__actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
}
.ws-readdoc__actions > .ws-ops-fineprint { margin: 0; }
.ws-readdoc__actions > button { flex: 0 0 auto; }

@media (max-width: 720px) {
  .ws-readdoc__actions { flex-direction: column; align-items: stretch; gap: 12px; }
}

/* ── Releases: the integrity verdict ──────────────────────────────────────
 * MISMATCH is the most serious finding this product can make about a
 * shipped artifact and it used to render in the same grey as everything
 * else on the line. Three states, three weights. */
.dc-release__verdict {
  font-family: var(--gs-mono);
  font-size: 11.5px;
  text-transform: uppercase;
  letter-spacing: .05em;
}
.dc-release__verdict abbr { text-decoration: none; border-bottom: 1px dotted currentColor; }
.dc-release__verdict.is-good { color: var(--gs-green); }
.dc-release__verdict.is-warn { color: var(--gs-amber); }
.dc-release__verdict.is-bad { color: var(--gs-danger); font-weight: 600; }
.dc-release__hashes {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin-top: 6px;
  font-family: var(--gs-mono);
  font-size: 11px;
}

/* ── Decisions: the exportable audit record ───────────────────────────────
 * Two groups, side by side at width: what governs the term, and what was
 * refused. Kept apart because "no governing proposal" and "no rejected
 * attempt" are different findings and a merged list hides which is which. */
.ws-audit__host { margin-top: 22px; border-top: 1px solid var(--gs-line); padding-top: 16px; }
.ws-audit__out:not(:empty) { margin-top: 14px; }
.ws-audit { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px; }
.ws-audit > .ws-ops-callout { grid-column: 1 / -1; }
.ws-audit__group-title {
  margin: 0 0 8px;
  font-size: 11px;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--gs-muted);
  font-weight: 600;
}
.ws-audit__entry {
  display: grid;
  grid-template-columns: 74px minmax(0, 1fr);
  gap: 10px;
  padding: 9px 0;
  border-top: 1px solid var(--gs-soft-line);
}
.ws-audit__state {
  font-family: var(--gs-mono);
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: .05em;
}
.ws-audit__state.is-good { color: var(--gs-green); }
.ws-audit__state.is-bad { color: var(--gs-danger); }
.ws-audit__body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.ws-audit__title { font-size: 13px; color: var(--gs-ink); font-weight: 500; }
.ws-audit__meta { font-family: var(--gs-mono); font-size: 11px; color: var(--gs-muted); }
.ws-audit__why { font-size: 12.5px; color: var(--gs-copy); line-height: 1.5; }
.ws-audit__matched, .ws-audit__precedents { font-size: 11.5px; color: var(--gs-muted); }

@media (max-width: 900px) {
  .ws-audit { grid-template-columns: minmax(0, 1fr); }
}

/* ── Duplicates: the counterfactual (PROBLEMS 4.10) ───────────────────────
 * The two candidates side by side with what each MEANS and how much
 * depends on it. Side by side rather than stacked: the reviewer is
 * comparing, and a vertical list reads as a ranking. */
.ws-review-duplicate__sides {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 18px;
  margin: 10px 0 12px;
}
.ws-review-duplicate__side-term { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.ws-review-duplicate__side-label { font-size: 13px; font-weight: 500; color: var(--gs-ink); }
.ws-review-duplicate__side-uri {
  font-family: var(--gs-mono);
  font-size: 11px;
  color: var(--gs-muted);
  overflow: hidden;
  text-overflow: ellipsis;
}
.ws-review-duplicate__side-def { font-size: 12.5px; color: var(--gs-copy); line-height: 1.5; }
.ws-review-duplicate__side-uses { font-size: 11.5px; color: var(--gs-ink); font-variant-numeric: tabular-nums; }
/* Nothing referencing a term is the cheap side of a merge, and the
   reviewer should see which side that is at a glance. */
.ws-review-duplicate__side-uses.is-unused { color: var(--gs-muted); }

/* ── 4.10b: the recommendation, and why it leans ──────────────────────────
   Sits in the action column directly above the two buttons, because it is
   about which button to press. Deliberately quiet -- a rule, small caps
   and body copy, no filled panel: it is advice, not a verdict, and the
   card already spends its red on the evidence rows. */
.ws-review-duplicate__advice {
  display: flex;
  flex-direction: column;
  gap: 3px;
  margin-top: 12px;
  padding-top: 9px;
  border-top: 1px solid var(--gs-soft-line);
}
.ws-review-duplicate__advice-head {
  font-size: 10.5px;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--gs-muted);
}
.ws-review-duplicate__advice-body {
  font-size: 11.5px;
  line-height: 1.55;
  color: var(--gs-copy);
}
/* The held case is the one that changed behaviour, so it is the one that
   gets the accent. Burgundy, the product's own emphasis -- not a warning
   colour, because holding is the safe outcome, not an alarm. */
.ws-review-duplicate__advice.is-hold .ws-review-duplicate__advice-head {
  color: var(--gs-burgundy);
}

/* ── 4.4b: drift between two runs of one source ───────────────────────────
   A band inside the extraction card, above the field rows, because that
   is the list it is a warning about. Amber, not red: a second run reading
   differently is a fact to check, not a failure — and it is hidden
   entirely unless there is something to say. */
.ws-ingest-drift {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin: 0 0 2px;
  padding: 11px 14px;
  border: 1px solid var(--gs-line);
  border-left: 2px solid var(--gs-amber);
  border-radius: 0 var(--gs-radius) var(--gs-radius) 0;
  background: var(--gs-panel);
}
.ws-ingest-drift[hidden] { display: none; }
.ws-ingest-drift__head {
  font-size: 10.5px;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--gs-amber);
}
.ws-ingest-drift__body { font-size: 12.5px; color: var(--gs-copy); line-height: 1.5; }
/* The dropped fields get their own line. A field the first run read and
   the second did not is the only thing here that can quietly shrink a
   schema, so it is never left to be counted out of a summary. */
.ws-ingest-drift__lost { font-size: 12px; color: var(--gs-ink); line-height: 1.5; }
.ws-ingest-drift__caveat { font-size: 11.5px; color: var(--gs-muted); line-height: 1.5; }

@media (max-width: 720px) {
  .ws-review-duplicate__sides { grid-template-columns: minmax(0, 1fr); gap: 14px; }
}

/* ── Author: the reuse check and the compiled preview ─────────────────────
 * The reuse hits sit INSIDE the form, above the create button, because
 * that is the moment the decision is made -- a panel in the sidebar is a
 * panel somebody mints past. */
.dc-reuse:not(:empty) { margin-top: 16px; }
.dc-reuse__hits { display: flex; flex-direction: column; gap: 6px; }
.dc-reuse__hit {
  display: grid;
  grid-template-columns: 52px minmax(0, 1.1fr) 96px minmax(0, 1.4fr);
  gap: 10px;
  align-items: baseline;
  padding: 7px 0;
  border-top: 1px solid var(--gs-soft-line);
  font-size: 12.5px;
}
.dc-reuse__score { font-family: var(--gs-mono); font-variant-numeric: tabular-nums; color: var(--gs-ink); }
.dc-reuse__label { color: var(--gs-ink); font-weight: 500; }
.dc-reuse__where { font-size: 11.5px; color: var(--gs-muted); text-transform: uppercase; letter-spacing: .05em; }
.dc-reuse__uri {
  font-family: var(--gs-mono);
  font-size: 11.5px;
  color: var(--gs-muted);
  overflow: hidden;
  text-overflow: ellipsis;
}
.dc-preview__problems:not(:empty) { margin: 12px 0; }

@media (max-width: 720px) {
  .dc-reuse__hit { grid-template-columns: 46px minmax(0, 1fr); }
  .dc-reuse__where, .dc-reuse__uri { grid-column: 2; }
}

/* ── Build: Maintain (the update and deletion agents) ─────────────────────
 * Two agent runs on one page. The controls sit inline because each is a
 * one-line question -- which module, which term -- and a stacked form
 * would imply more ceremony than either deserves. */
.ws-maintain__budget { width: 92px; }
.ws-maintain__term { max-width: 420px; }
.ws-maintain__ids {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  margin-top: 14px;
}
.ws-maintain__ids > .ws-ops-fineprint { width: 100%; margin: 0 0 2px; }
.ws-maintain__nested { margin-top: 14px; box-shadow: none; }

@media (max-width: 720px) {
  .ws-maintain__term { max-width: 100%; }
  .ws-maintain__budget { width: 100%; }
}

/* ── Operate: Embedding model (F-5, the migration surface) ────────────────
 * The two comparison columns sit SIDE BY SIDE and are never stacked into
 * one ranked list. What the reader has to do here is weigh an expensive
 * mistake against a recoverable one, and a layout that put one above the
 * other would read as an order of importance the data does not carry. */
.ws-embed__actions { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
/* `.dc-input` is width:100%, so a select in this row ate the whole line
   and pushed its own button onto the next one - which is why the module
   picker and the term picker were different widths on the same card.
   Bounded here rather than per control, so a row added later lines up
   without anyone remembering to say so. */
/* `.sel` too: once selects.js enhances a control the WRAPPER is the flex
   child and the select is hidden inside it, so a rule naming only
   `select` sizes something invisible. */
.ws-embed__actions > select,
.ws-embed__actions > .sel,
.ws-embed__actions > input { flex: 0 1 auto; width: auto; min-width: 180px; max-width: 340px; }
.ws-embed__actions > input[type="number"] { min-width: 0; width: 84px; }
.ws-embed__actions + .ws-ops-fineprint { margin-top: 12px; }
.ws-embed__job:not(:empty) { margin-top: 16px; }
.ws-embed__job-state { font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; font-size: 11px; }
.ws-embed__columns {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 22px;
  margin-top: 18px;
}
.ws-embed__column-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  padding-bottom: 8px;
  border-bottom: 2px solid var(--gs-line);
  margin-bottom: 10px;
}
.ws-embed__column-head.is-bad { border-bottom-color: var(--gs-danger); }
.ws-embed__column-head.is-warn { border-bottom-color: var(--gs-amber); }
.ws-embed__column-title { font-size: 12.5px; font-weight: 600; color: var(--gs-ink); line-height: 1.35; }
.ws-embed__column-count {
  font-family: var(--gs-mono);
  font-size: 17px;
  font-variant-numeric: tabular-nums;
  color: var(--gs-ink);
}
.ws-embed__rollback { margin-top: 16px; border-top: 1px solid var(--gs-soft-line); padding-top: 14px; }
.ws-embed__rollback-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  padding: 9px 0;
  border-top: 1px solid var(--gs-soft-line);
}
.ws-embed__rollback-row:first-of-type { border-top: 0; }
.ws-embed__rollback-row > .ws-ops-row__name { flex: 1 1 180px; text-align: left; }

@media (max-width: 720px) {
  .ws-embed__columns { grid-template-columns: minmax(0, 1fr); gap: 26px; }
}

/* ── Knowledge: Facts · Ask the graph (roadmap M4.5/M4.6) ─────────────────
 * The question form sits above its own answer, so it stays a control rather
 * than becoming a header: a reader who has just been told "3 of 12" is
 * usually about to ask the next question. */
.ws-ask__form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}
.ws-ask__form .dc-input { min-height: 34px; }
.ws-ask__form select.dc-input { flex: 0 0 auto; min-width: 190px; }
.ws-ask__form input.dc-input { flex: 1 1 260px; min-width: 0; }

@media (max-width: 720px) {
  .ws-ask__form select.dc-input,
  .ws-ask__form input.dc-input { flex: 1 1 100%; }
}

/* The chosen verdict in a spot audit. A pressed state is not decoration
 * here: an auditor working through thirty rows needs to see which ones they
 * have already judged, or they judge some twice and miss others. */
.dc-button.is-chosen {
  border-color: var(--gs-ink);
  background: var(--gs-ink);
  color: var(--gs-paper);
}


/* -- P007 discussion threads ---------------------------------------------
   One component, three subject kinds (a proposal, a duplicate pair, a
   term). Collapsed by default everywhere except a proposal that already
   has comments: an argument nobody has started should not push the
   decision it belongs to off the screen. */
.ws-thread {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid #ede9e2;
}
.ws-thread__toggle {
  display: inline-flex;
  gap: 8px;
  align-items: center;
  padding: 5px 10px;
  border: 1px solid #ddd7cc;
  border-radius: 999px;
  background: #fffdfa;
  color: #3a342c;
  font: inherit;
  font-size: 11.5px;
  cursor: pointer;
}
.ws-thread__toggle:hover { background: #f4f1ec; }
.ws-thread__count {
  min-width: 17px;
  padding: 0 5px;
  border-radius: 999px;
  background: #7e2230;
  color: #fffdfa;
  font-size: 10px;
  font-weight: 600;
  line-height: 17px;
  text-align: center;
}
.ws-thread__body { display: none; margin-top: 12px; }
.ws-thread.is-open .ws-thread__body { display: block; }
.ws-thread__note,
.ws-thread__owners {
  margin: 0 0 10px;
  color: #6f6961;
  font-size: 11.5px;
  line-height: 1.55;
}
.ws-thread__owners { color: #3a342c; }
.ws-thread__owners--none { font-style: italic; }
/* An agreement is the artifact this whole feature exists to produce, so
   it is the one thing in the panel that is not grey. */
.ws-thread__resolved {
  margin: 0 0 12px;
  padding: 9px 11px;
  border-left: 2px solid #2f6b46;
  border-radius: 0 7px 7px 0;
  background: #f2f7f3;
  color: #275c3b;
  font-size: 12px;
  line-height: 1.55;
}
.ws-thread__entries {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-bottom: 14px;
}
.ws-thread__entry {
  padding-left: 10px;
  border-left: 2px solid #e3ded4;
}
/* Withdrawn, not gone: dimmed and struck, never hidden. The record of
   somebody changing their mind is part of the argument. */
.ws-thread__entry.is-retracted { opacity: .6; }
.ws-thread__entry.is-retracted .ws-thread__text { text-decoration: line-through; }
.ws-thread__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: baseline;
  margin-bottom: 3px;
}
.ws-thread__author { color: #17140f; font-size: 12px; font-weight: 600; }
.ws-thread__kind {
  padding: 1px 6px;
  border-radius: 4px;
  background: #efece5;
  color: #6f6961;
  font-size: 9.5px;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.ws-thread__when { color: #9a938a; font-family: var(--gs-mono); font-size: 10.5px; }
.ws-thread__text { margin: 0; color: #3a342c; font-size: 12.5px; line-height: 1.6; }
.ws-thread__retraction { margin: 4px 0 0; color: #9a938a; font-size: 10.5px; font-style: italic; }
.ws-thread__retract {
  margin-top: 4px;
  padding: 0;
  border: 0;
  background: none;
  color: #9a938a;
  font: inherit;
  font-size: 10.5px;
  text-decoration: underline;
  cursor: pointer;
}
.ws-thread__retract:hover { color: #7e2230; }
.ws-thread__input {
  display: block;
  width: 100%;
  padding: 9px 11px;
  border: 1px solid #ddd7cc;
  border-radius: 9px;
  background: #fffdfa;
  color: #17140f;
  font: inherit;
  font-size: 12.5px;
  line-height: 1.55;
  resize: vertical;
}
.ws-thread__actions { display: flex; gap: 8px; margin-top: 8px; }
.ws-thread__button {
  padding: 6px 12px;
  border: 1px solid #ddd7cc;
  border-radius: 8px;
  background: #fffdfa;
  color: #3a342c;
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}
.ws-thread__button:hover { background: #f4f1ec; }
.ws-thread__button--primary {
  border-color: #7e2230;
  background: #7e2230;
  color: #fffdfa;
}
.ws-thread__button--primary:hover { background: #6b1c28; }

/* -- P008 decisions, grouped by term --------------------------------------
   The term list is the index; the timeline is the answer. Two columns
   rather than a master-detail route change, because the question being
   answered ("why is this term like this?") is usually asked about several
   terms in a row. */
.ws-decisions {
  display: grid;
  grid-template-columns: minmax(0, 280px) minmax(0, 1fr);
  gap: 22px;
  align-items: start;
}
.ws-decisions__terms {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid #e3ded4;
  border-radius: 14px;
  background: #fffdfa;
}
.ws-decisions__term {
  padding: 11px 14px;
  border-bottom: 1px solid #ede9e2;
  border-left: 2px solid transparent;
  color: inherit;
  text-decoration: none;
}
.ws-decisions__term:last-of-type { border-bottom: 0; }
.ws-decisions__term:hover { background: #f5f2ec; }
.ws-decisions__term.is-selected { border-left-color: #7e2230; background: #f5f2ec; }
.ws-decisions__term-label {
  display: block;
  color: #17140f;
  font-size: 13px;
  font-weight: 500;
}
.ws-decisions__term-meta,
.ws-decisions__term-people {
  display: block;
  overflow: hidden;
  margin-top: 2px;
  color: #6f6961;
  font-size: 10.5px;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ws-decisions__term-people { color: #9a938a; font-family: var(--gs-mono); }
.ws-decisions__timeline {
  padding: 18px 20px;
  border: 1px solid #e3ded4;
  border-radius: 14px;
  background: #fffdfa;
}
.ws-decisions__filters { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 14px; }
.ws-decisions__person {
  padding: 4px 10px;
  border: 1px solid #ddd7cc;
  border-radius: 999px;
  color: #6f6961;
  font-size: 11px;
  text-decoration: none;
}
.ws-decisions__person:hover { background: #f4f1ec; }
.ws-decisions__person.is-active { border-color: #7e2230; background: #7e2230; color: #fffdfa; }
/* A filtered view that does not say so is a partial record wearing the
   clothes of a whole one. */
.ws-decisions__hidden {
  margin: 0 0 14px;
  padding: 8px 10px;
  border-left: 2px solid #b07d2a;
  border-radius: 0 7px 7px 0;
  background: #fdf7ec;
  color: #7d5a1c;
  font-size: 11.5px;
}
.ws-decisions__events { margin: 0; padding: 0; list-style: none; }
.ws-decisions__event {
  position: relative;
  padding: 0 0 16px 18px;
  border-left: 1px solid #e3ded4;
}
.ws-decisions__event:last-child { border-left-color: transparent; padding-bottom: 0; }
.ws-decisions__event::before {
  position: absolute;
  top: 5px;
  left: -4px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #c9c2b6;
  content: "";
}
/* Colour carries meaning, not decoration: a verdict is the moment the
   schema changed, a refusal is the moment it did not. */
.ws-decisions__event.is-decided::before { background: #7e2230; }
.ws-decisions__event.is-refused::before { background: #b07d2a; }
.ws-decisions__event.is-resolved::before { background: #2f6b46; }
.ws-decisions__event-head {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: baseline;
}
.ws-decisions__event-kind {
  padding: 1px 6px;
  border-radius: 4px;
  background: #efece5;
  color: #6f6961;
  font-size: 9.5px;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.ws-decisions__event-who { color: #17140f; font-size: 12px; font-weight: 600; }
.ws-decisions__event-when { color: #9a938a; font-family: var(--gs-mono); font-size: 10.5px; }
.ws-decisions__event-what { margin-top: 3px; color: #3a342c; font-size: 12.5px; }
.ws-decisions__event-why {
  margin: 4px 0 0;
  color: #6f6961;
  font-size: 12px;
  line-height: 1.6;
}
.ws-decisions__event-caveat,
.ws-decisions__caveat {
  margin: 4px 0 0;
  color: #9a938a;
  font-size: 10.5px;
  font-style: italic;
}
.ws-decisions__note,
.ws-decisions__empty {
  margin: 0;
  color: #6f6961;
  font-size: 12px;
  line-height: 1.6;
}
@media (max-width: 900px) {
  .ws-decisions { grid-template-columns: minmax(0, 1fr); }
}

/* -- sign-in gate + identity chip (auth.js) --------------------------------
   `auth.js` shipped in the initial commit, is cited by three other modules
   as the reference implementation, and had neither a <script> tag nor a
   single CSS rule anywhere in this tree. With `--auth` that meant no gate
   was ever built: every /api call 401'd and the workspace rendered "0 types
   across 0 domains", which is a confident wrong answer where "sign in" was
   the true one.

   The class contract below is auth.js's, read off the source rather than
   invented: gate/panel/brand/title/lede/input/error/submit/foot, plus
   chip/avatar/who/name/role/signout, and the `.open` / `.done` states its
   own comments describe. */
.auth-gate {
  position: fixed;
  z-index: 1200;
  display: grid;
  place-items: center;
  inset: 0;
  padding: 24px;
  background: #17140f;
  opacity: 0;
  transition: opacity .32s ease;
}
/* auth.js flushes layout then adds `.open`; it must never depend on a frame
   being produced, so this is the only thing that reveals the gate. */
.auth-gate.open { opacity: 1; }
.auth-gate.done { opacity: 0; pointer-events: none; }
.auth-panel {
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: min(380px, 100%);
  padding: 34px 32px;
  border: 1px solid #332d25;
  border-radius: 18px;
  background: #fffdfa;
}
.auth-brand {
  color: #7e2230;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: -.01em;
}
.auth-title {
  margin: 0;
  color: #17140f;
  font-size: 25px;
  font-weight: 600;
  letter-spacing: -.02em;
}
.auth-lede {
  margin: 0 0 6px;
  color: #6f6961;
  font-size: 12.5px;
  line-height: 1.6;
}
.auth-input {
  width: 100%;
  padding: 11px 13px;
  border: 1px solid #ddd7cc;
  border-radius: 10px;
  background: #fffdfa;
  color: #17140f;
  font: inherit;
  font-size: 13.5px;
}
.auth-input:focus-visible {
  border-color: #7e2230;
  outline: 2px solid rgba(126, 34, 48, .28);
  outline-offset: 1px;
}
.auth-error {
  padding: 8px 11px;
  border-left: 2px solid #7e2230;
  border-radius: 0 7px 7px 0;
  background: #fbf3f2;
  color: #7e2230;
  font-size: 12px;
}
.auth-submit {
  padding: 11px 14px;
  border: 0;
  border-radius: 10px;
  background: #7e2230;
  color: #fffdfa;
  font: inherit;
  font-size: 13.5px;
  font-weight: 500;
  cursor: pointer;
}
.auth-submit:hover { background: #6b1c28; }
.auth-submit:disabled { background: #a8757e; cursor: default; }
.auth-foot {
  margin: 4px 0 0;
  color: #9a938a;
  font-size: 11px;
  line-height: 1.55;
}

/* The chip lives in the workspace header. Without these rules the three
   spans ran together as "Aadminowner". */
.auth-chip {
  display: flex;
  gap: 8px;
  align-items: center;
  margin-left: 10px;
  padding: 4px 6px 4px 4px;
  border: 1px solid #e3ded4;
  border-radius: 999px;
  background: #fffdfa;
}
.auth-avatar {
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: #7e2230;
  color: #fffdfa;
  font-size: 11px;
  font-weight: 600;
}
.auth-who { display: flex; flex-direction: column; line-height: 1.15; }
.auth-name { color: #17140f; font-size: 11.5px; font-weight: 600; }
.auth-role {
  color: #9a938a;
  font-size: 9.5px;
  letter-spacing: .06em;
  text-transform: uppercase;
}
.auth-signout {
  padding: 2px 5px;
  border: 0;
  border-radius: 6px;
  background: none;
  color: #9a938a;
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}
.auth-signout:hover { background: #f4f1ec; color: #7e2230; }
@media (max-width: 1200px) { .auth-who { display: none; } }

/* `shell.js` already puts a `gs-avatar` in the header — initials read from
   /api/me, and a shortcut to #/admin. auth.js's chip is a superset of it
   (initials, name, ROLE, and a sign-out button `gs-avatar` has no way to
   offer), so loading auth.js put two identity indicators side by side.
   Scoped with :has() rather than unconditionally, because in open mode
   there is no chip and `gs-avatar` is the only one there is. */
body:has(.auth-chip) .gs-avatar { display: none; }

/* -- P009: the graph's two colour channels --------------------------------
   Fill says where a term came from (stable); outline says what state it is
   in (changes). The legend is two labelled rows for that reason - one flat
   row of six swatches was what made the old single scale unreadable,
   because nothing said the entries answered different questions. */
.ws-schema-graph__legend {
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding: 12px 16px;
  border-top: 1px solid #ede9e2;
}
.ws-schema-graph__legend-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 14px;
  align-items: center;
}
.ws-schema-graph__legend-title {
  min-width: 152px;
  color: #9a938a;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
}
.ws-schema-graph__legend-item {
  display: inline-flex;
  gap: 6px;
  align-items: center;
  color: #3a342c;
  font-size: 11px;
}
.ws-schema-graph__legend-swatch {
  display: inline-block;
  width: 11px;
  height: 11px;
  border: 1.5px solid #9a938a;
  border-radius: 50%;
  background: #efece5;
}
/* The lifecycle row shows PATTERN, so its swatches carry no fill - a
   filled one would imply an origin the row is not talking about. */
.ws-schema-graph__legend-swatch.is-outline { background: transparent; border-color: #3a342c; }
.ws-schema-graph__legend-line {
  display: inline-block;
  width: 20px;
  height: 0;
  border-top: 1.6px solid #9a938a;
}
.ws-schema-graph__legend-line.is-object { border-color: #7e2230; }
.ws-schema-graph__legend-line.is-subclass { border-top-style: dashed; }
.ws-schema-graph__legend-line.is-replaced { border-color: #8a6a22; border-top-style: dotted; }

/* Both channels in words, in the inspector. Colour is a shortcut, never
   the only carrier. */
.ws-schema-graph__channels { display: flex; flex-wrap: wrap; gap: 6px; margin: 6px 0 10px; }
.ws-schema-graph__channel {
  padding: 2px 8px;
  border: 1px solid #e3ded4;
  border-radius: 999px;
  background: #fffdfa;
  color: #6f6961;
  font-size: 10.5px;
}

/* -- P012 relationship suggestions (skema.relate) ---------------------------
   Advisory by construction, and styled to read that way: nothing here is a
   verdict, so nothing here uses the wine that marks one. */
.ws-suggest { margin-top: 16px; padding-top: 14px; border-top: 1px solid #ede9e2; }
.ws-suggest__note { margin: 6px 0 10px; color: #6f6961; font-size: 11px; line-height: 1.55; }
.ws-suggest__button {
  padding: 6px 12px;
  border: 1px solid #ddd7cc;
  border-radius: 8px;
  background: #fffdfa;
  color: #3a342c;
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}
.ws-suggest__button:hover { background: #f4f1ec; }
.ws-suggest__button:disabled { color: #9a938a; cursor: default; }
.ws-suggest__list { display: flex; flex-direction: column; gap: 9px; margin-bottom: 10px; }
.ws-suggest__row { padding-left: 9px; border-left: 2px solid #c9c2b6; }
.ws-suggest__head { display: flex; flex-wrap: wrap; gap: 7px; align-items: baseline; }
.ws-suggest__relation {
  padding: 1px 6px;
  border-radius: 4px;
  background: #efece5;
  color: #6f6961;
  font-family: var(--gs-mono);
  font-size: 9.5px;
}
.ws-suggest__target { color: #17140f; font-size: 12.5px; font-weight: 600; }
.ws-suggest__why { margin: 3px 0 0; color: #6f6961; font-size: 11.5px; line-height: 1.55; }
/* A refusal is information, so it is legible rather than hidden - just
   plainly weaker than a suggestion that survived. */
.ws-suggest__refused {
  padding-left: 9px;
  border-left: 2px solid #ede9e2;
  color: #9a938a;
  font-size: 11px;
  line-height: 1.5;
}

/* -- P011: the front door -------------------------------------------------
   The first control on the screen you land on is a question. It routes to
   the Ask surface rather than answering inline: an answer here would have
   to be a summary, and a summarised answer is the one thing this product
   refuses to produce. */

/* -- PROBLEMS 3.2b: asking in your own words ------------------------------
   The chips remain the authoritative control; this only sets them. What it
   says it understood is shown BEFORE the answer, so a wrong reading is
   caught by the asker rather than absorbed as fact. */
.ws-ask__field--sentence { margin-bottom: 10px; }
.ws-ask__sentence {
  width: 100%;
  padding: 10px 13px;
  border: 1px solid #ddd7cc;
  border-radius: 10px;
  background: #fffdfa;
  color: #17140f;
  font: inherit;
  font-size: 13.5px;
}
.ws-ask__sentence:focus-visible {
  border-color: #7e2230;
  outline: 2px solid rgba(126, 34, 48, .26);
  outline-offset: 1px;
}
.ws-ask__routed {
  margin: 0 0 12px;
  padding: 8px 11px;
  border-left: 2px solid #c9c2b6;
  border-radius: 0 7px 7px 0;
  background: #f7f4ee;
  color: #6f6961;
  font-size: 11.5px;
  line-height: 1.55;
}
/* A refusal is not a failure - it is the record saying which questions it
   answers. Styled to be read, with the askable set beside it. */
.ws-ask__routed.is-refused { border-left-color: #b07d2a; background: #fdf7ec; color: #7d5a1c; }
.ws-ask__routed-alts { display: block; margin-top: 5px; color: #9a938a; font-size: 11px; }

/* =====================================================================
 * SCHEMA CANVAS
 *
 * Two channels, unchanged in meaning from the previous canvas: FILL says
 * where a term came from, OUTLINE says what state it is in. What is new
 * is that the RELATIONSHIP is drawn as an object rather than implied by
 * a line - so the styling here is mostly about making a labelled,
 * directed, selectable edge readable over a working canvas.
 *
 * Colours are the existing Skema tokens. Nothing here introduces a
 * palette; the graph is a document, not a dashboard.
 * ===================================================================== */

.sg-canvas {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 330px;
  gap: 20px;
  align-items: start;
}
.sg-stage {
  position: relative;
  overflow: hidden;
  border: 1px solid var(--gs-line);
  border-radius: var(--gs-radius);
  background: var(--gs-paper);
}
.sg-plot {
  position: relative;
  height: min(74vh, 780px);
  container-type: inline-size;
}
.sg-svg {
  display: block;
  width: 100%;
  height: 100%;
  cursor: default;
  touch-action: none;
}
.sg-svg.is-panning { cursor: grabbing; }
.sg-grid { pointer-events: none; }

/* -- bands (Layers mode) ---------------------------------------------- */
.sg-band { pointer-events: none; }
.sg-band__label {
  fill: #8d857a;
  font-family: var(--gs-mono);
  font-size: 10px;
  letter-spacing: .12em;
  text-transform: uppercase;
  pointer-events: none;
}

/* -- edges -------------------------------------------------------------
 * The hit path is 14 units wide and invisible: a 1.4-unit line is not a
 * click target, and an edge nobody can hit is not really selectable. */
.sg-edge__hit {
  fill: none;
  stroke: transparent;
  stroke-width: 14;
  cursor: pointer;
}
.sg-edge__line {
  fill: none;
  stroke: #a79e92;
  stroke-width: 1.4;
  transition: stroke .12s ease, stroke-width .12s ease, opacity .12s ease;
}
.sg-edge--subclass .sg-edge__line { stroke: #9a9288; stroke-dasharray: 6 4; }
.sg-edge--object .sg-edge__line { stroke: #a8697a; }
.sg-edge--replaced .sg-edge__line { stroke: #b08b3a; stroke-dasharray: 2 4; }
.sg-edge.is-deprecated .sg-edge__line { stroke-opacity: .45; stroke-dasharray: 2 3; }
.sg-edge.is-added .sg-edge__line { stroke: var(--gs-burgundy); stroke-width: 2; }

.sg-edge:hover .sg-edge__line,
.sg-edge.is-selected .sg-edge__line,
.sg-edge.is-traced .sg-edge__line {
  stroke: var(--gs-ink);
  stroke-width: 2.2;
  stroke-opacity: 1;
}
.sg-edge.is-outgoing .sg-edge__line { stroke: var(--gs-burgundy); stroke-width: 2; stroke-opacity: 1; }
.sg-edge.is-incoming .sg-edge__line { stroke: #38536f; stroke-width: 2; stroke-opacity: 1; }
.sg-edge.is-dim .sg-edge__line { stroke-opacity: .16; }
/* A visible focus ring, not a replaced one. `outline: none` plus a
   thicker stroke reads fine to me and not at all to somebody navigating
   by keyboard on a low-contrast screen - which is why the accessibility
   gate refuses it outright. The outline is restyled and the stroke
   emphasis is kept on top. */
.sg-edge:focus-visible { outline: 2px solid rgb(126 34 48 / .6); outline-offset: 3px; }
.sg-edge:focus-visible .sg-edge__line { stroke: var(--gs-ink); stroke-width: 2.4; }

/* -- edge labels: the point of the whole rebuild ----------------------- */
.sg-edge__label { cursor: pointer; transition: opacity .12s ease; }
.sg-edge__label-bg {
  fill: var(--gs-paper);
  stroke: var(--gs-soft-line);
  stroke-width: 1;
}
.sg-edge__label-text {
  fill: #5d564d;
  font-family: var(--gs-mono);
  font-size: 10.5px;
  letter-spacing: .01em;
  pointer-events: none;
}
.sg-edge__label:hover .sg-edge__label-bg,
.sg-edge__label.is-selected .sg-edge__label-bg { stroke: var(--gs-ink); }
.sg-edge__label.is-live .sg-edge__label-bg { stroke: var(--gs-burgundy); }
.sg-edge__label.is-selected .sg-edge__label-text,
.sg-edge__label.is-live .sg-edge__label-text { fill: var(--gs-ink); }
.sg-edge__label.is-dim { opacity: .18; }

/* -- nodes -------------------------------------------------------------- */
.sg-node { cursor: grab; transition: opacity .14s ease; }
.sg-node.is-dragging { cursor: grabbing; }
.sg-node__body { transition: stroke-width .12s ease, filter .12s ease; }
.sg-node__label {
  fill: var(--gs-ink);
  font-family: var(--gs-sans);
  font-size: 12.5px;
  font-weight: 500;
  pointer-events: none;
}
.sg-node__meta {
  fill: #8d857a;
  font-family: var(--gs-mono);
  font-size: 9.5px;
  letter-spacing: .04em;
  pointer-events: none;
}
.sg-node__attr {
  fill: #6f6961;
  font-family: var(--gs-mono);
  font-size: 9.5px;
  pointer-events: none;
}
.sg-node__attr--more { fill: #a09889; }
.sg-node:hover .sg-node__body { stroke-width: 2.6; }
.sg-node.is-selected .sg-node__body {
  stroke: var(--gs-ink);
  stroke-width: 3;
}
.sg-node.is-selected .sg-node__label { font-weight: 700; }
.sg-node.is-neighbour .sg-node__body { stroke-width: 2.4; }
.sg-node.is-traced .sg-node__body { stroke: var(--gs-burgundy); stroke-width: 3; }
.sg-node.is-dim { opacity: .2; }
.sg-node:focus-visible { outline: 2px solid rgb(126 34 48 / .6); outline-offset: 3px; }
.sg-node:focus-visible .sg-node__body {
  stroke: var(--gs-ink);
  stroke-width: 3;
}

/* -- collapse badge ----------------------------------------------------- */
.sg-node__collapse { cursor: pointer; }
.sg-node__collapse-dot {
  fill: var(--gs-paper);
  stroke: #b6ab9c;
  stroke-width: 1.2;
}
.sg-node__collapse:hover .sg-node__collapse-dot { stroke: var(--gs-ink); }
.sg-node__collapse-mark {
  fill: #5d564d;
  font-family: var(--gs-mono);
  font-size: 9px;
  pointer-events: none;
}

/* -- semantic zoom -----------------------------------------------------
 * Zoom changes WHAT is drawn, not only how big it is. Attribute names
 * exist in the DOM from the first paint and are revealed by class, so a
 * wheel tick costs a class flip rather than a re-layout. */
.sg-node__attrs { opacity: 0; transition: opacity .14s ease; pointer-events: none; }
.sg-svg[data-zoom="detail"] .sg-node__attrs { opacity: 1; }
/* Not inside a focus, though: the inspector is already listing this
   class's properties in full, and printing four of their names beside the
   circle as well is the same information twice, in the smaller and less
   readable place. */
.sg-svg[data-level="class"] .sg-node__attrs,
.sg-svg[data-level="property"] .sg-node__attrs { opacity: 0; }
.sg-svg[data-zoom="overview"] .sg-edge__label { opacity: 0; pointer-events: none; }
/* Structural labels (subClassOf, replacedBy) stay quiet until asked for:
   the line already says what they are. */
.sg-edge__label.is-structural { opacity: 0; }
.sg-svg[data-zoom="detail"] .sg-edge__label.is-structural { opacity: 1; }
.sg-edge__label.is-structural.is-revealed,
.sg-edge__label.is-structural.is-selected,
.sg-edge__label.is-structural.is-live { opacity: 1; }
.sg-svg[data-zoom="overview"] .sg-node__meta { opacity: 0; }
.sg-svg[data-zoom="overview"] .sg-node__label { font-size: 13.5px; }

/* -- floating controls --------------------------------------------------- */
.sg-toolbar {
  position: absolute;
  top: 12px;
  left: 12px;
  right: 12px;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 8px;
  pointer-events: none;
}
.sg-toolbar__group {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 6px;
  border: 1px solid var(--gs-line);
  border-radius: 10px;
  background: rgb(255 253 250 / .92);
  backdrop-filter: blur(6px);
  box-shadow: 0 1px 2px rgb(23 20 15 / .04);
  pointer-events: auto;
}
.sg-select {
  max-width: 190px;
  padding: 5px 8px;
  border: 1px solid var(--gs-soft-line);
  border-radius: 7px;
  background-color: var(--gs-paper);
  color: var(--gs-copy);
  font-family: var(--gs-sans);
  font-size: 11.5px;
}
.sg-ctl {
  min-width: 30px;
  padding: 5px 9px;
  border: 1px solid var(--gs-soft-line);
  border-radius: 7px;
  background: var(--gs-paper);
  color: var(--gs-copy);
  font-family: var(--gs-sans);
  font-size: 11.5px;
  cursor: pointer;
}
.sg-ctl:hover { border-color: #c8bfb2; color: var(--gs-ink); }
.sg-ctl.is-active { border-color: var(--gs-ink); background: var(--gs-ink); color: var(--gs-paper); }

.sg-tier {
  position: absolute;
  bottom: 12px;
  left: 12px;
  display: flex;
  gap: 7px;
  align-items: baseline;
  padding: 5px 10px;
  border: 1px solid var(--gs-soft-line);
  border-radius: 999px;
  background: rgb(255 253 250 / .92);
  pointer-events: none;
}
.sg-tier__name {
  color: var(--gs-ink);
  font-family: var(--gs-mono);
  font-size: 10px;
  letter-spacing: .1em;
  text-transform: uppercase;
}
.sg-tier__note { color: var(--gs-muted); font-size: 11px; }

.sg-hint {
  position: absolute;
  bottom: 12px;
  left: 50%;
  transform: translateX(-50%);
  color: #a09889;
  font-size: 11px;
  pointer-events: none;
  white-space: nowrap;
}

/* -- graph key ----------------------------------------------------------
 * A popover, not a permanent strip. The previous legend occupied a band
 * of canvas on every visit to explain six swatches that most visits did
 * not need. */
.sg-key {
  position: absolute;
  top: 58px;
  right: 12px;
  z-index: 3;
  width: 250px;
  padding: 14px 16px;
  border: 1px solid var(--gs-line);
  border-radius: 12px;
  background: var(--gs-paper);
  box-shadow: 0 12px 30px rgb(23 20 15 / .12);
}
.sg-key__title {
  margin: 10px 0 6px;
  color: var(--gs-muted);
  font-family: var(--gs-mono);
  font-size: 9.5px;
  letter-spacing: .12em;
  text-transform: uppercase;
}
.sg-key__title:first-child { margin-top: 0; }
.sg-key__row { display: flex; flex-wrap: wrap; gap: 6px 12px; }
.sg-key__item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--gs-copy);
  font-size: 11.5px;
}
.sg-key__dot {
  width: 11px;
  height: 11px;
  border: 1.5px solid var(--gs-muted);
  border-radius: 50%;
  background: var(--gs-panel);
}
.sg-key__dot.is-outline { background: transparent; border-color: var(--gs-copy); }
.sg-key__line { width: 22px; border-top: 1.6px solid #a79e92; }
.sg-key__line--subclass { border-top-style: dashed; }
.sg-key__line--object { border-color: #a8697a; }
.sg-key__line--replaced { border-color: #b08b3a; border-top-style: dotted; }
.sg-key__note { margin: 12px 0 0; color: var(--gs-muted); font-size: 11px; line-height: 1.5; }

/* -- minimap ------------------------------------------------------------- */
.sg-minimap {
  position: absolute;
  right: 12px;
  bottom: 12px;
  width: 168px;
  height: 108px;
  overflow: hidden;
  border: 1px solid var(--gs-line);
  border-radius: 9px;
  background: rgb(255 253 250 / .94);
  cursor: pointer;
}
.sg-minimap__svg { display: block; width: 100%; height: 100%; }
.sg-minimap__dot { opacity: .55; }
.sg-minimap__dot.is-selected { opacity: 1; }
.sg-minimap__frame {
  fill: rgb(126 34 48 / .08);
  stroke: var(--gs-burgundy);
  stroke-width: 6;
}

/* -- tooltip -------------------------------------------------------------- */
.sg-tip {
  position: absolute;
  z-index: 4;
  max-width: 240px;
  padding: 9px 11px;
  border: 1px solid var(--gs-line);
  border-radius: 9px;
  background: var(--gs-paper);
  box-shadow: 0 8px 24px rgb(23 20 15 / .12);
  pointer-events: none;
}
.sg-tip__eyebrow {
  color: var(--gs-muted);
  font-family: var(--gs-mono);
  font-size: 9px;
  letter-spacing: .12em;
  text-transform: uppercase;
}
.sg-tip__title { margin: 2px 0 4px; color: var(--gs-ink); font-size: 13px; font-weight: 600; }
.sg-tip__row { color: var(--gs-copy); font-size: 11.5px; line-height: 1.45; }
.sg-tip__meta { margin-top: 3px; color: var(--gs-muted); font-family: var(--gs-mono); font-size: 10.5px; }

/* -- inspector ------------------------------------------------------------ */
.sg-inspector {
  position: sticky;
  top: 84px;
  padding: 18px 20px;
  border: 1px solid var(--gs-line);
  border-radius: var(--gs-radius);
  background: var(--gs-paper);
}
.sg-inspector__eyebrow {
  color: var(--gs-muted);
  font-family: var(--gs-mono);
  font-size: 9.5px;
  letter-spacing: .14em;
  text-transform: uppercase;
}
.sg-inspector__uri {
  overflow: hidden;
  margin: 6px 0 2px;
  color: var(--gs-muted);
  font-family: var(--gs-mono);
  font-size: 10.5px;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sg-inspector h2 { margin: 4px 0 8px; font-size: 17px; font-weight: 600; letter-spacing: -.02em; }
.sg-inspector__lede { margin: 0 0 14px; color: #6c665c; font-size: 12.5px; line-height: 1.55; text-wrap: pretty; }
.sg-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
.sg-chip {
  padding: 3px 8px;
  border: 1px solid var(--gs-soft-line);
  border-radius: 999px;
  background: var(--gs-panel);
  color: #5d564d;
  font-family: var(--gs-mono);
  font-size: 10px;
}
.sg-stats {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px 8px;
  margin-bottom: 16px;
}
.sg-stat { display: flex; flex-direction: column; gap: 2px; }
.sg-stat__value { color: var(--gs-ink); font-family: var(--gs-mono); font-size: 17px; }
.sg-stat__label { color: var(--gs-muted); font-size: 10.5px; }
.sg-inspector__title {
  margin: 16px 0 7px;
  color: var(--gs-muted);
  font-family: var(--gs-mono);
  font-size: 9.5px;
  letter-spacing: .12em;
  text-transform: uppercase;
}
.sg-inspector__list { display: flex; flex-direction: column; gap: 6px; }
.sg-inspector__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  min-width: 0;
}
.sg-inspector__key { color: var(--gs-muted); font-size: 11.5px; }
.sg-inspector__value {
  overflow: hidden;
  color: #3a342c;
  font-family: var(--gs-mono);
  font-size: 11px;
  text-align: right;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sg-inspector__empty { color: var(--gs-muted); font-size: 11.5px; }
.sg-inspector__audit {
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--gs-soft-line);
  color: var(--gs-muted);
  font-size: 11.5px;
  line-height: 1.6;
}
.sg-inspector__actions { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 14px; }
.sg-action {
  padding: 5px 10px;
  border: 1px solid var(--gs-soft-line);
  border-radius: 7px;
  background: var(--gs-paper);
  color: var(--gs-copy);
  font-family: var(--gs-sans);
  font-size: 11.5px;
  cursor: pointer;
}
.sg-action:hover { border-color: #c8bfb2; color: var(--gs-ink); }
.sg-action.is-active { border-color: var(--gs-burgundy); color: var(--gs-burgundy); }
.sg-link {
  padding: 0;
  border: 0;
  background: none;
  color: var(--gs-burgundy);
  font-family: var(--gs-mono);
  font-size: 11.5px;
  text-align: left;
  cursor: pointer;
}
.sg-link:hover { text-decoration: underline; }
.sg-rel {
  display: grid;
  grid-template-columns: 14px minmax(0, 1fr) minmax(0, 1fr);
  gap: 8px;
  align-items: baseline;
  padding: 4px 6px;
  border: 1px solid transparent;
  border-radius: 7px;
  background: none;
  font-family: var(--gs-mono);
  font-size: 11px;
  text-align: left;
  cursor: pointer;
}
.sg-rel:hover { border-color: var(--gs-soft-line); background: var(--gs-panel); }
.sg-rel__dir.is-out { color: var(--gs-burgundy); }
.sg-rel__dir.is-in { color: #38536f; }
.sg-rel__name { overflow: hidden; color: var(--gs-ink); text-overflow: ellipsis; white-space: nowrap; }
.sg-rel__target { overflow: hidden; color: var(--gs-muted); text-align: right; text-overflow: ellipsis; white-space: nowrap; }
.sg-path__step { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.sg-path__edge { color: var(--gs-muted); font-family: var(--gs-mono); font-size: 10.5px; }
.sg-empty { padding: 60px 24px; text-align: center; }
.sg-empty h2 { margin: 0 0 8px; font-size: 17px; font-weight: 600; }
.sg-empty p { margin: 0; color: var(--gs-muted); font-size: 12.5px; }

@media (max-width: 1100px) {
  .sg-canvas { grid-template-columns: minmax(0, 1fr); }
  .sg-inspector { position: static; }
  .sg-plot { height: min(64vh, 620px); }
  .sg-minimap { display: none; }
}
@media (max-width: 720px) {
  .sg-toolbar { position: static; margin: 10px; }
  .sg-hint { display: none; }
  .sg-select { max-width: 150px; }
  /* Portrait, and sized against the WIDTH rather than the viewport.
     `min(64vh, 620px)` on a 390px phone is a box twice as tall as it is
     wide, which a hierarchy cannot fill however it is laid out - the
     drawing ends up small with dead bands above and below. Capping the
     ratio at 1.35 keeps the box a shape the graph can actually fill;
     `min-height` keeps it usable on a short landscape phone. */
  .sg-plot {
    height: min(135vw, 64vh, 620px);
    min-height: 320px;
  }
}
@media (orientation: landscape) and (max-height: 560px) {
  /* A phone on its side is SHORT, not narrow - and `max-width` does not
     see that: a 844x390 phone is "wide" by every width breakpoint there
     is, so it kept the 64vh box, which is 250px, which draws a concept
     fourteen pixels across. Height is the binding dimension here, so the
     box has to claim more of it. */
  .sg-plot { height: min(86vh, 420px); min-height: 260px; }
  .sg-toolbar { position: static; margin: 8px 10px; }
}
@media (prefers-reduced-motion: reduce) {
  .sg-node, .sg-node__body, .sg-edge__line, .sg-edge__label, .sg-node__attrs { transition: none; }
}

/* =====================================================================
 * CLASS FOCUS / PROPERTY FOCUS
 *
 * Three tiers of attention, one canvas:
 *
 *   primary   what you are looking at
 *   context   what it inherits from, relates to, and points at
 *   back      the rest of the ontology - dimmed, never removed
 *
 * The background stays because spatial awareness is the whole difference
 * between drilling into a graph and opening a different screen. Opacity
 * does the work rather than `filter: blur()`: a blur on twenty nodes and
 * forty edges costs a repaint per frame while panning, and the effect it
 * buys is the same one a lower opacity gives for free.
 * ===================================================================== */

.sg-node.is-context { opacity: .62; }
.sg-node.is-back { opacity: .16; }
.sg-edge.is-context .sg-edge__line { stroke-opacity: .5; }
.sg-edge.is-back .sg-edge__line { stroke-opacity: .1; }
.sg-edge.is-back .sg-edge__hit { pointer-events: none; }
.sg-node.is-primary .sg-node__body { stroke: var(--gs-ink); stroke-width: 3; }
.sg-node.is-primary .sg-node__label { font-weight: 700; }
.sg-edge.is-primary .sg-edge__line { stroke: var(--gs-ink); stroke-width: 2.2; stroke-opacity: 1; }

/* Property focus pushes the background further back than class focus:
   the subgraph on top of it needs the contrast, and by then the user has
   already chosen to go two levels deep. */
.sg-svg[data-level="property"] .sg-node.is-back { opacity: .08; }
.sg-svg[data-level="property"] .sg-node.is-context { opacity: .42; }
.sg-svg[data-level="property"] .sg-edge.is-back .sg-edge__line { stroke-opacity: .05; }
/* In property focus the subgraph supplies every label that matters, so
   the class graph's own labels go quiet - including the ones on the edges
   between the domain and range, which otherwise print a second name
   across the spine explaining the same pair. `context` keeps them: that
   mode exists to make the ontology readable again. */
.sg-svg[data-level="property"] .sg-edge__label { opacity: 0; }

/* -- the property subgraph ---------------------------------------------
   Properties draw as PILLS, never circles. A property is not a class,
   and drawing it like one is the commonest way an ontology tool teaches
   the wrong model. */
.sg-prop { cursor: pointer; }
.sg-prop__body {
  fill: var(--gs-paper);
  stroke: var(--gs-burgundy);
  stroke-width: 1.6;
}
.sg-prop--primary .sg-prop__body { fill: #F6EEF0; stroke: var(--gs-ink); stroke-width: 2.4; }
.sg-prop--related .sg-prop__body { stroke: #A8697A; stroke-dasharray: 0; }
.sg-prop.is-deprecated .sg-prop__body { stroke-dasharray: 2 3; }
.sg-prop:hover .sg-prop__body { stroke: var(--gs-ink); }
.sg-prop:focus-visible { outline: 2px solid rgb(126 34 48 / .6); outline-offset: 3px; }
.sg-prop__label {
  fill: var(--gs-ink);
  font-family: var(--gs-mono);
  font-size: 12px;
  pointer-events: none;
}
.sg-prop--primary .sg-prop__label { font-weight: 600; }
.sg-prop__kind {
  fill: #a09889;
  font-family: var(--gs-mono);
  font-size: 9px;
  letter-spacing: .04em;
  pointer-events: none;
}
.sg-focus-edge__line { fill: none; stroke: var(--gs-ink); stroke-width: 1.8; }
.sg-focus-edge--structural .sg-focus-edge__line { stroke: var(--gs-burgundy); }
.sg-focus-edge--relation .sg-focus-edge__line { stroke: #6F6961; stroke-dasharray: 5 4; }
.sg-focus-edge__label .sg-edge__label-bg { fill: var(--gs-paper); stroke: var(--gs-line); }
.sg-focus-edge__label .sg-edge__label-text { fill: var(--gs-ink); }

/* -- breadcrumb ---------------------------------------------------------- */
/* `display: flex` beats the user agent's `[hidden] { display: none }`,
   so the breadcrumb stayed on screen at schema level however carefully
   the JS set `hidden`. The element reported itself hidden and was not -
   which is why the test that only read the property missed it. */
.sg-crumbs[hidden] { display: none; }
.sg-crumbs {
  position: absolute;
  bottom: 12px;
  left: 12px;
  z-index: 2;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  padding: 5px 10px;
  border: 1px solid var(--gs-line);
  border-radius: 999px;
  background: rgb(255 253 250 / .95);
  box-shadow: 0 1px 2px rgb(23 20 15 / .05);
}
.sg-crumb {
  padding: 2px 6px;
  border: 0;
  border-radius: 6px;
  background: none;
  color: var(--gs-muted);
  font-family: var(--gs-sans);
  font-size: 11.5px;
  cursor: pointer;
}
.sg-crumb:hover { color: var(--gs-ink); background: var(--gs-panel); }
.sg-crumb.is-current { color: var(--gs-ink); font-weight: 600; cursor: default; }
.sg-crumb--prop.is-current { font-family: var(--gs-mono); font-size: 11px; }
.sg-crumbs__sep { color: #c3bbae; font-size: 11px; }
.sg-crumbs__back,
.sg-crumbs__context {
  margin-left: 4px;
  padding: 3px 9px;
  border: 1px solid var(--gs-soft-line);
  border-radius: 999px;
  background: var(--gs-paper);
  color: var(--gs-copy);
  font-family: var(--gs-sans);
  font-size: 11px;
  cursor: pointer;
}
.sg-crumbs__back:hover,
.sg-crumbs__context:hover { border-color: #c8bfb2; color: var(--gs-ink); }
.sg-crumbs__context.is-active { border-color: var(--gs-ink); background: var(--gs-ink); color: var(--gs-paper); }

/* When the breadcrumb is up it owns the bottom-left: the level badge
   moves above it, and the canvas hint - which explains gestures that are
   no longer the point once you are two levels deep - gets out of the way
   entirely. */
.sg-svg[data-level="class"] ~ .sg-hint,
.sg-svg[data-level="property"] ~ .sg-hint,
.sg-svg[data-level="context"] ~ .sg-hint { display: none; }

/* When the breadcrumb is up, the level badge moves out of its way. */
.sg-svg[data-level="class"] ~ .sg-tier,
.sg-svg[data-level="property"] ~ .sg-tier,
.sg-svg[data-level="context"] ~ .sg-tier { bottom: 52px; }

/* -- the property list --------------------------------------------------
   Text first, one line each, clickable. Not cards: a property list that
   looks like a set of panels competes with the canvas for attention,
   and the canvas is the thing being explored. */
.sg-prop-group { display: flex; flex-direction: column; gap: 2px; margin-bottom: 10px; }
.sg-prop-group__label {
  margin: 8px 0 4px;
  color: var(--gs-muted);
  font-family: var(--gs-mono);
  font-size: 9.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
}
.sg-prop-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  gap: 8px;
  align-items: baseline;
  width: 100%;
  padding: 5px 7px;
  border: 1px solid transparent;
  border-radius: 7px;
  background: none;
  font-family: var(--gs-mono);
  font-size: 11.5px;
  text-align: left;
  cursor: pointer;
}
.sg-prop-row:hover { border-color: var(--gs-soft-line); background: var(--gs-panel); }
.sg-prop-row:focus-visible { outline: 2px solid rgb(126 34 48 / .5); outline-offset: 1px; }
.sg-prop-row.is-deprecated .sg-prop-row__name { text-decoration: line-through; color: var(--gs-muted); }
.sg-prop-row__name { overflow: hidden; color: var(--gs-ink); text-overflow: ellipsis; white-space: nowrap; }
.sg-prop-row__range { color: var(--gs-muted); font-size: 10.5px; white-space: nowrap; }
.sg-prop-row__badge {
  min-width: 16px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--gs-panel);
  color: var(--gs-muted);
  font-size: 9.5px;
  text-align: center;
}

/* -- ghosts ---------------------------------------------------------------
   A class that is not part of the current semantic scope stays on the
   canvas as a landmark - so the reader keeps their map - and takes no
   part in it: no edges, no pointer events, no label competing for
   attention. This is not "the same graph, faded"; the edges of an
   out-of-scope class are not in the document at all. */
.sg-node.is-ghost { opacity: .1; pointer-events: none; }
.sg-node.is-ghost .sg-node__meta,
.sg-node.is-ghost .sg-node__collapse { display: none; }
.sg-svg[data-level="property"] .sg-node.is-ghost { opacity: .07; }

/* A class-to-class relation declared as a marker (`disjointWith`) rather
   than drawn as an edge by the payload. Violet, matching the metamodel's
   own provenance colour, because that is where it comes from. */
.sg-edge--declared .sg-edge__line { stroke: #584A73; stroke-dasharray: 7 3; }
.sg-edge--declared .sg-edge__label-bg { stroke: #584A73; }

/* A datatype range. Square, grey, unmistakably not a concept - `xsd:date`
   is a real range and belongs in the picture, but drawing it as a circle
   would put a class in the schema that is not there. */
.sg-datatype__body {
  fill: #EFEDE7;
  stroke: #B6AC9C;
  stroke-width: 1.2;
}
.sg-datatype__label {
  fill: #5d564d;
  font-family: var(--gs-mono);
  font-size: 11px;
  pointer-events: none;
}

/* The proposal-review select that names WHICH change the canvas shows.
   It sits in the page actions beside "Committed", so it inherits that
   row's alignment rather than the toolbar's. */
.ws-schema-mode__select { max-width: 260px; }

/* -- the import plan ------------------------------------------------------
   Shown before an import runs, because importing a standard term mints a
   URI and a minted URI never moves (§5.2 rule 2). Deliberately plain: it
   is a list of decisions, not a dashboard. */
.ws-explore-row-wrap { display: flex; flex-direction: column; }
.ws-plan {
  margin: 0 0 10px;
  padding: 14px 16px;
  border: 1px solid var(--gs-line);
  border-radius: 12px;
  background: var(--gs-panel);
}
.ws-plan[hidden] { display: none; }
.ws-plan__head {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
  margin-bottom: 10px;
}
.ws-plan__title {
  flex: 1 1 auto;
  color: var(--gs-muted);
  font-family: var(--gs-mono);
  font-size: 10px;
  letter-spacing: .12em;
  text-transform: uppercase;
}
.ws-plan__domain { max-width: 180px; }
.ws-plan__toggle {
  display: inline-flex;
  gap: 6px;
  align-items: center;
  color: var(--gs-copy);
  font-size: 11.5px;
}
.ws-plan__list { display: flex; flex-direction: column; gap: 6px; }
.ws-plan__row {
  display: grid;
  grid-template-columns: 190px minmax(0, 1fr);
  gap: 12px;
  align-items: baseline;
  padding: 6px 8px;
  border-radius: 8px;
  background: var(--gs-paper);
}
.ws-plan__verdict {
  display: flex;
  flex-direction: column;
  gap: 1px;
  font-size: 11.5px;
}
.ws-plan__verdict strong { color: var(--gs-ink); font-weight: 600; }
.ws-plan__verdict.is-match strong { color: var(--gs-green); }
.ws-plan__verdict.is-unsure strong { color: var(--gs-amber); }
.ws-plan__verdict-note { color: var(--gs-muted); font-size: 10.5px; }
.ws-plan__term { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.ws-plan__label { color: var(--gs-ink); font-family: var(--gs-mono); font-size: 12px; }
.ws-plan__meta { color: var(--gs-muted); font-size: 11px; }
.ws-plan__note { margin: 10px 0 0; color: var(--gs-muted); font-size: 11px; line-height: 1.5; }
.ws-plan__actions { display: flex; justify-content: flex-end; margin-top: 12px; }

/* The note carries which checks ran. Only the broken case is coloured:
   "spelling only" is a normal, correct mode of operation, while an index
   that was asked and could not answer is a fact the reviewer has to
   weigh - so it gets the warning tone and the other two do not. */
.ws-plan__note::before {
  content: ""; display: inline-block; width: 6px; height: 6px;
  border-radius: 50%; margin-right: 7px; vertical-align: 1px;
  background: var(--gs-line);
}
.ws-plan__note.is-embedding::before { background: var(--gs-green); }
.ws-plan__note.is-lexical::before { background: var(--gs-line); }
.ws-plan__note.is-degraded { color: var(--gs-amber); }
.ws-plan__note.is-degraded::before { background: currentColor; }

@media (max-width: 680px) {
  .ws-plan__row { grid-template-columns: minmax(0, 1fr); }
}


/* ---------- people & access: a role, its reach, and whether it is live ----
 *
 * A roster that showed only a role would make a reviewer WITH no domain
 * granted look identical to one who can sign a change, and those are
 * opposites. Every row therefore carries three things: what they are,
 * where it counts, and whether the account is on.
 */
.ws-grants { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 7px; }
.ws-grant {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2px 4px 2px 9px; border: 1px solid var(--gs-line);
  border-radius: 999px; background: var(--gs-paper);
  font-family: var(--gs-mono); font-size: 10.5px; color: var(--gs-copy);
}
.ws-grant__name { letter-spacing: .02em; }
.ws-grant__x {
  border: 0; background: none; cursor: pointer; line-height: 1;
  padding: 0 3px; color: var(--gs-muted); font-size: 12px; border-radius: 50%;
}
.ws-grant__x:hover { color: var(--gs-burgundy); background: #f6ebec; }
.ws-grant__add {
  width: 92px; padding: 3px 8px; border: 1px dashed var(--gs-line);
  border-radius: 999px; background: transparent;
  font-family: var(--gs-mono); font-size: 10.5px; color: var(--gs-copy);
}
/* The border going solid is a nice touch and it is NOT a focus
   indicator - a 1px colour change on a dashed outline is not something a
   keyboard user can find. The ring stays and the border change rides
   along with it. */
.ws-grant__add:focus-visible {
  outline: 2px solid var(--gs-burgundy); outline-offset: 2px;
  border-color: var(--gs-burgundy); border-style: solid;
}

.ws-person__actions { display: inline-flex; align-items: center; gap: 10px; }
.ws-person__toggle {
  border: 1px solid var(--gs-line); background: var(--gs-paper);
  border-radius: 8px; padding: 4px 10px; cursor: pointer;
  font-size: 11.5px; color: var(--gs-muted);
}
.ws-person__toggle:hover { border-color: var(--gs-burgundy); color: var(--gs-burgundy); }
/* Deactivated, never deleted: the row stays because their name is still
   on every decision they signed, and an account that vanished from the
   roster would make those signatures look like they came from nobody. */
.ws-settings-admin__person.is-inactive { opacity: .55; }
.ws-person__gone {
  margin-left: 8px; font-family: var(--gs-mono); font-size: 9.5px;
  letter-spacing: .1em; text-transform: uppercase; color: var(--gs-burgundy);
}


/* ---------- cross-domain holds -------------------------------------------
 * A change that reaches into another domain waits for that domain's
 * admin. Shown to everybody, because a reviewer looking at a blocked
 * change needs to know who they are waiting on; only the buttons are
 * gated, and the server gates them again.
 */
.ws-hold {
  border: 1px solid var(--gs-line); border-left-width: 3px;
  border-radius: 10px; padding: 11px 13px; margin-top: 10px;
  background: var(--gs-paper);
}
.ws-hold.is-open { border-left-color: var(--gs-amber); }
.ws-hold.is-acknowledged { border-left-color: var(--gs-green); }
.ws-hold.is-objected { border-left-color: var(--gs-danger); }
.ws-hold__head { display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap; }
.ws-hold__domain { font-family: var(--gs-mono); font-size: 12.5px; color: var(--gs-ink); }
.ws-hold__kind { font-size: 11.5px; color: var(--gs-muted); }
.ws-hold__state {
  margin-left: auto; font-family: var(--gs-mono); font-size: 9.5px;
  letter-spacing: .1em; text-transform: uppercase; color: var(--gs-muted);
}
.ws-hold__note { margin: 6px 0 0; font-size: 12px; line-height: 1.5; color: var(--gs-copy); }
.ws-hold__terms { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 7px; }
.ws-hold__term {
  font-family: var(--gs-mono); font-size: 10.5px; color: var(--gs-muted);
  border: 1px solid var(--gs-soft-line); border-radius: 5px; padding: 1px 6px;
}
.ws-hold__actions { display: flex; gap: 8px; margin-top: 10px; }
.ws-hold__ack, .ws-hold__object {
  border: 1px solid var(--gs-line); border-radius: 8px; padding: 5px 12px;
  background: var(--gs-paper); cursor: pointer; font-size: 12px;
}
.ws-hold__ack:hover { border-color: var(--gs-green); color: var(--gs-green); }
/* An objection stops somebody else's work. It should not be the easy
   button on the panel. */
.ws-hold__object { color: var(--gs-muted); }
.ws-hold__object:hover { border-color: var(--gs-danger); color: var(--gs-danger); }

/* The role, as the control it is. Sized to sit level with the chip it
   replaced so a row with a control and a row without do not step. */
.ws-person__role-select {
  border: 1px solid var(--gs-line); border-radius: 8px;
  padding: 4px 8px; background-color: var(--gs-paper);
  font-family: inherit; font-size: 11.5px; color: var(--gs-ink);
}
.ws-person__role-select:hover { border-color: var(--gs-burgundy); }

/* The vocabulary index has three states and this screen used to show
   one. "Nothing looked" must never render as "nothing was found" on the
   page whose job is checking for reuse before minting a permanent URI. */
.ws-lib-state {
  display: flex; flex-direction: column; gap: 3px;
  border: 1px solid var(--gs-line); border-left-width: 3px;
  border-radius: 10px; padding: 11px 14px; margin-bottom: 14px;
  background: var(--gs-paper);
}
.ws-lib-state.is-building { border-left-color: var(--gs-line-2, var(--gs-line)); }
.ws-lib-state.is-unavailable { border-left-color: var(--gs-amber); }
.ws-lib-state__what { font-size: 13px; font-weight: 600; color: var(--gs-ink); }
.ws-lib-state__why { font-size: 12px; color: var(--gs-muted); line-height: 1.5; }

/* ---------- domains, and who is on them ---------------------------------- */
.ws-settings-admin__deployment { margin-top: 28px; }
.ws-settings-admin__caption { margin: 6px 0 14px; font-size: 12.5px; color: var(--gs-muted); }
.ws-domain {
  border: 1px solid var(--gs-line); border-left-width: 3px;
  border-left-color: var(--gs-line); border-radius: 10px;
  padding: 11px 14px; margin-bottom: 8px; background: var(--gs-paper);
}
/* Work with nobody answerable for it. The one row on this panel that is
   a finding rather than a fact. */
.ws-domain.is-unattended { border-left-color: var(--gs-amber); }
.ws-domain__head { display: flex; align-items: baseline; gap: 10px; }
.ws-domain__name { font-family: var(--gs-mono); font-size: 13px; color: var(--gs-ink); }
.ws-domain__open { margin-left: auto; font-size: 11.5px; color: var(--gs-muted); }

/* The domain's technical quorum, beside its reviewer count. `margin-left:
   auto` already belongs to __open, so this sits after it on the same row
   and wraps beneath on narrow screens rather than squeezing the name. */
.ws-domain__quorum { display: inline-flex; align-items: center; gap: 6px;
  font-size: 11.5px; color: var(--gs-muted); }
.ws-domain__quorum-input { width: 52px; padding: 2px 6px; font: inherit;
  color: var(--gs-ink); background: var(--gs-surface);
  border: 1px solid var(--gs-line); border-radius: 5px; }
.ws-domain__quorum-note.is-unreachable { color: var(--gs-amber, #b45309); font-weight: 600; }
.ws-domain__people { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 5px; }
.ws-domain__admins, .ws-domain__revs { font-size: 11.5px; color: var(--gs-copy); }
.ws-domain__none { font-size: 11.5px; color: var(--gs-amber); }
.ws-domain__tasks { margin-top: 8px; border-top: 1px solid var(--gs-soft-line); padding-top: 7px; }
.ws-domain__task { display: flex; gap: 10px; font-size: 11.5px; padding: 2px 0; }
.ws-domain__task-who { font-family: var(--gs-mono); color: var(--gs-burgundy); min-width: 96px; }
.ws-domain__task-what { color: var(--gs-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ── THEMED DROPDOWNS (selects.js) ────────────────────────────────────────
 *
 * A native <select>'s OPEN list is drawn by the operating system and
 * cannot be styled by CSS at all. Styling the closed control - which the
 * `select` rule near the top of this file does - fixes the button and
 * leaves the popup as a raw OS menu, which is what "the dropdowns are
 * still unstyled" meant.
 *
 * `selects.js` draws the list itself. It shipped complete (full APG
 * select-only combobox: roles, aria-activedescendant, arrow keys,
 * type-ahead, Home/End, Escape) and was PARKED for one reason recorded
 * in `test_static_assets.py` - it had no CSS, so loading it would have
 * drawn an unstyled list over a working control. This block is that
 * missing piece.
 *
 * DELIBERATELY LAST IN THE FILE. `.sel` carries the select's own classes
 * (see selects.js), so `.sel` and `.dc-input` collide at equal
 * specificity and source order decides - these resets must win.
 */
.sel {
  position: relative;
  display: inline-block;
  /* Reset the PAINT the inherited class applied; the button draws it. */
  min-height: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
}
/* The real control stays for `.value`, form submission and `change`.
   Clipped rather than `display: none` so it remains a form participant;
   selects.js already gives it tabindex="-1" and aria-hidden. */
.sel > select {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  border: 0;
  opacity: 0;
  pointer-events: none;
}
.sel-button {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
  min-height: 32px;
  padding: 0 11px;
  border: 1px solid var(--gs-line);
  border-radius: 8px;
  background-color: var(--gs-paper);
  color: var(--gs-ink);
  font-size: 12.5px;
  text-align: left;
}
.sel-button:hover { border-color: #cfc7ba; }
.sel-open .sel-button,
.sel-button:focus-visible { border-color: #a49b8e; box-shadow: 0 0 0 3px rgba(126, 34, 48, .08); }
.sel-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sel-caret {
  flex: 0 0 auto;
  width: 10px;
  height: 6px;
  background-color: currentColor;
  opacity: .55;
  clip-path: polygon(0 0, 100% 0, 50% 100%);
  transition: transform .12s ease;
}
.sel-open .sel-caret { transform: rotate(180deg); }

.sel-panel {
  position: absolute;
  z-index: 60;
  top: calc(100% + 5px);
  left: 0;
  min-width: 100%;
  max-width: min(420px, 88vw);
  max-height: 288px;
  overflow-y: auto;
  padding: 5px;
  border: 1px solid var(--gs-line);
  border-radius: 10px;
  background: var(--gs-paper);
  box-shadow: 0 12px 32px rgb(23 20 15 / .13);
}
.sel-group {
  padding: 7px 9px 4px;
  color: var(--gs-muted);
  font-size: 10.5px;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.sel-option {
  padding: 7px 9px;
  border-radius: 6px;
  color: var(--gs-ink);
  font-size: 12.5px;
  cursor: pointer;
}
/* `sel-active` is KEYBOARD position and `is-selected` is the committed
   value. They are different facts and must not look the same: arrowing
   past an option is not choosing it. */
.sel-option.sel-active { background: #f2efe9; }
.sel-option.is-selected { color: var(--gs-burgundy); font-weight: 500; }
.sel-option.is-selected::after { content: " ✓"; }
.sel-option.is-disabled { color: var(--gs-muted); cursor: not-allowed; }
.sel-label-text { display: inline; }

@media (prefers-reduced-motion: reduce) {
  .sel-caret { transition: none; }
}

/* ==========================================================================
   THE PRODUCTION RAIL  +  THE PROVENANCE THREAD
   ==========================================================================
   Two dimensions over the same screens. `gs-nav` answers "what can I do";
   the rail answers "where am I in the production". Neither replaces the
   other, and the rail is a journey indicator first and navigation second.

   Every value below is an existing --gs-* token. No new palette. */

/* -- the thread -------------------------------------------------------- */
.ws-thread { list-style: none; margin: 0 0 14px; padding: 0; }
.ws-thread__node {
  position: relative; display: grid; grid-template-columns: 14px minmax(0, 1fr);
  gap: 10px; padding: 7px 0;
  animation: gs-rise .3s cubic-bezier(.22,.61,.36,1) calc(var(--i, 0) * 60ms) backwards;
}
/* one spine, drawn per node, so it is exact at any label length */
.ws-thread__node::before {
  content: ""; position: absolute; left: 4px; top: 0; bottom: 0; width: 1px;
  background: var(--gs-line);
}
.ws-thread__node:first-child::before { top: 14px; }
.ws-thread__node:last-child::before { bottom: calc(100% - 14px); }
.ws-thread__mark {
  width: 9px; height: 9px; margin-top: 5px; border-radius: 50%;
  background: var(--gs-paper); box-shadow: inset 0 0 0 1.5px var(--gs-burgundy);
  position: relative; z-index: 1;
}
/* a link the data does not carry, and a step that has not happened, must
   not look like a fact. Both are hollow and dashed; neither is red. */
.ws-thread__node.is-unlinked .ws-thread__mark,
.ws-thread__node.is-pending .ws-thread__mark {
  box-shadow: inset 0 0 0 1px var(--gs-line);
}
.ws-thread__node.is-unlinked .ws-thread__label,
.ws-thread__node.is-pending .ws-thread__label { color: var(--gs-muted); }
.ws-thread__body { display: grid; gap: 2px; min-width: 0; }
.ws-thread__k {
  font-family: var(--gs-mono); font-size: 8.5px; letter-spacing: .18em;
  text-transform: uppercase; color: var(--gs-muted);
}
.ws-thread__label { font-size: 13px; color: var(--gs-ink); }
a.ws-thread__label { color: var(--gs-burgundy); }
.ws-thread__detail { font-size: 11.5px; line-height: 1.45; color: var(--gs-muted); }

/* -- precedent --------------------------------------------------------- */
.ws-prec { margin-top: 4px; }
.ws-prec__head { display: flex; align-items: center; gap: 9px; margin-bottom: 9px; }
.ws-prec__k {
  font-family: var(--gs-mono); font-size: 9px; letter-spacing: .18em;
  text-transform: uppercase; color: var(--gs-burgundy); font-weight: 600;
}
.ws-prec__n {
  font-family: var(--gs-mono); font-size: 10px; padding: 1px 6px;
  border-radius: 4px; background: var(--gs-panel); color: var(--gs-copy);
}
.ws-prec__list { list-style: none; margin: 0; padding: 0; }
.ws-prec__item {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding: 7px 0; border-top: 1px solid var(--gs-soft-line);
}
.ws-prec__id { font-family: var(--gs-mono); font-size: 11.5px; color: var(--gs-burgundy); }
.ws-prec__go { font-family: var(--gs-mono); font-size: 9px; letter-spacing: .1em; text-transform: uppercase; color: var(--gs-muted); }
.ws-prec__why { margin: 11px 0 0; font-size: 11.5px; line-height: 1.5; color: var(--gs-copy); }
/* said plainly, because the alternative is printing a verdict we do not have */
.ws-prec__gap {
  margin: 7px 0 0; font-family: var(--gs-mono); font-size: 9.5px;
  line-height: 1.5; color: var(--gs-muted);
}
.ws-prec__none { margin: 0; font-size: 11.5px; color: var(--gs-muted); }


/* ==========================================================================
   THE LIFECYCLE IN THE NAVIGATION, AND THE STORY ON HOME
   ==========================================================================
   No rail, no stepper, no numbers in the nav. The lifecycle is carried by
   ORDER and by one rule where the kind changes:

     Home │ Sources  Model  Review  Release  Decisions │ Graph │ Operate Settings

   Existing --gs-* tokens only. */

.gs-nav__sep {
  width: 1px; align-self: stretch; margin: 7px 9px;
  background: var(--gs-line); flex: none;
}
/* Graph is the only member of its kind, and it is what the whole line is
   building. It reads as a destination, not as the step after Decisions. */
.gs-nav__item--artifact { color: var(--gs-burgundy); }
.gs-nav__item--artifact:hover { color: var(--gs-burgundy); }
.gs-nav__item--support { color: var(--gs-muted); }
/* Hovering one lifecycle group quietens the others, so the five read as a
   set without a bar being drawn around them. */
@media (hover: hover) {
  .gs-nav:hover .gs-nav__item--flow:not(:hover) { opacity: .62; }
  .gs-nav__item--flow { transition: opacity .2s ease, color .2s ease; }
}

/* Restored with the original Home composition. */
.ws-frontdoor {
  margin-bottom: 26px;
  padding: 24px 26px;
  border: 1px solid #e3ded4;
  border-radius: 16px;
  background: linear-gradient(180deg, #fffdfa 0%, #f7f4ee 100%);
}
.ws-frontdoor__eyebrow {
  color: #7e2230;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
}
.ws-frontdoor__title {
  margin: 6px 0 4px;
  color: #17140f;
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -.02em;
}
.ws-frontdoor__copy {
  max-width: 62ch;
  margin: 0 0 14px;
  color: #6f6961;
  font-size: 12.5px;
  line-height: 1.6;
}
.ws-frontdoor__row { display: flex; flex-wrap: wrap; gap: 8px; }
.ws-frontdoor__input {
  flex: 1 1 320px;
  min-width: 0;
  padding: 10px 13px;
  border: 1px solid #ddd7cc;
  border-radius: 10px;
  background: #fffdfa;
  color: #17140f;
  font: inherit;
  font-size: 13.5px;
}
.ws-frontdoor__input:focus-visible {
  border-color: #7e2230;
  outline: 2px solid rgba(126, 34, 48, .26);
  outline-offset: 1px;
}
.ws-frontdoor__go {
  padding: 10px 20px;
  border: 0;
  border-radius: 10px;
  background: #17140f;
  color: #fffdfa;
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}
.ws-frontdoor__go:hover { background: #2c261e; }
.ws-frontdoor__shapes { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 12px; }
.ws-frontdoor__shape {
  padding: 4px 11px;
  border: 1px solid #ddd7cc;
  border-radius: 999px;
  background: #fffdfa;
  color: #6f6961;
  font-size: 11px;
  text-decoration: none;
}
.ws-frontdoor__shape:hover { border-color: #7e2230; color: #7e2230; }


/* -- Writing a competency test -------------------------------------------
   The screen could read tests and not write one, and - the harder half -
   never said what a test IS. "Unit tests for meaning" does not tell anyone
   what to type.

   Kind first, fields second: the same grammar-before-input move the Ask
   surface makes. `CompetencyStore.add` rejects a term_exists with no term,
   a subclass_of missing either end, and a sparql_ask that is not a valid
   ASK; none of those states are reachable from this form. */
.ws-cq {
  margin-top: 26px; padding: 22px 24px 20px;
  border: 1px solid var(--gs-line); border-radius: 14px; background: var(--gs-paper);
}
.ws-cq__title { margin: 0; font-size: 15px; font-weight: 600; letter-spacing: -.012em; color: var(--gs-ink); }
.ws-cq__lede { margin: 7px 0 0; max-width: 62ch; font-size: 13px; line-height: 1.6; color: var(--gs-copy); }

.ws-cq__kinds { display: flex; flex-wrap: wrap; gap: 8px; margin: 18px 0 0; }
.ws-cq__kind {
  padding: 7px 13px; border: 1px solid var(--gs-line); border-radius: 8px;
  background: transparent; color: var(--gs-muted); font-size: 12.5px;
  transition: border-color .18s ease, color .18s ease, background .18s ease;
}
.ws-cq__kind:hover { border-color: #cfc8bc; color: var(--gs-ink); }
.ws-cq__kind.is-on { border-color: var(--gs-burgundy); color: var(--gs-burgundy); background: #f7eff0; }
.ws-cq__kind:focus-visible { outline: 2px solid rgb(126 34 48 / .48); outline-offset: 2px; }

.ws-cq__body { margin-top: 16px; }
/* what the selected kind is FOR - the sentence the page never had */
.ws-cq__what {
  margin: 0 0 16px; padding-left: 13px; max-width: 68ch;
  box-shadow: inset 2px 0 0 var(--gs-soft-line);
  font-size: 12.5px; line-height: 1.62; color: var(--gs-copy);
}
.ws-cq__field { display: block; margin-bottom: 14px; }
.ws-cq__label {
  display: block; margin-bottom: 5px; font-family: var(--gs-mono); font-size: 10px;
  font-weight: 600; letter-spacing: .14em; text-transform: uppercase; color: var(--gs-muted);
}
.ws-cq__input {
  width: 100%; min-height: 38px; padding: 9px 12px;
  border: 1px solid var(--gs-line); border-radius: 8px;
  background: var(--gs-bg); color: var(--gs-ink); font-size: 13px;
}
/* the placeholder is a REAL example, so it stays legible rather than being
   the usual near-invisible grey */
.ws-cq__input::placeholder { color: #9a9287; }
.ws-cq__input:focus-visible { outline: 2px solid rgb(126 34 48 / .48); outline-offset: 1px; border-color: #cfc8bc; }
.ws-cq__input--area { font-family: var(--gs-mono); font-size: 12px; line-height: 1.6; resize: vertical; }
.ws-cq__help { display: block; margin-top: 5px; font-size: 11.5px; line-height: 1.5; color: var(--gs-muted); }
@media (min-width: 720px) {
  .ws-cq__params { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 16px; }
  .ws-cq__params .ws-cq__field:only-child { grid-column: 1 / -1; }
  .ws-cq__field:has(.ws-cq__input--area) { grid-column: 1 / -1; }
}

.ws-cq__foot {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding-top: 15px; border-top: 1px solid var(--gs-soft-line);
}
.ws-cq__add {
  padding: 9px 18px; border: 0; border-radius: 8px;
  background: var(--gs-ink); color: var(--gs-bg); font-size: 13px; font-weight: 500;
}
.ws-cq__add:hover { background: #2c261e; }
.ws-cq__add:focus-visible { outline: 2px solid rgb(126 34 48 / .48); outline-offset: 2px; }
/* straight from `CompetencyStore.add`: the API records gating=False and has
   no parameter to change it. Saying so beats implying a new test gates. */
.ws-cq__gating { flex: 1; min-width: 22ch; font-size: 11.5px; line-height: 1.5; color: var(--gs-muted); }

.ws-schema-test__remove {
  margin-left: 12px; padding: 4px 10px; border: 1px solid var(--gs-line);
  border-radius: 6px; background: transparent; color: var(--gs-muted); font-size: 11.5px;
}
.ws-schema-test__remove:hover { border-color: var(--gs-burgundy); color: var(--gs-burgundy); }
.ws-schema-test__remove:focus-visible { outline: 2px solid rgb(126 34 48 / .48); outline-offset: 2px; }

/* -- Semantic identity, in the graph inspector ---------------------------
   The inspector named a concept and drew its neighbourhood but never
   showed what the product actually governs: the globally addressable URI
   and the vocabulary the term was born into.

   "Applies to" rather than "Domain": `rdfs:domain` and `skema:birthDomain`
   are two different things and putting "Domain: Team" next to "Defined in:
   Sports" reads as a contradiction to anyone who has not memorised RDFS. */
.sg-ident {
  margin: 12px 0 4px; padding: 12px 0; display: grid; gap: 10px;
  border-top: 1px solid var(--gs-soft-line); border-bottom: 1px solid var(--gs-soft-line);
}
.sg-ident__k {
  font-family: var(--gs-mono); font-size: 9px; font-weight: 600;
  letter-spacing: .16em; text-transform: uppercase; color: var(--gs-muted);
}
.sg-ident__uri {
  display: block; font-family: var(--gs-mono); font-size: 10.5px;
  line-height: 1.5; color: var(--gs-ink); word-break: break-all;
  /* selectable on purpose: this is the thing a reader copies into a
     query, an export or another system */
  user-select: all;
}
.sg-ident__slot:empty { display: none; }
.sg-ident__slot { display: grid; gap: 8px; }
.sg-ident__row { display: grid; gap: 3px; }
.sg-ident__v { font-size: 12.5px; color: var(--gs-ink); display: flex; flex-wrap: wrap; gap: 5px; }
.sg-ident__ref {
  font-family: var(--gs-mono); font-size: 10.5px; color: var(--gs-burgundy);
  background: #f7eff0; border-radius: 4px; padding: 1px 6px;
}
.sg-ident__list { display: grid; gap: 4px; }
.sg-ident__inst {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; padding: 4px 0; border-top: 1px solid var(--gs-soft-line);
}
.sg-ident__inst:first-child { border-top: 0; }
.sg-ident__inst-l { font-size: 12.5px; color: var(--gs-ink); }
.sg-ident__more { font-family: var(--gs-mono); font-size: 10px; color: var(--gs-muted); }

/* -- Browse by vocabulary -----------------------------------------------
   Explore's resting state was a sentence telling you to type. A reader who
   does not know what the schema CONTAINS cannot search it.

   Classes and properties are two lists under one domain heading, never one
   nested in the other: `rdfs:domain` is what relates them, and indentation
   would assert an ownership the RDF does not have. */
.ws-vocab { display: grid; gap: clamp(22px, 2.6vw, 36px); }
.ws-vocab__lede {
  margin: 0; max-width: 68ch; font-size: 13px; line-height: 1.6; color: var(--gs-copy);
}
.ws-vocab__domain { display: grid; gap: 14px; }
.ws-vocab__head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 14px; padding-bottom: 9px; border-bottom: 1px solid var(--gs-line);
}
.ws-vocab__name {
  margin: 0; font-size: 15px; font-weight: 600; letter-spacing: .02em;
  text-transform: uppercase; color: var(--gs-ink);
}
.ws-vocab__count { font-family: var(--gs-mono); font-size: 10.5px; color: var(--gs-muted); }
.ws-vocab__part { display: grid; gap: 9px; }
/* the two kinds are peers - same weight, same indent, one heading each */
.ws-vocab__kind {
  font-family: var(--gs-mono); font-size: 9.5px; font-weight: 600;
  letter-spacing: .16em; text-transform: uppercase; color: var(--gs-burgundy);
}
.ws-vocab__list { display: grid; gap: 8px; }
@media (min-width: 900px) {
  .ws-vocab__list { grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); }
}

/* -- taxonomy tree ------------------------------------------------------
 *
 * Indentation is a CSS variable rather than nested markup: the tree is
 * flattened to one row per concept so it stays scannable at ten thousand
 * rows, and a nested DOM at that size is what makes a hierarchy view slow.
 * Every row names its parent in words as well, because indentation is the
 * first thing lost to a narrow screen. */
.ws-tax__tree { display: flex; flex-direction: column; }
.ws-tax__row {
  display: grid;
  grid-template-columns: auto 1fr auto auto;
  align-items: baseline;
  gap: 10px;
  padding: 7px 0 7px calc(var(--tax-depth, 0) * 20px);
  border-bottom: 1px solid var(--gs-soft-line);
}
.ws-tax__row:last-child { border-bottom: 0; }
.ws-tax__twig { color: var(--gs-line-2, #cfc7ba); font-family: var(--gs-mono); font-size: 11px; }
.ws-tax__label { font-size: 14px; color: var(--gs-ink); }
.ws-tax__parent { font-size: 12px; color: var(--gs-muted); white-space: nowrap; }
.ws-tax__domain {
  font-family: var(--gs-mono); font-size: 10.5px; color: var(--gs-muted);
  white-space: nowrap;
}
.ws-tax__flag {
  grid-column: 1 / -1;
  font-family: var(--gs-mono); font-size: 10.5px;
  color: var(--gs-amber); letter-spacing: .04em;
}
.ws-tax__row.is-orphaned { background: #fbf6ec; }
/* A retired concept is kept in the tree and must not read as live. Muted
   and struck rather than hidden: deprecate-never-delete means the term
   still resolves, so the row stays and says what it is. */
.ws-tax__row.is-retired .ws-tax__label {
  color: var(--gs-muted); text-decoration: line-through;
}
.ws-tax__flag.is-retired { color: var(--gs-muted); }
/* A cross-listing: the same concept, shown under a second parent. Muted
   so the primary placement reads as the canonical one. */
.ws-tax__row.is-secondary .ws-tax__label { color: var(--gs-muted); }
.ws-tax__flag.is-secondary { color: var(--gs-muted); }

/* The read-back of a spreadsheet, before anything is minted. Scrolls in
   its own box: a four-hundred-row hierarchy must not push the button
   that imports it off the screen. */
.ws-tax__preview {
  max-height: 260px; overflow-y: auto; margin-top: 10px;
  border: 1px solid var(--gs-line); border-radius: 8px; padding: 8px 10px;
}
.ws-tax__preview-row { padding: 2px 0; }
.ws-tax__preview-path {
  font-family: var(--gs-mono); font-size: 11.5px; color: var(--gs-ink);
}

@media (max-width: 720px) {
  .ws-tax__row { grid-template-columns: auto 1fr; }
  .ws-tax__domain { display: none; }
}
.ws-tax__move {
  border: 1px solid var(--gs-line);
  border-radius: 6px;
  background: var(--gs-paper);
  color: var(--gs-muted);
  font-family: inherit;
  font-size: 11.5px;
  padding: 3px 9px;
  cursor: pointer;
  opacity: 0;                       /* revealed on hover/focus, see below */
  transition: opacity .14s ease;
}
/* The row is the thing being read; the control appears when it is wanted.
   Always visible on touch, where there is no hover to reveal it. */
.ws-tax__row:hover .ws-tax__move,
.ws-tax__move:focus-visible { opacity: 1; }
.ws-tax__move:hover { border-color: var(--gs-burgundy); color: var(--gs-burgundy); }
@media (hover: none) { .ws-tax__move { opacity: 1; } }
.ws-tax__result:empty { display: none; }

/* -- discovered structures ------------------------------------------------- *
 *
 * The one screen where the product says "I found something" about a schema
 * nobody wrote. The visual job is to make the STRUCTURE legible - a subject
 * reaching a mediator inbound, attributes hanging off it - while never
 * letting it look settled. Hence: the same node/edge vocabulary the
 * relationship rows use, and two explicit unfinished states (`is-unnamed`,
 * `is-ungoverned`) drawn dashed rather than solid. A dashed box is a box
 * somebody still has to close.
 */
.ws-pattern .ws-ops-stats { padding: 12px 20px 0; }
/* The section itself is edge-to-edge (it is a `ws-rels` panel), so every
   direct child that holds prose or controls carries the inset. The
   evidence and confirm panels are last because they are appended at
   runtime - without them the drafted cards rendered flush against the
   left edge of the card, under a heading that was not. */
.ws-pattern > .ws-subject__desc,
.ws-pattern > .ws-ops-fineprint,
.ws-pattern > .ws-ops-notes,
.ws-pattern > .ws-ask__form,
.ws-pattern > div > .ws-pattern__confirm,
.ws-pattern > div > .dc-card,
.ws-pattern > div > .ws-ops-callout,
.ws-pattern > div > .ws-ops-notes,
.ws-pattern > div > .ws-ops-fineprint,
.ws-pattern > div > .ws-ops-empty { padding-left: 20px; padding-right: 20px; }
.ws-pattern > .ws-ask__form { padding-bottom: 4px; }

.ws-pattern__flow {
  margin: 14px 20px;
  padding: 14px 16px;
  border: 1px solid var(--gs-soft-line);
  border-radius: 9px;
  background: var(--gs-panel);
}
.ws-pattern__spine {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}
/* NOT YET NAMED and NOT YET GOVERNED, drawn as what they are. A solid box
   here would read as a term that exists. */
.ws-rel__node.is-unnamed,
.ws-rel__node.is-ungoverned {
  border-style: dashed;
  background: transparent;
  color: var(--gs-muted);
}
.ws-pattern__unnamed {
  color: var(--gs-muted);
  font-family: var(--gs-mono);
  font-size: 11.5px;
  font-style: italic;
}
.ws-pattern__attrs {
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin: 10px 0 0 22px;
  padding-left: 14px;
  border-left: 1px solid var(--gs-line);
}
.ws-pattern__attr {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}
.ws-pattern__branch {
  color: var(--gs-muted);
  font-family: var(--gs-mono);
  font-size: 11px;
}
/* The support count is the number a reviewer scans for, so it is the one
   thing on the row set in the ink colour rather than the muted one. */
.ws-pattern__share {
  min-width: 34px;
  color: var(--gs-ink);
  font-family: var(--gs-mono);
  font-size: 11.5px;
  font-weight: 600;
}
.ws-pattern__toggle {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--gs-copy);
  font-size: 12.5px;
}
.ws-pattern__confirm {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-top: 12px;
}
@media (max-width: 720px) {
  .ws-pattern__attrs { margin-left: 6px; }
}
.ws-pattern__pickers {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  padding: 4px 20px;
}
.ws-pattern__field {
  display: flex;
  flex: 1 1 260px;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}
.ws-pattern__field-label {
  color: var(--gs-muted);
  font-family: var(--gs-mono);
  font-size: 10.5px;
  letter-spacing: .03em;
  text-transform: uppercase;
}
.ws-pattern__limits { padding: 4px 20px 0; }
.ws-pattern__limits-head {
  margin-bottom: 4px;
  color: var(--gs-muted);
  font-family: var(--gs-mono);
  font-size: 10.5px;
  letter-spacing: .03em;
  text-transform: uppercase;
}

/* ===================================================================
 * CHANGE INTELLIGENCE ON THE GRAPH
 *
 * CHANGE / CONTRIBUTION / IMPACT are lenses over the one canonical
 * graph, so nothing here introduces a second visual language: the
 * banner, the panel and the node outlines reuse the canvas's existing
 * tokens and only add the distinctions that carry new meaning.
 * =================================================================== */

/* The banner's tone is the change's STANDING, and the four are told
   apart by colour because "applied" and "rejected" are the two facts a
   reader must never confuse. Amber stays the default for a preview -
   something is pending - and the applied case goes quiet, because a
   committed change is not a warning. */
.ws-schema-graph__delta-banner { flex-wrap: wrap; }
.ws-schema-graph__delta-banner.is-applied {
  border-color: #cfe0d2; background: #f2f8f3; color: #35603f;
}
.ws-schema-graph__delta-banner.is-applied .ws-schema-graph__delta-dot { background: #4b8259; }
.ws-schema-graph__delta-banner.is-refused {
  border-color: #e7c6c6; background: #fdf2f2; color: #8a2a2a;
}
.ws-schema-graph__delta-banner.is-refused .ws-schema-graph__delta-dot { background: #a83a3a; }
.ws-schema-graph__delta-banner.is-draft,
.ws-schema-graph__delta-banner.is-empty {
  border-color: var(--gs-soft-line); background: var(--gs-paper); color: var(--gs-quiet);
}
.ws-schema-graph__delta-banner.is-draft .ws-schema-graph__delta-dot,
.ws-schema-graph__delta-banner.is-empty .ws-schema-graph__delta-dot { background: #9a938a; }
.ws-schema-graph__delta-error {
  flex-basis: 100%;
  font-size: 11.5px;
  opacity: .85;
}

/* Search sits with the change picker, in the page actions. */
.sg-search {
  min-width: 220px;
  padding: 6px 10px;
  border: 1px solid var(--gs-soft-line);
  border-radius: 7px;
  background: var(--gs-paper);
  color: var(--gs-copy);
  font-family: var(--gs-sans);
  font-size: 12px;
}
.sg-search__note {
  display: inline-flex;
  gap: 8px;
  align-items: center;
  color: var(--gs-quiet);
  font-size: 11.5px;
}

/* The empty-Direct explanation. Full width under the controls so it
   reads as an answer rather than as a tooltip on one of them. */
.sg-filter-note {
  flex-basis: 100%;
  max-width: 330px;
  padding: 2px 4px;
  color: var(--gs-quiet);
  font-size: 11px;
  line-height: 1.35;
}
.sg-select:disabled { opacity: .5; cursor: not-allowed; }

/* One row per changed term, marked with what happened to it. The bar on
   the left is the only colour: the row is a fact, not a warning. */
.sg-change__term {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: baseline;
  padding: 5px 0 5px 9px;
  border-left: 3px solid var(--gs-soft-line);
}
.sg-change__term.is-added { border-left-color: #4b8259; }
.sg-change__term.is-removed { border-left-color: #a83a3a; }
.sg-change__term.is-modified { border-left-color: #b57a18; }
.sg-change__kind,
.sg-change__pred {
  color: var(--gs-quiet);
  font-size: 11px;
}
.sg-change__pred { flex-basis: 100%; font-family: var(--gs-mono, monospace); }
.sg-inspector__absent { color: var(--gs-quiet); font-style: italic; }
.sg-chip.is-warn { border-color: #e7c6c6; background: #fdf2f2; color: #8a2a2a; }
.sg-link.is-active { font-weight: 600; text-decoration: underline; }
.sg-term-changes { margin-top: 4px; }

/* A mediator's second ring. Not a colour: colour already carries
   provenance on this canvas, and a seventh fill would take a channel
   that means something else. */
.sg-node__inner { opacity: .55; pointer-events: none; }
/* The anchor a focus ring is measured FROM, when that anchor is a set of
   changed terms rather than one clicked concept. */
.sg-node.is-anchor .sg-node__body { stroke-width: 2.6; }
.sg-change__group { margin-top: 2px; }

/* ===================================================================
 * HOME — the page as a narrative
 *
 * The overview was a hero, a metric band and six cards in a 12-column
 * grid: everything real, everything flat. These rules give it chapters
 * and a flow, in the same language the rest of the product already
 * speaks — the existing tokens, the existing card treatment, the
 * existing uppercase eyebrow. Nothing here restyles a card; the cards
 * are the same components in a different arrangement.
 * =================================================================== */

.ws-home__chapters {
  display: flex;
  flex-direction: column;
  gap: 58px;
}

.ws-home__chapter {
  display: grid;
  grid-template-columns: 210px minmax(0, 1fr);
  gap: 28px;
  align-items: start;
}

/* The heading column is a MARGIN NOTE, which is the editorial move this
   product's typography is already making elsewhere: the chapter names
   itself down the side and the substance runs full-width beside it. */
.ws-home__chapter-head {
  position: sticky;
  top: 78px;
  display: flex;
  flex-direction: column;
  gap: 9px;
  padding-top: 2px;
}

.ws-home__chapter-eyebrow {
  display: flex;
  gap: 10px;
  align-items: baseline;
  color: var(--gs-ink);
  font-size: 12.5px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
}

/* The two-digit prefix is the nav rail's, deliberately: a reader who has
   seen the stage numbers already knows what it means here. */
.ws-home__chapter-num {
  color: var(--gs-burgundy);
  font-family: var(--gs-mono);
  font-size: 11px;
  letter-spacing: .04em;
}

.ws-home__chapter-lede {
  margin: 0;
  color: var(--gs-muted);
  font-size: 12.5px;
  line-height: 1.55;
}

.ws-home__chapter-link {
  color: var(--gs-burgundy);
  font-size: 12px;
  text-decoration: none;
}
.ws-home__chapter-link:hover { text-decoration: underline; }

.ws-home__chapter-body { min-width: 0; }

/* Two panels that answer two halves of one question. The existing cards
   are dropped in unchanged; only the column split is new. */
.ws-home__pair {
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
  gap: 18px;
  align-items: start;
}
.ws-home__pair-main, .ws-home__pair-side { min-width: 0; }

/* -- the journey ---------------------------------------------------- */

.ws-flow { position: relative; }

.ws-flow__steps {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 2px 0;
}

/* The connector runs from each node to the next, and stops at the end of
   its ROW. One absolute bar across the container covered the first row
   only — eight stages wrap to two rows at every width this grid uses, so
   the second row's nodes floated unconnected. */
.ws-flow__step::after {
  content: "";
  position: absolute;
  top: 13px;
  left: calc(50% + 11px);
  right: calc(-50% + 11px);
  height: 1px;
  background: var(--gs-line);
}
.ws-flow__steps > .ws-flow__step:nth-child(4n)::after,
.ws-flow__steps > .ws-flow__step:last-child::after { content: none; }

.ws-flow__step {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding: 0 12px 14px;
  color: inherit;
  text-decoration: none;
  border-radius: 10px;
  transition: background .18s, transform .18s;
}
.ws-flow__step:hover { background: var(--gs-panel); transform: translateY(-1px); }
.ws-flow__step:focus-visible {
  outline: 2px solid rgb(126 34 48 / .5);
  outline-offset: 2px;
}

.ws-flow__node {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 27px;
}

/* Hollow by default, filled when the stage actually holds something. An
   empty stage is drawn, never hidden: "nothing is waiting here" is the
   answer, not the absence of one. */
.ws-flow__dot {
  width: 9px;
  height: 9px;
  border: 1px solid var(--gs-line);
  border-radius: 50%;
  background: var(--gs-bg);
  transition: background .18s, border-color .18s, box-shadow .18s;
}
.ws-flow__step.is-live .ws-flow__dot {
  border-color: var(--gs-burgundy);
  background: var(--gs-burgundy);
  box-shadow: 0 0 0 3px rgb(126 34 48 / .1);
}
.ws-flow__step:hover .ws-flow__dot { border-color: var(--gs-burgundy); }

.ws-flow__num {
  color: var(--gs-muted);
  font-family: var(--gs-mono);
  font-size: 10px;
  letter-spacing: .04em;
}
.ws-flow__name { color: var(--gs-ink); font-size: 13px; font-weight: 500; }
.ws-flow__blurb {
  color: var(--gs-muted);
  font-size: 11.5px;
  line-height: 1.4;
}
.ws-flow__figure {
  color: var(--gs-copy);
  font-family: var(--gs-mono);
  font-size: 11px;
}
.ws-flow__step.is-live .ws-flow__figure { color: var(--gs-burgundy); }
.ws-flow__cta { color: var(--gs-burgundy); font-size: 11.5px; }

.ws-flow__foot {
  margin: 18px 0 0;
  max-width: 62ch;
  color: var(--gs-muted);
  font-size: 11.5px;
  line-height: 1.55;
}

/* -- arrival -------------------------------------------------------- */

/* Applied by script only, and only when the reader has not asked to be
   left alone. Everything is visible without it. */
.ws-home__chapter.is-waiting {
  opacity: 0;
  transform: translateY(10px);
}
.ws-home__chapter {
  transition: opacity .5s ease, transform .5s ease;
}

@media (prefers-reduced-motion: reduce) {
  .ws-home__chapter,
  .ws-flow__step,
  .ws-flow__dot { transition: none; }
  .ws-home__chapter.is-waiting { opacity: 1; transform: none; }
}

/* -- narrower ------------------------------------------------------- */

@media (max-width: 1180px) {
  .ws-flow__steps { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px 0; }
  /* Two columns: the row ends every second step, not every fourth. */
  .ws-flow__steps > .ws-flow__step:nth-child(4n)::after { content: ""; }
  .ws-flow__steps > .ws-flow__step:nth-child(2n)::after { content: none; }
}

@media (max-width: 1080px) {
  .ws-home__chapter { grid-template-columns: 1fr; gap: 14px; }
  /* Stuck to the top of a one-column page, the heading would cover the
     content it names. */
  .ws-home__chapter-head { position: static; }
  .ws-home__chapter-head { flex-flow: row wrap; align-items: baseline; gap: 8px 14px; }
  .ws-home__chapter-lede { flex-basis: 100%; }
  .ws-home__pair { grid-template-columns: 1fr; }
  .ws-home__chapters { gap: 42px; }
}

@media (max-width: 680px) {
  .ws-flow__steps { grid-template-columns: 1fr; gap: 4px; }
  /* One column: the steps follow one another down the page and a
     horizontal connector would point at nothing. */
  .ws-flow__steps > .ws-flow__step::after { content: none; }
  .ws-flow__step {
    display: grid;
    grid-template-columns: 27px 34px minmax(0, 1fr);
    gap: 2px 10px;
    align-items: baseline;
    padding: 8px 8px 10px;
  }
  /* One column: the flow reads down the page, so the marker and the
     number take a gutter and the words run beside them. */
  .ws-flow__node { grid-row: 1 / 3; justify-content: flex-start; }
  .ws-flow__num { grid-row: 1 / 3; }
  .ws-flow__blurb, .ws-flow__figure, .ws-flow__cta { grid-column: 3; }
  .ws-home__chapters { gap: 34px; }
}

/* -- Library: importing a SUBSET of a vocabulary --------------------------
 *
 * The Library could take a vocabulary whole or one term at a time. FOAF
 * has 75 terms; schema.org has thousands. The middle case is the one a
 * real adoption needs, and `/api/library/import-terms` had served it,
 * tested and metered, with no screen.
 *
 * Collapsed by default: a card that unrolled 75 checkboxes would bury the
 * catalogue it sits in. */
.ws-library-subset { margin-top: 12px; }
.ws-library-subset__head {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
  margin: 12px 0 8px;
}
.ws-library-subset__filter { flex: 1 1 200px; min-width: 0; }
.ws-library-subset__count {
  color: var(--gs-muted);
  font-family: var(--gs-mono);
  font-size: 11px;
}
/* Capped and scrolled. A vocabulary's term list is not a page. */
.ws-library-subset__list {
  max-height: 300px;
  overflow-y: auto;
  border: 1px solid var(--gs-soft-line);
  border-radius: 10px;
  background: var(--gs-paper);
}
.ws-library-subset__row {
  display: grid;
  grid-template-columns: 18px minmax(0, 1fr) auto;
  gap: 4px 10px;
  align-items: baseline;
  padding: 7px 11px;
  border-bottom: 1px solid var(--gs-soft-line);
  cursor: pointer;
}
.ws-library-subset__row:last-child { border-bottom: 0; }
.ws-library-subset__row:hover { background: var(--gs-panel); }
.ws-library-subset__label { color: var(--gs-ink); font-size: 12.5px; }
.ws-library-subset__kind {
  color: var(--gs-muted);
  font-family: var(--gs-mono);
  font-size: 10.5px;
}
/* The URI is the identity and stays available, but it is not the name:
   same rule the graph follows. */
.ws-library-subset__uri {
  grid-column: 2 / -1;
  overflow: hidden;
  color: var(--gs-muted);
  font-family: var(--gs-mono);
  font-size: 10px;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ws-library-subset__outcome:not(:empty) { margin-top: 10px; }
.ws-library-subset__summary {
  margin: 0 0 6px;
  color: var(--gs-ink);
  font-size: 12px;
}
.ws-library-subset__results {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
/* One row per requested term, because the route answers per term: with
   twenty selected, "some were already adopted" is the answer and WHICH
   ones is the useful half. */
.ws-library-subset__result {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 10px;
  align-items: baseline;
  padding: 4px 0 4px 9px;
  border-left: 3px solid var(--gs-soft-line);
}
.ws-library-subset__result.is-made { border-left-color: var(--gs-green); }
.ws-library-subset__result.is-dupe { border-left-color: var(--gs-amber); }
.ws-library-subset__result.is-error { border-left-color: var(--gs-danger); }
.ws-library-subset__note { color: var(--gs-muted); font-size: 11px; }

@media (max-width: 680px) {
  .ws-library-subset__row { grid-template-columns: 18px minmax(0, 1fr); }
  .ws-library-subset__kind { grid-column: 2; }
}

/* Checkboxes and radios take the brand colour.
 *
 * `accent-color` is one line and it is the whole fix: without it every
 * checkbox in the product is drawn in the operating system's blue, which
 * is the same category of problem as an emoji glyph among line icons —
 * a control the platform paints in a palette this interface does not
 * use. Nothing else about the control changes, so the native focus ring,
 * keyboard behaviour and indeterminate state are all untouched; this is
 * deliberately NOT a re-implementation of the control the way
 * `selects.js` had to be for the open list. */
input[type="checkbox"],
input[type="radio"] { accent-color: var(--gs-burgundy); }

/* How widely a property is already used, beside the property.
   "reuse beats invention" is the rule the product is built on, and until
   now the canvas could not say whether a term was reused at all. */
.sg-prop-reuse-slot { margin-left: auto; }
.sg-prop-reuse {
  color: var(--gs-muted);
  font-family: var(--gs-mono);
  font-size: 10px;
  white-space: nowrap;
}

/* -- Author: a class and its properties as ONE proposal -------------------
 *
 * The gauntlet resolves a referent inside the diff it is given, so a
 * property whose class is not applied yet cannot validate alone. Measured
 * while governing the Purchase structure through this product: four
 * property cards came back `rejected` until their two classes had been
 * applied first. `/api/authoring/terms` co-diffs them; this is its
 * screen. */
.dc-batch { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--gs-soft-line); }
.dc-batch__list:not(:empty) { margin: 10px 0; }
.dc-batch__row {
  display: flex;
  gap: 10px;
  align-items: baseline;
  justify-content: space-between;
  padding: 5px 0 5px 9px;
  border-left: 3px solid var(--gs-soft-line);
}
.dc-batch__what { color: var(--gs-ink); font-size: 12.5px; }
.dc-batch__note { color: var(--gs-muted); font-size: 11px; }
.dc-batch__outcome:not(:empty) { margin-top: 12px; }
.dc-batch__summary { margin: 0 0 6px; color: var(--gs-ink); font-size: 12px; }
.dc-batch__results { display: flex; flex-direction: column; gap: 2px; }
.dc-batch__result {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 10px;
  align-items: baseline;
  padding: 4px 0 4px 9px;
  border-left: 3px solid var(--gs-soft-line);
}
.dc-batch__result.is-made { border-left-color: var(--gs-green); }
.dc-batch__result.is-bad { border-left-color: var(--gs-danger); }
.dc-batch__attach { font-family: var(--gs-mono); font-size: 10.5px; }


/* ── Finish joining: the screen a held account lands on ───────────────────
   Narrow and centred, with nothing else on it. Somebody halfway through
   joining has exactly one thing to do, and a page offering a second thing
   would be offering a distraction from it. */
.ws-rotate-page { display: flex; justify-content: center; padding: 72px 24px; }
.ws-rotate {
  width: 100%;
  max-width: 460px;
  background: var(--gs-paper);
  border: 1px solid var(--gs-line);
  border-radius: var(--gs-radius);
  padding: 34px 34px 30px;
}
.ws-rotate__eyebrow {
  margin: 0 0 10px;
  font-size: 10.5px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--gs-burgundy);
}
.ws-rotate__title {
  margin: 0 0 12px;
  font-family: var(--gs-serif, Georgia, serif);
  font-size: 27px;
  line-height: 1.15;
  color: var(--gs-ink);
}
.ws-rotate__body {
  margin: 0 0 24px;
  font-size: 14px;
  line-height: 1.6;
  color: var(--gs-copy);
}
.ws-rotate__form { display: flex; flex-direction: column; gap: 14px; }
.ws-rotate__note {
  margin: 16px 0 0;
  min-height: 18px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--gs-burgundy);
}


/* ── Organization: membership, invitations, verification ──────────────────
   Sits above the roster on People & access, because it answers the wider
   question the roster answers a part of: who is in this organization at
   all, and on what standing. Same restrained card language as the panels
   around it -- this is administration, not a dashboard. */
.ws-org { display: flex; flex-direction: column; gap: 14px; }
.ws-org__head {
  display: flex;
  flex-wrap: wrap;
  gap: 14px 22px;
  align-items: center;
  justify-content: space-between;
  padding: 14px 16px;
  border: 1px solid var(--gs-line);
  border-radius: var(--gs-radius);
  background: var(--gs-panel);
}
/* State is the one thing on this panel that changes what everybody else
   can do, so it is the one thing that carries colour. */
.ws-org__state {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  padding: 3px 9px;
  border: 1px solid currentColor;
  border-radius: 2px;
  color: var(--gs-muted);
}
.ws-org__state.is-verified { color: var(--gs-green); }
.ws-org__state.is-pending,
.ws-org__state.is-verification_required { color: var(--gs-amber); }
.ws-org__state.is-suspended,
.ws-org__state.is-disabled { color: var(--gs-danger); }
.ws-org__state-note {
  display: block;
  margin-top: 6px;
  font-size: 12.5px;
  color: var(--gs-copy);
}
.ws-org__verify { display: flex; gap: 8px; align-items: center; }
.ws-org__mine { margin: 0; font-size: 13px; color: var(--gs-copy); }
.ws-org__subtitle {
  margin: 0 0 8px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--gs-muted);
}
.ws-org__invite { padding-top: 4px; }
.ws-org__invite-form {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr) auto;
  gap: 8px;
  align-items: center;
}
/* The one-time link. Monospace and selectable, because the person has to
   copy it accurately and nothing will send it for them. */
.ws-org__link { display: flex; flex-direction: column; gap: 6px; }
.ws-org__link:empty { display: none; }
.ws-org__link-label { font-size: 12px; color: var(--gs-amber); }
.ws-org__link-value {
  font-family: var(--gs-mono);
  font-size: 11.5px;
  line-height: 1.5;
  word-break: break-all;
  padding: 9px 11px;
  border: 1px solid var(--gs-line);
  border-radius: 6px;
  background: var(--gs-panel);
  color: var(--gs-ink);
  user-select: all;
}
.ws-org__list { display: flex; flex-direction: column; }
.ws-org__row {
  display: grid;
  grid-template-columns: minmax(0, 2fr) 90px 110px auto;
  gap: 12px;
  align-items: center;
  padding: 9px 0;
  border-top: 1px solid var(--gs-soft-line);
  font-size: 13px;
}
.ws-org__who { color: var(--gs-ink); overflow: hidden; text-overflow: ellipsis; }
.ws-org__role { color: var(--gs-copy); }
.ws-org__member-state { font-size: 11.5px; color: var(--gs-muted); }
.ws-org__member-state.is-active { color: var(--gs-green); }
.ws-org__member-state.is-suspended,
.ws-org__member-state.is-removed,
.ws-org__member-state.is-revoked { color: var(--gs-danger); }
.ws-org__member-state.is-pending { color: var(--gs-amber); }
.ws-org__actions { display: flex; gap: 6px; justify-content: flex-end; }

@media (max-width: 720px) {
  .ws-org__invite-form { grid-template-columns: minmax(0, 1fr); }
  .ws-org__row { grid-template-columns: minmax(0, 1fr); gap: 4px; }
  .ws-org__actions { justify-content: flex-start; }
}


/* ── The bell ─────────────────────────────────────────────────────────────
   Hidden when nothing is unread. A permanent bell reading zero is
   furniture; one that appears when there is something to see is a
   signal. */
.gs-bell {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 1px solid var(--gs-line);
  border-radius: 8px;
  background: var(--gs-paper);
  color: var(--gs-ink);
  cursor: pointer;
}
.gs-bell[hidden] { display: none; }
.gs-bell:hover { border-color: var(--gs-burgundy); color: var(--gs-burgundy); }
.gs-bell:focus-visible {
  outline: 3px solid rgb(126 34 48 / 24%);
  outline-offset: 2px;
}
.gs-bell__icon { width: 15px; height: 15px; }
.gs-bell__count {
  position: absolute;
  top: -5px;
  right: -5px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 8px;
  background: var(--gs-burgundy);
  color: #fffdfa;
  font-size: 10px;
  font-weight: 600;
  line-height: 16px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

/* ── The notification centre ──────────────────────────────────────────────
   Grouped by WHEN, because somebody opening this is catching up and that
   is a question about time. Category is a mark on the row instead, since
   it changes what to DO rather than where the row belongs. */
.ws-notes { display: flex; flex-direction: column; gap: 18px; }
.ws-notes__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--gs-line);
}
.ws-notes__count { font-size: 13px; color: var(--gs-copy); }
.ws-notes__list { display: flex; flex-direction: column; gap: 22px; }
.ws-notes__group { display: flex; flex-direction: column; gap: 8px; }
.ws-notes__group-title {
  margin: 0 0 2px;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .11em;
  text-transform: uppercase;
  color: var(--gs-muted);
}
.ws-notes__empty { font-size: 13px; color: var(--gs-muted); line-height: 1.6; }

.ws-note {
  display: flex;
  gap: 14px;
  align-items: flex-start;
  justify-content: space-between;
  padding: 13px 15px;
  border: 1px solid var(--gs-line);
  border-left: 3px solid var(--gs-muted);
  border-radius: 0 var(--gs-radius) var(--gs-radius) 0;
  background: var(--gs-paper);
}
/* Read is quieter, never hidden: an inbox that hides what you have seen
   makes it impossible to find the thing you read and meant to act on. */
.ws-note.is-read { background: transparent; opacity: .72; }
.ws-note.is-act { border-left-color: var(--gs-burgundy); }
.ws-note.is-warn { border-left-color: var(--gs-amber); }
.ws-note.is-secure { border-left-color: var(--gs-danger); }
.ws-note.is-good { border-left-color: var(--gs-green); }
.ws-note.is-info { border-left-color: var(--gs-line); }
.ws-note__body { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.ws-note__head { display: flex; flex-wrap: wrap; gap: 10px; align-items: baseline; }
.ws-note__title { font-size: 14px; color: var(--gs-ink); }
.ws-note__category {
  font-size: 10px;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--gs-muted);
}
.ws-note.is-act .ws-note__category { color: var(--gs-burgundy); }
.ws-note.is-secure .ws-note__category { color: var(--gs-danger); }
.ws-note__detail { margin: 0; font-size: 12.5px; line-height: 1.55; color: var(--gs-copy); }
.ws-note__when { font-size: 11.5px; color: var(--gs-muted); }
.ws-note__actions { display: flex; gap: 6px; flex-shrink: 0; }

@media (max-width: 640px) {
  .ws-note { flex-direction: column; }
  .ws-note__actions { width: 100%; }
}

.ws-notes__prefs { display: flex; flex-direction: column; gap: 2px; }
.ws-notes__pref {
  display: grid;
  grid-template-columns: 140px 1fr auto;
  gap: 14px;
  align-items: center;
  padding: 9px 0;
  border-top: 1px solid var(--gs-soft-line);
  font-size: 13px;
}
.ws-notes__pref-name { color: var(--gs-ink); text-transform: capitalize; }
.ws-notes__pref-toggle {
  display: inline-flex;
  gap: 7px;
  align-items: center;
  color: var(--gs-copy);
  cursor: pointer;
}
.ws-notes__pref-box { accent-color: var(--gs-burgundy); }
.ws-notes__pref-locked { font-size: 12px; color: var(--gs-muted); }
.ws-notes__pref-email { font-size: 11.5px; color: var(--gs-muted); }
@media (max-width: 720px) {
  .ws-notes__pref { grid-template-columns: 1fr; gap: 4px; }
}


/* ── Switching organizations ──────────────────────────────────────────────
   Drawn only when somebody belongs to more than one. The blocked rows
   carry the server's reason rather than a greyed button, because
   "awaiting approval" and "suspended" need different actions from the
   person reading them. */
.ws-switch { display: flex; flex-direction: column; gap: 4px; }
.ws-switch__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 90px auto;
  gap: 12px;
  align-items: center;
  padding: 10px 0;
  border-top: 1px solid var(--gs-soft-line);
  font-size: 13px;
}
.ws-switch__name { color: var(--gs-ink); overflow: hidden; text-overflow: ellipsis; }
.ws-switch__role { color: var(--gs-copy); }
.ws-switch__here { font-size: 11.5px; color: var(--gs-green); text-align: right; }
.ws-switch__blocked {
  font-size: 11.5px;
  color: var(--gs-amber);
  line-height: 1.45;
  text-align: right;
}
@media (max-width: 720px) {
  .ws-switch__row { grid-template-columns: minmax(0, 1fr); gap: 4px; }
  .ws-switch__here, .ws-switch__blocked { text-align: left; }
}

/* An invitation that actually went out. Green and quiet: nothing to copy,
   nothing to do, and no link shown because the token is already where it
   belongs and a second copy is a second thing to leak. */
.ws-org__link-sent { font-size: 12.5px; color: var(--gs-green); line-height: 1.5; }

/* Confirming your own address. Sits with the password panel because it
   is the same subject: your account, nobody else's. */
.ws-verify { display: flex; flex-direction: column; gap: 10px; }
.ws-verify__form { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.ws-verify__form .ws-settings-control { max-width: 150px; }
.ws-verify__note { margin: 0; min-height: 17px; font-size: 12.5px; color: var(--gs-copy); }
